fire=1 appears in 0 of 13521 samples. The gate that closes is measured rather than guessed: over the 3004 samples that had a target, |aim yaw| is EXACTLY 90.0 degrees every time, which is the `if ez < 0` branch in sticks() - the target is astern - with pitch near 180 and a range that grows 22km -> 49km and plateaus. The craft flies away from what it is chasing for fifteen minutes and the turn never completes. What is NOT established is why, and the attempt is withdrawn rather than kept: aim_probe.py reported the forward vector pinned at [-1,0,0] with 0.00 deg/s under neutral, full-left and full-right stick, which looks like a stale attitude matrix - but the guest had FROZEN partway through the probe, confirmed after the fact by frozen.py and by the player position being identical across 3 s. A dead world holds every matrix still. The probe is committed because it is the right experiment; its numbers are not evidence. One confusion resolved: today's entities2.py "0 moving triples" bind failures are the freeze, not a tool defect - moving() types entities by position CHANGING, so a frozen world yields nothing by construction. live_delta.py gains a per-1MB-region summary; a flat list is useless at 236000 hits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
89 lines
3.2 KiB
Python
Executable File
89 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Which guest words are still changing while the game is frozen?
|
|
|
|
`mission-freeze-resume-spin.md` establishes that a frozen run is guest code
|
|
SPINNING — the main thread stays in state `R` and gains ~40 % of a core over ten
|
|
seconds while making not one kernel call. Spinning code is waiting for something
|
|
in guest memory to change, and its own bookkeeping is the only thing still
|
|
moving. So diff guest RAM against itself across a few seconds: while the game
|
|
runs this is hopeless (everything moves), but while it is frozen the survivors
|
|
should be a handful of words — the loop's counter, and whatever it polls.
|
|
|
|
The comparison is over the file's *data extents* only (`SEEK_DATA`), so the 4.6 GB
|
|
of sparse holes cost nothing, and the first snapshot is kept in memory per extent.
|
|
|
|
Usage: live_delta.py [gap_s] [max_report] [--range LO HI]
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import gmem # noqa: E402
|
|
|
|
|
|
def snapshot(fd, size, lo=None, hi=None):
|
|
out = []
|
|
f0 = gmem.va_to_off(lo) if lo is not None else 0
|
|
f1 = gmem.va_to_off(hi) if hi is not None else size
|
|
for a, b in gmem.extents(fd, size):
|
|
a, b = max(a, f0), min(b, f1)
|
|
n = (b - a) // 4 * 4
|
|
if n >= 4:
|
|
out.append((a, np.frombuffer(os.pread(fd, n, a), dtype=">u4")))
|
|
return out
|
|
|
|
|
|
def main():
|
|
gap = float(sys.argv[1]) if len(sys.argv) > 1 else 6.0
|
|
top = int(sys.argv[2]) if len(sys.argv) > 2 else 60
|
|
lo = hi = None
|
|
if "--range" in sys.argv:
|
|
i = sys.argv.index("--range")
|
|
lo, hi = int(sys.argv[i + 1], 0), int(sys.argv[i + 2], 0)
|
|
|
|
path = gmem.mem_path()
|
|
fd = os.open(path, os.O_RDONLY)
|
|
size = os.path.getsize(path)
|
|
a = snapshot(fd, size, lo, hi)
|
|
total = sum(len(x[1]) for x in a) * 4
|
|
print(f"# {path}: {len(a)} extents, {total/1e6:.1f} MB of data; "
|
|
f"waiting {gap}s", flush=True)
|
|
time.sleep(gap)
|
|
b = dict((off, arr) for off, arr in snapshot(fd, size, lo, hi))
|
|
|
|
changed = []
|
|
for off, arr0 in a:
|
|
arr1 = b.get(off)
|
|
if arr1 is None or len(arr1) != len(arr0):
|
|
continue
|
|
idx = np.flatnonzero(arr0 != arr1)
|
|
for i in idx:
|
|
changed.append((off + int(i) * 4, int(arr0[i]), int(arr1[i])))
|
|
print(f"# {len(changed)} words changed in {gap}s "
|
|
f"({len(changed)*4/max(total,1)*100:.4f} % of the data)", flush=True)
|
|
# A flat list is useless at 235 973 hits. Summarise by 1 MB region first:
|
|
# where the activity is says far more than which word moved.
|
|
from collections import Counter
|
|
reg = Counter()
|
|
for o, _, _ in changed:
|
|
va = gmem.primary_va(o)
|
|
reg[(va >> 20) << 20 if va is not None else 0] += 1
|
|
print("# by 1 MB region (top 12):")
|
|
for base, n in reg.most_common(12):
|
|
print(f" {base:#010x} {n:8d} words")
|
|
for o, v0, v1 in changed[:top]:
|
|
va = gmem.primary_va(o)
|
|
d = v1 - v0
|
|
print(f" va {va:#010x} {v0:#010x} -> {v1:#010x}"
|
|
f"{f' ({v0} -> {v1}, {d:+d})' if abs(v0) < 1 << 28 and abs(v1) < 1 << 28 else ''}")
|
|
if len(changed) > top:
|
|
print(f" ... and {len(changed)-top} more")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|