chore: initialize age-reimpl repo
Reverse-engineering + open reimplementation workspace for Eushully's AGE/SYS4 engine (first target: Himegari). The repo root is age-reimpl/; the original game install and the extracted ALF data are siblings outside the repo and are never tracked. build/ (derived corpora) is gitignored and regenerated by the tools. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
74
tools/probe_leads.py
Normal file
74
tools/probe_leads.py
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Corpus-wide candidate-opcode histogram.
|
||||
|
||||
T3 entries point at [0x8F, 0, line#] records embedded in CODE. The dword right
|
||||
after each 3-dword T3 record is the lead of the next statement -> candidate
|
||||
opcode. Histogram those leads over all root-override + DATA1 scripts.
|
||||
Also: histogram the dword immediately preceding each `0x02 <off>` string ref
|
||||
(candidate "takes-a-string" opcodes), with example files.
|
||||
"""
|
||||
import os, sys, collections
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import paths
|
||||
import sys4load
|
||||
|
||||
# authoritative set: game-dir overrides shadow extracted/DATA1
|
||||
files = paths.scripts()
|
||||
print(f"{len(files)} scripts (overrides win)")
|
||||
|
||||
lead_hist = collections.Counter() # dword after each T3 record
|
||||
lead_examples = collections.defaultdict(set)
|
||||
pre_str = collections.Counter() # dword before each 0x02 <off> ref
|
||||
pre_str_ex = collections.defaultdict(set)
|
||||
first_dword = collections.Counter()
|
||||
t3_rec_shape_ok = 0
|
||||
t3_rec_total = 0
|
||||
bad = 0
|
||||
|
||||
for name, p in sorted(files.items()):
|
||||
try:
|
||||
scr = sys4load.load(p)
|
||||
except Exception as e:
|
||||
bad += 1
|
||||
continue
|
||||
dw = scr.dwords
|
||||
code_len = scr.code_len
|
||||
first_dword[dw[0]] += 1
|
||||
# statement leads via T3 records
|
||||
for e in scr.table_entries("T3"):
|
||||
if not (0 <= e < code_len):
|
||||
continue
|
||||
t3_rec_total += 1
|
||||
if e + 2 < code_len and dw[e] == 0x8F and dw[e+1] == 0:
|
||||
t3_rec_shape_ok += 1
|
||||
nxt = e + 3
|
||||
if nxt < code_len:
|
||||
v = dw[nxt]
|
||||
lead_hist[v] += 1
|
||||
if len(lead_examples[v]) < 3:
|
||||
lead_examples[v].add(name)
|
||||
# dword preceding string refs (ref index i+1 holds offset; tag at i)
|
||||
for ref_idx in scr.string_refs: # ref_idx points at the offset dword
|
||||
tag_idx = ref_idx - 1 # the 0x02
|
||||
if tag_idx - 1 >= 0:
|
||||
v = dw[tag_idx - 1]
|
||||
pre_str[v] += 1
|
||||
if len(pre_str_ex[v]) < 3:
|
||||
pre_str_ex[v].add(name)
|
||||
|
||||
print(f"parse failures: {bad}")
|
||||
print(f"T3 records with shape [0x8F,0,*]: {t3_rec_shape_ok}/{t3_rec_total}")
|
||||
print("\n== first body dword ==")
|
||||
for v, c in first_dword.most_common(8):
|
||||
print(f" {v:#06x} {c}")
|
||||
print(f"\n== statement leads after T3 records (top 40 of {len(lead_hist)}) ==")
|
||||
total = sum(lead_hist.values())
|
||||
for v, c in lead_hist.most_common(40):
|
||||
ex = ",".join(sorted(lead_examples[v]))
|
||||
print(f" {v:#06x} {c:>7} ({100*c/total:5.2f}%) e.g. {ex}")
|
||||
print(f"\n== dword preceding `0x02 <off>` string refs (top 25 of {len(pre_str)}) ==")
|
||||
tot2 = sum(pre_str.values())
|
||||
for v, c in pre_str.most_common(25):
|
||||
ex = ",".join(sorted(pre_str_ex[v]))
|
||||
print(f" {v:#06x} {c:>7} ({100*c/tot2:5.2f}%) e.g. {ex}")
|
||||
Reference in New Issue
Block a user