Files
kronic 4ac1ae5a2e Rewrite /dots-dev skill: Workflow-first, two-review sandwich, 6.5 pins
Realigns the skill with how sessions actually run now — it predated the
Workflow tool and the ultracode two-review practice. Driven by an analysis
of the full session history + the 26-file memory corpus + skill-authoring
research, adversarially reviewed.

Key changes:
- Workflow-first orchestration: drop the dead manual "swarm (<=N agents)"
  model and the impossible 3-5-agent impl swarm; implementation is serial
  orchestrator MCP edits. New references/workflow-patterns.md (ground
  fan-out + design-review lens/critic) replaces agent-briefs.md.
- Pre-code design-review + post-impl diff-review are now first-class gated
  phases (the spine that catches what green tests + a clean Play miss).
- Size by blast-radius / netcode-heaviness, not time estimates.
- Lean SKILL.md (222 -> 131 lines, -25% KB): leans on CLAUDE.md instead of
  duplicating its MCP cheat-sheet / anti-patterns / error-recovery.
- ctx7 CLI / find-docs mechanism (the MCP verbs are gone); live-verified
  6.5-era library pins; kill the dead NetCodeTestWorld test path.
- Encode operator gates (no-time-estimates, present-forks, never-defer,
  tuning-autonomy) + the highest-recurrence MCP-edit / Workflow gotchas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:19:49 -07:00

69 lines
8.9 KiB
Markdown

