Add world scenario validation coverage
This commit is contained in:
178
scripts/validate_runtime_scenarios.py
Executable file
178
scripts/validate_runtime_scenarios.py
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SETTING_TEXTURESET_RE = re.compile(r"^TextureSet\s+(.+)$", re.IGNORECASE)
|
||||
SETTING_ENV_RE = re.compile(r"^Environment\s+(.+)$", re.IGNORECASE)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MapCheck:
|
||||
pack: str
|
||||
map_dir: str
|
||||
setting_ok: bool
|
||||
mapproperty_ok: bool
|
||||
textureset_ref: str | None
|
||||
textureset_exists: bool
|
||||
environment_ref: str | None
|
||||
environment_exists: bool
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate cross-pack runtime references for Metin2 map/world content."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--runtime-root",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Client runtime root containing assets/.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pack",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Specific outdoor/world asset directory to validate. Repeatable. Defaults to common world packs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Emit JSON output.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def normalize_virtual_path(value: str) -> str:
|
||||
value = value.strip().strip('"').replace("\\", "/").lower()
|
||||
prefixes = ["d:/", "d:"]
|
||||
for prefix in prefixes:
|
||||
if value.startswith(prefix):
|
||||
value = value[len(prefix):]
|
||||
break
|
||||
return value.lstrip("/")
|
||||
|
||||
|
||||
def resolve_textureset_path(runtime_assets: Path, ref: str) -> Path:
|
||||
rel = normalize_virtual_path(ref)
|
||||
if not rel.startswith("textureset/"):
|
||||
rel = f"textureset/{rel}"
|
||||
return runtime_assets / "textureset" / rel
|
||||
|
||||
|
||||
def resolve_environment_path(runtime_assets: Path, ref: str) -> Path:
|
||||
rel = normalize_virtual_path(ref)
|
||||
if "/" not in rel:
|
||||
rel = f"ymir work/environment/{rel}"
|
||||
return runtime_assets / "ETC" / rel
|
||||
|
||||
|
||||
def validate_map_dir(pack_name: str, map_dir: Path, runtime_assets: Path) -> MapCheck:
|
||||
setting_path = map_dir / "setting.txt"
|
||||
mapproperty_path = map_dir / "mapproperty.txt"
|
||||
|
||||
textureset_ref = None
|
||||
environment_ref = None
|
||||
textureset_exists = False
|
||||
environment_exists = False
|
||||
|
||||
if setting_path.is_file():
|
||||
for raw_line in setting_path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line:
|
||||
continue
|
||||
texture_match = SETTING_TEXTURESET_RE.match(line)
|
||||
if texture_match:
|
||||
textureset_ref = texture_match.group(1).strip()
|
||||
textureset_exists = resolve_textureset_path(runtime_assets, textureset_ref).is_file()
|
||||
continue
|
||||
environment_match = SETTING_ENV_RE.match(line)
|
||||
if environment_match:
|
||||
environment_ref = environment_match.group(1).strip()
|
||||
environment_exists = resolve_environment_path(runtime_assets, environment_ref).is_file()
|
||||
|
||||
return MapCheck(
|
||||
pack=pack_name,
|
||||
map_dir=map_dir.name,
|
||||
setting_ok=setting_path.is_file(),
|
||||
mapproperty_ok=mapproperty_path.is_file(),
|
||||
textureset_ref=textureset_ref,
|
||||
textureset_exists=textureset_exists if textureset_ref else False,
|
||||
environment_ref=environment_ref,
|
||||
environment_exists=environment_exists if environment_ref else False,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
runtime_root = args.runtime_root.resolve()
|
||||
runtime_assets = runtime_root / "assets"
|
||||
if not runtime_assets.is_dir():
|
||||
raise SystemExit(f"assets dir not found: {runtime_assets}")
|
||||
|
||||
packs = args.pack or [
|
||||
"OutdoorA1",
|
||||
"OutdoorA2",
|
||||
"OutdoorA3",
|
||||
"OutdoorB1",
|
||||
"OutdoorB3",
|
||||
"OutdoorC1",
|
||||
"OutdoorC3",
|
||||
"OutdoorSnow1",
|
||||
"outdoordesert1",
|
||||
"outdoorflame1",
|
||||
"outdoorfielddungeon1",
|
||||
]
|
||||
|
||||
checks: list[MapCheck] = []
|
||||
failures: list[str] = []
|
||||
|
||||
for pack_name in packs:
|
||||
pack_dir = runtime_assets / pack_name
|
||||
if not pack_dir.is_dir():
|
||||
failures.append(f"missing pack dir: {pack_name}")
|
||||
continue
|
||||
|
||||
map_dirs = sorted([p for p in pack_dir.iterdir() if p.is_dir()])
|
||||
if not map_dirs:
|
||||
failures.append(f"no map dirs in pack: {pack_name}")
|
||||
continue
|
||||
|
||||
for map_dir in map_dirs:
|
||||
check = validate_map_dir(pack_name, map_dir, runtime_assets)
|
||||
checks.append(check)
|
||||
if not check.setting_ok:
|
||||
failures.append(f"{pack_name}/{map_dir.name}: missing setting.txt")
|
||||
if not check.mapproperty_ok:
|
||||
failures.append(f"{pack_name}/{map_dir.name}: missing mapproperty.txt")
|
||||
if not check.textureset_ref or not check.textureset_exists:
|
||||
failures.append(f"{pack_name}/{map_dir.name}: missing textureset target for {check.textureset_ref!r}")
|
||||
if not check.environment_ref or not check.environment_exists:
|
||||
failures.append(f"{pack_name}/{map_dir.name}: missing environment target for {check.environment_ref!r}")
|
||||
|
||||
result = {
|
||||
"ok": not failures,
|
||||
"checked_map_dirs": len(checks),
|
||||
"packs": packs,
|
||||
"failures": failures,
|
||||
"checks": [asdict(check) for check in checks],
|
||||
}
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print(f"ok={result['ok']} checked_map_dirs={result['checked_map_dirs']}")
|
||||
for failure in failures:
|
||||
print(f"FAIL: {failure}")
|
||||
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user