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 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. 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 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 `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 `fpu_cw = 0x127f` makes the original do) or to accept a documented `--ulps` budget on a named
list of fields. list of fields.
**What I cannot settle from the host side:** whether the turn pipeline's results depend on the **SETTLED on the VM, 2026-09-08 (lane F).** It was not settleable from the host side — every
x87 intermediate precision at all. Every save on this host was produced with `fpu_cw = 0x127f`, save in the corpus was made at `fpu_cw = 0x127f`, one point rather than a curve — so lane F ran
so the corpus is one point, not a curve. It is possible that every value in the turn pipeline the End Turn seven times from `ref-turn2.sav` with the shim forcing the control word, and
is computed in a way that gives the same float32 under 24-bit, 53-bit and 64-bit precision checksummed the results with this tool. Full write-up and the evidence chain that the setting
control, in which case an SSE port has **no** double-rounding budget to spend and the strict actually *held* (38 independent in-pipeline samples per run):
policy is free forever. It is equally possible that a handful of fields are precision-sensitive. `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 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 a measured requirement. It is the right default either way — it fails loudly rather than