diff --git a/.claude/skills/dots-dev/SKILL.md b/.claude/skills/dots-dev/SKILL.md index f880afc3f..1ea9607c2 100644 --- a/.claude/skills/dots-dev/SKILL.md +++ b/.claude/skills/dots-dev/SKILL.md @@ -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. 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. -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. 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. ### 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. - **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. @@ -94,7 +94,7 @@ Per `CLAUDE.md` Memory section + [`references/memory-protocol.md`](references/me **Reviews**: pre-code · post-impl . **Next step**: . ``` -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) @@ -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/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/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. diff --git a/.claude/skills/dots-dev/references/validation-harness.md b/.claude/skills/dots-dev/references/validation-harness.md new file mode 100644 index 000000000..7c015370a --- /dev/null +++ b/.claude/skills/dots-dev/references/validation-harness.md @@ -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(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(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. diff --git a/CLAUDE.md b/CLAUDE.md index d66bc2cf2..d167ecce6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`. - **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. -- 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 @@ -86,15 +86,12 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui ### 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. -- **`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`, 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. -- **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` (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]]. +- **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`, 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]]. +- **`GoalProgress{[GhostField] int Charge,Target}`** (win meter — ≠ the `ResourceId.Charge` ammo) rides the CycleDirector ghost; buffs/tiers reuse `StatModifier`→`EffectiveAbilityStats`. +- **Automation (server-only):** catch-up `ProductionMath.CyclesDue` **lower-bound 0**; `RuntimePlacedTag`=player-built. [[DR-020_M7_Automation_Production_Chains]]. +- **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/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 @@ -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]]. - **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) diff --git a/Docs/Vault/07_Sessions/2026/2026-07-07_Workflow_Consolidation.md b/Docs/Vault/07_Sessions/2026/2026-07-07_Workflow_Consolidation.md new file mode 100644 index 000000000..798a3ae73 --- /dev/null +++ b/Docs/Vault/07_Sessions/2026/2026-07-07_Workflow_Consolidation.md @@ -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]] diff --git a/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md b/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md index 80b71514b..e57987b57 100644 --- a/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md +++ b/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md @@ -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-###`)." **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`, 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` (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).