Support Piklib 6.1/7.1: CMC_Scene::resolve factory + tag-based types

Earlier text-script engines (Piklib 6.1/7.1, added text scripts in 6.1) keep the
type factory on CMC_Scene::resolve, not CMC_ObjectsContainer::resolve — so the
extractor bailed with "resolve not found". find_factory() now tries both anchors.

6.1's factory is also tag-based: each branch is operator==(NAME) -> new(0x74) ->
store tag -> jmp, with the ctor in a separate tag switch (no inline ctor). extract_types
gains a pre-emit: when the next operator== arrives still armed, it records the pending
type by name (size known, ctor/cpp_class not). The 8.x inline-ctor factory clears `armed`
first, so it's untouched (golden pair unchanged).

Per-version reality: 6.1 = 23 types / 0 methods (no prepareMthHashSet yet) / 103 events
/ 80 fields; 7.1 = 26 / 322 / 102 / 86 / 288 dispatch (full); type names line up across
6.1->7.1->8.x so version diffs work.

- snapshots/PIKLib61 + PIKLIB71 added as golden fixtures (evolution chain)
- tests/test_versions.py: 6.1 partial surface, 7.1 full, 61->71 diff -> 38/38

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Patryk Gensch
2026-05-31 19:53:47 +02:00
parent ba9db82a4c
commit 67cbc32a2c
5 changed files with 19247 additions and 3 deletions

52
tests/test_versions.py Normal file
View File

@@ -0,0 +1,52 @@
"""Per-version coverage on the Piklib evolution 6.1 -> 7.1 (-> 8.x). Documents what each
early engine actually exposes, and guards the tagged-factory type extraction (6.1)."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from ams.diff import compute_diff
from ams.snapshot import Snapshot
SNAP_DIR = Path(__file__).resolve().parents[1] / "snapshots"
P61 = SNAP_DIR / "PIKLib61.dll.snapshot.json"
P71 = SNAP_DIR / "PIKLIB71.dll.snapshot.json"
pytestmark = pytest.mark.skipif(not P61.exists(), reason="6.1/7.1 snapshots not present")
def _load(p: Path) -> Snapshot:
with open(p, encoding="utf-8") as fh:
return Snapshot(json.load(fh))
def test_piklib61_early_engine_partial_surface():
s = _load(P61)
assert s.binary["engine"] == "Piklib"
# types come from the tag-based CMC_Scene::resolve ladder (names recovered, ctor/size not)
names = {t["script_name"] for t in s.types}
assert len(names) >= 20 and {"ANIMO", "ARRAY", "BUTTON"} <= names
assert all(t.get("cpp_class") is None or t["cpp_class"].startswith("CMC_") for t in s.types)
# events + script fields work; method registration (prepareMthHashSet) doesn't exist yet
assert s.events and s.fields
assert s.methods == [] and s.method_dispatch == []
def test_piklib71_full_surface():
s = _load(P71)
assert s.binary["engine"] == "Piklib"
assert s.types and s.methods and s.events and s.fields and s.method_dispatch
# 7.1 uses the inline-ctor factory, so most types resolve their C++ class
assert sum(1 for t in s.types if t.get("cpp_class")) > len(s.types) // 2
def test_61_to_71_diff_adds_methods():
s61, s71 = _load(P61), _load(P71)
shared = {t["script_name"] for t in s61.types} & {t["script_name"] for t in s71.types}
assert {"ANIMO", "ARRAY", "BUTTON"} <= shared # stable core across the two early versions
# 7.1 introduces the registered-method machinery 6.1 lacked entirely
d = compute_diff(s61, s71)
assert len(d["methods"]["added"]) > 100 and d["methods"]["removed"] == []