- SKILL.md: logic units port into extension/src/port/<Lib>/<File>, platform adapters under extension/src/platform; legacy GDScript owners are migration sources; python layer frozen until the embedding decision - port_map.py: logic/python/platform layers with per-layer progress, queue --layer, LEGACY layout check; inventory adds EterBase, EterPythonLib, ScriptLib, SpeedTreeLib, SphereLib, EterImageLib - py_embed_spike.py + docs/PYTHON-EMBED-EVAL.md: the 40250 system.py bootstrap loads 67/74 root modules unchanged under CPython 2.7 with stub natives Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
351 lines
18 KiB
Python
351 lines
18 KiB
Python
#!/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())
|