using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.NetCode; using Unity.Transforms; namespace ProjectM.Server { /// /// 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 + 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. /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial struct CycleDirectorSpawnSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); state.RequireForUpdate(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var serverTick = SystemAPI.GetSingleton().ServerTick; if (!serverTick.IsValid) return; var spawnerEntity = SystemAPI.GetSingletonEntity(); var spawner = SystemAPI.GetComponent(spawnerEntity); var ecb = new EntityCommandBuffer(Allocator.Temp); if (spawner.Prefab != Entity.Null) { var director = ecb.Instantiate(spawner.Prefab); // Place at the base center, preserving the prefab's baked scale/rotation. var xform = SystemAPI.GetComponent(spawner.Prefab); if (SystemAPI.TryGetSingleton(out var anchor)) xform.Position = BaseGridMath.PlotCenter(anchor); ecb.SetComponent(director, xform); // Born-correct load: if the menu staged a save (Continue), apply the ledger AT SPAWN so the // director ghost never serializes an empty ledger to clients (no replication flicker). // // 2026-08-07 audit purge: this block also seeded RunRuntime (HostSalt), RouteCommand, // PortalCommand, MetaCounters, MetaTierState and a born-correct RunInfo mirror. All of that was // the superseded base/expedition run-FSM and meta shop; the director now hosts exactly one thing // — the global resource ledger. bool restoredLedger = false; if (SystemAPI.TryGetSingletonEntity(out var pendingEntity)) { var pending = SystemAPI.GetComponent(pendingEntity); if (pending.HasData != 0) { var srcLedger = SystemAPI.GetBuffer(pendingEntity); var destLedger = ecb.SetBuffer(director); SaveApply.WriteLedger(srcLedger, destLedger); restoredLedger = true; // a save restored the ledger -> do NOT seed starting Ore } ecb.DestroyEntity(pendingEntity); } // 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 (RunDirectorSystem raises it on bank). ecb.AddComponent(director, new SaveRequest { Pending = 0 }); } // One-shot: remove the spawner so RequireForUpdate fails and the system idles. ecb.DestroyEntity(spawnerEntity); ecb.Playback(state.EntityManager); ecb.Dispose(); } } }