Skip to content

A ported fixed-point function differs from the original in a few cases out of thousands ​

Symptoms ​

A port of a 16.16 routine agrees with the original almost always, and differs in a handful of cases when both are run on many inputs. In POD the gate test (FUN_0042a1f0, which decides whether a car crossed a lap line or checkpoint) differed in 15 of 20,000 moves between a floor model and a round-to-nearest model, all with a point within a hair of the gate's plane.

Cause ​

The routine converts a float sum to an integer with the "magic number" trick: fadd of a large constant, fstp to a double, then read the double's low word. In POD's dot product (FUN_004db530) the constant at 0x4fa000 is 1.5 × 2^68, so the low word holds the sum divided by 65536: a 16.16 result. The division's rounding is the FPU's current mode, and with the x87 at 64-bit precision it rounds twice: each fadd to a step of 32 (the constant is added to the first product, then the others one by one), then the store to a double to a step of 65536 (observed in the instructions; measured below).

Evidence ​

The original run under Unicorn (measure/race_rules.py) with a small stub that loads a chosen control word (fninit; fldcw [cw]; call FUNC; hlt), on the track's own gates and points placed near each plane. Each control word needs its own model: under 0x037f (64-bit) each sum rounds to a step of 32 first; under 0x027f (53-bit) straight to a step of 65536; under 0x0f7f the sums are floored. Each model matched all 20,000 cases under its own word. Which word the game uses had to be measured in the running game: POD races with 0x027f (pod-physics' step log reads it each step), not the 0x037f its C library's start-up leaves. On POD Gold's gate test the words differ in 11 (0x027f against 0x037f) and 20 (against truncation) of 20,000 cases.

What to do ​

Model the conversion exactly in integers: keep the magic constant, add each product in the order the code does, round each fadd to the precision the control word sets, then to the double's step, and subtract the constant (crates/pod-race/src/gate.rs, dot). Then read the control word in the running game (a hook or a step log; the C library's start-up word is not enough: POD's changes after it), and measure the function under that word. Keep the measurement as the port's test.

pe32_emu's emu.fpu_modes(FUNC, cases, cws=(0x037f, 0x027f, 0x0f7f)) runs a routine under each control word and lists the cases whose result differs; call(..., fpu_cw=...) runs it under one.

Tried ​

  • A floor model (from an earlier, wrong reading that the game ran truncated): 15 of 20,000 wrong under round to nearest.
  • A 64-bit model (from the start-up's fninit): right under 0x037f, but the game races with 0x027f; corrected on Gold.
  • Rounding the exact sum once: matched these 20,000 cases too, but differs from the x87 wherever the 64-bit step of a partial sum lands on a tie; the sequential model is the instruction's own.

Code: Apache 2.0. Content: CC0 1.0. No game material: bring your own copy.