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);
}
}