Files
Soul_Game/scripts/SpatialGrid.cs
T
mmcghen f9e22e872f Run 13: the full world - four towns, resources, bonds, spatial grid
World scaled 3x (768x768) with all four towns at compass points, each
running its own soul culture: North generational, South nature, West
materialist (new profile), East selfish. Two new resources: Ore (metal
tier) and Herb (medicine - mends health, reason to forage, trade good).
Spatial hash grid replaces O(n^2) proximity scans (soul pressure,
neighbor, teacher) - 3.6x headless speedup at ~80 NPCs. First social
verb beyond work: pair-bonds - settled adults who keep warm company pair
up; warmth now accrues from proximity not only gifts; bonds break at
death. Reproduction deferred (bonds-first per plan).

The four-town thesis fully realized on one engine (240-day run):
North know 1.27 clean, South nourish 0.85 clean, West prosperous but
debris 0.23, East hungriest 0.60 and lowest knowledge 1.08. Positive
cultures stay soul-clean, negative ones carry real debris, all from
behavior weights - nothing scripted. 99 bonds, 80 deaths, four living
societies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 17:04:19 -04:00

53 lines
1.8 KiB
C#

using System.Collections.Generic;
using Godot;
namespace WorldSim;
/// <summary>
/// A coarse spatial hash over NPC positions so proximity queries stop being
/// O(n²). Rebuilt once per tick from the live NPC list; queries return only
/// the buckets within range. At ~80 NPCs this turns every soul-pressure,
/// neighbor, and teacher scan from "look at everyone" into "look at the few
/// nearby" — the difference between a live view that stutters and one that
/// doesn't.
/// </summary>
public class SpatialGrid
{
private readonly float _cell;
private readonly Dictionary<(int, int), List<Npc>> _buckets = new();
public SpatialGrid(float cellSize) => _cell = cellSize;
private (int, int) Key(Vector2 p) =>
(Mathf.FloorToInt(p.X / _cell), Mathf.FloorToInt(p.Y / _cell));
public void Rebuild(IReadOnlyList<Npc> npcs)
{
foreach (var list in _buckets.Values) list.Clear();
foreach (var npc in npcs)
{
var key = Key(npc.Position);
if (!_buckets.TryGetValue(key, out var list))
_buckets[key] = list = new List<Npc>();
list.Add(npc);
}
}
/// <summary>
/// Invoke <paramref name="action"/> for every NPC within <paramref
/// name="radius"/> of <paramref name="center"/> (excluding none — caller
/// filters). Scans only the buckets the radius touches.
/// </summary>
public void ForEachNear(Vector2 center, float radius, System.Action<Npc> action)
{
int reach = Mathf.CeilToInt(radius / _cell);
var (cx, cy) = Key(center);
for (int dx = -reach; dx <= reach; dx++)
for (int dy = -reach; dy <= reach; dy++)
{
if (_buckets.TryGetValue((cx + dx, cy + dy), out var list))
foreach (var npc in list) action(npc);
}
}
}