feat: globals.toml registry source + loader/lint (Task 1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
78
tools/globals_build.py
Normal file
78
tools/globals_build.py
Normal file
@@ -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())
|
||||
34
tools/test_globals.py
Normal file
34
tools/test_globals.py
Normal file
@@ -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)
|
||||
139
vm-map/globals.toml
Normal file
139
vm-map/globals.toml
Normal file
@@ -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 = []
|
||||
Reference in New Issue
Block a user