lane F: x87 precision sensitivity measured; STATE_CHECKSUM 3.5 closed

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).
This commit is contained in:
alex 2026-09-08 05:07:53 -04:00
parent d523d27f50
commit 849c5069fc
44 changed files with 1292 additions and 21 deletions

View file

@ -0,0 +1,207 @@
# x87 precision sensitivity of the turn pipeline — measured (2026-09-08, lane F)
Question (`verify/state-checksum/STATE_CHECKSUM.md` §3.5): *does any value the turn pipeline
produces actually **depend** on the x87 control word?* Until now every save on this host was
made at the game's own `fpu_cw = 0x127f`, so "the x64/SSE port must match bit-for-bit" was
**policy, not measurement**.
It is now measured. Seven End-Turn runs from the same `ref-turn2.sav`, six distinct control-word
settings, whole-state checksum on each post-turn autosave.
## Verdict
| forced `fpu_cw` | precision | rounding | `(Autosave).sav` | vs baseline |
|---|---|---|---|---|
| *(stock, none forced)* | 53-bit | nearest | `978041ac…` 67,219 B | — (baseline) |
| `0x027f` | 53-bit | nearest | `978041ac…` 67,219 B | **identical** |
| `0x127f` | 53-bit | nearest | `978041ac…` 67,219 B | **identical** |
| `0x137f` | **64-bit (extended)** | nearest | `978041ac…` 67,219 B | **identical** |
| `0x007f` | **24-bit (single)** | nearest | `ba2435be…` 67,222 B | **2 leaves** |
| `0x1a7f` | 53-bit | **up (+∞)** | `e48e25fa…` 67,219 B | **2 leaves** |
The pre-turn `(Autosave EndTurn).sav` is `bb4fd9ac…` in **all seven** runs — the state at the
moment End Turn was pressed is upstream of every forced value, which is the run-to-run control.
**For the reimplementation:**
1. **The x87's 64-bit intermediates are not load-bearing.** 53-bit and 64-bit produce the same
state, leaf for leaf, across 35,394 leaves. A port that computes in IEEE `double` reproduces
this turn exactly, and there is **no double-rounding budget to preserve** — the strict
`floats=bits` policy costs an x64/SSE port nothing on this axis. §3.5 closes.
2. **Narrowing intermediates to `float` does change state.** At 24-bit the home system's
civilian population lands two people higher. So the port must hold in `double` exactly where
the original holds in an x87 register, and narrow exactly where the original stores to a
`dword` — which is precisely the discipline lane M documented for `MoveFleet`
(`movefleet-position-rounding.md`: five separate float32 narrowings, products and sums held
at 53 bits in between). Getting a narrowing point wrong is not a rounding nicety; it moves
saved integers.
3. **Round-to-nearest is required.** Round-toward-+∞ moves fleet 34's position by 1 ULP in two
of three components. SSE defaults to round-to-nearest, so this is satisfied for free — but it
is now a measured requirement rather than an assumption.
### The two named precision-sensitive witnesses
Everything the experiment found, in full — this is the complete list for this turn:
```
0x007f (24-bit) vs baseline:
/Sim/systems/Sys[112 "Gamma Cephei"]/Pop2/PopG/PopC : 540000000 -> 540000002
/Summary/Checksum : -769976634 -> -769976632 (derived)
0x1a7f (round-up) vs baseline:
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[0] : -10.563499450683594 -> -10.563498497009277 [1 ulp]
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[2] : 1.564733624458313 -> 1.5647337436676025 [1 ulp]
```
Both reproduce exactly on a repeat run (`run-007f-rep`, `run-1a7f-rep`) — same root hash, same
leaves. `Summary/Checksum` moves by the sum of the changed bytes, consistent with the additive
checksum already characterised in `determinism-oracle.md`; treat it as derived, never as state.
The two witnesses are on **different axes and different subsystems**: the population integer is
precision-sensitive but not rounding-sensitive, and the fleet position is rounding-sensitive but
not precision-sensitive. That is a useful shape — it says an SSE port can go wrong in two
independent ways, and each has a cheap regression witness on turn 2 of `ref-turn2.sav`.
## The evidence that the control word actually held
This is what makes the result mean anything: an experiment where the setting silently reverted
would produce identical saves and a confident, false "nothing depends on precision".
**1. Read-back at the point of forcing.** Each force site logs observed-before → requested →
read-back-after. Every one reports `OK`, e.g. (`run-007f/shim.log`):
```
fpu: FORCE at StrategyServer::BeginProcessTurn: observed=0x127f 53bit-double/nearest
-> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
fpu: sample at StrategyServer::ProcessTurn: cw=0x007f 24bit-single/nearest
```
`StrategyServer::ProcessTurn` is a **sample-only** hook — it never writes the word. Its reading
is independent evidence that the value forced at the turn gate survived into the simulation.
**2. 38 independent in-pipeline samples per run.** Every template hook already snapshots the
control word it finds. In each of the five forced runs, all 38 samples read the forced value and
nothing else (`verify/results/fpu-cw/cw-census.txt`):
```
run-007f: ServerSystem::ProcessTurn cw=0x007f x28 call_id 1110..1137
StrategyServer::MoveFleet cw=0x007f x7 call_id 1103..1109
TechTree::ProcessResearch cw=0x007f x3 call_id 1140..1144
ALL SAMPLES: 0x007f x38
```
Those three hooks sit in turn phases **4 (movement), 6 (colony) and 8 (per-player research)** —
so the samples span the pipeline from its first mover to its last research pass, in call-id
order, with no gap and no other value. The stock run reads `0x127f` x38 in the same places.
**3. The forcing is a single write per turn, never re-applied inside the pipeline.** Re-forcing
at each hook would have guaranteed the samples without proving anything.
## Why the forcing point matters: the game re-arms the word every frame
`Mars::Application::Run` (`0x0089f5b0`) calls **`_controlfp(0x50000, 0x3070300)` once per frame**
at `0x0089f606`, between `OnUpdate` (vft+0x18) and `OnTick` (vft+0x1c). In MSVC's abstract
encoding that is `_PC_53 | _IC_AFFINE` under the mask `_MCW_DN|_MCW_IC|_MCW_PC|_MCW_RC`, which
is exactly the x87 word **`0x127f`**. There is no literal `0x127f` anywhere in the image
(`find-constant-uses` → 0 hits); the value is synthesised by the CRT. `_controlfp` is also
called from `Mars::Application::Initialize` (`0x008a10c6`) and from `FUN_00451920`
(`0x00451929`, one caller, a data reference).
Two consequences, both visible in the logs:
* Forcing at `StrategyClient::EndTurn` **does not survive**. The client's End Turn only raises
`SETurnEndPending` and sends `SNMEndTurn`; the server's `OnMessage` → `BeginProcessTurn` →
`ProcessTurn` runs on a **later frame**, and the per-frame `_controlfp` wipes the setting in
between. Every log shows `FORCE at StrategyClient::EndTurn … readback=0x007f` at *t*, then
`FORCE at StrategyServer::BeginProcessTurn: observed=0x127f` about a second later.
`BeginProcessTurn` is the forcing point that works, because `BeginProcessTurn` and
`ProcessTurn` are called from the same `OnMessage`, inside one `OnUpdate`, with no
`_controlfp` between them.
* The per-tick sampler never logs a change, and that is **expected, not evidence**: `_controlfp`
runs immediately before `OnTick`, so `OnTick` always observes a freshly re-armed `0x127f`.
The sampler's real contribution is the negative one — it proves the process never sits in a
forced state outside the turn, so nothing leaks between runs.
This also retro-explains why every previous lane measured `fpu_cw = 0x127f` at every hook: the
main loop guarantees it, frame by frame.
## The lane brief's three values do not span three FPU modes
The experiment as specified in §3.5 and in the lane brief would have been under-powered, and
this is worth recording so nobody re-derives it:
| value | actually is | brief called it |
|---|---|---|
| `0x027f` | 53-bit, nearest (MSVC CRT default) | "24-bit mantissa (single precision)" |
| `0x127f` | 53-bit, nearest (+ infinity-control bit) | "53-bit — the game's own setting" ✓ |
| `0x137f` | **64-bit extended**, nearest | "53-bit but a different rounding mode" |
`0x027f` and `0x127f` differ **only in bit 12** (infinity control), which the 387 and every later
x87 ignore — they are the same arithmetic. So the briefed triple spans two modes (53-bit and
64-bit), not three, tests single precision **not at all**, and tests rounding **not at all**.
Run literally, all three come back identical (they do — see the table) and the honest conclusion
from them alone would have been "no precision sensitivity", which is false for rounding and
unproven for single precision.
The control-word layout: bits 0–5 exception masks, **bits 8–9 precision control**
(`00`=24-bit, `10`=53-bit, `11`=64-bit), **bits 10–11 rounding control**
(`00`=nearest, `01`=down, `10`=up, `11`=truncate), bit 12 infinity control (ignored).
So the genuine probes are `0x007f` (24-bit) and `0x1a7f` (53-bit, round-up), and those are the
two runs that found something.
## Trap: the trace's own float rendering is precision-sensitive
Under a forced 24-bit control word, **the shim's trace text is not a valid comparison surface.**
The emitter prints an f32 with `%.9g`, and the CRT's digit generation is itself x87 arithmetic,
so the same float32 renders differently:
```
127f run: "z":{"t":"f32","v":1.56473362} both are float32 0x3fc84931
007f run: "z":{"t":"f32","v":1.5647336} — the value did not move, the printf did
127f run: "suit":{"t":"f32","v":1.65671718} both are float32 0x3fd40f4f
007f run: "suit":{"t":"f32","v":1.65671721}
```
A text diff of the two traces reports dozens of these as divergences. `verify/fpu-cw/trace_bitdiff.py`
re-quantises every float to its IEEE bit pattern before comparing, and then the 24-bit run shows
**zero** differences in any hooked call — the population change happens in a byte no hook
declares. This is the `STATE_CHECKSUM.md` §1 thesis with a fresh example: the per-function
verdict was clean and the state had still moved; only the whole-state checksum saw it.
Any future `tracecmp`-style comparison across control words must compare bits, not text.
## Incidental: guard spans differ between byte-identical runs
`ServerSystem::ProcessTurn`'s undeclared-write spans are not stable run to run even when the
saves are byte-identical (`trace-bitdiff.txt`, `127f vs off`): the same three writes at
`system+420..432` are reported once as one 12-byte span and once as three 3-byte spans, and once
shifted by a byte. Harmless for a verdict (the *set* of touched bytes is the same) but it means
guard-span text is not a stable comparison surface either. Not chased — outside this lane.
## Method
* VM140, `ref-turn2.sav` (`ab4ac2d7…`), click path per `determinism-oracle.md`. Shim build
`fpucw-cef889e-20260908T0803Z`, hook set held **identical** across all seven runs — the only
difference between `shim.cfg.fpu*` files is the `fpu.force` line.
* `SavedGames\` is reset to the same four files before every run, so the Load dialog rows never
move and the click path is constant.
* Forcing implemented in `sots-engine/src/shim/fpu_force.cpp` (branch `wip/fpucw`):
register-transparent asm stubs, same pattern as the `Application::Initialize` hook, so the
`[unverified]` prototypes of the turn-gate functions are never relied on.
shim.cfg keys `fpu.force=<cw>|off` and `fpu.sample_ticks=on|off`.
* Evidence: `verify/results/fpu-cw/run-<tag>/` — both autosaves, `shim.log`, `shim.cfg`,
gzipped trace; plus `cw-census.txt`, `state-checksum-diffs.txt`, `trace-bitdiff.txt`.
* Tools: `verify/fpu-cw/cw_census.py` (per-hook control-word census),
`verify/fpu-cw/trace_bitdiff.py` (bit-exact trace comparison).
## Coverage and limits
* The whole-state checksum **proves** coverage on every run: 609,080 bytes rebuilt byte-for-byte
from 35,394 leaves. "Identical" here means no leaf anywhere moved, not "no declared region
moved".
* **One turn, one save, one galaxy.** Turn 2→3 of `ref-turn2.sav`: 28 systems, 3 owned, 7
`MoveFleet` calls of which one fleet actually moves, 3 research passes, no combat, no Zuul, no
plague/rebellion/terraform. The two witnesses are what *this* turn exposes; a turn with combat
or more movers could expose more. The result "53-bit == 64-bit" is the one that generalises
best, because it held across all 35,394 leaves and all 38 in-pipeline samples.
* Nothing here says the *original* is x87-free — only that its results on this turn do not move
between 53-bit and 64-bit intermediates, which is the property an SSE port needs.

View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Census of the `fpu_cw` field the shim's template hooks record on every call.
Each hook snapshots the x87 control word as it finds it, so a run's trace carries independent
samples of the word from *inside* the turn pipeline -- phase 4 (MoveFleet), phase 6
(ServerSystem::ProcessTurn) and phase 8 (ComputeBudget / ProcessResearch). That is the evidence
that a control word forced at the turn gate actually held for the duration of the turn, rather
than being silently restored partway through (which would produce identical saves and a
completely false "nothing depends on precision" conclusion).
Usage: cw_census.py <shim.trace.jsonl> [...]
"""
import gzip
import json
import sys
from collections import Counter, defaultdict
def _open(path):
op = gzip.open if path.endswith(".gz") else open
return op(path, "rt", encoding="utf-8", errors="replace")
def census(path):
per_hook = defaultdict(Counter)
first_last = {}
n = 0
with _open(path) as f:
for line in f:
line = line.strip()
if not line.startswith("{"):
continue
try:
rec = json.loads(line)
except json.JSONDecodeError:
continue
hook = rec.get("hook")
if not hook:
continue
n += 1
for arg in rec.get("args", []) or []:
if arg.get("n") == "fpu_cw":
cw = int(arg["v"])
per_hook[hook][cw] += 1
key = (hook, cw)
if key not in first_last:
first_last[key] = [rec.get("call_id"), rec.get("call_id")]
else:
first_last[key][1] = rec.get("call_id")
return n, per_hook, first_last
def main():
for path in sys.argv[1:]:
n, per_hook, first_last = census(path)
print(f"== {path} ({n} hook records)")
total = Counter()
for hook in sorted(per_hook):
for cw, count in sorted(per_hook[hook].items()):
lo, hi = first_last[(hook, cw)]
total[cw] += count
print(f" {hook:<42} cw=0x{cw:04x} x{count:<5} call_id {lo}..{hi}")
if not per_hook:
print(" (no hook recorded an fpu_cw field)")
else:
vals = ", ".join(f"0x{cw:04x} x{c}" for cw, c in sorted(total.items()))
print(f" ALL SAMPLES: {vals}")
print()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Bit-exact comparison of two shim traces of the same turn, hook by hook.
Why not just diff the text: **the trace's own float rendering is not a stable comparison
surface when the x87 control word has been forced.** The emitter prints an f32 with `%.9g`,
and the CRT's digit generation is itself done in x87 arithmetic, so under `fpu_cw = 0x007f`
(24-bit) the *same* float32 renders as `1.5647336` instead of `1.56473362`. Comparing the text
of a 24-bit run against a 53-bit run reports dozens of "differences" that are all the printf,
not the simulation. Every float here is therefore re-quantised to its IEEE bit pattern before
comparison, and per-process noise (pointers, thread id, timestamps, call ids, the `fpu_cw`
field that is the independent variable) is scrubbed.
Usage: trace_bitdiff.py <a.jsonl[.gz]> <b.jsonl[.gz]> [hook ...]
"""
import gzip
import json
import re
import struct
import sys
PTR = re.compile(r"^0x[0-9a-f]{8}$")
# Only hooks that fire *inside* the turn pipeline: ServerPlayer::ComputeBudget also runs once
# per UI frame, so its call count depends on how long the session sat at a menu and is not
# comparable between runs. Name it explicitly on the command line if you want it anyway.
DEFAULT_HOOKS = (
"Game::ServerSystem::ProcessTurn",
"Game::StrategyServer::MoveFleet",
"Game::TechTree::ProcessResearch",
"Game::ServerPlayer::OnTechResearched",
)
NOISE = ("ts", "call_id", "thread")
def f32_bits(v):
return struct.unpack("<I", struct.pack("<f", float(v)))[0]
def f64_bits(v):
return struct.unpack("<Q", struct.pack("<d", float(v)))[0]
def norm(o):
if isinstance(o, dict):
t = o.get("t")
if t == "ptr":
return {"t": "ptr", "n": o.get("n")}
if t == "f32":
return {"t": "f32", "n": o.get("n"), "bits": f32_bits(o["v"])}
if t == "f64":
return {"t": "f64", "n": o.get("n"), "bits": f64_bits(o["v"])}
return {k: norm(v) for k, v in o.items() if k not in NOISE}
if isinstance(o, list):
return [norm(x) for x in o]
if isinstance(o, str) and PTR.match(o):
return "<ptr>"
return o
def _open(path):
op = gzip.open if path.endswith(".gz") else open
return op(path, "rt", encoding="utf-8", errors="replace")
def load(path, hook):
out = []
needle = '"hook":"%s"' % hook
with _open(path) as f:
for line in f:
if needle not in line:
continue
rec = norm(json.loads(line))
rec["args"] = [a for a in rec.get("args", []) if a.get("n") != "fpu_cw"]
for side in (rec.get("side") or {}).values():
if not isinstance(side, dict):
continue
for snap in side.values():
if isinstance(snap, dict) and isinstance(snap.get("v"), dict):
snap["v"].pop("fpu_cw", None)
out.append(rec)
return out
def leafdiff(a, b, path=""):
out = []
if isinstance(a, dict) and isinstance(b, dict):
for k in sorted(set(a) | set(b)):
out += leafdiff(a.get(k), b.get(k), f"{path}.{k}")
elif isinstance(a, list) and isinstance(b, list) and len(a) == len(b):
for i, (x, y) in enumerate(zip(a, b)):
out += leafdiff(x, y, f"{path}[{i}]")
elif a != b:
out.append((path, a, b))
return out
def main():
pa, pb = sys.argv[1], sys.argv[2]
hooks = sys.argv[3:] or list(DEFAULT_HOOKS)
print(f"A {pa}\nB {pb}")
total = 0
for hook in hooks:
a, b = load(pa, hook), load(pb, hook)
if not a and not b:
continue
if len(a) != len(b):
print(f"{hook}: CALL COUNT DIFFERS {len(a)} vs {len(b)}")
total += 1
continue
diffs = [(i, p, x, y) for i, (ra, rb) in enumerate(zip(a, b)) for p, x, y in leafdiff(ra, rb)]
total += len(diffs)
print(f"{hook}: {len(diffs)} leaf difference(s) over {len(a)} record(s)")
for i, p, x, y in diffs:
print(f" rec[{i}] {p}: {x} -> {y}")
print(f"TOTAL: {total}")
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,55 @@
# 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.

View file

@ -0,0 +1,36 @@
== verify/results/fpu-cw/run-007f/shim.trace.jsonl.gz (2212 hook records)
Game::ServerSystem::ProcessTurn cw=0x007f x28 call_id 1110..1137
Game::StrategyServer::MoveFleet cw=0x007f x7 call_id 1103..1109
Game::TechTree::ProcessResearch cw=0x007f x3 call_id 1140..1144
ALL SAMPLES: 0x007f x38
== verify/results/fpu-cw/run-027f/shim.trace.jsonl.gz (2231 hook records)
Game::ServerSystem::ProcessTurn cw=0x027f x28 call_id 1134..1161
Game::StrategyServer::MoveFleet cw=0x027f x7 call_id 1127..1133
Game::TechTree::ProcessResearch cw=0x027f x3 call_id 1164..1168
ALL SAMPLES: 0x027f x38
== verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz (2317 hook records)
Game::ServerSystem::ProcessTurn cw=0x127f x28 call_id 1195..1222
Game::StrategyServer::MoveFleet cw=0x127f x7 call_id 1188..1194
Game::TechTree::ProcessResearch cw=0x127f x3 call_id 1225..1229
ALL SAMPLES: 0x127f x38
== verify/results/fpu-cw/run-137f/shim.trace.jsonl.gz (2280 hook records)
Game::ServerSystem::ProcessTurn cw=0x137f x28 call_id 1152..1179
Game::StrategyServer::MoveFleet cw=0x137f x7 call_id 1145..1151
Game::TechTree::ProcessResearch cw=0x137f x3 call_id 1182..1186
ALL SAMPLES: 0x137f x38
== verify/results/fpu-cw/run-1a7f/shim.trace.jsonl.gz (2304 hook records)
Game::ServerSystem::ProcessTurn cw=0x1a7f x28 call_id 1187..1214
Game::StrategyServer::MoveFleet cw=0x1a7f x7 call_id 1180..1186
Game::TechTree::ProcessResearch cw=0x1a7f x3 call_id 1217..1221
ALL SAMPLES: 0x1a7f x38
== verify/results/fpu-cw/run-off/shim.trace.jsonl.gz (2449 hook records)
Game::ServerSystem::ProcessTurn cw=0x127f x28 call_id 1152..1179
Game::StrategyServer::MoveFleet cw=0x127f x7 call_id 1145..1151
Game::TechTree::ProcessResearch cw=0x127f x3 call_id 1182..1186
ALL SAMPLES: 0x127f x38

Binary file not shown.

View file

@ -0,0 +1,61 @@
04:47:30.428 [tid 2160] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:47:30.428 [tid 2160] exe: C:\SOTS\Sword of the Stars.exe
04:47:30.428 [tid 2160] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=7180 shim=696a0000
04:47:30.428 [tid 2160] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:47:30.428 [tid 2160] config: hooks=trace
04:47:30.428 [tid 2160] config: hook.Shim::SelfTest::Fill=off
04:47:30.428 [tid 2160] config: hook.Mars::GlobalConsts::LoadFile=off
04:47:30.428 [tid 2160] config: hook.Game::WeaponDictionary::Init=off
04:47:30.428 [tid 2160] config: hook.Game::SectionDictionary::SectionDictionary=off
04:47:30.428 [tid 2160] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:47:30.428 [tid 2160] config: hook.Game::TechTree::ProcessResearch=trace
04:47:30.428 [tid 2160] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:47:30.428 [tid 2160] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:47:30.428 [tid 2160] config: hook.Game::ServerSystem::ProcessTurn=trace
04:47:30.428 [tid 2160] config: hook.Game::StrategyServer::MoveFleet=trace
04:47:30.428 [tid 2160] config: trace.path=C:\SOTS\shim.trace.jsonl
04:47:30.428 [tid 2160] config: trace.inline_max=256
04:47:30.428 [tid 2160] config: trace.flush=always
04:47:30.428 [tid 2160] config: fpu.force=0x007f
04:47:30.428 [tid 2160] config: fpu.sample_ticks=on
04:47:30.490 [tid 2160] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:47:30.490 [tid 2160] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:47:30.490 [tid 2160] hook: MH_Initialize -> MH_OK
04:47:30.490 [tid 2160] hook: MH_CreateHook -> MH_OK (trampoline=017d0fe0)
04:47:30.506 [tid 2160] hook: MH_EnableHook -> MH_OK
04:47:30.506 [tid 2160] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:47:30.506 [tid 2160] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:47:30.506 [tid 2160] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:47:30.506 [tid 2160] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:47:30.506 [tid 2160] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:47:30.506 [tid 2160] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:47:30.506 [tid 2160] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=017d0fc0)
04:47:30.522 [tid 2160] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:47:30.522 [tid 2160] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:47:30.522 [tid 2160] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=017d0fa0)
04:47:30.522 [tid 2160] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:47:30.522 [tid 2160] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=017d0f80)
04:47:30.537 [tid 2160] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:47:30.537 [tid 2160] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=017d0f60)
04:47:30.553 [tid 2160] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:47:30.553 [tid 2160] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=017d0f40)
04:47:30.569 [tid 2160] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:47:30.569 [tid 2160] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:47:30.569 [tid 2160] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x007f sample_ticks=on
04:47:30.569 [tid 2160] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=017d0f20)
04:47:30.584 [tid 2160] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:47:30.584 [tid 2160] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=017d0f00)
04:47:30.600 [tid 2160] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:47:30.600 [tid 2160] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=017d0ee0)
04:47:30.615 [tid 2160] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:47:30.615 [tid 2160] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=017d0ec0)
04:47:30.631 [tid 2160] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:47:30.631 [tid 2160] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:47:30.631 [tid 2160] Application::Initialize called (this=03b08128)
04:47:31.365 [tid 2160] fpu: TICK BASELINE at OnTick (this=03b08128): cw=0x127f 53bit-double/nearest
04:51:34.694 [tid 2160] fpu: FORCE at StrategyClient::EndTurn (this=0e8b0ba0): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:51:37.757 [tid 2160] fpu: FORCE at StrategyClient::EndTurn (this=351a8bf0): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:51:37.788 [tid 2160] fpu: FORCE at StrategyClient::EndTurn (this=351a6788): observed=0x007f 24bit-single/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:51:37.819 [tid 2160] fpu: FORCE at StrategyClient::EndTurn (this=351ab7a0): observed=0x007f 24bit-single/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:51:38.788 [tid 2160] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e8ebc90): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:51:38.788 [tid 2160] fpu: sample at StrategyServer::ProcessTurn (this=0e8ebc90): cw=0x007f 24bit-single/nearest

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,31 @@
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
# 0x007f = 24-bit significand (SINGLE precision), round-to-nearest. The genuine single-precision setting the brief meant by 0x027f, and the run most likely to move a result: it is the strongest available positive control on the whole apparatus.
#
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
# and the flush policy are held fixed so the control word is the only variable across runs.
#
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
# proving it ever held.
fpu.force=0x007f
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
# the value rather than a single claim made at a single instant.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:19:06.590 [tid 7008] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:19:06.590 [tid 7008] exe: C:\SOTS\Sword of the Stars.exe
04:19:06.590 [tid 7008] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=1276 shim=696a0000
04:19:06.590 [tid 7008] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:19:06.606 [tid 7008] config: hooks=trace
04:19:06.606 [tid 7008] config: hook.Shim::SelfTest::Fill=off
04:19:06.606 [tid 7008] config: hook.Mars::GlobalConsts::LoadFile=off
04:19:06.606 [tid 7008] config: hook.Game::WeaponDictionary::Init=off
04:19:06.606 [tid 7008] config: hook.Game::SectionDictionary::SectionDictionary=off
04:19:06.606 [tid 7008] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:19:06.606 [tid 7008] config: hook.Game::TechTree::ProcessResearch=trace
04:19:06.606 [tid 7008] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:19:06.606 [tid 7008] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:19:06.606 [tid 7008] config: hook.Game::ServerSystem::ProcessTurn=trace
04:19:06.606 [tid 7008] config: hook.Game::StrategyServer::MoveFleet=trace
04:19:06.606 [tid 7008] config: trace.path=C:\SOTS\shim.trace.jsonl
04:19:06.606 [tid 7008] config: trace.inline_max=256
04:19:06.606 [tid 7008] config: trace.flush=always
04:19:06.606 [tid 7008] config: fpu.force=0x007f
04:19:06.606 [tid 7008] config: fpu.sample_ticks=on
04:19:06.653 [tid 7008] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:19:06.653 [tid 7008] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:19:06.653 [tid 7008] hook: MH_Initialize -> MH_OK
04:19:06.653 [tid 7008] hook: MH_CreateHook -> MH_OK (trampoline=01740fe0)
04:19:06.668 [tid 7008] hook: MH_EnableHook -> MH_OK
04:19:06.668 [tid 7008] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:19:06.668 [tid 7008] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:19:06.668 [tid 7008] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:19:06.668 [tid 7008] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:19:06.668 [tid 7008] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:19:06.668 [tid 7008] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:19:06.668 [tid 7008] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=01740fc0)
04:19:06.684 [tid 7008] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:19:06.684 [tid 7008] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:19:06.684 [tid 7008] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=01740fa0)
04:19:06.700 [tid 7008] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:19:06.700 [tid 7008] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=01740f80)
04:19:06.715 [tid 7008] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:19:06.715 [tid 7008] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=01740f60)
04:19:06.731 [tid 7008] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:19:06.731 [tid 7008] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=01740f40)
04:19:06.747 [tid 7008] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:19:06.747 [tid 7008] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:19:06.747 [tid 7008] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x007f sample_ticks=on
04:19:06.747 [tid 7008] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=01740f20)
04:19:06.762 [tid 7008] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:19:06.762 [tid 7008] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=01740f00)
04:19:06.762 [tid 7008] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:19:06.762 [tid 7008] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=01740ee0)
04:19:06.778 [tid 7008] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:19:06.778 [tid 7008] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=01740ec0)
04:19:06.793 [tid 7008] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:19:06.793 [tid 7008] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:19:06.809 [tid 7008] Application::Initialize called (this=03528128)
04:19:07.528 [tid 7008] fpu: TICK BASELINE at OnTick (this=03528128): cw=0x127f 53bit-double/nearest
04:23:06.089 [tid 7008] fpu: FORCE at StrategyClient::EndTurn (this=0e2aa720): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:23:09.152 [tid 7008] fpu: FORCE at StrategyClient::EndTurn (this=34b68d80): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:23:09.167 [tid 7008] fpu: FORCE at StrategyClient::EndTurn (this=34b64bf8): observed=0x007f 24bit-single/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:23:09.198 [tid 7008] fpu: FORCE at StrategyClient::EndTurn (this=34b65340): observed=0x007f 24bit-single/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:23:10.152 [tid 7008] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e208b28): observed=0x127f 53bit-double/nearest -> requested=0x007f -> readback=0x007f 24bit-single/nearest OK
04:23:10.152 [tid 7008] fpu: sample at StrategyServer::ProcessTurn (this=0e208b28): cw=0x007f 24bit-single/nearest

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,31 @@
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
# 0x027f = 53-bit significand, round-to-nearest. The MSVC CRT default. Numerically identical to the game's own 0x127f: they differ only in bit 12 (infinity control), which the 387 and later ignore. Named in the lane brief as "24-bit"; it is not.
#
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
# and the flush policy are held fixed so the control word is the only variable across runs.
#
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
# proving it ever held.
fpu.force=0x027f
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
# the value rather than a single claim made at a single instant.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:24:39.698 [tid 7416] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:24:39.698 [tid 7416] exe: C:\SOTS\Sword of the Stars.exe
04:24:39.698 [tid 7416] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=7776 shim=696a0000
04:24:39.698 [tid 7416] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:24:39.698 [tid 7416] config: hooks=trace
04:24:39.698 [tid 7416] config: hook.Shim::SelfTest::Fill=off
04:24:39.698 [tid 7416] config: hook.Mars::GlobalConsts::LoadFile=off
04:24:39.698 [tid 7416] config: hook.Game::WeaponDictionary::Init=off
04:24:39.698 [tid 7416] config: hook.Game::SectionDictionary::SectionDictionary=off
04:24:39.698 [tid 7416] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:24:39.698 [tid 7416] config: hook.Game::TechTree::ProcessResearch=trace
04:24:39.698 [tid 7416] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:24:39.698 [tid 7416] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:24:39.698 [tid 7416] config: hook.Game::ServerSystem::ProcessTurn=trace
04:24:39.698 [tid 7416] config: hook.Game::StrategyServer::MoveFleet=trace
04:24:39.698 [tid 7416] config: trace.path=C:\SOTS\shim.trace.jsonl
04:24:39.698 [tid 7416] config: trace.inline_max=256
04:24:39.698 [tid 7416] config: trace.flush=always
04:24:39.698 [tid 7416] config: fpu.force=0x027f
04:24:39.698 [tid 7416] config: fpu.sample_ticks=on
04:24:39.761 [tid 7416] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:24:39.761 [tid 7416] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:24:39.761 [tid 7416] hook: MH_Initialize -> MH_OK
04:24:39.761 [tid 7416] hook: MH_CreateHook -> MH_OK (trampoline=00be0fe0)
04:24:39.777 [tid 7416] hook: MH_EnableHook -> MH_OK
04:24:39.777 [tid 7416] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:24:39.777 [tid 7416] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:24:39.777 [tid 7416] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:24:39.777 [tid 7416] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:24:39.777 [tid 7416] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:24:39.777 [tid 7416] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:24:39.777 [tid 7416] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=00be0fc0)
04:24:39.777 [tid 7416] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:24:39.777 [tid 7416] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:24:39.777 [tid 7416] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=00be0fa0)
04:24:39.792 [tid 7416] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:24:39.792 [tid 7416] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=00be0f80)
04:24:39.808 [tid 7416] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:24:39.808 [tid 7416] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=00be0f60)
04:24:39.823 [tid 7416] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:24:39.823 [tid 7416] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=00be0f40)
04:24:39.839 [tid 7416] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:24:39.839 [tid 7416] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:24:39.839 [tid 7416] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x027f sample_ticks=on
04:24:39.839 [tid 7416] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=00be0f20)
04:24:39.855 [tid 7416] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:24:39.855 [tid 7416] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=00be0f00)
04:24:39.855 [tid 7416] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:24:39.855 [tid 7416] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=00be0ee0)
04:24:39.870 [tid 7416] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:24:39.870 [tid 7416] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=00be0ec0)
04:24:39.886 [tid 7416] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:24:39.886 [tid 7416] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:24:39.902 [tid 7416] Application::Initialize called (this=038a8128)
04:24:40.636 [tid 7416] fpu: TICK BASELINE at OnTick (this=038a8128): cw=0x127f 53bit-double/nearest
04:28:38.077 [tid 7416] fpu: FORCE at StrategyClient::EndTurn (this=0e5c3758): observed=0x127f 53bit-double/nearest -> requested=0x027f -> readback=0x027f 53bit-double/nearest OK
04:28:41.139 [tid 7416] fpu: FORCE at StrategyClient::EndTurn (this=3572eed0): observed=0x127f 53bit-double/nearest -> requested=0x027f -> readback=0x027f 53bit-double/nearest OK
04:28:41.155 [tid 7416] fpu: FORCE at StrategyClient::EndTurn (this=357354c0): observed=0x027f 53bit-double/nearest -> requested=0x027f -> readback=0x027f 53bit-double/nearest OK
04:28:41.186 [tid 7416] fpu: FORCE at StrategyClient::EndTurn (this=3572f618): observed=0x027f 53bit-double/nearest -> requested=0x027f -> readback=0x027f 53bit-double/nearest OK
04:28:42.155 [tid 7416] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e5b3c40): observed=0x127f 53bit-double/nearest -> requested=0x027f -> readback=0x027f 53bit-double/nearest OK
04:28:42.155 [tid 7416] fpu: sample at StrategyServer::ProcessTurn (this=0e5b3c40): cw=0x027f 53bit-double/nearest

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,31 @@
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
# 0x127f = 53-bit significand, round-to-nearest. THE CONTROL: exactly what the game sets itself, so this run must reproduce the stock oracle hashes.
#
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
# and the flush policy are held fixed so the control word is the only variable across runs.
#
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
# proving it ever held.
fpu.force=0x127f
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
# the value rather than a single claim made at a single instant.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:29:33.858 [tid 4476] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:29:33.858 [tid 4476] exe: C:\SOTS\Sword of the Stars.exe
04:29:33.858 [tid 4476] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=592 shim=696a0000
04:29:33.858 [tid 4476] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:29:33.858 [tid 4476] config: hooks=trace
04:29:33.858 [tid 4476] config: hook.Shim::SelfTest::Fill=off
04:29:33.858 [tid 4476] config: hook.Mars::GlobalConsts::LoadFile=off
04:29:33.858 [tid 4476] config: hook.Game::WeaponDictionary::Init=off
04:29:33.858 [tid 4476] config: hook.Game::SectionDictionary::SectionDictionary=off
04:29:33.858 [tid 4476] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:29:33.858 [tid 4476] config: hook.Game::TechTree::ProcessResearch=trace
04:29:33.858 [tid 4476] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:29:33.858 [tid 4476] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:29:33.858 [tid 4476] config: hook.Game::ServerSystem::ProcessTurn=trace
04:29:33.858 [tid 4476] config: hook.Game::StrategyServer::MoveFleet=trace
04:29:33.858 [tid 4476] config: trace.path=C:\SOTS\shim.trace.jsonl
04:29:33.858 [tid 4476] config: trace.inline_max=256
04:29:33.858 [tid 4476] config: trace.flush=always
04:29:33.858 [tid 4476] config: fpu.force=0x127f
04:29:33.858 [tid 4476] config: fpu.sample_ticks=on
04:29:33.920 [tid 4476] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:29:33.920 [tid 4476] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:29:33.920 [tid 4476] hook: MH_Initialize -> MH_OK
04:29:33.920 [tid 4476] hook: MH_CreateHook -> MH_OK (trampoline=00e60fe0)
04:29:33.936 [tid 4476] hook: MH_EnableHook -> MH_OK
04:29:33.936 [tid 4476] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:29:33.936 [tid 4476] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:29:33.936 [tid 4476] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:29:33.936 [tid 4476] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:29:33.936 [tid 4476] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:29:33.936 [tid 4476] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:29:33.936 [tid 4476] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=00e60fc0)
04:29:33.952 [tid 4476] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:29:33.952 [tid 4476] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:29:33.952 [tid 4476] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=00e60fa0)
04:29:33.967 [tid 4476] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:29:33.967 [tid 4476] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=00e60f80)
04:29:33.983 [tid 4476] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:29:33.983 [tid 4476] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=00e60f60)
04:29:33.998 [tid 4476] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:29:33.998 [tid 4476] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=00e60f40)
04:29:34.014 [tid 4476] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:29:34.014 [tid 4476] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:29:34.014 [tid 4476] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x127f sample_ticks=on
04:29:34.014 [tid 4476] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=00e60f20)
04:29:34.014 [tid 4476] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:29:34.014 [tid 4476] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=00e60f00)
04:29:34.030 [tid 4476] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:29:34.030 [tid 4476] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=00e60ee0)
04:29:34.045 [tid 4476] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:29:34.045 [tid 4476] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=00e60ec0)
04:29:34.061 [tid 4476] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:29:34.061 [tid 4476] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:29:34.077 [tid 4476] Application::Initialize called (this=039a8128)
04:29:34.811 [tid 4476] fpu: TICK BASELINE at OnTick (this=039a8128): cw=0x127f 53bit-double/nearest
04:33:32.312 [tid 4476] fpu: FORCE at StrategyClient::EndTurn (this=0e6e6c30): observed=0x127f 53bit-double/nearest -> requested=0x127f -> readback=0x127f 53bit-double/nearest OK
04:33:35.375 [tid 4476] fpu: FORCE at StrategyClient::EndTurn (this=3503a4a0): observed=0x127f 53bit-double/nearest -> requested=0x127f -> readback=0x127f 53bit-double/nearest OK
04:33:35.390 [tid 4476] fpu: FORCE at StrategyClient::EndTurn (this=3503d050): observed=0x127f 53bit-double/nearest -> requested=0x127f -> readback=0x127f 53bit-double/nearest OK
04:33:35.422 [tid 4476] fpu: FORCE at StrategyClient::EndTurn (this=3503ed70): observed=0x127f 53bit-double/nearest -> requested=0x127f -> readback=0x127f 53bit-double/nearest OK
04:33:36.375 [tid 4476] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e705cd8): observed=0x127f 53bit-double/nearest -> requested=0x127f -> readback=0x127f 53bit-double/nearest OK
04:33:36.390 [tid 4476] fpu: sample at StrategyServer::ProcessTurn (this=0e705cd8): cw=0x127f 53bit-double/nearest

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,31 @@
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
# 0x137f = 64-bit significand (x87 extended), round-to-nearest. Named in the lane brief as "53-bit, different rounding"; it is in fact the precision axis, and the one an x64/SSE port cannot reproduce.
#
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
# and the flush policy are held fixed so the control word is the only variable across runs.
#
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
# proving it ever held.
fpu.force=0x137f
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
# the value rather than a single claim made at a single instant.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:34:28.562 [tid 7608] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:34:28.562 [tid 7608] exe: C:\SOTS\Sword of the Stars.exe
04:34:28.562 [tid 7608] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=3296 shim=696a0000
04:34:28.562 [tid 7608] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:34:28.562 [tid 7608] config: hooks=trace
04:34:28.562 [tid 7608] config: hook.Shim::SelfTest::Fill=off
04:34:28.562 [tid 7608] config: hook.Mars::GlobalConsts::LoadFile=off
04:34:28.562 [tid 7608] config: hook.Game::WeaponDictionary::Init=off
04:34:28.562 [tid 7608] config: hook.Game::SectionDictionary::SectionDictionary=off
04:34:28.562 [tid 7608] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:34:28.562 [tid 7608] config: hook.Game::TechTree::ProcessResearch=trace
04:34:28.562 [tid 7608] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:34:28.562 [tid 7608] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:34:28.562 [tid 7608] config: hook.Game::ServerSystem::ProcessTurn=trace
04:34:28.562 [tid 7608] config: hook.Game::StrategyServer::MoveFleet=trace
04:34:28.562 [tid 7608] config: trace.path=C:\SOTS\shim.trace.jsonl
04:34:28.562 [tid 7608] config: trace.inline_max=256
04:34:28.562 [tid 7608] config: trace.flush=always
04:34:28.562 [tid 7608] config: fpu.force=0x137f
04:34:28.562 [tid 7608] config: fpu.sample_ticks=on
04:34:28.640 [tid 7608] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:34:28.640 [tid 7608] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:34:28.640 [tid 7608] hook: MH_Initialize -> MH_OK
04:34:28.640 [tid 7608] hook: MH_CreateHook -> MH_OK (trampoline=003f0fe0)
04:34:28.672 [tid 7608] hook: MH_EnableHook -> MH_OK
04:34:28.672 [tid 7608] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:34:28.672 [tid 7608] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:34:28.672 [tid 7608] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:34:28.672 [tid 7608] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:34:28.672 [tid 7608] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:34:28.672 [tid 7608] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:34:28.672 [tid 7608] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=003f0fc0)
04:34:28.687 [tid 7608] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:34:28.687 [tid 7608] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:34:28.687 [tid 7608] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=003f0fa0)
04:34:28.703 [tid 7608] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:34:28.703 [tid 7608] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=003f0f80)
04:34:28.718 [tid 7608] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:34:28.718 [tid 7608] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=003f0f60)
04:34:28.718 [tid 7608] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:34:28.734 [tid 7608] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=003f0f40)
04:34:28.734 [tid 7608] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:34:28.734 [tid 7608] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:34:28.734 [tid 7608] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x137f sample_ticks=on
04:34:28.734 [tid 7608] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=003f0f20)
04:34:28.750 [tid 7608] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:34:28.750 [tid 7608] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=003f0f00)
04:34:28.765 [tid 7608] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:34:28.765 [tid 7608] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=003f0ee0)
04:34:28.781 [tid 7608] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:34:28.781 [tid 7608] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=003f0ec0)
04:34:28.797 [tid 7608] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:34:28.797 [tid 7608] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:34:28.812 [tid 7608] Application::Initialize called (this=02da8128)
04:34:29.562 [tid 7608] fpu: TICK BASELINE at OnTick (this=02da8128): cw=0x127f 53bit-double/nearest
04:38:23.424 [tid 7608] fpu: FORCE at StrategyClient::EndTurn (this=0e02e4b0): observed=0x127f 53bit-double/nearest -> requested=0x137f -> readback=0x137f 64bit-extended/nearest OK
04:38:26.502 [tid 7608] fpu: FORCE at StrategyClient::EndTurn (this=338724a0): observed=0x127f 53bit-double/nearest -> requested=0x137f -> readback=0x137f 64bit-extended/nearest OK
04:38:26.534 [tid 7608] fpu: FORCE at StrategyClient::EndTurn (this=33876d70): observed=0x137f 64bit-extended/nearest -> requested=0x137f -> readback=0x137f 64bit-extended/nearest OK
04:38:26.565 [tid 7608] fpu: FORCE at StrategyClient::EndTurn (this=33871d58): observed=0x137f 64bit-extended/nearest -> requested=0x137f -> readback=0x137f 64bit-extended/nearest OK
04:38:27.534 [tid 7608] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0df4cbf8): observed=0x127f 53bit-double/nearest -> requested=0x137f -> readback=0x137f 64bit-extended/nearest OK
04:38:27.534 [tid 7608] fpu: sample at StrategyServer::ProcessTurn (this=0df4cbf8): cw=0x137f 64bit-extended/nearest

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,61 @@
04:52:25.866 [tid 8672] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:52:25.866 [tid 8672] exe: C:\SOTS\Sword of the Stars.exe
04:52:25.866 [tid 8672] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=5980 shim=696a0000
04:52:25.866 [tid 8672] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:52:25.866 [tid 8672] config: hooks=trace
04:52:25.866 [tid 8672] config: hook.Shim::SelfTest::Fill=off
04:52:25.866 [tid 8672] config: hook.Mars::GlobalConsts::LoadFile=off
04:52:25.866 [tid 8672] config: hook.Game::WeaponDictionary::Init=off
04:52:25.866 [tid 8672] config: hook.Game::SectionDictionary::SectionDictionary=off
04:52:25.866 [tid 8672] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:52:25.866 [tid 8672] config: hook.Game::TechTree::ProcessResearch=trace
04:52:25.866 [tid 8672] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:52:25.866 [tid 8672] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:52:25.866 [tid 8672] config: hook.Game::ServerSystem::ProcessTurn=trace
04:52:25.866 [tid 8672] config: hook.Game::StrategyServer::MoveFleet=trace
04:52:25.866 [tid 8672] config: trace.path=C:\SOTS\shim.trace.jsonl
04:52:25.866 [tid 8672] config: trace.inline_max=256
04:52:25.866 [tid 8672] config: trace.flush=always
04:52:25.866 [tid 8672] config: fpu.force=0x1a7f
04:52:25.866 [tid 8672] config: fpu.sample_ticks=on
04:52:25.929 [tid 8672] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:52:25.929 [tid 8672] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:52:25.929 [tid 8672] hook: MH_Initialize -> MH_OK
04:52:25.929 [tid 8672] hook: MH_CreateHook -> MH_OK (trampoline=009e0fe0)
04:52:25.944 [tid 8672] hook: MH_EnableHook -> MH_OK
04:52:25.944 [tid 8672] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:52:25.944 [tid 8672] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:52:25.944 [tid 8672] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:52:25.944 [tid 8672] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:52:25.944 [tid 8672] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:52:25.944 [tid 8672] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:52:25.944 [tid 8672] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=009e0fc0)
04:52:25.960 [tid 8672] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:52:25.960 [tid 8672] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:52:25.960 [tid 8672] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=009e0fa0)
04:52:25.976 [tid 8672] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:52:25.976 [tid 8672] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=009e0f80)
04:52:25.976 [tid 8672] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:52:25.976 [tid 8672] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=009e0f60)
04:52:25.991 [tid 8672] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:52:25.991 [tid 8672] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=009e0f40)
04:52:26.007 [tid 8672] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:52:26.007 [tid 8672] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:52:26.007 [tid 8672] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x1a7f sample_ticks=on
04:52:26.007 [tid 8672] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=009e0f20)
04:52:26.023 [tid 8672] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:52:26.023 [tid 8672] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=009e0f00)
04:52:26.038 [tid 8672] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:52:26.038 [tid 8672] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=009e0ee0)
04:52:26.054 [tid 8672] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:52:26.054 [tid 8672] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=009e0ec0)
04:52:26.069 [tid 8672] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:52:26.069 [tid 8672] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:52:26.085 [tid 8672] Application::Initialize called (this=03748128)
04:52:26.851 [tid 8672] fpu: TICK BASELINE at OnTick (this=03748128): cw=0x127f 53bit-double/nearest
04:56:21.683 [tid 8672] fpu: FORCE at StrategyClient::EndTurn (this=0e3f2ee8): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:56:24.746 [tid 8672] fpu: FORCE at StrategyClient::EndTurn (this=354f8628): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:56:24.777 [tid 8672] fpu: FORCE at StrategyClient::EndTurn (this=354f4be8): observed=0x1a7f 53bit-double/up -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:56:24.793 [tid 8672] fpu: FORCE at StrategyClient::EndTurn (this=354f6908): observed=0x1a7f 53bit-double/up -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:56:25.761 [tid 8672] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e3e65a8): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:56:25.761 [tid 8672] fpu: sample at StrategyServer::ProcessTurn (this=0e3e65a8): cw=0x1a7f 53bit-double/up

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,31 @@
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
# 0x1a7f = 53-bit significand, round-toward-+infinity. The genuine rounding-mode change the brief meant by 0x137f.
#
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
# and the flush policy are held fixed so the control word is the only variable across runs.
#
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
# proving it ever held.
fpu.force=0x1a7f
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
# the value rather than a single claim made at a single instant.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:39:19.877 [tid 8716] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:39:19.877 [tid 8716] exe: C:\SOTS\Sword of the Stars.exe
04:39:19.877 [tid 8716] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=8004 shim=696a0000
04:39:19.877 [tid 8716] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:39:19.877 [tid 8716] config: hooks=trace
04:39:19.877 [tid 8716] config: hook.Shim::SelfTest::Fill=off
04:39:19.877 [tid 8716] config: hook.Mars::GlobalConsts::LoadFile=off
04:39:19.877 [tid 8716] config: hook.Game::WeaponDictionary::Init=off
04:39:19.877 [tid 8716] config: hook.Game::SectionDictionary::SectionDictionary=off
04:39:19.877 [tid 8716] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:39:19.877 [tid 8716] config: hook.Game::TechTree::ProcessResearch=trace
04:39:19.877 [tid 8716] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:39:19.877 [tid 8716] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:39:19.877 [tid 8716] config: hook.Game::ServerSystem::ProcessTurn=trace
04:39:19.893 [tid 8716] config: hook.Game::StrategyServer::MoveFleet=trace
04:39:19.893 [tid 8716] config: trace.path=C:\SOTS\shim.trace.jsonl
04:39:19.893 [tid 8716] config: trace.inline_max=256
04:39:19.893 [tid 8716] config: trace.flush=always
04:39:19.893 [tid 8716] config: fpu.force=0x1a7f
04:39:19.893 [tid 8716] config: fpu.sample_ticks=on
04:39:19.940 [tid 8716] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:39:19.940 [tid 8716] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:39:19.940 [tid 8716] hook: MH_Initialize -> MH_OK
04:39:19.940 [tid 8716] hook: MH_CreateHook -> MH_OK (trampoline=009b0fe0)
04:39:19.956 [tid 8716] hook: MH_EnableHook -> MH_OK
04:39:19.956 [tid 8716] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:39:19.956 [tid 8716] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:39:19.956 [tid 8716] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:39:19.956 [tid 8716] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:39:19.956 [tid 8716] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:39:19.956 [tid 8716] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:39:19.956 [tid 8716] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=009b0fc0)
04:39:19.971 [tid 8716] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:39:19.971 [tid 8716] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:39:19.971 [tid 8716] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=009b0fa0)
04:39:19.987 [tid 8716] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:39:19.987 [tid 8716] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=009b0f80)
04:39:20.002 [tid 8716] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:39:20.002 [tid 8716] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=009b0f60)
04:39:20.018 [tid 8716] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:39:20.018 [tid 8716] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=009b0f40)
04:39:20.034 [tid 8716] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:39:20.034 [tid 8716] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:39:20.034 [tid 8716] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=on value=0x1a7f sample_ticks=on
04:39:20.034 [tid 8716] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=009b0f20)
04:39:20.049 [tid 8716] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:39:20.049 [tid 8716] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=009b0f00)
04:39:20.065 [tid 8716] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:39:20.065 [tid 8716] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=009b0ee0)
04:39:20.081 [tid 8716] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:39:20.081 [tid 8716] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=009b0ec0)
04:39:20.096 [tid 8716] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:39:20.096 [tid 8716] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:39:20.112 [tid 8716] Application::Initialize called (this=03258128)
04:39:20.862 [tid 8716] fpu: TICK BASELINE at OnTick (this=03258128): cw=0x127f 53bit-double/nearest
04:43:18.459 [tid 8716] fpu: FORCE at StrategyClient::EndTurn (this=0e1fac88): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:43:21.537 [tid 8716] fpu: FORCE at StrategyClient::EndTurn (this=33ad9ee0): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:43:21.553 [tid 8716] fpu: FORCE at StrategyClient::EndTurn (this=33ad7330): observed=0x1a7f 53bit-double/up -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:43:21.584 [tid 8716] fpu: FORCE at StrategyClient::EndTurn (this=33ad7a78): observed=0x1a7f 53bit-double/up -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:43:22.553 [tid 8716] fpu: FORCE at StrategyServer::BeginProcessTurn (this=0e1878a0): observed=0x127f 53bit-double/nearest -> requested=0x1a7f -> readback=0x1a7f 53bit-double/up OK
04:43:22.553 [tid 8716] fpu: sample at StrategyServer::ProcessTurn (this=0e1878a0): cw=0x1a7f 53bit-double/up

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,24 @@
# Lane F, x87 precision-sensitivity experiment. STOCK: no forcing. Oracle re-confirmation + the game's own control-word timeline.
# Identical to every other shim.cfg.fpu* except the fpu.force line: the hook set, the trace
# path and the flush policy are held fixed so the control word is the only variable.
hooks=trace
hook.Shim::SelfTest::Fill=off
hook.Mars::GlobalConsts::LoadFile=off
hook.Game::WeaponDictionary::Init=off
hook.Game::SectionDictionary::SectionDictionary=off
hook.Game::StrategyServer::ProcessFleetMovement=off
hook.Game::TechTree::ProcessResearch=trace
hook.Game::ServerPlayer::ComputeBudget=trace
hook.Game::ServerPlayer::OnTechResearched=trace
hook.Game::ServerSystem::ProcessTurn=trace
hook.Game::StrategyServer::MoveFleet=trace
trace.path=C:\SOTS\shim.trace.jsonl
trace.inline_max=256
trace.flush=always
# x87 control word forced once at the turn gate (StrategyClient::EndTurn and
# StrategyServer::BeginProcessTurn), never re-forced inside the pipeline.
fpu.force=off
# Per-tick sampler: logs the control word to shim.log whenever it MOVES, so the log carries a
# timeline of the value rather than a single claim.
fpu.sample_ticks=on

View file

@ -0,0 +1,61 @@
04:06:40.848 [tid 2040] ==== sots-engine shim (binkw32 proxy) build fpucw-cef889e-20260908T0803Z ====
04:06:40.848 [tid 2040] exe: C:\SOTS\Sword of the Stars.exe
04:06:40.848 [tid 2040] exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=8848 shim=696a0000
04:06:40.848 [tid 2040] addresses: Source: sots-re ghidra/addresses.json @ f5b37c2, generated 2026-09-08 by tools/gen_addresses.py
04:06:40.848 [tid 2040] config: hooks=trace
04:06:40.848 [tid 2040] config: hook.Shim::SelfTest::Fill=off
04:06:40.848 [tid 2040] config: hook.Mars::GlobalConsts::LoadFile=off
04:06:40.848 [tid 2040] config: hook.Game::WeaponDictionary::Init=off
04:06:40.848 [tid 2040] config: hook.Game::SectionDictionary::SectionDictionary=off
04:06:40.848 [tid 2040] config: hook.Game::StrategyServer::ProcessFleetMovement=off
04:06:40.848 [tid 2040] config: hook.Game::TechTree::ProcessResearch=trace
04:06:40.848 [tid 2040] config: hook.Game::ServerPlayer::ComputeBudget=trace
04:06:40.848 [tid 2040] config: hook.Game::ServerPlayer::OnTechResearched=trace
04:06:40.848 [tid 2040] config: hook.Game::ServerSystem::ProcessTurn=trace
04:06:40.848 [tid 2040] config: hook.Game::StrategyServer::MoveFleet=trace
04:06:40.848 [tid 2040] config: trace.path=C:\SOTS\shim.trace.jsonl
04:06:40.848 [tid 2040] config: trace.inline_max=256
04:06:40.848 [tid 2040] config: trace.flush=always
04:06:40.848 [tid 2040] config: fpu.force=off
04:06:40.848 [tid 2040] config: fpu.sample_ticks=on
04:06:40.910 [tid 2040] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 256, flush always)
04:06:40.910 [tid 2040] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
04:06:40.910 [tid 2040] hook: MH_Initialize -> MH_OK
04:06:40.910 [tid 2040] hook: MH_CreateHook -> MH_OK (trampoline=00940fe0)
04:06:40.926 [tid 2040] hook: MH_EnableHook -> MH_OK
04:06:40.926 [tid 2040] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
04:06:40.926 [tid 2040] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
04:06:40.926 [tid 2040] dict: dictionaries hook ready (crt new=6adc232b delete=6adc0174)
04:06:40.926 [tid 2040] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
04:06:40.926 [tid 2040] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
04:06:40.926 [tid 2040] research: ProcessResearch hook ready (Cost=00ffda00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
04:06:40.926 [tid 2040] hook: Game::TechTree::ProcessResearch rva=0x001876c0 -> va=010076c0 MH_CreateHook -> MH_OK (trampoline=00940fc0)
04:06:40.941 [tid 2040] hook: Game::TechTree::ProcessResearch MH_EnableHook -> MH_OK mode=trace
04:06:40.941 [tid 2040] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
04:06:40.941 [tid 2040] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 -> va=01311790 MH_CreateHook -> MH_OK (trampoline=00940fa0)
04:06:40.957 [tid 2040] hook: Game::ServerPlayer::OnTechResearched MH_EnableHook -> MH_OK mode=trace
04:06:40.957 [tid 2040] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 -> va=012e3030 MH_CreateHook -> MH_OK (trampoline=00940f80)
04:06:40.973 [tid 2040] hook: Game::ServerPlayer::ComputeBudget MH_EnableHook -> MH_OK mode=trace
04:06:40.973 [tid 2040] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 -> va=011d98e0 MH_CreateHook -> MH_OK (trampoline=00940f60)
04:06:40.988 [tid 2040] hook: Game::ServerSystem::ProcessTurn MH_EnableHook -> MH_OK mode=trace
04:06:40.988 [tid 2040] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 -> va=01259ee0 MH_CreateHook -> MH_OK (trampoline=00940f40)
04:06:41.004 [tid 2040] hook: Game::StrategyServer::MoveFleet MH_EnableHook -> MH_OK mode=trace
04:06:41.004 [tid 2040] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
04:06:41.004 [tid 2040] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=off value=0x0000 sample_ticks=on
04:06:41.004 [tid 2040] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=01203be0 MH_CreateHook -> MH_OK (trampoline=00940f20)
04:06:41.020 [tid 2040] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
04:06:41.020 [tid 2040] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=012598e0 MH_CreateHook -> MH_OK (trampoline=00940f00)
04:06:41.035 [tid 2040] fpu: StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK
04:06:41.035 [tid 2040] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=0125c6c0 MH_CreateHook -> MH_OK (trampoline=00940ee0)
04:06:41.051 [tid 2040] fpu: StrategyServer::ProcessTurn MH_EnableHook -> MH_OK
04:06:41.051 [tid 2040] fpu: DemoApp::OnTick rva=0x0049a640 -> va=0131a640 MH_CreateHook -> MH_OK (trampoline=00940ec0)
04:06:41.066 [tid 2040] fpu: DemoApp::OnTick MH_EnableHook -> MH_OK
04:06:41.066 [tid 2040] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
04:06:41.082 [tid 2040] Application::Initialize called (this=03248128)
04:06:41.832 [tid 2040] fpu: TICK BASELINE at OnTick (this=03248128): cw=0x127f 53bit-double/nearest
04:12:04.617 [tid 2040] fpu: sample at StrategyClient::EndTurn (this=0e19c4b8): cw=0x127f 53bit-double/nearest [no fpu.force configured]
04:12:07.695 [tid 2040] fpu: sample at StrategyClient::EndTurn (this=34b76be0): cw=0x127f 53bit-double/nearest [no fpu.force configured]
04:12:07.726 [tid 2040] fpu: sample at StrategyClient::EndTurn (this=34b79048): cw=0x127f 53bit-double/nearest [no fpu.force configured]
04:12:07.757 [tid 2040] fpu: sample at StrategyClient::EndTurn (this=34b77a70): cw=0x127f 53bit-double/nearest [no fpu.force configured]
04:12:08.710 [tid 2040] fpu: sample at StrategyServer::BeginProcessTurn (this=0e1bf750): cw=0x127f 53bit-double/nearest [no fpu.force configured]
04:12:08.710 [tid 2040] fpu: sample at StrategyServer::ProcessTurn (this=0e1bf750): cw=0x127f 53bit-double/nearest

Binary file not shown.

View file

@ -0,0 +1,50 @@
===== state_checksum: run-127f vs run-off (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-off/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
IDENTICAL
===== state_checksum: run-127f vs run-007f (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B 222b03e932f684d5d61f54c155be4f36 ../results/fpu-cw/run-007f/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
DIVERGED: 2 leaf difference(s)
/Summary/Checksum: -769976634 -> -769976632
/Sim/systems/Sys[112 "Gamma Cephei"]/Pop2/PopG/PopC: 540000000 -> 540000002
===== state_checksum: run-127f vs run-027f (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-027f/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
IDENTICAL
===== state_checksum: run-127f vs run-137f (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-137f/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
IDENTICAL
===== state_checksum: run-127f vs run-1a7f (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B e86df4556578665ce1b4cdf7a77d22a7 ../results/fpu-cw/run-1a7f/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
DIVERGED: 2 leaf difference(s)
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[0]: -10.563499450683594 -> -10.563498497009277 [1 ulp]
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[2]: 1.564733624458313 -> 1.5647337436676025 [1 ulp]
===== state_checksum: run-127f vs run-007f-rep (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B 222b03e932f684d5d61f54c155be4f36 ../results/fpu-cw/run-007f-rep/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
DIVERGED: 2 leaf difference(s)
/Summary/Checksum: -769976634 -> -769976632
/Sim/systems/Sys[112 "Gamma Cephei"]/Pop2/PopG/PopC: 540000000 -> 540000002
===== state_checksum: run-127f vs run-1a7f-rep (post-turn autosave) =====
A 5ac4a24197e82de49f3077cd8dd25fad ../results/fpu-cw/run-127f/(Autosave).sav
B e86df4556578665ce1b4cdf7a77d22a7 ../results/fpu-cw/run-1a7f-rep/(Autosave).sav
policy: floats=bits mask=none reader=fe5a6f7cd4ae7910
DIVERGED: 2 leaf difference(s)
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[0]: -10.563499450683594 -> -10.563498497009277 [1 ulp]
/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[2]: 1.564733624458313 -> 1.5647337436676025 [1 ulp]

View file

@ -0,0 +1,50 @@
===== 127f vs off =====
A verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz
B verify/results/fpu-cw/run-off/shim.trace.jsonl.gz
Game::ServerSystem::ProcessTurn: 6 leaf difference(s) over 28 record(s)
rec[4] .coverage.n: 3 -> 1
rec[4] .coverage.undeclared: [{'region': 'system', 'off': 420, 'len': 3}, {'region': 'system', 'off': 424, 'len': 3}, {'region': 'system', 'off': 428, 'len': 3}] -> [{'region': 'system', 'off': 420, 'len': 12}]
rec[15] .coverage.n: 6 -> 4
rec[15] .coverage.undeclared: [{'region': 'system', 'off': 216, 'len': 1}, {'region': 'system', 'off': 368, 'len': 1}, {'region': 'system', 'off': 420, 'len': 3}, {'region': 'system', 'off': 424, 'len': 3}, {'region': 'system', 'off': 428, 'len': 3}, {'region': 'system', 'off': 568, 'len': 4}] -> [{'region': 'system', 'off': 216, 'len': 1}, {'region': 'system', 'off': 368, 'len': 1}, {'region': 'system', 'off': 420, 'len': 12}, {'region': 'system', 'off': 568, 'len': 4}]
rec[16] .coverage.n: 3 -> 1
rec[16] .coverage.undeclared: [{'region': 'system', 'off': 420, 'len': 3}, {'region': 'system', 'off': 424, 'len': 3}, {'region': 'system', 'off': 428, 'len': 3}] -> [{'region': 'system', 'off': 420, 'len': 12}]
Game::StrategyServer::MoveFleet: 0 leaf difference(s) over 7 record(s)
Game::TechTree::ProcessResearch: 0 leaf difference(s) over 3 record(s)
TOTAL: 6
===== 127f vs 007f =====
A verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz
B verify/results/fpu-cw/run-007f/shim.trace.jsonl.gz
Game::ServerSystem::ProcessTurn: 0 leaf difference(s) over 28 record(s)
Game::StrategyServer::MoveFleet: 0 leaf difference(s) over 7 record(s)
Game::TechTree::ProcessResearch: 0 leaf difference(s) over 3 record(s)
TOTAL: 0
===== 127f vs 027f =====
A verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz
B verify/results/fpu-cw/run-027f/shim.trace.jsonl.gz
Game::ServerSystem::ProcessTurn: 2 leaf difference(s) over 28 record(s)
rec[15] .coverage.undeclared[2].len: 3 -> 2
rec[15] .coverage.undeclared[2].off: 420 -> 421
Game::StrategyServer::MoveFleet: 0 leaf difference(s) over 7 record(s)
Game::TechTree::ProcessResearch: 0 leaf difference(s) over 3 record(s)
TOTAL: 2
===== 127f vs 137f =====
A verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz
B verify/results/fpu-cw/run-137f/shim.trace.jsonl.gz
Game::ServerSystem::ProcessTurn: 0 leaf difference(s) over 28 record(s)
Game::StrategyServer::MoveFleet: 0 leaf difference(s) over 7 record(s)
Game::TechTree::ProcessResearch: 0 leaf difference(s) over 3 record(s)
TOTAL: 0
===== 127f vs 1a7f =====
A verify/results/fpu-cw/run-127f/shim.trace.jsonl.gz
B verify/results/fpu-cw/run-1a7f/shim.trace.jsonl.gz
Game::ServerSystem::ProcessTurn: 0 leaf difference(s) over 28 record(s)
Game::StrategyServer::MoveFleet: 2 leaf difference(s) over 7 record(s)
rec[6] .side.pos.after.v.x.bits: 3240690712 -> 3240690711
rec[6] .side.pos.after.v.z.bits: 1070090545 -> 1070090546
Game::TechTree::ProcessResearch: 0 leaf difference(s) over 3 record(s)
TOTAL: 2

View file

@ -267,7 +267,7 @@ glance that all four are genuine simulation changes (tens of thousands of ULPs
growing over a turn), not float-path noise. Had a port produced `[1 ulp]` on these instead, the
same line would say so and the judgement call would be an explicit one.
### 3.5 The x64/SSE budget, and the one thing this cannot settle
### 3.5 The x64/SSE budget — MEASURED 2026-09-08, no budget needed
x87 with PC=53 rounds an intermediate to double and then to float32 — **double rounding**. SSE
`mulss`/`addss` rounds once, directly to float32. For a minority of inputs those differ by one
@ -281,28 +281,43 @@ double rounding (compute in double, narrow explicitly at each store — which is
`fpu_cw = 0x127f` makes the original do) or to accept a documented `--ulps` budget on a named
list of fields.
**What I cannot settle from the host side:** whether the turn pipeline's results depend on the
x87 intermediate precision at all. Every save on this host was produced with `fpu_cw = 0x127f`,
so the corpus is one point, not a curve. It is possible that every value in the turn pipeline
is computed in a way that gives the same float32 under 24-bit, 53-bit and 64-bit precision
control, in which case an SSE port has **no** double-rounding budget to spend and the strict
policy is free forever. It is equally possible that a handful of fields are precision-sensitive.
**SETTLED on the VM, 2026-09-08 (lane F).** It was not settleable from the host side — every
save in the corpus was made at `fpu_cw = 0x127f`, one point rather than a curve — so lane F ran
the End Turn seven times from `ref-turn2.sav` with the shim forcing the control word, and
checksummed the results with this tool. Full write-up and the evidence chain that the setting
actually *held* (38 independent in-pipeline samples per run):
`findings/subsystems/fpu-precision-sensitivity.md`; artefacts in
`verify/results/fpu-cw/`.
**The experiment that would settle it** (VM140, lane R):
| forced `fpu_cw` | precision / rounding | root vs baseline |
|---|---|---|
| stock, `0x027f`, `0x127f`, `0x137f` | 53-bit and **64-bit**, nearest | **identical**, all 35,394 leaves |
| `0x007f` | **24-bit**, nearest | 1 leaf + derived `Summary/Checksum` |
| `0x1a7f` | 53-bit, **round-up** | 2 leaves, 1 ULP each |
**The answer for §3, in one line: 53-bit and 64-bit x87 give the same state, so an SSE port
computing in IEEE `double` has no double-rounding budget to preserve and `floats=bits` is free.**
What is *not* free is the two things the other two runs found:
```
0x007f (24-bit): /Sim/systems/Sys[112 "Gamma Cephei"]/Pop2/PopG/PopC : 540000000 -> 540000002
0x1a7f (round-up):/Sim/fleets/Flt[34 "Beta Fleet"]/Pos/.[0] and /Pos/.[2] : 1 ulp each
```
So the port must (a) hold intermediates at 53 bits and narrow to float32 only where the original
stores to a `dword` — never compute a chain in `float` — and (b) use round-to-nearest. Both are
SSE defaults; they are now measured requirements rather than assumptions, each with a named
regression witness on turn 2 of `ref-turn2.sav`.
**Correction to the experiment as it was written here.** The three values proposed above span
only *two* FPU modes. `0x027f` is 53-bit — it differs from `0x127f` only in bit 12 (infinity
control), which every x87 since the 387 ignores — and `0x137f` is 64-bit extended, not a
rounding change. Precision control is bits 8–9 (`00`=24, `10`=53, `11`=64) and rounding control
is bits 10–11. The genuine single-precision word is `0x007f` and a genuine rounding change is
`0x1a7f`; run as originally specified, all three settings come back identical and the tempting
conclusion — "nothing depends on the control word" — would have been wrong on both of the axes
that actually matter.
> Load `ref-turn2.sav`, press End Turn, and capture `(Autosave).sav` three times, with the shim
> forcing `fpu_cw` to `0x027f` (24-bit), `0x127f` (53-bit, the baseline) and `0x137f` (64-bit)
> across the turn call. Then run
> `state_checksum.py baseline.sav variant.sav` on each pair.
>
> * All three roots equal → no turn-pipeline value depends on x87 intermediate precision. The
> strict policy costs the SSE port nothing, and §3.5 can be closed.
> * Roots differ → the diff *is* the answer: it names every precision-sensitive field, and that
> list becomes the port's work item and the only place a `--ulps` budget is ever justified.
>
> Cheap, because the oracle is already byte-identical and the tool already localises. It needs
> nothing from this lane; it needs a shim knob that sets the control word around the turn call
> and the existing End-Turn click path from §5.
Until that runs, "the checksum is exact and the port must match bit-for-bit" is a *policy*, not
a measured requirement. It is the right default either way — it fails loudly rather than