#!/usr/bin/env python3 """Test the hypothesis that a unit sub-record's runtime fields are laid out in *schema declaration order*, one 4-byte slot each. The IDXD string pool lists a sub-record's field names in declaration order, including the ones this .tbl leaves at their default (they appear as a bare key with no preceding value). If the runtime struct is `base + 4*index` over that list, then the offsets the solver found independently must all satisfy it. That is a much stronger check than any single field's agreement count: one base offset has to explain dozens of independently-derived anchors at once. Usage: order_check.py """ import re import sys NUMERIC = re.compile(r"^[-+]?[0-9]*\.?[0-9]+[fF]?$|^0x[0-9a-fA-F]+$") VALUE_WORDS = {"Yes", "No", "YES", "NO", "On", "Off", "ON", "OFF"} def section(dump, tbl, sub, subs): """Ordered token list of one sub-record of one .tbl.""" toks, on = [], False for line in open(dump): if line.startswith("RAW "): on = line.split()[1] == tbl continue if on and line.startswith("T "): toks.append(line[2:].rstrip("\n")) starts = [i for i, t in enumerate(toks) if t in subs or t.lstrip("(") in subs] for n, i in enumerate(starts): name = toks[i].lstrip("(") if name != sub: continue end = starts[n + 1] if n + 1 < len(starts) else len(toks) return toks[i + 1 : end] return [] def keys_in_order(toks): """Field names, in declaration order: every token that is not a value. In the numeric sub-records every value is a number literal, so a token that is not numeric and not a Yes/No word is a key. """ out = [] for t in toks: if NUMERIC.match(t) or t in VALUE_WORDS: continue if t not in out: out.append(t) return out def solved_offsets(path): out = {} for line in open(path): m = re.match(r"\s*(0x[0-9a-f]+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$", line) if m: off, enc, field, agree, dist, bad = m.groups() out[field] = (int(off, 16), enc, int(agree), int(dist), int(bad)) return out def main(): dump, tbl, sub, solvedf = sys.argv[1:5] subs = {"Generic", "Maneuver", "Shield", "Explosion", "Mass", "Effect", "SE"} keys = keys_in_order(section(dump, tbl, sub, subs)) solved = solved_offsets(solvedf) anchors = [(i, k, solved[k]) for i, k in enumerate(keys) if k in solved] if not anchors: sys.exit(f"no solved field lands in {sub}") # base that the most anchors agree on votes = {} for i, k, (off, *_) in anchors: votes.setdefault(off - 4 * i, []).append(k) base, winners = max(votes.items(), key=lambda kv: len(kv[1])) print(f"# {sub}: {len(keys)} declared fields, {len(anchors)} solved anchors") print(f"# best base = {base:#x} -> {len(winners)}/{len(anchors)} anchors fit " f"offset = base + 4*index\n") for i, k in enumerate(keys): off = base + 4 * i if k in solved: so, enc, agree, dist, bad = solved[k] mark = "fits" if so == off else f"CONFLICT solver={so:#x}" print(f" +{off:#05x} [{i:3d}] {k:<34} {enc} agree={agree:<3} " f"dist={dist:<3} {mark}") else: print(f" +{off:#05x} [{i:3d}] {k:<34} — (never valued on disc)") if __name__ == "__main__": main()