From fea0226af3c32656b83c741947311bdacbfc7a45 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 7 Oct 2026 17:40:39 +0200 Subject: [PATCH 1/2] [M2-PART-01 fix] Explicit integer-to-Scalar conversion for the macOS P0 toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The macOS Intel (macos-14, AppleClang 15) lane of the M2-PART-01 merge run (37640282584) failed to build particles_tests.cpp: no matching conversion for static_cast from 'const std::uint32_t' to 'laige::fpx16_16' Root cause: static_cast(u32) with Scalar = fpx16_16 relies on the C++17 single-member-aggregate parenthesized-initialization corner (the cast behaves like fpx16_16(u32), aggregate-initializing raw from the int32 conversion). g++ 16, Linux clang 22, MSVC 2022, and AppleClang 16 (macos-15) all accept it; AppleClang 15 (the macOS-14 P0 toolchain) rejects it. The macOS arm64 lane passing in the same run while Intel failed confirms the compiler-version split. Fix: replace the aggregate cast with an explicit per-backend construction (a private ParticleSystem::fromInt and the test's oracle mirror sampleFromInt): the fpx branch uses the aggregate brace init (the house idiom, e.g. fpx16_16.h itself), the fp32 branch a plain int32->Scalar static_cast. The values are identical (exact on both backends: u < 2^24), so the determinism contract and the pinned KAT hashes are unchanged (verified: the particles-determinism fnv1a lines are bit-identical before/after). No public API change (private member only — laige-api.json regenerated and unchanged: 1410 symbols / 41 headers; api-real-tree green). All six local trees warning-clean + full ctest green (115/115, 115/115, 104/104, 115/115, 112/112, 112/112); lints green. --- src/laige-sim/include/laige/sim/particles.h | 15 +++++++++++++-- tests/laige-sim/particles_tests.cpp | 18 ++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/laige-sim/include/laige/sim/particles.h b/src/laige-sim/include/laige/sim/particles.h index e327268..0e353c7 100644 --- a/src/laige-sim/include/laige/sim/particles.h +++ b/src/laige-sim/include/laige/sim/particles.h @@ -507,6 +507,18 @@ class ParticleSystem { ++spawnedTotal_; } + // An integer v < 2^24 as a Scalar — exact on both backends (the fpx + // raw units; the fp32 24-bit mantissa represents every integer < 2^24 + // exactly). Not a static_cast: the fpx16_16 aggregate's single-member + // parenthesized cast is rejected by the older AppleClang of the + // macOS P0 toolchain. + static Scalar fromInt(std::uint32_t v) noexcept { + if constexpr (std::is_same_v) { + return Scalar{static_cast(v)}; + } + return static_cast(static_cast(v)); + } + // One uniform scalar in [lo, hi]: one 24-bit Prng tap resolved to // [0, 1) (u / 2^24 — the tap resolution, the header's // determinism section) and one SimMath lerp (the ADR 0002 rounding @@ -514,8 +526,7 @@ class ParticleSystem { // beyond the documented draw). Scalar sampleScalar(Scalar lo, Scalar hi) noexcept { const std::uint32_t u = rng_.next_range(0, kParticleSampleDenominator); - const Scalar t = - M::div(static_cast(u), static_cast(kParticleSampleDenominator)); + const Scalar t = M::div(fromInt(u), fromInt(kParticleSampleDenominator)); return M::lerp(lo, hi, t); } diff --git a/tests/laige-sim/particles_tests.cpp b/tests/laige-sim/particles_tests.cpp index 97b700f..19098a9 100644 --- a/tests/laige-sim/particles_tests.cpp +++ b/tests/laige-sim/particles_tests.cpp @@ -234,6 +234,19 @@ std::uint64_t particlesStateHash(const ParticleSystem& sys) { // One uniform scalar exactly as the engine samples it (the test-side // oracle for the 4-draw contract). +// Integer → Scalar, exact on both backends (v < 2^24) — mirrors the +// engine's ParticleSystem::fromInt (the oracle must follow the +// engine's sample path; not a static_cast — the older AppleClang of +// the macOS P0 toolchain rejects the fpx16_16 aggregate cast). +template +typename M_::Scalar sampleFromInt(std::uint32_t v) { + using Scalar = typename M_::Scalar; + if constexpr (std::is_same_v) { + return laige::fpx16_16{static_cast(v)}; + } + return static_cast(static_cast(v)); +} + template typename M_::Scalar oracleSample(laige::Prng& oracle, typename M_::Scalar lo, @@ -241,8 +254,9 @@ typename M_::Scalar oracleSample(laige::Prng& oracle, using M = M_; const std::uint32_t u = oracle.next_range(0, laige::kParticleSampleDenominator); - const auto t = M::div(static_cast(u), - static_cast(laige::kParticleSampleDenominator)); + const auto t = + M::div(sampleFromInt(u), + sampleFromInt(laige::kParticleSampleDenominator)); return M::lerp(lo, hi, t); } From 1d7bea518f2b3df4c0127621405d09a0a45331a9 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 7 Oct 2026 18:10:58 +0200 Subject: [PATCH 2/2] [M2-PART-01 fix] Add the missing else to the if-constexpr branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first fix left the fp32 branch of fromInt/sampleFromInt as a plain statement AFTER the if-constexpr (no else), so it was still instantiated for the fpx backend — and it carries the same int32->fpx16_16 cast AppleClang 15 rejects (the PR-lane macOS Intel job 112876659688 failed on exactly that line). With the else the discarded branch is not instantiated: the fpx backend only ever instantiates the aggregate brace init, the fp32 backend only the int32->Scalar standard conversion. KAT unchanged (bit-identical particles-determinism lines); all six local trees warning-clean + full ctest green (115/115, 115/115, 104/104, 115/115, 112/112, 112/112); lints green; laige-api.json unchanged (private member only). --- src/laige-sim/include/laige/sim/particles.h | 3 ++- tests/laige-sim/particles_tests.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/laige-sim/include/laige/sim/particles.h b/src/laige-sim/include/laige/sim/particles.h index 0e353c7..51ef579 100644 --- a/src/laige-sim/include/laige/sim/particles.h +++ b/src/laige-sim/include/laige/sim/particles.h @@ -515,8 +515,9 @@ class ParticleSystem { static Scalar fromInt(std::uint32_t v) noexcept { if constexpr (std::is_same_v) { return Scalar{static_cast(v)}; + } else { + return static_cast(static_cast(v)); } - return static_cast(static_cast(v)); } // One uniform scalar in [lo, hi]: one 24-bit Prng tap resolved to diff --git a/tests/laige-sim/particles_tests.cpp b/tests/laige-sim/particles_tests.cpp index 19098a9..dbddedd 100644 --- a/tests/laige-sim/particles_tests.cpp +++ b/tests/laige-sim/particles_tests.cpp @@ -243,8 +243,9 @@ typename M_::Scalar sampleFromInt(std::uint32_t v) { using Scalar = typename M_::Scalar; if constexpr (std::is_same_v) { return laige::fpx16_16{static_cast(v)}; + } else { + return static_cast(static_cast(v)); } - return static_cast(static_cast(v)); } template