Docs: truth pass over CLAUDE.md, DRs and the MOCs (audit M13/M14/M15/M16/M19/L11/L12)

CLAUDE.md — it is loaded every session, so its errors cost the most:
- M13: it still said "No world code until the world-model spike passes
  its design review" while DR-049 has been `accepted` since 07-13 and the
  roadmap says PASSED. A hard contradiction between the two authority
  docs, with CLAUDE.md winning by being always-loaded — plausibly part of
  why Phase 2 never started. Now points at the build spec, warns it needs
  re-anchoring, and links the parked engine fork.
- Records the H2 hazard as a rule: a socket whose SparkId is missing from
  the baked AbilityDatabase silently reads Damage/Range/Cooldown 0.
- L12: the asmdef table was stale on all four rows; also notes the fifth
  compilation unit (Scripts/Editor has no asmdef) and that tests now
  reference Authoring.
- M19: per-machine setup — the machine-loss recovery checklist — was
  missing Blender + blendermcp (which the committed /art-dev skill
  hard-requires) and ctx7, and claimed a ${CLAUDE_PROJECT_DIR} in
  .mcp.json that isn't there.
- Structures / harvest / persistence / BefourStudios bullets rewritten:
  they described deleted subsystems.
- Net-zero rule honoured: the additions pushed it to 41,057 (over the
  40,960 hard limit), so two long art-pipeline bullets moved verbatim to
  the gotchas archive under a dated heading. 39,661 now — inside the
  file's own 39,936 soft ceiling.

- M14: DR-053 files the Bevy fork as PARKED. The guide was reachable from
  NOTHING — one file, zero inbound links, and its gating instruction
  lived only in machine-local memory, which CLAUDE.md explicitly forbids
  as a sole home. It now has a committed home and inbound links.
- DR-054 records this purge, including the three files that were in the
  delete set until their consumers were checked and turned out to be
  load-bearing.
- M15: 13 superseded DRs re-statused with `superseded_by`. Only DR-002
  carried the field before, though the project's own template prescribes
  it.
- M19: Home.md stated three wrong facts (latest DR, latest session, date)
  — refreshed. Pillars.md and Systems_Index.md now carry warning banners:
  both assert things the purges falsified, and Systems_Index still claims
  to be "the accurate map of what exists in code today".
