Set up DOTS + Netcode for Entities foundation

One-time stack setup per Docs/dots-setup-task.md (Unity 6.4.7 / 6000.4.7f1).
Packages: entities 6.4.0, entities.graphics 6.4.0, netcode 1.13.2, physics 1.4.6.

- Assets/_Project asmdef split: ProjectM.Simulation/Client/Server/Authoring (root ns ProjectM)
- GameBootstrap : ClientServerBootstrap; verified separate client + server worlds in Play Mode
- Gameplay subscene wired into SampleScene as a baking target
- Heartbeat component + Burst ISystem; EditMode smoke test green (1/1)
- In-repo Obsidian vault (Docs/Vault) incl. DR-001 (plain-Entities test over internal NetCodeTestWorld)
- Portable .mcp.json (basic-memory + serena via ${CLAUDE_PROJECT_DIR}); CLAUDE.md conventions
- .gitignore for DOTS baking cache + machine-local config

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Luis Gonzalez
2026-05-29 22:06:44 -07:00
parent 25e3493e75
commit 99d8d2d2a9
74 changed files with 1609 additions and 36 deletions
+82
View File
@@ -0,0 +1,82 @@
# Project M — CLAUDE.md
Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-authoritative, input-only clients, client prediction. This file is committed and is the authoritative, cross-machine source of conventions. The `/dots-dev` skill drives feature work; the one-time stack setup lives in `Docs/dots-setup-task.md`.
## Stack (installed versions — Unity 6.4.7 / `6000.4.7f1`)
| Package | Version | Notes |
|---|---|---|
| `com.unity.entities` | **6.4.0** | Entities/Collections/Graphics now track the **Editor** version (6.x), not the old 1.x line. |
| `com.unity.entities.graphics` | **6.4.0** | Renders entities under URP 17.4. |
| `com.unity.collections` | 6.4.0 | (transitive) |
| `com.unity.netcode` | **1.13.2** | Netcode **for Entities** (ECS). NOT `com.unity.netcode.gameobjects`. Independent 1.x versioning. |
| `com.unity.physics` | **1.4.6** | Unity Physics (DOTS). Independent 1.x. |
| `com.unity.transport` | 2.7.2 | (transitive) |
| `com.unity.burst` | 1.8.29 | (transitive) |
| `com.unity.mathematics` | 1.3.3 | (transitive) |
> **Upgrading Unity 6.4 → 6.6:** Entities/Collections/Graphics would renumber to 6.6.x; Netcode/Physics stay independent 1.x (slated to become Core packages later in 2026). The setup here (asmdefs, bootstrap, subscene, smoke test) is forward-compatible — just let packages re-resolve. The `NetCodeTestWorld` access constraint below is **unchanged** by the upgrade.
## Namespaces & assembly split
Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in four asmdefs (never create/edit `.csproj`/`.sln`; only `.asmdef`):
| Assembly | Namespace | Runs in | References |
|---|---|---|---|
| `ProjectM.Simulation` | `ProjectM.Simulation` | **client + server** worlds | Entities, Collections, Mathematics, Burst, Unity.Physics, Unity.NetCode |
| `ProjectM.Client` | `ProjectM.Client` | client world only | + Simulation, Unity.Entities.Graphics |
| `ProjectM.Server` | `ProjectM.Server` | server world only | + Simulation, Unity.NetCode |
| `ProjectM.Authoring` | `ProjectM.Authoring` | bake time (+ scene runtime) | Simulation, Entities, Mathematics, Unity.NetCode |
- **Simulation** = components + systems shared by both worlds (most gameplay). **Client/Server** = world-specific. **Authoring** = `…Authoring` MonoBehaviours + `Baker<T>`.
- Other folders: `Assets/_Project/Subscenes/` (baked entity subscenes), `Assets/_Project/Prefabs/`, `Assets/_Project/Tests/EditMode/`.
## Bootstrap & worlds
- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` → overrides `Initialize`, sets `AutoConnectPort = 0` (no auto-connect), calls `CreateDefaultClientServerWorlds()`. Entering Play Mode creates separate `ServerWorld` (`WorldFlags.GameServer`) and `ClientWorld` (`WorldFlags.GameClient`) — verified.
- `Assets/_Project/Subscenes/Gameplay.unity` is wired into `SampleScene` (GameObject `GameplaySubScene`) as a baking target. Replace `SampleScene` with a dedicated bootstrap scene when building for real.
## DOTS / ECS conventions (authoritative summary)
Full rules: `~/.claude/skills/dots-dev/references/dots-conventions.md` (Windows: `%USERPROFILE%\.claude\skills\dots-dev\references\`). These **replace** classic MonoBehaviour/GameObject patterns.
- **`struct : IComponentData`** is the default (unmanaged, Burst/job-friendly). `class : IComponentData` only for genuine managed refs (main-thread, no Burst). `IBufferElementData` for per-entity arrays. `IEnableableComponent` to toggle state without a structural change.
- **Systems:** `ISystem` (struct) + `[BurstCompile]` is the **default**; `SystemBase` only when touching managed objects. `SystemAPI.Query<…>()` to iterate. **Aspects (`IAspect`) are DEPRECATED (Entities 1.4+) — do not author new ones.** `Entities.ForEach` is legacy.
- **Jobs:** `IJobEntity` / `IJobChunk`; thread `JobHandle` through `state.Dependency`; mark inputs `[ReadOnly]`. Allocators: `Temp` (frame), `TempJob` (one job), `Persistent` (must dispose). Burst breaks on managed types/exceptions/reflection/strings.
- **Structural changes** (add/remove component, create/destroy entity) invalidate handles + cause sync points → batch via **`EntityCommandBuffer`** (Begin/End`Simulation`EntityCommandBufferSystem; `.AsParallelWriter()` in parallel jobs).
- **Baking:** `…Authoring` MonoBehaviour + `class FooBaker : Baker<FooAuthoring>``GetEntity(authoring, TransformUsageFlags.…)` then `AddComponent`. Subscenes stream async — entities aren't present the instant a reference exists.
- **Netcode:** ghosts = replicated entities (`GhostAuthoringComponent` + `[GhostField]`); predicted (player-controlled, rolled back) vs interpolated. Core sim runs in `PredictedSimulationSystemGroup` (fixed step, **runs multiple times per frame** on rollback → must be deterministic/idempotent; filter with `.WithAll<Simulate>()`). **Server-authoritative: clients send input (`IInputComponentData`), not state.** RPCs (`IRpcCommand`) for one-off events. **No wall-clock/`Time.deltaTime`/`System.Random` in predicted sim.**
- **Always verify volatile DOTS/Netcode API shape via context7 at code-time** — do not trust memory. See `context7-libraries.md`. Pinned IDs for our versions: Entities → `/websites/unity3d_packages_com_unity_entities_6_5_manual`; Netcode → `/websites/unity3d_packages_com_unity_netcode_1_10_api` (closest published; we run 1.13.2 — re-resolve if a 1.13 set appears); ECS samples → `/unity-technologies/entitycomponentsystemsamples`.
## Testing
- **Default pattern = plain-Entities EditMode test:** create a `World`, register the system in `SimulationSystemGroup`, tick, assert. Public API, always green, version-independent. Example: `Assets/_Project/Tests/EditMode/HeartbeatSystemTests.cs`. Run via Unity Test Runner or MCP `run_tests(mode="EditMode", assembly_names=["ProjectM.Tests.EditMode"])`.
- **`NetCodeTestWorld` is `internal`** in netcode 1.13.2 (`Unity.NetCode.Tests`, assembly `Unity.NetCode.TestsUtils.Runtime.Tests`), exposed only via a fixed `[InternalsVisibleTo]` allow-list of Unity assemblies. To use it you must name a test asmdef to match an entry (e.g. `Unity.NetcodeSamples.EditModeTests`) — or vendor the test utils. See `Docs/Vault/07_Sessions/_Decisions/DR-001_Netcode_Test_Harness.md`. **This does not change on Unity 6.6.** Netcode world boot is covered by the Play Mode check, not a NetCodeTestWorld test.
- Burst/source-gen errors surface at editor compile, not a plain build — always check `read_console` after script changes, and run a play/tick test, not just a compile.
## Guardrails
- **Never** edit a `.meta` independently of its asset; delete an asset **and** its `.meta` together.
- **Never** read/write `Library/`, `Temp/`, `obj/`, `Logs/`, `UserSettings/` (generated/cache). Use MCP resources for editor state instead.
- **Never** create/edit/commit `.csproj`/`.sln` — only `.asmdef`.
- **No asset/scene edits during Play Mode.** Check `editor_state.advice.ready_for_tools` before mutating; package adds/refreshes trigger domain reloads — wait for `is_compiling=false`.
## Memory — four layers (which tool when)
| Layer | Use for | Crosses machines? |
|---|---|---|
| **In-repo vault** `Docs/Vault/` | Design docs, decision records (DR-###), session logs, roadmap — human-facing truth | **Yes** (git) |
| **basic-memory** MCP | Semantic/wikilink recall over those same vault files | Yes (indexes the vault) |
| **serena** MCP | C# symbol nav (`find_symbol`, references) of `Assets/_Project/` | N/A (from code) |
| **Native Claude memory** (`memory/`, `MEMORY.md`) | Machine-local facts, working-style, preferences | **No** |
- Where is X defined / who calls it → **serena** (fallback `Grep`/`Glob`). What did we decide / how does Z work → **basic-memory** → read the vault note. Literal string / asset GUID → **Grep/Glob**. Current DOTS API → **context7**. Conventions → this file.
- **Cross-machine rule:** durable truth goes in the **vault** or **this file** (both committed). Native `memory/` is local-only and does NOT sync — never the sole home of a decision.
- **serena C# caveat:** its language server is flaky on Unity (can auto-install the wrong .NET, `.sln` load timeouts). If `find_symbol` errors/stalls, **fall back to `Glob`/`Grep`** (or add `claude-context` with local embeddings as a code-search index). serena live-verification was deferred at setup; confirm on first use.
## Per-machine setup (NOT in git — redo on each machine)
`.mcp.json` is committed and portable (`${CLAUDE_PROJECT_DIR}` only). But each machine still needs:
1. `uv`/`uvx`, the Obsidian app + `obsidian-cli`, and the `dots-dev` skill in `~/.claude/skills/`.
2. **basic-memory project registration** (machine-local config): `uvx basic-memory project add gamevault "<repo>/Docs/Vault" --default`, then `uvx basic-memory reindex --full --search --embeddings --project gamevault`.
3. Unity 6.4 opens the project and the CoplayDev Unity-MCP bridge connects (`mcpforunity://editor/state``ready_for_tools`).