using System; using Unity.Collections; namespace ProjectM.Simulation { /// /// Room-TYPE ids for a run-map node. A byte, never a C# enum — kept Burst-safe, serialization/replay-stable, /// and APPEND-ONLY (a persisted meta / replay reproducibility depends on these values never being re-meaned). /// public static class RoomTypeId { public const byte Combat = 0; public const byte Elite = 1; public const byte Reward = 2; public const byte Boss = 3; public const byte Count = 4; } /// /// Room SHAPE ids — the arena footprint places nodes/enemies within. /// A byte (append-only). Resolved to a concrete radius/footprint by . /// public static class RoomShapeId { public const byte Disk = 0; // circular arena (area-uniform scatter) public const byte Wide = 1; // rectangle, wider on X public const byte Long = 2; // rectangle, longer on Z public const byte Cross = 3; // plus/cross of two bars public const byte Count = 4; } /// /// Cosmetic BIOME ids — resolved to atmosphere/fog/tint by the client presentation layer (WorldAtmosphereSystem) /// per room. A byte (append-only); purely visual, no gameplay authority. /// public static class RoomBiomeId { public const byte Meadow = 0; public const byte Arid = 1; public const byte Cavern = 2; public const byte Blight = 3; public const byte Count = 4; } /// /// One node in the branching run-map DAG (Slay-the-Spire style). 4 bytes, unmanaged. is a /// bit set: bit j ⇒ this node can advance to column j of the NEXT layer (j < RunMap.MaxWidth). /// A node with == 0 is a terminal (the single Boss node). Generated purely from the run seed /// by , so it is identical on server + client (client regenerates for display). /// public struct RunMapNode : IEquatable { /// . public byte RoomType; /// (cosmetic). public byte Biome; /// . public byte ShapeId; /// Reachable next-layer columns: bit j ⇒ column j of the next layer. 0 = terminal (Boss). public byte NextMask; public bool Equals(RunMapNode o) => RoomType == o.RoomType && Biome == o.Biome && ShapeId == o.ShapeId && NextMask == o.NextMask; public override bool Equals(object o) => o is RunMapNode n && Equals(n); public override int GetHashCode() => RoomType | (Biome << 8) | (ShapeId << 16) | (NextMask << 24); } /// /// A generated branching run map: a layered DAG the party traverses one node per layer. TRANSIENT — regenerated /// from the run seed via and NEVER a ghost buffer / never persisted (only the /// seed + the party's current column ride the wire). Fixed stride of per layer, so the /// stable node key nodeId = layer*MaxWidth + col resolves the SAME room regardless of the path taken — /// which keeps per-room content (layout, boons) deterministic. Bounded to so it lives in a /// (30 × 4 B = 120 B). /// public struct RunMap { /// Max layers (run length is seed-varied within [6, ]). public const int MaxLayers = 10; /// Max nodes per layer (branch width 1–3). public const int MaxWidth = 3; /// Node-buffer capacity (fixed stride): × . public const int MaxNodes = MaxLayers * MaxWidth; /// Nodes, fixed stride per layer (LayerCount*MaxWidth entries; columns /// ≥ are absent/unused). public FixedList512Bytes Nodes; /// Per-layer branch width ( entries, each in [1, ]). public FixedList64Bytes LayerWidths; /// Number of layers this run (== room count, in [6, ]). public byte LayerCount; /// Stable node key for a (layer, col) — fixed stride, so the same key is the same room on any path. public static int NodeId(int layer, int col) => layer * MaxWidth + col; /// Branch width of a layer. public int Width(int layer) => LayerWidths[layer]; /// Node at (layer, col). public RunMapNode Node(int layer, int col) => Nodes[NodeId(layer, col)]; /// Node by stable id. public RunMapNode NodeAt(int nodeId) => Nodes[nodeId]; /// Layer of a node id. public int LayerOf(int nodeId) => nodeId / MaxWidth; /// Column of a node id. public int ColOf(int nodeId) => nodeId % MaxWidth; /// The single Boss node id (last layer, column 0). public int BossNodeId => NodeId(LayerCount - 1, 0); } }