Game Scene Split up

This commit is contained in:
2026-06-04 13:45:46 -07:00
parent dbc4a92a86
commit 16b01bec38
49 changed files with 11976 additions and 188 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
tags:
- moc
- home
updated: 2026-06-03
updated: 2026-06-04
permalink: gamevault/00-home/home
---
@@ -15,8 +15,8 @@ Multiplayer game on **Unity DOTS** (Entities) + **Netcode for Entities** (server
- **Vision** → [[Pillars]] — design pillars & locked decisions · [[Identity]] — the fiction (sci-fi frontier colony)
- **Game Design** → [[Systems_Index]] — per-system design docs
- **Roadmap** → [[Milestones]] · [[Backlog]]
- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-04_Polish_Backlog_Pass]])
- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-016 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-016_Stage_G_Combat_Gameplay]]
- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-04_M8_Persistent_Base_Player_Driven_Pacing]])
- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-017 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-017_Persistent_Base_Player_Driven_Pacing]]
- **Meta** → [[Documentation_Protocol]] · [[Tags]]
- **Templates** → [[Session_Log_Template]] · [[Decision_Record_Template]]
+2 -2
View File
@@ -15,7 +15,7 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com
- [x] Upgrade Unity 6.4 → 6.6 — done (now `6000.6.0a6`). Entities/Collections/Graphics → 6.5.0; **Netcode → 6.6.0 and Physics → 6.5.0 also renumbered into the editor line** (not independent 1.x as [[DR-001_Netcode_Test_Harness]] assumed). See [[2026-05-30_M1_Player_Slice]].
- [x] Define the core gameplay loop and the first predicted player ghost — delivered as M1 ([[2026-05-30_M1_Player_Slice]]).
- [x] **Re-validate the M1 play-tick on a stable Unity 6.x** — moot/subsumed 2026-06-04: M1M6 are all runtime-validated on the stable **6.4.7** line (the 6.6 alpha netcode bug never affected 6.4.7). The original "blocked on 6.6 alpha" framing ([[DR-002_Unity66_Alpha_Netcode_Transport]]) no longer applies.
- [ ] Replace template `SampleScene` with a dedicated bootstrap scene + gameplay subscene.
- [x] Replace template `SampleScene` with a dedicated bootstrap scene + gameplay subscene**done 2026-06-04** ([[DR-017_Persistent_Base_Player_Driven_Pacing]]): dedicated **`Game.unity`** (duplicated from SampleScene to preserve the SubScene `_SceneAsset`/`_SceneGUID` Hash128 MCP would drop) is now `EditorBuildSettings` index 0; a minimal **`DevSandbox.unity`** carries the dev overlay. SampleScene retained as a reference (retire later).
- [x] Optional template cleanup: remove `com.unity.visualscripting`, `Assets/TutorialInfo/`, `Assets/Readme.asset`**done 2026-06-03** (pre-M6 cleanup; see the "2026-06-03 Visual & Controls Polish" section below + [[2026-06-03_Pre_M6_Cleanup]]). Duplicate of the now-checked item there.
- [x] Decide **relay provider** before M4 — resolved: **Direct IP/LAN now, Unity Relay later** ([[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]).
- [x] Decide home-base **grid 2D vs 3D** before M6 — resolved 2026-06-02: **planar single-level `int2` grid**, CellSize 1.0, 32×32 plot (full 3D/stacked deferred). Locked in `BaseGridMath` — [[DR-008_M5_HomeBase_BaseLayer_Storage]].
@@ -28,7 +28,7 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com
- [ ] **M3 follow-up — UI/icon/description pipeline** for abilities (managed lookup keyed by `AbilityId`, off the blob). Deferred from M3 ([[2026-05-31_M3_Data_Driven_Abilities]]).
- [x] **M3 follow-up — timed / removable modifiers** — done 2026-06-04 ([[DR-016_Stage_G_Combat_Gameplay]]): a SEPARATE server-only `TimedModifier{SourceId,UntilTick}` buffer (keeps the replicated `StatModifier` layout byte-identical → no re-bake) + `TimedModifierExpirySystem` (expiry on `NetworkTick`) + `TimedModifierUtil.RemoveBySourceId` (clear-by-type). +4 EditMode tests.
- [ ] **M3 follow-up — multi-prefab abilities** (a per-ability *different* projectile ghost) needs `ProjectileClassificationSystem` generalized beyond the single shared prefab.
- [ ] **M3 follow-up — standalone-server debug modifier path** via `IRpcCommand` (current `DebugModifierInjectionSystem` is in-editor single-process only).
- [x] **M3 follow-up — standalone-server debug modifier path** via `IRpcCommand` **done 2026-06-04** ([[DR-017_Persistent_Base_Player_Driven_Pacing]]): the dev-tools `DebugCommandRequest` RPC (unconditional wire type; `#if UNITY_EDITOR` send/receive) drives modifiers/resources/state over a real connection, superseding the in-editor-only static-poke `DebugModifierInjectionSystem`. (Still editor-gated; flip the systems' guard to `DEVELOPMENT_BUILD` to ship in a dev player build.)
- [x] **M3 follow-up — rate-limited turning** — done 2026-06-03 (pre-M6 cleanup): `PlayerAimSystem` now rotates `PlayerFacing` toward the aim target at `EffectiveCharacterStats.TurnRateRadiansPerSec` (authored 720°/s) instead of snapping; deterministic in the predicted loop (fixed-step `dt`, replays on rollback). [[2026-06-03_Pre_M6_Cleanup]].
- [ ] **M3 polish — pickup visuals** (primitive sphere/default material currently); pickup auto-grant feel (continuous overlap).
- [ ] **M5 follow-up — base/expedition subscene split + streaming (Option C)****superseded 2026-06-03 by [[DR-013_M6_Aether_Cycle_Region_Split]]** (coordinate-region + per-connection `GhostRelevancy` delivered the split without `SceneSystem` streaming). Kept only as a note for a future larger-world milestone where true async streaming is wanted; do NOT build streaming now. Original framing: the persistent-space split the locked world design ultimately needs (`SceneSystem.LoadSceneAsync`/`UnloadScene`, per-world load on the listen-server, enter-expedition/return-to-base transition). Deferred to its own world-architecture milestone — M6/M7 only need the anchor + grid, now done ([[DR-008_M5_HomeBase_BaseLayer_Storage]]). The physics-in-prediction + base-layer slices of M5 are done ([[DR-006_M5_Physics_In_Prediction]], [[DR-008_M5_HomeBase_BaseLayer_Storage]]).
+2 -1
View File
@@ -2,7 +2,7 @@
tags:
- roadmap
- milestones
updated: 2026-06-03
updated: 2026-06-04
permalink: gamevault/06-roadmap/milestones
---
@@ -21,6 +21,7 @@ permalink: gamevault/06-roadmap/milestones
| **— 2026-06-03 Pre-M6 cleanup —** | Loose-ends pass before M6: vault roadmap reconcile, Unity-template + orphaned-material removal, rate-limited turning, console/runtime health gate. | ✅ Done 2026-06-03 — [[2026-06-03_Pre_M6_Cleanup]] |
| **M6 — The Aether Cycle (core loop)** | Reframed from "grid build placement" into the first vertical slice of the **core game loop**: Expedition (gather) → Defend (wave) → Build/Charge (spend), persistent base + procedural sorties, escalating toward a goal. Build placement is now Stage 3 of this milestone. | 🚧 In progress 2026-06-03 — **Stages 04 done + runtime-validated** on 6.4.7 (M6 core loop systems complete): **base/expedition split via coordinate-region + `GhostRelevancy`** (player transit despawns/re-grants the other region's ghosts; server==client); a **server phase-director** (Expedition→Defend→Build→Expedition auto-cycle, cycle 1→2, Husk `WaveSystem` only in Defend, escalation 4→6); and **resources + harvest** — a **global CycleDirector ghost** carrying the replicated `CycleState` + a shared resource **ledger** (relevant in every region, unlike the base storage), a procedural **expedition field** (8 resource-node ghosts seeded per cycle, region-scoped), and a tunnel-safe **harvest** sweep depositing into the ledger; client **HUD** shows phase + resource counts. Supersedes DR-008's "split requires streaming" framing. **Stage 3** (generic automation-ready **structure model** + data-driven catalog + grid **build-placement** RPC with co-op-atomic commit + a hitscan **turret** that auto-defends + **ability tiers** via a bounded StatModifier) and **Stage 4 goal meter** are **done + validated** (turret placed/Ore-deducted/replicated; two same-tick requests → one build; turrets killed the wave; ability damage 20→30 bounded; goal increments per cycle). Disk-persistence **writer deferred to post-M7** (M7-additive surface — tick fields + frozen schema — baked now); the structure model is the M7 production-chain foundation. Playable walk-in-gate loop with build/spend, visible in the HUD. — [[DR-014_M6_Build_Structures_Automation_Foundation]] — [[DR-013_M6_Aether_Cycle_Region_Split]], [[2026-06-03_M6_Aether_Cycle_CoreLoop]] |
| **— 2026-06-04 Polish & backlog-clear pass —** | Comprehensive *sequential* polish across hygiene/reconcile, system-level tests, HUD (TMP + replicated wave number), procedural audio + combat juice, ghost-prop reskin + post-processing, new gameplay content, controls/UX, and a validation-harness + operator handoff. Clears the open [[Backlog]]. | 🚧 In progress 2026-06-04 |
| **M8 — Persistent base & player-driven pacing** | Replace the forced-timer cycle with a persistent **Calm** base (no countdown) + **player-initiated** expeditions + **event-triggered** sieges (composite **ThreatDirector**, post-expedition retaliation); **dev-tools-over-RPC** (spawn/force-siege/grant/teleport/god-mode) + a dedicated **Game** scene + **DevSandbox**. | ✅ Done 2026-06-04 — runtime-validated on 6.4.7 (server+client, focused editor): boots into **Calm** (no timer); a returning player arms a retaliation siege (Gate→ThreatDirector→CyclePhase→Wave), the atomic WaveState seed spawns the **exact** configured size, **server==client** (phase + husk ghosts replicated); dev overlay drives the real server RPCs (unconditional wire type → handshake intact); god-mode baked present+disabled; `Game.unity` boots the calm base with the new "AT BASE — deploy when ready" HUD. EditMode **137/137**. Supersedes the M6 forced-timer rhythm. — [[DR-017_Persistent_Base_Player_Driven_Pacing]], [[2026-06-04_M8_Persistent_Base_Player_Driven_Pacing]] |
| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ⬜ |
Promote items from [[Backlog]] here when committed.
@@ -0,0 +1,64 @@
---
date: 2026-06-04
type: session
tags: [session, core-loop, pacing, netcode, dev-tools, scenes, m8]
---
# Session 2026-06-04 — M8: Persistent base & player-driven pacing
## Goal
Operator: *"I don't like the current pacing — the home base should feel persistent and like you aren't in a rush... when you are ready and have the inventory and prep you need for an expedition, you go. Attacks on the base will be triggered based on various events and timelines (possibly after an expedition). Built out properly — also make a separate scene and various dev tools to test/debug/validate (spawn waves, control game state, grant things). The main gameplay scene should feel like an actual game."*
Replaces the M6 **forced-timer treadmill** (Expedition 60s → Defend → Build 20s → repeat) with a **player-driven loop**: persistent **Calm** base by default → deploy when ready → **event-triggered Siege** (post-expedition retaliation) → defend → Calm. Decisions chosen by operator: **composite ThreatDirector**, **full staged build**, **new Game scene + DevSandbox**. Full rationale + the de-risked architecture: [[DR-017_Persistent_Base_Player_Driven_Pacing]].
## Process
- Phase-1 exploration (3 Explore agents: run-state/wave blast radius · scene/subscene/bootstrap wiring · dev-tools/RPC/test precedents).
- Phase-2 **adversarial design review** (Workflow: 3 critics — netcode/determinism · reuse/scope · dev-tooling/scene/build-gotchas → synthesis). Caught the **co-op-breaking global-Expedition-phase flaw** (one byte can't represent player-A-out/player-B-home) and steered: reuse-symbols-re-mean-bytes, atomic WaveState siege seed, server-only ThreatConfig/State, duplicate-scenes-not-scratch, unconditional RPC type.
- Plan approved → 5 staged steps, each `compile → read_console → EditMode → (Play where relevant)`.
## Done
### Stage 1 — Calm/Siege run-state + presence + boot-Calm + goal decouple
- `CycleComponents.cs`: re-meaned `CyclePhase``Calm=0`/`Siege=1` (+ deprecated `Expedition`/`Defend`/`Build` aliases; byte values unchanged → **no serializer re-bake**); `CycleRuntime``ExpeditionEpoch`/`LastSpawnedEpoch`/`PrevExpeditionOccupied`.
- `CyclePhaseSystem` rewritten to the **Calm↔Siege machine** (consume `ThreatState.PendingSiegeSize` → atomic `WaveState{Spawning, RemainingToSpawn=size, WaveNumber+1}` seed → Siege; exit on `DefendCleared` → Calm + goal `+1`). Boot = Calm (`CycleDirectorAuthoring` default + `CycleDirectorSpawnSystem` stamp `Phase=Calm, PhaseEndTick=0`, adds `ThreatState`). `ExpeditionFieldSystem` re-keyed off expedition-region presence + epoch. `ExpeditionGateSystem` hack deleted → increments `ThreatState.PendingReturns` on return. `WaveSystem` gate `!= Siege`.
- Tests: rewrote `CyclePhaseSystemTests` (Calm holds; PendingSiege→Siege exact size; Siege→Calm + goal once; **co-op split-presence keeps global phase Calm**); `ExpeditionGateSystemTests` → return-signals-once. **137 baseline preserved.**
### Stage 2 — Composite ThreatDirector
- `ThreatComponents.cs` (`ThreatConfig` + `ThreatState` + `ThreatStartCondition`, server-only, Heat/Schedule inert). `ThreatDirectorSystem` (Gate→ThreatDirector→CyclePhase order): post-expedition source arms a `SizeBase` siege after a telegraph; bounded-timeout collapse (no soft-lock). `ThreatConfig` baked via `CycleDirectorAuthoring` (inspector-tunable flat fields).
- Tests: `ThreatDirectorSystemTests` ×5 (return→pending; multi-return de-dup; size=config-not-curve; Immediate arms via ArmTick; empty-base auto-resolves bounded).
### Stage 3 — Dev tools over RPC + god-mode
- Unconditional `DebugCommandRequest`/`DebugOp` (Simulation); `DebugGodMode` enableable. `DebugCommandReceiveSystem` (server `#if UNITY_EDITOR` switch, reuses StorageMath/StatModifier/RegionMath/wave+cycle singletons, sender resolve). `DebugCommandSendSystem` (client static-queue → RPC, headless-friendly) + `DebugOverlay` MonoBehaviour (IMGUI). `HealthApplyDamageSystem` god-mode guard; `PlayerAuthoring` bakes `DebugGodMode` present+disabled; `AimPresentation.ForceCursorVisible` (overlay clickability); `[RuntimeInitializeOnLoadMethod]` resets.
- Tests: `DebugCommandReceiveSystemTests` ×4 + god-mode skip-damage. **137/137.**
### Stage 4 — Scenes
- Duplicated SampleScene → **`Game.unity`** (clean main, boots Calm) + **`DevSandbox.unity`** (Synty world disabled, `~DevTools`+overlay). Verified the SubScene `_SceneAsset` guid `9dc8ce2e…` + `_SceneGUID.x=3807153369` survived. `Game.unity` added to `EditorBuildSettings` index 0.
### Stage 5 — Feel pass
- `HudSystem`: Calm/Siege labels + colors, "AT BASE — deploy through the gate when you're ready" prompt, "INCURSION IN Ns" telegraph (reuses `PhaseEndTick=ArmTick` countdown), dropped vestigial "CYCLE N". `AmbientAudioSystem`: Calm/Siege stinger + drone swell mapping.
### Validation
- **EditMode 137/137** (was 127; rewrote/added per stage, no regressions), console clean.
- **Focused-editor Play (server+client), SampleScene + Game.unity:** boot **Calm** (`Phase=0, PhaseEndTick=0`); re-bakes confirmed (`ThreatConfig{SizeBase=5,Delay=300,Timeout=3600}`, player `DebugGodMode` present+disabled); armed siege → **Siege**, **exactly 4 Husks** spawned, `WaveState{Spawning,Remaining=0}`, pending consumed; **server==client** (Phase + husk ghosts replicated). `Game.unity` screenshot: calm base + "AT BASE — deploy…" HUD. (`Assets/Screenshots/M8_Game_Calm_Boot.png`.)
## Decisions
- **Created [[DR-017_Persistent_Base_Player_Driven_Pacing]]** (supersedes the M6 forced-timer rhythm of [[DR-013_M6_Aether_Cycle_Region_Split]]/[[DR-014_M6_Build_Structures_Automation_Foundation]]; reuses their netcode infra).
## Build gotchas (→ [[CLAUDE]] addendum)
- A **system-ordering cycle is invisible to plain-Entities EditMode tests** (systems registered individually); only `ComponentSystemSorter` throws at Play/world-creation. Re-audit *existing* `[Update*]` attributes when reordering — caught the gate's stale `[UpdateAfter(CyclePhaseSystem)]` vs the new ThreatDirector chain.
- **MCP `apply_text_edits` multi-edit-in-one-call can misalign** (a paired replace+delete hit the wrong line). One edit per call (or strict bottom-first), `precondition_sha256` always. `create_script` won't overwrite; `script_apply_edits replace_method` still can't target struct ISystems.
- **Re-mean bytes, don't rename**: unchanged byte values keep the `[GhostField]` serializer identical → a global-loop reframe stayed re-bake-free (only authoring default-value edits re-bake the subscene).
## Open / deferred
- Base-integrity / visible fail-state (siege currently just collapses on timeout); haul-scaled retaliation (`SizePerExpeditionResource=0`); Heat/Schedule trigger sources (baked-but-inert); dev overlay in dev *player* builds; deploy-gate 3D marker. All defaulted/tunable — see [[DR-017_Persistent_Base_Player_Driven_Pacing]].
## Next
- **Build/automation/customization expansion** on the now-persistent base (the operator's stated next direction) — the ThreatDirector inert sources + the DR-014 structure tick fields are the additive hooks.
- Multi-client (MPPM) run of the co-op split-presence + dev-tools-over-a-real-connection (the unconditional RPC type is the enabler).
- Optional: base-integrity fail-state if losing a siege should have visible teeth.
@@ -0,0 +1,56 @@
---
id: DR-017
title: M8 — Persistent base & player-driven pacing (Calm↔Siege run-state, composite ThreatDirector, dev-tools-over-RPC, Game/DevSandbox scenes)
status: accepted
date: 2026-06-04
tags:
- decision
- netcode
- core-loop
- pacing
- dev-tools
- world-architecture
- m8
permalink: gamevault/07-sessions/decisions/dr-017-persistent-base-player-driven-pacing
---
# DR-017 — Persistent base & player-driven pacing
## Context
The M6 core loop ([[DR-013_M6_Aether_Cycle_Region_Split]] / [[DR-014_M6_Build_Structures_Automation_Foundation]]) shipped a **Dome-Keeper forced-timer rhythm**: `CyclePhaseSystem` auto-advanced Expedition (~60s) → Defend (wave) → Build (~20s) → repeat, +1 goal/lap, booting straight into a 60-second Expedition timer. The operator disliked the pacing: **the home base should feel persistent and unhurried** — with build/automation/customization layered on, you should **deploy on an expedition when YOU are ready** (inventory + prep), not on a treadmill; **base attacks should be event/timeline-triggered** ("possibly after an expedition"), not every lap. Plus: a **separate dev scene + dev tools** (spawn waves, control state, grant resources/upgrades, teleport, god-mode), and the **main scene should feel like an actual game**.
Operator chose (this session): **composite, data-driven ThreatDirector** (post-expedition default-on); **full staged build**; a **new dedicated Game scene + a DevSandbox scene**. A 3-critic + synthesis design-review Workflow ran pre-code (per [[CLAUDE]]'s netcode-slice rule) and caught a co-op-breaking flaw + steered the de-risked shape below.
## Decision
1. **Global run-state = `{Calm, Siege}` only; "expedition" is per-player presence (the co-op fix).** A single global `CycleState.Phase` byte cannot represent "player A out / player B home", so it carries only the shared posture: **`Calm`** (persistent, unhurried DEFAULT — build/prep, no countdown) or **`Siege`** (event-triggered base-defense wave). Being out is per-player (server-only `RegionTag`, read client-side by the HUD's `Camera.main.x > 500`). **Reuse symbols, re-mean bytes — no rename**: `CyclePhase.Calm = 0` (was Expedition), `Siege = 1` (was Defend), `Build = 2` retired; deprecated aliases kept so HUD/audio/tests compile through the cut-over. **Byte values are unchanged → the GhostField serializer layout is identical → the const re-mean forces no re-bake** (boot-into-Calm comes from the spawn-stamp + baker default, both byte 0). `CyclePhaseSystem` (name kept, behaviour rewritten) is the Calm↔Siege machine; boot = Calm (`CycleDirectorSpawnSystem` stamps `Phase=Calm, PhaseEndTick=0`). `ExpeditionFieldSystem` re-keyed off **expedition-region player presence + a server-only `ExpeditionEpoch`** (RNG seed; field lives while anyone is out, torn down when the last leaves) — replaces the global-phase edge + `CycleNumber`-as-seed.
2. **Composite ThreatDirector (server-only, data-driven, extensible).** Two **server-only** components on the global CycleDirector ghost (neither a `[GhostField]` → no ghost re-bake; future sources are additive): **`ThreatConfig`** (baked flat scalars via `CycleDirectorAuthoring`: PostExpedition enable/delay/sizeBase/sizePerResource, StartCondition, SiegeTimeoutTicks; Heat/Schedule fields **present-but-inert**) + **`ThreatState`** runtime (`PendingSiegeSize` = the single documented entry point, `ArmTick`, `PendingReturns`, `ExpeditionsCompleted`, `SiegeStartTick`, inert `Heat`/`NextScheduledTick`). **`ThreatDirectorSystem`** (plain server `SimulationSystemGroup`, ordered **Gate → ThreatDirector → CyclePhaseSystem → Wave**) consumes the gate's per-player `PendingReturns` (de-duped: the gate teleports the returner out of its radius) and arms a siege `SizeBase` after a telegraph delay; `StartCondition.Immediate` is the default (`RequirePlayerAtBase` would soft-lock an empty base). A **bounded-resolution timeout** culls the wave after `SiegeTimeoutTicks` so an unattended/empty-base siege can never soft-lock. **Siege sizing rides WaveSystem's own `Lull→Spawning` entry**: on the Calm→Siege edge `CyclePhaseSystem` writes `WaveState{ WaveNumber+1, Phase=Spawning, RemainingToSpawn=size, NextActionTick=now }` in one atomic `SetComponent` (Phase=Spawning bypasses WaveSystem's `(WaveNumber-1)*CountPerWave` escalation recompute, which only runs while `Phase==Lull`), so the siege spawns EXACTLY the director-chosen size and WaveSystem stays the sole `WaveState` writer thereafter — **no new SiegeState component**. **Goal decoupled**: `GoalProgress.Charge += 1` per **siege survived** (single global edge, co-op-safe), moved from the retired Build→Expedition edge.
3. **Dev tools over RPC (in-editor now; connection-ready).** A single **unconditional** wire type `DebugCommandRequest : IRpcCommand { byte Op; int ArgA; int ArgB }` (+ `DebugOp` byte consts) — **never `#if`-gated** (the reflection-built RpcCollection hash must match across release/dev peers; gating the *type* breaks the handshake). Only the systems/overlay are `#if UNITY_EDITOR`: `DebugCommandSendSystem` (client, static-queue → request entities, like `StorageOpSendSystem`; also drives headless from `execute_code`), `DebugCommandReceiveSystem` (server switch, plain `SimulationSystemGroup`, reusing `StorageMath.Deposit` / `StatModifier` replace-by-SourceId / `RegionMath` / the wave+cycle singletons; sender-targeted ops resolve `SourceConnection → NetworkId → GhostOwner`), and a `DebugOverlay` MonoBehaviour (IMGUI `OnGUI`, mirroring `ConnectionUI`). Ops: SpawnWave/EndSiege/ClearEnemies/SetCalm/GrantResource/GrantUpgrade/Teleport/ToggleGod/Heal/Kill/AdvanceGoal/SetHeat. **God-mode** = server-only enableable `DebugGodMode` baked **present+DISABLED** on the player prefab (mirrors `Dead`; bit-flip, no structural change), guarded in `HealthApplyDamageSystem` beside the `RespawnInvuln` early-out. The overlay forces the OS cursor visible (`AimPresentation.ForceCursorVisible`, non-#if) so its buttons stay clickable while aiming; all debug statics reset via `[RuntimeInitializeOnLoadMethod]`.
4. **Dedicated Game scene + DevSandbox, built by DUPLICATION.** `manage_asset duplicate` SampleScene → **`Game.unity`** (clean main scene; boots Calm; kept Synty world + post-FX + glue) and → **`DevSandbox.unity`** (Synty world disabled, `~DevTools` GameObject with the overlay). Duplication (a file copy) preserves the `GameplaySubScene` `Unity.Scenes.SubScene` `_SceneAsset` guid + non-zero `_SceneGUID` Hash128 verbatim — MCP `component_properties` silently drops Hash128, so a scratch rebuild would bake nothing. Both reference the SAME `Gameplay.unity` subscene. `Game.unity` added to `EditorBuildSettings` at **index 0** (a player build boots Game). HUD/audio re-meaned to the Calm/Siege semantics (deploy prompt, incursion telegraph reusing the `PhaseEndTick=ArmTick` countdown).
## Consequences
- **Validated:** EditMode **137/137** green (rewrote `CyclePhaseSystemTests`; +`ThreatDirectorSystemTests` ×5, +`DebugCommandReceiveSystemTests` ×4, +god-mode skip-damage, +gate return-signal; incl. a co-op split-presence invariant test). **Focused-editor Play (server+client):** boots into **Calm** (`Phase=0, PhaseEndTick=0`, no countdown); the Stage-2/3 re-bakes landed (`ThreatConfig{SizeBase=5,Delay=300,Timeout=3600}` on the director, `DebugGodMode` present+disabled on the player); arming a siege → `CyclePhaseSystem`**Siege**, WaveSystem spawned **exactly 4** Husks (`WaveState{Spawning, Remaining=0, WaveNum=1}`), `ThreatState{Pending=0, SiegeStart=…}`; **server==client** (Phase + 4 Husk ghosts replicated; client connecting confirms the unconditional RPC type didn't break the handshake). `Game.unity` boots into the calm base with the new HUD ("AT BASE — deploy through the gate when you're ready"). Console clean (only the pre-existing in-editor Server-Tick-Batching warning).
- **No new asmdefs.** New code under `…/World/` (`ThreatComponents`, `ThreatDirectorSystem`), `…/Debug/` (Simulation `DebugCommandRequest`/`DebugGodMode`; Server `DebugCommandReceiveSystem`; Client `DebugCommandSendSystem`/`DebugOverlay`). Supersedes the M6 forced-timer rhythm; reuses region split / GhostRelevancy / runtime-ghost / tick-safe math / RPC recipe / gates / ledger verbatim.
- **Foundation for the build/automation expansion**: the persistent calm base is now the default state with room for the upcoming building/automation/customization layer; the ThreatDirector's inert Heat/Schedule sources + the structure tick fields ([[DR-014_M6_Build_Structures_Automation_Foundation]]) are the additive hooks.
## Open / deferred (defaulted, tunable)
- **Empty-base / unattended-siege "teeth"**: this slice ships the bounded-timeout collapse (no soft-lock) but **no base-integrity stat / fail-state** yet — defaulted to "siege just collapses"; a visible base-integrity HUD bar is a follow-up (would fold one `[GhostField]`).
- **Haul-scaled retaliation**: `SizePerExpeditionResource` baked but ships **0** (flat sieges); enabling is a one-line tuning change, no re-bake.
- **Heat + Schedule trigger sources**: config fields baked-but-inert; drop in additively (server-only, no re-bake).
- **Dev overlay in dev *player* builds**: `#if UNITY_EDITOR` only this slice (no `DEVELOPMENT_BUILD` configured); the wire type stays unconditional so switching later is just the systems' guard.
- **Deploy-gate world marker / top-down DevSandbox cam**: the deploy affordance is HUD-text for now; a 3D gate marker is polish.
## Build gotchas recorded this session
- **A system-ordering cycle is NOT caught by plain-Entities EditMode tests** (they register systems individually) — it only throws `ComponentSystemSorter` "circular dependency" at **world creation (Play)**. Caught here: a new system's `[UpdateAfter(A)]+[UpdateBefore(B)]` collided with B's pre-existing `[UpdateAfter(A)]`. Always Play-validate after adding cross-system `[Update*]` attributes; re-audit the *existing* attributes of every system you reorder around.
- **MCP `apply_text_edits` with MULTIPLE non-adjacent edits in one call can misalign** (observed: a paired replace+delete deleted the line *above* the intended one). Do one edit per call (or strictly bottom-first, verified), with `precondition_sha256`. The structured `script_apply_edits` (`insert_method`, `replace_method`) is safer for class methods — but `replace_method` still can't target `struct : ISystem`.
- **`create_script` does not overwrite** an existing path (errors); `manage_script` has only create/read/delete. Full-file rewrites = `apply_text_edits` over the whole span, or delete+create for non-GUID-referenced files (systems/tests). Its brace-balance validator will reject a botched span — a useful guard.
- **Re-mean-bytes-don't-rename** kept a global-loop reframe re-bake-free: an enum/const whose byte values are unchanged leaves the `[GhostField]` serializer identical, so only authoring *default-value* edits (not the const re-mean) trigger a subscene re-bake.
Builds on + supersedes the forced-rhythm framing of [[DR-013_M6_Aether_Cycle_Region_Split]] / [[DR-014_M6_Build_Structures_Automation_Foundation]]; reuses the RPC/runtime-ghost/tick-safe patterns from [[DR-008_M5_HomeBase_BaseLayer_Storage]] and the StatModifier path from [[DR-004_M3_DataDriven_Abilities_Modifiers]]. Serves the persistent-base + player-driven pillars in [[Pillars]].