Docs: 07-21 auto-recast root-cause correction (wire-count InputEvent ★), harness lessons, CLAUDE.md net-zero trims
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 → archive dated headings: 06-17 · 07-04(a–c) · 07-06 · 07-07(b,c) · 07-13 · 07-15 · 07-16 (what each paid is noted in the archive).
|
||||
- Condensation history → the archive's dated headings (each edit's payment noted there).
|
||||
|
||||
## Stack — Unity 6.5.1 (`6000.5.1f1`, stable) as of 2026-06-27
|
||||
|
||||
@@ -26,7 +26,7 @@ Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-
|
||||
| `com.unity.mathematics` | 1.4.0 | (transitive) |
|
||||
| `com.rukhanka.animation` | **2.9.0** | Local pkg (`Packages/com.rukhanka.animation`). ECS skeletal animation (Burst CPU/GPU skinning). Resolves on 6.5.0 via SemVer floor. Netcode replication **OFF** → client-derived. See [[DR-022_Animation_Pipeline_Rukhanka_Synty]]. |
|
||||
|
||||
Values match `packages-lock.json` (reconciled 06-17; URP 17.5.0; `ai.assistant` removed 07-04). **History:** 6.6.0a6 transport bug — [[DR-002_Unity66_Alpha_Netcode_Transport]] + archive.
|
||||
Values match `packages-lock.json` (URP 17.5.0). **History:** 6.6.0a6 transport bug — [[DR-002_Unity66_Alpha_Netcode_Transport]] + archive.
|
||||
|
||||
## Namespaces & assembly split
|
||||
|
||||
@@ -67,7 +67,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
- **Move ownerless INTERPOLATED ghosts (enemies, pickups) SERVER-ONLY in the plain `SimulationSystemGroup`** — they aren't predicted; the server has no rollback. Stock `LocalTransform` replication carries position (no hand-written `[GhostField]`). A contact `DamageEvent` appended there drains the *following* tick (~16ms, fine for melee).
|
||||
- **`PhysicsVelocity` auto-replicates** (Netcode ships the default variant + serializer) — drive a predicted-physics body by writing `PhysicsVelocity.Linear`, not by teleporting `LocalTransform`.
|
||||
- **Ownerless interpolated ghost ≠ owner-predicted for buffer replication.** A server-spawned ownerless ghost replicates a `[GhostField] IBufferElementData` to all clients with **no `OwnerSendType` / no `GhostOwner`** — server mutations just propagate. `OwnerSendType.All` + `GhostOwner` are only for a predicting owner to recompute its own state.
|
||||
- **One-off shared-state actions belong on an `IRpcCommand`, not a predicted `InputEvent`** (RPCs are reliable; one-shot `InputEvent`s — like `Fire` — drop under server tick-batching). RPC payloads are plain blittable scalars (`int CellX/CellZ`, not `int2`; no `[GhostField]`). For a SINGLE shared target resolve a **server singleton** — never put an `Entity` in the command; use ghost-id+spawn-tick (`SpawnedGhostEntityMap`) only for many targets.
|
||||
- **One-off shared-state actions belong on an `IRpcCommand`, not a predicted `InputEvent`** (RPCs are reliable; one-shot `InputEvent`s drop under tick-batching). **★ Raw `InputBufferData` reads: `InputEvent.IsSet` = EVER-pressed — wire counts ACCUMULATE (only the decoded component is delta-corrected); gate on a count-STEP vs the prior tick's command, else one press recasts at every cooldown reopen (07-21).** RPC payloads are plain blittable scalars (`int CellX/CellZ`, not `int2`; no `[GhostField]`). For a SINGLE shared target resolve a **server singleton** — never put an `Entity` in the command; use ghost-id+spawn-tick (`SpawnedGhostEntityMap`) only for many targets.
|
||||
- **Apply server-only RPC effects in the server `SimulationSystemGroup`, NOT the predicted loop** (rollback would double-apply). Mutating a `DynamicBuffer` is not a structural change, so it's safe while iterating a different query.
|
||||
- **A system-ordering CYCLE is INVISIBLE to plain-Entities EditMode tests** (they register systems individually, unsorted) — it only throws `ComponentSystemSorter` "circular dependency cycle" at **world creation (Play)**. When you add cross-system `[UpdateBefore/After]`, re-audit the EXISTING `[Update*]` attributes of the systems you order around and **always Play-validate**. [[DR-017_Persistent_Base_Player_Driven_Pacing]]
|
||||
- **A dev/debug `IRpcCommand` wire TYPE must be UNCONDITIONAL** — the RpcCollection hash must match across release/dev peers; `#if`-gate only the send/receive SYSTEMS, never the struct. **Re-mean bytes, don't rename**: unchanged byte VALUES keep the `[GhostField]` serializer identical → re-bake-free (only authoring *default-value* edits re-bake the subscene).
|
||||
@@ -88,7 +88,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
- **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 bullets → gotchas archive 07-15). **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]].
|
||||
- **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]].
|
||||
|
||||
@@ -153,7 +153,7 @@ Full rules: `.claude/skills/dots-dev/references/dots-conventions.md` (in-repo; t
|
||||
|
||||
## Testing
|
||||
|
||||
- **Default = plain-Entities EditMode test:** create a `World`, register the system in `SimulationSystemGroup`, tick, assert. Public API, version-independent. Example: `Assets/_Project/Tests/EditMode/HealthApplyDamageSystemTests.cs`. Run via `run_tests(mode="EditMode", assembly_names=["ProjectM.Tests.EditMode"])`.
|
||||
- **Default = plain-Entities EditMode test:** create a `World`, register the system in `SimulationSystemGroup`, tick, assert. Public API, version-independent. e.g. `HealthApplyDamageSystemTests`. Run via `run_tests(mode="EditMode", assembly_names=["ProjectM.Tests.EditMode"])`.
|
||||
- **`NetCodeTestWorld` is `internal`** (6.5.0 re-check: not even loaded outside test asmdefs), exposed only to a fixed `[InternalsVisibleTo]` allow-list — to use it, name a test asmdef to match an entry (e.g. `Unity.NetcodeSamples.EditModeTests`) or vendor the test utils. Netcode world boot is covered by the Play Mode check, not a NetCodeTestWorld test. See [[DR-001_Netcode_Test_Harness]].
|
||||
- Burst/source-gen errors surface at editor compile, not a plain build — always `read_console` after script changes, and run a play/tick test, not just a compile. **Cover swept hit-detection with a tunnelling regression test** (the point-check tunnel bug doesn't surface in a point-based unit test).
|
||||
|
||||
@@ -173,4 +173,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` (the `unity-mcp-skill` + native `memory/` are 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 + 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`).
|
||||
|
||||
Reference in New Issue
Block a user