Skip to content

Commit 03b2ff3

Browse files
authored
[M1-SAMPLE-01] hello.laige (headless template) (#40)
* [M1-SAMPLE-01] hello.laige headless template game (samples/hello) M1-SAMPLE-01 (roadmap/M1-heartbeat.md, NFR-13.5, PRD §9.4/§13, TEST-004, ADR 0002, ARCH-010, CORE-008): the first complete Laige game — the headless template that every game project (human or AI agent) starts from: one component, one system, one entity, deterministic by default, records and replays its own log. - samples/hello/: hello.cpp (99 code lines < the PRD §9.4 budget of 100; the NFR-13.5 "heavily commented" requirement lives in the ~76 comment lines and does not count), config.json (canonical 60 Hz / 8-entity / 256-churn / 0x1F055EED / fpx16_16 config), hello.laige (provisional manifest — M3-ASSET-01 owns the real format), LICENSE, README.md (the full contract: files, build/run, CLI + stream + exit codes, the game walkthrough, replay, tests, line budget). - The game: PlayerPos (M::Vec2, LAIGE_COMPONENT + LAIGE_DETERMINISM_SAFE at namespace scope — the marks specialize laige::detail traits and cannot live in an anonymous namespace), MovePlayer (LAIGE_SYSTEM; constant +1/tick per axis wrapped in the [-16, 16] box; the each Status is cast away per the FR-1.3/query-guard contract — asserts in debug, warns in release), one entity at the box center. The config is embedded in the binary (config.json is the declarative record consumed by `laige-run --headless` — the hello_config_valid CTest keeps the two in step); the registration order is part of the replay identity (ADR 0002). - stdout contract (docs/api/detcheck.md): the tick-0 line plus one World::stateHash line per completed tick — 301 lines at 300 ticks — the detcheck scenario stream; stderr: the engine logging facade + the `hello {headless|replay} ticks=N status=ok` summary; exit codes 0 ok / 1 run failure (runSystems/writeFrame/finish) / 2 usage/IO/ identity (strict arg parse; paired options; --log/--replay exclusive). - Replay: --replay LOG records its own run (debug builds only — parity with the engine's startReplayRecording policy; exit 2 under NDEBUG), --log LOG loads the log, checks the replay identity against its own (world, config) via replayIdentityDiff (mismatch = rejected replay, never a silent divergence), and re-runs the log's frame count through the same tick loop; record → replay is byte-identical (hello_replay CTest). laige-replay cannot replay a game log (it registers only the built-ins — different identity); the scenario binary carries the mode (docs/api/replay.md). - Build: samples/hello/CMakeLists.txt (laige_apply_engine_policy + laige_apply_simmath_policy, links laige-sim, RUNTIME_OUTPUT_DIRECTORY = samples/hello/bin so the CI detcheck step invokes it from the repository root with no arguments — the shared-path caveat is documented in the README); wired into the root CMakeLists (the sample is a shipped project) and .gitignore (samples/*/bin/). - CI: tests/sample/ — 9 CTest entries with TSan env blocks and generated scripts (the tests/replay pattern): hello_scenario (no-arg run, 301-line structural check, stderr summary), hello_replay (record → replay byte-identical; Debug-gated — recording is an NDEBUG-gated feature), hello_replay_missing / _malformed (exit 2 + `hello: replay:` naming), hello_mode_exclusive, hello_usage_unknown / _missing_value (exit 2), hello_config_valid (laige-run --headless consumes config.json, status=ok), hello_line_budget (CMake-core counter — code lines = non-blank, non-// lines; the script protects semicolons before the CMake list split and spells the whitespace class explicitly, since CMake regexes have no POSIX classes). - CI wiring: the detcheck job's real-scenario step (ci.yml + ci-pull.yml) self-activates now that samples/hello/bin/hello is built in the job — two no-arg runs must both exit 0 (M1-DET-04 swaps this for the build-asan vs build two-configuration comparison); the ci.yml step comment was stale and now matches. - Docs: detcheck.md (the real scenario now exists — the CI status and hash-scope paragraphs updated), replay.md (scope: the scenario binary's own loadReplay + replayIdentityDiff + tick loop), building.md (the two hello command rows + the target note), docs/README.md (M1 progress + the Getting-started link). Local verification: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug + build warning-clean (-Wall -Werror); ctest 76/76 on the Debug tree (incl. the 9 hello entries) and 9/9 on a fresh ASan+UBSan tree (build-asan, -DLAIGE_ASAN=ON); Release tree spot check — no-arg run exits 0, --replay exits 2 (the NDEBUG gate); the CI forms from the repository root: `./samples/hello/bin/hello` (301 lines, exit 0) and `./build/bin/laige-detcheck --run-a=samples/hello/bin/hello --run-b=samples/hello/bin/hello` (both exit 0, streams byte-identical); laige-determinism-lint OK (20 files, 0 violations), laige-include-lint OK (37 files, 1/10 vendored); api-manifest: the sample adds no public header (no drift — the api tests pass in the full suite). No float/double tokens in the sample source. Untested locally: the MSVC/AppleClang paths and the CI-only TSan lane (single-threaded binary; the ASan+UBSan run covers the memory side). Compat: additive only — no existing symbol, behavior, or CI gate changed. * [M1-SAMPLE-01] roadmap: check M1-SAMPLE-01, board 17/25, change log - roadmap/M1-heartbeat.md: M1-SAMPLE-01 box checked (the step's Verify holds: CI builds hello, runs it, replay is bit-exact (hello_replay CTest + the detcheck job's real-scenario step), and the game-code line count is < 100 checked in CI (hello_line_budget — 99 code lines). - roadmap/README.md: Progress Board M1 15 -> 17 done (also picks up the M1-DET-03 box that was checked without a board update), status in progress (M1-SAMPLE-01); Change Log line for 75d1df6.
1 parent 50a1000 commit 03b2ff3

20 files changed

Lines changed: 817 additions & 22 deletions

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,11 @@ jobs:
354354
- name: Synthetic scenario self-check
355355
run: ./build/bin/laige-detcheck --scenario=synthetic
356356
- name: Real scenario (M1-SAMPLE-01)
357-
# Skipped until M1-SAMPLE-01 lands the hello scenario binary;
358-
# M1-DET-04 replaces this step with the two-configuration
359-
# comparison, e.g.:
357+
# The hello template (samples/hello) is always built in this
358+
# job, so this step always executes: two no-arg runs of the
359+
# scenario — both must exit 0 and print the 301-line per-tick
360+
# hash stream (the guard is defensive only). M1-DET-04 replaces
361+
# this step with the two-configuration comparison, e.g.:
360362
# ./build/bin/laige-detcheck --run-a=build-asan/bin/hello
361363
# --run-b=build/bin/hello
362364
run: |

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ build-*/
66
Testing/
77
CMakeUserPresets.json
88

9+
# Sample binaries (samples/hello builds into its own source-tree bin/ —
10+
# the CI detcheck step invokes samples/hello/bin/hello from the repo
11+
# root; M1-SAMPLE-01)
12+
samples/*/bin/
13+
914
# Compiled artifacts (in case they ever land outside build trees)
1015
*.o
1116
*.obj

‎CMakeLists.txt‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ endfunction()
242242
add_subdirectory(src/laige-core)
243243
add_subdirectory(src/laige-sim)
244244

245+
# Samples (M1-SAMPLE-01): the hello.laige headless template game.
246+
# Unconditional (not test-gated): the sample is a shipped project, and
247+
# the CI detcheck step builds and runs it from the default tree. Its
248+
# binary lands at the source-tree path samples/hello/bin/hello (its own
249+
# CMakeLists sets RUNTIME_OUTPUT_DIRECTORY — see there why).
250+
add_subdirectory(samples/hello)
251+
245252
# ---------------------------------------------------------------------------
246253
# Tests (gated by LAIGE_BUILD_TESTS; conventions in tests/README.md)
247254
# ---------------------------------------------------------------------------

‎docs/README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ temp+rename, size-bounded), `Engine::startReplayRecording`, and
2222
`laige-run --replay`; M1-DET-03: replay execution —
2323
`World::stateHash` (the deterministic state hash), `runReplay`
2424
(the identity-checked re-run and its per-tick hash stream), and the
25-
`laige-replay` runner).
25+
`laige-replay` runner; M1-SAMPLE-01: `hello.laige` — the headless
26+
template game (samples/hello: one component, one system, one entity,
27+
the per-tick `World::stateHash` stream on stdout, record → replay
28+
through the scenario's own binary, the PRD §9.4 line budget —
29+
[samples/hello/README.md](../../samples/hello/README.md)).
2630
Every section of the AGENTS §13 `docs/` tree exists; each entry below
2731
links what is written and the "not yet written" section marks what is
2832
still to land.
@@ -35,6 +39,10 @@ still to land.
3539
status. Tool commands: `laige-run`, `laige-replay`, `laige-fuzz`, `laige-bench`,
3640
`laige-detcheck`, the `laige-api` manifest target, and the
3741
include-graph lint.
42+
- [hello.laige](../../samples/hello/README.md) — the headless template
43+
game (M1-SAMPLE-01): the canonical smallest-complete Laige game —
44+
build it with the repository, run it headless, record and replay it,
45+
and learn every component/system/tick mark a Laige game uses.
3846

3947
## Concepts
4048

‎docs/api/detcheck.md‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ state across configurations (NFR-8.3, FR-1.4). The engine state-hash API
1414
landed with **M1-DET-03** (`World::stateHash`,
1515
[api/entity.md](entity.md)); this tool works against the **hash-file
1616
output contract** defined below and compares two such streams. The
17-
current scenario fixture still prints its own ad-hoc hash stream; the
18-
scenario wiring that switches it to the `World::stateHash` stream lands
19-
with M1-SAMPLE-01 (the tool's line-by-line comparison is unchanged).
17+
real scenario now exists: **M1-SAMPLE-01**'s `hello` (samples/hello)
18+
prints exactly this contract — the tick-0 line plus one
19+
`World::stateHash` line per completed tick — and the CI job's real-
20+
scenario step runs it; **M1-DET-04** activates the two-configuration
21+
comparison of it (the tool's line-by-line comparison is unchanged).
2022

2123
## Scenario contract
2224

@@ -159,10 +161,9 @@ body index and a nudge in [-4, 3] applied to x. Per-tick hash: FNV-1a 64
159161
and compiler by the language standard; no float anywhere in the workload.
160162
**Hash scope (M0):** tick counter + seed + body words. The Prng position
161163
is a pure function of (seed, nudge history) in this workload; the
162-
definitive scope — including PRNG state and the exact hash function —
163-
is M1-DET-03's `World::stateHash`
164-
([api/entity.md](entity.md)), which the scenario wiring switches to
165-
with M1-SAMPLE-01 (the tool's line-by-line comparison is unchanged).
164+
definitive scope — including PRNG state and the exact hash function — is
165+
M1-DET-03's `World::stateHash` ([api/entity.md](entity.md)), which the
166+
`hello` scenario (M1-SAMPLE-01) prints directly on its stdout stream.
166167

167168
## Performance and bounds
168169

@@ -190,7 +191,12 @@ script asserting both the exit code and the required output fragments
190191
The `detcheck` CI job (`.github/workflows/ci.yml` and `ci-pull.yml`) runs
191192
the built-in self-check on every PR and merge (like `include-lint` and
192193
`api-manifest`, independent of the `ci:*` label selector — it is a
193-
tooling check, not an additional P0 OS build). The real-scenario
194-
comparison — two build configurations of M1-SAMPLE-01's `hello` — is
195-
**skipped until that sample exists**; **M1-DET-04** activates it and
196-
records the result per ARCH-010.
194+
tooling check, not an additional P0 OS build). It also runs the **real
195+
scenario** (M1-SAMPLE-01): both `--run-a` and `--run-b` point at
196+
`samples/hello/bin/hello` (the no-arg 300-tick headless run — the step
197+
activates once the sample binary exists, which it now does), so every
198+
PR and merge executes the template game's detcheck contract: both runs
199+
must complete (exit 0) and print the 301-line hash stream. **M1-DET-04**
200+
replaces this same-configuration step with the two-configuration
201+
comparison (`build-asan/bin/hello` vs `build/bin/hello`) and records the
202+
result per ARCH-010.

‎docs/api/replay.md‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,10 +349,13 @@ laige-replay --log LOG --config CONFIG.json [--expect BASELINE]
349349
(the ADR 0003 bounded-read precedent).
350350
- **Scope:** `laige-replay` replays logs recorded by `laige-run
351351
--headless --replay` (the engine's built-in registration). A
352-
game-scenario log is replayed by the scenario binary itself through
353-
the same `runReplay` library (the M1-SAMPLE-01 scenario wires it;
354-
M1-DET-04's detcheck `--run-a/--run-b` compares two scenarios' hash
355-
streams).
352+
game-scenario log is replayed by the scenario binary itself: it loads
353+
the log (`loadReplay`), checks the replay identity against its own
354+
(world, config) (`replayIdentityDiff` — a mismatch is a rejected
355+
replay, never a silent divergence), and re-runs the log's frame count
356+
through the same tick loop `runReplay` drives (M1-SAMPLE-01's
357+
`hello --log` wires this; M1-DET-04's detcheck `--run-a/--run-b`
358+
compares two scenarios' hash streams).
356359
357360
## Performance (PERF-002/003, LOG-003)
358361

‎docs/getting-started/building.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ The canonical-commands table in [roadmap/README.md](../../roadmap/README.md)
3838
| Benchmarks | `./build/bin/laige-bench --suite=<name>` |
3939
| Determinism check | `./build/bin/laige-detcheck --scenario=<name>` |
4040
| Replay runner | `./build/bin/laige-replay --log LOG --config CONFIG.json [--expect BASELINE]` |
41+
| hello template (run) | `./samples/hello/bin/hello [--replay LOG]` |
42+
| hello template (replay) | `./samples/hello/bin/hello --log LOG` |
4143
| API manifest | `cmake --build build --target laige-api` |
4244
| Include-graph lint + dependency count | `python3 tools/laige-include-lint` |
4345
| Determinism source scan (sim module) | `python3 tools/laige-determinism-lint` |
@@ -58,7 +60,16 @@ Notes:
5860
and exits 1 at the first diverging tick — contract in
5961
[docs/api/replay.md](../api/replay.md)), and target `laige-api`
6062
(M0-TOOL-01). Their command forms were fixed here when they were
61-
reserved, so no step can drift them.
63+
reserved, so no step can drift them. The `hello` rows are the
64+
M1-SAMPLE-01 template game (samples/hello): it builds into the
65+
source-tree path `samples/hello/bin/hello` (its own CMake target
66+
property — the CI detcheck step invokes it from the repository
67+
root with no arguments), prints the 301-line per-tick state-hash
68+
stream on stdout (the detcheck scenario contract —
69+
[docs/api/detcheck.md](../api/detcheck.md)), and exits 0/1/2; the
70+
`hello_*` CTest entries (`tests/sample`) are its CI form, and
71+
`--replay` is a debug-build feature. Contract:
72+
[samples/hello/README.md](../../samples/hello/README.md).
6273
Fuzz and randomized-test seeds: fixed default `0x1F055EED`,
6374
overridable (`laige-fuzz --seed=…`; tests via the `LAIGE_TEST_SEED`
6475
environment variable) — see [docs/testing.md](../testing.md).

‎roadmap/M1-heartbeat.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ zero-allocation property (M1-ALLOC-01 enforces it once it exists; before that, A
271271
- **Verify:** `laige-bench --suite=sim-tick` avg ≤ 3.0 ms and p99 ≤ 5 ms on both backends on the CI reference machine (if local machine differs, record measured value + CI is the gate); baseline file exists.
272272
- **Size:** ~200 lines + baseline doc
273273

274-
- [ ] **M1-SAMPLE-01 · `hello.laige` (headless template)**
274+
- [x] **M1-SAMPLE-01 · `hello.laige` (headless template)**
275275
- **Refs:** NFR-13.5 (template < 100 lines), PRD §13
276276
- **Depends:** M1-HEAD-01, M1-DET-03
277277
- **Scope:**

0 commit comments

Comments
 (0)