#!/usr/bin/env python3 """Audit active 40250 project sources against parity contract ownership. This is a coverage gate, not a parity verifier. A file inferred to a contract still requires that contract's branch-by-branch review; the report deliberately labels inferred ownership separately from exact manifest references. """ from __future__ import annotations import argparse import json import pathlib import re import xml.etree.ElementTree as ET from collections import Counter, defaultdict import refroot # script directory is on sys.path when run directly REFERENCE_DIRS = ( "UserInterface", "GameLib", "EffectLib", "EterLib", "EterGrnLib", "PRTerrainLib", "MilesLib", "EterPack", "EterLocale", ) SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".h", ".hpp", ".inl"} IGNORED_BASENAMES = {"stdafx.cpp", "stdafx.h", "stdafx.hxx", "stdafx.hpp"} REVIEWED_STATIC_GROUPS = { "EffectLib", "EterGrnLib", "MilesLib", "GameLib", "PRTerrainLib", "EterPack", "EterLocale", "EterLib", } REVIEWED_STATIC_FILES = { "UserInterface/PythonNetworkStreamCommand.cpp", "EterLib/Camera.cpp", "EterLib/Camera.h", "EterLib/IME.cpp", "EterLib/IME.h", "EterLib/Input.h", "EterLib/msctf.h", "EterLib/Resource.cpp", "EterLib/Resource.h", "EterLib/ResourceManager.cpp", "EterLib/ResourceManager.h", "EterLib/FileLoaderThread.cpp", "EterLib/FileLoaderThread.h", "EterLib/TextFileLoader.cpp", "EterLib/TextFileLoader.h", "EterLib/parser.cpp", "EterLib/parser.h", "EterLib/PathStack.cpp", "EterLib/PathStack.h", "EterLib/SkyBox.cpp", "EterLib/SkyBox.h", "EterLib/ScreenFilter.cpp", "EterLib/ScreenFilter.h", "EterLib/LensFlare.cpp", "EterLib/LensFlare.h", "EterLib/ColorTransitionHelper.cpp", "EterLib/ColorTransitionHelper.h", "EterLib/AttributeData.cpp", "EterLib/AttributeData.h", "EterLib/AttributeInstance.h", "EterLib/CollisionData.cpp", "EterLib/CollisionData.h", "EterLib/Decal.cpp", "EterLib/Decal.h", "EterLib/CullingManager.cpp", "EterLib/CullingManager.h", "EterLib/GrpText.cpp", "EterLib/GrpText.h", "EterLib/GrpTextInstance.cpp", "EterLib/GrpTextInstance.h", "EterLib/TextBar.cpp", "EterLib/TextBar.h", "EterLib/TextTag.cpp", "EterLib/TextTag.h", "EterLib/GrpFontTexture.cpp", "EterLib/GrpFontTexture.h", "EterLib/GrpMarkInstance.cpp", "EterLib/GrpMarkInstance.h", "EterLib/GrpDevice.cpp", "EterLib/GrpDevice.h", "EterLib/GrpScreen.cpp", "EterLib/GrpScreen.h", "EterLib/GrpShadowTexture.cpp", "EterLib/GrpShadowTexture.h", "UserInterface/AbstractApplication.h", "UserInterface/AbstractSingleton.h", "UserInterface/CheckLatestFiles.h", "UserInterface/ProcessCRC.cpp", "UserInterface/ProcessCRC.h", "UserInterface/ProcessScanner.cpp", "UserInterface/ProcessScanner.h", "UserInterface/HackShield.cpp", "UserInterface/Hackshield.h", "UserInterface/HackshieldLicense.h", "UserInterface/HackshieldLicense_metin2.h", "UserInterface/HackshieldLicense_metin2client.h", "UserInterface/NProtectGameGuard.cpp", "UserInterface/NProtectGameGuard.h", "UserInterface/PythonApplicationLogo.cpp", "UserInterface/PythonApplicationModule.cpp", "UserInterface/PythonApplicationWebPage.cpp", "UserInterface/PythonExceptionSender.cpp", "UserInterface/PythonExceptionSender.h", "UserInterface/PythonProfilerModule.cpp", "UserInterface/PythonSystem.cpp", "UserInterface/PythonSystem.h", "UserInterface/PythonSystemModule.cpp", "UserInterface/MovieMan.cpp", "UserInterface/MovieMan.h", "UserInterface/UserInterface.cpp", "UserInterface/Test.h", "UserInterface/resource.h", "UserInterface/WiseLogicXTrap.cpp", "UserInterface/WiseLogicXTrap.h", "UserInterface/AbstractCharacterManager.h", "UserInterface/AbstractChat.h", "UserInterface/AbstractPlayer.h", "UserInterface/AccountConnector.h", "UserInterface/AffectFlagContainer.h", "UserInterface/CameraProcedure.cpp", "UserInterface/Discord.h", "UserInterface/GameType.cpp", "UserInterface/InstanceBaseEvent.cpp", "UserInterface/InstanceBaseTransform.cpp", "UserInterface/NetworkActorManager.h", "UserInterface/PythonBackground.h", "UserInterface/PythonBackgroundModule.cpp", "UserInterface/PythonCharacterManager.h", "UserInterface/PythonCharacterManagerModule.cpp", "UserInterface/PythonCharacterModule.cpp", "UserInterface/PythonNonPlayer.h", "UserInterface/PythonNonPlayerModule.cpp", "UserInterface/PythonPlayerEventHandler.h", "UserInterface/GuildMarkDownloader.h", "UserInterface/GuildMarkUploader.h", "UserInterface/MarkImage.cpp", "UserInterface/MarkImage.h", "UserInterface/MarkManager.h", "UserInterface/PythonGuild.h", "UserInterface/PythonMessenger.h", "UserInterface/InsultChecker.cpp", "UserInterface/InsultChecker.h", "UserInterface/PythonChatModule.cpp", "UserInterface/PythonTextTailModule.cpp", "UserInterface/PythonEventManagerMoudle.cpp", "UserInterface/PythonGameEventManagerModule.cpp", "UserInterface/PythonQuest.h", "UserInterface/PythonExchange.h", "UserInterface/PythonExchangeModule.cpp", "UserInterface/PythonSafeBox.h", "UserInterface/PythonShop.h", "UserInterface/PythonIME.h", "UserInterface/PythonIMEModule.cpp", "UserInterface/PythonMiniMapModule.cpp", "UserInterface/PythonNetworkDatagram.cpp", "UserInterface/PythonNetworkDatagram.h", "UserInterface/PythonNetworkDatagramModule.cpp", "UserInterface/PythonNetworkStream.h", "UserInterface/PythonNetworkStreamPhaseOffline.cpp", "UserInterface/ServerStateChecker.h", "UserInterface/ServerStateCheckerModule.cpp", "UserInterface/PythonSkill.h", "UserInterface/Locale.cpp", "UserInterface/Locale.h", "UserInterface/Locale_inc_BR.h", "UserInterface/Locale_inc_CA.h", "UserInterface/Locale_inc_EU.h", "UserInterface/Locale_inc_EU2.h", "UserInterface/Locale_inc_GB.h", "UserInterface/Locale_inc_HK.h", "UserInterface/Locale_inc_JP.h", "UserInterface/Locale_inc_KO.h", "UserInterface/Locale_inc_KO_DEV.h", "UserInterface/Locale_inc_NEWCIBN.h", "UserInterface/Locale_inc_SG.h", "UserInterface/Locale_inc_VI.h", "UserInterface/Locale_inc_dev.h", } def normalize(path: pathlib.Path, root: pathlib.Path) -> str: return path.resolve().relative_to(root.resolve()).as_posix() def manifest_data(repo: pathlib.Path) -> tuple[set[str], dict[str, list[str]]]: data = json.loads((repo / "audit/manifest.json").read_text(encoding="utf-8")) listed: set[str] = set() owners: dict[str, list[str]] = defaultdict(list) for contract in data["contracts"]: cid = contract["id"] for raw in contract.get("reference", {}).get("files", []): path = raw.replace("\\", "/") listed.add(path) owners[path].append(cid) return listed, owners def active_project_sources(reference_root: pathlib.Path, dirs: tuple[str, ...] = REFERENCE_DIRS) -> set[str]: active: set[str] = set() for project in sorted((reference_root.parent / "vs_files").glob("*/*.vcxproj")): try: tree = ET.parse(project) except ET.ParseError: continue for node in tree.iter(): if not (node.tag.endswith("ClCompile") or node.tag.endswith("ClInclude")): continue include = node.attrib.get("Include", "") if not include: continue candidate = (project.parent / pathlib.Path(include.replace("\\", "/"))).resolve() try: relative = candidate.relative_to(reference_root.resolve()) except ValueError: continue if relative.parts and relative.parts[0] in dirs: if relative.suffix.lower() in SOURCE_SUFFIXES: active.add(relative.as_posix()) return active def infer_owners(path: str) -> list[str]: """Infer behavior ownership for a source not explicitly listed in manifest. These rules are intentionally broad. They identify review queues, never upgrade a contract status or claim semantic equivalence. """ lower = path.lower() area, _, name = lower.partition("/") stem = pathlib.PurePosixPath(name).stem owners: list[str] = [] def add(*ids: str) -> None: for cid in ids: if cid not in owners: owners.append(cid) if area == "effectlib": add("render.effect_particle_motion") elif area == "etergrnlib": add("render.model_attachment") elif area == "mileslib": add("audio.bgm_ambience_3d") elif area == "eterpack" or area == "eterlocale": add("resource.pack_locale_proto") elif area == "prterrainlib": add("world.terrain_water_collision") elif area == "eterlib": if any(x in stem for x in ("net", "packet")): add("network.packet_dispatch_protocol") if any(x in stem for x in ("camera", "input", "ime")): add("ui.input_camera_mobile") if any(x in stem for x in ("attribute", "collision", "ray", "intersect", "decal")): add("world.terrain_water_collision", "movement.click_collision", "combat.flying_projectile") if any(x in stem for x in ("sky", "lensflare", "screenfilter", "colortransition")): add("world.environment_weather") if any(x in stem for x in ("text", "font", "tag")): add("ui.hud_target_minimap_status", "social.chat_text_tail") if "mark" in stem: add("social.party_guild_messenger") if any(x in stem for x in ("resource", "path", "fileloader", "parser", "image", "targa", "jpeg")): add("resource.pack_locale_proto") if any(x in stem for x in ("device", "state", "grp", "culling", "shadow", "blocktexture")): add("render.model_attachment", "lifecycle.platform_release") if any(x in stem for x in ("msapplication", "mswindow", "mutex", "thread", "referenceobject", "profiler")): add("lifecycle.platform_release") if any(x in stem for x in ("dimm", "dynamic", "event", "funcobject", "pool", "ref", "util")): add("lifecycle.platform_release") if "dibbar" in stem: add("resource.pack_locale_proto", "ui.hud_target_minimap_status") if "msctf" in stem: add("ui.input_camera_mobile") elif area == "gamelib": if any(x in stem for x in ("fly", "flying")): add("combat.flying_projectile") if any(x in stem for x in ("weapontrace", "battle")): add("combat.attack_combo_damage") if "actorinstance" in stem or any(x in stem for x in ("motion", "blend", "rotation", "position")): add("movement.motion_resource_events", "render.model_attachment", "skill.cast.effect_timing") if any(x in stem for x in ("map", "terrain", "physics")): add("world.terrain_water_collision", "world.map_load_transition") if "dungeon" in stem: add("world.dungeon_activity") if "snow" in stem: add("world.environment_weather") if any(x in stem for x in ("property", "monsterarea")): add("world.map_load_transition", "world.npc_drop_actor_spawn") if any(x in stem for x in ("race", "item")): add("resource.pack_locale_proto", "render.model_attachment") if not owners: add("world.map_load_transition") elif area == "userinterface": if any(x in stem for x in ("chat", "texttail", "insult")): add("social.chat_text_tail") if any(x in stem for x in ("character", "character_manager")): add("network.character_select.create_delete", "social.party_guild_messenger") if any(x in stem for x in ("eventmanager", "quest")): add("quest.quest_event_navigation") if any(x in stem for x in ("ime", "input", "cursor")): add("ui.input_camera_mobile") if "serverstate" in stem or "account" in stem: add("network.error_timeout_server_state", "network.login.phase_flow") if any(x in stem for x in ("locale", "language")): add("resource.pack_locale_proto") if any(x in stem for x in ("mark", "guild", "messenger")): add("social.party_guild_messenger") if any(x in stem for x in ("exchange", "safebox", "shop")): add("social.exchange_safebox", "npc.dialog_shop") if any(x in stem for x in ("application", "exception", "crc", "process", "movie", "system")): add("lifecycle.bootstrap_phase_host", "lifecycle.platform_release") if "background" in stem: add("world.map_load_transition", "world.environment_weather") if "minimap" in stem: add("ui.hud_target_minimap_status") if "nonplayer" in stem: add("world.npc_drop_actor_spawn") if any(x in stem for x in ("networkstream", "networkdatagram")): add("network.packet_dispatch_protocol", "network.reconnect_warp") if "player" in stem or "instancebase" in stem: add("movement.keyboard.motion", "combat.attack_combo_damage", "world.entity_spawn_state_sync") if "skill" in stem: add("skill.player_ui_cooldown", "skill.cast.effect_timing") if "item" in stem: add("item.inventory_equipment_state", "item.consumable_action", "item.refine_socket_costume") if "pythontexttail" in stem: add("ui.hud_target_minimap_status") if "profiler" in stem: add("lifecycle.platform_release") if not owners: add("lifecycle.bootstrap_phase_host") return owners def build(repo: pathlib.Path) -> dict: reference_root = refroot.reference_root(repo) listed, exact_owners = manifest_data(repo) active = active_project_sources(reference_root) rows = [] for path in sorted(active): if pathlib.PurePosixPath(path).name.lower() in IGNORED_BASENAMES: kind = "build_support" owners = [] elif path in exact_owners: kind = "manifest_exact" owners = exact_owners[path] else: kind = "inferred_review_queue" owners = infer_owners(path) if path in REVIEWED_STATIC_FILES or pathlib.PurePosixPath(path).parts[0] in REVIEWED_STATIC_GROUPS: kind = "reviewed_static_group" rows.append({"path": path, "kind": kind, "owners": owners}) counts = Counter(row["kind"] for row in rows) unowned = [row["path"] for row in rows if not row["owners"] and row["kind"] != "build_support"] by_contract: dict[str, dict[str, int]] = defaultdict(lambda: Counter()) for row in rows: for owner in row["owners"]: by_contract[owner][row["kind"]] += 1 return { "reference_root": json.loads((repo / "audit/manifest.json").read_text(encoding="utf-8"))["reference_root"], "active_source_count": len(rows), "manifest_exact_count": counts["manifest_exact"], "inferred_review_queue_count": counts["inferred_review_queue"], "reviewed_static_group_count": counts["reviewed_static_group"], "build_support_count": counts["build_support"], "unowned_behavior_candidates": unowned, "by_contract": {k: dict(v) for k, v in sorted(by_contract.items())}, "files": rows, } def markdown(data: dict) -> str: lines = [ "# Active 40250 source coverage", "", "This report is a coverage gate, not proof of parity. `manifest_exact` means the source is explicitly listed by a contract; `inferred_review_queue` means it is assigned by a broad area rule and still requires semantic review.", "", f"- Active Visual Studio source/include files: **{data['active_source_count']}**", f"- Explicitly listed by manifest: **{data['manifest_exact_count']}**", f"- Static-reviewed groups (still PARTIAL for parity): **{data['reviewed_static_group_count']}**", f"- Inferred review queue: **{data['inferred_review_queue_count']}**", f"- Build support (`StdAfx`): **{data['build_support_count']}**", f"- Unowned behavior candidates: **{len(data['unowned_behavior_candidates'])}**", "", "## Review queue by contract", "", "| Contract | Exact manifest files | Static-reviewed files | Inferred files |", "|---|---:|---:|---:|", ] for cid, counts in data["by_contract"].items(): lines.append(f"| `{cid}` | {counts.get('manifest_exact', 0)} | {counts.get('reviewed_static_group', 0)} | {counts.get('inferred_review_queue', 0)} |") lines.extend(["", "## Unowned behavior candidates", ""]) if data["unowned_behavior_candidates"]: lines.extend(f"- `{p}`" for p in data["unowned_behavior_candidates"]) else: lines.append("None") lines.extend([ "", "## Required interpretation", "", "The inferred queue is not considered audited merely because a rule assigned it to a contract. Each queue must be reviewed through the contract's reference call chain and either added to exact evidence, explicitly classified as support/disabled/vendor code, or recorded as a remaining gap. Static-reviewed groups have had their active symbols and lifecycle call chain checked in the current round, but their contracts remain PARTIAL until implementation equivalence and tests close the documented gaps.", "", ]) return "\n".join(lines) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--write", action="store_true") args = parser.parse_args() repo = pathlib.Path(__file__).resolve().parents[4] data = build(repo) if args.write: (repo / "audit/source-coverage.json").write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") (repo / "audit/reports/source-coverage.md").write_text(markdown(data), encoding="utf-8") else: print(markdown(data)) return 0 if not data["unowned_behavior_candidates"] else 2 if __name__ == "__main__": raise SystemExit(main())