For gameplay code, float behavior is part of the contract. This policy explains why and how to maintain native float32 fidelity.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/banteg/crimson/llms.txt
Use this file to discover all available pages before exploring further.
Default Rule
Keep Decompiled Float Constants
Keep decompiled float constants when they influence simulation outcomes.
0.6000000238418579 to 0.6 in parity-critical code unless parity evidence shows the change is behavior-neutral.
Why This Matters
Small float deltas can reorder branch decisions and collision outcomes, then amplify into RNG drift and deterministic divergence over long runs.Real Example: Session 18 & 19
Fromdocs/frida/differential-sessions/:
- Session 18: Decompile-order
angle_approachfix moved first mismatch from frame 7722 to 7756 - Session 19: Tighter float32 spill behavior in creature heading/tau-boundary handling cleared the remaining
quest_1_8capture
These sessions demonstrate that even minor float precision changes cause measurable gameplay divergence.
Native x87 Usage
The original executable uses x87 floating-point in gameplay hot paths, but persistent state is float32.Concrete Findings
CRT Startup Sets 53-bit Precision
CRT Startup Sets 53-bit Precision
The CRT explicitly configures x87 to 53-bit mode (
PC_53):_start→crt_run_initializers→FUN_00460cb8→sub_4636e7→sub_469e81(0x10000, 0x30000)arg1 & 0x30000 == 0x10000sets control word precision bits to0x200(53-bit mode)- IDA names:
__setdefaultprecision→__controlfp
analysis/binary_ninja/raw/crimsonland.exe.bndb_hlil.txt lines 83734, 83777, 91988-91993Trig Uses x87 with float10 Temporaries
Trig Uses x87 with float10 Temporaries
Trig and atan paths use x87 transcendental ops:Binary Ninja shows
fconvert.t(...) (widen to extended precision).Results Spilled Back to float32
Results Spilled Back to float32
Extended precision results are explicitly spilled to Binary Ninja shows
float storage:fconvert.s(...) (narrow to float32).What This Means (Non-Handwavy)
Not 80-bit All The Way
The game is not “everything in 80-bit all the way down.” Startup precision is
PC_53, and authoritative state is float32.Two Failure Modes
Parity errors come from:
- Wrong trig/atan evaluation around branch boundaries
- Wrong placement of float32 spills (too early or too late)
Rewrite Math Model (Current)
Deterministic gameplay math follows three rules:Use f32 as Domain Type
Use
f32 as the gameplay-domain type (positions, headings, timers, speeds, projectile scalar state) unless a value is truly boundary-only.Widen Only at Boundaries
Widen only at boundaries (replay decode, serialization, diagnostics), then immediately spill back to
f32 at the native-equivalent store point.Zig Runtime Implementation
Canonical native-style math helpers live in the Zig runtime.Location
crimson-zig/src/runtime/native_math.zig
Constants
Native constants are sourced from exactf32 bit patterns:
Trig Functions
Native-style trig uses extended precision when available:- Use
sinl/cosl/atan2lwhenc_longdoubleis wider thanf64 - Otherwise use
sin/cos/atan2 - Freestanding builds fall back to
std.math
Spill Helper
Explicit float32 truncation:float.
Angle Helpers
Shared angle helpers encode decompile/native corner cases:Math Dispatch
crimson-zig/src/runtime/math.zig dispatches by type:
Python/Zig Interop
Python code calls Zig native math via bindings:Allowed Normalization
Literal simplification is acceptable when all of the following are true:Non-Deterministic or Presentation-Only
The path is non-deterministic or presentation-only (not gameplay simulation).
Differential Evidence Shows No Change
Differential evidence (capture + verifier) shows no behavior change.
Example
Implementation Guidance
Single Shared Helper Source
Single Shared Helper Source
Keep Domain State in f32
Keep Domain State in f32
Keep gameplay-domain state in
f32; avoid repeated f64 -> f32 -> f64 churn inside hot loops.Explicit Spill Points
Explicit Spill Points
Use explicit spill points (
roundF32) where native would store to float:Prefer Captures Over Intuition
Prefer Captures Over Intuition
Prefer parity captures and focused traces over intuitive “cleanup”.
Document Deviations
Document Deviations
Document any intentional float deviation in differential session docs:
docs/frida/differential-sessions.mddocs/frida/differential-sessions/session-*.md
Common Mistakes
Normalizing Constants
Wrong Operation Order
Missing Spill Points
Using Wrong Math Functions
Verification
Verify float parity using differential testing:Resources
Differential Sessions
docs/frida/differential-sessions/ - Investigation logs documenting float-related parity fixesFloat Expression Map
docs/rewrite/float-expression-precision-map.md - Expression-level lookup table with decompile anchorsZig Native Math
crimson-zig/src/runtime/native_math.zig - Canonical native-style math implementationParity Workflow
Full parity-first development workflow
Next Steps
Parity Workflow
Learn the parity-first development approach
Testing Guide
Write deterministic float parity tests
Code Style
Follow project coding standards
Verification Process
Complete verification workflow