diff --git a/README.md b/README.md index 2a9e8fb..545ad42 100644 --- a/README.md +++ b/README.md @@ -238,11 +238,15 @@ curl -OJ "http://127.0.0.1:8000/compat?format=md" # → compat.md curl -OJ "http://127.0.0.1:8000/compat?format=json" # → compat.json (grupy + warianty + diffy) ``` -W UI: przyciski **⬇ md** / **⬇ json** w panelu *Gry / wersje*. Markdown to macierz `biblioteka × gra` -(`✓` = obecna; `A`/`B*`/… gdy nazwa ma kilka wariantów powierzchni) plus sekcja *Warianty*: dla -każdego rozbieżnego wariantu overlap %, zbiorcze `types/methods/events/fields +N/-N/~N`, a pod tym -**nazwany rozkład per klasa** — które metody/eventy/pola doszły (`+`), zniknęły (`−`) lub zmieniły -się (`~`), np. `CMC_Animo: −PAUSE ~FPS (type int→float)`. Pełne listy (bez cap-a) są w `vs_reference.detail` JSON-a. +**JSON niesie pełny baseline.** Każdy wariant ma `api` = *co jest obsługiwane*: lista typów oraz +`classes` (per klasa jej `methods`/`events`/`fields` z nazwami, id, typami). To punkt odniesienia, +względem którego czyta się diff — masz i „co było", i „co się zmieniło". Diff rozbieżnego wariantu +siedzi w `vs_reference` (overlap %, zbiorcze liczby + `detail` z nazwanym rozkładem per klasa, bez cap-a). + +W UI: przyciski **⬇ md** / **⬇ json** w panelu *Gry / wersje*. Markdown to tylko **podgląd**: +macierz `biblioteka × gra` (`✓` = obecna; `A`/`B*`/… gdy nazwa ma kilka wariantów) plus sekcja +*Warianty* — dla każdego wariantu liczba i nazwy klas, a dla rozbieżnych skrócony rozkład zmian +(`CMC_Animo: −PAUSE ~FPS (type int→float)`). Pełne listy metod bierz z JSON-a (`api` / `vs_reference.detail`). ## Front — Command Center diff --git a/ams/compat.py b/ams/compat.py index f927a56..0153dd6 100644 --- a/ams/compat.py +++ b/ams/compat.py @@ -58,6 +58,35 @@ def _surface_counts(snap: Snapshot) -> dict[str, int]: return {axis: len(_keys(snap, axis)) for axis in _AXES} +def surface_api(snap: Snapshot) -> dict[str, Any]: + """The full *supported* surface of one variant: every type, and per class the methods, events + and fields it exposes. This is the baseline — 'what is there' — so the diff against the + reference variant has a point of reference to read against.""" + classes: dict[str, dict[str, list]] = {} + + def cls(owner: str | None) -> dict[str, list]: + return classes.setdefault(owner or "?", {"methods": [], "events": [], "fields": []}) + + for m in snap.methods: + cls(m.get("owner"))["methods"].append({"name": m["name"], "id": m.get("id")}) + for e in snap.events: + cls(e.get("owner"))["events"].append({"name": e["name"], "order": e.get("order")}) + for f in snap.fields: + cls(f.get("owner"))["fields"].append({"name": f["name"], "type": f.get("type")}) + + for d in classes.values(): + d["methods"].sort(key=lambda x: x["name"]) + d["events"].sort(key=lambda x: (x["order"] if x["order"] is not None else 1 << 30, x["name"])) + d["fields"].sort(key=lambda x: x["name"]) + + types = sorted( + ({"script_name": t["script_name"], "cpp_class": t.get("cpp_class"), + "object_size": t.get("object_size")} for t in snap.types), + key=lambda x: x["script_name"], + ) + return {"types": types, "classes": dict(sorted(classes.items()))} + + def _axis_counts(block: dict[str, Any]) -> dict[str, int]: return { "added": len(block["added"]), @@ -182,6 +211,7 @@ def _build_library(name: str, entries: list[Entry]) -> dict[str, Any]: "id": vid, "reference": is_ref, "surface": _surface_counts(sample), + "api": surface_api(sample), "members": members, "vs_reference": None if is_ref else _vs_reference(reference_snap, sample), } @@ -344,10 +374,13 @@ def render_markdown(table: dict[str, Any]) -> str: m["game"], m["engine"] or "?", m["compiler"] or "?", _short(m["sha256"])) for m in v["members"] ) + class_names = list(v["api"]["classes"]) out.append( - "- **{0}** ({1}) — typy {2} / metody {3} / eventy {4} / pola {5}".format( - v["id"], tag, s["types"], s["methods"], s["events"], s["fields"])) + "- **{0}** ({1}) — klasy {2} / typy {3} / metody {4} / eventy {5} / pola {6}".format( + v["id"], tag, len(class_names), s["types"], s["methods"], s["events"], s["fields"])) out.append(" - gry: {0}".format(games_str)) + if class_names: + out.append(" - klasy: {0}".format(_signed("", class_names, cap=30))) vs = v["vs_reference"] if vs: ax = vs["axes"] diff --git a/tests/test_compat.py b/tests/test_compat.py index 7e115de..14d0e3d 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -97,6 +97,17 @@ def test_hash_prefix_is_stripped_for_grouping(): assert "333ab0c4_PIKLIB8.dll" in files +def test_variant_carries_full_supported_api(): + # the baseline: each variant lists *what is there*, grouped by class — not just the diff + t = compatibility_table([ + Entry("Game A", _snap("X.dll", ["SHOW", "HIDE"], events=["ONCLICK"])), + ]) + api = t["libraries"][0]["variants"][0]["api"] + assert [m["name"] for m in api["classes"]["CMC_Animo"]["methods"]] == ["HIDE", "SHOW"] + assert [e["name"] for e in api["classes"]["CMC_Animo"]["events"]] == ["ONCLICK"] + assert [t_["script_name"] for t_ in api["types"]] == ["ANIMO"] + + def test_grouping_is_case_insensitive_keeps_common_spelling(): t = compatibility_table([ Entry("Game A", _snap("Piklib8.dll", ["SHOW"])),