80 lines
4.4 KiB
Python
80 lines
4.4 KiB
Python
"""Audit every installed Endgame room against stage tables and Client metadata.
|
|
|
|
This reads data only. It does not establish that a room or battle plays in the
|
|
Client, nor does it validate reward/settlement callbacks.
|
|
"""
|
|
import argparse
|
|
import collections
|
|
import json
|
|
import pathlib
|
|
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--root", type=pathlib.Path, default=pathlib.Path(__file__).resolve().parents[1])
|
|
parser.add_argument("--output", type=pathlib.Path, required=True)
|
|
args = parser.parse_args()
|
|
def read(name):
|
|
return json.loads((args.root / name).read_text(encoding="utf-8"))
|
|
metadata = read("resources/SceneMetadata.json")["floors"]
|
|
entrances = {row["ID"]: row for row in read("resources/MapEntrance.json")["map_entrance_config"]}
|
|
stages = {row["StageID"]: row for row in read("resources/StageConfig.json")["stage_config"]}
|
|
root_scenes = read("res.json")["levelOutputConfigs"]
|
|
supplementary = {row["entryID"]: row for row in read("resources/res.json")["scene_config"]}
|
|
checks = []
|
|
def check(mode, identifier, node, entry, group, events, config_ids):
|
|
problems = []
|
|
entrance = entrances.get(entry)
|
|
floor_id = entrance["FloorID"] if entrance else None
|
|
floor = metadata.get(str(floor_id))
|
|
if floor is None or len(floor["groups"]) != floor["group_count"]:
|
|
problems.append("missing or incomplete Client floor metadata")
|
|
else:
|
|
group_data = floor["groups"].get(str(group))
|
|
if not group_data or group_data["is_deleted"] or group_data["load_side"] == "Client":
|
|
problems.append("invalid server group")
|
|
if not floor.get("start_anchor"):
|
|
problems.append("missing Client start anchor")
|
|
placements = floor.get("monsters", {}).get(str(group), {})
|
|
for config_id in config_ids:
|
|
if str(config_id) not in placements:
|
|
problems.append(f"missing Client monster placement {config_id}")
|
|
source = root_scenes.get(str(entry), {})
|
|
if source:
|
|
monsters = [m for scene in source.values() for m in scene["scenes"].get(str(group), {}).get("monsters", [])]
|
|
else:
|
|
monsters = [m for m in supplementary.get(entry, {}).get("monsters", []) if m["groupId"] == group]
|
|
if not monsters:
|
|
problems.append("missing scene monster source used by Go loader")
|
|
for event in events:
|
|
stage = stages.get(event)
|
|
if not stage or not stage["MonsterList"] or any(not wave for wave in stage["MonsterList"]):
|
|
problems.append(f"missing stage waves {event}")
|
|
checks.append({"mode": mode, "id": identifier, "node": node, "entry": entry,
|
|
"floor": floor_id, "group": group, "events": events, "issues": problems})
|
|
|
|
maze = read("resources/ChallengeMazeConfig.json")["challenge_config"]
|
|
tierce = read("resources/ChallengeMazeTierceConfig.json")["challenge_tierce_config"]
|
|
peak = read("resources/ChallengePeakConfig.json")["challenge_peak_config"]
|
|
for row in maze:
|
|
mode = "PF" if 20000 < row["ID"] < 30000 else "AS" if row["ID"] > 30000 else "Memory/MoC"
|
|
for index in (1, 2):
|
|
suffix = str(index)
|
|
if row.get("EventIDList" + suffix):
|
|
check(mode, row["ID"], index - 1, row["MapEntranceID" + ("2" if index == 2 else "")],
|
|
row["MazeGroupID" + suffix], row["EventIDList" + suffix], row["ConfigList" + suffix])
|
|
for row in tierce:
|
|
check("Tierce", row["ID"], 2, row["MapEntranceID"], row["MazeGroupID"], row["EventIDList"], row["ConfigList"])
|
|
for row in peak:
|
|
# Peak's selected monster instance is resolved from its configured group.
|
|
check("Peak", row["ID"], 0, row["MapEntranceID"], row["MazeGroupID"], row["EventIDList"], [])
|
|
by_id = {row["ID"]: row for row in peak}
|
|
for row in read("resources/ChallengePeakBossConfig.json")["challenge_peak_boss_config"]:
|
|
base = by_id[row["ID"]]
|
|
check("Peak hard", row["ID"], 0, base["MapEntranceID"], base["MazeGroupID"], row["HardEventIDList"], [])
|
|
report = {"scope": "resource and Client metadata audit; no gameplay verification",
|
|
"counts": dict(collections.Counter(row["mode"] for row in checks)),
|
|
"checked_rooms": len(checks), "floors": len({row["floor"] for row in checks}),
|
|
"issues": [row for row in checks if row["issues"]]}
|
|
args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
|
print(json.dumps({key: value for key, value in report.items() if key != "issues"}))
|
|
print(f"Issues: {len(report['issues'])}; details: {args.output}")
|