From f7daa4e44d840fb70e42bb62e252105e9fb93dc3 Mon Sep 17 00:00:00 2001 From: rope Date: Wed, 21 Oct 2026 13:22:20 +0100 Subject: [PATCH] Warmth shared by every pile hot enough Co-Authored-By: Claude Opus 5.5 --- README.md | 5 +++-- game.py | 13 ++++++++----- tests/test_game.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 01af2cf..e483a5f 100644 --- a/README.md +++ b/README.md @@ -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 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 - when your pile is full; the fire is as hot as its hottest pile. Whoever owns - that pile gets 10% of the XP of every craft the fire pays for. Because the fires are shared, the server runs + when your pile is full; the fire is as hot as its hottest pile. Warmth: 10% + 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. - **Events:** raids are announced a day or two ahead. Reconnaissance from scouts, informants and the spymaster's guild narrows down when, how strong, diff --git a/game.py b/game.py index 8aaeb7d..e33e226 100644 --- a/game.py +++ b/game.py @@ -167,7 +167,7 @@ FIRES = { FLAMES = [("Red", "#ff6a2a"), ("Gold", "#ffcb3d"), ("White", "#eeeaf8"), ("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 -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 @@ -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: - """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["saved"] = fire.get("saved", 0) + 1 - keeper = world["players"].get(burning(fire["fuel"])["by"]) - if keeper and keeper is not crafter: - gain_xp(world, keeper, "firekeeping", xp * WARMTH, now, keeper.setdefault("away", empty_summary())) + keepers = [world["players"].get(by) for by in {chunk["by"] for chunk in fire["fuel"]} + if burning(fire["fuel"], by)["tier"] >= action["fuel"]] + 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: diff --git a/tests/test_game.py b/tests/test_game.py index 7478ff9..320e152 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -1953,6 +1953,21 @@ class FirekeepingTest(unittest.TestCase): self.assertEqual(self.fires["kitchen"]["saved"], 4) 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): game.start_action(self.world, self.sam, "mine_stone", T0) self.assertTrue(game.catch_up(self.world, T0 + 100, random.Random(1)))