Files

79 lines
4.4 KiB
Python
Raw Permalink Normal View History

"""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}")