LANTERN purge B3+B5: delete the cycle/core/win-lose spine + onboarding; save epoch v7
Deletes CyclePhaseSystem, GoalReachedSystem, CoreDamage/CoreRestore, ThreatDirector, CoreIntegrity/GoalProgress/RunPhase/RunOutcome/ThreatState components, CoreVisualFeedbackSystem, and the whole Client/Onboarding slice (+6 test files). Keepers reworked: RunDirectorSystem (UpdateBefore attr + launch guard + goal/threat bank removed; sole SaveRequest raiser now), CycleDirectorSpawnSystem (ledger/meta host only), WaveSystem UNGATED (waves run wherever a WaveDirector is baked), EnemyAISystem core-fallback stripped, AmbientAudioSystem reworked (bed + run cues; no CycleState gate), MusicSystem RunInfo-only, HudSystem big trim (goal meter, core bar, siege banner, terminal banner, outcome flash, onboarding hook all gone), MetaShop/ClassPrep/AimReticle siege gates dropped, DebugOverlay/ops re-meant (SpawnWave=force next wave, EndSiege=quiet arena; SetCalm/AdvanceGoal/SetHeat retired, bytes reserved), TuningConfig Core knobs retired (ids 20-23 reserved), StorageMath.DrainFraction deleted, HowToPlay copy rewritten. Save epoch v7 (fresh epoch, operator-approved): SaveData drops goal/core/outcome + conveyor/machine-IO fields; MinLoadableVersion=7; PendingSave/PendingStructure trimmed; RollTerminalCampaignForward deleted; SaveStructureScan signature slimmed. 390 tests green; Play world-creation clean (player + waves live, no exceptions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// END-1 — the losable Engine Core. An aggregate base-integrity meter that rides the GLOBAL CycleDirector
|
||||
/// ghost (the untagged ghost already carrying <see cref="CycleState"/>/<see cref="GoalProgress"/>/the resource
|
||||
/// ledger), so it is visible to every player regardless of region with NO new ghost and NO relevancy work — it
|
||||
/// must NEVER be region-tagged (the shared-global-state rule; <c>SetIsIrrelevant</c> would hide it cross-region).
|
||||
/// <para>
|
||||
/// A Husk that breaches to the Core radius drains <see cref="Current"/> and despawns (server-only
|
||||
/// <c>CoreDamageSystem</c>); in Calm the Core regenerates toward <see cref="Max"/> (<c>CoreRestoreSystem</c>) so a
|
||||
/// chipped-but-survived base reads as "we got hurt but we're okay." When <see cref="Current"/> reaches 0 during a
|
||||
/// Siege the SOFT-loss edge fires once in <c>CyclePhaseSystem</c> (the sole Phase writer): the siege ends, the
|
||||
/// shared ledger is drained, the base persists wounded (no rollback — the locked DR-029 fork). <see cref="Max"/> is
|
||||
/// baked from <c>CycleDirectorAuthoring</c>; <see cref="Current"/> is born-correct at spawn (full, or the persisted
|
||||
/// wounded value from a Continue save).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public struct CoreIntegrity : IComponentData
|
||||
{
|
||||
/// <summary>Current integrity (0 = breached/overrun). Server-authoritative; replicated for the HUD bar.</summary>
|
||||
[GhostField] public int Current;
|
||||
|
||||
/// <summary>Integrity ceiling (baked from authoring; not persisted — a restored Core caps at the baked Max).</summary>
|
||||
[GhostField] public int Max;
|
||||
|
||||
/// <summary>Server tick of the most recent overrun breach (0 = never). A TRANSIENT pulse the client HUD
|
||||
/// edge-detects to flash a "BASE OVERRUN" banner — a SOFT loss is non-terminal ("keep playing"), so this is
|
||||
/// the right shape, not a latching run-outcome (the terminal Victory latch is END-2's job).</summary>
|
||||
[GhostField] public uint OverrunTick;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a933c59d7c550d844b615e3672b333f6
|
||||
@@ -3,75 +3,15 @@ using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public struct CycleState : IComponentData
|
||||
{
|
||||
/// <summary>Current phase (see <see cref="CyclePhase"/>).</summary>
|
||||
[GhostField] public byte Phase;
|
||||
// NOTE (LANTERN purge): CycleState/CyclePhase/CycleRuntime (the Calm↔Siege macro-loop) are DELETED.
|
||||
// ExpeditionObjective below is the surviving replicated room-objective readout (live consumers:
|
||||
// RoomEnemyDirectorSystem writes it; RunDirectorSystem/HudSystem read it).
|
||||
|
||||
/// <summary>1-based cycle counter (increments when a new Expedition begins).</summary>
|
||||
[GhostField] public int CycleNumber;
|
||||
|
||||
/// <summary>Server tick the current timed phase ends (Expedition/Build only; 0 in Defend).</summary>
|
||||
[GhostField] public uint PhaseEndTick;
|
||||
|
||||
/// <summary>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).</summary>
|
||||
[GhostField] public int WaveNumber;
|
||||
}
|
||||
|
||||
/// <summary>Phase constants for <see cref="CycleState.Phase"/> — 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.</summary>
|
||||
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.
|
||||
|
||||
/// <summary>The persistent, unhurried home base — the DEFAULT posture. No countdown; build/prep at your pace.</summary>
|
||||
public const byte Calm = 0;
|
||||
|
||||
/// <summary>The base is under assault by a Husk wave (event-triggered; ends when the wave is cleared).</summary>
|
||||
public const byte Siege = 1;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only bookkeeping for the run-state machine that must NOT replicate (kept separate from the
|
||||
/// replicated <see cref="CycleState"/>). 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).
|
||||
/// </summary>
|
||||
public struct CycleRuntime : IComponentData
|
||||
{
|
||||
/// <summary>WaveState.WaveNumber captured the moment the current Siege started (DefendCleared tests > this).</summary>
|
||||
public int DefendStartWave;
|
||||
|
||||
/// <summary>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, ...)).</summary>
|
||||
public int ExpeditionEpoch;
|
||||
|
||||
/// <summary>The <see cref="ExpeditionEpoch"/> the field was last seeded for (compared by int equality).</summary>
|
||||
public int LastSpawnedEpoch;
|
||||
|
||||
/// <summary>Previous-tick expedition occupancy (1 = at least one player out), for the empty<->occupied edge.</summary>
|
||||
public byte PrevExpeditionOccupied;
|
||||
|
||||
/// <summary>The <see cref="ExpeditionEpoch"/> 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).</summary>
|
||||
public int LastRewardedEpoch;
|
||||
|
||||
/// <summary>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.</summary>
|
||||
public byte ClearedThisEpoch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="CycleState"/> / GoalProgress) so GhostRelevancy.SetIsIrrelevant never hides it
|
||||
/// "enemies remaining / cleared — return to claim" readout. Rides the GLOBAL UNTAGGED director ghost 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]).
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Long-arc progress toward the goal ("reach THEM"). Lives on the GLOBAL CycleDirector ghost (relevant in
|
||||
/// every region, alongside CycleState + the resource ledger), so it is visible to all players regardless
|
||||
/// of region. Sole PRODUCTION writer (DR-042): <c>ExpeditionGateSystem</c> increments <see cref="Charge"/> by
|
||||
/// one per cleared EXPEDITION (on the player's return). <c>GoalReachedSystem</c> only READS the Charge==Target
|
||||
/// edge to arm the climactic final siege. (<c>DebugCommandReceiveSystem</c> is a manual dev-op writer.) The HUD
|
||||
/// observes it for a progress bar.
|
||||
/// </summary>
|
||||
public struct GoalProgress : IComponentData
|
||||
{
|
||||
/// <summary>Accumulated progress.</summary>
|
||||
[GhostField] public int Charge;
|
||||
|
||||
/// <summary>Charge required to reach the goal.</summary>
|
||||
[GhostField] public int Target;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1f60b3396850074ca0e44b831b5980c
|
||||
@@ -1,61 +0,0 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// END-2 — server-only marker of which run-phase the macro loop is in. Lives on the GLOBAL CycleDirector
|
||||
/// entity beside <see cref="CycleState"/>/<see cref="CycleRuntime"/>/<see cref="ThreatState"/>; NOT replicated
|
||||
/// (the client never needs to distinguish "the final siege is armed" — the larger wave + telegraph already read
|
||||
/// as escalation; the client shows the terminal banner from the replicated <see cref="RunOutcome"/> instead).
|
||||
/// SINGLE writer: <c>GoalReachedSystem</c> flips <see cref="RunPhaseId.Normal"/> ->
|
||||
/// <see cref="RunPhaseId.FinalDefense"/> exactly once when <see cref="GoalProgress.Charge"/> reaches Target.
|
||||
/// Added at spawn by <c>CycleDirectorSpawnSystem</c> (like CycleRuntime/ThreatState), so it is server-world-only
|
||||
/// and never on the ghost serializer (no re-hash). A <c>byte</c> (never an enum) so a Bursted reader can't trip
|
||||
/// the cross-assembly-enum Burst ICE.
|
||||
/// </summary>
|
||||
public struct RunPhase : IComponentData
|
||||
{
|
||||
public byte Value;
|
||||
}
|
||||
|
||||
/// <summary>Phase constants for <see cref="RunPhase.Value"/> (bytes — never an enum on a Bursted path).</summary>
|
||||
public static class RunPhaseId
|
||||
{
|
||||
/// <summary>Normal play: scheduled / post-expedition sieges arm; the goal meter climbs +1 per survived siege.</summary>
|
||||
public const byte Normal = 0;
|
||||
|
||||
/// <summary>The goal cap was reached: the larger FINAL siege is armed/running. No further sieges arm.</summary>
|
||||
public const byte FinalDefense = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// END-2 — the terminal run outcome: the LATCHING win/lose state, REPLICATED so the client HUD shows the
|
||||
/// victory/loss banner by simply observing it (no fragile client-side reconstruction). Rides the GLOBAL
|
||||
/// untagged CycleDirector ghost (relevant to every connection in every region — it must NEVER be region-tagged;
|
||||
/// the shared-global-state rule), one <c>[GhostField] byte</c> alongside <see cref="CoreIntegrity"/>/
|
||||
/// <see cref="GoalProgress"/>. SINGLE writer: <c>CyclePhaseSystem</c> latches <see cref="RunOutcomeId.Victory"/>
|
||||
/// (final siege cleared) or <see cref="RunOutcomeId.Loss"/> (Core breached during the final siege). Once it is
|
||||
/// non-<see cref="RunOutcomeId.InProgress"/> the run HALTS (GoalReachedSystem + ThreatDirectorSystem stop arming;
|
||||
/// CoreRestoreSystem stops regen). Baked onto the prefab so it is part of the ghost (adding this <c>[GhostField]</c>
|
||||
/// re-hashes the CycleDirector ghost -> one re-bake); born-correct at spawn (InProgress for a New Game, or the
|
||||
/// persisted value on Continue — SaveData v5).
|
||||
/// </summary>
|
||||
public struct RunOutcome : IComponentData
|
||||
{
|
||||
[GhostField] public byte Value;
|
||||
}
|
||||
|
||||
/// <summary>Outcome constants for <see cref="RunOutcome.Value"/> (bytes — never an enum on a Bursted/serialized path).</summary>
|
||||
public static class RunOutcomeId
|
||||
{
|
||||
/// <summary>The run is live (no terminal result yet).</summary>
|
||||
public const byte InProgress = 0;
|
||||
|
||||
/// <summary>The final siege was survived — the Engine holds. Terminal; the run halts.</summary>
|
||||
public const byte Victory = 1;
|
||||
|
||||
/// <summary>The Core was breached during the final siege — overrun. Terminal; the run halts.</summary>
|
||||
public const byte Loss = 2;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ce481dc9a135834fa6d59882895b0f5
|
||||
@@ -1,88 +0,0 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Baked, server-only tuning for the composite ThreatDirector (the data-driven base-attack scheduler). Lives
|
||||
/// on the global CycleDirector entity but is NOT a [GhostField] — the server alone decides when a siege
|
||||
/// fires; clients learn about it only through the replicated <see cref="CycleState.Phase"/> flip to Siege.
|
||||
/// Flat scalar fields (never an enum/array — dodges the MCP authoring-drop gotcha + stays Burst-trivial). This
|
||||
/// slice wires only the POST-EXPEDITION source; the Heat/Schedule fields are baked-but-inert so those sources
|
||||
/// drop in later additively with NO re-bake (server-only layout, not a ghost serializer change).
|
||||
/// </summary>
|
||||
public struct ThreatConfig : IComponentData
|
||||
{
|
||||
// ---- Post-expedition retaliation (the only source wired this slice) ----
|
||||
|
||||
/// <summary>1 = a completed expedition (a player returning to base) can draw a retaliation siege.</summary>
|
||||
public byte PostExpeditionEnabled;
|
||||
|
||||
/// <summary>Telegraph/arming delay (server ticks) between the trigger and the siege actually spawning.</summary>
|
||||
public uint PostExpeditionDelayTicks;
|
||||
|
||||
/// <summary>Siege size floor (Husk count) for a post-expedition retaliation.</summary>
|
||||
public int SizeBase;
|
||||
|
||||
/// <summary>Extra Husks per unit of resources hauled back this run (0 = a flat <see cref="SizeBase"/> siege).</summary>
|
||||
public int SizePerExpeditionResource;
|
||||
|
||||
/// <summary>How a pending siege starts (see <see cref="ThreatStartCondition"/>).</summary>
|
||||
public byte StartCondition;
|
||||
|
||||
/// <summary>Max server ticks a Siege may run before it auto-collapses (remaining Husks culled) so an unattended/empty-base siege can never soft-lock. 0 = no cap.</summary>
|
||||
public uint SiegeTimeoutTicks;
|
||||
|
||||
// ---- Reserved, present-but-inert this slice (additive later, no re-bake) ----
|
||||
|
||||
public byte HeatEnabled;
|
||||
public float HeatPerTickAtBase;
|
||||
public float HeatPerHarvest;
|
||||
public float HeatThreshold;
|
||||
public byte ScheduleEnabled;
|
||||
public uint ScheduleIntervalTicks;
|
||||
/// <summary>Extra Husks per surviving wave for a SCHEDULED base siege (size = SizeBase + this*WaveNumber). 0 = flat SizeBase.</summary>
|
||||
public int ScheduleSizePerWave;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Start-condition constants for <see cref="ThreatConfig.StartCondition"/> (bytes — never an enum, never in an RPC).</summary>
|
||||
public static class ThreatStartCondition
|
||||
{
|
||||
/// <summary>DEFAULT: arm via the telegraph countdown (<see cref="ThreatState.ArmTick"/>) then fire — even at an empty base.</summary>
|
||||
public const byte Immediate = 0;
|
||||
|
||||
/// <summary>Hold the pending siege until ≥1 player is in the base region OR the arm tick + a grace window elapses (bounded — never a soft-lock).</summary>
|
||||
public const byte RequirePlayerAtBase = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only runtime state of the ThreatDirector, on the global CycleDirector entity beside
|
||||
/// <see cref="CycleRuntime"/>. NOT replicated. <see cref="PendingSiegeSize"/> is the single documented entry
|
||||
/// point: any source (post-expedition, dev tools, later Heat/Schedule) sets it; <c>CyclePhaseSystem</c>
|
||||
/// consumes it on the Calm→Siege edge and zeroes it. All stored ticks are wrap-safe (TickUtil.NonZero +
|
||||
/// NetworkTick compares), never raw uint.
|
||||
/// </summary>
|
||||
public struct ThreatState : IComponentData
|
||||
{
|
||||
/// <summary>Husk count of the armed siege; 0 = none pending. Consumed (zeroed) by CyclePhaseSystem at Siege entry.</summary>
|
||||
public int PendingSiegeSize;
|
||||
|
||||
/// <summary>Server tick the pending siege fires (telegraph). 0 = fire as soon as seen. Routed through TickUtil.NonZero.</summary>
|
||||
public uint ArmTick;
|
||||
|
||||
/// <summary>Server tick the current Siege began (0 = not under siege). The bounded-resolution timeout measures from here (TickUtil.NonZero) so an unattended/empty-base siege can never soft-lock.</summary>
|
||||
public uint SiegeStartTick;
|
||||
|
||||
/// <summary>Count of expeditions completed (a player returned to base). Drives the post-expedition source + stats.</summary>
|
||||
public int ExpeditionsCompleted;
|
||||
|
||||
/// <summary>Return events the gate has signalled but the director has not yet consumed (the gate teleports the player out of its radius, so one increment per return — natural de-dup).</summary>
|
||||
public int PendingReturns;
|
||||
|
||||
/// <summary>Accumulated heat (inert this slice; the Heat source reads/writes it later).</summary>
|
||||
public float Heat;
|
||||
|
||||
/// <summary>Next scheduled-siege tick (inert this slice; the Schedule source uses it later). TickUtil.NonZero when used.</summary>
|
||||
public uint NextScheduledTick;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e66b1e7c715ceb418459c9323853271
|
||||
Reference in New Issue
Block a user