Nothing here changes what a tool computes; it changes where tools look. - tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has existed nowhere since /work became a clone, so they matched nothing and printed empty results. They now resolve the disc through a new disc.py from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised). Nine scripts that imported siblings from the retired Reborn checkout or an old session scratchpad now import from their own directory. unitgroup.py only needs the variable when --pak is not given. - sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead devkit key and a doc comment claiming a devkit fallback that does not exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either. - sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so always skipped. It reads $SYLPHEED_DISC now, and passes against the disc. - Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe as places to look now name sylpheed.db, Canary's ppc_context.h and the flat .pe; docs/re/README.md no longer says the native Canary build does not run. Historical records keep their original paths: findings that were measured against /work/xenia-rs/sylpheed.db still say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Encoding-agnostic: which words of an object actually carry live state?
|
|
|
|
Rather than assume the orientation is a matrix (it is not) or a quaternion,
|
|
classify every 4-byte word of the captured window by how it behaves over the
|
|
capture: constant, smoothly varying (a continuous quantity), or jumpy.
|
|
|
|
Usage: whatchanges.py <probe.bin> [name-substring] [max-report]
|
|
"""
|
|
import math
|
|
import os
|
|
import struct
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from flight_analyze import load # noqa: E402
|
|
|
|
|
|
def main():
|
|
path = sys.argv[1]
|
|
want = sys.argv[2] if len(sys.argv) > 2 else "Player"
|
|
top = int(sys.argv[3]) if len(sys.argv) > 3 else 60
|
|
insts, window, frames = load(path)
|
|
i = next(k for k, (_, _, nm) in enumerate(insts) if want in nm)
|
|
print(f"# {insts[i][2]} @ {insts[i][0]:#010x}, {len(frames)} frames, window {window:#x}")
|
|
|
|
nconst = nsmooth = njump = 0
|
|
smooth = []
|
|
for o in range(0, window - 3, 4):
|
|
vals = []
|
|
ok = True
|
|
for t, inp, lab, bufs in frames:
|
|
(v,) = struct.unpack_from(">f", bufs[i], o)
|
|
if not math.isfinite(v):
|
|
ok = False
|
|
break
|
|
vals.append(v)
|
|
if not ok:
|
|
continue
|
|
lo, hi = min(vals), max(vals)
|
|
if lo == hi:
|
|
nconst += 1
|
|
continue
|
|
rng = hi - lo
|
|
steps = [abs(b - a) for a, b in zip(vals, vals[1:])]
|
|
mx = max(steps)
|
|
# smooth = no single step is a large fraction of the whole excursion
|
|
if mx < 0.25 * rng:
|
|
nsmooth += 1
|
|
smooth.append((o, lo, hi, vals))
|
|
else:
|
|
njump += 1
|
|
print(f"# constant {nconst} smooth {nsmooth} jumpy {njump}")
|
|
print(f"\n# smoothly varying words (candidate continuous state):")
|
|
for o, lo, hi, vals in smooth[:top]:
|
|
print(f" +{o:#05x} [{lo:12.4g} .. {hi:12.4g}] "
|
|
f"start {vals[0]:12.4g} end {vals[-1]:12.4g}")
|
|
|
|
# consecutive runs of smooth words -> vectors
|
|
offs = [o for o, _, _, _ in smooth]
|
|
runs, cur = [], []
|
|
for o in offs:
|
|
if cur and o == cur[-1] + 4:
|
|
cur.append(o)
|
|
else:
|
|
if len(cur) >= 3:
|
|
runs.append(cur)
|
|
cur = [o]
|
|
if len(cur) >= 3:
|
|
runs.append(cur)
|
|
print(f"\n# runs of >=3 consecutive smooth words (vector-shaped):")
|
|
for r in runs:
|
|
print(f" +{r[0]:#05x} .. +{r[-1]:#05x} ({len(r)} words)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|