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