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:
gamer147
2026-07-07 09:20:33 -04:00
parent 110994ec77
commit 9862e29af0
2 changed files with 80 additions and 0 deletions

View File

@@ -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

View File

@@ -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)