diff --git a/README.md b/README.md index ab00d09..c2cef62 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,12 @@ isometric-first rendering, and a server-authoritative MMO path. - **License:** MIT (engine code). Samples and assets are separately licensed; each sample ships its own `LICENSE` ([ADR 0001](docs/decisions/0001-name-and-license.md)). -- **Status:** **M0 — Foundations**, in progress. The repository skeleton, - build system, `laige-core` library target, and dependency lock - (`deps.lock` with vendored GoogleTest) have landed; the functional core - (math, pools, Result, logging, config) lands over the remaining M0 steps - in [roadmap/M0-foundations.md](roadmap/M0-foundations.md). So far: +- **Status:** **M0 — Foundations complete** (exit gate M0-EXIT-01, + 2026-09-13). The repository skeleton, CI on all P0 OSes, build system, + `laige-core` (math, pools, alloc, Result, logging, config), dependency + lock (`deps.lock` with vendored GoogleTest), and the API manifest + generator are in place — see + [roadmap/M0-foundations.md](roadmap/M0-foundations.md). Landed so far: `laige::Result` / `laige::Status` plus the error-code registry (M0-CORE-01), the structured logging facade (M0-CORE-02), the SimMath deterministic-math interface with the default `fpx16_16` backend diff --git a/docs/benchmarks/baselines/README.md b/docs/benchmarks/baselines/README.md index 88db3d0..5bc88b0 100644 --- a/docs/benchmarks/baselines/README.md +++ b/docs/benchmarks/baselines/README.md @@ -16,5 +16,8 @@ and the exact command + commit that produced it. Baseline files are **immutable**: superseding a baseline adds a new file and updates `measured` in `budgets.json` — it never edits an existing baseline. -No baselines are recorded yet (every `budgets.json` entry has -`measured: 0`); the first lands with M0-EXIT-01. +Every `budgets.json` entry still has `measured: 0` (their subsystems land +in M1+); the first recorded baseline is +[m0-synthetic.md](m0-synthetic.md) (M0-EXIT-01, 2026-09-13) — the +synthetic harness workload, which proves the measurement pipeline end to +end but does not measure any real budget. diff --git a/docs/benchmarks/baselines/m0-synthetic.md b/docs/benchmarks/baselines/m0-synthetic.md new file mode 100644 index 0000000..ad31f27 --- /dev/null +++ b/docs/benchmarks/baselines/m0-synthetic.md @@ -0,0 +1,98 @@ +# Baseline: `m0-synthetic` — M0 exit gate, synthetic harness workload + +Recorded by **M0-EXIT-01** (2026-09-13). This is the **first** baseline +file; it is immutable (methodology §4 — superseding it later adds a new +file, it is never edited). + +## What this baseline measures + +The `synthetic` suite of `laige-bench` (M0-CORE-08): a deterministic, +allocation-free 4096-step Marsaglia LCG64 + double pipeline with named +constants and no RNG. **It models no physical quantity** — it is the +harness stand-in that proves the measurement pipeline (timer → histogram +→ statistics → budget check → report) end to end before the real PRD +§8.1 workloads land with their subsystems in M1+. Its numbers are +therefore **not** measurements of any real engine budget, and this +baseline **does not update any `measured` field in `budgets.json`** +(all 15 entries keep `measured: 0` = not yet measured until their +subsystem lands). + +## AGENTS §12 metadata + +| # | Field | Value | +|---|---|---| +| 1 | Hardware | AMD Ryzen 9 7950X3D (16 cores / 32 threads), 64 GB RAM | +| 2 | OS | CachyOS (Arch-based Linux), kernel `7.2.2-1-cachyos`, x86_64 | +| 3 | Compiler and version | `g++ (GCC) 16.2.1 20260810` | +| 4 | Build type | `Debug` (canonical, `build/` tree) | +| 5 | Relevant flags | Engine policy (NFR-8.10): `-Wall -Werror -fno-exceptions -fno-rtti`; SimMath pinned set (ADR 0002): `-ffp-contract=off -fno-associative-math`. No sanitizers (canonical tree). | +| 6 | Dataset / workload | `synthetic` — deterministic 4096-step LCG64+double pipeline (Marsaglia constants), allocation-free, no RNG; harness stand-in, no physical meaning | +| 7 | Warm-up | 200 discarded iterations (`--warmup=200`) | +| 8 | Sample count | `n=2000` (histogram window `capacity >= runs`; no truncation) | +| 9 | Summary statistics | `min=0.009708 mean=0.00995194 p50=0.009768 p95=0.010019 p99=0.014748 max=0.023965` (ms) | +| 10 | Before / after | `before=0` (not yet measured — first recording) · `after=0.00995588` ms (the `--budget` proof run's `mean`) · `target=3` ms (`sim_tick_avg`) | + +## Verbatim run output + +### Run A — canonical statistics run + +Command (run from the repository root; `LAIGE_BENCH_MACHINE` set to the +machine string so the `context:` line carries it): + +```console +$ ./build/bin/laige-bench --suite=synthetic --runs=2000 --warmup=200 +``` + +```text +suite=synthetic runs=2000 warmup=200 + stats: n=2000 min=0.009708 mean=0.00995194 p50=0.009768 p95=0.010019 p99=0.014748 max=0.023965 + context: workload=synthetic build=GCC 16.2.1 20260810, Debug machine=AMD Ryzen 9 7950X3D 16C/32T, 64 GB RAM, CachyOS (Arch-based) x86_64 warmup=200 +``` + +Exit code: `0`. + +### Run B — end-to-end `budgetCheck` pipeline proof + +Command: + +```console +$ ./build/bin/laige-bench --suite=synthetic --runs=2000 --warmup=200 --budget=sim_tick_avg +``` + +```text +suite=synthetic runs=2000 warmup=200 +budget=sim_tick_avg result=PASS metric=mean unit=ms + after=0.00995588 before=0 target=3 + stats: n=2000 min=0.009658 mean=0.00995588 p50=0.009909 p95=0.009969 p99=0.014588 max=0.023154 + context: workload=10k entities, 2k dynamic bodies (PRD 8.1) build=GCC 16.2.1 20260810, Debug machine=AMD Ryzen 9 7950X3D 16C/32T, 64 GB RAM, CachyOS (Arch-based) x86_64 warmup=200 +``` + +Exit code: `0`. + +**Interpretation note (CORE-008, no silent claims):** Run B checks the +synthetic stand-in against the `sim_tick_avg` `budgets.json` entry +purely to exercise `loadBudgets` + `budgetCheck` end to end (the gate's +"budget harness runs end-to-end" clause). The `context: workload=` line +echoes the budget entry's workload text — it is **not** a claim that a +10k-entity simulation tick was measured. The check passing +(`after < target`, `before == 0` so no regression band applies per +methodology §5) proves the pass/fail/exit-code machinery, not a real +budget. + +## Measured on + +- Commit: `829026f60c0f7c779c82882e608ed04e15cadc5a` (`[M0-DOC-01] docs/ skeleton + index (#14)`, `master`) +- Date: 2026-09-13 (09:45 UTC session time) +- Local trees for cross-check (same commit, all `ctest` 32/32, zero + warnings under the NFR-8.10 policy): `build` (g++ 16.2.1 static), + `build-shared` (static→shared, NFR-8.9), `build-asan` (ASan+UBSan + fatal), `build-tsan` (TSan `halt_on_error=1`), `build-clang` + (clang++ 22.1.8). + +## Related gate evidence + +- CI green on all 3 P0 OSes (merge lane): [run 34749756361](https://github.com/offdev/laige-cpp/actions/runs/34749756361) on this commit — all 10 jobs `success`, each under 1.2 min; `ctest` **32/32 passed** in every P0 OS job (linux-gcc, linux-clang, linux-asan+UBSan, linux-tsan, windows-msvc, macos-arm64, macos-intel). +- Fuzz lane (bounded, in every P0 job's ctest): `fuzz_json_parse` — `laige-fuzz json_parse --runs=1000` clean locally (seed `0x1F055EED`). +- Determinism checker self-check: `laige-detcheck --scenario=synthetic` → `result=OK ticks=256`. +- API manifest drift: `laige-api-scanner --root . --check laige-api.json` → up to date (376 symbols). +- Include-graph lint: `python3 tools/laige-include-lint` → OK, `count: 1 (budget: 10, PRD §11)`. diff --git a/roadmap/M0-foundations.md b/roadmap/M0-foundations.md index 61c8512..b524490 100644 --- a/roadmap/M0-foundations.md +++ b/roadmap/M0-foundations.md @@ -980,11 +980,44 @@ No rendering, no physics, no networking yet — `laige-core` only. ## Milestone gate -- [ ] **M0-EXIT-01 · M0 exit gate** +- [x] **M0-EXIT-01 · M0 exit gate** - **Refs:** PRD §15 M0 exit criteria - **Depends:** all other M0 steps - **Scope:** - Confirm and record: (1) CI green on all 3 P0 OSes (link CI run URLs), (2) all `laige-core` unit tests pass on all 3 OSes, (3) budget harness runs end-to-end on a synthetic workload and writes a baseline file to `docs/benchmarks/baselines/m0-synthetic.md` with full AGENTS §12 metadata. - Mark milestone complete in `roadmap/README.md` Progress Board. - - **Verify:** gate checklist item all checked with evidence links; no open M0 step remains. - - **Size:** docs only + - **Decision (2026-09-13):** all 21 prior M0 steps re-verified against + their Scope/Verify clauses on commit `829026f`: fresh canonical + `build` tree (g++ 16.2.1) configures and builds with zero warnings, + `ctest` 32/32; `build-shared`, `build-asan` (ASan+UBSan fatal), + `build-tsan` (TSan `halt_on_error=1`), and a fresh Clang 22.1.8 tree + all 32/32 as well; `laige-fuzz json_parse --runs=1000` clean; + `laige-detcheck --scenario=synthetic` OK; `laige-api-scanner + --check laige-api.json` up to date (376 symbols); `python3 + tools/laige-include-lint` OK (`count: 1 (budget: 10, PRD §11)`); the + vendored-tree lock re-proven live — a tampered `deps/googletest` + file fails the configure with expected-vs-actual hashes, the + restored tree passes. Gate items: (1) **CI green on all 3 P0 OSes** — + merge-lane run + [34749756361](https://github.com/offdev/laige-cpp/actions/runs/34749756361) + on `829026f`: all 10 jobs `success` (linux-gcc, linux-clang, + linux-asan, linux-tsan, windows-msvc, macos-arm64, macos-intel, + include-lint, api-manifest, detcheck), each under 1.2 min (inside + the 10-min PRD §8.1 build budget); (2) **all `laige-core` unit tests + pass on all 3 OSes** — that run's job logs show `100% tests passed, + 0 tests failed out of 32` in every P0 OS job, including + windows-msvc; (3) **budget harness end-to-end** — `./build/bin/ + laige-bench --suite=synthetic --runs=2000 --warmup=200` (statistics + run) plus the same run with `--budget=sim_tick_avg` (PASS, exit 0 — + pipeline proof only: the synthetic stand-in measures no physical + quantity, so no `budgets.json` `measured` field is updated), recorded + in the first baseline + [`docs/benchmarks/baselines/m0-synthetic.md`](../docs/benchmarks/baselines/m0-synthetic.md) + with the full AGENTS §12 metadata. + - **Verify:** (verified 2026-09-13; evidence as recorded in the + Decision above): gate checklist complete with evidence links — CI + run URL, per-OS ctest summaries, baseline file; every other M0 step + box is checked and no open M0 step remains. Milestone marked + complete in the `roadmap/README.md` Progress Board (22/22). + - **Size:** docs only (baseline file + roadmap/README records + root + README status line) diff --git a/roadmap/README.md b/roadmap/README.md index 3ab99e0..ab7cdb2 100644 --- a/roadmap/README.md +++ b/roadmap/README.md @@ -155,7 +155,7 @@ Updated in the same PR that closes steps. "Done" = box checked + Verify green. | Milestone | Steps | Done | Status | |---|---|---|---| -| M0 | 22 | 21 | ▶ in progress | +| M0 | 22 | 22 | ✅ complete (2026-09-13, M0-EXIT-01) | | M1 | 25 | 0 | ⬜ not started | | M2 | 32 | 0 | ⬜ not started | | M3 | 36 | 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** | **193** | **21** | | +| **Total** | **193** | **22** | | --- @@ -194,6 +194,7 @@ One line per completed (or split/renumbered) step. | 2026-09-12 | M0-TOOL-02 | `fe4460c` | Determinism checker skeleton (FR-11.5, AGENTS ARCH-010, TEST-004): `laige-detcheck` (`tools/detcheck`) runs a named scenario in two build configurations and compares per-tick hash streams; scenario contract (normative in the tool header, mirrored in `docs/api/detcheck.md`): one stdout line per tick ` ` — 16 lowercase hex hash digits (algorithm NOT part of the contract — lines compare byte-for-byte), tick starts at 0 step 1 no padding, trailing newline optional / trailing `\r` tolerated, stderr ignored, exit 0 — enforced strictly and bounded (65536 ticks, 64 bytes/line; a violation is a loud exit 2, CORE-008); modes: `--scenario=synthetic\|synthetic-perturbed` (built-in 32-body `fpx16_16`+Prng workload, two in-process runs — pure integer arithmetic, bit-exact per ADR 0002; perturbation fixture: +1 unit to body 3's x at tick 7) and `--run-a= --run-b= [-- scenario-args...]` (the M1-DET-04 mode; `--` separator keeps scenario args unambiguous); stable report `detcheck scenario= result=OK\|DIVERGED [first_diff_tick=]` + run-a/run-b lines (LOG-001); exit 0 match / 1 divergence / 2 error; POSIX fork/exec + pipe capture, Windows CreateProcessW + PeekNamedPipe (no shell, bounded memory, scenario stderr stays on the CI log); 9 CTest entries (`tests/detcheck`, `ctest -R detcheck`): self-check, built-in perturbation, identical/diverged/malformed/failure/short-stream cross-binary pairs — one fixture source, five compiled variants — each a generated `cmake -P` script asserting exit code + output fragments (tests/api pattern); CI `detcheck` job in ci-pull.yml/ci.yml (every PR and merge, no ci:* condition — tooling check, not a P0 OS build): runs the synthetic self-check and SKIPS the real-scenario step (two build configurations of M1-SAMPLE-01's hello) until it lands — M1-DET-04 activates it; local Verify: ctest 31/31, zero warnings on g++ static/shared, clang++, ASan+UBSan, TSan trees; Windows path compile-verified by the CI MSVC job — but NOT runtime-verified (stale as of 2026-09-13: the mode-2 capture was broken on the Windows CI runner and the mode-2 fragment assertions were dead, so the Windows failures were silent; both were found and fixed inside M0-TEST-01 / PR #13 — see the 2026-09-13 row); CI (observed 2026-09-12 via the GitHub API): `ci-pull.yml` run 34697638239 on `82c548d` green - the default-Linux lane's four P0/sanitizer jobs (linux-gcc, linux-clang, linux-asan+UBSan, linux-tsan) plus all three tooling jobs (include-lint, api-manifest, detcheck) passed; Windows/macOS jobs label-skipped as expected; the new `detcheck` job's real-scenario step correctly reported `skipped: no real scenario yet (M1-SAMPLE-01)` | | 2026-09-12 | M0-TEST-01 | `a292aef` | Test infrastructure conventions (docs/testing.md, source of truth, linked from docs/README.md, tests/README.md, building.md, README.md): tests/support/laige_test_seed.h (TestSeed()/TestPrng() — fixed default 0x1F055EED, identical to laige-fuzz's kDefaultSeed, one documented default seed repo-wide; LAIGE_TEST_SEED env override, 0x-hex/decimal, read at call time; set-but-unparseable value records a loud test failure and falls back to the default — CORE-008; per-test substream ids so streams never share position) + tests/testing (test_infra_tests, CTest entry `test_infra`, SeededRandom suite, 6 cases: 65536-draw FNV-1a KATs under the default seed 0x7EA4049545656830 and override seed 0x535D2CA741B61CBF, first-8-draw KAT, default/fuzz seed identity, loud invalid-env path via EXPECT_NONFATAL_FAILURE (gtest-spi.h), seed parser, substream isolation; machine-greppable `test-seed-check` line per KAT before asserting — the byte-identical-across-two-CI-runs identity check, M0-TEST-01 Verify) + first regress_ test (M0-CORE-08's ConfigJsonValid.ObjectMemberWhitespace renamed regress_json_object_member_ws; suite unchanged so `ctest -R config_json` still covers it; historical M0-CORE-08 record unchanged) + fuzz lane semantics (no new CI job — bounded --runs=1000 is the existing fuzz_json_parse ctest entry inside every P0 job's ctest, PRD §14 "every commit (bounded)"; nightly long form --runs=1000000 documented in docs/testing.md §3 + canonical command table; scheduled nightly lane lands with the first M1 fuzz target); CI workflow headers note the fuzz lane; local Verify: 32/32 ctest, zero warnings under NFR-8.10, on g++ static/shared, ASan+UBSan, TSan, and Clang trees; seeded KAT line identical on g++ and clang++ locally; cross-CI-run Verify: byte-identical `test-seed-check` lines in the archived Linux ASan `LastTest.log` across runs 34713354925/34714039135 (same-commit pair) and again across commits e2abce5→c8b8221 (runs 34728782624/34746755055); Windows CI fix chain landed inside this PR (M0-TOOL-02's Windows path, provenance per the established pattern — see the corrected M0-TOOL-02 row): MSVC portability (NOMINMAX, `getenv_s`/`fopen_s` C4996, C2664/C2440/C4457/C2660), `WaitForSingleObject` before `GetExitCodeProcess` (STILL_ACTIVE), then the root cause — the CI Windows runner (windows-2022) never delivers handles the process creates itself (pipes or files, INHERIT bit confirmed set, even duplicated) to children through `STARTUPINFO`; only parent-inherited (kernel-assigned) handles are delivered (measured runs 34728950395/34729337292) — and NULL `STARTUPINFO` is rejected by its CreateProcess machinery (run 34732057356); fixed by two-stage marker capture on all platforms (phase 1 `--run-a/--run-b` spawns with plain stdout inheritance + `@@DETCHK-RUN-A/B-BEGIN/END@@` markers; phase 2 `--compare-combined` splits/validates/compares) plus the zeroed-STARTUPINFO spawn; dead fragment assertions fixed in `7928379` (`@CHECKS@` variable-name misspelling + CMake single-backslash stripping → double-escaped regexes); final CI run 34746755055 (c8b8221): all 10 jobs green, Windows 32/32 | | 2026-09-13 | M0-DOC-01 | `972090d` | Docs only: full AGENTS §13 `docs/` tree — `docs/README.md` rewritten as the single index (every section linked + honest "not yet written" list, DOC-001); new section indexes `concepts/` (planned docs + interim homes incl. the ARCH-008 coordinates topic), `guides/`, `debugging/` (usable today; AGENTS §15 debug-mode status), `compatibility/` (P0 platform/compiler table per PRD §6 + CI, current formats `budgets.json`/`laige-api.json`/`deps.lock`, no migration guides yet); `docs/benchmarks/` with `methodology.md` (normative AGENTS §12 report fields, `budgets.json` field→report mapping, immutable baseline-file convention, PRD §8.1 regression policy: >10% band, hard-zero budgets, loud `NO_SAMPLES`) and empty `baselines/` (first baseline `m0-synthetic.md` lands with M0-EXIT-01); `decisions/` index updated (stale M0-DOC-01 note replaced; ADRs 0001–0004 indexed); stale references fixed in the same change (root README docs pointer + ADR 0004; old index's non-existent "M0-DOC-02" reference dropped); dead-link check over all 49 repo `*.md` files (104 internal links, 0 dead — manual per the step's Verify clause; docs-only scope, no CI job added); `ctest` 32/32 green on the existing `build` tree; CI run 34749554015: 7/7 executed jobs green (macOS/Windows skipped, label-gated) | +| 2026-09-13 | M0-EXIT-01 | `636257c` | M0 exit gate: all 21 prior M0 steps re-verified against their Scope/Verify clauses on commit `829026f` — fresh canonical g++ tree zero-warning, `ctest` 32/32; `build-shared`, `build-asan` (ASan+UBSan), `build-tsan`, and a fresh Clang 22.1.8 tree all 32/32; `laige-fuzz json_parse --runs=1000` clean; `laige-detcheck --scenario=synthetic` OK; `laige-api-scanner --check` up to date (376 symbols); `tools/laige-include-lint` OK (1/10 deps); vendored-tree lock re-proven live (tampered `deps/googletest` file fails the configure with expected-vs-actual hashes, restored tree passes); gate evidence: CI merge-lane run 34749756361 on `829026f` — all 10 jobs `success`, each under 1.2 min, `ctest` 32/32 in every P0 OS job (linux-gcc/clang/asan/tsan, windows-msvc, macos-arm64/intel); first baseline `docs/benchmarks/baselines/m0-synthetic.md` written (synthetic harness workload, full AGENTS §12 metadata, verbatim runs, commit `829026f`; no `budgets.json` `measured` updated — the stand-in measures no real budget); Progress Board 22/22, milestone marked complete | ---