From 0066b7669e065709bab791ec6b81ae68e0b7ead7 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Fri, 25 Sep 2026 21:10:27 +0200 Subject: [PATCH] [M2-DEC-02] Confirm minimal UI widget set (D-UI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR 0006 (docs/decisions/0006-ui-scope.md): confirms the M2 retained UI widget set as the seven FR-2.8 widgets — panel, button, text, image, list, slider, input — in that canonical order, with the PRD §18 item 5 question text's omission of slider flagged as a typo (the normative FR-2.8 list and the register's D-UI default agree). Deferred widgets named: checkbox/toggle, progress bar, tab, dropdown, tooltip, multi-line input, separator — additive growth only, never breaking the M2-UI-01 surface. Closes PRD §18 item 5 / roadmap D-UI, unblocking M2-UI-01. Docs only, no code: - ADR 0006 written (context, decision, alternatives, evidence, consequences, review conditions per DOC-005) - ADR indexed in docs/decisions/README.md and docs/README.md - M2-DEC-02 box checked in roadmap/M2-rendering-2.5d.md - Decision register D-UI row, Progress Board (M2 2/33, total 49/194), and change log updated in roadmap/README.md --- docs/README.md | 3 +- docs/decisions/0006-ui-scope.md | 156 ++++++++++++++++++++++++++++++++ docs/decisions/README.md | 1 + roadmap/M2-rendering-2.5d.md | 2 +- roadmap/README.md | 7 +- 5 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 docs/decisions/0006-ui-scope.md diff --git a/docs/README.md b/docs/README.md index 698633a..858b421 100644 --- a/docs/README.md +++ b/docs/README.md @@ -212,7 +212,8 @@ still to land. - [ADR index](decisions/README.md) — 0001 (name and license), 0002 (deterministic math), 0003 (config JSON), 0004 (GoogleTest - vendoring), 0005 (iso default: 2:1 dimetric). + vendoring), 0005 (iso default: 2:1 dimetric), 0006 (UI widget scope: + the seven FR-2.8 widgets). ## Compatibility diff --git a/docs/decisions/0006-ui-scope.md b/docs/decisions/0006-ui-scope.md new file mode 100644 index 0000000..aebb957 --- /dev/null +++ b/docs/decisions/0006-ui-scope.md @@ -0,0 +1,156 @@ +# ADR 0006 — M2 retained UI widget set: the seven FR-2.8 widgets + +- **Status:** Accepted +- **Date:** 2026-09-25 +- **Decider:** Roadmap step M2-DEC-02 (D-UI) +- **Refs:** PRD §18 item 5, FR-2.8, FR-4.4, §11 (UI framework is ours), §15 (M2 + scope "basic UI"); roadmap D-UI, M2-TEXT-02, M2-UI-01, M2-UI-02, + M2-SCENE-01, M2-PERF-01, M2-EXIT-01; M3-INPUT-04; M5-ED-01 + +## Context + +PRD §18 item 5 (roadmap decision D-UI) leaves one question open for M2: the +retained-mode UI widget set — "confirm minimal list for M2 +(panel/button/text/image/list/input) vs. defer full UI to M4." Note that the +question text omits `slider`, while the normative P0 requirement FR-2.8 names +the widget list as "(panel, button, text, image, list, slider, input)" and the +decision register's D-UI default-if-unresolved is exactly that seven-widget +FR-2.8 list. The M2-DEC-02 scope restates the same seven widgets as the +default. This ADR confirms that default rather than re-deciding from scratch. + +M2 needs *some* UI rather than deferring all of it to M4: + +- M2's own acceptance chain includes UI: the M2-SCENE-01 reference scene + carries "a UI panel", M2-UI-02 renders the UI pass and the first golden UI + scene, and M2-TEXT-02's `TextItem` exists "in the UI pass" — deferring UI to + M4 would orphan those steps. +- PRD §15 M2 scope names "basic UI" explicitly. +- M5-ED-01's editor shell ("menus, docks — built from M2 widgets") composes + this set; deferring to M4 would not remove the editor's UI work. +- The UI framework is ours (PRD §11: explicitly *not* a dependency), so the + set size is a scope choice, not a dependency constraint (DEP-001). + +The seven widgets map one-to-one onto M2's downstream consumers; the event +column matches M2-UI-01's per-frame event list ("pointer enter/leave/ +press/release, button pressed, slider changed, input text"): + +| Widget | Retained role (M2-UI-01) | Own events (on top of the generic pointer enter/leave/press/release) | +|---|---|---| +| `panel` | Structural container holding child widgets; opaque-background option — the occlusion-culling target of M2-UI-02 | — | +| `button` | Press action; the primary interactive primitive (menus, options) | button pressed | +| `text` | Static text/label; content from the M2-TEXT-02 `TextItem` (string table — no per-frame `std::string`), with its alignment and max-width wrap | — | +| `image` | Fixed UI-atlas sub-rect; static art and decoration | — | +| `list` | Bounded scrollable container of child widgets (e.g. menu rows); children receive the generic pointer events | — (children's events) | +| `slider` | Continuous value within a documented `[min, max]` | slider changed | +| `input` | Single-line text entry; the widget that consumes keyboard events when focused (M3-INPUT-04's focus test) | input text | + +Focus follows M2-UI-01's focus system (focusable widgets, tab/pointer focus, +the UI-first routing hook); the minimum keyboard consumer is `input` — the +widget M3-INPUT-04's routing test names ("with focused input widget, typing +goes to the widget and not to a bound world action"). + +## Decision + +- **The M2 retained widget set is exactly seven widgets, in this canonical + order: `panel`, `button`, `text`, `image`, `list`, `slider`, `input`.** + M2-UI-01 implements exactly this list — the M2-DEC-02 Verify clause is + "widget list in ADR == widget list implemented in M2-UI-01" — and its + widget-kind enum follows this order. +- **The PRD §18 item 5 question text's omission of `slider` is treated as a + typo in the question.** The normative list is FR-2.8's, and the register's + D-UI default-if-unresolved is the same seven-widget list; confirming seven + widgets matches both. +- **The widget candidates below are deferred out of M2 (named per the + M2-DEC-02 scope).** They land later as additive widget kinds — the set only + grows; none of the deferrals is a breaking change to the M2-UI-01 surface. + +Deferred widgets: + +| Deferred widget | Why out of M2 | Expected landing | +|---|---|---| +| checkbox (toggle) | No M2 acceptance fixture needs a boolean control; `button` + `slider` cover M2 settings UI | later step (M4/M5) as an additive widget kind, on demand | +| progress bar | No M2 game/UI content requires a status bar; the §15 profiler counters are not widgets and do not render in M2 | later step with the first consumer (e.g. editor load progress) | +| tab (tab bar) | Multi-pane switching is an editor feature; the M5 shell can compose from `button` + `panel` if needed | M5 editor, additively | +| dropdown (combo) | A collapsed list adds no M2 capability; a menu is `panel` + `list` | later step, on demand | +| tooltip | Hover hint; convenience, required by no M2 step | later step, on demand | +| multi-line input (text area) | M2 `input` is single-line; the first P0 consumer is chat (FR-10.7, P1, M6) | M6 with chat, or earlier if the editor needs it | +| separator (divider) | Decorative line; covered by the `image` widget where needed | none — covered by existing widgets | + +- The UI stays presentation-only (ARCH-009): widgets never mutate sim state — + they emit the M2-UI-01 per-frame event list, and routing to the world is + M3-INPUT-04 (FR-4.4). +- No new dependencies: the UI framework is ours (PRD §11); widgets render as + batched quads + text through the M2-SPRITE-02 path in the screen-space UI + pass (M2-UI-02), one texture per UI atlas. + +## Alternatives considered + +- **The PRD §18 question's six-widget list (no slider)** — rejected: it + contradicts the normative FR-2.8 list and the register's own D-UI default, + and dropping `slider` removes a single continuous-value control with no M2 + cost benefit (its event and render surface are one row of the M2-UI-01 event + list and one batched-quad pattern shared with the other widgets). +- **Deferring the whole UI to M4** (the §18 alternative) — rejected: M2's exit + chain requires UI (M2-TEXT-02's `TextItem` "in the UI pass", the M2-SCENE-01 + reference scene's UI panel, M2-UI-02's golden scene), PRD §15 M2 scope names + "basic UI", and M5-ED-01 composes the editor shell from the M2 widgets — + deferring to M4 would orphan M2 steps without removing the editor work. +- **A larger M2 set (checkbox/progress/tab in P0)** — rejected (CORE-004, + smallest complete change): no M2 acceptance criterion (AC-4.x, the 50k-sprite + budget, the golden scene) exercises them, and shipping unused widget kinds + would force M2-UI-01's CRUD/layout/focus tests to cover unused paths. The + set is extensible additively — the deferred list above names the expected + growth. + +## Evidence + +- This is a scope-confirmation decision, not a performance claim, so no + CORE-001 measurement applies. The cost is bounded by construction: each + widget is a batched quad (or text run) in the M2-SPRITE-02 UI pass — one + texture per UI atlas (M2-UI-02) — so the UI pass adds a small, bounded number + of (atlas, blend) groups to the PRD §8.1 ≤ 30 draw-call budget; M2-PERF-01 + measures the 50k-sprite reference scene including its UI panel, and the + M2-UI-02 golden scene pins the render. +- Normative references: FR-2.8 (the P0 widget list), PRD §18 item 5, the + roadmap D-UI register row; the M2-UI-01/M2-UI-02/M2-TEXT-02/M2-SCENE-01/ + M2-EXIT-01 scope wording; M3-INPUT-04 (the focus test names the `input` + widget); M5-ED-01 (the editor shell is built from M2 widgets); PRD §11 (UI + framework ours — no vendoring decision to make, DEP-001). +- Enforcement after this ADR: M2-UI-01 implements exactly the seven widgets and + re-checks the list equality with this ADR in its Verify clause; M2-EXIT-01 + records the D-UI status alongside the other M2 scope statuses. + +## Consequences + +- M2-UI-01's widget-kind set is fixed at {panel, button, text, image, list, + slider, input} in this order; its tests (tree CRUD, layout goldens, focus + traversal, event delivery) cover exactly these kinds. +- M2-UI-02 renders all seven kinds in the screen-space UI pass; its golden + fixture uses a subset of them. +- The M2-SCENE-01 reference scene's UI (a panel plus a few widgets) fits the + set; its draw-call contribution is counted in M2-PERF-01's ≤ 30 budget. +- M3-INPUT-04's routing tests run against `input` (keyboard) and the + pointer-focused widgets; no routing change is needed when deferred kinds + land. +- M5-ED-01 composes the editor shell: menu = `panel` + `list` + `button`, dock + = `panel` (+ `list`); deferred kinds (tab, dropdown, multi-line input, + checkbox) are added as new widget kinds in M5 or earlier if a P0 consumer + appears — additively, never breaking. +- No change to simulation, determinism, or replication (ARCH-009); no new + dependency; `laige-api.json` unchanged (docs only). + +## Review conditions + +- Reopen (new ADR, additively) if a P0 consumer — a sample game, the + M2-SCENE-01 scene, or the M5 editor — needs one of the named deferred + widgets before M2-UI-02: add it as a new widget kind behind the same + retained tree and extend M2-UI-01's tests; the seven-widget set stays + implemented and tested. +- Reopen as a breaking change only if the set must *shrink*, which would break + the M2-UI-01 Verify equality and M5's composition plan; no mechanism for + this is known. +- If the PRD is revised, the §18 item 5 question text should be corrected to + include `slider` (the typo this ADR resolves); the normative FR-2.8 list + itself is unchanged. +- Any change to the set must be recorded by M2-EXIT-01 and re-verified by + M2-UI-01's list-equality check against this ADR or its successor. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 3f338ab..6abd547 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -12,3 +12,4 @@ alternatives, evidence, consequences, and review conditions; the full | [0003](0003-config-json.md) | Config JSON strategy | Accepted | 2026-09-10 | | [0004](0004-google-test-vendoring.md) | Vendoring GoogleTest as the dev-only test dependency | Accepted | 2026-09-10 | | [0005](0005-iso-default.md) | Default isometric projection for new projects: 2:1 dimetric (D-ISO) | Accepted | 2026-09-25 | +| [0006](0006-ui-scope.md) | M2 retained UI widget set: the seven FR-2.8 widgets (D-UI) | Accepted | 2026-09-25 | diff --git a/roadmap/M2-rendering-2.5d.md b/roadmap/M2-rendering-2.5d.md index 23f6184..c8a7cd8 100644 --- a/roadmap/M2-rendering-2.5d.md +++ b/roadmap/M2-rendering-2.5d.md @@ -28,7 +28,7 @@ if M2 slips, and its status is recorded in M2-EXIT-01. - **Verify:** ADR exists. - **Size:** docs only -- [ ] **M2-DEC-02 · Confirm minimal UI widget set (D-UI)** +- [x] **M2-DEC-02 · Confirm minimal UI widget set (D-UI)** - **Refs:** PRD §18.5, FR-2.8 - **Depends:** — - **Scope:** diff --git a/roadmap/README.md b/roadmap/README.md index c75ab34..2d624ec 100644 --- a/roadmap/README.md +++ b/roadmap/README.md @@ -141,7 +141,7 @@ Steps tagged `DEC` close these. ADRs live in `docs/decisions/`. | D-MATH | Fixed-point default for all deterministic paths, or float-pinned + FP only for lockstep | M1-DET-01, M3-PHYS-11 | Q16.16 default for deterministic mode (PRD §10.3 recommends it for lockstep/MMO) | **Decided 2026-09-10:** SimMath — config-selectable; `fpx16_16` default + `fp32_pinned` opt-in (ADR 0002) | | D-JSON | Config JSON: tiny in-engine parser vs vendored library | M0-CORE-07 | in-engine bounded parser (no new dep) | **Decided 2026-09-10:** in-engine bounded parser, no new dep (ADR 0003) | | D-ISO | 2:1 dimetric vs true iso as template default | M2-CAM-02 | 2:1 dimetric (PRD v0.2: pixel-art default) | **Decided 2026-09-25:** 2:1 dimetric as template default (ADR 0005); both presets selectable per scene | -| D-UI | Confirm minimal retained UI widget set for M2 | M2-UI-01 | the FR-2.8 P0 list (panel/button/text/image/list/slider/input) | — open | +| D-UI | Confirm minimal retained UI widget set for M2 | M2-UI-01 | the FR-2.8 P0 list (panel/button/text/image/list/slider/input) | **Decided 2026-09-25:** the seven FR-2.8 widgets confirmed as-is (ADR 0006); extras deferred and named in the ADR | | D-EDITOR | Editor embedded vs standalone | M5-ED-01 | standalone binary (FR-8.7) | — open | | D-LUA | Confirm Lua 5.4 (vs no scripting in 1.0, vs WASM) | M4-SCRIPT-01 | Lua 5.4, optional module, off by default | — open | | D-NAT | M6 NAT traversal scope: STUN-only vs STUN+TURN | M6-NET-11 | STUN-style + relay endpoint, no TURN server in-engine | — open | @@ -157,7 +157,7 @@ Updated in the same PR that closes steps. "Done" = box checked + Verify green. |---|---|---|---| | M0 | 22 | 22 | ✅ complete (2026-09-13, M0-EXIT-01) | | M1 | 25 | 25 | ✅ complete (M1-EXIT-01, 2026-09-25) | -| M2 | 33 | 1 | ⬜ in progress | +| M2 | 33 | 2 | ⬜ in progress | | M3 | 36 | 0 | ⬜ not started | | M4 | 12 | 0 | ⬜ not started | | M5 | 21 | 0 | ⬜ not started | @@ -165,7 +165,7 @@ Updated in the same PR that closes steps. "Done" = box checked + Verify green. | M7 | 15 | 0 | ⬜ not started | | M8 | 8 | 0 | ⬜ not started | | M9 | 6 | 0 | ⬜ proposals only | -| **Total** | **194** | **48** | | +| **Total** | **194** | **49** | | --- @@ -220,6 +220,7 @@ One line per completed (or split/renumbered) step. | 2026-09-25 | M1-BENCH-01 | `—` | 10k-entity simulation-tick benchmark (PRD §8.1 ≤3.0 ms avg / ≤5 ms p99, ADR 0002, CORE-001; M1-BENCH-01 scope, nothing else): the `sim-tick` suite in `laige-bench` (`tools/bench/laige-bench.cpp` + CMake) — the PRD §8.1 "Simulation tick" workload: 10 000 entities at the 100% scene budget (capacity 10000, deterministic, seed `0x1F055EED` — the repo-wide test-seed convention), 2 000 dynamic bodies carrying the built-in `Position2D` plus the workload component `BenchVel` (a `{Vec2 v}`, `LAIGE_COMPONENT` + `LAIGE_DETERMINISM_SAFE` marks for both backends) and 8 000 bare entities; deterministic index-derived initial state (50×50 grid span — one body per cell of the first 2 000 cells, `x = (i/50)%50 − 25`, `y = i%50`; `vx ∈ -3..3`, `vy ∈ -5..5` units/tick — max coordinate magnitude 20 049 units over the 4 000-tick run, inside fpx16_16's ±32 768 Q16.16 range: no saturating-overflow edge in the measured window); two systems in registration order — `BenchMove` (1 ms declared budget; `pos += vel` through the active backend's `SimMath::add`, the ADR 0002 pinned op surface) and `BenchHash` (2 ms budget; the per-tick `World::stateHash(tick−1)` — the M1 determinism work made part of the measured tick) — driven at 60 Hz by the `GameLoop` on an EXACT synthetic clock (one due tick/frame: 16 666 667 ns — the M1-LOOP-01 exact integer due computation); one measured sample = one completed tick (beginFrame + the systems + the loop's bookkeeping; the presentation snapshot is excluded — ARCH-009); canonical shape `--runs=3000 --warmup=1000` (n=3000, histogram capacity 3000, no truncation); every measured tick in debug non-sanitizer builds passes the engine's OWN G-R1 per-tick zero-allocation assertion (M1-ALLOC-01 — an allocating tick aborts the run, so a PASS is a zero-alloc-asserted tick); tool changes (smallest complete change): `--math=` (the ADR 0002 backend selection; default fpx16; other suites ignore it), **repeatable** `--budget=` (one run gates EVERY named budget against the same histogram — each entry's `metric` picks its statistic; duplicate name = usage error; any FAIL exits 2), suite setup/teardown hooks (the stateless synthetic suite keeps nullptrs), and a compiler-id fix (Clang checked before GCC: Clang 22 defines `__GNUC__` and its `__VERSION__` is "Clang 22.1.8" — the old order mislabeled clang builds as "GCC Clang 22.1.8"; the id is now built from `__clang_major__/minor/patchlevel`, stable across Clang versions); CMake: `laige-bench` links `laige-sim` and gains `laige_apply_simmath_policy` (the TU carries deterministic sim math — ADR 0002 pinned flags), plus two CI gate entries `laige_bench_sim_tick_{fpx16,fp32}` (the canonical 3000/1000 shape, `--budget=sim_tick_avg --budget=sim_tick_p99`, `LAIGE_BUDGETS_PATH` = the repo root; EXCLUDED from the ASan/TSan trees — instrumentation inflates the tick's absolute cost: ASan+UBSan measures ≈1.64 ms/tick on this workload, 2.7× the canonical 0.60 ms, so a sanitizer measurement would measure the instrumentation, not the sim — the m1-profiler-cost precedent; those trees verify the workload's safety properties instead); the PRD §8.1 budget-regression gate is these entries inside every P0 job's full `ctest` (the CI perf lane, PRD §14 — no separate workflow step needed); **first recorded `measured` values in `budgets.json`** (the M0 convention's `0 = not yet measured` retired for the two entries — recorded as the WORSE of the two backends on the canonical Debug tree, since the budget covers both backends and the 10% regression band (methodology §5) stays conservative): `sim_tick_avg` (mean) **0.598405** ms, `sim_tick_p99` (p99) **0.615179** ms; **fourth baseline** `docs/benchmarks/baselines/m1-sim-tick.md` (full AGENTS §12 metadata, verbatim budget-checked runs + the ctest gate, the cross-tree/cross-compiler table, the interpretation — records that M1 measures the ECS-only slice of the §8.1 workload: kinematic movement + the state hash; the PRD's remaining tick content (physics, presentation) lands in M3/M4 and supersedes the baseline per methodology §4; the file also records the initial-state grid-shape fix folded into this PR — the first commit stacked the 2 000 bodies in one grid column instead of one-per-cell over the 50×50 span; re-measured on every tree (numbers moved <2%, both backends still PASS; the recorded numbers are the post-fix ones); docs updated in the same change (DOC-007): `docs/benchmarks/README.md` + `docs/benchmarks/baselines/README.md` (the stale "every entry still `measured: 0`" lines; the profiler-cost baseline entry that was never added), `docs/api/budget_harness.md` (the new CLI form, the sim-tick suite, the measured-field note, both suites' determinism); no new public API header — `laige-api.json` unchanged (api-real-tree green in the full-suite runs); local Verify (2026-09-25, AMD Ryzen 9 7950X3D / CachyOS): canonical Debug g++ 16.2.1 — fpx16_16 mean=0.598405 p99=0.606001, fp32_pinned mean=0.591438 p99=0.615179 — BOTH budgets PASS on BOTH backends (5.0× / 8.1× inside the 3.0/5.0 ms targets); Release g++ 0.0801446/0.08439 (≈7.5× the Debug cost — the instrumented, unoptimized tree the gate measures); Clang 22.1.8 Debug 0.913303/0.935979 (1.5× GCC Debug — codegen difference, both far inside budget); shared-lib Debug 0.623729/0.639315; **the run's `final_hash` fingerprint is bit-identical across all four non-instrumented trees and both compilers, at both backends** (fpx16_16 `0x7840644a24334cd0`, fp32_pinned `0x7a60d70232e448c7` — the ADR 0002 cross-build bit-exactness demonstrated on the 4 000-tick workload); ASan (clang) run leak-free (exit 0, 600-tick safety shape; ≈1.64 ms/tick — 2.7× the canonical, the instrumentation the gate excludes); TSan `halt_on_error=1` race-free (exit 0) with one legitimate `system/budget_overrun` warn (BenchHash 2.04 ms vs its 2 ms budget under TSan's ≈4× slowdown — the engine's correct G-R5 observation, not a workload defect); full `ctest` green on all six local trees: 94/94 `build`/`build-clang`/`build-shared` (the 92 pre-existing + the two new gate entries), 93/93 `build-release` (one pre-existing Debug-only entry), 92/92 `build-asan`/`build-tsan` (the gate entries correctly absent there); zero new warnings under NFR-8.10; `tools/laige-include-lint` OK (47 source files, 1/10 deps), `tools/laige-determinism-lint` OK (28 sim files, 0 violations); CI: the PR's P0 jobs carry the gate entries in their full `ctest` (linux-gcc/clang default lane + the `ci:macos`/`ci:windows`-labeled windows-msvc/macos jobs + asan/tsan) — the M1-EXIT-01 gate records the final CI evidence (commit lands with the step's PR) | | 2026-09-25 | M1-EXIT-01 | `—` / PR #52 | M1 exit gate: all 25 M1 steps confirmed against their Scope/Verify clauses — (1) sim-tick budget green on both SimMath backends: baseline report `docs/benchmarks/baselines/m1-sim-tick.md` (canonical Debug g++ 16.2.1: fpx16_16 mean 0.598405 / p99 0.606001 ms, fp32_pinned mean 0.591438 / p99 0.615179 ms — both PASS, 5.0×/8.1× inside the 3.0/5.0 ms targets) + CI (the CI reference machine, ubuntu-24.04, is the gate): PR-lane run 36152843429 (linux-gcc + linux-clang full ctest 94/94, including the two budget-gate entries `laige_bench_sim_tick_fpx16`/`laige_bench_sim_tick_fp32` — both budgets, both backends, exit 0) and run 36155154836 (macOS arm64 + Intel full ctest, the same two gate entries green on AppleClang); (2) replay bit-exact on CI across all P0 OS jobs and both backends: the determinism matrix `docs/benchmarks/determinism-matrix.md` (every P0 OS job asserts per-tick identity of the hello baseline on both backends — `hello_baseline_fpx`/`hello_baseline_fp32` — and the always-on detcheck job compares the cross-build pairs on both backends) — all-OS evidence: merge-lane run 36037080146 on master (2026-09-24: all 7 P0 jobs + detcheck green) and this PR's label-gated P0 matrix — Linux run 36152843429, macOS arm64/Intel run 36155154836, windows-msvc run 36156322585 — every job green, including the two hello-baseline tests in each OS job's full ctest and the detcheck job; (3) zero-alloc assertion green on the 10k workload: the `zero_alloc` ctest entry (M1-ALLOC-01) green in every P0 job's full ctest in the runs above (test logs in each run's job logs; the ASan/TSan lanes additionally archive the sanitizer reports) — and the M1-BENCH-01 measurement itself is zero-alloc-asserted: every tick of both backends (4 000 each — warm-up and measured) passed the engine's G-R1 per-tick allocation assertion in the debug trees (an allocating tick aborts the run). Deferred P1 items: none — every M1 step is P0. Progress Board: M1 25/25 complete (total 47/193); the board's stale Total row (41) corrected to 47 in the gate commit | | 2026-09-25 | M2-DEC-01 | `948b942` | D-ISO decided: 2:1 dimetric confirmed as the template default (true 30°/60° iso remains selectable per scene — the decision is presentation-only, no sim/determinism impact); ADR 0005 written in `docs/decisions/` (context, decision, alternatives, evidence, consequences, review conditions per DOC-005); ADR indexed in `docs/decisions/README.md` and `docs/README.md`; M2-DEC-01 box checked, decision register D-ISO row closed, Progress Board M2 1/33 (total 48/194); docs only, no code (board/changelog row added in the follow-up commit of the same PR) | +| 2026-09-25 | M2-DEC-02 | `—` | D-UI decided: the M2 retained UI widget set confirmed as the seven FR-2.8 widgets in canonical order — `panel`, `button`, `text`, `image`, `list`, `slider`, `input` — with the PRD §18 item 5 question text's omission of `slider` flagged as a typo (the normative FR-2.8 list and the register's D-UI default agree); deferred widgets named in the ADR (checkbox/toggle, progress bar, tab, dropdown, tooltip, multi-line input, separator) — they land later as additive widget kinds, never breaking the seven-widget M2-UI-01 surface; ADR 0006 written in `docs/decisions/` (context, decision, alternatives, evidence, consequences, review conditions per DOC-005); ADR indexed in `docs/decisions/README.md` and `docs/README.md`; M2-DEC-02 box checked, decision register D-UI row closed, Progress Board M2 2/33 (total 49/194); docs only, no code — Verify clause "widget list in ADR == widget list implemented in M2-UI-01" binds M2-UI-01 when it lands | ---