Warmth shared by every pile hot enough

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-21 13:22:20 +01:00
parent b4ecacf96c
commit f7daa4e44d
3 changed files with 26 additions and 7 deletions

View file

@ -130,8 +130,9 @@ Alpha. One season, bandit raids plus two friendly events, six buildings.
so every log adds to how long it lasts and a full fire never shuts a so every log adds to how long it lasts and a full fire never shuts a
beginner out. Better logs burn hotter colours (red, gold, white, blue, beginner out. Better logs burn hotter colours (red, gold, white, blue,
green, violet), burn first in their pile, and push your own cheaper fuel out green, violet), burn first in their pile, and push your own cheaper fuel out
when your pile is full; the fire is as hot as its hottest pile. Whoever owns when your pile is full; the fire is as hot as its hottest pile. Warmth: 10%
that pile gets 10% of the XP of every craft the fire pays for. Because the fires are shared, the server runs of the XP of every craft the fire pays for, shared evenly by everyone whose
pile is hot enough for that craft on its own. Because the fires are shared, the server runs
everyone's actions as time passes, not only when they next look. everyone's actions as time passes, not only when they next look.
- **Events:** raids are announced a day or two ahead. Reconnaissance from - **Events:** raids are announced a day or two ahead. Reconnaissance from
scouts, informants and the spymaster's guild narrows down when, how strong, scouts, informants and the spymaster's guild narrows down when, how strong,

13
game.py
View file

@ -167,7 +167,7 @@ FIRES = {
FLAMES = [("Red", "#ff6a2a"), ("Gold", "#ffcb3d"), ("White", "#eeeaf8"), FLAMES = [("Red", "#ff6a2a"), ("Gold", "#ffcb3d"), ("White", "#eeeaf8"),
("Blue", "#4fa3ff"), ("Green", "#3ed37a"), ("Violet", "#b07cff")] ("Blue", "#4fa3ff"), ("Green", "#3ed37a"), ("Violet", "#b07cff")]
TEND_XP = [2, 3, 4.5, 5.5, 6.5, 7.5] # per game minute of fire fed, by the log's tier TEND_XP = [2, 3, 4.5, 5.5, 6.5, 7.5] # per game minute of fire fed, by the log's tier
WARMTH = 0.1 # of a craft's XP, to whoever fed the fuel that paid for it WARMTH = 0.1 # of a craft's XP, shared by everyone whose pile is hot enough for it
FIRE_LOW = 30 * 60 # game seconds left when a fire counts as low FIRE_LOW = 30 * 60 # game seconds left when a fire counts as low
@ -2139,12 +2139,15 @@ def fire_inputs(world: dict, action: dict) -> dict:
def _warm(world: dict, crafter: dict, action: dict, xp: float, now: float) -> None: def _warm(world: dict, crafter: dict, action: dict, xp: float, now: float) -> None:
"""A fire paid for a craft: count it, and give its feeder a share of the XP.""" """A fire paid for a craft: count it, and share the warmth between everyone whose
pile alone was hot enough for it (a beginner's logs count for bricks and copper)."""
fire = world["city"]["fires"][action["fire"]] fire = world["city"]["fires"][action["fire"]]
fire["saved"] = fire.get("saved", 0) + 1 fire["saved"] = fire.get("saved", 0) + 1
keeper = world["players"].get(burning(fire["fuel"])["by"]) keepers = [world["players"].get(by) for by in {chunk["by"] for chunk in fire["fuel"]}
if keeper and keeper is not crafter: if burning(fire["fuel"], by)["tier"] >= action["fuel"]]
gain_xp(world, keeper, "firekeeping", xp * WARMTH, now, keeper.setdefault("away", empty_summary())) keepers = [keeper for keeper in keepers if keeper and keeper is not crafter]
for keeper in keepers:
gain_xp(world, keeper, "firekeeping", xp * WARMTH / len(keepers), now, keeper.setdefault("away", empty_summary()))
def fires_view(world: dict, now: float) -> dict: def fires_view(world: dict, now: float) -> dict:

View file

@ -1953,6 +1953,21 @@ class FirekeepingTest(unittest.TestCase):
self.assertEqual(self.fires["kitchen"]["saved"], 4) self.assertEqual(self.fires["kitchen"]["saved"], 4)
self.assertEqual(self.sam["xp"]["firekeeping"], 0) self.assertEqual(self.sam["xp"]["firekeeping"], 0)
def test_warmth_is_shared_by_every_pile_hot_enough_for_the_craft(self):
self.world["players"]["ash"] = ash = game.new_player("ash", "Ash", "commoner", T0)
ash["xp"]["cooking"] = game.XP_TABLE[15]
self.fires["kitchen"].update(at=T0, fuel=[{"tier": 3, "left": 6000, "by": "kai"},
{"tier": 0, "left": 6000, "by": "sam"}])
ash["bank"] = {"raw_shrimp": 1, "raw_trout": 1}
game.start_action(self.world, ash, "cook_shrimp", T0, once=True)
game.process_player(self.world, ash, T0 + 25, random.Random(1))
self.assertAlmostEqual(self.kai["xp"]["firekeeping"], 6 * game.WARMTH / 2)
self.assertAlmostEqual(self.sam["xp"]["firekeeping"], 6 * game.WARMTH / 2)
game.start_action(self.world, ash, "cook_trout", T0 + 25, once=True)
game.process_player(self.world, ash, T0 + 60, random.Random(1))
self.assertAlmostEqual(self.kai["xp"]["firekeeping"], 6 * game.WARMTH / 2 + 15 * game.WARMTH) # trout: kai's alone
self.assertAlmostEqual(self.sam["xp"]["firekeeping"], 6 * game.WARMTH / 2)
def test_catch_up_runs_everyone_and_keeps_what_they_missed(self): def test_catch_up_runs_everyone_and_keeps_what_they_missed(self):
game.start_action(self.world, self.sam, "mine_stone", T0) game.start_action(self.world, self.sam, "mine_stone", T0)
self.assertTrue(game.catch_up(self.world, T0 + 100, random.Random(1))) self.assertTrue(game.catch_up(self.world, T0 + 100, random.Random(1)))