diff --git a/src/laige-sim/include/laige/sim/particles.h b/src/laige-sim/include/laige/sim/particles.h index e327268..51ef579 100644 --- a/src/laige-sim/include/laige/sim/particles.h +++ b/src/laige-sim/include/laige/sim/particles.h @@ -507,6 +507,19 @@ 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)}; + } else { + 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 +527,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..dbddedd 100644 --- a/tests/laige-sim/particles_tests.cpp +++ b/tests/laige-sim/particles_tests.cpp @@ -234,6 +234,20 @@ 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)}; + } else { + return static_cast(static_cast(v)); + } +} + template typename M_::Scalar oracleSample(laige::Prng& oracle, typename M_::Scalar lo, @@ -241,8 +255,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); }