docs(parity): mirror 40250 layout for the logic layer; evaluate embedded Python UI

- 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>
This commit is contained in:
shenlei
2026-09-22 18:06:32 +09:00
co-authored by Claude Opus 5
parent a989b8f19b
commit 9d0e50de8f
7 changed files with 285 additions and 37 deletions
@@ -5,11 +5,16 @@ Units are the active 40250 C++ sources (from the .vcxproj files) plus the 40250
root (`../../Client/Eternexus/root/*.py` relative to the reference root). Port-map entries
live in `audit/port-map/<unit path>.json`; functions without an entry count as TODO.
port_map.py status [--lib UserInterface] coverage per library (and per unit with --units)
port_map.py queue [--limit 20] next units by priority, then by TODO count
port_map.py status [--lib UserInterface] coverage per library and per layer (per unit with --units)
port_map.py queue [--limit 20] [--layer L] next units by priority, then by TODO count (default layer: logic)
port_map.py init <unit> [<unit>...] create or extend entries (new functions as TODO)
port_map.py show <unit> per-function status of one unit
port_map.py check stale hashes, unknown functions, tag mismatches
port_map.py check stale hashes, unknown functions, tag mismatches, layout
Layers: `logic` (ported into extension/src/port/<Lib>/<File>, same names as 40250), `python`
(the embedded-Python track: Python binding modules, EterPythonLib window system, ScriptLib and the
Python root) and `platform` (Direct3D/Granny/Miles/Win32 rendering, audio and OS plumbing, covered
by adapters). Progress is reported per layer so platform code does not dilute the logic figure.
"""
from __future__ import annotations
@@ -24,11 +29,34 @@ from collections import Counter
from pathlib import Path
import refroot # script directory is on sys.path when run directly
from audit_source_coverage import active_project_sources
from audit_source_coverage import REFERENCE_DIRS, active_project_sources
STATUSES = ("TODO", "PORTED", "ADAPTED", "N_A", "DIVERGENT", "NEEDS_LIVE")
DONE = {"PORTED", "ADAPTED", "N_A"}
PY_ROOT = "../../Client/Eternexus/root"
UNIT_DIRS = REFERENCE_DIRS + ("EterBase", "EterPythonLib", "ScriptLib", "SpeedTreeLib", "SphereLib", "EterImageLib")
PORT_DIR = "extension/src/port"
LAYERS = ("logic", "python", "platform")
# First matching pattern wins; anything unmatched is `logic`.
LAYER_PATTERNS = [
("python", [f"{PY_ROOT}/*.py", "EterPythonLib/*", "ScriptLib/*", "UserInterface/*Module.cpp",
"UserInterface/*Moudle.cpp", "UserInterface/PythonSkill.cpp", "UserInterface/PythonGuild.cpp",
"UserInterface/PythonShop.cpp", "UserInterface/PythonSafeBox.cpp", "UserInterface/PythonQuest.cpp",
"UserInterface/PythonMessenger.cpp"]),
("platform", ["EterGrnLib/*", "MilesLib/*", "SpeedTreeLib/*", "SphereLib/*", "EterImageLib/*", "EffectLib/*",
"PRTerrainLib/*", "EterLib/Grp*.cpp", "EterLib/StateManager.cpp", "EterLib/MSApplication.cpp",
"EterLib/MSWindow.cpp", "EterLib/Input.cpp", "EterLib/IME.cpp", "EterLib/SkyBox.cpp",
"EterLib/LensFlare.cpp", "EterLib/ScreenFilter.cpp", "EterLib/Decal.cpp", "EterLib/EnvironmentMap.cpp",
"EterLib/JpegFile.cpp", "EterLib/TargaResource.cpp", "EterLib/DibBar.cpp", "EterLib/BlockTexture.cpp",
"EterLib/CullingManager.cpp", "EterLib/Thread.cpp", "EterLib/Mutex.cpp", "EterLib/FileLoaderThread.cpp",
"EterLib/TextBar.cpp", "EterBase/*", "GameLib/MapOutdoorRender*.cpp", "GameLib/MapOutdoorWater.cpp",
"GameLib/SnowParticle.cpp", "GameLib/SnowEnvironment.cpp", "GameLib/TerrainDecal.cpp",
"GameLib/TerrainPatch.cpp", "UserInterface/PythonApplicationLogo.cpp", "UserInterface/MovieMan.cpp",
"UserInterface/HackShield.cpp", "UserInterface/NProtectGameGuard.cpp", "UserInterface/ProcessCRC.cpp",
"UserInterface/ProcessScanner.cpp", "UserInterface/WiseLogicXTrap.cpp",
"UserInterface/PythonApplicationWebPage.cpp", "UserInterface/PythonExceptionSender.cpp",
"UserInterface/UserInterface.cpp", "UserInterface/CheckLatestFiles.cpp"]),
]
# Order follows SKILL.md "Priority"; the first matching pattern wins.
PRIORITY = [
@@ -100,7 +128,8 @@ def py_functions(path: Path) -> list[str]:
def inventory(ref: Path) -> dict[str, Path]:
units = {p: ref / p for p in sorted(active_project_sources(ref)) if p.endswith(".cpp")}
units = {p: ref / p for p in sorted(active_project_sources(ref, UNIT_DIRS))
if p.endswith(".cpp") and not p.lower().endswith("stdafx.cpp")}
py_dir = (ref / PY_ROOT).resolve()
if py_dir.is_dir():
for p in sorted(py_dir.glob("*.py")):
@@ -119,6 +148,18 @@ def priority(unit: str) -> str:
return DEFAULT_PRIORITY
def layer(unit: str) -> str:
for name, patterns in LAYER_PATTERNS:
if any(fnmatch.fnmatch(unit, pat) for pat in patterns):
return name
return "logic"
def port_file(unit: str) -> str:
"""Expected implementation path stem for a `logic` unit (`extension/src/port/GameLib/ActorInstance`)."""
return f"{PORT_DIR}/{unit.rsplit('.', 1)[0]}"
def entry_path(repo: Path, unit: str) -> Path:
rel = unit.replace("../../Client/Eternexus/", "Client/")
return repo / "audit" / "port-map" / (rel + ".json")
@@ -162,6 +203,9 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
per_lib.setdefault(lib, Counter()).update(counts)
rows.append((unit, counts))
total = Counter()
per_layer: dict[str, Counter] = {}
for unit, counts in rows:
per_layer.setdefault(layer(unit), Counter()).update(counts)
print(f"{'library':<16}{'funcs':>7}{'done':>7} " + " ".join(f"{s:>10}" for s in STATUSES))
for lib, c in sorted(per_lib.items()):
total.update(c)
@@ -172,6 +216,13 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
done = sum(total[s] for s in DONE)
print(f"{'TOTAL':<16}{n:>7}{done:>7} " + " ".join(f"{total[s]:>10}" for s in STATUSES)
+ f" ({100.0 * done / max(n, 1):.1f}% done)")
print()
for name in LAYERS:
c = per_layer.get(name, Counter())
n = sum(c.values())
done = sum(c[s] for s in DONE)
print(f"layer {name:<10}{n:>7}{done:>7} " + " ".join(f"{c[s]:>10}" for s in STATUSES)
+ f" ({100.0 * done / max(n, 1):.1f}% done)")
if args.units:
for unit, c in rows:
if sum(c.values()) != c["TODO"]:
@@ -182,6 +233,8 @@ def cmd_status(repo: Path, units: dict[str, Path], args) -> int:
def cmd_queue(repo: Path, units: dict[str, Path], args) -> int:
rows = []
for unit, path in units.items():
if layer(unit) != args.layer:
continue
c = unit_counts(repo, unit, path)
open_ = c["TODO"] + c["DIVERGENT"]
if open_:
@@ -235,6 +288,7 @@ def scan_tags(repo: Path) -> dict[str, list[str]]:
def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
problems = 0
legacy = 0
recorded_ported: set[str] = set()
for entry_file in sorted((repo / "audit" / "port-map").rglob("*.json")):
entry = json.loads(entry_file.read_text(encoding="utf-8"))
@@ -265,6 +319,10 @@ def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
if status == "ADAPTED" and not info.get("test"):
print(f"ERROR {label}: {fn} is ADAPTED without test")
problems += 1
if status in {"PORTED", "NEEDS_LIVE"} and layer(unit) == "logic" and not any(
i.startswith(port_file(unit) + ".") for i in info.get("impl", [])):
print(f"LEGACY {label}: {fn} impl is outside {port_file(unit)}.{{h,cpp}}; move it when the unit is ported")
legacy += 1
if status in {"PORTED", "ADAPTED"}:
recorded_ported.add(fn.split("::")[-1] if "::" not in fn else fn)
recorded_ported.add(fn)
@@ -272,7 +330,8 @@ def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
untracked = sorted(t for t in tags if t not in recorded_ported)
for tag in untracked:
print(f"WARN tag `40250: {tag}` at {tags[tag][0]} has no PORTED/ADAPTED port-map entry")
print(f"check: {problems} error(s), {len(untracked)} untracked tag(s), {len(tags)} tag(s) scanned")
print(f"check: {problems} error(s), {legacy} legacy location(s), {len(untracked)} untracked tag(s), "
f"{len(tags)} tag(s) scanned")
return 1 if problems else 0
@@ -281,6 +340,7 @@ def main() -> int:
sub = parser.add_subparsers(dest="command", required=True)
p = sub.add_parser("status"); p.add_argument("--lib"); p.add_argument("--units", action="store_true")
p = sub.add_parser("queue"); p.add_argument("--limit", type=int, default=20)
p.add_argument("--layer", choices=LAYERS, default="logic")
p = sub.add_parser("init"); p.add_argument("units", nargs="+")
p = sub.add_parser("show"); p.add_argument("unit")
sub.add_parser("check")