# DOTS / Netcode for Entities — Conventions & Gotchas
Reusable rule set the `/dots-dev` skill enforces. Targets **Unity 6.5, Entities 6.5, Netcode for Entities 6.5 (≡ the old ~1.12/1.13 API lineage), Unity Physics 6.x** — the unified 6.x DOTS line. These APIs move fast — anything flagged **[verify]** must be confirmed via `ctx7`/`find-docs` at code-time (see [`context7-libraries.md`](context7-libraries.md)), not trusted from memory. The project's `CLAUDE.md` is authoritative where it differs.
These conventions **replace** classic MonoBehaviour/GameObject conventions. If a design reaches for a MonoBehaviour singleton, a ScriptableObject "service" for runtime simulation, `[SerializeField] private` data carriers, or coroutines for sim logic, it is almost certainly wrong for DOTS.
## 1. Code structure
- **`struct : IComponentData`** — the default. Unmanaged, blittable, Burst- and job-friendly. Use for nearly all data.
- **`class : IComponentData`** — managed component. Only for genuine managed refs (a `GameObject`, `Material`). Main-thread only, no Burst, no jobs. Keep out of hot paths.
- **`IBufferElementData`** — per-entity dynamic array (`DynamicBuffer<T>`): inventory, path points, command history.
- **`ISharedComponentData`** — groups entities into chunks by shared value; changing it is a **structural change**. Use sparingly; prefer unmanaged shared components.
- **`IEnableableComponent`** — toggle a component on/off **without a structural change**. Prefer over add/remove tag components for frequently-flipped state. Enabled by default.
- **Systems**: `ISystem` (struct, Burst-compatible — **default**) vs `SystemBase` (class, managed, main-thread, no Burst — only when you must touch managed objects). Tag system methods `[BurstCompile]`.
- **`SystemAPI`** is the entry point inside systems: `SystemAPI.Query<...>()`, `.GetComponent`, `.Time`, `.GetSingleton`. Source-generated; valid only inside `ISystem`/`SystemBase`.
- **Aspects (`IAspect`) are DEPRECATED as of Entities 1.4 — do not author new ones.** Use components + `SystemAPI.Query`/`IJobEntity` directly. `Entities.ForEach` is legacy; avoid. **[verify]** the current recommended replacement.
- **Naming**: components are nouns/adjectives (`Health`, `Velocity`, `MoveSpeed`); tags suffix-free or `…Tag`; authoring MonoBehaviours suffix `Authoring`; systems suffix `System`; buffer elements often `…Element`/`…BufferElement`.
## 2. Jobs & Burst
- **`IJobEntity`** — preferred per-entity job (source-generated `Execute` over a query). **`IJobChunk`** — lower-level, chunk-wide/manual iteration. **`SystemAPI.Query`** — main-thread iteration.
- **Scheduling** — `Schedule()` / `ScheduleParallel()`; thread the returned `JobHandle` through `state.Dependency`. Avoid manual `.Complete()` unless you need results now (it's a sync point).
- **Burst breaks on** — managed types, `try/catch`/exceptions, `typeof`/reflection, virtual calls, `string` ops, GC allocation, most of `System.*`. Keep job code `unmanaged`.
- **Collections** — `NativeArray/List/HashMap`, `NativeQueue`, etc. Allocators: `Allocator.Temp` (frame/scope, auto-freed, **not** across job boundaries), `Allocator.TempJob` (one job, must dispose), `Allocator.Persistent` (long-lived, must dispose). **Always dispose** non-Temp, or `.Dispose(jobHandle)`.
- Mark inputs `[ReadOnly]` to allow parallel access; write-aliasing across parallel jobs is a safety error.
## 3. Baking (authoring → entities)
- **Why**: authoring data is converted **offline at edit/import time** into entities — no runtime conversion cost, deterministic, streamable.
- **Pattern**: a `MonoBehaviour` named `…Authoring` + `class FooBaker : Baker<FooAuthoring>` whose `Bake()` calls `GetEntity(...)` then `AddComponent(entity, new Foo{...})`.
- **Subscenes** hold baked entities and stream in/out asynchronously — entities are **not present the instant a subscene reference exists**; gate logic on load state.
- **Mistakes**: referencing other entities without `GetEntity(authoring, TransformUsageFlags.…)`; not declaring `DependsOn` (so bake doesn't re-run on asset change); doing runtime-only work in `Bake()`; forgetting `TransformUsageFlags` (→ missing `LocalTransform`).
## 4. Netcode for Entities
- **Ghosts** = replicated entities. Mark a prefab with **`GhostAuthoringComponent`**; mark replicated fields with **`[GhostField]`**; tune per-component replication with **`[GhostComponent]`** (e.g. `SendToOwner`). Variants via **`IGhostComponentVariation`**. **[verify]** attribute parameters.
- **Predicted vs interpolated ghosts**: predicted = simulated locally + rolled back (player-controlled, fast reaction). Interpolated = smoothed display of past server state (remote/non-critical).
- **Prediction & rollback**: core sim runs in **`PredictedSimulationSystemGroup`**, a **fixed-step** group on **both client and server**. On each new snapshot the client re-simulates from the oldest received tick → systems run **multiple times per frame**; sim code must be idempotent/deterministic. Filter queries with **`.WithAll<Simulate>()`** so only currently-simulated entities run. **[verify]** group internals/names.
- **Server-authoritative**: server is truth; clients send **input**, not state.
- **Input**: prefer **`IInputComponentData`** (auto-buffered to `InputBufferData<T>`); `ICommandData` is the lower-level command-buffer form. Don't read raw `UnityEngine.Input` in sim systems. **[verify]** helper signatures.
- **RPCs**: `struct : IRpcCommand` for one-off events (connect, requests). Not for per-tick state.
- **Determinism**: no `Time.deltaTime`/wall-clock in sim — use the netcode fixed tick. Avoid non-deterministic float sources, `System.Random`, ordering by hash. Cross-machine float determinism is fragile — keep predicted sim simple and consistent.
- **Worlds & bootstrap**: separate **client** and **server** `World`s; subclass **`ClientServerBootstrap`** (`CreateClientWorld`/`CreateServerWorld`) to customize. Annotate systems with `[WorldSystemFilter(...)]` to target client/server/thin. **[verify]** bootstrap method names + `WorldSystemFilterFlags`.
## 5. Common footguns
- **Structural changes** (add/remove component, create/destroy entity, set shared component) **invalidate `Entity`/component handles & references** and cause sync points. Batch via **ECB**.
- **ECB (`EntityCommandBuffer`)**: record now, play back later. Use built-in `Begin/EndSimulationEntityCommandBufferSystem` (and `BeginInitialization…`). One ECB per job; in parallel jobs use `.AsParallelWriter()`. Don't read back values you just recorded.
- **System order**: `[UpdateInGroup]`, `[UpdateBefore/After]`. Key groups: `InitializationSystemGroup`, `SimulationSystemGroup`, `PresentationSystemGroup`; netcode adds `PredictedSimulationSystemGroup`, `GhostSimulationSystemGroup`. **[verify]** netcode group set.
- **Main-thread-only**: managed components, `EntityManager` structural ops, most `World`/managed access. Keep out of Burst jobs.
- **No managed refs in unmanaged components** — won't compile / breaks Burst. Use a managed `IComponentData`, a `BlobAssetReference`, or an `Entity` reference instead.
- **Subscene timing & GO↔entity mixing**: entities may not exist yet; bridging GameObjects↔entities is manual and a known pain point — design around it, don't sprinkle hybrid links.
## 6. Testing (what "compiles clean" misses)
- **Default = plain-Entities EditMode test:** `new World` → register the system in `SimulationSystemGroup``SortSystems``Update()` → assert. Public API, version-independent. This is the project's actual harness.
- **Extract pure logic to a `*Math.cs` helper and unit-test it** — deterministic, fast, version-proof. Cover swept hit-detection with a **tunnelling regression** (a point-based test won't catch the tunnel).
- **Netcode coverage = one live Play smoke:** boot client+server (`execute_code`), step ticks, assert **server == client** for the replicated surface (identify worlds by `world.Name == "ServerWorld"/"ClientWorld"`). **`NetCodeTestWorld` is `internal`** in this line (exposed only to a fixed allow-list) and **0 of the project's test files use it** — do not prescribe it as the primary path. (Thin clients via Multiplayer PlayMode Tools exist for soak/load testing if ever needed — not in current practice.)
- Burst/source-gen failures surface at **editor compile/play**, not a plain C# build. Determinism desync, rollback bugs, sync-point stalls, and structural-change invalidation only show at **runtime under prediction** — always run a play/tick test, not just a build.
## 7. Verify via context7 at code-time (volatile — do NOT hardcode)
- Aspect deprecation status & the recommended replacement.
- Exact **system group names/order** and netcode prediction-group internals.
- **Input** API (`IInputComponentData` vs `ICommandData`) helper signatures.
- Ghost attribute options (`[GhostField]`/`[GhostComponent]` parameters, variants).
- Bootstrap/world-creation method names and `WorldSystemFilterFlags`.
- ECB system names + allocator/Collections API surface.