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).
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
#!/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()
|