namespace Theriapolis.Core.Tactical; /// /// One streamed chunk of the tactical world. Always /// ² tactical tiles. /// /// Chunks are produced by from the deterministic /// inputs (worldSeed, ChunkCoord, WorldState) plus an optional delta overlay /// from the save layer. The chunk itself does not know whether deltas have /// been applied — that's 's job. /// public sealed class TacticalChunk { public ChunkCoord Coord { get; } /// Indexed [tx, ty] in chunk-local coordinates (0..CHUNK_SIZE-1). public TacticalTile[,] Tiles { get; } = new TacticalTile[C.TACTICAL_CHUNK_SIZE, C.TACTICAL_CHUNK_SIZE]; /// Phase-4 spawn list. Stored, not yet acted on (Phase 5 reads it). public List Spawns { get; } = new(); /// /// Phase 5 M5 difficulty tier (0..C.DANGER_ZONE_MAX). Drives which template /// each instantiates as a live NPC. Set /// in 's spawn pass; folded into the hash /// so determinism tests catch zone-formula drift. /// public byte DangerZone { get; set; } /// Set true by ChunkStreamer when a delta is applied; flushed back on eviction. public bool Dirty { get; set; } /// True if any field has been modified relative to the deterministic baseline. public bool HasDelta { get; set; } public TacticalChunk(ChunkCoord coord) { Coord = coord; } /// Top-left tactical tile coordinate of this chunk in world-pixel space. public int OriginX => Coord.X * C.TACTICAL_CHUNK_SIZE; public int OriginY => Coord.Y * C.TACTICAL_CHUNK_SIZE; /// Returns the tile at chunk-local (lx, ly) — bounds-checked. public ref TacticalTile TileAt(int lx, int ly) { if ((uint)lx >= C.TACTICAL_CHUNK_SIZE || (uint)ly >= C.TACTICAL_CHUNK_SIZE) throw new ArgumentOutOfRangeException($"({lx},{ly}) outside chunk"); return ref Tiles[lx, ly]; } /// FNV-1a hash over every tile. Used by determinism tests. public ulong Hash() { const ulong FNV_PRIME = 1099511628211UL; const ulong FNV_OFFSET = 14695981039346656037UL; ulong h = FNV_OFFSET; for (int y = 0; y < C.TACTICAL_CHUNK_SIZE; y++) for (int x = 0; x < C.TACTICAL_CHUNK_SIZE; x++) { ref var t = ref Tiles[x, y]; h = (h ^ (byte)t.Surface) * FNV_PRIME; h = (h ^ (byte)t.Deco) * FNV_PRIME; h = (h ^ t.Variant) * FNV_PRIME; h = (h ^ t.Flags) * FNV_PRIME; } // Fold spawn list into the hash so any non-determinism there shows up. foreach (var s in Spawns) { h = (h ^ (byte)s.Kind) * FNV_PRIME; h = (h ^ (uint)s.LocalX) * FNV_PRIME; h = (h ^ (uint)s.LocalY) * FNV_PRIME; } // Fold the DangerZone in so changes to the zone formula register as // a chunk-hash change in the determinism tests. h = (h ^ DangerZone) * FNV_PRIME; return h; } } public enum SpawnKind : byte { None = 0, WildAnimal, Brigand, Merchant, Patrol, PoiGuard, /// /// Phase 6 M0 — emitted by /// for each occupied building role. Phase 6 M1 instantiates these as /// friendly s with role-specific dialogue. /// Resident = 16, } /// Single spawn record in chunk-local coordinates. Phase 5 acts on these. public readonly struct TacticalSpawn { public readonly SpawnKind Kind; public readonly int LocalX; public readonly int LocalY; public TacticalSpawn(SpawnKind kind, int lx, int ly) { Kind = kind; LocalX = lx; LocalY = ly; } }