Empty homes draw settlers: growth follows housing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 20:31:25 +01:00
parent 0b7e4a722f
commit 8fbab37a56
3 changed files with 33 additions and 7 deletions

View file

@ -35,7 +35,8 @@ Alpha. One season, bandit raids plus two friendly events, six buildings.
Construction also saws planks, which higher building levels need, and Construction also saws planks, which higher building levels need, and
builds fittings from them: one-off upgrades a building takes once it's high builds fittings from them: one-off upgrades a building takes once it's high
enough (shelving, a well pump, weapon racks, enchanted chests…). Population eats food, enough (shelving, a well pump, weapon racks, enchanted chests…). Population eats food,
pays taxes, and grows up to the housing cap; it promotes the settlement pays taxes, and fills the houses (empty homes draw settlers, about a
quarter of all homes a day, plus slow family growth); it promotes the settlement
(hamlet, village at 40, town at 90, city at 150), which raises building (hamlet, village at 40, town at 90, city at 150), which raises building
level caps and gives everyone bank slots. level caps and gives everyone bank slots.
- **Prayer:** enemies drop bones; burying them gives prayer points and - **Prayer:** enemies drop bones; burying them gives prayer points and

17
game.py
View file

@ -482,7 +482,10 @@ TIERS = [
{"name": "City", "pop": 150, "cap": 99, "houses": 8}, {"name": "City", "pop": 150, "cap": 99, "houses": 8},
] ]
TIER_SLOTS = 2 TIER_SLOTS = 2
GROWTH_PER_DAY = 0.12 # Growth: empty homes draw settlers (a share of all the homes, every day, while
# any stand empty), and families grow a little on their own.
SETTLERS_PER_HOME = 0.25
FAMILY_GROWTH = 0.05
RAID_GROUPS = ["the Red Hand", "the Hollow Crows", "the Blackwater gang", "the Ashen Wolves", RAID_GROUPS = ["the Red Hand", "the Hollow Crows", "the Blackwater gang", "the Ashen Wolves",
"the Gallows Men", "the Thornback raiders"] "the Gallows Men", "the Thornback raiders"]
@ -1431,6 +1434,13 @@ def _start_next(world: dict, now: float) -> None:
log(world, now, "build", f"{verb} the {BUILDINGS[key]['name'].lower()} began") log(world, now, "build", f"{verb} the {BUILDINGS[key]['name'].lower()} began")
def growth_per_day(world: dict) -> float:
"""New townsfolk a day while there's room: settlers for the homes, plus families."""
pop = world["city"]["population"]
growth = SETTLERS_PER_HOME * housing(world) + FAMILY_GROWTH * pop
return growth * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1)
def population_view(world: dict) -> dict: def population_view(world: dict) -> dict:
"""Population, food and tier, with the numbers that explain them.""" """Population, food and tier, with the numbers that explain them."""
city = world["city"] city = world["city"]
@ -1439,7 +1449,7 @@ def population_view(world: dict) -> dict:
if city["starving"]: if city["starving"]:
growth = -pop * 0.04 growth = -pop * 0.04
elif pop < room - 0.01: elif pop < room - 0.01:
growth = max(0.5, pop * GROWTH_PER_DAY) growth = growth_per_day(world)
elif pop > room: elif pop > room:
growth = -pop * 0.05 growth = -pop * 0.05
else: else:
@ -1619,8 +1629,7 @@ def _city_hour(world: dict, now: float) -> None:
else: else:
room = housing(world) room = housing(world)
if pop < room: if pop < room:
growth = max(0.5, pop * GROWTH_PER_DAY) * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1) city["population"] = min(room, pop + growth_per_day(world) / 24)
city["population"] = min(room, pop + growth / 24)
elif pop > room: elif pop > room:
city["population"] = max(room, pop - pop * 0.05 / 24) city["population"] = max(room, pop - pop * 0.05 / 24)
city["treasury"] += pop * 0.5 / 24 city["treasury"] += pop * 0.5 / 24

View file

@ -302,11 +302,26 @@ class CityTest(unittest.TestCase):
world = world_with() world = world_with()
city = world["city"] city = world["city"]
city["stockpile"] = {"bread": 5} city["stockpile"] = {"bread": 5}
city["population"] = 25.0 # a full town, so nobody new arrives
world["next_fortune_at"] = None # no lucky wagon of bread world["next_fortune_at"] = None # no lucky wagon of bread
game.tick(world, T0 + 4 * game.DAY, random.Random(1)) game.tick(world, T0 + 4 * game.DAY, random.Random(1))
self.assertTrue(city["starving"]) self.assertTrue(city["starving"])
self.assertNotIn("bread", city["stockpile"]) self.assertNotIn("bread", city["stockpile"])
self.assertLess(city["population"], 20) self.assertLess(city["population"], 25)
def test_empty_homes_draw_settlers(self):
world = world_with()
city = world["city"]
city["population"] = 22.0
city["buildings"]["houses"]["level"] = 2
city["stockpile"] = {"bread": 10_000}
self.assertAlmostEqual(game.growth_per_day(world), 0.25 * 65 + 0.05 * 22)
for _hour in range(24):
game._city_hour(world, T0)
self.assertGreater(city["population"], 38)
for _hour in range(48):
game._city_hour(world, T0)
self.assertEqual(city["population"], 65)
def test_fed_city_grows_to_housing_and_pays_tax(self): def test_fed_city_grows_to_housing_and_pays_tax(self):
world = world_with() world = world_with()
@ -899,8 +914,9 @@ class ConstructionTest(unittest.TestCase):
def test_townsfolk_trickle_and_levels_help(self): def test_townsfolk_trickle_and_levels_help(self):
world = self.site() world = self.site()
world["city"]["population"] = 25.0 # houses full, so the workforce stays put
game.tick(world, T0 + 10 * game.HOUR, random.Random(1)) game.tick(world, T0 + 10 * game.HOUR, random.Random(1))
self.assertAlmostEqual(world["city"]["construction"]["labour"], 10 * 20 * game.TOWNSFOLK_LABOUR, delta=5) self.assertAlmostEqual(world["city"]["construction"]["labour"], 10 * 25 * game.TOWNSFOLK_LABOUR, delta=5)
p = world["players"]["bob"] p = world["players"]["bob"]
self.assertEqual(game.labour_per_action(p), 1) self.assertEqual(game.labour_per_action(p), 1)
p["xp"]["construction"] = game.XP_TABLE[40] p["xp"]["construction"] = game.XP_TABLE[40]