Run 11: satiation and quarry return - clean two-year stability

Granary satiation: hunger scars motivate storage only until town
capacity could carry a winter (pop x 45); sprawl of ~10 granaries falls
to the sated level. Quarry return: packs stuck with undroppable stone
after rebuilds made starving porters; unneeded stone goes back to the
quarry (matter conserved, packs unclogged).

Also corrects the record: run 10's zero-ruin year-2 claim came from
CSV contamination by orphaned headless child processes (console wrapper
kills didn't kill the tree). Clean verified result now: year-1
catastrophe and rebuild, then year-2 at 0.77 nourishment, zero new
ruins, 100% condition, textbook granary sawtooth.

Open in FINDINGS: accessible stone exhaustion plateaus housing below
population - a Phase 3 economy question (deeper quarrying, stockpiles).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 15:55:39 -04:00
parent e661a3f739
commit 3ffde3d9e9
8 changed files with 6726 additions and 6437 deletions
+38 -4
View File
@@ -259,8 +259,10 @@ public class Npc
return MaterialKind.Food;
// 4.5. Hunger remembered, and nowhere to store against it:
// gather the surplus that will found the granary.
if (_hungerMemory && !TownHasFoodStorage(gm))
// gather the surplus that will found the granary — until the
// town's storage could carry a winter. Scars motivate; they
// shouldn't compel forever. Enough is a real quantity.
if (_hungerMemory && !TownHasFoodStorage(gm) && !TownStorageSated(gm))
return MaterialKind.Food;
// 5. Ambition: expansion modules for sound, stocked shelters.
@@ -298,6 +300,20 @@ public class Npc
return false;
}
/// <summary>
/// Enough is a real quantity: true when the town's total granary
/// capacity (built or building) could carry its people through a
/// winter (~45 food per resident). Past this, no new storage rises.
/// </summary>
private bool TownStorageSated(GameManager gm)
{
float capacity = 0f;
foreach (var b in gm.Buildings)
if (b.Town == HomeTown && b.Kind == BuildingKind.Granary && !b.IsRuined)
capacity += Building.FoodStockCap;
return capacity >= gm.TownPopulation(HomeTown) * 45f;
}
/// <summary>
/// When full but mostly carrying the wrong thing, set the excess down to
/// make room for what's actually needed. Lossy for now —
@@ -526,14 +542,32 @@ public class Npc
}
// Hunger remembered + a surplus with nowhere to put it → the
// barn goes where the harvest is. Founded by scarcity survived,
// not by abundance alone; another appears only when this fills.
else if (_hungerMemory &&
// not by abundance alone — and only until the town's storage
// could carry a winter. Enough is enough.
else if (_hungerMemory && !TownStorageSated(gm) &&
Inventory.TryGetValue(MaterialKind.Food, out float surplus) && surplus > 8f)
{
site = Building.NewGranary(Position + jitter);
site.Town = HomeTown;
gm.Buildings.Add(site);
}
// Nothing needs your stone: take it back to the quarry rather
// than carry a house on your back forever. Matter is conserved;
// packs unclog; the quarry is the town's bank.
else if (Inventory.TryGetValue(MaterialKind.Stone, out float stoneCarried) && stoneCarried > 5f)
{
var quarry = gm.World.FindNearestAny(MaterialKind.Stone, Position);
if (quarry == null) { Enter(NpcState.Socializing); return; }
Activity = "returning stone to the quarry";
MoveToward(quarry.Cell);
if (Position.DistanceTo(quarry.Cell) < ArriveDist)
{
quarry.Amount = Mathf.Min(quarry.MaxAmount, quarry.Amount + stoneCarried);
Inventory[MaterialKind.Stone] = 0f;
Enter(NpcState.Socializing);
}
return;
}
else { Enter(NpcState.Socializing); return; }
}