f9e22e872f
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>
53 lines
1.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|