From 5d1ec6026477e6261cf6da95b9726af089c54566 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Mon, 14 Sep 2026 18:51:10 +0200 Subject: [PATCH 1/2] [M1-SYS-02] Fix Windows CI: MSVC C4267/C4127 fatal under /WX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The windows-msvc job (merge matrix, ci-pull.yml ci:windows) is red on every master merge since M1-ECS-05: two warning classes are fatal under the engine policy's /W4 /WX (NFR-8.10, CORE-010): - C4267 (size_t -> uint32_t) in iter_order_tests.cpp's per-round Fisher-Yates shuffle: the loop variable is initialized from live.size(), which is 64-bit on Windows x64. live holds at most kScenarioEntities (16, a uint32_t constant) elements, so an explicit narrowing cast to uint32_t is lossless and the loop is unchanged. - C4127 (conditional expression is constant) in the new World::resolveIoEntry template (M1-SYS-01): the access of the Io tag is a compile-time constant, and the plain if tripped the warning in every instantiating translation unit (system_registry_tests.cpp, scheduler_tests.cpp — the two registerSystem call sites). The branch is now if constexpr, as the compiler's own diagnostic suggests: the discarded branch is never emitted, and the behavior is identical (the condition was constant to begin with). Both are code fixes, not warning suppressions (CORE-010). No public API change (resolveIoEntry is a detail template), so laige-api.json is unchanged and the api-manifest job stays green. Validation: local g++ Debug tree 42/42 ctest, clang++ Debug and clang++ ASan trees rebuilt and re-tested; the PR carries the ci:windows label so the windows-msvc job verifies MSVC 2022 in CI. --- src/laige-sim/include/laige/sim/entity.h | 11 +++++++++-- tests/laige-sim/iter_order_tests.cpp | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/laige-sim/include/laige/sim/entity.h b/src/laige-sim/include/laige/sim/entity.h index 257d5d8..9eb2f28 100644 --- a/src/laige-sim/include/laige/sim/entity.h +++ b/src/laige-sim/include/laige/sim/entity.h @@ -1020,8 +1020,15 @@ detail::IoResolution World::resolveIoEntry(detail::IdSet256& read, *failedId = id; return detail::IoResolution::Duplicate; } - if (detail::IoComponent::access == Access::Read) read.set(id); - else write.set(id); + // if constexpr, not if: the access is a compile-time constant of the + // Io tag, and a plain if trips MSVC C4127 (fatal under /WX) in every + // instantiating translation unit. The discarded branch is never + // emitted — the behavior is identical. + if constexpr (detail::IoComponent::access == Access::Read) { + read.set(id); + } else { + write.set(id); + } return detail::IoResolution::Ok; } diff --git a/tests/laige-sim/iter_order_tests.cpp b/tests/laige-sim/iter_order_tests.cpp index 632bf60..8177866 100644 --- a/tests/laige-sim/iter_order_tests.cpp +++ b/tests/laige-sim/iter_order_tests.cpp @@ -301,7 +301,12 @@ Scenario buildScenario(laige::Prng rng, bool deterministic) { if (!isDead(i)) live.push_back(i); } if (!deterministic) { - for (std::uint32_t j = live.size(); j > 1; --j) { + // live holds at most kScenarioEntities (a uint32_t constant) + // elements, so its size fits a uint32_t: an explicit narrowing + // keeps MSVC C4267 quiet under /WX. + const std::uint32_t liveCount = + static_cast(live.size()); + for (std::uint32_t j = liveCount; j > 1; --j) { const std::uint32_t k = rng.next_range(0u, j); std::swap(live[j - 1], live[k]); } From 608d1f6086783c89109e2d2c26d38aa3223a2367 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Mon, 14 Sep 2026 19:18:59 +0200 Subject: [PATCH 2/2] [M1-SYS-02] Windows CI: assert the no-deps spec contract, not its compiler-specific spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows job's next failure layer (with the compile errors fixed): SystemScheduler.MacroCarriesDependsOnSpec asserted the EXACT spelling of the no-deps spec built by LAIGE_SYSTEM(SchNoop, 1): EXPECT_STREQ(SchNoop_Def.dependsOn, ""); The empty variadic pack's spelling is a preprocessor property, and the compilers diverge: - GCC/Clang: #__VA_ARGS__ stringizes the empty pack to "" ([cpp.stringize]/2: "The character string literal corresponding to an empty stringizing argument is \"\"") - MSVC 2022: the empty pack stays an empty token sequence, so the 4th initializer is absent and dependsOn is value-initialized to nullptr. Both are the documented no-deps form (SystemDef::dependsOn: "nullptr or "" = no dependencies"; parseDepSpec: "nullptr or a whitespace-only spec is Ok with count 0"), and the engine consumes the spec through parseDepSpec only — every other scheduler test passed on MSVC, so the observable behavior is identical. The assertion pinned one compiler's spelling, which is exactly the kind of platform-dependent raw assertion that does not travel across P0 platforms (the M1-ECS-03 churn-check fix, same reasoning). The test now pins the contract (no deps <=> nullptr or ""), keeping the exact-spelling KATs for the one- and two-name cases, which stringize identically on all three compiler families. system.h's macro documentation updated to state both spellings (DOC-003). laige-api.json regenerated: the documentation lines shifted the system.h symbol lines (27 entries, line numbers only — no signature or summary changes). Validation: local g++ and clang++ Debug trees, 42/42 ctest each; the api-real-tree drift check and the api-manifest job see the regenerated manifest. --- laige-api.json | 54 ++++++++++++------------ src/laige-sim/include/laige/sim/system.h | 11 +++-- tests/laige-sim/scheduler_tests.cpp | 12 +++++- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/laige-api.json b/laige-api.json index d31d994..cbda2a9 100644 --- a/laige-api.json +++ b/laige-api.json @@ -488,32 +488,32 @@ {"name": "laige::Read::value", "kind": "variable", "header": "src/laige-sim/include/laige/sim/query.h", "line": 247, "signature": "static constexpr Access value = Access::Read", "summary": null, "budget": null, "experimental": false}, {"name": "laige::Write", "kind": "struct", "header": "src/laige-sim/include/laige/sim/query.h", "line": 249, "signature": "struct Write", "summary": null, "budget": null, "experimental": false}, {"name": "laige::Write::value", "kind": "variable", "header": "src/laige-sim/include/laige/sim/query.h", "line": 250, "signature": "static constexpr Access value = Access::Write", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemId", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 320, "signature": "struct SystemId", "summary": "The stable per-world system id (FR-1.3): assigned in registration order, densely from 1. See the header preamble for the id and determinism contract.", "budget": null, "experimental": false}, - {"name": "laige::SystemId::value", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 321, "signature": "std::uint32_t value{}", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::kInvalidSystemId", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 326, "signature": "inline constexpr SystemId kInvalidSystemId{0}", "summary": "The never-assigned id (API-008: the invalid state is representable and checkable; call sites never spell raw 0s).", "budget": null, "experimental": false}, - {"name": "laige::operator==", "kind": "function", "header": "src/laige-sim/include/laige/sim/system.h", "line": 328, "signature": "inline bool operator==(SystemId a, SystemId b) noexcept", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::operator!=", "kind": "function", "header": "src/laige-sim/include/laige/sim/system.h", "line": 331, "signature": "inline bool operator!=(SystemId a, SystemId b) noexcept", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::kMaxSystems", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 339, "signature": "inline constexpr std::uint32_t kMaxSystems = 256", "summary": "The engine-level cap on systems per world (CORE-005: a named engine constant, the kMaxComponentTypes precedent — a game's system count is orders of magnitude smaller than its entity count; raising it is an ADR, not a knob).", "budget": null, "experimental": false}, - {"name": "laige::kMaxSystemDependencies", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 346, "signature": "inline constexpr std::uint32_t kMaxSystemDependencies = 16", "summary": "The bound on one system's direct depends_on list (CORE-005). A direct dependency list is a small hand-written declaration; beyond 16 the ordering should be carried by registration position (a barrier is registration order, not a dependency list). Raising it is an ADR.", "budget": null, "experimental": false}, - {"name": "laige::SystemFn", "kind": "alias", "header": "src/laige-sim/include/laige/sim/system.h", "line": 352, "signature": "using SystemFn = void (*)(World&, SystemContext&)", "summary": "The system function signature (FR-1.3): a plain free function — no class, no inheritance. `world` is the world the system runs on; `ctx` is that tick's SystemContext (one world, one owner thread, PRD §10.2).", "budget": null, "experimental": false}, - {"name": "laige::SystemDef", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 367, "signature": "struct SystemDef", "summary": "The static declaration of a system (FR-1.3): one per system, built by the LAIGE_SYSTEM macro (see the header preamble for the shape). `name` is the stable registration name (unique per world); `run` is the plain system function; `budgetMs` is the declared per-tick time budget in MILLISECONDS (fpx16_16 — exact, no floating point; ADR 0002). `dependsOn` is the raw depends_on spec (M1-SYS-02): a comma-separated list of registration names — nullptr or \"\" means no dependencies (see the preamble \"Scheduler\" for the format and the validation). The declared component I/O is NOT part of the def (per-world runtime ids, see the preamble): it is declared at registration (the Io<...> pack of World::registerSystem) and stored in the world's record. The def is a small trivially-copyable value — registerSystem copies it, so a def on the stack is safe.", "budget": null, "experimental": false}, - {"name": "laige::SystemDef::name", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 368, "signature": "const char* name", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemDef::run", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 369, "signature": "SystemFn run", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemDef::budgetMs", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 370, "signature": "fpx16_16 budgetMs", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemDef::dependsOn", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 371, "signature": "const char* dependsOn", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemSchedule", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 382, "signature": "struct SystemSchedule", "summary": "The computed execution order of one world's systems (M1-SYS-02). A plain value: built by World::scheduleSystems (setup phase), consumed by World::runSystems once per tick, owned by the caller (the game's engine object — M1-HEAD-01). `systemCount` is the world's system count AT SCHEDULING TIME (runSystems' staleness check); `order[i]` is the SystemId of the system that runs i-th (order[0] first, order[systemCount - 1] last; no repeats, dense 1..systemCount).", "budget": null, "experimental": false}, - {"name": "laige::SystemSchedule::systemCount", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 383, "signature": "std::uint32_t systemCount{}", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemSchedule::order", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 384, "signature": "std::uint32_t order[kMaxSystems]{}", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemContext", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 394, "signature": "struct SystemContext", "summary": "The per-tick context handed to a system's run() (FR-1.3; the PRD Appendix B sketch's `ctx`). It names the world the system runs on and delegates iteration to World::each (query.h) — the sketch's `ctx.each<...>()`. The context is built per system per tick by the scheduler (M1-SYS-02); until then games and tests build it directly. It is a non-owning view (the world owns the storage): never store it across ticks.", "budget": null, "experimental": false}, - {"name": "laige::SystemContext::world", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 396, "signature": "World& world", "summary": "The world the system runs on (one world, one owner thread).", "budget": null, "experimental": false}, - {"name": "laige::SystemContext::each", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 405, "signature": "template [[nodiscard]] Status each(F&& fn, Acc... acc) noexcept", "summary": "Delegate to World::each(fn, Read/Write tags...) on the same world: identical semantics, visit order, iteration-legality behavior, and Status results (query.h). No allocation. The definition is out-of-line in entity.h (the World home): World is incomplete here, and the delegated call is checked at instantiation — which needs the complete World.", "budget": "O(kMaxArchetypes * N) scan + one visit per matching entity; no allocation.", "experimental": false}, - {"name": "laige::Io", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 422, "signature": "template struct Io", "summary": "One declared component I/O entry of a system (FR-1.3): component type T and its declared access. Pass one value of this tag type per component in the Io<...> pack of World::registerSystem:", "budget": null, "experimental": false}, - {"name": "laige::Io::access", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 425, "signature": "static constexpr Access access = kAccess", "summary": "The declared access of the entry (Read or Write).", "budget": null, "experimental": false}, - {"name": "laige::SystemInfo", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 435, "signature": "struct SystemInfo", "summary": "A registered system's snapshot (a plain value; the M1-SYS-02 scheduler and the M1-PROF-01 profiler pull it). `def` is the value copy of the registered def; `id` is the world's SystemId. The declared component I/O list (FR-1.3) is stored as the disjoint read/write id sets and read back through the membership queries: the documented list order is ascending ComponentTypeId (component.h id contract — a pure function of the sets).", "budget": null, "experimental": false}, - {"name": "laige::SystemInfo::def", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 436, "signature": "SystemDef def{}", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemInfo::id", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 437, "signature": "SystemId id{}", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::SystemInfo::declaresRead", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 441, "signature": "[[nodiscard]] bool declaresRead(ComponentTypeId componentId) const noexcept", "summary": "True when the system declares `componentId` for reading.", "budget": "O(1); no allocation.", "experimental": false}, - {"name": "laige::SystemInfo::declaresWrite", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 445, "signature": "[[nodiscard]] bool declaresWrite(ComponentTypeId componentId) const noexcept", "summary": "True when the system declares `componentId` for writing.", "budget": "O(1); no allocation.", "experimental": false}, - {"name": "LAIGE_SYSTEM", "kind": "macro", "header": "src/laige-sim/include/laige/sim/system.h", "line": 480, "signature": "#define LAIGE_SYSTEM(Name, budget_ms, ...)", "summary": "Declare a system (FR-1.3): at namespace scope, directly above the plain system function's definition. `Name` is both the C++ function name and the system's registration name (stringified); `budget_ms` is the declared per-tick time budget in milliseconds (a numeric literal, e.g. 1 or 0.5 — converted to the exact fpx16_16 once, at program start); the optional trailing `Dep...` names are the depends_on spec (M1-SYS-02): the registration names of the systems `Name` must run after, stringified verbatim into the def's `dependsOn` field (comma-separated, as written). Expands to the function declaration plus", "budget": null, "experimental": false} + {"name": "laige::SystemId", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 325, "signature": "struct SystemId", "summary": "The stable per-world system id (FR-1.3): assigned in registration order, densely from 1. See the header preamble for the id and determinism contract.", "budget": null, "experimental": false}, + {"name": "laige::SystemId::value", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 326, "signature": "std::uint32_t value{}", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::kInvalidSystemId", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 331, "signature": "inline constexpr SystemId kInvalidSystemId{0}", "summary": "The never-assigned id (API-008: the invalid state is representable and checkable; call sites never spell raw 0s).", "budget": null, "experimental": false}, + {"name": "laige::operator==", "kind": "function", "header": "src/laige-sim/include/laige/sim/system.h", "line": 333, "signature": "inline bool operator==(SystemId a, SystemId b) noexcept", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::operator!=", "kind": "function", "header": "src/laige-sim/include/laige/sim/system.h", "line": 336, "signature": "inline bool operator!=(SystemId a, SystemId b) noexcept", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::kMaxSystems", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 344, "signature": "inline constexpr std::uint32_t kMaxSystems = 256", "summary": "The engine-level cap on systems per world (CORE-005: a named engine constant, the kMaxComponentTypes precedent — a game's system count is orders of magnitude smaller than its entity count; raising it is an ADR, not a knob).", "budget": null, "experimental": false}, + {"name": "laige::kMaxSystemDependencies", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 351, "signature": "inline constexpr std::uint32_t kMaxSystemDependencies = 16", "summary": "The bound on one system's direct depends_on list (CORE-005). A direct dependency list is a small hand-written declaration; beyond 16 the ordering should be carried by registration position (a barrier is registration order, not a dependency list). Raising it is an ADR.", "budget": null, "experimental": false}, + {"name": "laige::SystemFn", "kind": "alias", "header": "src/laige-sim/include/laige/sim/system.h", "line": 357, "signature": "using SystemFn = void (*)(World&, SystemContext&)", "summary": "The system function signature (FR-1.3): a plain free function — no class, no inheritance. `world` is the world the system runs on; `ctx` is that tick's SystemContext (one world, one owner thread, PRD §10.2).", "budget": null, "experimental": false}, + {"name": "laige::SystemDef", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 372, "signature": "struct SystemDef", "summary": "The static declaration of a system (FR-1.3): one per system, built by the LAIGE_SYSTEM macro (see the header preamble for the shape). `name` is the stable registration name (unique per world); `run` is the plain system function; `budgetMs` is the declared per-tick time budget in MILLISECONDS (fpx16_16 — exact, no floating point; ADR 0002). `dependsOn` is the raw depends_on spec (M1-SYS-02): a comma-separated list of registration names — nullptr or \"\" means no dependencies (see the preamble \"Scheduler\" for the format and the validation). The declared component I/O is NOT part of the def (per-world runtime ids, see the preamble): it is declared at registration (the Io<...> pack of World::registerSystem) and stored in the world's record. The def is a small trivially-copyable value — registerSystem copies it, so a def on the stack is safe.", "budget": null, "experimental": false}, + {"name": "laige::SystemDef::name", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 373, "signature": "const char* name", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemDef::run", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 374, "signature": "SystemFn run", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemDef::budgetMs", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 375, "signature": "fpx16_16 budgetMs", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemDef::dependsOn", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 376, "signature": "const char* dependsOn", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemSchedule", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 387, "signature": "struct SystemSchedule", "summary": "The computed execution order of one world's systems (M1-SYS-02). A plain value: built by World::scheduleSystems (setup phase), consumed by World::runSystems once per tick, owned by the caller (the game's engine object — M1-HEAD-01). `systemCount` is the world's system count AT SCHEDULING TIME (runSystems' staleness check); `order[i]` is the SystemId of the system that runs i-th (order[0] first, order[systemCount - 1] last; no repeats, dense 1..systemCount).", "budget": null, "experimental": false}, + {"name": "laige::SystemSchedule::systemCount", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 388, "signature": "std::uint32_t systemCount{}", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemSchedule::order", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 389, "signature": "std::uint32_t order[kMaxSystems]{}", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemContext", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 399, "signature": "struct SystemContext", "summary": "The per-tick context handed to a system's run() (FR-1.3; the PRD Appendix B sketch's `ctx`). It names the world the system runs on and delegates iteration to World::each (query.h) — the sketch's `ctx.each<...>()`. The context is built per system per tick by the scheduler (M1-SYS-02); until then games and tests build it directly. It is a non-owning view (the world owns the storage): never store it across ticks.", "budget": null, "experimental": false}, + {"name": "laige::SystemContext::world", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 401, "signature": "World& world", "summary": "The world the system runs on (one world, one owner thread).", "budget": null, "experimental": false}, + {"name": "laige::SystemContext::each", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 410, "signature": "template [[nodiscard]] Status each(F&& fn, Acc... acc) noexcept", "summary": "Delegate to World::each(fn, Read/Write tags...) on the same world: identical semantics, visit order, iteration-legality behavior, and Status results (query.h). No allocation. The definition is out-of-line in entity.h (the World home): World is incomplete here, and the delegated call is checked at instantiation — which needs the complete World.", "budget": "O(kMaxArchetypes * N) scan + one visit per matching entity; no allocation.", "experimental": false}, + {"name": "laige::Io", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 427, "signature": "template struct Io", "summary": "One declared component I/O entry of a system (FR-1.3): component type T and its declared access. Pass one value of this tag type per component in the Io<...> pack of World::registerSystem:", "budget": null, "experimental": false}, + {"name": "laige::Io::access", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 430, "signature": "static constexpr Access access = kAccess", "summary": "The declared access of the entry (Read or Write).", "budget": null, "experimental": false}, + {"name": "laige::SystemInfo", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 440, "signature": "struct SystemInfo", "summary": "A registered system's snapshot (a plain value; the M1-SYS-02 scheduler and the M1-PROF-01 profiler pull it). `def` is the value copy of the registered def; `id` is the world's SystemId. The declared component I/O list (FR-1.3) is stored as the disjoint read/write id sets and read back through the membership queries: the documented list order is ascending ComponentTypeId (component.h id contract — a pure function of the sets).", "budget": null, "experimental": false}, + {"name": "laige::SystemInfo::def", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 441, "signature": "SystemDef def{}", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemInfo::id", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 442, "signature": "SystemId id{}", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::SystemInfo::declaresRead", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 446, "signature": "[[nodiscard]] bool declaresRead(ComponentTypeId componentId) const noexcept", "summary": "True when the system declares `componentId` for reading.", "budget": "O(1); no allocation.", "experimental": false}, + {"name": "laige::SystemInfo::declaresWrite", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 450, "signature": "[[nodiscard]] bool declaresWrite(ComponentTypeId componentId) const noexcept", "summary": "True when the system declares `componentId` for writing.", "budget": "O(1); no allocation.", "experimental": false}, + {"name": "LAIGE_SYSTEM", "kind": "macro", "header": "src/laige-sim/include/laige/sim/system.h", "line": 485, "signature": "#define LAIGE_SYSTEM(Name, budget_ms, ...)", "summary": "Declare a system (FR-1.3): at namespace scope, directly above the plain system function's definition. `Name` is both the C++ function name and the system's registration name (stringified); `budget_ms` is the declared per-tick time budget in milliseconds (a numeric literal, e.g. 1 or 0.5 — converted to the exact fpx16_16 once, at program start); the optional trailing `Dep...` names are the depends_on spec (M1-SYS-02): the registration names of the systems `Name` must run after, stringified verbatim into the def's `dependsOn` field (comma-separated, as written). Expands to the function declaration plus", "budget": null, "experimental": false} ] } diff --git a/src/laige-sim/include/laige/sim/system.h b/src/laige-sim/include/laige/sim/system.h index 74d3230..3d0d5e4 100644 --- a/src/laige-sim/include/laige/sim/system.h +++ b/src/laige-sim/include/laige/sim/system.h @@ -73,9 +73,14 @@ // def's `dependsOn` spec ("Spawner"). They may name systems // registered LATER in the same world (forward dependencies: the spec // is validated against the world at scheduling time, not at -// registration). The empty list (no trailing names) is "" = no -// dependencies. See the "Scheduler" section below for the spec format -// and the ordering semantics. +// registration). The empty list (no trailing names) is the no- +// dependencies spec: "" where the preprocessor stringizes the empty +// variadic pack (GCC/Clang, [cpp.stringize]) and nullptr where it +// leaves the pack empty and the 4th initializer value-initializes +// (MSVC 2022) — both are the documented no-deps form +// (SystemDef::dependsOn), and parseDepSpec treats them alike. See +// the "Scheduler" section below for the spec format and the ordering +// semantics. // // --------------------------------------------------------------------------- // Registration and the id contract (FR-1.3, component.h precedent) diff --git a/tests/laige-sim/scheduler_tests.cpp b/tests/laige-sim/scheduler_tests.cpp index be85385..66ac788 100644 --- a/tests/laige-sim/scheduler_tests.cpp +++ b/tests/laige-sim/scheduler_tests.cpp @@ -352,8 +352,16 @@ std::uint64_t fnv1a64(const std::uint64_t* values, std::size_t n) { // --------------------------------------------------------------------------- TEST(SystemScheduler, MacroCarriesDependsOnSpec) { - // No trailing names: the spec is "" (no dependencies). - EXPECT_STREQ(SchNoop_Def.dependsOn, ""); + // No trailing names: the no-deps spec — nullptr or "" (both are the + // documented forms; SystemDef::dependsOn, parseDepSpec). The exact + // representation is a preprocessor property of an EMPTY variadic + // pack: "" where #__VA_ARGS__ stringizes it (GCC/Clang, + // [cpp.stringize]), nullptr where the pack stays empty and the 4th + // initializer value-initializes (MSVC 2022). The engine treats both + // alike, so the test pins the contract, not the representation + // (platform-dependent assertions do not travel across P0 platforms). + ASSERT_TRUE(SchNoop_Def.dependsOn == nullptr || + SchNoop_Def.dependsOn[0] == '\0'); // One trailing name, stringified verbatim: EXPECT_STREQ(SchDepOnNoop_Def.dependsOn, "SchNoop"); // Two trailing names: the comma-separated list as written.