Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T,E>` / `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
Expand Down
7 changes: 5 additions & 2 deletions docs/benchmarks/baselines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
98 changes: 98 additions & 0 deletions docs/benchmarks/baselines/m0-synthetic.md
Original file line number Diff line number Diff line change
@@ -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)`.
39 changes: 36 additions & 3 deletions roadmap/M0-foundations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
5 changes: 3 additions & 2 deletions roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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** | |

---

Expand Down Expand Up @@ -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 `<tick> <hash>` — 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=<bin> --run-b=<bin> [-- scenario-args...]` (the M1-DET-04 mode; `--` separator keeps scenario args unambiguous); stable report `detcheck scenario=<name> result=OK\|DIVERGED [first_diff_tick=<t>]` + 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 |


---
Expand Down
Loading