Toroidal world: boundless wrapping globe, 2x2 even town grid
The map is now a torus - walk off any edge and reappear on the opposite side, no edges or corners. A new Toroidal helper carries all wrap-aware distance/direction/movement math, and every position comparison in the sim routes through it: soul pressure, node + structure sensing, gift/teacher/ neighbor scans, bonding, movement, the spatial grids (NPC and node cell index both wrap their bucket lookups at the seam), and town centroid (averaged in anchor-relative deltas, since averaging raw coordinates across the seam is meaningless). Towns re-laid as a 2x2 even grid on the torus (quarter/three-quarter points, 320 & 960 on a 1280 world): every town sits exactly 640 units from each of its two axis-neighbours in all directions, wrap included - no more edge-boxed North/South that could only forage inward. World grew 1152 -> 1280 with resources scaled to area. Verified over a 240-day soak: all four towns alive and balanced (pop 23-25), population stable ~96, 119 bonds (27 cross-town), cultures still diverge, and wrapped distances correctly cap at the torus half-diagonal (no false across-seam blowups). 183s headless. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+18
-15
@@ -28,7 +28,7 @@ public class ResourceNode
|
||||
/// </summary>
|
||||
public class World
|
||||
{
|
||||
public const int GridSize = 1152;
|
||||
public const int GridSize = 1280;
|
||||
|
||||
public List<ResourceNode> Nodes { get; } = new();
|
||||
|
||||
@@ -63,15 +63,15 @@ public class World
|
||||
/// </summary>
|
||||
public void Generate()
|
||||
{
|
||||
// Resource counts scale with area (1152² ≈ 2.25× the 768² world) so
|
||||
// density holds as the towns spread to wider cardinal points.
|
||||
PlaceMany(MaterialKind.Water, count: 90, amount: float.PositiveInfinity, regen: 0f);
|
||||
PlaceMany(MaterialKind.Clay, count: 180, amount: 40f, regen: 0.5f);
|
||||
PlaceMany(MaterialKind.Wood, count: 1080, amount: 30f, regen: 2f);
|
||||
PlaceMany(MaterialKind.Stone, count: 315, amount: 80f, regen: 0f);
|
||||
PlaceMany(MaterialKind.Food, count: 810, amount: 20f, regen: 4f);
|
||||
PlaceMany(MaterialKind.Ore, count: 90, amount: 60f, regen: 0f);
|
||||
PlaceMany(MaterialKind.Herb, count: 270, amount: 15f, regen: 3f);
|
||||
// Resource counts scale with area (1280² ≈ 1.23× the 1152² world) so
|
||||
// density holds across the boundless globe.
|
||||
PlaceMany(MaterialKind.Water, count: 110, amount: float.PositiveInfinity, regen: 0f);
|
||||
PlaceMany(MaterialKind.Clay, count: 220, amount: 40f, regen: 0.5f);
|
||||
PlaceMany(MaterialKind.Wood, count: 1330, amount: 30f, regen: 2f);
|
||||
PlaceMany(MaterialKind.Stone, count: 390, amount: 80f, regen: 0f);
|
||||
PlaceMany(MaterialKind.Food, count: 1000, amount: 20f, regen: 4f);
|
||||
PlaceMany(MaterialKind.Ore, count: 110, amount: 60f, regen: 0f);
|
||||
PlaceMany(MaterialKind.Herb, count: 330, amount: 15f, regen: 3f);
|
||||
}
|
||||
|
||||
private void PlaceMany(MaterialKind kind, int count, float amount, float regen)
|
||||
@@ -137,14 +137,17 @@ public class World
|
||||
{
|
||||
float r2 = radius * radius;
|
||||
int reach = (int)(radius / NodeCell) + 1;
|
||||
int cols = (GridSize + NodeCell - 1) / NodeCell; // cells per axis
|
||||
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;
|
||||
int gx = ((cx + dx) % cols + cols) % cols; // wrap at the seam
|
||||
int gy = ((cy + dy) % cols + cols) % cols;
|
||||
if (!_nodeCells.TryGetValue((gx, gy), out var list)) continue;
|
||||
foreach (var node in list)
|
||||
if (position.DistanceSquaredTo(node.Cell) <= r2)
|
||||
if (Toroidal.DistanceSquared(position, node.Cell) <= r2)
|
||||
action(node);
|
||||
}
|
||||
}
|
||||
@@ -155,7 +158,7 @@ public class World
|
||||
{
|
||||
float r2 = radius * radius;
|
||||
foreach (var node in Nodes)
|
||||
if (center.DistanceSquaredTo(node.Cell) <= r2)
|
||||
if (Toroidal.DistanceSquared(center, node.Cell) <= r2)
|
||||
yield return node;
|
||||
}
|
||||
|
||||
@@ -168,7 +171,7 @@ public class World
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node.Kind != kind) continue;
|
||||
float d = position.DistanceSquaredTo(node.Cell);
|
||||
float d = Toroidal.DistanceSquared(position, node.Cell);
|
||||
if (d < bestDist) { best = node; bestDist = d; }
|
||||
}
|
||||
return best;
|
||||
@@ -225,7 +228,7 @@ public class World
|
||||
if (node.Kind != MaterialKind.Water && node.RegenPerDay > 0f &&
|
||||
node.Amount <= node.MaxAmount * minFraction)
|
||||
continue;
|
||||
float d = position.DistanceSquaredTo(node.Cell);
|
||||
float d = Toroidal.DistanceSquared(position, node.Cell);
|
||||
if (d < bestDist) { best = node; bestDist = d; }
|
||||
}
|
||||
return best;
|
||||
|
||||
Reference in New Issue
Block a user