Skip to content

[M1-CFG-01] Declarative game config - #45

Merged
offdev merged 1 commit into
masterfrom
m1-cfg-01-config
Sep 21, 2026
Merged

offdev merged 1 commit into
masterfrom
m1-cfg-01-config

Conversation

@offdev

@offdev offdev commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Closes roadmap/M1-heartbeat.md step M1-CFG-01 (FR-1.5, PRD §7.1/§10.2/§10.3, ARCH-007).

What this PR does

The declarative game config surface — the version 1 config.json schema, its loaders, the programmatic override merge, and the debug-build-only hot reload of non-simulation keys.

  • New public header src/laige-sim/include/laige/sim/config.h (+ config.cpp):
    • EngineConfig moved from engine.h — the five original members keep their order (existing aggregate initializers compile unchanged), gaining the budgets block (system_time_default_ms, draw_calls_per_frame, particles_per_frame — declared values before their M2 consumers), the camera block (fov/zoom/follow-lerp — stored, consumed in M2), and asset_roots (non-empty strings; no existence check — the M2 asset pipeline owns it).
    • The REQUIRED version key is checked before any other key (missing → config/version_missing, non-integer → config/version_invalid, ≠ 1 → config/version_unsupported); the provisional M1-HEAD-01 documents migrate by adding "version": 1.
    • Unknown keys at any level warn config/unknown_key and are ignored (forward-compat); first failure wins in document order; every rejection is a rate-limited warn with the NFR-13.3 5-field text + InvalidArgument.
    • loadGameConfig(path) — bounded 1 MiB read + the M0-CORE-07 parse + schema validation; laige-run now uses it (exit codes unchanged).
    • EngineConfigOverride + applyConfigOverride — the FR-1.5 override-of-a-subset merge (per-leaf optionals, each set field validated, first set field that fails wins, value semantics).
    • ConfigHotReloader — move-only, no thread, caller-driven poll; debug builds only (release rejects with config/hot_reload_disabled): non-sim changes apply in place + config/hot_reload_applied; sim-affecting changes are refused atomically + config/hot_reload_rejected (old/new fields, config untouched).
  • Replay identity preserved: the configHash covers only the sim-affecting fields, so the encoding (tag 1) is unchanged and every committed baseline / replay log stays valid (replay.h/.cpp comments + docs updated).
  • Docs: new docs/api/config.md (key table, versioning + migration, rejection table, override API, hot-reload contract, Performance, misuse warnings); engine.md / replay.md / determinism.md / testing.md / docs-README / sim-README cross-refs updated to the final surface.
  • Tests: new game_config CTest entry (38 tests: every rejection domain, the version gate, first-failure-wins, the file loader, the override merge, the hot-reload contract incl. the release-disabled path); engine_tests drops the migrated EngineConfigParse.*; fixtures gain "version": 1 (headless_smoke, samples/hello, the replay smoke fixtures). laige-api.json regenerated (734 symbols).
  • Roadmap bookkeeping: M1-CFG-01 box checked; board 20/25 (38 total); change log row + retroactive M1-DET-05 row (the M1-ECS-06 precedent).

Verification

  • ctest -R config green (config_json + game_config + hello_config_valid)
  • Full ctest 88/88 on build (Debug g++), build-asan (leak-free), build-release (the hot_reload_disabled path exercised), build-clang, build-tsan (config suites, halt_on_error=1), build-shared
  • tools/laige-include-lint OK, tools/laige-determinism-lint OK (24 sim sources, 0 violations), laige-api-scanner --check clean
  • Zero new warnings under NFR-8.10

Untested paths

  • The MSVC _fsopen read branch (CI-only; the M1-DET-02 precedent).

Compatibility

No existing symbol or behavior changed: the provisional keys carry over unchanged under the version 1 schema, the EngineConfig member order is preserved, the replay configHash encoding is unchanged, and the CLI exit codes are unchanged.

…ides, hot reload

- New public header src/laige-sim/include/laige/sim/config.h (+ config.cpp):
  the FINAL version 1 declarative config schema — EngineConfig moved from
  engine.h (the five original members keep their order), the REQUIRED
  "version" key (gated before every other key, ARCH-007), the budgets /
  camera / asset_roots declared blocks, parseEngineConfig, loadGameConfig,
  EngineConfigOverride + applyConfigOverride (the FR-1.5 override merge),
  and the debug-only ConfigHotReloader (non-sim keys reload live;
  sim-affecting keys are refused with config/hot_reload_rejected).
- engine.h/engine.cpp: the provisional config surface is folded into
  config.h (engine.h includes it; Engine::create re-validates the tick
  rate with the shared kConfigTickRateInvalidMessage); laige-run.cpp
  now uses loadGameConfig (exit codes unchanged).
- The replay identity's configHash covers only the sim-affecting fields,
  so the encoding (tag 1) is unchanged and every committed baseline /
  replay log stays valid (replay.h/.cpp comments + docs updated).
- docs/api/config.md (new): key table, versioning + migration, rejection
  table, override API, hot-reload contract, Performance, misuse warnings;
  engine.md / replay.md / determinism.md / testing.md / docs-README /
  sim-README cross-refs updated to the final surface.
- Tests: new game_config CTest entry (38 tests: every rejection domain,
  the version gate, first-failure-wins, the file loader, the override
  merge, the hot-reload contract incl. the release-disabled path);
  engine_tests drops the migrated EngineConfigParse.*; determinism_tests'
  config docs gain "version": 1; fixtures gain "version": 1
  (headless_smoke, samples/hello, the replay smoke fixtures).
- laige-api.json regenerated (734 symbols; api-real-tree green).
- Verify: ctest -R config green; full ctest 88/88 on build (Debug g++),
  build-asan, build-release (the hot_reload_disabled path), build-clang,
  build-tsan, build-shared; zero new warnings under NFR-8.10.
  Untested: the MSVC _fsopen branch (CI-only).
- Roadmap: M1-CFG-01 box checked; board 20/25 (38 total); change log row
  + retroactive M1-DET-05 row (the M1-ECS-06 precedent).

Closes roadmap/M1-heartbeat.md step M1-CFG-01.
@offdev
offdev merged commit cf6c850 into master Sep 21, 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