Files
psxrecomp/tools/verify_audit_schema.py
T
Matthew Stanley 2f709ee542 Phase B: config-driven unified runtime + BIOS shell boot restored
- Config loader (recompiler/src/config_loader.{h,cpp}): single source of
  truth for both BIOS and game runtime settings (debug_port, window_title,
  memcard_dir, disc path). psxrecomp-bios and psxrecomp-game both accept
  --config <toml>; runtime takes --game <toml> (no more CLI overrides).
- runtime/src/main.cpp: replaced --disc/--memcard-dir/--game-root CLI
  overrides with --game <toml>; game.toml is the single source of truth.
  Discless launch (no args) goes to BIOS shell.
- runtime/src/dma.c: fixed update_master_irq() re-asserting I_STAT bit 3
  on every DICR read. BIOS shell DMA handler at 0x80058628 only clears
  DICR bit 27 (ch3 flag), so level-sensitive re-assert pinned the kernel
  in the exception loop. Bit 3 is now edge-triggered (one assert per DMA
  completion). Discless boot now reaches MAIN MENU / MEMORY CARD / CD
  PLAYER. With-disc Tomba boots through to Whoopee Camp splash.
- Audit tooling: codegen_audit.py (BIOS), codegen_audit_game.py (game),
  audit_config.py (config loader for tools), verify_audit_schema.py.
  Replaces older tools/_codegen_audit.py probe.
- Docs: CURRENT_STATE.md (project state), UNIFICATION_PLAN.md (Phase C
  translator merge plan), docs/config_schema.md (TOML schema reference),
  docs/audit_inventory.md (audit tier inventory).
- .gitignore: tools/_*.py convention for ad-hoc scratch probes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:31:27 -07:00

69 lines
2.6 KiB
Python

"""B0 schema verification: parse both audit configs and check shape.
Confirms tools/* can rely on a stable layout. If this passes, B1 can
proceed to consume the configs.
"""
import sys
from pathlib import Path
try:
import tomllib # Python 3.11+
except ImportError:
import tomli as tomllib
ROOT = Path(__file__).parent.parent
def hex_int(s, field):
if not isinstance(s, str): raise ValueError(f"{field}: expected hex string, got {type(s).__name__}")
return int(s, 16)
def validate(path, expect_remaps):
print(f"\n=== {path} ===")
with open(path, "rb") as f:
cfg = tomllib.load(f)
# Program info: accept [program] (BIOS / non-game) or [game] (game convention).
p = cfg.get("program") or cfg.get("game")
if p is None:
raise KeyError("config has neither [program] nor [game] block")
rom_field = p.get("rom") or p.get("exe") # [program].rom or [game].exe
if rom_field is None:
raise KeyError("program/game block missing rom/exe path")
print(f" program.name = {p['name']!r}")
print(f" program.rom = {rom_field!r}")
print(f" program.load_address = {p['load_address']} = 0x{hex_int(p['load_address'], 'load_address'):08X}")
print(f" program.text_size = {p['text_size']} = 0x{hex_int(p['text_size'], 'text_size'):08X}")
# [audit] block
a = cfg["audit"]
fs = a.get("function_starts")
print(f" audit.function_starts = {fs!r}")
regions = a["regions"]
print(f" audit.regions = {len(regions)} region(s)")
for r in regions:
lo = hex_int(r["rom_start"], "rom_start")
hi = hex_int(r["rom_end"], "rom_end")
va = hex_int(r["vaddr_base"], "vaddr_base")
print(f" - {r['name']:8s} rom 0x{lo:05X}..0x{hi:05X} vaddr base 0x{va:08X} ({hi-lo} bytes)")
norm = a["normalize"]
mask = hex_int(norm["kseg_mask"], "kseg_mask")
print(f" audit.normalize.kseg_mask = 0x{mask:08X}")
remaps = norm.get("remap", [])
print(f" audit.normalize.remap = {len(remaps)} remap(s)")
assert len(remaps) == expect_remaps, f"expected {expect_remaps} remaps, got {len(remaps)}"
for rm in remaps:
flo = hex_int(rm["from_lo"], "from_lo")
fhi = hex_int(rm["from_hi"], "from_hi")
tlo = hex_int(rm["to_lo"], "to_lo")
print(f" - 0x{flo:08X}..0x{fhi:08X} -> +0x{tlo:X} ({rm.get('description','')})")
return cfg
bios = validate(ROOT / "bios" / "SCPH1001.toml", expect_remaps=2)
tomba = validate(Path("F:/Projects/TombaRecomp/game.toml"), expect_remaps=0)
print("\nOK: both configs parse and validate.")