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:
@@ -8,13 +8,13 @@ using Unity.Transforms;
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only, one-shot spawner for the GLOBAL cycle-director ghost (mirrors SharedStorageSpawnSystem,
|
||||
/// but MINUS the RegionTag — the director must stay global so GhostRelevancy keeps it relevant to every
|
||||
/// region). On its first update it reads the baked <see cref="CycleDirectorSpawner"/> + NetworkTime,
|
||||
/// instantiates the ghost, initializes <see cref="CycleState"/> (Expedition, cycle 1, PhaseEndTick =
|
||||
/// now + the initial phase delay), adds the server-only <see cref="CycleRuntime"/>, and
|
||||
/// places it at the base center (preserving the prefab's baked LocalTransform scale — FromPosition would
|
||||
/// reset the replicated Scale GhostField), then destroys the spawner so it idles.
|
||||
/// Server-only, one-shot spawner for the GLOBAL director ghost (mirrors SharedStorageSpawnSystem, but MINUS
|
||||
/// the RegionTag — the director must stay global so GhostRelevancy keeps it relevant to every region). On its
|
||||
/// first update it reads the baked <see cref="CycleDirectorSpawner"/> + NetworkTime, instantiates the ghost
|
||||
/// — the shared-ledger / RunInfo / meta host (its old cycle/siege/goal/core state is retired, LANTERN purge) —
|
||||
/// applies a menu-staged save born-correct, and places it at the base center (preserving the prefab's baked
|
||||
/// LocalTransform scale — FromPosition would reset the replicated Scale GhostField), then destroys the
|
||||
/// spawner so it idles.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
@@ -49,24 +49,11 @@ namespace ProjectM.Server
|
||||
xform.Position = BaseGridMath.PlotCenter(anchor);
|
||||
ecb.SetComponent(director, xform);
|
||||
|
||||
// Boot the run-state in Calm (the persistent default) — no timer; ThreatDirector arms sieges.
|
||||
ecb.SetComponent(director, new CycleState
|
||||
{
|
||||
Phase = CyclePhase.Calm,
|
||||
CycleNumber = 1,
|
||||
PhaseEndTick = 0u,
|
||||
});
|
||||
ecb.AddComponent(director, new CycleRuntime { DefendStartWave = 0 });
|
||||
ecb.AddComponent(director, new ThreatState());
|
||||
// END-2: server-only run-phase marker (Normal until the goal cap arms the final siege). Added at
|
||||
// spawn like CycleRuntime/ThreatState (never on the ghost serializer). RunOutcome is baked on the prefab.
|
||||
ecb.AddComponent(director, new RunPhase { Value = RunPhaseId.Normal });
|
||||
|
||||
// Expedition redesign: run-FSM working state + the co-op route first-commit latch + the persisted
|
||||
// meta counters — ALL added UNCONDITIONALLY at spawn (the CycleRuntime/ThreatState/RunPhase idiom;
|
||||
// D-F2: a New-Game boot must have the components the bank block reads; Continue restores VALUES only,
|
||||
// inside the HasData block — Step 12b). HostSalt starts a fixed non-tick seed lineage (bumped per
|
||||
// launch); SaveData v6 folds persisted RunsCompleted in at restore so cross-session runs diverge.
|
||||
// meta counters — ALL added UNCONDITIONALLY at spawn (D-F2: a New-Game boot must have the components
|
||||
// the bank block reads; Continue restores VALUES only, inside the HasData block — Step 12b).
|
||||
// HostSalt starts a fixed non-tick seed lineage (bumped per launch); the save folds persisted
|
||||
// RunsCompleted in at restore so cross-session runs diverge.
|
||||
ecb.AddComponent(director, new RunRuntime { HostSalt = 0x5EED0001u });
|
||||
ecb.AddComponent(director, default(RouteCommand));
|
||||
ecb.AddComponent(director, default(PortalCommand)); // DR-046 room-exit portal interact latch
|
||||
@@ -74,47 +61,25 @@ namespace ProjectM.Server
|
||||
ecb.AddComponent(director, default(MetaCounters));
|
||||
|
||||
// Born-correct load: if the menu staged a save (Continue), apply it AT SPAWN so the director
|
||||
// ghost never serializes an empty ledger to clients (no replication flicker).
|
||||
// DR-042 C6c: a NEW game seeds starting Ore below; a restored save (Continue) keeps its ledger.
|
||||
bool restoredLedger = false;
|
||||
// ghost never serializes a default GoalProgress / empty ledger to clients (no replication flicker).
|
||||
if (SystemAPI.TryGetSingletonEntity<PendingSave>(out var pendingEntity))
|
||||
{
|
||||
var pending = SystemAPI.GetComponent<PendingSave>(pendingEntity);
|
||||
if (pending.HasData != 0)
|
||||
{
|
||||
// END-2: clamp the restored Target to the baked run-length so a pre-v5 save carrying the old
|
||||
// Target=10 still honours the slice's baked Target=4 (the final siege stays reachable).
|
||||
int bakedTarget = SystemAPI.HasComponent<GoalProgress>(spawner.Prefab)
|
||||
? SystemAPI.GetComponent<GoalProgress>(spawner.Prefab).Target : pending.GoalTarget;
|
||||
int restoredTarget = pending.GoalTarget > 0 && pending.GoalTarget < bakedTarget
|
||||
? pending.GoalTarget : bakedTarget;
|
||||
ecb.SetComponent(director, new GoalProgress { Charge = pending.GoalCharge, Target = restoredTarget });
|
||||
var srcLedger = SystemAPI.GetBuffer<PendingSaveLedgerRow>(pendingEntity);
|
||||
var destLedger = ecb.SetBuffer<StorageEntry>(director);
|
||||
SaveApply.WriteLedger(srcLedger, destLedger);
|
||||
restoredLedger = true; // a save restored the ledger -> do NOT seed starting Ore (C6c)
|
||||
|
||||
// END-1: born-correct the Engine Core. Max comes from the BAKED prefab (never the save); a
|
||||
// persisted wounded Current (>0) restores clamped to Max, else (0 = pre-v4 save) born full.
|
||||
if (SystemAPI.HasComponent<CoreIntegrity>(spawner.Prefab))
|
||||
{
|
||||
var bakedCore = SystemAPI.GetComponent<CoreIntegrity>(spawner.Prefab);
|
||||
int restoredCore = pending.CoreCurrent > 0
|
||||
? (pending.CoreCurrent < bakedCore.Max ? pending.CoreCurrent : bakedCore.Max)
|
||||
: bakedCore.Max;
|
||||
ecb.SetComponent(director, new CoreIntegrity { Current = restoredCore, Max = bakedCore.Max, OverrunTick = 0u });
|
||||
}
|
||||
|
||||
// END-2: born-correct the terminal run outcome (a won/lost run loads finished + halted; a pre-v5
|
||||
// save / New Game = 0 -> InProgress). Independent of the Core -> NOT nested in the CoreIntegrity guard.
|
||||
ecb.SetComponent(director, new RunOutcome { Value = pending.RunOutcome });
|
||||
|
||||
// v6: restore the permanent meta — counters (VALUES only; the component was added
|
||||
// unconditionally above, D-F2), the tier record (SetBuffer replaces the baked-empty
|
||||
// [GhostField] buffer pre-Playback — the StorageEntry idiom — rows VERBATIM incl. unknown
|
||||
// ids), the born-correct RunInfo HUD mirror (the spawn-time exception to RunDirector's
|
||||
// sole-writer rule, like CycleState/RunOutcome above), and the HostSalt fold (cross-session
|
||||
// first-run maps diverge once you've banked clears — the promise at the RunRuntime add).
|
||||
// sole-writer rule), and the HostSalt fold (cross-session first-run maps diverge once
|
||||
// you've banked clears — the promise at the RunRuntime add).
|
||||
ecb.SetComponent(director, new MetaCounters
|
||||
{
|
||||
RunsCompleted = pending.RunsCompleted,
|
||||
@@ -140,13 +105,13 @@ namespace ProjectM.Server
|
||||
ecb.DestroyEntity(pendingEntity);
|
||||
}
|
||||
|
||||
// DR-042 C6c: NEW game only (no restored ledger) -> seed a little Ore so the build loop isn't a cold
|
||||
// deadlock (a turret needs Charge from a Fabricator that costs Ore you haven't mined yet). Appended
|
||||
// BEFORE Playback so the ghost first-serializes WITH the seed (no empty-ledger replication flicker).
|
||||
// DR-042 C6c: NEW game only (no restored ledger) -> seed a little Ore so the build loop isn't a
|
||||
// cold start with nothing to place. Appended BEFORE Playback so the ghost first-serializes WITH
|
||||
// the seed (no empty-ledger replication flicker).
|
||||
if (!restoredLedger)
|
||||
ecb.AppendToBuffer(director, new StorageEntry { ItemId = ResourceId.Ore, Count = Tuning.StartingOre });
|
||||
|
||||
// Host-only autosave flag; SaveWriteSystem consumes it on the Siege->Calm checkpoint.
|
||||
// Host-only autosave flag; SaveWriteSystem consumes it (RunDirectorSystem raises it on bank).
|
||||
ecb.AddComponent(director, new SaveRequest { Pending = 0 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user