"""Text-only coverage gate for every visual kind currently present in the game.""" from __future__ import annotations import argparse from collections import defaultdict from pathlib import Path import sys import yaml ROOT = Path(__file__).resolve().parents[1] COVERAGE = ROOT / "assets/content_coverage.yaml" MANIFESTS = ROOT / "assets/manifests" def declared_assets() -> set[str]: result: set[str] = set() for path in sorted(MANIFESTS.glob("*.yaml")): raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {} result.update(key for key, value in raw.items() if key not in {"version", "defaults"} and isinstance(value, dict)) return result def requirements(spec: dict) -> dict[str, list[str]]: result: dict[str, list[str]] = defaultdict(list) agent = spec["agents"] for slot, components in agent["components"].items(): for component in components: for animation in agent["animations"]: result[f"agent/{slot}"].append(f"agent_{component}_{animation}") for kind in spec["enemies"]["kinds"]: for animation in spec["enemies"]["animations"]: result["enemy"].append(f"{kind}_{animation}") result["environment"].extend(spec["environment"]["required"]) result["item_icon"].extend(spec["item_icons"]["required"]) return result def report(release: bool) -> int: spec = yaml.safe_load(COVERAGE.read_text(encoding="utf-8")) if spec.get("version") != 1: raise ValueError("assets/content_coverage.yaml: unsupported version") have = declared_assets() groups = requirements(spec) missing_total = 0 print(f"{'GROUP':<20} {'HAVE':>5} {'NEED':>5} {'MISSING':>7}") print("-" * 40) for group, wanted in groups.items(): missing = sorted(set(wanted) - have) missing_total += len(missing) print(f"{group:<20} {len(wanted)-len(missing):>5} {len(wanted):>5} {len(missing):>7}") for asset in missing: print(f" - {asset}") covered = sum(len(v) for v in groups.values()) - missing_total total = sum(len(v) for v in groups.values()) print(f"coverage: {covered}/{total}; missing={missing_total}") return 1 if release and missing_total else 0 def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--release", action="store_true", help="fail if any required asset is absent") return report(parser.parse_args().release) if __name__ == "__main__": try: raise SystemExit(main()) except (OSError, ValueError, KeyError) as error: print(f"ERROR: {error}", file=sys.stderr) raise SystemExit(2)