3e4e44c623
SPEC §5 built: three skill domains (forage/woodcraft/masonry) that pay off and grow by practice; proximity diffusion soul-calibrated by absorption x teaching-drive; and mortality - the piece that makes knowledge generational. Elders die (~150-230 days), everything unshared dies with them, a youth comes of age near knowing nothing. North soul (absorption 1.6, teaching 0.9) vs South (1.0, 0.3); East teaches no one. Two findings: (1) headless soaks were contaminated by orphaned child processes writing later runs' CSVs - fixed with StopAfterDays clean self-terminate; (2) passive diffusion barely helped - the North's real culture is the young seeking elders on purpose, so added deliberate teacher-seeking and youths now cradle beside the wisest elder. North now leads knowledge at every checkpoint, but a stable town approaches the ceiling regardless, so teaching is a recovery advantage not an equilibrium one. The crisis-recovery experiment is the real test, noted in FINDINGS as next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
219 lines
7.5 KiB
C#
219 lines
7.5 KiB
C#
using System.Collections.Generic;
|
|
using Godot;
|
|
|
|
namespace WorldSim;
|
|
|
|
public enum BuildingKind { Shelter, Granary }
|
|
|
|
/// <summary>
|
|
/// Modular buildings (SPEC §6): physically-sequential construction
|
|
/// (foundation → walls → roof) with multi-NPC material delivery.
|
|
///
|
|
/// Buildings are the world's metabolism:
|
|
/// Shelters — burn firewood daily, decay into ruins if neglected, and
|
|
/// accept expansion modules that permanently raise their burn.
|
|
/// Granary — the village's winter insurance: stores food surplus in the
|
|
/// abundant seasons, feeds the hungry when the land gives nothing.
|
|
/// Its walls are clay — the first real use for it.
|
|
/// </summary>
|
|
public class Building
|
|
{
|
|
public BuildingKind Kind = BuildingKind.Shelter;
|
|
public Vector2 Site;
|
|
|
|
/// <summary>Which settlement this building belongs to.</summary>
|
|
public string Town = "";
|
|
|
|
public static Building NewShelter(Vector2 site) => new()
|
|
{
|
|
Kind = BuildingKind.Shelter,
|
|
Site = site,
|
|
Stages = new()
|
|
{
|
|
new("foundation", MaterialKind.Stone, 10f),
|
|
new("walls", MaterialKind.Wood, 20f),
|
|
new("roof", MaterialKind.Wood, 10f),
|
|
},
|
|
};
|
|
|
|
public static Building NewGranary(Vector2 site) => new()
|
|
{
|
|
Kind = BuildingKind.Granary,
|
|
Site = site,
|
|
Stages = new()
|
|
{
|
|
new("foundation", MaterialKind.Stone, 10f),
|
|
new("walls", MaterialKind.Clay, 25f),
|
|
new("roof", MaterialKind.Wood, 10f),
|
|
},
|
|
};
|
|
|
|
// --- Construction ----------------------------------------------------
|
|
|
|
public record Stage(string Name, MaterialKind Material, float Cost);
|
|
|
|
public List<Stage> Stages = new();
|
|
|
|
public int CurrentStage;
|
|
public float Delivered; // toward the current stage's cost
|
|
public bool IsComplete => CurrentStage >= Stages.Count;
|
|
|
|
public MaterialKind? NeededMaterial => IsComplete ? null : Stages[CurrentStage].Material;
|
|
|
|
public float StageProgress =>
|
|
IsComplete ? 1f : Delivered / Stages[CurrentStage].Cost;
|
|
|
|
// --- Shelter: upkeep -------------------------------------------------
|
|
|
|
public const float UpkeepStockCap = 10f;
|
|
|
|
public float UpkeepStock; // delivered wood reserve
|
|
public float Condition = 1f; // 1 = sound; 0 = ruin
|
|
public bool IsRuined => Condition <= 0f;
|
|
|
|
/// <summary>Base burn plus half a unit per expansion module, forever.</summary>
|
|
public float DailyBurn => Kind == BuildingKind.Shelter ? 2f + Modules * 0.5f : 0f;
|
|
|
|
/// <summary>True when this shelter wants a firewood delivery.</summary>
|
|
public bool WantsUpkeepWood =>
|
|
Kind == BuildingKind.Shelter && IsComplete && !IsRuined &&
|
|
UpkeepStock < UpkeepStockCap * 0.7f;
|
|
|
|
// --- Shelter: expansion modules --------------------------------------
|
|
|
|
public const int ModuleCap = 4;
|
|
public const float ModuleCost = 15f; // wood per module
|
|
|
|
public int Modules;
|
|
public float ModuleDelivered; // toward the next module
|
|
|
|
/// <summary>Sound, stocked shelters accept expansion work.</summary>
|
|
public bool WantsExpansion =>
|
|
Kind == BuildingKind.Shelter && IsComplete && !IsRuined &&
|
|
Modules < ModuleCap && !WantsUpkeepWood;
|
|
|
|
/// <summary>How many can sleep here. Expansion adds beds — growth has a
|
|
/// human meaning, not just a bigger square.</summary>
|
|
public int RestCapacity =>
|
|
Kind == BuildingKind.Shelter && IsComplete && !IsRuined ? 3 + Modules : 0;
|
|
|
|
// --- Granary: food store ---------------------------------------------
|
|
|
|
public const float FoodStockCap = 150f;
|
|
|
|
public float FoodStock;
|
|
|
|
/// <summary>True when the granary has room for more food.</summary>
|
|
public bool WantsFood =>
|
|
Kind == BuildingKind.Granary && IsComplete && FoodStock < FoodStockCap;
|
|
|
|
/// <summary>Take food out for a hungry villager. Returns amount granted.</summary>
|
|
public float WithdrawFood(float requested)
|
|
{
|
|
float granted = Mathf.Min(requested, FoodStock);
|
|
FoodStock -= granted;
|
|
return granted;
|
|
}
|
|
|
|
// --- Daily tick -------------------------------------------------------
|
|
|
|
/// <summary>Called once per in-game day by GameManager.</summary>
|
|
public void DailyUpkeep()
|
|
{
|
|
if (Kind != BuildingKind.Shelter || !IsComplete || IsRuined) return;
|
|
|
|
if (UpkeepStock >= DailyBurn)
|
|
{
|
|
UpkeepStock -= DailyBurn;
|
|
Condition = Mathf.Min(1f, Condition + 0.05f); // tended buildings mend
|
|
}
|
|
else
|
|
{
|
|
UpkeepStock = 0f;
|
|
Condition -= 0.1f; // neglect shows in ~10 days
|
|
}
|
|
}
|
|
|
|
// --- Delivery --------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// The visitor delivers what the building currently needs — construction
|
|
/// material while building; then firewood/expansion (shelter) or food
|
|
/// (granary). Returns false if they had nothing useful to give.
|
|
/// </summary>
|
|
public bool Deliver(Npc npc)
|
|
{
|
|
if (IsRuined) return false;
|
|
|
|
if (!IsComplete)
|
|
{
|
|
var stage = Stages[CurrentStage];
|
|
if (!npc.Inventory.TryGetValue(stage.Material, out float have) || have <= 0f)
|
|
return false;
|
|
|
|
// A practiced mason wastes less: the same materials go further
|
|
// in skilled hands — and the building is the teacher.
|
|
float masonry = npc.Know[KnowledgeDomain.Masonry];
|
|
float take = Mathf.Min(have, (stage.Cost - Delivered) / (1f + masonry * 0.5f));
|
|
npc.Inventory[stage.Material] = have - take;
|
|
Delivered += take * (1f + masonry * 0.5f);
|
|
npc.Know.Gain(KnowledgeDomain.Masonry, 0.002f);
|
|
|
|
if (Delivered >= stage.Cost)
|
|
{
|
|
CurrentStage++;
|
|
Delivered = 0f;
|
|
}
|
|
return take > 0f;
|
|
}
|
|
|
|
if (Kind == BuildingKind.Granary)
|
|
{
|
|
// Deposit real surplus only (keep 5) — and never redeposit what
|
|
// was just withdrawn: depositors must arrive carrying > 8.
|
|
if (!npc.Inventory.TryGetValue(MaterialKind.Food, out float carried) || carried <= 8f)
|
|
return false;
|
|
|
|
float space = FoodStockCap - FoodStock;
|
|
float taken = Mathf.Min(carried - 5f, space);
|
|
if (taken <= 0f) return false;
|
|
|
|
npc.Inventory[MaterialKind.Food] = carried - taken;
|
|
FoodStock += taken;
|
|
return true;
|
|
}
|
|
|
|
// Shelter: reserve first — never expand a shelter you can't heat.
|
|
if (!npc.Inventory.TryGetValue(MaterialKind.Wood, out float wood) || wood <= 0f)
|
|
return false;
|
|
|
|
float woodSpace = UpkeepStockCap - UpkeepStock;
|
|
if (woodSpace > 0f)
|
|
{
|
|
float taken = Mathf.Min(wood, woodSpace);
|
|
npc.Inventory[MaterialKind.Wood] = wood - taken;
|
|
UpkeepStock += taken;
|
|
return taken > 0f;
|
|
}
|
|
|
|
if (Modules < ModuleCap)
|
|
{
|
|
float take = Mathf.Min(wood, ModuleCost - ModuleDelivered);
|
|
npc.Inventory[MaterialKind.Wood] = wood - take;
|
|
ModuleDelivered += take;
|
|
|
|
if (ModuleDelivered >= ModuleCost)
|
|
{
|
|
Modules++;
|
|
ModuleDelivered = 0f;
|
|
}
|
|
return take > 0f;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Ruins persist in the world — they are not removed. What a place was
|
|
// remains visible in what it left behind.
|
|
}
|