- Status: Accepted
- Date: 2026-09-10
- Decider: Project owner (roadmap step M0-DEC-02)
- Refs: PRD §10.3, §12.3, FR-1.4, FR-1.5, FR-3.3, NFR-8.3, AC-10.3; AGENTS.md ARCH-010, CORE-004, PERF-006; roadmap S-7, G-R8
NFR-8.3 / FR-1.4 require deterministic-mode simulation state to be bit-identical across all P0 platforms and compilers, verified in CI every merge. P0 spans two ISAs (x86-64: Windows/Linux/Intel macOS; arm64: Apple Silicon/macOS/Linux) and three compiler families (MSVC, g++, clang++). Lockstep (AC-10.3) and authoritative MMO simulation (PRD §12) require bit-identity across arbitrary client/server hardware. ARCH-010 requires the determinism scope to be stated explicitly.
Why the two candidate strategies differ:
- Pinned IEEE float is bit-identical between x86-64 SSE2 and arm64 NEON
only if every compiler emits the same operation sequence. FMA contraction
(
a*b+cfusing into one operation with a single rounding), vectorized reassociation of sums, and per-compiler default flags silently break bit-identity. "Pinned float" is therefore a guarantee that must be defended forever: pinned flags (no FMA in sim translation units,-ffp-contract=offor equivalent), a ban on floating-point intrinsics in sim code, and re-audits at every toolchain upgrade. PRD §10.3's own wording ("x86-64floatsemantics pinned") does not honestly cover the arm64 P0 platforms. - Q16.16 fixed-point integer arithmetic is bit-identical on every ISA/compiler by language standard (C++20 two's complement; no fused integer operations). Determinism is guaranteed by construction, not by flag archaeology — the established approach of lockstep engines. Costs: multiply/divide run on 64-bit intermediates (~2× float cost in tight loops, budgeted and measured in M1) and the range/precision of ±32768 units at 1/65536 resolution — ample for tile-based 2D worlds (~65k sub-tile steps, ±16k tile world bounds). Bonus for the MMO goal: the PRD §12.3 bandwidth degradation ladder ("coarser precision — fixed-point bit-count reduction") falls out naturally: fewer fraction bits can be emitted to distant clients.
Deterministic simulation code uses one math interface, SimMath (S-7 /
G-R8: engine math ops are the only math allowed in deterministic systems).
SimMath has two backends, selected once per engine/zone init from game
config (determinism.math):
| Backend | Type | Determinism scope (ARCH-010) | Used for |
|---|---|---|---|
fpx16_16 |
Q16.16 in int32_t storage, int64_t intermediates |
Bit-exact across build, platform, ISA, and compiler (guaranteed by the language standard) | Default deterministic backend; required for lockstep and authoritative MMO |
fp32_pinned |
IEEE float with pinned semantics (documented per-compiler flag set: no FMA in sim translation units, -ffp-contract=off/equivalent, no reassociation, no floating-point intrinsics) |
Bit-exact across runs of the same build on the same platform/ISA; cross-ISA not promised until the CI detcheck matrix proves it — any desyncing pair is declared unsupported for this backend | Opt-in for single-player / non-lockstep games that want IEEE float semantics |
Mechanics:
- One op surface (add/sub/mul/div, compare, clamp, lerp, normalize, length, …), two implementations. Game and system code is written once against the interface — there is no per-game double code path.
- Backend selection is compile-time dispatch (one template instantiation per backend, factory-selected at init) — no per-call indirection in the hot loop (PERF-006).
- The backend id is part of replay identity: replay = inputs + seed + math backend + config hash. Cross-backend replays are not bit-exact and are not supported (documented).
- The PRD §12.3 bandwidth degradation ladder (fixed-point bit-count
reduction) is defined on the
fpx16_16backend;fp32_pinnedzones do not participate in the ladder (they are single-ISA by definition). - Both backends must meet the same sim budget (PRD §8.1: 10k entities @ 60 Hz ≤ 3 ms); measured in M1 (CORE-001).
- Determinism test matrix (M1-DET): both backends × the CI build matrix;
fp32_pinnedadditionally carries a per-platform support list generated from detcheck results.
- Q16.16 only (the roadmap register default) — strongest determinism story with one code path. Rejected in favor of flexibility: single-player games keep access to IEEE float semantics, and the fixed-point range/precision ceiling (±32768 units, 1/65536 resolution) is avoided for large or fine-grained worlds.
- Pinned float only — faster with finer precision, but cross-ISA determinism becomes a flag-pinning promise that requires re-audits at every toolchain upgrade and does not honestly cover arm64 P0 platforms.
- SimMath (chosen) — costs two backends to implement, budget, and detcheck. Mitigations: shared op surface, shared test matrix, config gate, trait enforcement (G-R8). This ADR is the explicit record of the extra backend against CORE-004 (smallest complete change) — a deliberate, owner-approved flexibility trade.
- IEEE-754/FMA semantics analysis (Context above); C++20 two's-complement integer identity across ISAs.
- PRD §10.3 ("fixed-point is the default for lockstep/MMO"), §12.3 (degradation ladder), AC-10.3 (lockstep bit-determinism), NFR-8.3 (cross-platform bit-identity verified in CI).
- No performance measurement yet: both backends' budgets are established in M1 (this is a design decision, not a measured performance claim).
- M0-CORE-03 implements the SimMath interface + the
fp32_pinnedbackend and the pinned flag set; M0-CORE-04 implementsfpx16_16as the default backend. - M1-DET adds SimMath trait enforcement (G-R8), the detcheck matrix for both backends, and the per-backend platform support lists.
- Config surface:
determinism.math("fixed_point_16_16" | "float_pinned_32"), P0 via FR-1.5. - The deterministic test matrix doubles; both backends are subject to the §8.1 budget gate.
- If
fp32_pinnedfails any cross-ISA detcheck pair, that pair is declared unsupported for it (automated by CI). If it misses the §8.1 sim budget on mid-range P0 hardware, it is dropped (PRD revision). - If
fpx16_16misses the §8.1 sim budget, revisit precision (e.g. Q12.20) or the default choice (PRD revision). - Revisit if a third backend (e.g. Q8.24 for larger worlds) is requested — SimMath is designed so that adding one is an additive change.