Intel: scaled recon costs, time-gated levels, spymaster multiplies recon

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 15:09:51 +01:00
parent 366a195c6d
commit 27bdb6a154
5 changed files with 113 additions and 33 deletions

View file

@ -343,21 +343,45 @@ class EventTest(unittest.TestCase):
self.assertLessEqual(view["earliest"], raid["fire_at"])
self.assertGreaterEqual(view["latest"], raid["fire_at"])
def test_spymaster_and_recon_sharpen_intel(self):
def test_recon_sharpens_intel_as_the_raid_draws_near(self):
world = world_with(("sam", "scout"))
raid = self.announce(world)
world["city"]["buildings"]["spymaster"]["level"] = 1
self.assertIn("size", game.event_view(world, raid, T0))
p = world["players"]["sam"]
p["xp"]["reconnaissance"] = game.XP_TABLE[20]
game.start_action(world, p, "recon_camps", T0)
game.process_player(world, p, T0 + 8 * 72, random.Random(1))
view = game.event_view(world, raid, T0)
game.process_player(world, p, T0 + 4 * game.HOUR, random.Random(1))
self.assertGreaterEqual(raid["recon"], game.intel_costs(raid)[-1])
# Plenty of scouting, but a day or more out only the first level is knowable.
view = game.event_view(world, raid, T0 + 4 * game.HOUR)
self.assertEqual(view["intel"], 1)
self.assertIn("size", view)
self.assertNotIn("strength", view)
self.assertEqual(view["recon_next"], 0)
late = raid["fire_at"] - 12 * game.HOUR
self.assertEqual(game.intel_level(world, raid, late), 2)
view = game.event_view(world, raid, raid["fire_at"] - game.HOUR)
self.assertEqual(view["intel"], 3)
self.assertEqual(view["exact"], raid["fire_at"])
self.assertIn("strength", view)
self.assertTrue(view["camp"])
def test_bigger_bands_take_more_scouting(self):
small = {"kind": "raid", "strength": 20}
huge = {"kind": "raid", "strength": 150}
dragon = {"kind": "dragon", "strength": 20}
self.assertEqual(game.intel_costs(small), [15, 45, 90])
self.assertEqual(game.intel_costs(huge), [30, 90, 180])
self.assertEqual(game.intel_costs(dragon), [8, 22, 45])
def test_the_spymasters_guild_boosts_and_gathers_recon(self):
world = world_with()
raid = self.announce(world)
action = game.ACTION_BY_ID["recon_roads"]
self.assertEqual(game.recon_gain(world, action), 1)
world["city"]["buildings"]["spymaster"]["level"] = 2
self.assertEqual(game.recon_gain(world, action), 1.5)
game.tick(world, T0 + 2 * game.HOUR, random.Random(2))
self.assertAlmostEqual(raid["recon"], 2.0)
def test_victory_rewards_garrison(self):
world = world_with(("wil", "warrior"))
raid = self.announce(world)
@ -404,9 +428,11 @@ class EventTest(unittest.TestCase):
p["hp"] = game.max_hp(p)
raid = self.announce(world)
self.assertEqual(game.start_action(world, p, "sabotage", T0), "No bandit camp has been located")
raid["recon"] = 3 * game.RECON_PER_INTEL
self.assertIsNone(game.start_action(world, p, "sabotage", T0))
game.process_player(world, p, T0 + 96 * 20, random.Random(1))
raid["recon"] = game.intel_costs(raid)[-1]
now = raid["fire_at"] - game.HOUR
world["city"]["last_tick"] = p["last_active"] = now
self.assertIsNone(game.start_action(world, p, "sabotage", now))
game.process_player(world, p, now + 96 * 20, random.Random(1))
self.assertGreater(raid["sabotage"], 0)
self.assertLessEqual(raid["sabotage"], 0.3)
@ -552,11 +578,12 @@ class FavourTest(unittest.TestCase):
self.assertEqual(game.spend_favour(world, p, "informant", T0), "No news to dig into")
world["next_event_at"] = T0
game.tick(world, T0, random.Random(2))
event = game.upcoming_event(world)
for level in (1, 2, 3):
event = game.upcoming_raid(world)
while event["recon"] < game.intel_costs(event)[-1]:
self.assertIsNone(game.spend_favour(world, p, "informant", T0))
self.assertEqual(game.intel_level(world, event), level)
self.assertEqual(event["recon"] % game.INFORMANT_RECON, 0)
self.assertEqual(game.spend_favour(world, p, "informant", T0), "Nothing more to learn")
self.assertEqual(game.intel_level(world, event, T0), 1) # the rest waits for the raid to draw near
def test_rallies_cost_more_each_time_and_end_with_the_raid(self):
world, p = self.world, self.p