Files
Patryk Gensch 27399a52b1 Method dispatch axis: map id -> body via Runner::run switch
Recovers how a script method id maps to its implementation, the foundation for
body-level normalisation. Each CMC_*_Runner::run is a switch(id) (vtable slot 17);
every case is the method body — inline (MSVC6) or a tail-call to a separate
show()/load() (MSVC8). The extractor parses the jump table at the disassembly
level (Ghidra's decompiler jump-table recovery silently dropped the big runners),
fingerprints each case by its ordered CALL anchors (Class::method / vtbl+0xNN),
and expands thin wrappers one level so MSVC8 lines up with MSVC6.

Validated on the golden pair: Animo SHOW..RESUME (id 1-4) yield identical leaves
(getAnimo + vtbl+0xa0/0xa4/0x4c/0x50) across both compilers. Coverage 30/32
runners; Piklib 475 / BlooMoo 619 dispatch rows.

- extract_engine_surface.py: extract_method_dispatch (schema_version -> 4)
- snapshots regenerated with the method_dispatch axis
- ams: Snapshot.method_dispatch; diff axis keyed (owner,id) on [impl,calls] with
  method-name join; render METHOD BODIES section; cli --only dispatch; owner filter
- UI: "Ciała metod" diff axis + browse tab
- tests: body-change unit + cross-compiler vtbl assertion -> 29/29

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 13:15:58 +02:00

50 lines
1.5 KiB
Python

"""CLI: diff two engine-surface snapshots.
python -m ams OLD.snapshot.json NEW.snapshot.json [--owner CMC_Animo] [--only types,methods] [--json]
"""
from __future__ import annotations
import argparse
import json
import sys
from .diff import compute_diff, filter_by_owner
from .render import render_text
from .snapshot import Snapshot
_AXES = ["types", "methods", "events", "fields", "layout", "dispatch"]
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="ams", description="Diff two engine-surface snapshots.")
p.add_argument("old", help="older snapshot.json")
p.add_argument("new", help="newer snapshot.json")
p.add_argument("--owner", help="restrict to one class, e.g. CMC_Animo")
p.add_argument("--only", help="comma-separated axes to show: " + ",".join(_AXES))
p.add_argument("--json", action="store_true", help="emit machine-readable JSON")
args = p.parse_args(argv)
old = Snapshot.load(args.old)
new = Snapshot.load(args.new)
diff = compute_diff(old, new)
if args.owner:
diff = filter_by_owner(diff, args.owner)
if args.json:
print(json.dumps(diff, indent=2, sort_keys=True))
return 0
only = None
if args.only:
only = {a.strip() for a in args.only.split(",") if a.strip()}
bad = only - set(_AXES)
if bad:
p.error("unknown axis: {0} (choose from {1})".format(",".join(sorted(bad)), ",".join(_AXES)))
print(render_text(diff, only=only))
return 0
if __name__ == "__main__":
sys.exit(main())