feat: emit SCJUMP decision table (json+md, registry-named) (Task 3)
build/scjump-decisions.* are generated (build/ gitignored). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,74 @@ def decode(scr):
|
||||
return decisions
|
||||
|
||||
|
||||
def load_names() -> dict:
|
||||
"""addr:int -> curated registry name (from build/globals.json), or {} if absent."""
|
||||
try:
|
||||
data = json.loads((paths.BUILD / "globals.json").read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
out = {}
|
||||
for addr_s, e in data.get("globals", {}).items():
|
||||
if e.get("name"):
|
||||
out[int(addr_s, 16)] = e["name"]
|
||||
return out
|
||||
|
||||
|
||||
def render_guard(g, names) -> str:
|
||||
if "opaque" in g: return g["opaque"]
|
||||
if "and" in g: return "(" + " AND ".join(render_guard(x, names) for x in g["and"]) + ")"
|
||||
if "or" in g: return "(" + " OR ".join(render_guard(x, names) for x in g["or"]) + ")"
|
||||
lbl = names.get(g["global"], f"0x{g['global']:x}")
|
||||
return f"{lbl}{g['op']}{g['value']}"
|
||||
|
||||
|
||||
def emit_json(decs, names) -> str:
|
||||
def jguard(g):
|
||||
if "opaque" in g or "and" in g or "or" in g: return g
|
||||
return {"global": f"0x{g['global']:x}", "name": names.get(g["global"]), "op": g["op"], "value": g["value"]}
|
||||
out_decs = [{"site_offset": f"0x{d['site_offset']:x}", "chapter": d["chapter"],
|
||||
"decision": d["decision"], "guards": [jguard(g) for g in d["guards"]]} for d in decs]
|
||||
by_chapter = collections.defaultdict(list)
|
||||
for d in out_decs:
|
||||
by_chapter[d["chapter"]].append(d["decision"])
|
||||
meta = {"note": "GENERATED by tools/scjump_decode.py — do not edit. Decision->scene is native (u00428010), not resolved here.",
|
||||
"decision_sites": len({d["site_offset"] for d in decs}),
|
||||
"distinct_decisions": len({d["decision"] for d in decs})}
|
||||
return json.dumps({"meta": meta, "decisions": out_decs,
|
||||
"by_chapter": {str(k): sorted(set(v)) for k, v in sorted(by_chapter.items(), key=lambda kv: (kv[0] is None, kv[0]))}},
|
||||
ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def emit_md(decs, names) -> str:
|
||||
L = ["<!-- GENERATED by tools/scjump_decode.py — do not edit -->",
|
||||
"# SCJUMP progression decisions (generated)", "",
|
||||
f"{len({d['site_offset'] for d in decs})} decision sites. Each rule: guards (all true along the "
|
||||
"path) -> decision value written to `0x62ccf`. Decision->scene is native (`u00428010`), see "
|
||||
"`docs/scjump-progression.md`.", ""]
|
||||
by_ch = collections.defaultdict(list)
|
||||
for d in decs:
|
||||
by_ch[d["chapter"]].append(d)
|
||||
for ch in sorted(by_ch, key=lambda c: (c is None, c)):
|
||||
L += [f"## chapter {ch}", ""]
|
||||
for d in sorted(by_ch[ch], key=lambda d: d["site_offset"]):
|
||||
guards = " AND ".join(render_guard(g, names) for g in d["guards"]) or "(unconditional)"
|
||||
L.append(f"- `0x{d['site_offset']:x}`: {guards} → **decision {d['decision']}**")
|
||||
L.append("")
|
||||
return "\n".join(L) + "\n"
|
||||
|
||||
|
||||
def build(scr=None) -> int:
|
||||
scr = scr or load_scjump()
|
||||
decs = decode(scr)
|
||||
names = load_names()
|
||||
paths.BUILD.mkdir(parents=True, exist_ok=True)
|
||||
(paths.BUILD / "scjump-decisions.json").write_text(emit_json(decs, names), encoding="utf-8")
|
||||
(paths.BUILD / "scjump-decisions.md").write_text(emit_md(decs, names), encoding="utf-8")
|
||||
print(f"decode: {len({d['site_offset'] for d in decs})} decision sites, "
|
||||
f"{len({d['decision'] for d in decs})} distinct decisions -> build/scjump-decisions.{{json,md}}")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--verify", action="store_true") # implemented in Task 4
|
||||
|
||||
@@ -29,8 +29,20 @@ def test_decode_anchor_and_count():
|
||||
check(has_chapter, "anchor guarded by chapter_mode==1")
|
||||
check(has_flag, "anchor guarded by 0x6d3!=1")
|
||||
|
||||
def test_emit_json_shape():
|
||||
scr = S.load_scjump()
|
||||
decs = S.decode(scr)
|
||||
names = S.load_names()
|
||||
obj = json.loads(S.emit_json(decs, names))
|
||||
check(obj["meta"]["decision_sites"] == 1755, "json meta reports 1755 sites")
|
||||
check(all(set(("site_offset", "chapter", "decision", "guards")) <= set(d) for d in obj["decisions"]), "each decision has required keys")
|
||||
# rendering uses registry names when present
|
||||
check(S.render_guard({"global": 0x3234, "op": "==", "value": 7}, {0x3234: "chapter_mode"}) == "chapter_mode==7",
|
||||
"render_guard uses registry name")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cfg_acyclic_and_dispatch()
|
||||
test_decode_anchor_and_count()
|
||||
test_emit_json_shape()
|
||||
print(f"\n{len(FAILS)} failures")
|
||||
sys.exit(1 if FAILS else 0)
|
||||
|
||||
Reference in New Issue
Block a user