#!/usr/bin/env python3 """Round-trip verifier for the .torso and .engine decompilers. Regenerates both files for every chassis and compares key order and values against the authored source. `$(SYMBOL)` and its numeric expansion are treated as equal, since the record stores only the resolved float. python3 verify_smallmodel.py [--show KIND CHASSIS] """ import argparse, collections, glob, os, re, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import smallmodel REC = "/home/rich/Repositories/FS_Ours_extracted/core/mechs" SRC = "/home/rich/Repositories/firestorm/Gameleap/mw4/Content/Mechs" NUM = re.compile(r'^-?(?:\d+\.?\d*|\.\d+)$') def resolve(value, symbols): """$(NAME) -> its numeric value, so symbol and literal compare equal.""" m = re.fullmatch(r'\$\((\w+)\)', value.strip()) if m: return symbols.get(m.group(1).lower(), value.strip().lower()) return value.strip().lower() def canon(value, symbols): v = resolve(value, symbols) return f"{float(v):.5g}" if NUM.match(str(v)) else str(v) def symbol_values(path): """-> {symbolNameLower: numericString}""" out = {} if os.path.exists(path): txt = re.sub(r'//[^\n]*', '', open(path, encoding="latin-1", errors="replace").read()) for name, val in re.findall(r'^\s*!(\w+)\s*=\s*([-\d.]+)\s*$', txt, re.M): out[name.lower()] = val return out def source_kv(path): txt = re.sub(r'//[^\n]*', '', open(path, "rb").read().decode("latin-1")) return [(m.group(1), m.group(2).strip()) for m in re.finditer(r'^([A-Za-z]\w*)=([^\r\n]*)', txt, re.M)] def main(): ap = argparse.ArgumentParser() ap.add_argument("--show", nargs=2, metavar=("KIND", "CHASSIS")) args = ap.parse_args() t = collections.Counter() bad = collections.Counter() examples = [] order_bad = [] for kind, spec in sorted(smallmodel.SPECS.items()): symbols = symbol_values(spec["defines"]) for d in sorted(glob.glob(REC + "/*")): ch = os.path.basename(d) src = [p for p in glob.glob(f"{SRC}/*/*.{kind}") if os.path.basename(p).lower() == f"{ch.lower()}.{kind}"] if not src or smallmodel.record(d, kind) is None: continue t[f"{kind} files"] += 1 want = source_kv(src[0]) got = smallmodel.decompile(d, kind) if args.show and args.show[0] == kind and args.show[1].lower() == ch.lower(): sys.stdout.write(smallmodel.emit(kind, got)) return if [k.lower() for k, _ in want] != [k.lower() for k, _ in got]: order_bad.append((kind, ch, [k for k, _ in want], [k for k, _ in got])) continue for (wk, wv), (_gk, gv) in zip(want, got): t["keys"] += 1 if canon(wv, symbols) == canon(gv, symbols): t["ok"] += 1 else: bad[f"{kind}.{wk}"] += 1 if len(examples) < 12: examples.append((kind, ch, wk, wv, gv)) for kind in sorted(smallmodel.SPECS): print(f"{kind:8s} files : {t[kind + ' files']}") print(f"keys compared : {t['keys']} exact: {t['ok']} wrong: {t['keys'] - t['ok']}") if order_bad: print(f"\nkey order mismatches: {len(order_bad)}") for kind, ch, w, g in order_bad[:3]: print(f" {kind} {ch}\n want {w}\n got {g}") if bad: print("\nvalue mismatches:") for k, n in bad.most_common(15): print(f" {k:34s} x{n}") print("\nexamples (kind, chassis, key, source, decoded):") for e in examples: print(f" {e[0]:7s} {e[1]:14s} {e[2]:22s} want={e[3]!r:22s} got={e[4]!r}") if __name__ == "__main__": main()