Files
Project-M/Assets/_Project/Scripts/Simulation/World/RunInfo.cs
T
kronic 304ee8c2a7 Base<->Expedition ties: portal rooms, class-at-base, prep spend + Health.Max, C3/C4/Spitter (DR-046)
Portal-gated rooms: new RunLifecycle.RoomExplore loot window; room+nodes
persist past the last kill; PortalInteractRequest -> server-only PortalCommand
advances (client derives the portal pos). RunDirectorSystem moves teardown off
the InRoom edge, relocates the boss-branch/route-gate into the RoomExplore exit,
and advances an empty expedition immediately (incl. a boss clear).

Class-at-base via a shared ClassSwapUtil (meta-band strip + per-class
MetaTierState replay, so the base RPC and the dev SetClass can't drift);
ClassSelectRequest Staging-gated. Per-run PREP buffs (PrepPurchaseRequest,
PrepCatalog): once-per-run == the row's prep StatModifier band is present,
TotalOf-before-Withdraw atomic, stripped on Returning beside the boon band.

Health.Max promoted to [GhostField] (the one ghost-hash re-bake) so the boss +
floating enemy HP bars read it directly. Feel: melee cone connect-thunk (C3),
hit-stop throttle so a horde wipe can't stutter (C4), Spitter cornered-hold.

456/456 EditMode; two adversarial reviews (pre-code 13-confirmed folded;
post-impl clean bar the RoomExplore boss-clear dead-time, fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:37:24 -07:00

86 lines
5.3 KiB
C#

using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// Lifecycle states for a co-op expedition run (<see cref="RunInfo.Lifecycle"/>). A <c>byte</c>, never an enum
/// (Burst/serialization safe), APPEND-ONLY. <see cref="RouteSelect"/> is appended after the original four so no
/// value is re-meaned. The party is at the base hub in <see cref="Staging"/>; a discrete run spans
/// <see cref="Launching"/>→<see cref="InRoom"/>→<see cref="RoomReward"/>→<see cref="RouteSelect"/> (loop) →
/// <see cref="Returning"/>→<see cref="Staging"/>.
/// </summary>
public static class RunLifecycle
{
/// <summary>Party in the base hub; ready-check active; no expedition ghosts exist.</summary>
public const byte Staging = 0;
/// <summary>All-ready launch transient: seed chosen, party teleporting into room 0.</summary>
public const byte Launching = 1;
/// <summary>Active room populated; party fighting/looting.</summary>
public const byte InRoom = 2;
/// <summary>Room cleared; per-player boon offers pending; room torn down.</summary>
public const byte RoomReward = 3;
/// <summary>Run ended (boss cleared / party wiped / all left): teleport home, bank, → Staging.</summary>
public const byte Returning = 4;
/// <summary>Boons picked; party choosing the next branch (no room materialized — the teardown gap).</summary>
public const byte RouteSelect = 5;
/// <summary>DR-046: room cleared + boon picked, but the room + resource NODES persist and a portal is up.
/// Party loots; interacting the portal (or a soft-timeout) tears the room down + advances (RouteSelect, or
/// Returning if the boss fell). Append-only byte value — no ghost re-mean.</summary>
public const byte RoomExplore = 6;
}
/// <summary>
/// The REPLICATED run-lifecycle summary the whole party observes — a server-decided, client-observed FSM on the
/// GLOBAL untagged CycleDirector ghost (so it is relevant cross-region for free, like <see cref="CycleState"/>/
/// <see cref="GoalProgress"/>/<see cref="RunOutcome"/>). SOLE writer: <c>RunDirectorSystem</c>. Distinct from
/// <see cref="CycleState.Phase"/> (that stays the BASE Calm↔Siege posture for retaliation/final sieges).
///
/// Fields split three ways: the lifecycle/room readout (HUD "Room i/N", biome cross-fade), the branching-map
/// wire (<see cref="RunSeed"/> so the client regenerates the map for DISPLAY, + <see cref="CurrentCol"/> and the
/// authoritative reachable <c>RouteOpt*</c> the clickable options bind to), and a two-field mirror of the
/// persisted meta counters for the HUD. All integers/bytes → replicate exact (no quantization). Adding this
/// <c>[GhostField]</c> component re-hashes the runtime-spawned director ghost (server + client bake the same
/// prefab → hash matches), exactly like <see cref="CoreIntegrity"/>/<see cref="RunOutcome"/>.
/// </summary>
public struct RunInfo : IComponentData
{
// ---- lifecycle + room readout ----
/// <summary><see cref="RunLifecycle"/>.</summary>
[GhostField] public byte Lifecycle;
/// <summary>0-based depth of the active room (HUD "Room CurrentRoom+1 / RoomCount").</summary>
[GhostField] public int CurrentRoom;
/// <summary>Total rooms this run (== map layer count, seed-varied in [6,10]).</summary>
[GhostField] public int RoomCount;
/// <summary><see cref="RoomTypeId"/> of the active room.</summary>
[GhostField] public byte CurrentRoomType;
/// <summary><see cref="RoomBiomeId"/> of the active room (client atmosphere cross-fade).</summary>
[GhostField] public byte CurrentBiome;
/// <summary>Server tick the launch countdown elapses (0 = none). Via <see cref="TickUtil.NonZero"/>; compared with IsNewerThan.</summary>
[GhostField] public uint LaunchTick;
// ---- branching map wire ----
/// <summary>The run seed — clients regenerate the map layout for DISPLAY via <see cref="RunMapMath.Generate"/> (no gameplay authority).</summary>
[GhostField] public uint RunSeed;
/// <summary>The party's current column in the active layer.</summary>
[GhostField] public byte CurrentCol;
/// <summary>Number of reachable next-room options (0 unless <see cref="RunLifecycle.RouteSelect"/>).</summary>
[GhostField] public byte RouteOptionCount;
/// <summary>Reachable next-layer column for option 0 (authoritative — the clickable button binds to this, not the regen).</summary>
[GhostField] public byte RouteOpt0Col;
[GhostField] public byte RouteOpt1Col;
[GhostField] public byte RouteOpt2Col;
/// <summary><see cref="RoomTypeId"/> of option 0 (so the HUD labels the choice).</summary>
[GhostField] public byte RouteOpt0Type;
[GhostField] public byte RouteOpt1Type;
[GhostField] public byte RouteOpt2Type;
// ---- persisted-meta HUD mirror ----
/// <summary>Runs completed (boss-cleared), mirrored for the HUD from the persisted meta counters.</summary>
[GhostField] public int RunsCompleted;
/// <summary>Deepest room reached across runs, mirrored for the HUD.</summary>
[GhostField] public int MaxDepthReached;
}
}