Files
aetherbound-guild/tools/verify_recruit_loadout_level_contract.py
T

87 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Verify the Phase 7.1 single-item and individual-level design contract."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
AUTHORITY_PATHS = [
"docs/product/GAME_PRODUCT_CONTRACT.md",
"docs/product/SYSTEMS_AND_BATTLE.md",
"docs/product/ECONOMY_AND_BALANCE.md",
"docs/product/SAVE_AND_FAILURE_CONTRACT.md",
"docs/content/RECRUIT_GENERATION.md",
"docs/content/TRAIT_CATALOG.md",
"docs/content/EQUIPMENT_CATALOG.md",
"docs/presentation/SCREEN_AND_STATE_MAP.md",
"docs/presentation/UI_AND_VISUAL_SYSTEM.md",
"docs/presentation/ART_ASSET_AND_ANIMATION_CATALOG.md",
]
FORBIDDEN = [
r"three equipment slots",
r"all three equipment slots",
r"starting_equipment_ids\[\]",
r"one or two starter items",
r"two starter items",
r"two-item starts",
r"Fallen Recruit owns three items",
r"all three item IDs returned",
r"another equipped item",
r"three different equipped trigger tags",
]
def main() -> int:
errors: list[str] = []
corpus = "\n".join(
(ROOT / path).read_text(encoding="utf-8") for path in AUTHORITY_PATHS
)
lowered = corpus.lower()
for pattern in FORBIDDEN:
if re.search(pattern, lowered):
errors.append(f"forbidden multi-item contract remains: {pattern}")
required = {
"one universal active equipment slot": "systems one-slot authority",
"starting_equipment_id, level, xp": "Recruit schema",
"level cap 10": "individual XP authority",
"one-slot replacement receipt": "save transaction",
"角色独立等级": "Chinese UI level label",
"唯一装备槽": "Chinese UI equipment label",
"XP 44/50": "visible current XP progress",
"EQ-014 退回本轮库存": "visible replacement return",
}
ui_text = (ROOT / "prototype/site/app.js").read_text(encoding="utf-8")
combined = f"{corpus}\n{ui_text}"
for phrase, label in required.items():
if phrase not in combined:
errors.append(f"missing {label}: {phrase}")
equipment = (ROOT / "docs/content/EQUIPMENT_CATALOG.md").read_text(
encoding="utf-8"
)
ids = re.findall(r"^\| `EQ-(\d{3})`", equipment, flags=re.MULTILINE)
if len(ids) != 320 or len(set(ids)) != 320:
errors.append(f"equipment identity count changed: rows={len(ids)} unique={len(set(ids))}")
if errors:
for error in errors:
print(f"ERROR: {error}")
return 1
print(
"ABG_RECRUIT_LOADOUT_LEVEL_OK "
"equipment_slots=1 level_scope=individual level_cap=10 equipment=320"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())