Skip to content

[M1-SAMPLE-01] hello.laige (headless template) - #40

Merged
offdev merged 2 commits into
masterfrom
m1-sample-01-hello
Sep 17, 2026
Merged

offdev merged 2 commits into
masterfrom
m1-sample-01-hello

Conversation

@offdev

@offdev offdev commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Implements M1-SAMPLE-01 · hello.laige (headless template) per roadmap/M1-heartbeat.md.

The first complete Laige game — the headless template every game project (human or AI agent) starts from: one component, one system, one entity, deterministic by default, records and replays its own log.

What lands

  • 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, which don't count), config.json (canonical 60 Hz / 8-entity / 0x1F055EED / fpx16_16 config), hello.laige (provisional manifest — M3-ASSET-01 owns the real format), LICENSE, README.md (the full contract).
  • The game — PlayerPos (M::Vec2, component + determinism marks at namespace scope), MovePlayer (LAIGE_SYSTEM; constant +1/tick per axis, wrapped in the [-16, 16] box), one entity at the box center. Config embedded in the binary; config.json is its declarative record (the hello_config_valid CTest runs laige-run --headless against it).
  • The detcheck scenario contract — stdout = tick-0 line + one World::stateHash line per completed tick (301 lines at 300 ticks); stderr = engine logging + hello {headless|replay} ticks=N status=ok; exit 0/1/2.
  • 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 via replayIdentityDiff, and re-runs the log's frame count through the same tick loop. Record → replay is byte-identical. (laige-replay can't replay a game log — it registers only the built-ins — so the scenario binary carries the mode; noted in docs/api/replay.md.)
  • CI — tests/sample/: 9 CTest entries (hello_scenario, hello_replay (Debug-gated), hello_replay_missing/_malformed, hello_mode_exclusive, hello_usage_unknown/_missing_value, hello_config_valid, hello_line_budget — a CMake-core code-line counter checked in CI per the step's Verify). The detcheck job's real-scenario step self-activates: both no-arg runs must exit 0 (M1-DET-04 swaps in the two-configuration comparison).
  • Docs — detcheck.md (the real scenario now exists), replay.md (scope), building.md (command rows + target note), docs/README.md (M1 progress + link), ci.yml step comment.
  • Roadmap — step checked; Progress Board M1 → 17/25 (also picks up the M1-DET-03 box that was checked without a board update); Change Log line.

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 (-DLAIGE_ASAN=ON).
  • Release tree spot check: no-arg run exits 0, --replay exits 2 (the NDEBUG gate).
  • The exact 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 no drift (the sample adds no public header). No float/double tokens in the sample source.
  • Untested locally: MSVC/AppleClang paths and the CI-only TSan lane (single-threaded binary; the ASan+UBSan run covers the memory side).

Compatibility

Additive only — no existing symbol, behavior, or CI gate changed.

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.
- 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.
@offdev
offdev merged commit 03b2ff3 into master Sep 17, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant