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:
+47
-2
@@ -37,6 +37,20 @@ public class World
|
||||
|
||||
private readonly Random _rng;
|
||||
|
||||
// Node cell index (built once — nodes never move). Only the sensing sweep
|
||||
// uses it, but that runs per-NPC constantly, so it's the one place a node
|
||||
// index genuinely pays (unlike nearest-of-kind, which stays a linear scan).
|
||||
private const int NodeCell = 32;
|
||||
private readonly Dictionary<(int, int), List<ResourceNode>> _nodeCells = new();
|
||||
|
||||
private void IndexNode(ResourceNode n)
|
||||
{
|
||||
var key = ((int)(n.Cell.X / NodeCell), (int)(n.Cell.Y / NodeCell));
|
||||
if (!_nodeCells.TryGetValue(key, out var list))
|
||||
_nodeCells[key] = list = new List<ResourceNode>();
|
||||
list.Add(n);
|
||||
}
|
||||
|
||||
public World(int seed)
|
||||
{
|
||||
_rng = new Random(seed);
|
||||
@@ -63,14 +77,16 @@ public class World
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Nodes.Add(new ResourceNode
|
||||
var node = new ResourceNode
|
||||
{
|
||||
Kind = kind,
|
||||
Cell = new Vector2I(_rng.Next(GridSize), _rng.Next(GridSize)),
|
||||
Amount = amount,
|
||||
MaxAmount = amount,
|
||||
RegenPerDay = regen,
|
||||
});
|
||||
};
|
||||
Nodes.Add(node);
|
||||
IndexNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +129,35 @@ public class World
|
||||
return taken;
|
||||
}
|
||||
|
||||
/// <summary>Invoke action for every node within radius — used by NPCs to
|
||||
/// discover nearby nodes (addendum §4 sensing). Cell-indexed: scans only
|
||||
/// the buckets the radius touches, not all ~1,260 nodes.</summary>
|
||||
public void ForEachNodeNear(Vector2 position, float radius, System.Action<ResourceNode> action)
|
||||
{
|
||||
float r2 = radius * radius;
|
||||
int reach = (int)(radius / NodeCell) + 1;
|
||||
int cx = (int)(position.X / NodeCell);
|
||||
int cy = (int)(position.Y / NodeCell);
|
||||
for (int dx = -reach; dx <= reach; dx++)
|
||||
for (int dy = -reach; dy <= reach; dy++)
|
||||
{
|
||||
if (!_nodeCells.TryGetValue((cx + dx, cy + dy), out var list)) continue;
|
||||
foreach (var node in list)
|
||||
if (position.DistanceSquaredTo(node.Cell) <= r2)
|
||||
action(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Seed a town's founders with the resources around their home,
|
||||
/// so they begin with a working local map instead of blind.</summary>
|
||||
public IEnumerable<ResourceNode> NodesWithin(Vector2 center, float radius)
|
||||
{
|
||||
float r2 = radius * radius;
|
||||
foreach (var node in Nodes)
|
||||
if (center.DistanceSquaredTo(node.Cell) <= r2)
|
||||
yield return node;
|
||||
}
|
||||
|
||||
/// <summary>Nearest node of a kind regardless of amount — for returning
|
||||
/// unneeded minerals to the quarry rather than carrying them forever.</summary>
|
||||
public ResourceNode? FindNearestAny(MaterialKind kind, Vector2 position)
|
||||
|
||||
Reference in New Issue
Block a user