Grid-scope the food-gift and trade-partner searches

Route the two remaining full-population scans (a hungry villager seeking
someone with food; a trader seeking a partner) through the NPC spatial
grid with a 60-cell reach instead of scanning all ~80 villagers. Realistic
reach, cheaper at scale.

(A resource-node spatial index was also tried and reverted: benchmarked
~13% slower - at ~1,260 nodes the linear array scan is cache-friendly and
beats a dictionary-of-lists ring search. The node scan was never the
bottleneck; profile before optimizing next time.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 18:28:35 -04:00
parent 7de76fa45d
commit d7448c497d
+15 -11
View File
@@ -453,15 +453,17 @@ public class Npc
// this keeps gifts meaningful instead of a food-swapping loop. // this keeps gifts meaningful instead of a food-swapping loop.
if (Nourishment >= 0.55f) { Enter(NpcState.Socializing); return; } if (Nourishment >= 0.55f) { Enter(NpcState.Socializing); return; }
float bestScore = float.MinValue; float bestScore = float.MinValue;
foreach (var other in gm.Npcs) Npc? giftFound = null;
gm.Grid.ForEachNear(Position, 60f, other =>
{ {
if (other == this) continue; if (other == this) return;
float warmth = GetWarmth(other.Name); float warmth = GetWarmth(other.Name);
if (warmth < -0.3f) continue; if (warmth < -0.3f) return;
if (!other.Inventory.TryGetValue(MaterialKind.Food, out float f) || f <= 3f) continue; if (!other.Inventory.TryGetValue(MaterialKind.Food, out float f) || f <= 3f) return;
float score = warmth * 40f - Position.DistanceTo(other.Position); float score = warmth * 40f - Position.DistanceTo(other.Position);
if (score > bestScore) { bestScore = score; _giftSource = other; } if (score > bestScore) { bestScore = score; giftFound = other; }
} });
_giftSource = giftFound;
if (_giftSource != null) return; if (_giftSource != null) return;
} }
@@ -503,13 +505,15 @@ public class Npc
if (_targetNpc == null) if (_targetNpc == null)
{ {
float bestDist = float.MaxValue; float bestDist = float.MaxValue;
foreach (var other in gm.Npcs) Npc? partner = null;
gm.Grid.ForEachNear(Position, 60f, other =>
{ {
if (other == this) continue; if (other == this) return;
if (!TradeSystem.CanTrade(this, other, out _)) continue; if (!TradeSystem.CanTrade(this, other, out _)) return;
float d = Position.DistanceSquaredTo(other.Position); float d = Position.DistanceSquaredTo(other.Position);
if (d < bestDist) { bestDist = d; _targetNpc = other; } if (d < bestDist) { bestDist = d; partner = other; }
} });
_targetNpc = partner;
if (_targetNpc == null) { Enter(NpcState.Building); return; } if (_targetNpc == null) { Enter(NpcState.Building); return; }
} }