diff --git a/tools/globals_build.py b/tools/globals_build.py new file mode 100644 index 0000000..d20862b --- /dev/null +++ b/tools/globals_build.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Generator + linter for the living global-variable registry (vm-map/globals.toml). + --build merge curated over build/global-var-map.json -> build/globals.json + docs/global-reference.md + --lint run the linter, print errors/warnings, exit nonzero on errors +See docs/superpowers/specs/2026-07-07-globals-registry-and-story-flags-design.md.""" +from __future__ import annotations +import os, sys, json, argparse, collections, tomllib +from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import paths + +TOML_DEFAULT = paths.VM_MAP / "globals.toml" +AUTO_MAP = paths.BUILD / "global-var-map.json" + +CATEGORIES = {"story-flag", "index-pointer", "data-table", "string-table", + "ui-toggle", "choice-output", "counter", "unknown"} +SOURCES = {"investigation", "harness", "inference", "auto-shape"} +CONFIDENCE = {"low": 1, "med": 2, "high": 3} + + +def _parse_addr(v) -> int: + return int(v, 16) if isinstance(v, str) else int(v) + + +def load_toml(path) -> tuple[dict[int, dict], dict]: + """Return ({addr:int -> entry-dict (with _addr set)}, meta-dict).""" + data = tomllib.loads(Path(path).read_text(encoding="utf-8")) + out: dict[int, dict] = {} + for e in data.get("global", []): + addr = _parse_addr(e["address"]) + e = dict(e) + e["_addr"] = addr + out[addr] = e + return out, data.get("meta", {}) + + +def lint(entries: dict[int, dict], all_addrs: set[int]) -> tuple[list[str], list[str]]: + """Errors: bad vocabulary, auto-shape@high, dangling depends_on. Warnings: (reserved).""" + errors: list[str] = [] + warnings: list[str] = [] + for addr, e in sorted(entries.items()): + tag = f"0x{addr:x}" + cat, src, conf = e.get("category"), e.get("source"), e.get("confidence") + if cat not in CATEGORIES: + errors.append(f"{tag}: bad category {cat!r}") + if src not in SOURCES: + errors.append(f"{tag}: bad source {src!r}") + if conf not in CONFIDENCE: + errors.append(f"{tag}: bad confidence {conf!r}") + if src == "auto-shape" and conf == "high": + errors.append(f"{tag}: auto-shape source may not claim high confidence") + for dep in e.get("depends_on", []): + if _parse_addr(dep) not in all_addrs: + errors.append(f"{tag}: depends_on missing address {dep}") + return errors, warnings + + +def main(argv=None): + ap = argparse.ArgumentParser() + ap.add_argument("--build", action="store_true") + ap.add_argument("--lint", action="store_true") + ap.add_argument("--toml", default=str(TOML_DEFAULT)) + args = ap.parse_args(argv) + tp = Path(args.toml) + if args.lint: + entries, _ = load_toml(tp) + errors, warnings = lint(entries, set(entries)) + for m in warnings: print("warn:", m) + for m in errors: print("error:", m) + print(f"lint: {len(errors)} errors, {len(warnings)} warnings") + return 1 if errors else 0 + if args.build: + return build(tp) # implemented in Task 2 + ap.error("no action (expected --build/--lint)") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/test_globals.py b/tools/test_globals.py new file mode 100644 index 0000000..d40f9e8 --- /dev/null +++ b/tools/test_globals.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Standalone tests for the globals registry tooling. Run: py -3.11 -X utf8 tools/test_globals.py""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import paths +import globals_build as G + +FAILS = [] +def check(cond, msg): + print((" ok " if cond else " FAIL ") + msg) + if not cond: FAILS.append(msg) + +def test_load_and_lint(): + entries, meta = G.load_toml(paths.VM_MAP / "globals.toml") + check(0xa57 in entries, "0xa57 present in globals.toml") + check(entries[0xa57]["category"] == "story-flag", "0xa57 is category story-flag") + check(entries[0x3234]["category"] == "story-flag", "0x3234 is category story-flag") + check({0xa57, 0xa58, 0xa59} <= set(entries), "Lily form flags A/B/C all present") + # lint clean against a permissive address universe (curated addrs are self-consistent) + errors, warnings = G.lint(entries, set(entries)) + check(errors == [], f"globals.toml lints clean (errors={errors})") + +def test_lint_catches_bad_vocab(): + bad = {0x1: {"_addr": 0x1, "name": "x", "category": "bogus", + "source": "auto-shape", "confidence": "high"}} + errors, _ = G.lint(bad, {0x1}) + check(any("category" in e for e in errors), "lint flags bad category") + check(any("confidence" in e for e in errors), "lint flags auto-shape claiming high confidence") + +if __name__ == "__main__": + test_load_and_lint() + test_lint_catches_bad_vocab() + print(f"\n{len(FAILS)} failures") + sys.exit(1 if FAILS else 0) diff --git a/vm-map/globals.toml b/vm-map/globals.toml new file mode 100644 index 0000000..c7a70f8 --- /dev/null +++ b/vm-map/globals.toml @@ -0,0 +1,139 @@ +# vm-map/globals.toml -- CANONICAL living global-variable registry (hand-edited). +# Generated artifacts (build/globals.json, docs/global-reference.md) come from this file via +# tools/globals_build.py --build. Do not edit those. Skeletons are appended by +# tools/story_flags.py --bootstrap; enrich each entry as we investigate. +# See docs/superpowers/specs/2026-07-07-globals-registry-and-story-flags-design.md. +[meta] +note = "Curated global addresses override the auto shape-inference map (build/global-var-map.json)." + +[[global]] +address = "0x3234" +name = "chapter_mode" +category = "story-flag" +type = "int" +value_domain = "1..9" +usage = "Progression chapter/mode selector. SCJUMP's top-level switch keys on it; branch-read by progression scripts (FIELD etc.), not directly by SC/SP scenes." +source = "investigation" +confidence = "high" +depends_on = ["0x62ccf"] + +[[global]] +address = "0xa57" +name = "lily_form_a" +category = "story-flag" +type = "int" +value_domain = "{0,1}" +usage = "Lily current-form flag A. Exactly one of form A/B/C is 1; gates form-specific voiced dialogue (seeding 0xa57=1 -> SC0000 186->229 lines). Set externally (menu/save), no static writer." +source = "investigation" +confidence = "high" +depends_on = ["0xa58", "0xa59"] + +[[global]] +address = "0xa58" +name = "lily_form_b" +category = "story-flag" +type = "int" +value_domain = "{0,1}" +usage = "Lily current-form flag B. See lily_form_a." +source = "investigation" +confidence = "high" +depends_on = ["0xa57", "0xa59"] + +[[global]] +address = "0xa59" +name = "lily_form_c" +category = "story-flag" +type = "int" +value_domain = "{0,1}" +usage = "Lily current-form flag C. See lily_form_a." +source = "investigation" +confidence = "high" +depends_on = ["0xa57", "0xa58"] + +[[global]] +address = "0x6c9" +name = "ui_toggle_0" +category = "ui-toggle" +type = "int" +value_domain = "{0,1}" +usage = "ADV-chrome hotspot button toggle (op 0x90 site, near-universal across scenes)." +source = "investigation" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x6ca" +name = "ui_toggle_1" +category = "ui-toggle" +type = "int" +value_domain = "{0,1}" +usage = "ADV-chrome hotspot button toggle. See ui_toggle_0." +source = "investigation" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x6cb" +name = "ui_toggle_2" +category = "ui-toggle" +type = "int" +value_domain = "{0,1}" +usage = "ADV-chrome hotspot button toggle. See ui_toggle_0." +source = "investigation" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x6cc" +name = "ui_toggle_3" +category = "ui-toggle" +type = "int" +value_domain = "{0,1}" +usage = "ADV-chrome hotspot button toggle. See ui_toggle_0." +source = "investigation" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x6cd" +name = "ui_toggle_4" +category = "ui-toggle" +type = "int" +value_domain = "{0,1}" +usage = "ADV-chrome hotspot button toggle. See ui_toggle_0." +source = "investigation" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x62ccf" +name = "scjump_decision_out" +category = "choice-output" +type = "int" +value_domain = "?" +usage = "One of SCJUMP's output/decision globals (progression state machine writes it). Related to chapter_mode." +source = "inference" +confidence = "low" +depends_on = [] + +[[global]] +address = "0xeff75" +name = "current_entity_index_hi" +category = "index-pointer" +type = "int" +value_domain = "row index" +usage = "High-purity current-entity row index (purity 0.95 in the auto shape map); dominant 2D-table row selector." +source = "inference" +confidence = "med" +depends_on = [] + +[[global]] +address = "0x152616" +name = "current_entity_index" +category = "index-pointer" +type = "int" +value_domain = "row index" +usage = "Primary current-entity row index (RECOVER-confirmed; purity 0.51, 363 row-index uses)." +source = "investigation" +confidence = "med" +depends_on = []