- L11: the 4 genuinely-broken wikilinks fixed (typo'd DR filenames). Zero
  broken vault-internal links now.
- Session log written — the last three commits before today had none,
  breaking the protocol's own bookend rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-07 13:44:18 -07:00
parent e42760e7d0
commit c87dd04e87
21 changed files with 296 additions and 35 deletions
+14 -15
View File
@@ -34,12 +34,13 @@ Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in f
| Assembly | Namespace | Runs in | References |
|---|---|---|---|
| `ProjectM.Simulation` | `ProjectM.Simulation` | **client + server** worlds | Entities, **Unity.Transforms**, Collections, Mathematics, Burst, Unity.Physics, Unity.NetCode |
| `ProjectM.Client` | `ProjectM.Client` | client world only | + Simulation, Unity.Entities.Graphics, **Unity.InputSystem**, Unity.Transforms, Unity.NetCode, **Unity.Physics + Unity.CharacterController** (KinematicCharacterBody source-gen), **Rukhanka.Runtime** (animation) |
| `ProjectM.Server` | `ProjectM.Server` | server world only | + Simulation, **Unity.Transforms**, Unity.NetCode |
| `ProjectM.Authoring` | `ProjectM.Authoring` | bake time (+ scene runtime) | Simulation, Entities, **Unity.Entities.Hybrid**, Collections, Mathematics, Unity.NetCode |
| `ProjectM.Simulation` | `ProjectM.Simulation` | **client + server** worlds | Entities, **Unity.Transforms**, Collections, Mathematics, Burst, Unity.Physics, Unity.NetCode, Unity.CharacterController |
| `ProjectM.Client` | `ProjectM.Client` | client world only | + Simulation, Unity.Entities.Graphics, **Unity.InputSystem**, Unity.Transforms, Unity.NetCode, **Unity.Physics + Unity.CharacterController** (KinematicCharacterBody source-gen), **Rukhanka.Runtime** (animation), Unity.Networking.Transport, UnityEngine.UI, Rukhanka.Toolbox |
| `ProjectM.Server` | `ProjectM.Server` | server world only | + Simulation, **Unity.Transforms**, Unity.NetCode, Unity.Networking.Transport, Unity.Physics |
| `ProjectM.Authoring` | `ProjectM.Authoring` | bake time (+ scene runtime) | Simulation, Entities, **Unity.Entities.Hybrid**, Collections, Mathematics, Unity.NetCode, Unity.Physics, Unity.CharacterController |
- **Simulation** = components + systems shared by both worlds (most gameplay). **Client/Server** = world-specific. **Authoring** = `…Authoring` MonoBehaviours + `Baker<T>`.
- A **fifth** compilation unit exists by omission: `Scripts/Editor/` has NO asmdef, so it compiles into `Assembly-CSharp-Editor`. `ProjectM.Tests.EditMode` references all four asmdefs incl. **Authoring** (so bakers are testable).
- Other folders: `Assets/_Project/Subscenes/` (baked entity subscenes), `Assets/_Project/Prefabs/`, `Assets/_Project/Tests/EditMode/`.
- Feature folders added since (`Client/UI`, `Client/Settings`, `Server/Automation`, `Server/Persistence`, `Simulation/Automation`, `Simulation/Persistence`) live **inside the existing four asmdefs — no new assemblies**.
@@ -85,12 +86,11 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
- **Hit/area tests must be SWEPT, not point checks** — a point check tunnels when the per-tick step exceeds the target radius (high speed *or* tick-batching); test the segment traversed this tick. **In a PLAIN `SimulationSystemGroup` system do NOT use `SystemAPI.Time.DeltaTime`** (wall-frame delta, not the fixed step) — store the per-tick step on the projectile (`Projectile.LastStep`, written in the fixed-step group) and rebuild the segment as `cur - dir*LastStep`. `ecb.DestroyEntity` **at-most-once** per tick (destroyed-bitset; double destroy throws at Playback). **TWO target types in one pass: UNIFY into one best-target loop + one shared bitset** (separate sweeps double-destroy a projectile overlapping both — DR-018). **A per-hit yield `(int)` cast that also gates despawn is an immortal-sink** (sub-1.0→0→no deposit, shot still consumed): guard `math.max(1,(int)yield)` + `[Min(1f)]` authoring.
### Build / structures / grid
- **Build-grid math must be deterministic + integer-stable:** corner-origin, center-returning, **half-open** cell bounds, `math.floor`. Lock `CellSize`/`PlotSize` as a coordinate space once (`BaseGridMath`) — changing them invalidates placed structures.
- **Structures:** only `Type` replicates (client derives `Cell`); **occupancy is DERIVED** from live ghosts, never baked. See [[DR-014_M6_Build_Structures_Automation_Foundation]].
- **Co-op placement atomicity:** commit `StorageMath.Withdraw` + cell-reservation **in-place in the RPC foreach** (only `Instantiate` via ECB) so two same-tick requests for one cell can't both pass. Ledger spends generally: afford→act else SOFT-FAIL (no cooldown-burn), read LIVE in-loop (no hoist); a Health-less entity silently drops OUT of an aggro snapshot (snapshot ABOVE the early-return).
- **DR-051 purge (07-15) ★:** siege/cycle/core/turret/automation + legacy `AbilityRef` path + onboarding **DELETED** (git = the archive). **Retired byte VALUES stay reserved, never renumbered** (`StructureType` 1-4, `ResourceId.Charge`, `DebugOp` 3/10/11, `TuningKnob` 20-23); `DebugOp.SpawnWave`/`EndSiege` RE-MEANT (force-wave / quiet-arena). **Waves UNGATED** — a baked `WaveDirectorAuthoring` decides by placement. Sockets are THE ability model (frame loadout seeded unconditionally at spawn); `CharacterId``FrameKind`. [[DR-051_Lantern_Realignment_Purge]].
- **Harvest:** in-run nodes→PERSONAL `InventorySlot` (`[GhostField] OwnerSendType.All`, spill→ledger); `G`=deposit at base. Inventory/equipment PAUSED → archive 2026-06-12 + [[DR-026_Inventory_Equipment_Progression_Foundation]].
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned) ★:** **born-correct load**`CycleDirectorSpawnSystem` (the ledger/RunInfo/meta host) applies the menu-staged `PendingSave` AT SPAWN; `BaseRestoreSystem` replays structures charge-free + HP. **v7 = a FRESH EPOCH: `MinLoadableVersion = CurrentVersion = 7`** (older saves rejected → New Game); additive again going forward. `RunDirectorSystem`'s terminal bank is the sole autosave trigger. See [[DR-019_Frontend_Menu_Settings_Saves_Build]] + [[DR-051_Lantern_Realignment_Purge]].
- **Grid math** (`BaseGridMath`, still live for spawn rings/respawn/lights): corner-origin, center-returning, **half-open** cell bounds, `math.floor`; lock cell size as a coordinate space once. Structures/placement themselves are deleted — recipe + atomicity rules in [[DR-014_M6_Build_Structures_Automation_Foundation]] if buildables return.
- **Ledger spends:** afford→act else SOFT-FAIL (no cooldown-burn), read LIVE in-loop (no hoist); a Health-less entity silently drops OUT of an aggro snapshot (snapshot ABOVE the early-return).
- **DR-051 purge (07-15) ★:** siege/cycle/core/turret/automation + legacy `AbilityRef` path + onboarding **DELETED** (git = the archive). **Retired byte VALUES stay reserved, never renumbered** (`StructureType` 1-4, `ResourceId.Charge`, `DebugOp` 3/10/11, `TuningKnob` 20-23); `DebugOp.SpawnWave`/`EndSiege` RE-MEANT (force-wave / quiet-arena). **Waves UNGATED** — a baked `WaveDirectorAuthoring` decides by placement. Sockets are THE ability model (frame loadout seeded unconditionally at spawn) — **the Spark defs must be in EVERY gameplay subscene's `AbilityDatabaseAuthoring`; a socket whose SparkId is missing from the baked blob silently reads Damage/Range/Cooldown 0** (audit H2, live-proven). `FrameKind` = Bathynaut(2)/Harpooner(3); `PlayerClass` is gone (FrameId is the single frame identity). [[DR-051_Lantern_Realignment_Purge]] · **2026-08-07 audit purge deleted the whole base/expedition shell** (run FSM, meta shop, prep, boons, build/structures, storage, inventory/equipment, enemy variants + boss): [[DR-054_Audit_Purge_2026-08]].
- **Harvest is single-sink** (→ the shared ledger, via `HarvestMath.DepositYield`). The personal-bag/equipment layer was deleted 2026-08-07; reintroduce LANTERN's carried-vs-banked split *inside HarvestMath*, not at its two call sites.
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned) ★:** **born-correct load**`CycleDirectorSpawnSystem` (now the ledger host only) applies the menu-staged `PendingSave` AT SPAWN. **v7 = a FRESH EPOCH: `MinLoadableVersion = CurrentVersion = 7`**; additive going forward — the save now carries only the ledger (structure/meta fields persist empty so v7 files still load). See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
### Presentation / juice / VFX
- **All juice/HUD = client-only observe-only `SystemBase` in `PresentationSystemGroup`** (once/frame, no rollback double-fire), never mutates the sim. Read ECS via `SystemAPI.Query` + `EntityManager.CompleteDependencyBeforeRO<T>()` — NOT MonoBehaviour `LateUpdate` (job-safety throw). `Entity` = a stable client dict key per ghost lifetime — **prune the cache each frame** (a pruned ghost = a kill/loss → death VFX); **never `DestroyEntity` a ghost client-side** (`GhostDespawnSystem` owns despawn). Hit-stop = camera punch, **never `Time.timeScale`**.
@@ -100,7 +100,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
- **HUD skin = build-safe `HudTheme` SO of serialized sprite refs** (runtime `Resources.Load` by name is build-stripped); tint MULTIPLIES, never set `unitySlice*` on 9-slices → archive 2026-07-06 + [[DR-024_HUD_Synty_Skin_Theme]].
### Art import (HDRP store packs → URP)
- BefourStudios HDRP art: convert via `EnvArtTools.cs`, never switch pipelines (HDRP breaks EG); Synty = URP-native → archive 2026-07-06.
- Synty = URP-native. (BefourStudios HDRP pack deleted 2026-08-07 — 4 reachable textures kept in `_Project/Textures/Env`.)
- **World = the LANTERN murk ★ (DR-051; Synty biomes deleted):** ONE look — `PostFX_Lantern.asset` (ACES; needs URP HDR grading) + `Env_SeabedKit.prefab` (ArtStaging-sourced) + unified RenderSettings (NO skybox; Exp² teal fog {0.02,0.10,0.12}; flat ambient {0.03,0.055,0.08}; density knob 0.035 play / 0.075 staging; camera clearFlags **SolidColor deep-water** — else no-skybox corners bleed blue). `ScenePolicy.IsGameplayScene()` gates the dynamic-look systems — never re-add `scene.name` string checks. `WorldAtmosphereSystem` = water-column murk.
- **A dark-lit screenshot MASKS material bugs — verify material *values*** (`GetPropertyType`-guard before `GetColor`/`GetFloat`; detail → archive 07-16).
- **EG per-instance tint (`URPMaterialPropertyBaseColor`) works ONLY on a Hybrid-Per-Instance `_BaseColor` graph** (AnimatedLitShader yes; stock Synty `Generic_Basic` = Unity-Per-Material → renders but silently no-ops) — check the graph first; else procedural decals. Detail → archive 07-16.
@@ -120,8 +120,7 @@ Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_A
- **First Rukhanka bake is ~60 s, main-thread-synchronous** (editor freezes — not a hang; cached after).
- **The server runs Rukhanka unless you strip it** — its **deformation** systems are `[WorldSystemFilter(Default)]` (⊇ ServerSimulation). **`ServerStripAnimationSystem`** (server-only one-shot) disables every `Rukhanka.Runtime` system on the server (group-disable cascades; matched by assembly name → no type ref). *Only Play-validation caught this.*
- **Build the controller via the `AnimatorController` API** (`manage_animation` drops enum/Vector blend-tree fields). **Skeleton-root = walk up from a bone to the soldier's direct child**, NOT `SkinnedMeshRenderer.rootBone` (the *bounds* root — head SMR's is `Spine_03` → destroys the lower skeleton).
- **The rig pipeline is HUMANOID** (muscle clips retarget onto player + monster rigs; the old "Generic" note was WRONG). **Blender clips:** per-action FBX (Key All Bones + Force Start/End + FBX Units Scale; **5.1: +NLA-strips OFF or ZERO takes bake, silently**); import `CreateFromThisModel` (**CopyFromOther FAILS on Blender's extra `Armature` node**); bake root motion into pose. Optimize Game Objects **OFF**; root motion **OFF** (CC owns the transform).
- **★ Skinned ATTACHMENTS onto an existing rig (07-16 suit-kit recipe):** Blender rigid-skin (vgroup w=1 + armature modifier; **UNHIDE the armature before selection-export or the FBX is silently skinless**) → `PlayerRigTools.GraftSmr` REBASES on rebind (verts→rest-world; bindposes = inverse of **rigid scale-stripped** rest matrices — a Blender roundtrip imports cm bones/0.01 armature, raw bindpose reuse explodes ×100; **`RecalculateTangents()` or Rukhanka/BRG registration fails and the whole rig vanishes**). Hand-written deformation shaders: DOTS-instanced `_DeformedMeshIndex` block **BEFORE** the `ComputeDeformedVertex.hlsl` include + the property ALSO in the Properties block (the baker validates `HasProperty`). [[DR-052_SoD_Facing_Underwater_Feel]].
- **Skinned attachments (graft/rebase/bindpose) + the HUMANOID clip pipeline (Blender per-action FBX rules) → archive 2026-08-07** (long-form; also in the `/art-dev` cookbook). Rig type IS Humanoid; root motion OFF; Optimize Game Objects OFF.
- **ENEMIES reuse the player pipeline** — a Husk = ownerless interpolated ghost = a remote player, so `EnemyAnimationDriveSystem` mirrors the REMOTE path (`LocalTransform` delta velocity + prevPos cache; `IsAttacking = AttackWindup != 0`). **Drop `[RequireMatchingQueriesForUpdate]`** so the prune runs every frame (else a cache entry leaks per kill). Build enemy prefabs via **`EnemyRigTools`**, GUID-preserving (`DeleteAsset+CopyAsset` orphans subscene refs); `WaveSystem` uses `baked.WithPosition` (not `FromPosition` → Scale reset). See [[DR-023_Enemy_Animation_MonsterMash]].
### MCP / editor workflow ★
@@ -137,7 +136,7 @@ Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_A
- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` overrides `Initialize` with `AutoConnectPort = 0` (M4 — listen/connect is explicit via the `ConnectionConfig` singleton + per-world ConnectionControlSystems). **Editor default = instant-into-game + MPPM** (creates `ServerWorld` (`WorldFlags.GameServer`) + `ClientWorld` (`WorldFlags.GameClient`)); the `ProjectM/Boot Into Menu (Editor)` EditorPref flips the MAIN editor to the frontend path. **Player builds boot the UITK frontend menu** (`return false` → one menu world, no netcode worlds until a menu choice). See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
- **Scenes (the DR-051 contract — exactly these four):** `MainMenu.unity` (build 0, UITK frontend) · `Game.unity` (build 1, the seabed arena; subscene `Gameplay.unity`) · `DevSandbox.unity` (renamed from Gym; dev tooling + subscene `GymSub.unity`; the `DebugOverlay`/F1-F2 dev scripts gate on this scene NAME) · `ArtStaging.unity` (art viewing, no player; the look's source of truth). All share the LANTERN look (see World bullet). The on-demand lifecycle (`WorldLauncher`/`SessionRunner`/`MainMenuController`) creates the right worlds per menu choice (Single/Host/Join), THEN `LoadScene(Game)` (subscene-streaming rule above).
- **Direction = LANTERN ★ — pivot LOCKED 2026-07-13 ([[DR-048_Lantern_Adoption_Full_Pivot]]).** Co-op action-RPG, *light is territory* (seed-pinned pocket-graph; SoD manual-aim skillshots; suit-frames + Sparks + wild mutations). Supersedes the Awakening-Engine fiction + the Co-op Hades iteration. Operative roadmap [[Roadmap_Lantern_Slice]]; **existing code (combat feel, ability/boon plumbing, run/hub lifecycle, save, regions/relevancy) is QUARRY, not foundation** — keep/rework/mothball per [[Lantern_Strip_Mothball_Inventory]]. **No world code until the ★review-first world-model spike ([[Lantern_World_Model_Spike]]) passes its design review.** The prior co-op-Hades core-loop (ready-check multi-room RUNS; `RunDirectorSystem`/`BossState`; DR-044/045/046) is salvage — invariants archived 07-13 in the gotchas archive. ★ **general gotcha kept: a serialized prefab bool ignores the C# initializer — flip the value in the prefab.**
- **Direction = LANTERN ★ — pivot LOCKED 2026-07-13 ([[DR-048_Lantern_Adoption_Full_Pivot]]).** Co-op action-RPG, *light is territory* (seed-pinned pocket-graph; SoD manual-aim skillshots; suit-frames + Sparks + wild mutations). Supersedes the Awakening-Engine fiction + the Co-op Hades iteration. Operative roadmap [[Roadmap_Lantern_Slice]]; **existing code (combat feel, ability/boon plumbing, run/hub lifecycle, save, regions/relevancy) is QUARRY, not foundation** — keep/rework/mothball per [[Lantern_Strip_Mothball_Inventory]]. World-model review **PASSED** 2026-07-13 ([[DR-049_Lantern_World_Model_Design]] `accepted`) → build against [[World_Model_Build_Spec]]; **re-anchor it first** (its step 1 assumes SaveData `MinLoadableVersion` < 7, which DR-051 already shipped, and its RegionTag→PocketTag line anchors have moved). **★ The engine fork ([[DR-053_Engine_Fork_Bevy_Parked]]) is open — surface it before starting world code.** The prior co-op-Hades core-loop was DELETED 2026-08-07 (audit H1), not mothballed; git is the archive. ★ **general gotcha kept: a serialized prefab bool ignores the C# initializer — flip the value in the prefab.**
## DOTS / ECS conventions (authoritative summary)
@@ -173,4 +172,4 @@ Full protocol + per-layer detail: [[Documentation_Protocol]] (`Docs/Vault/_Meta/
## Per-machine setup (NOT in git — redo on each machine)
`.mcp.json` is committed + portable (`${CLAUDE_PROJECT_DIR}`); the **`dots-dev` skill travels with the repo** (`.claude/skills/dots-dev/`). Each machine still needs: (1) `uv`/`uvx` + Obsidian app + `obsidian-cli` (machine-local, don't sync); (2) basic-memory registration — `uvx basic-memory project add gamevault "<repo>/Docs/Vault" --default` then `uvx basic-memory reindex --full --search --embeddings --project gamevault`; (3) Unity 6.5 open + the Unity-MCP bridge connected (`mcpforunity://editor/state``ready_for_tools`).
`.mcp.json` is committed (one server: `basic-memory`); the **`dots-dev` + `art-dev` skills travel with the repo** (`.claude/skills/dots-dev/`). Each machine still needs: (1) `uv`/`uvx` + Obsidian app + `obsidian-cli` (machine-local, don't sync); (2) basic-memory registration — `uvx basic-memory project add gamevault "<repo>/Docs/Vault" --default` then `uvx basic-memory reindex --full --search --embeddings --project gamevault`; (3) Unity 6.5 open + the Unity-MCP bridge connected (`mcpforunity://editor/state``ready_for_tools`); (4) **Blender 5.1 + the blendermcp addon** (socket 9876) — the committed `/art-dev` skill hard-requires it; (5) **`npx ctx7`** available (+ `CONTEXT7_API_KEY` for higher limits).