#!/usr/bin/env python3 """Verification harness for the .armature decompiler. Rebuilds every chassis from our own packed records and compares against the known source .armature. Compares page transforms as a multiset keyed on (name, transform) so duplicate page names - Victor has two [site_lshellport] pages under different joints - are handled. python3 verify_armature.py """ import sys, os, glob, re, collections sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import armature def source_entries(path): """-> (list of (name, rot, trans), {parent: [child, ...]})""" txt = open(path, "rb").read().decode("latin-1") entries, children = [], {} for m in re.finditer(r'^\[([^\]]+)\]\s*\r?\n(.*?)(?=^\[|\Z)', txt, re.M | re.S): name, body = m.group(1).lower(), m.group(2) r = re.search(r'Rotation=([-\d.eE ]+)', body) t = re.search(r'Translation=([-\d.eE ]+)', body) kids = [c.lower() for c in re.findall(r'Child=(\S+)', body)] if kids: children[name] = kids if r and t: entries.append((name, tuple(float(x) for x in r.group(1).split()), tuple(float(x) for x in t.group(1).split()))) return entries, children def close(a, b, tol): return a and b and max(abs(x - y) for x, y in zip(a, b)) <= tol def angles_close(a, b, tol=0.01): return a and b and max(abs(((x - y + 180) % 360) - 180) for x, y in zip(a, b)) <= tol def main(): src_paths = {os.path.basename(p)[:-len(".armature")].lower(): p for p in glob.glob(armature.OUR_MECH_SOURCE + "/*/*.armature")} t = collections.Counter() residual = collections.Counter() for mech_dir in sorted(glob.glob(armature.OUR_RECORDS + "/*")): if not glob.glob(mech_dir + "/*{armature}"): continue chassis, entries, children = armature.rebuild(mech_dir) if not chassis or chassis.lower() not in src_paths: continue t["chassis"] += 1 src_list, src_children = source_entries(src_paths[chassis.lower()]) got = [(e["name"].lower(), e["rot"], e["trans"]) for e in entries] pool = list(got) for name, rot, tr in src_list: t["pages"] += 1 hit = next((g for g in pool if g[0] == name and close(g[2], tr, 2e-3) and angles_close(g[1], rot)), None) if hit: pool.remove(hit) t["exact"] += 1 continue near = next((g for g in pool if g[0] == name and close(g[2], tr, 2e-3)), None) if near: pool.remove(near) t["rot_only"] += 1 residual[name] += 1 else: t["missing"] += 1 residual["MISSING " + name] += 1 for parent, kids in src_children.items(): t["childlists"] += 1 if sorted(kids) == sorted(c.lower() for c in children.get(parent, ())): t["childlists_ok"] += 1 else: residual["CHILDREN " + parent] += 1 print(f"chassis : {t['chassis']}") print(f"pages compared : {t['pages']}") print(f" fully exact : {t['exact']}") print(f" translation ok, rotation lost by the packer : {t['rot_only']}") print(f" not recovered : {t['missing']}") print(f"child lists : {t['childlists_ok']}/{t['childlists']} exact") if residual: print("\nresidual, by page:") for k, v in residual.most_common(10): print(f" {k:22s} x{v}") if __name__ == "__main__": main()