b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
165 lines
7.5 KiB
C#
165 lines
7.5 KiB
C#
using Theriapolis.Core.Data;
|
|
using Theriapolis.Core.World.Polylines;
|
|
|
|
namespace Theriapolis.Core.World;
|
|
|
|
/// <summary>
|
|
/// The runtime world model. Holds all canonical simulation data for the generated continent.
|
|
/// Arrays are indexed [x, y] with (0,0) at the top-left (north-west).
|
|
/// </summary>
|
|
public sealed class WorldState
|
|
{
|
|
public ulong WorldSeed { get; init; }
|
|
|
|
// ── Canonical arrays ─────────────────────────────────────────────────────
|
|
public WorldTile[,] Tiles { get; } = new WorldTile[C.WORLD_WIDTH_TILES, C.WORLD_HEIGHT_TILES];
|
|
|
|
// Convenience accessors into the tile array (avoid struct copies in hot paths)
|
|
public ref WorldTile TileAt(int x, int y) => ref Tiles[x, y];
|
|
|
|
// ── Macro grid ────────────────────────────────────────────────────────────
|
|
public MacroCell[,]? MacroGrid { get; set; }
|
|
|
|
// ── Content defs (loaded from JSON, not generated) ────────────────────────
|
|
public BiomeDef[]? BiomeDefs { get; set; }
|
|
public FactionDef[]? FactionDefs { get; set; }
|
|
|
|
// ── Phase 2+3: Polylines (source of truth for linear features) ────────────
|
|
public List<Polyline> Rivers { get; } = new();
|
|
public List<Polyline> Roads { get; } = new();
|
|
public List<Polyline> Rails { get; } = new();
|
|
|
|
// ── Phase 2+3: Settlements ───────────────────────────────────────────────
|
|
public List<Settlement> Settlements { get; } = new();
|
|
|
|
// ── Phase 2+3: Bridges (road/rail crossings over rivers) ────────────────
|
|
public List<Bridge> Bridges { get; } = new();
|
|
|
|
// ── Phase 2+3: Computed maps ─────────────────────────────────────────────
|
|
public float[,]? Habitability { get; set; }
|
|
public float[,]? EncounterDensity { get; set; }
|
|
public FactionInfluenceMap? FactionInfluence { get; set; }
|
|
|
|
// ── Stage hashes for save integrity ───────────────────────────────────────
|
|
// Each stage appends its hash here after completing.
|
|
public Dictionary<string, ulong> StageHashes { get; } = new();
|
|
|
|
// ── Helper: macro cell for a given world tile coordinate ─────────────────
|
|
/// <summary>
|
|
/// Looks up a macro cell by unwarped grid position. Use this only for
|
|
/// pre-ElevationGen stages or places where you explicitly want the raw
|
|
/// grid lookup. Most callers should use <see cref="MacroCellForTile"/>.
|
|
/// </summary>
|
|
public MacroCell MacroCellAt(int tileX, int tileY)
|
|
{
|
|
if (MacroGrid is null) throw new InvalidOperationException("MacroGrid not loaded yet.");
|
|
int mx = Math.Clamp(tileX / (C.WORLD_WIDTH_TILES / C.MACRO_GRID_WIDTH), 0, C.MACRO_GRID_WIDTH - 1);
|
|
int my = Math.Clamp(tileY / (C.WORLD_HEIGHT_TILES / C.MACRO_GRID_HEIGHT), 0, C.MACRO_GRID_HEIGHT - 1);
|
|
return MacroGrid[mx, my];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the macro cell stored on the given tile. <see cref="ElevationGenStage"/>
|
|
/// overwrites each tile's <see cref="WorldTile.MacroX"/> and <see cref="WorldTile.MacroY"/>
|
|
/// with border-warped coordinates so that macro cell boundaries follow
|
|
/// organic wiggly curves instead of grid-aligned lines (Addendum A §1).
|
|
/// All post-ElevationGen stages and tests should use this method.
|
|
/// </summary>
|
|
public MacroCell MacroCellForTile(in WorldTile tile)
|
|
{
|
|
if (MacroGrid is null) throw new InvalidOperationException("MacroGrid not loaded yet.");
|
|
return MacroGrid[tile.MacroX, tile.MacroY];
|
|
}
|
|
|
|
// ── Sea level constant ─────────────────────────────────────────────────────
|
|
// Tiles with elevation < SeaLevel are ocean.
|
|
public const float SeaLevel = 0.35f;
|
|
|
|
// ── Fast hash for determinism tests ──────────────────────────────────────
|
|
/// <summary>FNV-1a hash over all elevation values (for determinism tests).</summary>
|
|
public ulong HashElevation()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
for (int y = 0; y < C.WORLD_HEIGHT_TILES; y++)
|
|
for (int x = 0; x < C.WORLD_WIDTH_TILES; x++)
|
|
{
|
|
uint bits = BitConverter.SingleToUInt32Bits(Tiles[x, y].Elevation);
|
|
hash = (hash ^ bits) * FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
public ulong HashMoisture()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
for (int y = 0; y < C.WORLD_HEIGHT_TILES; y++)
|
|
for (int x = 0; x < C.WORLD_WIDTH_TILES; x++)
|
|
{
|
|
uint bits = BitConverter.SingleToUInt32Bits(Tiles[x, y].Moisture);
|
|
hash = (hash ^ bits) * FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
public ulong HashTemperature()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
for (int y = 0; y < C.WORLD_HEIGHT_TILES; y++)
|
|
for (int x = 0; x < C.WORLD_WIDTH_TILES; x++)
|
|
{
|
|
uint bits = BitConverter.SingleToUInt32Bits(Tiles[x, y].Temperature);
|
|
hash = (hash ^ bits) * FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
public ulong HashBiomes()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
for (int y = 0; y < C.WORLD_HEIGHT_TILES; y++)
|
|
for (int x = 0; x < C.WORLD_WIDTH_TILES; x++)
|
|
hash = (hash ^ (byte)Tiles[x, y].Biome) * FNV_PRIME;
|
|
return hash;
|
|
}
|
|
|
|
/// <summary>FNV-1a hash over all settlements (sorted by ID).</summary>
|
|
public ulong HashSettlements()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
foreach (var s in Settlements.OrderBy(s => s.Id))
|
|
{
|
|
hash = (hash ^ (ulong)s.Id) * FNV_PRIME;
|
|
hash = (hash ^ (ulong)s.Tier) * FNV_PRIME;
|
|
hash = (hash ^ (ulong)s.TileX) * FNV_PRIME;
|
|
hash = (hash ^ (ulong)s.TileY) * FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
/// <summary>FNV-1a hash over all polyline points (rivers, then roads, then rails).</summary>
|
|
public ulong HashPolylines()
|
|
{
|
|
const ulong FNV_PRIME = 1099511628211UL;
|
|
const ulong FNV_OFFSET = 14695981039346656037UL;
|
|
ulong hash = FNV_OFFSET;
|
|
foreach (var polylineList in new[] { Rivers, Roads, Rails })
|
|
foreach (var p in polylineList.OrderBy(p => p.Id))
|
|
foreach (var pt in p.Points)
|
|
{
|
|
hash = (hash ^ BitConverter.SingleToUInt32Bits(pt.X)) * FNV_PRIME;
|
|
hash = (hash ^ BitConverter.SingleToUInt32Bits(pt.Y)) * FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
}
|