using Unity.Entities; using Unity.NetCode; namespace ProjectM.Simulation { /// /// Macro-loop state for "The Aether Cycle": which phase the run is in, the cycle number, and the server /// tick the current (timed) phase ends. Server-authoritative, maintained by CyclePhaseSystem. Currently a /// server-side singleton; the [GhostField]s below are inert until it is moved onto the runtime-spawned /// CycleDirector ghost (when the client HUD is wired), at which point the same struct replicates unchanged. /// The Defend phase is NOT timed — it ends when the base-defense wave is cleared — so PhaseEndTick is only /// meaningful in Expedition/Build (0 during Defend). /// public struct CycleState : IComponentData { /// Current phase (see ). [GhostField] public byte Phase; /// 1-based cycle counter (increments when a new Expedition begins). [GhostField] public int CycleNumber; /// Server tick the current timed phase ends (Expedition/Build only; 0 in Defend). [GhostField] public uint PhaseEndTick; /// Live Husk wave number during Defend, synced from the server-only WaveState by CyclePhaseSystem so the replicated-state-only HUD can show it (holds the last wave number outside Defend; the HUD gates the display to the Defend phase). [GhostField] public int WaveNumber; } /// Phase constants for — the GLOBAL shared posture (a byte, not an enum, for trivial Burst/serialization). Being out on an expedition is per-player presence (server-only RegionTag), NOT a global phase. public static class CyclePhase { // Re-meaned IN PLACE — the byte VALUES are unchanged from the old Expedition/Defend/Build, so the // [GhostField] serializer layout is identical (the const re-mean alone forces no re-bake). slot 0 (was // Expedition) -> Calm; slot 1 (was Defend) -> Siege; slot 2 (was Build) -> retired. /// The persistent, unhurried home base — the DEFAULT posture. No countdown; build/prep at your pace. public const byte Calm = 0; /// The base is under assault by a Husk wave (event-triggered; ends when the wave is cleared). public const byte Siege = 1; // ---- Deprecated aliases (kept so HUD/audio/tests keep compiling through the cut-over; cleaned up later). ---- /// DEPRECATED alias of . public const byte Expedition = 0; /// DEPRECATED alias of . public const byte Defend = 1; /// DEPRECATED, unreachable — the timed Build phase is retired. public const byte Build = 2; /// DEPRECATED — the forced Expedition timer is retired (the loop is player-driven). Kept so existing crefs resolve. public const uint ExpeditionTicks = 3600; /// DEPRECATED — the forced Build timer is retired. Kept so existing crefs resolve. public const uint BuildTicks = 1200; } /// /// Server-only bookkeeping for the run-state machine that must NOT replicate (kept separate from the /// replicated ). Records the wave number captured when the current Siege began plus /// the procedural-expedition-field session epoch (bumped when the expedition region goes empty->occupied so /// the field reseeds per sortie). /// public struct CycleRuntime : IComponentData { /// WaveState.WaveNumber captured the moment the current Siege started (DefendCleared tests > this). public int DefendStartWave; /// Monotonic expedition-field session counter; bumped on the expedition region's empty->occupied edge so each sortie reseeds. RNG seed (never tick math; never 0, via max(1, ...)). public int ExpeditionEpoch; /// The the field was last seeded for (compared by int equality). public int LastSpawnedEpoch; /// Previous-tick expedition occupancy (1 = at least one player out), for the empty<->occupied edge. public byte PrevExpeditionOccupied; /// The a zone-clear Ore reward was last banked for — gates the once-per-epoch reward so two same-tick co-op returners pay once and gate re-entry can't farm (int equality, never tick math). public int LastRewardedEpoch; /// 1 once the current epoch's expedition wave has FULLY spawned and been cleared to zero live zone enemies; reset to 0 on the empty->occupied epoch bump. The reward fires only on a REAL clear. public byte ClearedThisEpoch; } /// /// DR-042 C7b — a SMALL replicated summary of the current expedition objective so the client HUD can show an /// "enemies remaining / cleared — return to claim" readout. Rides the GLOBAL UNTAGGED CycleDirector ghost /// (alongside / GoalProgress) so GhostRelevancy.SetIsIrrelevant never hides it /// cross-region — a base teammate can't see the expedition's own (region-tagged, relevancy-hidden) enemy /// ghosts. SOLE writer: ZoneEnemyDirectorSystem (server, plain group), written ABOVE its early-returns /// (snapshot-above-early-return) so the readout never freezes stale. byte/short, never enum (writer is [BurstCompile]). /// public struct ExpeditionObjective : IComponentData { /// 0 = Idle (no sortie active), 1 = Active (wave in progress), 2 = Cleared (return to claim). [GhostField] public byte State; /// Live zone enemies remaining (alive + not-yet-spawned) while Active; 0 when Idle/Cleared. [GhostField] public short Remaining; } /// State constants for (byte, not enum — Burst/serialization). public static class ExpeditionObjectiveState { public const byte Idle = 0; public const byte Active = 1; public const byte Cleared = 2; } }