Run 15: known-nodes exploration (addendum stage 1), per-town debug, O(n^2) fix
Exploration addendum stage 1: per-NPC known-nodes. NPCs gather only from resources they've personally sensed (radius 22) or been told about; founders seed with home ground, children inherit parents' maps, node knowledge diffuses between neighbors like skills. Starving NPCs venture outward and sense as they go - non-random needs-driven exploration, the seed of first contact. Repopulation preserved (seeding + survival food fallback): all towns fed, population stable 86-109 over 240 days. Debug screen rebuilt: the ~68-row per-villager roster (overflowing at four-town scale) replaced with a per-town summary - pop, fed%, knowledge, debris, state breakdown, buildings, culture-colored, plus world births/deaths. Scales to any population. Performance: added phase timers and measured instead of guessing - npc.Tick was 94% of cost, O(n^2) via TickResting calling RestingOccupancy (full population scan) per building per resting NPC. Fixed with a once-per-tick occupancy cache on Building. npc phase ~4x faster and now linear; full 240-day four-town run in 180s, faster than the pre-exploration baseline. Profiling scaffolding removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+74
-50
@@ -119,68 +119,92 @@ public partial class Visualization : Node2D
|
||||
$"{(_gm.IsNight ? "· night" : "· day")} {_gm.TicksPerRealSecond:0}x",
|
||||
fontSize: 18, modulate: TextMain);
|
||||
|
||||
// --- Panel: villager roster --------------------------------------
|
||||
float y = 66f;
|
||||
DrawString(font, new Vector2(PanelX + 16, y), "VILLAGERS",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
y += 10f;
|
||||
// --- Panel: per-town summary (scales to any population) ----------
|
||||
float y = 74f;
|
||||
|
||||
for (int i = 0; i < _gm.Npcs.Count; i++)
|
||||
foreach (var town in _gm.Towns)
|
||||
{
|
||||
var npc = _gm.Npcs[i];
|
||||
y += 20f;
|
||||
// Aggregate this town's people.
|
||||
int pop = 0; float nour = 0f, know = 0f, debris = 0f;
|
||||
int gathering = 0, building = 0, resting = 0, social = 0, other = 0;
|
||||
foreach (var npc in _gm.Npcs)
|
||||
{
|
||||
if (npc.HomeTown != town.Name) continue;
|
||||
pop++; nour += npc.Nourishment; know += npc.Know.Total; debris += npc.Imprint.Total;
|
||||
switch (npc.State)
|
||||
{
|
||||
case NpcState.Gathering: gathering++; break;
|
||||
case NpcState.Building: building++; break;
|
||||
case NpcState.Resting: resting++; break;
|
||||
case NpcState.Socializing: social++; break;
|
||||
default: other++; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (npc.Soul.Type == SoulType.Selfish)
|
||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 6f, new Color(0.75f, 0.15f, 0.15f));
|
||||
DrawCircle(new Vector2(PanelX + 24, y - 5f), 4.5f,
|
||||
StateColor(npc.State).Darkened(npc.Imprint.Total * 0.7f));
|
||||
DrawString(font, new Vector2(PanelX + 36, y),
|
||||
$"{i + 1,2} {npc.State,-11} {npc.Activity}",
|
||||
fontSize: 13, modulate: TextMain);
|
||||
DrawString(font, new Vector2(PanelX + 390, y),
|
||||
$"N {npc.Nourishment * 100,3:0} D {npc.Imprint.Total * 100,2:0}" +
|
||||
$" K {npc.Know.Total,4:0.00} A {npc.AgeDays,3:0}" +
|
||||
$" {npc.DominantStateToday().ToString().ToLower()[..4]}",
|
||||
fontSize: 12, modulate: TextDim);
|
||||
// Aggregate this town's buildings.
|
||||
int shelters = 0, ruins = 0; float granary = 0f;
|
||||
foreach (var b in _gm.Buildings)
|
||||
{
|
||||
if (b.Town != town.Name) continue;
|
||||
if (b.IsRuined) ruins++;
|
||||
else if (b.Kind == BuildingKind.Granary) granary += b.FoodStock;
|
||||
else if (b.IsComplete) shelters++;
|
||||
}
|
||||
|
||||
var head = TownColor(town.Soul);
|
||||
DrawString(font, new Vector2(PanelX + 16, y),
|
||||
$"{town.Name.ToUpper()} · {town.Soul.ToString().ToLower()}",
|
||||
fontSize: 15, modulate: pop > 0 ? head : TextDim);
|
||||
DrawString(font, new Vector2(PanelX + 250, y),
|
||||
pop > 0 ? $"pop {pop}" : "— extinct —",
|
||||
fontSize: 15, modulate: pop > 0 ? TextMain : new Color(0.8f, 0.4f, 0.4f));
|
||||
y += 22f;
|
||||
|
||||
if (pop > 0)
|
||||
{
|
||||
float inv = 1f / pop;
|
||||
DrawString(font, new Vector2(PanelX + 28, y),
|
||||
$"fed {nour * inv * 100,3:0}% know {know * inv,4:0.00} debris {debris * inv * 100,2:0}%",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
DrawString(font, new Vector2(PanelX + 320, y),
|
||||
$"⌂{shelters} ▦{granary:0}" + (ruins > 0 ? $" ✗{ruins}" : ""),
|
||||
fontSize: 13, modulate: TextDim);
|
||||
y += 19f;
|
||||
DrawString(font, new Vector2(PanelX + 28, y),
|
||||
$"gather {gathering} build {building} rest {resting} social {social}" +
|
||||
(other > 0 ? $" other {other}" : ""),
|
||||
fontSize: 12, modulate: TextDim);
|
||||
y += 19f;
|
||||
}
|
||||
y += 12f;
|
||||
}
|
||||
|
||||
// --- Panel: buildings --------------------------------------------
|
||||
y += 34f;
|
||||
DrawString(font, new Vector2(PanelX + 16, y), "BUILDINGS",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
y += 10f;
|
||||
|
||||
for (int i = 0; i < _gm.Buildings.Count; i++)
|
||||
{
|
||||
var b = _gm.Buildings[i];
|
||||
y += 18f;
|
||||
string status = b.IsRuined ? "RUIN"
|
||||
: !b.IsComplete
|
||||
? $"building: {b.Stages[b.CurrentStage].Name} {b.StageProgress * 100,3:0}%"
|
||||
: b.Kind == BuildingKind.Granary
|
||||
? $"granary food {b.FoodStock,5:0.0}/{Building.FoodStockCap:0}"
|
||||
: $"cond {b.Condition * 100,3:0}% wood {b.UpkeepStock,4:0.0}/{Building.UpkeepStockCap:0}" +
|
||||
$" mods {b.Modules}/{Building.ModuleCap} burn {b.DailyBurn:0.0}/day";
|
||||
DrawString(font, new Vector2(PanelX + 36, y),
|
||||
$"{i + 1} {b.Town,-5} {status}", fontSize: 12,
|
||||
modulate: b.IsRuined ? TextDim : TextMain);
|
||||
}
|
||||
|
||||
// --- Panel: world totals -----------------------------------------
|
||||
y += 34f;
|
||||
float wood = 0f, stone = 0f, clay = 0f, foodTotal = 0f;
|
||||
// --- Panel: world vitals (bottom) --------------------------------
|
||||
y += 8f;
|
||||
float wood = 0f, stone = 0f, food = 0f;
|
||||
foreach (var n in _gm.World.Nodes)
|
||||
{
|
||||
switch (n.Kind)
|
||||
{
|
||||
case MaterialKind.Wood: wood += n.Amount; break;
|
||||
case MaterialKind.Stone: stone += n.Amount; break;
|
||||
case MaterialKind.Clay: clay += n.Amount; break;
|
||||
case MaterialKind.Food: foodTotal += n.Amount; break;
|
||||
case MaterialKind.Wood: wood += n.Amount; break;
|
||||
case MaterialKind.Stone: stone += n.Amount; break;
|
||||
case MaterialKind.Food: food += n.Amount; break;
|
||||
}
|
||||
}
|
||||
DrawString(font, new Vector2(PanelX + 16, y),
|
||||
$"WORLD wood {wood:0} stone {stone:0} clay {clay:0} food {foodTotal:0}",
|
||||
$"WORLD pop {_gm.Npcs.Count} +{_gm.BirthsToday}/−{_gm.DeathsToday} today",
|
||||
fontSize: 14, modulate: TextMain);
|
||||
y += 20f;
|
||||
DrawString(font, new Vector2(PanelX + 16, y),
|
||||
$"land wood {wood:0} stone {stone:0} food {food:0}",
|
||||
fontSize: 13, modulate: TextDim);
|
||||
}
|
||||
|
||||
private static Color TownColor(SoulType t) => t switch
|
||||
{
|
||||
SoulType.Generational => new Color(0.55f, 0.75f, 0.95f), // North — cool
|
||||
SoulType.Nature => new Color(0.55f, 0.85f, 0.55f), // South — green
|
||||
SoulType.Materialist => new Color(0.90f, 0.80f, 0.45f), // West — gold
|
||||
_ => new Color(0.90f, 0.45f, 0.45f), // East — red
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user