Seven End Turns from ref-turn2.sav on VM140, six control words, whole-state checksum on every post-turn autosave. 53-bit and 64-bit x87 give byte-identical state across all 35,394 leaves, so an x64/SSE port computing in IEEE double has NO double-rounding budget to preserve and floats=bits is free. Two settings do move state, each reproduced on a repeat run: 0x007f (24-bit) Sys[112 "Gamma Cephei"]/Pop2/PopG/PopC 540000000 -> 540000002 0x1a7f (round-up) Flt[34 "Beta Fleet"]/Pos/.[0] and /Pos/.[2], 1 ULP each So the port must hold intermediates at 53 bits and use round-to-nearest -- both SSE defaults, now measured rather than assumed, each with a named regression witness. The briefed triple was under-powered: 0x027f is 53-bit (it differs from 0x127f only in bit 12, infinity control, ignored since the 387) and 0x137f is 64-bit, not a rounding change. Run as written all three come back identical, and that would have "proved" something false on both axes that matter. Evidence the forced word actually held: read-back at each force site plus 38 independent in-pipeline hook samples per run spanning turn phases 4, 6 and 8, all reading the forced value. Mars::Application::Run calls _controlfp(0x50000,0x3070300) at 0x0089f606 every frame, which is 0x127f, so forcing at StrategyClient::EndTurn is wiped before the turn runs; StrategyServer::BeginProcessTurn is the point that works. Also re-confirms the End-Turn determinism oracle on engine cef889e: bb4fd9ac... / 978041ac... unchanged. New tools: verify/fpu-cw/cw_census.py, verify/fpu-cw/trace_bitdiff.py (the latter exists because under a forced 24-bit word the CRT's own %g rendering degrades, so trace text is not a valid comparison surface).
55 lines
3 KiB
Markdown
55 lines
3 KiB
Markdown
# x87 precision-sensitivity experiment — artefacts (lane F, 2026-09-08)
|
|
|
|
Read `findings/subsystems/fpu-precision-sensitivity.md` for the result and the argument.
|
|
This directory is the evidence.
|
|
|
|
Seven End-Turn runs from `C:\SOTS\SavedGames\ref-turn2.sav` (`ab4ac2d7…`) on VM140, shim build
|
|
`fpucw-cef889e-20260908T0803Z`. The hook set, trace path and flush policy are identical in every
|
|
run; the ONLY difference between the `shim.cfg` files is the `fpu.force=` line.
|
|
|
|
```
|
|
run-off/ nothing forced -- oracle re-confirmation + the game's own control-word timeline
|
|
run-027f/ fpu.force=0x027f 53-bit, nearest (the MSVC CRT default; NOT 24-bit)
|
|
run-127f/ fpu.force=0x127f 53-bit, nearest -- the CONTROL: what the game sets itself
|
|
run-137f/ fpu.force=0x137f 64-bit extended, nearest
|
|
run-007f/ fpu.force=0x007f 24-bit single, nearest
|
|
run-1a7f/ fpu.force=0x1a7f 53-bit, round-toward-+infinity
|
|
run-007f-rep/ repeat of run-007f -- reproducibility of the divergence
|
|
run-1a7f-rep/ repeat of run-1a7f
|
|
```
|
|
|
|
Each run directory holds `(Autosave).sav` (post-turn, the thing under test),
|
|
`(Autosave EndTurn).sav` (pre-turn, the run-to-run control -- `bb4fd9ac…` in every run),
|
|
`shim.log` (the force / read-back / per-tick-sampler timeline), `shim.cfg` and the gzipped
|
|
trace. The two `-rep` runs keep only the saves and the log.
|
|
|
|
Derived, regenerate with the commands below:
|
|
|
|
* `cw-census.txt` — the `fpu_cw` every hooked call observed, per run. This is the evidence that
|
|
the forced word held for the *duration* of the turn: 38 samples per run spanning turn phases
|
|
4, 6 and 8, all reading the forced value.
|
|
* `state-checksum-diffs.txt` — every run's post-turn autosave against `run-127f`.
|
|
* `trace-bitdiff.txt` — bit-exact trace comparison. Note the trap it exists for: under a forced
|
|
24-bit control word the CRT's own `%g` rendering degrades, so trace *text* is not a valid
|
|
comparison surface.
|
|
|
|
```sh
|
|
uv run python3 verify/fpu-cw/cw_census.py verify/results/fpu-cw/run-*/shim.trace.jsonl.gz
|
|
uv run python3 verify/state-checksum/state_checksum.py \
|
|
"verify/results/fpu-cw/run-127f/(Autosave).sav" "verify/results/fpu-cw/run-007f/(Autosave).sav"
|
|
uv run python3 verify/fpu-cw/trace_bitdiff.py \
|
|
verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz verify/results/fpu-cw/run-1a7f/shim.trace.jsonl.gz
|
|
```
|
|
|
|
Headline hashes (sha256 of the file, gzip container included -- MTIME is 0, so this is valid):
|
|
|
|
```
|
|
bb4fd9ac89f41e3bc0db2af08b18ce83417521ac4bcee695fc9fa6ce16e30948 (Autosave EndTurn).sav ALL SEVEN RUNS
|
|
978041acd168b56ed8eb3f5e42e78d5e70eae6e6517d75e659a5eb7ca3d60921 (Autosave).sav stock / 0x027f / 0x127f / 0x137f
|
|
ba2435beb17265af3abddb58cd26e926d42b39b358ef6d0c76f9897386174918 (Autosave).sav 0x007f (both runs)
|
|
e48e25fa8425a8a1d174efe3e8a553153447f11f8f91c5fda25bd7db3a26cca7 (Autosave).sav 0x1a7f (both runs)
|
|
```
|
|
|
|
The first two lines are also the **determinism-oracle re-confirmation** on engine `cef889e`
|
|
(lane M's movement fix included): unchanged, as it must be — that fix touches `ours`, never the
|
|
original.
|