Float expression precision map (decompile-derived)¶
Purpose: fast lookup for Python parity work. This map classifies common expression families by the precision model they need in deterministic gameplay paths.
Use this with float parity policy.
Precision classes¶
F32_STORE: expression result should be stored/kept as float32 state.X87_INTERMEDIATE_THEN_F32: compute with x87-like widened intermediate, then spill to float32 at the native-equivalent store point.F64_BOUNDARY_ONLY: f64/double is acceptable at formatting/IO boundaries, but should not drive gameplay state evolution.
Runtime assumption for this map:
- Gameplay follows the PC24 policy: Direct3D device
creation uses flags 0x20, without D3DCREATE_FPU_PRESERVE (0x02). The
earlier CRT startup PC_53 setting therefore does not describe gameplay.
- “X87 intermediate” does not mean that ordinary arithmetic stays wide.
Add, subtract, multiply, divide, and square root follow PC24 rounding at each
operation. Preserve extended transcendental results until the following
arithmetic operation or store, as described in the policy.
- The frame witness matrix in
results.json
demonstrates 126 frame-index differences between PC24 and PC64 on the same
stored inputs. Port regressions use the PC24 observations.
Decompile-wide signal counts (for confidence)¶
Historical Ghidra whole-view scan:
float10occurrences:1496(float10)fcos/(float10)fsin/(float10)fpatan:270- explicit
(float)(...)casts:996 - explicit
(double)casts:31(mostly CRT/vararg boundaries)
Historical IDA whole-view scan:
fcos/fsin/fpatan/fsqrt:62/60/15/36fld/fstp/fmul/fadd/fsub/fcomp/fnstsw:1094/897/704/363/259/296/296
Expression lookup table¶
| ID | Expression family | Class | Native pattern (decompile) | Required model | Example anchor |
|---|---|---|---|---|---|
E01 |
Heading from delta (atan2 path) |
X87_INTERMEDIATE_THEN_F32 |
(float10)fpatan((float10)dy,(float10)dx) then (float) store |
Widen for atan2, spill to f32 immediately when writing heading |
player_update @ 0x004136b0 |
E02 |
Direction from heading (cos) |
X87_INTERMEDIATE_THEN_F32 |
(float10)fcos((float10)heading - (float10)1.5707964) |
Round the half-pi subtraction at PC24, keep fcos wide, then round every following fmul (move_speed, scalar or 3.1415927f - diff, 25.0f/7.957747f) before the move_dx store; frame_dt * move_dx rounds again |
player_update @ 0x00414152 |
E03 |
Direction from heading (sin) |
X87_INTERMEDIATE_THEN_F32 |
(float10)fsin((float10)heading - (float10)1.5707964) |
Same as E02 |
player_update @ 0x00414173 |
E04 |
Creature steering trig+spill | X87_INTERMEDIATE_THEN_F32 |
fpatan target heading; fcos/fsin heading-to-vel; (float) assignments |
Keep transcendental intermediates widened; persist heading/vel_* as f32 |
creature_update_all @ 0x00426220 |
E05 |
Projectile angle step trig | X87_INTERMEDIATE_THEN_F32 |
(float10)angle - half_pi; fcos/fsin in per-substep motion |
Widen trig/intermediate multiply chain; spill to f32 locals/state at native stores |
projectile_update @ 0x00420b90 |
E06 |
Length / distance compare | X87_INTERMEDIATE_THEN_F32 (branch-sensitive) |
SQRT(dx*dx + dy*dy) < threshold |
Preserve op order and compare boundary behavior; treat as branch-sensitive | creature_update_all @ 0x00426220 |
E07 |
Vec2 length helper return | F32_STORE |
return SQRT(v[1]*v[1] + *v * *v); |
API return is f32; avoid carrying widened value past the return boundary |
vec2_length @ 0x00417660 |
E08 |
Normalize via reciprocal sqrt | X87_INTERMEDIATE_THEN_F32 |
(1.0 / SQRT(a)) * ... assigned into vector components |
Keep rsqrt/sqrt intermediate precision; write normalized components as f32 |
d3dx_c_vec2_normalize @ 0x00455587 |
E09 |
Angle wrapping/approach arithmetic | F32_STORE |
add/sub/abs/clamp around tau; no transcendental |
Keep exact operation order and f32 store cadence; no need for boundary f64 promotion |
angle_approach @ 0x0041f430 |
E10 |
Timers/cooldowns/speeds state updates | F32_STORE |
state = state +/- frame_dt * k, clamp to bounds |
Keep state as f32; avoid accidental long-lived f64 accumulators in gameplay paths |
player_update @ 0x004136b0 |
E11 |
Float-int conversion hotspots (__ftol family) |
X87_INTERMEDIATE_THEN_F32 (int boundary) |
explicit __ftol() calls in movement/effects code |
Route through native-compatible helper; treat conversion semantics as parity-sensitive | projectile_update @ 0x00420b90 |
E12 |
Formatting/vararg conversion | F64_BOUNDARY_ONLY |
crt_sprintf(..., (double)f32_value) |
double here is boundary formatting ABI, not simulation precision policy |
mods_menu_update @ 0x0040e9a0 |
E13 |
Creature atlas frame selection | F32_STORE (int boundary) |
__ftol(phase + 0.5f) or __ftol((float)(base + 15) - lifecycle) |
Round the add/subtract at PC24 before truncating; select the lifecycle branch directly | creature_render_type @ 0x00418b60 |
E14 |
Heading-derived 60-unit aim point | X87_INTERMEDIATE_THEN_F32 |
Native keeps cosine wide through scaling but stores sine first | Subtract native half-pi at PC24; multiply wide cosine and stored sine at PC24; round each position add | player_update @ 0x004136b0 |
E15 |
Conventional projectile trail corners | F32_STORE at PC24 |
(camera + origin/position) +/- velocity * width_factor |
Round input fields, width product, camera sums, and corner additions/subtractions before viewport scaling | projectile_render @ 0x004230e5..0x00423663 |
E16 |
Sharpshooter laser corners | X87_INTERMEDIATE_THEN_F32 |
Offset near heading by 0.150915f, scale trig products by 15/512/1.1, then form camera-relative corners |
Preserve wide trig through multiplication except the stored far-end sine; round PC24 arithmetic and complete corners before per-axis viewport scaling | projectile_render laser geometry starting at 0x00422d63 |
E17 |
CRT pow (__CIpow) |
X87_INTERMEDIATE_THEN_F32 |
fyl2x, frndint, fsub, f2xm1, fadd 1.0, fscale under the caller's control word |
Keep fyl2x/f2xm1 wide; round the fraction fsub and the + 1.0 at PC24 (x87_pc24_crt_pow) |
level threshold in gameplay_update_and_render @ 0x0040afae |
E14 is an exception to treating both direction components as stored floats.
The original-image witnesses in
results.json pin the
asymmetry and held-turn ordering. Python is tested against 1,050 point and 240
turn witnesses; see that evidence package for modeled boundaries and limits.
E15 is checked against the original executable's submitted corner words in
conventional-corner-rounding-2026-09-11. Python's
bullet_trail_corners preserves the game's PC24 arithmetic before applying
the rewrite's viewport scale. The evidence package separately documents the
native wider-register/store asymmetries under diagnostic PC64; those are not
a reason to use binary64 arithmetic for the Python gameplay renderer.
E16 uses the native 15-unit angled near endpoint and 2.2-unit total width.
The witnesses in laser-trig-rounding-2026-09-11
check Python's actual submitted corners against native PC24 values at four
viewport scales and in both perk-ownership modes. Native Grim2D color-setter
execution also pins red near vertices, black far vertices, and truncating
alpha-byte conversion. These are CPU submission checks, not GPU pixel proof.
Binary Ninja cross-check pattern¶
Binary Ninja HLIL repeatedly shows the same model:
fconvert.t(...)before transcendental/extended arithmeticfconvert.s(...)at storage boundaries
Example: gameplay_update_and_render at 0x00409e1d widens the projectile
heading through fpatan before the float32 call boundary.
Practical implementation guidance¶
- Python gameplay domain:
- Keep long-lived runtime state in
np.float32/explicitf32wrappers. - For
E01..E06/E08/E11, run native-style helper path then spill tof32at the native-equivalent assignment point. - Do not keep replay-decoded
f64values live in gameplay state.
Fast lookup recipes¶
- Resolve the function and current tool snapshots:
uv run scripts/analysis_view.py show player_update- Search live Binary Ninja HLIL:
bn search text 'fconvert\\.(t|s)|fpatan|fcos|fsin' --view hlil --target crimsonland.exe.bndb- Save a reusable function artifact:
bn bundle function player_update --target crimsonland.exe.bndb --out /tmp/player_update.json