Docs: workflow consolidation - CLAUDE.md game-state extraction (-2KB, invariants+DR pointers), validation-harness skill ref, docs-commit standing auth
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ The spine: **size by blast-radius → ground → (design-review) → plan-gate
|
|||||||
2. **Plan-approval gate ★.** Phases up to approval are read-only. Code is written only after the operator approves and you call **`ExitPlanMode`**. Self-policed, not harness-enforced.
|
2. **Plan-approval gate ★.** Phases up to approval are read-only. Code is written only after the operator approves and you call **`ExitPlanMode`**. Self-policed, not harness-enforced.
|
||||||
3. **Doc + memory bookend ★.** Scan the vault/memory at start; update it at end (incl. CLAUDE.md self-maintenance). Protocol → `CLAUDE.md` Memory section + [`references/memory-protocol.md`](references/memory-protocol.md).
|
3. **Doc + memory bookend ★.** Scan the vault/memory at start; update it at end (incl. CLAUDE.md self-maintenance). Protocol → `CLAUDE.md` Memory section + [`references/memory-protocol.md`](references/memory-protocol.md).
|
||||||
4. **DOTS-correct.** ECS conventions per `CLAUDE.md` + [`references/dots-conventions.md`](references/dots-conventions.md). Any violation found in review is blocking.
|
4. **DOTS-correct.** ECS conventions per `CLAUDE.md` + [`references/dots-conventions.md`](references/dots-conventions.md). Any violation found in review is blocking.
|
||||||
5. **Commit only when asked.** Gated like the plan — offer + propose grouping in the final report; commit only on explicit "commit"; **never push** unless asked.
|
5. **Code commits gated; docs commits standing.** Code: offer + propose grouping in the final report; commit only on explicit "commit". **Docs-only changes (vault / CLAUDE.md / skill references) auto-commit at session end** in a logical `Docs:` group — operator standing authorization 2026-07-07. **Never push** unless asked.
|
||||||
6. **Operator gates.** Present-forks-don't-auto-decide · no time/effort/deadline estimates · never-silently-defer · tuning-defaults autonomy. See *Operator gates* below.
|
6. **Operator gates.** Present-forks-don't-auto-decide · no time/effort/deadline estimates · never-silently-defer · tuning-defaults autonomy. See *Operator gates* below.
|
||||||
7. **Path-portable.** Never write machine-specific absolute paths (`/Users/…`, `C:\…`) into the skill, code, docs, or `.mcp.json` — use repo-relative / `${CLAUDE_PROJECT_DIR}`.
|
7. **Path-portable.** Never write machine-specific absolute paths (`/Users/…`, `C:\…`) into the skill, code, docs, or `.mcp.json` — use repo-relative / `${CLAUDE_PROJECT_DIR}`.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ The single stateful editor + domain-reload ordering make parallel writes unsafe
|
|||||||
- **Tuning autonomy:** within an approved plan, pick sensible defaults + expose live knobs; consult only on a real gameplay/design fork.
|
- **Tuning autonomy:** within an approved plan, pick sensible defaults + expose live knobs; consult only on a real gameplay/design fork.
|
||||||
|
|
||||||
### Phase 6 — Verify ladder (definition of done — show evidence, not "done")
|
### Phase 6 — Verify ladder (definition of done — show evidence, not "done")
|
||||||
NetCodeTestWorld is `internal` and unused here — do NOT prescribe it.
|
NetCodeTestWorld is `internal` and unused here — do NOT prescribe it. Live-driving rules + the server-inject harness → [`references/validation-harness.md`](references/validation-harness.md).
|
||||||
- **L1** — `read_console(types=["error","warning","exception"], include_stacktrace=True)` clean vs the Phase-1 baseline. Burst/source-gen failures surface here, not in a plain build — any new one is a finding.
|
- **L1** — `read_console(types=["error","warning","exception"], include_stacktrace=True)` clean vs the Phase-1 baseline. Burst/source-gen failures surface here, not in a plain build — any new one is a finding.
|
||||||
- **L2** — EditMode tests: plain-Entities `new World → register in SimulationSystemGroup → SortSystems → Update → assert` (`run_tests mode=EditMode`). Extract pure logic to a `*Math.cs` helper and unit-test it (cover swept hit-detection with a tunnelling regression). **Plus one live Play netcode smoke** — `execute_code` boot, step ticks, assert **server == client** for the replicated surface (identify worlds by `world.Name == "ServerWorld"/"ClientWorld"`).
|
- **L2** — EditMode tests: plain-Entities `new World → register in SimulationSystemGroup → SortSystems → Update → assert` (`run_tests mode=EditMode`). Extract pure logic to a `*Math.cs` helper and unit-test it (cover swept hit-detection with a tunnelling regression). **Plus one live Play netcode smoke** — `execute_code` boot, step ticks, assert **server == client** for the replicated surface (identify worlds by `world.Name == "ServerWorld"/"ClientWorld"`).
|
||||||
- **L3** (visual/asset work) — screenshot via `execute_code` `ScreenCapture` → Read PNG, **and verify asset *values*** (`shader.GetPropertyType`-guard before `GetColor`/`GetFloat`) — a dark/stylized render masks material bugs.
|
- **L3** (visual/asset work) — screenshot via `execute_code` `ScreenCapture` → Read PNG, **and verify asset *values*** (`shader.GetPropertyType`-guard before `GetColor`/`GetFloat`) — a dark/stylized render masks material bugs.
|
||||||
@@ -94,7 +94,7 @@ Per `CLAUDE.md` Memory section + [`references/memory-protocol.md`](references/me
|
|||||||
**Reviews**: pre-code <result> · post-impl <result>.
|
**Reviews**: pre-code <result> · post-impl <result>.
|
||||||
**Next step**: <one concrete next action from the log's Next-session intent>.
|
**Next step**: <one concrete next action from the log's Next-session intent>.
|
||||||
```
|
```
|
||||||
Then **offer to commit** and propose grouping; commit only on explicit approval. **Review first** (`git status` + `git diff --stat`); **stage explicit paths — never `git add -A`** (excludes `Library/Temp/obj/Logs/UserSettings`, `.csproj`/`.sln`, scratch screenshots/exports); keep each asset **with its `.meta`** in one commit; one commit per concern; match the repo's message style + the Co-Authored-By trailer; **never push**. Don't auto-loop into a fresh session.
|
Then **commit the docs group directly** (standing authorization) and **offer code commits**; code commits only on explicit approval. **Review first** (`git status` + `git diff --stat`); **stage explicit paths — never `git add -A`** (excludes `Library/Temp/obj/Logs/UserSettings`, `.csproj`/`.sln`, scratch screenshots/exports); keep each asset **with its `.meta`** in one commit; one commit per concern; match the repo's message style + the Co-Authored-By trailer; **never push**. Don't auto-loop into a fresh session.
|
||||||
|
|
||||||
## Workflow orchestration (the parallelism primitive)
|
## Workflow orchestration (the parallelism primitive)
|
||||||
|
|
||||||
@@ -129,3 +129,4 @@ Pass only the upstream summary downstream — never raw agent dumps (research di
|
|||||||
- [`references/context7-libraries.md`](references/context7-libraries.md) — `ctx7`/`find-docs` flow + 6.5-era pinned library IDs.
|
- [`references/context7-libraries.md`](references/context7-libraries.md) — `ctx7`/`find-docs` flow + 6.5-era pinned library IDs.
|
||||||
- [`references/workflow-patterns.md`](references/workflow-patterns.md) — the named Workflow patterns (ground fan-out · design-review lens + adversarial-critic).
|
- [`references/workflow-patterns.md`](references/workflow-patterns.md) — the named Workflow patterns (ground fan-out · design-review lens + adversarial-critic).
|
||||||
- [`references/memory-protocol.md`](references/memory-protocol.md) — the four memory layers + bookend detail.
|
- [`references/memory-protocol.md`](references/memory-protocol.md) — the four memory layers + bookend detail.
|
||||||
|
- [`references/validation-harness.md`](references/validation-harness.md) — safe Play driving from MCP (pause/sleep/focus rules), execute_code traps, the server-inject validation harness, headless-run failure modes, visual-verification limits. Load for Phase 6 / any live diagnosis.
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Validation harness — driving Unity Play mode safely from MCP
|
||||||
|
|
||||||
|
Consolidates the Play-mode validation lore that used to live only in machine-local memories (screenshot-pause bug,
|
||||||
|
sleep-poll ban, execute_code traps, headless-run flakiness) plus the proven server-inject harness. Load when running
|
||||||
|
Phase 6 (verify ladder) or any live diagnosis. Committed + in-repo: this is the cross-machine home of these rules.
|
||||||
|
|
||||||
|
## Ground rules (violating these produces MISDIAGNOSES, not just failures)
|
||||||
|
|
||||||
|
1. **Never `Thread.Sleep` / sleep-poll inside `execute_code`.** It runs on the main thread — it freezes the entire
|
||||||
|
sim, and state read "after waiting" is state from a FROZEN world. This manufactured the retracted WasAllReady
|
||||||
|
"save deadlock" (2026-07-06). Spread observation across separate MCP calls instead — each round-trip gives the
|
||||||
|
sim hundreds of ms to advance.
|
||||||
|
2. **The bridge's game-view capture PAUSES the editor** (`EditorApplication.Step`). Check `isPaused` first, unpause
|
||||||
|
after any bridge capture. Prefer `ScreenCapture.CaptureScreenshot(path)` inside `execute_code` — async, lands at
|
||||||
|
frame end; Read the PNG on the NEXT MCP call (the round-trip covers it).
|
||||||
|
3. **An unfocused editor** throttles Edit mode to near-idle (MCP looks hung; test INIT stalls — pass
|
||||||
|
`init_timeout=120000`) and can leave a STALE Burst binary after Bursted query-set edits. Ask the operator to focus
|
||||||
|
Unity for compile/test/Burst-heavy phases. `Application.runInBackground` only helps in Play mode.
|
||||||
|
4. **Play-enter resets static presentation configs** via `[RuntimeInitializeOnLoadMethod]` — a value poked via
|
||||||
|
`execute_code` does not survive the next play-enter. Conversely: if a whole presentation slice is silent, FIRST
|
||||||
|
probe its static config (`return ProjectM.Client.XFeelConfig.Enabled;`) before theorizing structurally — the
|
||||||
|
2026-07-07 harvest-feedback bug was found by that one read after a plausible transform-hierarchy hypothesis
|
||||||
|
proved wrong.
|
||||||
|
|
||||||
|
## execute_code traps
|
||||||
|
|
||||||
|
- Runs as a **method body**: no `using` directives (parsed as statements) — fully-qualify every type.
|
||||||
|
- Identify worlds by **`world.Name == "ServerWorld"/"ClientWorld"`** (flags overlap a shared `Game` bit).
|
||||||
|
- **`Type.GetMethod(name, flags)` throws AmbiguousMatchException** on overloaded methods (EntityManager generics,
|
||||||
|
netcode statics) — iterate `GetMethods()` and filter by name/param-count, or call concrete generics directly in
|
||||||
|
compiled code.
|
||||||
|
- Rukhanka `ParameterValue` is a union — read `floatValue`/`boolValue` per the parameter type.
|
||||||
|
- Return a built string (`StringBuilder`) — it's the only channel out.
|
||||||
|
|
||||||
|
## The server-inject harness (validate replicated presentation WITHOUT gameplay driving)
|
||||||
|
|
||||||
|
Proven 2026-07-07 (NodeFeedbackSystem). Validates a replicated-state → client-presentation path end-to-end in ~4
|
||||||
|
MCP calls, no expedition run, no player positioning:
|
||||||
|
|
||||||
|
1. **Spawn** (server world): query the ghost prefab (`ComponentType` + `EntityQueryOptions.IncludePrefab`, pick the
|
||||||
|
one with `Prefab`), `Instantiate`, then `em.GetComponentData<LocalTransform>(prefab).WithPosition(playerPos +
|
||||||
|
offset)` — **never `LocalTransform.FromPosition`** (resets Scale=1; Scale replicates → consistent-but-wrong).
|
||||||
|
Find the player via `KinematicCharacterBody` + `LocalTransform`.
|
||||||
|
2. **Defeat relevancy**: a region-tagged ghost spawned at base gets culled from base players —
|
||||||
|
`em.SetComponentData(inst, new RegionTag { Region = 0 /* Base */ })` in the same call.
|
||||||
|
3. **Inject state changes server-side** (`SetComponentData` on the replicated component) in one call; **read the
|
||||||
|
client** in the NEXT call (round-trip ≥ several ticks — replication has happened).
|
||||||
|
4. **Assert numerically, then screenshot.** e.g. client `PostTransformMatrix.c0.x` vs the expected
|
||||||
|
`lerp(min, 1, frac)` and child `LocalToWorld` scale (`math.length(ltw.c0.xyz)`) vs `bakedScale × ptm`. Numbers
|
||||||
|
prove the math; the screenshot proves it renders (both matter — see visual limits below).
|
||||||
|
|
||||||
|
Ghost/LEG inspection idioms: `em.GetBuffer<LinkedEntityGroup>(root)`; render children = LEG entries with
|
||||||
|
`MaterialMeshInfo` (the root usually has none); parenting = `Parent` component; a baked child's uniform authoring
|
||||||
|
scale lands in `LocalTransform.Scale` (non-uniform → its own `PostTransformMatrix`). A root `PostTransformMatrix`
|
||||||
|
DOES propagate into parented children's `LocalToWorld` (verified live on 6.5.0).
|
||||||
|
|
||||||
|
## Headless expedition-run driving (flaky — know the failure modes)
|
||||||
|
|
||||||
|
- Launch by setting server `PlayerReady=1` from clean Staging. **Post-abort re-launch is intermittent** — a clean
|
||||||
|
Play restart launches reliably on the first ready; re-readying after an abort may not.
|
||||||
|
- **An AFK player dies to the swarm in ~15 s** and `Health` resets to the class value on respawn — a
|
||||||
|
`SetComponentData` HP buff does NOT stick across respawn; the run then aborts (0 expedition players → Returning)
|
||||||
|
and tears down the room.
|
||||||
|
- Therefore: **capture evidence in the SAME `execute_code` call that first detects the condition** (e.g.
|
||||||
|
`enemies>0`) — a separate "then screenshot" round-trip loses the window.
|
||||||
|
- Prefer the server-inject harness above whenever the question doesn't actually require a live run.
|
||||||
|
|
||||||
|
## Visual/asset verification limits
|
||||||
|
|
||||||
|
- **A dark/stylized frame masks material bugs** — verify VALUES: `shader.GetPropertyType(idx)`-guard before
|
||||||
|
`GetColor`/`GetFloat`/`GetTexture` (e.g. `S_General._BaseColorMultiply` is a float; `GetColor` returns black).
|
||||||
|
- **Skinned meshes lie to readers**: `WorldRenderBounds`/bone transforms return the bind-pose AABB, not the deformed
|
||||||
|
pose — deformed-pose visual bugs need the operator's eyes (or a screenshot), not entity reads.
|
||||||
|
- Body tint/flash validation: drive the stock EG `URPMaterialPropertyBaseColor` override on the render (LEG)
|
||||||
|
children; verify on the LOCAL player (persistent, centered) rather than a transient enemy.
|
||||||
|
- Forcing an animation pose: disable the drive system, write params via `AnimatorParametersAspect`; measure
|
||||||
|
ground-contact via feet-Y only on non-deformed geometry.
|
||||||
|
|
||||||
|
## Test-run mechanics
|
||||||
|
|
||||||
|
- `run_tests(mode="EditMode", assembly_names=["ProjectM.Tests.EditMode"])` → poll `get_test_job`
|
||||||
|
(`wait_timeout=60`). Unfocused editor: `init_timeout=120000` and retry once before diagnosing.
|
||||||
|
- A test count LOWER than expected with zero failures = tests not discovered (raw-written `.cs` without
|
||||||
|
`refresh_unity scope=all mode=force`), not a pass.
|
||||||
@@ -9,7 +9,7 @@ Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-
|
|||||||
- **Size check** — bash: `wc -c CLAUDE.md` · PowerShell: `(Get-Item CLAUDE.md).Length`. Must be `< 40960`.
|
- **Size check** — bash: `wc -c CLAUDE.md` · PowerShell: `(Get-Item CLAUDE.md).Length`. Must be `< 40960`.
|
||||||
- **Archive, don't delete.** When trimming, append the verbose / least-hot detail to the obsidian reference note `Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md` under a **new dated heading** (never overwrite an older snapshot), and leave a one-line pointer + the relevant `[[DR-###]]` link here.
|
- **Archive, don't delete.** When trimming, append the verbose / least-hot detail to the obsidian reference note `Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md` under a **new dated heading** (never overwrite an older snapshot), and leave a one-line pointer + the relevant `[[DR-###]]` link here.
|
||||||
- **Net-zero rule:** every addition is paid for by a condensation elsewhere. Keep only the hottest, highest-recurrence operational rules inline (flag them **★**); depth lives in the archive + DRs.
|
- **Net-zero rule:** every addition is paid for by a condensation elsewhere. Keep only the hottest, highest-recurrence operational rules inline (flag them **★**); depth lives in the archive + DRs.
|
||||||
- Condensation history (detail → archive dated headings): 06-17 · 07-04 · 07-04b · 07-04c · 07-06 · 07-07b (pays the attribute-swallow gotcha).
|
- Condensation history (detail → archive dated headings): 06-17 · 07-04 · 07-04b · 07-04c · 07-06 · 07-07b (pays the attribute-swallow gotcha) · 07-07c (game-state narrative → invariants+DR pointers; ~3 KB freed, operator-ordered).
|
||||||
|
|
||||||
## Stack — Unity 6.5.1 (`6000.5.1f1`, stable) as of 2026-06-27
|
## Stack — Unity 6.5.1 (`6000.5.1f1`, stable) as of 2026-06-27
|
||||||
|
|
||||||
@@ -86,15 +86,12 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
|||||||
|
|
||||||
### Build / structures / grid
|
### 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.
|
- **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.
|
||||||
- **`PlacedStructure{[GhostField] byte Type; int2 Cell (server-only); uint NextTick; uint LastProcessedTick}`** on an ownerless interpolated ghost. **Bake the tick fields** (catch-up linchpin); only `Type` replicates (client derives `Cell`). **Occupancy is DERIVED** by scanning live ghosts into a Temp `NativeHashSet<int2>`, never baked. See [[DR-014_M6_Build_Structures_Automation_Foundation]].
|
- **Structures:** bake the tick fields (catch-up linchpin); 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.
|
- **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.
|
||||||
- **EB-1 machines can die ★ (DR-032):** structures bake `Health`(`[GhostField]`)+`DamageEvent`+`Destructible`; `HealthApplyDamageSystem` destroys a `Destructible` at 0 (occupancy auto-frees). `EnemyAISystem` fortress-targets weighted-nearest players+structures (`EnemyAIMath.PickWeightedNearest`; snapshot ABOVE the early-return; `StructureAggroWeight`<1 SQUARED). ★ a Health-less machine silently drops OUT of the aggro snapshot. See [[DR-032_EB1_Machines_Can_Die]].
|
- **Siege-era systems ★ (EB-1/2 · END-1/2 — mothball-bound at Phase 2; these INVARIANTS outlive them):** ★ a Health-less machine silently drops OUT of the aggro snapshot (snapshot ABOVE the early-return; aggro weight <1 gets SQUARED) · spend from the ONE `GetSingletonEntity<ResourceLedger>`, afford→act else SOFT-FAIL (no cooldown-burn), read LIVE in-loop (no hoist) · `CyclePhaseSystem` = SOLE Phase writer (loss edges live there; transient flags, NOT latching) · **`RunOutcome{[GhostField] byte}` is REPLICATED — never client-derive the win/lose banner.** [[DR-032_EB1_Machines_Can_Die]] · [[DR-033_EB2_Felt_Spend_Charge_Economy]] · [[DR-034_END1_Losable_Core]] · [[DR-036_END2_Final_Siege_Win_Lose]].
|
||||||
- **EB-2 felt spend ★ (DR-033):** turret ammo = shared `Charge`(`ResourceId` **4**) on the `[GhostField] StorageEntry` ledger; `TurretFireSystem` spends from the ONE `GetSingletonEntity<ResourceLedger>` (afford→fire+cooldown, else SOFT-FAIL, no cooldown-burn). `Fabricator.InputFromLedger` reads it LIVE in-loop (no hoist → machines split a finite pool). See [[DR-033_EB2_Felt_Spend_Charge_Economy]].
|
- **`GoalProgress{[GhostField] int Charge,Target}`** (win meter — ≠ the `ResourceId.Charge` ammo) rides the CycleDirector ghost; buffs/tiers reuse `StatModifier`→`EffectiveAbilityStats`.
|
||||||
- **END-1 losable Core ★ (DR-034):** `CoreIntegrity{[GhostField] int Current,Max; uint OverrunTick}` on the GLOBAL CycleDirector ghost; `CoreDamageSystem`/`CoreRestoreSystem` (server) drain near `PlotCenter`, regen ONLY in Calm. SOFT-loss edge IN `CyclePhaseSystem` (sole Phase writer): `Current<=0` in Siege → Calm (NO reward; transient `OverrunTick`, NOT latching). Core = `EnemyAISystem` FALLBACK target. SaveData **v4**. See [[DR-034_END1_Losable_Core]].
|
- **Automation (server-only):** catch-up `ProductionMath.CyclesDue` **lower-bound 0**; `RuntimePlacedTag`=player-built. [[DR-020_M7_Automation_Production_Chains]].
|
||||||
- **END-2 win/lose ★ (DR-036):** server-only `RunPhase` (writer `GoalReachedSystem`) + **REPLICATED `RunOutcome{[GhostField] byte}`** (writer `CyclePhaseSystem`; do NOT client-derive the banner). `GoalReached` arms a final siege ×`FinalSiegeMultiplier` at `Charge>=Target` (once)+FinalDefense; latch Victory/Loss+halt; SiegeTimeout OFF in the final; SaveData **v5**. See [[DR-036_END2_Final_Siege_Win_Lose]].
|
- **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]].
|
||||||
- **`GoalProgress{[GhostField] int Charge,Target}`** (the goal meter — ≠ EB-2 `ResourceId.Charge` ammo) rides the CycleDirector ghost. Resource-gated ability tiers/buffs reuse `StatModifier` (`StatRecomputeSystem`→`EffectiveAbilityStats`).
|
|
||||||
- **M7 Automation (server-only) ★:** `Harvester`/`Conveyor` palette-TRIMMED (code intact), `Fabricator` LIVE (EB-2); catch-up `ProductionMath.CyclesDue` (**lower-bound 0**); `RuntimePlacedTag`=player-built. See [[DR-020_M7_Automation_Production_Chains]].
|
|
||||||
- **Harvest routes by node region ★; inventory/equipment PAUSED:** in-run nodes→PERSONAL `InventorySlot` (`[GhostField] OwnerSendType.All`, spill→ledger); `G`=deposit at base (base nodes GONE — DR-044). Items/equip detail → gotchas archive (2026-06-12). See [[DR-026_Inventory_Equipment_Progression_Foundation]].
|
|
||||||
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned/additive) ★:** **born-correct load** — `CycleDirectorSpawnSystem` stages `PendingSave` AT SPAWN; `BaseRestoreSystem` replays structures charge-free + REMAINING-tick cooldowns + HP. `SaveService.Load` = additive floor `[MinLoadableVersion=2, Current]` (old saves load; missing field 0-defaults). See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
|
- **Disk persistence (`SaveData`, single-slot atomic JSON, versioned/additive) ★:** **born-correct load** — `CycleDirectorSpawnSystem` stages `PendingSave` AT SPAWN; `BaseRestoreSystem` replays structures charge-free + REMAINING-tick cooldowns + HP. `SaveService.Load` = additive floor `[MinLoadableVersion=2, Current]` (old saves load; missing field 0-defaults). See [[DR-019_Frontend_Menu_Settings_Saves_Build]].
|
||||||
|
|
||||||
### Presentation / juice / VFX
|
### Presentation / juice / VFX
|
||||||
@@ -140,7 +137,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]].
|
- `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:** `Assets/Scenes/MainMenu.unity` (build index 0) boots the UITK frontend (menu world only); `Assets/Scenes/Game.unity` (index 1) holds gameplay with `Assets/_Project/Subscenes/Gameplay.unity` wired in as the baked subscene (GameObject `GameplaySubScene`). `SampleScene`/`DevSandbox` are kept as reference/dev scenes. The on-demand lifecycle (`WorldLauncher`/`SessionRunner`/`MainMenuController`) creates the right worlds per menu choice (Single/Host/Join), THEN `LoadScene(Game)` (subscene-streaming rule above).
|
- **Scenes:** `Assets/Scenes/MainMenu.unity` (build index 0) boots the UITK frontend (menu world only); `Assets/Scenes/Game.unity` (index 1) holds gameplay with `Assets/_Project/Subscenes/Gameplay.unity` wired in as the baked subscene (GameObject `GameplaySubScene`). `SampleScene`/`DevSandbox` are kept as reference/dev scenes. The on-demand lifecycle (`WorldLauncher`/`SessionRunner`/`MainMenuController`) creates the right worlds per menu choice (Single/Host/Join), THEN `LoadScene(Game)` (subscene-streaming rule above).
|
||||||
- **Core loop = ready-check multi-room RUNS ★ (DR-044; DR-031/042 base-mining text archived 07-04):** base = pure SPEND hub (no base nodes; `Tuning.StartingOre` grubstake). `RunDirectorSystem` = sole `RunInfo` writer (`[UpdateBefore(CyclePhaseSystem)]`; NOTHING else in the room chain touches CyclePhase); banks Charge+retaliation once-per-`RunEpoch` on boss-clear ONLY; strips the boon band + every `BoonOffer` on exit; teleports ONLY launch-stamped **`RunParticipant`s** (released on Returning — dead-respawned re-conscript, late joiners stay home). Rooms spawn per `RoomEpoch` at ping-pong sub-slots, `RoomTag`-torn-down; first slot waits `Tuning.RoomEntryGraceTicks`. ★ a serialized prefab bool ignores the C# initializer — flip `CycleDirector.prefab`. **Demo (DR-045/046): `GoalProgress.Target`=2; `Health.Max` now `[GhostField]` (boss + enemy HP bars). Boss = scaled Charger + server-only `BossState` kit (sole `BossAISystem` mover: SLAM/phase-2 summon/knockback-immune). Grunt windup commits ~last 30%; Charger stagger + Spitter cornered-hold. Rooms gate on a PORTAL (`RunLifecycle.RoomExplore=6` loot window; `PortalInteractRequest`→server `PortalCommand`; empty expedition advances now); class picked + per-run PREP buffs spent at base each run (Staging-gated RPCs via the shared `ClassSwapUtil`; disjoint prep `StatModifier` band stripped on Returning).** See [[DR-044_Expedition_Redesign_Shipped_Demo_Polish]] · [[DR-045_Combat_Demo_Feel_Boss_Fight]] · [[DR-046_Base_Expedition_Ties_Portal_Class_Prep]].
|
- **Core loop = ready-check multi-room RUNS ★ (DR-044/045/046):** base = pure SPEND hub. `RunDirectorSystem` = sole `RunInfo` writer (`[UpdateBefore(CyclePhaseSystem)]`; nothing else in the room chain touches CyclePhase); banks Charge+retaliation once-per-`RunEpoch` on boss-clear ONLY; strips the boon band + `BoonOffer`s on exit; teleports ONLY launch-stamped `RunParticipant`s (released on Returning). Rooms spawn per `RoomEpoch`, `RoomTag`-torn-down, gate on a PORTAL (`RunLifecycle.RoomExplore=6` loot window; `PortalInteractRequest`→server `PortalCommand`). Class + per-run PREP spent at base (Staging-gated RPCs via shared `ClassSwapUtil`; prep `StatModifier` band stripped on Returning). `Health.Max` is a `[GhostField]`. Boss = server-only `BossState`/`BossAISystem` (sole boss mover). ★ **a serialized prefab bool ignores the C# initializer — flip the value in the prefab.** Feel/tuning detail → [[DR-044_Expedition_Redesign_Shipped_Demo_Polish]] · [[DR-045_Combat_Demo_Feel_Boss_Fight]] · [[DR-046_Base_Expedition_Ties_Portal_Class_Prep]].
|
||||||
|
|
||||||
## DOTS / ECS conventions (authoritative summary)
|
## DOTS / ECS conventions (authoritative summary)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: 2026-07-07_Workflow_Consolidation
|
||||||
|
type: note
|
||||||
|
permalink: gamevault/07-sessions/2026/2026-07-07-workflow-consolidation
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2026-07-07 (session C) — Workflow consolidation: CLAUDE.md audit, extraction, validation harness
|
||||||
|
|
||||||
|
**Driver:** operator ask — "is CLAUDE.md appropriate and accurate? should any of it be separated into docs/skills?
|
||||||
|
what should change to make you a better agent?" Plus: collision playtest finding scoped + logged.
|
||||||
|
|
||||||
|
## Collision todo (Phase 1.5 stabilize #2 — scoped)
|
||||||
|
|
||||||
|
Operator: **collision shapes don't match the visual meshes** (cover rocks etc.) — environment collider FIDELITY, not
|
||||||
|
body-blocking or hit-reg. Logged on the roadmap with the fix direction (fit colliders to render meshes; verify in the
|
||||||
|
baked subscene PhysicsWorld; re-validate the enemy stuck-fix backstop after shape changes).
|
||||||
|
|
||||||
|
## CLAUDE.md audit → findings → fixes (all applied)
|
||||||
|
|
||||||
|
- **1 false claim:** `CreateLocalWorld` "is internal" — reflect-checked installed netcode 6.5.0: **PUBLIC** now.
|
||||||
|
- **3 stale "1.13.2" stamps** (pre-6.5-unification); `NetCodeTestWorld` re-checked (still effectively unavailable).
|
||||||
|
- **1 dead path:** conventions pointed at `~/.claude/skills/dots-dev/` — the skill is in-repo only.
|
||||||
|
- ctx7 netcode pin re-searched: **no 6.x docs set exists yet** — `1_10_api` stays, annotation refreshed.
|
||||||
|
- **Systemic:** chronic budget saturation (39.9–40.2 KB all week; every gotcha cost a condensation pass). Root cause:
|
||||||
|
~⅓ of the file was game-state narrative duplicating vault DRs.
|
||||||
|
|
||||||
|
## Operator decisions (4 locked)
|
||||||
|
|
||||||
|
1. **Full game-state extraction NOW** → done: PlacedStructure/EB-1/EB-2/END-1/END-2/M7/harvest/core-loop bullets
|
||||||
|
condensed to invariants + DR pointers (originals verbatim in the gotchas archive, heading 2026-07-07c).
|
||||||
|
**CLAUDE.md: 40,183 → 38,096 B** — ~2.9 KB headroom; the per-session condensation tax is gone.
|
||||||
|
2. **`validation-harness.md` built** (`.claude/skills/dots-dev/references/`) — consolidates the formerly
|
||||||
|
machine-local-only Play-driving lore (pause/sleep/focus rules, execute_code traps, the server-inject harness
|
||||||
|
proven on NodeFeedbackSystem, headless-run failure modes, visual-verification limits) into the in-repo skill;
|
||||||
|
referenced from SKILL.md Phase 6 + References. Closes a cross-machine-rule violation.
|
||||||
|
3. **Standing docs-commit authorization** — vault/CLAUDE.md/skill-reference changes now auto-commit at session end
|
||||||
|
as a `Docs:` group; code commits stay explicitly gated; never push. Recorded in SKILL.md non-negotiable #5 +
|
||||||
|
native memory `docs-commits-standing-authorization`.
|
||||||
|
4. **Pending tree committed**: `8baf98622` (WorldFeelConfig attribute fix) + `5a16acc56` (session-B docs).
|
||||||
|
|
||||||
|
## Also recommended to the operator (their side)
|
||||||
|
|
||||||
|
- **Playtest-notes drop file** (`07_Sessions/User Sessions/`, object + action + expected-vs-actual) — kills the
|
||||||
|
scoping round-trip on feel feedback.
|
||||||
|
- Editor focus during compile/Burst-heavy phases (reaffirmed); the DR-044 2-instance LAN smoke is still open.
|
||||||
|
|
||||||
|
## Housekeeping
|
||||||
|
|
||||||
|
MEMORY.md index de-bloated (two ~500-word narrative lines → true one-liners; detail stays in the memory files);
|
||||||
|
the stale "scale LEG children" root-cause claim corrected in the redirect memory (real fix was the swallowed
|
||||||
|
attribute — see [[2026-07-07_Phase15_NodeFeedback_Attribute_Swallow]]).
|
||||||
|
|
||||||
|
## Next-session intent
|
||||||
|
|
||||||
|
Phase 1.5 in earnest: collision-fidelity pass (stabilize #2, now scoped) → lighting/atmosphere pass → art-look
|
||||||
|
decision gate (pixel filter ON/OFF side-by-sides). Operator eyeballs harvest feel on real nodes en route.
|
||||||
|
|
||||||
|
Related: [[Iteration_2026-07_CoopHades]] · [[2026-07-07_Phase15_NodeFeedback_Attribute_Swallow]]
|
||||||
@@ -447,3 +447,19 @@ from the deleted samples tree) so the deformation-material rule is still self-co
|
|||||||
- Old maintenance-section sentence: "Design rationale already lives in the per-milestone DRs (`Docs/Vault/07_Sessions/_Decisions/DR-###`)."
|
- Old maintenance-section sentence: "Design rationale already lives in the per-milestone DRs (`Docs/Vault/07_Sessions/_Decisions/DR-###`)."
|
||||||
|
|
||||||
**The incident behind the new gotcha:** the 2026-07-07 "Node* knobs" edit to `WorldFeelConfig.cs` (commit `cc2e7ad95`) replaced a span that included the `[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]` attribute above `ResetDefaults()`, leaving a bare zero-indent signature. Nothing else calls `ResetDefaults` → all statics stayed C# defaults (`Enabled=false`, `ChipBurstCount=0`) → the ENTIRE harvest/world-feedback slice (node shrink/pop, chip particles, SFX, camera micro-punch) was silently dead; the operator caught it in the Phase 1 gate playtest. Live diagnosis disproved the session-log's own PTM-on-root hypothesis first (the Model child is properly parented; root PTM 0.783 → child LTW 1.723 = 2.2×0.783, exact). Second edit-swallows-adjacent-line bug in two days (07-06 `_portalMat` comment-swallow NRE). Full write-up: `07_Sessions/2026/2026-07-07_Phase15_NodeFeedback_Attribute_Swallow.md`.
|
**The incident behind the new gotcha:** the 2026-07-07 "Node* knobs" edit to `WorldFeelConfig.cs` (commit `cc2e7ad95`) replaced a span that included the `[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]` attribute above `ResetDefaults()`, leaving a bare zero-indent signature. Nothing else calls `ResetDefaults` → all statics stayed C# defaults (`Enabled=false`, `ChipBurstCount=0`) → the ENTIRE harvest/world-feedback slice (node shrink/pop, chip particles, SFX, camera micro-punch) was silently dead; the operator caught it in the Phase 1 gate playtest. Live diagnosis disproved the session-log's own PTM-on-root hypothesis first (the Model child is properly parented; root PTM 0.783 → child LTW 1.723 = 2.2×0.783, exact). Second edit-swallows-adjacent-line bug in two days (07-06 `_portalMat` comment-swallow NRE). Full write-up: `07_Sessions/2026/2026-07-07_Phase15_NodeFeedback_Attribute_Swallow.md`.
|
||||||
|
|
||||||
|
## 2026-07-07c — full game-state extraction (operator-ordered, workflow-consolidation session)
|
||||||
|
|
||||||
|
CLAUDE.md was chronically saturated (39.9–40.2 KB for a week; every gotcha addition cost a condensation pass). The operator ordered a FULL extraction of game-state narrative → one-line invariants + DR pointers. Original long-form bullets preserved here verbatim:
|
||||||
|
|
||||||
|
- **`PlacedStructure{[GhostField] byte Type; int2 Cell (server-only); uint NextTick; uint LastProcessedTick}`** on an ownerless interpolated ghost. **Bake the tick fields** (catch-up linchpin); only `Type` replicates (client derives `Cell`). **Occupancy is DERIVED** by scanning live ghosts into a Temp `NativeHashSet<int2>`, never baked. See [[DR-014_M6_Build_Structures_Automation_Foundation]].
|
||||||
|
- **EB-1 machines can die ★ (DR-032):** structures bake `Health`(`[GhostField]`)+`DamageEvent`+`Destructible`; `HealthApplyDamageSystem` destroys a `Destructible` at 0 (occupancy auto-frees). `EnemyAISystem` fortress-targets weighted-nearest players+structures (`EnemyAIMath.PickWeightedNearest`; snapshot ABOVE the early-return; `StructureAggroWeight`<1 SQUARED). ★ a Health-less machine silently drops OUT of the aggro snapshot. See [[DR-032_EB1_Machines_Can_Die]].
|
||||||
|
- **EB-2 felt spend ★ (DR-033):** turret ammo = shared `Charge`(`ResourceId` **4**) on the `[GhostField] StorageEntry` ledger; `TurretFireSystem` spends from the ONE `GetSingletonEntity<ResourceLedger>` (afford→fire+cooldown, else SOFT-FAIL, no cooldown-burn). `Fabricator.InputFromLedger` reads it LIVE in-loop (no hoist → machines split a finite pool). See [[DR-033_EB2_Felt_Spend_Charge_Economy]].
|
||||||
|
- **END-1 losable Core ★ (DR-034):** `CoreIntegrity{[GhostField] int Current,Max; uint OverrunTick}` on the GLOBAL CycleDirector ghost; `CoreDamageSystem`/`CoreRestoreSystem` (server) drain near `PlotCenter`, regen ONLY in Calm. SOFT-loss edge IN `CyclePhaseSystem` (sole Phase writer): `Current<=0` in Siege → Calm (NO reward; transient `OverrunTick`, NOT latching). Core = `EnemyAISystem` FALLBACK target. SaveData **v4**. See [[DR-034_END1_Losable_Core]].
|
||||||
|
- **END-2 win/lose ★ (DR-036):** server-only `RunPhase` (writer `GoalReachedSystem`) + **REPLICATED `RunOutcome{[GhostField] byte}`** (writer `CyclePhaseSystem`; do NOT client-derive the banner). `GoalReached` arms a final siege ×`FinalSiegeMultiplier` at `Charge>=Target` (once)+FinalDefense; latch Victory/Loss+halt; SiegeTimeout OFF in the final; SaveData **v5**. See [[DR-036_END2_Final_Siege_Win_Lose]].
|
||||||
|
- **`GoalProgress{[GhostField] int Charge,Target}`** (the goal meter — ≠ EB-2 `ResourceId.Charge` ammo) rides the CycleDirector ghost. Resource-gated ability tiers/buffs reuse `StatModifier` (`StatRecomputeSystem`→`EffectiveAbilityStats`).
|
||||||
|
- **M7 Automation (server-only) ★:** `Harvester`/`Conveyor` palette-TRIMMED (code intact), `Fabricator` LIVE (EB-2); catch-up `ProductionMath.CyclesDue` (**lower-bound 0**); `RuntimePlacedTag`=player-built. See [[DR-020_M7_Automation_Production_Chains]].
|
||||||
|
- **Harvest routes by node region ★; inventory/equipment PAUSED:** in-run nodes→PERSONAL `InventorySlot` (`[GhostField] OwnerSendType.All`, spill→ledger); `G`=deposit at base (base nodes GONE — DR-044). Items/equip detail → gotchas archive (2026-06-12). See [[DR-026_Inventory_Equipment_Progression_Foundation]].
|
||||||
|
- **Core loop = ready-check multi-room RUNS ★ (DR-044; DR-031/042 base-mining text archived 07-04):** base = pure SPEND hub (no base nodes; `Tuning.StartingOre` grubstake). `RunDirectorSystem` = sole `RunInfo` writer (`[UpdateBefore(CyclePhaseSystem)]`; NOTHING else in the room chain touches CyclePhase); banks Charge+retaliation once-per-`RunEpoch` on boss-clear ONLY; strips the boon band + every `BoonOffer` on exit; teleports ONLY launch-stamped **`RunParticipant`s** (released on Returning — dead-respawned re-conscript, late joiners stay home). Rooms spawn per `RoomEpoch` at ping-pong sub-slots, `RoomTag`-torn-down; first slot waits `Tuning.RoomEntryGraceTicks`. ★ a serialized prefab bool ignores the C# initializer — flip `CycleDirector.prefab`. **Demo (DR-045/046): `GoalProgress.Target`=2; `Health.Max` now `[GhostField]` (boss + enemy HP bars). Boss = scaled Charger + server-only `BossState` kit (sole `BossAISystem` mover: SLAM/phase-2 summon/knockback-immune). Grunt windup commits ~last 30%; Charger stagger + Spitter cornered-hold. Rooms gate on a PORTAL (`RunLifecycle.RoomExplore=6` loot window; `PortalInteractRequest`→server `PortalCommand`; empty expedition advances now); class picked + per-run PREP buffs spent at base each run (Staging-gated RPCs via the shared `ClassSwapUtil`; disjoint prep `StatModifier` band stripped on Returning).** See [[DR-044_Expedition_Redesign_Shipped_Demo_Polish]] · [[DR-045_Combat_Demo_Feel_Boss_Fight]] · [[DR-046_Base_Expedition_Ties_Portal_Class_Prep]].
|
||||||
|
|
||||||
|
Also this pass (earlier, same session): four stale netcode "1.13.2" stamps corrected to 6.5.0 (CreateLocalWorld is now PUBLIC — reflect-verified; NetCodeTestWorld still effectively unavailable), the dead `~/.claude/skills` path fixed to the in-repo skill, and the ctx7 netcode pin re-annotated (no 6.x docs set exists as of 07-07).
|
||||||
|
|||||||
Reference in New Issue
Block a user