You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: fix runbook markdown lint + regenerate AI config mirrors
Add blank lines around tables (MD058) and backtick-wrap _TYPE_ALIASES (MD037);
regenerate .cursor/rules/*.mdc mirrors for the three new .agents/rules files
(sync-agent-configs). Fixes the Lint Markdown and Validate AI Config Sync checks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
<!-- AUTO-GENERATED by scripts/sync-agent-configs.py. Edit .agents/rules/perf-work.md, then rerun the generator. -->
8
+
# Performance Work Rules (ENG2)
9
+
10
+
Applies to any change under `**/rendering/**`, `**/graphics/**`, `**/benches/**`, `scripts/bench-gate.py`, or any issue labeled `area:performance`.
11
+
12
+
## Definition of done (non-negotiable)
13
+
14
+
A performance change is done only when BOTH hold:
15
+
16
+
1. **A named scripted scene hits its numeric target** — S1–S5/H1/cull_scaling from `docs/src/runbook/perf-dod.md`, captured via `scripts/perf-capture` (3-run median on Apple M-series/Metal) or the CI `scripts/bench-gate.py` ratio-gate for CI-runnable benches.
17
+
2. **A phase counter attributes the cost.** The relevant GPU/CPU phase timing is non-zero and accounts for the frame.
18
+
19
+
**A counter that reads zero means the instrumentation is broken, never that the work is free.** GPU cost surfaces at surface-acquire/present, not in a CPU command-record timer — measure it with a real GPU timestamp (QuerySet), do not infer it.
20
+
21
+
## Rules
22
+
23
+
- No optimization without a measurement first. Post the before-number, then the after-number, on the issue.
24
+
- Never silently cap coverage (top-N, sampling, windowing) to hit a number — `log`/document what was dropped. A scene-windowing workaround is a consumer band-aid, not an engine fix.
25
+
- The target is **O(visible), not O(total)**: per-frame CPU cost must not scale with total scene/entity count. If it does, that is the bug, regardless of FPS.
26
+
- Steady-state render frame (no scene mutation) must perform **0 heap allocations** in the render path — assert it with the alloc-budget harness (ENG2-P0-15), do not eyeball it.
27
+
- Aim for headroom, not the 60 FPS floor: the v2 targets are 120+ FPS for tens of thousands of visible entities.
28
+
29
+
## Before closing
30
+
31
+
Run the relevant bench-gate scene, attach the capture (scene, numbers, phase breakdown, delta vs baseline). See `docs/src/runbook/perf-dod.md` for scenes and targets and `[perf-work]`↔`[render-v2]`↔`[testing-v2]` for the companion rules.
<!-- AUTO-GENERATED by scripts/sync-agent-configs.py. Edit .agents/rules/render-v2.md, then rerun the generator. -->
8
+
# Render Core v2 Rules (ENG2 Phases 2 & 6)
9
+
10
+
Applies to `**/graphics/**` and the renderer paths. These invariants supersede the performance/submission guidance in `graphics-patterns.md` (the backend-isolation and Renderer-trait rules there still hold). Full design: `docs/src/runbook/phases/phase-2.md` and RFC-0005.
11
+
12
+
## Submission model
13
+
14
+
- **O(visible), never O(total).** No per-frame iteration over the entire scene-object set. Cull against a spatial index (grid/BVH) *before* any transform/uniform/encode work. A culled object must pay zero per-frame cost.
15
+
- **Dense storage, not hash-map iteration.** Scene objects live in slot arrays with generational handles, iterated contiguously — not a per-frame walk of a `FxHashMap`.
16
+
- **Instancing by identity.** Draws are grouped by `(mesh id, material id)` and submitted instanced by default — including `CreatePlane`/`CreateCube`/`CreateSphere`/`CreateCylinder` primitives, not only `InstantiateModel`. A config flag that claims to enable instancing must actually drive the instanced path or not exist.
17
+
18
+
## Uniforms & buffers
19
+
20
+
- **Per-frame constants uploaded once** (camera, lights, fog) into one bind group. **Per-object data goes in dynamic-offset slots** — never copy a full multi-KB uniform block per draw, never issue one `write_buffer` per draw. Coalesce writes into one upload per frame.
21
+
- **Persistent, dirty-tracked instance/transform buffers.** Upload only changed ranges. Never rebuild the whole buffer, and never CPU-pre-transform vertices into a static VBO that must be re-baked on any change.
22
+
- Do not clone per-instance CPU vertex data for instances that share a mesh.
23
+
24
+
## Data seam
25
+
26
+
- Transforms are a **structure-of-arrays column shared between ECS and the renderer** (ENG2-P2-13). The renderer consumes dirty ranges from that column; it does not receive per-entity transforms across FFI one at a time.
27
+
28
+
## Every render/UI/particle change
29
+
30
+
- Add or update a **story** in the scene gallery (ENG2-P0-14) with a golden image and metric budget — see `[testing-v2]`.
31
+
- Meet the perf definition of done — see `[perf-work]` and `docs/src/runbook/perf-dod.md`.
32
+
- Raw GPU calls stay in `libs/graphics/backend/` (unchanged boundary).
<!-- AUTO-GENERATED by scripts/sync-agent-configs.py. Edit .agents/rules/testing-v2.md, then rerun the generator. -->
8
+
# Testing v2 Rules (ENG2)
9
+
10
+
Applies to all ENG2 work. Full strategy: `docs/src/runbook/testing-strategy.md`. Extends `testing.md` (GL-context and unit conventions there still hold).
11
+
12
+
## Spec-first (TDD)
13
+
14
+
- Every behavioral issue gets a spec test at `goud_engine/tests/spec/eng2_p<N>_<nn>_<slug>.rs` encoding its Acceptance Criteria as executable assertions. The spec **is** the gate evidence.
15
+
- Write the spec RED before the implementation; make it GREEN; then REFACTOR. A reviewer confirms it was red-before-green.
16
+
- The issue's Verification section names the spec path. Do not close an issue whose spec test does not exist or does not assert its acceptance criteria.
17
+
18
+
## Tiers — put each test at the right level
19
+
20
+
- **Unit** — pure logic, no GPU. Design v2 render/ECS logic (culling, instancing-grouping, sort-key, propagation) to be unit-testable without a GPU context.
21
+
- **Isolation** — one subsystem behind its null/provider boundary (`NullBackend`, null net/audio, in-memory VFS). If the seam to mock a dependency is missing, add the null/mock provider.
- **E2E** — SDK-driven headless engine (ENG2-P0-17); C#/TS/Python boot a real engine, run a scripted game, assert counts/world-hash/flat-RSS.
24
+
25
+
## Story gallery
26
+
27
+
- Every render/UI/particle/animation change adds or updates a **story** (ENG2-P0-14): scene + camera + knobs + expected invariants + golden image. CI renders it headless, diffs the golden, checks metric budgets, and publishes the HTML gallery.
28
+
- Changing a golden requires the documented golden-update approval — do not silently overwrite baselines.
29
+
30
+
## Determinism, property, fuzz
31
+
32
+
- Sim/scheduling changes run the determinism fixture (3 seeds × 10k ticks, hash-compare). Do not introduce nondeterminism (unordered iteration, wall-clock reads, FP drift).
| ENG2-P6-03 | Route FFI sprite paths through the core; delete immediate + rebuild-per-call paths | A | M | P6-02 |
@@ -416,6 +419,7 @@ CI runners cannot measure Metal. Two enforcement layers:
416
419
-**P6-05:** FFI batch sets only `u_viewport` (`draw/batch.rs:355`); Throne does world→screen math + visibility in C# per sprite (`GoudRenderPort.cs:275`). Camera uniform + grid/quadtree culling before submission; #554's configurable origin lands in the camera.
417
420
418
421
### Batch 6.3 — 2D scale features (3 groups, parallel)
422
+
419
423
| ID | Title | Grp | Effort | Blocked by |
420
424
|---|---|---|---|---|
421
425
| ENG2-P6-06 | Chunked tilemap renderer from TileLayer gids | A | M | P6-02 |
@@ -439,6 +443,7 @@ CI runners cannot measure Metal. Two enforcement layers:
439
443
**Goal:** The built-but-unwired services become real: async assets, mipmaps, one audio stack, one 2D physics, retained UI, multi-window. 6 issues.
440
444
441
445
### Batch 7.1 — Assets & media (3 groups, parallel)
446
+
442
447
| ID | Title | Grp | Effort | Blocked by |
443
448
|---|---|---|---|---|
444
449
| ENG2-P7-01 | Wire async asset pipeline into the runtime; background decode incl. RGB→RGBA off main thread | A | M | Phase 4 gate |
@@ -450,6 +455,7 @@ CI runners cannot measure Metal. Two enforcement layers:
450
455
-**P7-03:**`assets/audio_manager/` (2832 LOC) vs `libs/providers/impls/rodio_audio.rs:118-138` — two full rodio wrappers, two unsafe Send/Sync blocks; keep the provider-trait one, bridge asset data through it.
| ENG2-P8-05 | Engine event bus with FFI subscription (#547) | A | M | — |
@@ -498,6 +506,7 @@ CI runners cannot measure Metal. Two enforcement layers:
498
506
-**P8-08:** Deliberately late: Phases 2/3/6 already deleted much cgmath-using code; migrate the remainder onto `core/math`, drop the advisory-ignored dep.
499
507
500
508
### Batch 8.3 — Big-world (2 groups)
509
+
501
510
| ID | Title | Grp | Effort | Blocked by |
502
511
|---|---|---|---|---|
503
512
| ENG2-P8-09 | Navmesh generation/query for 3D navigation | A | L | P8-01 |
@@ -519,6 +528,7 @@ CI runners cannot measure Metal. Two enforcement layers:
519
528
**Goal:** Every kept platform is CI-real; developers can see and author scenes; examples prove the v2 API. 9 issues.
| ENG2-P9-01 | Scene inspector MVP (egui or web client over the MCP relay): entity tree + live component editing | A | L | P7-06 |
@@ -533,6 +543,7 @@ CI runners cannot measure Metal. Two enforcement layers:
533
543
-**P9-05:**`providers/impls/webrtc_network/mod.rs:1-17` is custom UDP+STUN labeled WebRTC, `net-webrtc=[]` pulls no crate; ~7-8k LOC networking has zero production consumers — decision record on tiering (default-feature status) + honest naming.
| ENG2-P6-03 | Route FFI sprite paths through the core; delete immediate + rebuild-per-call paths | A | M | P6-02 |
@@ -32,6 +35,7 @@ _Authoritative spec. See also the [phase index](../phase-index.md), [perf-dod](.
32
35
-**P6-05:** FFI batch sets only `u_viewport` (`draw/batch.rs:355`); Throne does world→screen math + visibility in C# per sprite (`GoudRenderPort.cs:275`). Camera uniform + grid/quadtree culling before submission; #554's configurable origin lands in the camera.
33
36
34
37
### Batch 6.3 — 2D scale features (3 groups, parallel)
38
+
35
39
| ID | Title | Grp | Effort | Blocked by |
36
40
|---|---|---|---|---|
37
41
| ENG2-P6-06 | Chunked tilemap renderer from TileLayer gids | A | M | P6-02 |
Copy file name to clipboardExpand all lines: docs/src/runbook/phases/phase-7.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,7 @@ _Authoritative spec. See also the [phase index](../phase-index.md), [perf-dod](.
9
9
**Goal:** The built-but-unwired services become real: async assets, mipmaps, one audio stack, one 2D physics, retained UI, multi-window. 6 issues.
10
10
11
11
### Batch 7.1 — Assets & media (3 groups, parallel)
12
+
12
13
| ID | Title | Grp | Effort | Blocked by |
13
14
|---|---|---|---|---|
14
15
| ENG2-P7-01 | Wire async asset pipeline into the runtime; background decode incl. RGB→RGBA off main thread | A | M | Phase 4 gate |
@@ -20,6 +21,7 @@ _Authoritative spec. See also the [phase index](../phase-index.md), [perf-dod](.
20
21
-**P7-03:**`assets/audio_manager/` (2832 LOC) vs `libs/providers/impls/rodio_audio.rs:118-138` — two full rodio wrappers, two unsafe Send/Sync blocks; keep the provider-trait one, bridge asset data through it.
| ENG2-P8-05 | Engine event bus with FFI subscription (#547) | A | M | — |
@@ -33,6 +35,7 @@ _Authoritative spec. See also the [phase index](../phase-index.md), [perf-dod](.
33
35
-**P8-08:** Deliberately late: Phases 2/3/6 already deleted much cgmath-using code; migrate the remainder onto `core/math`, drop the advisory-ignored dep.
34
36
35
37
### Batch 8.3 — Big-world (2 groups)
38
+
36
39
| ID | Title | Grp | Effort | Blocked by |
37
40
|---|---|---|---|---|
38
41
| ENG2-P8-09 | Navmesh generation/query for 3D navigation | A | L | P8-01 |
| ENG2-P9-01 | Scene inspector MVP (egui or web client over the MCP relay): entity tree + live component editing | A | L | P7-06 |
@@ -25,6 +26,7 @@ _Authoritative spec. See also the [phase index](../phase-index.md), [perf-dod](.
25
26
-**P9-05:**`providers/impls/webrtc_network/mod.rs:1-17` is custom UDP+STUN labeled WebRTC, `net-webrtc=[]` pulls no crate; ~7-8k LOC networking has zero production consumers — decision record on tiering (default-feature status) + honest naming.
0 commit comments