docs(port-plan): close migration prerequisite gaps

This commit is contained in:
shen
2026-09-22 03:01:57 -07:00
parent dcefc83211
commit ae68ec5944
9 changed files with 111 additions and 65 deletions
@@ -27,13 +27,13 @@ The 40250 source is the specification. Do not design behavior; transcribe it. Co
## Code layout: mirror 40250
The logic layer has the 40250 structure, not a new architecture. Route, batch order and
prerequisites (2A base, 2R pack inventory, 2V vertical slice) are in `docs/PORT-PLAN.md`. `port_map.py` assigns every unit
prerequisites (2A base, 2R pack inventory, 2V0-2V3 vertical slices) are in `docs/PORT-PLAN.md`. `port_map.py` assigns every unit
one of three layers:
| Layer | 40250 units | How to port | Where |
| --- | --- | --- | --- |
| `logic` | `UserInterface/`, `GameLib/`, `EterLib` net/timer/text parsing, `EterPack`, `EterLocale` (everything not listed below) | Copy C++ -> C++. Same file name, class name, method names, member names and statement order; one reference file = one implementation file | `extension/src/port/<Lib>/<File>.{h,cpp}` |
| `python` | `Client/Eternexus/root/*.py`, `uiscript/`, `UserInterface/*Module.cpp`, `EterPythonLib/`, `ScriptLib/` | Decided: embedded CPython 2.7.18 runs the scripts unchanged (`docs/PYTHON-EMBED-EVAL.md`, batch 2P); port only the C++ side. Never translate the scripts. Script functions get `RUN_AS_IS` (after the 2A re-baseline), never `N_A`. Reference is 40250 `Eternexus/root`, **not** `assets/root` | `extension/src/port/<Lib>/` (bindings), scripts from pack |
| `python` | `Client/Eternexus/root/*.py`, `uiscript/`, `UserInterface/*Module.cpp`, `EterPythonLib/`, `ScriptLib/` | Decided: embedded CPython 2.7.18 runs the scripts unchanged (`docs/PYTHON-EMBED-EVAL.md`, batch 2P); port only the C++ side. Never translate the scripts. Script functions get `RUN_AS_IS` once its evidence gate passes, never `N_A`. Reference is 40250 `Eternexus/root`, **not** `assets/root` | `extension/src/port/<Lib>/` (bindings), scripts from pack |
| `platform` | Direct3D/`Grp*`, Granny (`EterGrnLib`), Miles, SpeedTree, `EffectLib`/terrain rendering, Win32 window/input/IME, threads, anti-cheat | Adapter behind the interface the 40250 caller uses; equivalence by observable output | `extension/src/platform/` + existing render code (`metin2_model`, `metin2_anim`, `gr2_bridge`, ...) |
Rules for the `logic` layer:
@@ -48,10 +48,14 @@ Rules for the `logic` layer:
switch until the new path is wired into the runtime; delete the old logic in the same commit that
switches its callers to the ported code, leaving only glue (node creation, forwarding Godot input,
reading ported state to place nodes). A ported function nothing calls at runtime stays `TODO`.
- Keep 40250 **widths and overflow semantics**, not its C type names: 40250 is Win32, where `long` and
`unsigned long` are 32-bit. Use the fixed-width types from `port/common/Win32Types.h`; every
- Keep 40250 **widths and overflow semantics**, not its C type names: the 40250 executable targets
32-bit Win32 (ILP32), where `long`, `unsigned long`, and pointers are 32-bit. `Win32Types.h`
supplies fixed-width Win32 scalar aliases, but cannot redefine C++ keywords such as `long`;
serialized declarations use explicit fixed-width types. Every
serialized struct (proto records, packets, EPK index, msa/msm) gets 40250's `#pragma pack` and a
`static_assert(sizeof(T) == N)`; pointers stored in `DWORD` become `uintptr_t` with a port-map note.
`static_assert(sizeof(T) == N)`. Pointer/handle types stay pointer-sized behind platform adapters;
pointers stored in `DWORD` become `uintptr_t` with a port-map note. Arithmetic that relies on
32-bit wrap uses unsigned operations or explicit wrapping helpers, never signed-overflow UB.
- Keep 40250 units (`TPixelPosition` in cm, `DWORD` ms from `ELTimer_GetMSec`, degrees).
Convert to Godot space only in the adapter.
- Port in `#include`-dependency order: a shared header belongs to the first unit that needs it, and
@@ -97,6 +101,9 @@ conflicts between parallel rounds). Schema in `references/audit-schema.md`. Func
- `TODO` — not yet compared.
- `PORTED` — current counterpart is statement-equivalent; `impl` names it.
- `RUN_AS_IS` — Python source is byte-identical to the reference, packaged and loaded by embedded
CPython on a reachable runtime path; `impl` names the packaged script and `evidence` names the
target-platform import/runtime proof. It is invalid for C++ units.
- `ADAPTED` — platform adapter; `note` states both sides and the invariant, with a test.
- `N_A` — platform plumbing with no gameplay semantics; `note` states why.
- `DIVERGENT` — known different and not yet fixed; `note` says how.
@@ -124,8 +124,13 @@ One JSON file per 40250 source unit at `audit/port-map/<Lib>/<File>.json`:
```
- Function keys are the reference's qualified names (`Class::Method`, or the free-function name).
- `status` is one of `TODO`, `PORTED`, `ADAPTED`, `N_A`, `DIVERGENT`, `NEEDS_LIVE` (meanings in `SKILL.md`).
- `impl` is required for `PORTED`/`ADAPTED`/`NEEDS_LIVE`; `note` is required for `ADAPTED`, `N_A`,
- `status` is one of `TODO`, `PORTED`, `RUN_AS_IS`, `ADAPTED`, `N_A`, `DIVERGENT`, `NEEDS_LIVE`
(meanings in `SKILL.md`).
- `RUN_AS_IS` is valid only for a Python script unit whose exact reference bytes are shipped and
loaded unchanged by embedded CPython. It requires `impl` (the packaged script path) and a
non-empty `evidence` list naming target-platform import/runtime evidence. The unit's
`reference_sha256` plus the committed resource manifest bind the evidence to exact source bytes.
- `impl` is required for `PORTED`/`RUN_AS_IS`/`ADAPTED`/`NEEDS_LIVE`; `note` is required for `ADAPTED`, `N_A`,
`DIVERGENT` and `NEEDS_LIVE`. `ADAPTED` also needs a `test` path.
- When `reference_sha256` no longer matches the file, every non-`TODO` function in the unit must be
rechecked before the hash is updated.
@@ -31,8 +31,8 @@ from pathlib import Path
import refroot # script directory is on sys.path when run directly
from audit_source_coverage import REFERENCE_DIRS, active_project_sources
STATUSES = ("TODO", "PORTED", "ADAPTED", "N_A", "DIVERGENT", "NEEDS_LIVE")
DONE = {"PORTED", "ADAPTED", "N_A"}
STATUSES = ("TODO", "PORTED", "RUN_AS_IS", "ADAPTED", "N_A", "DIVERGENT", "NEEDS_LIVE")
DONE = {"PORTED", "RUN_AS_IS", "ADAPTED", "N_A"}
PY_ROOT = "../../Client/Eternexus/root"
UNIT_DIRS = REFERENCE_DIRS + ("EterBase", "EterPythonLib", "ScriptLib", "SpeedTreeLib", "SphereLib", "EterImageLib")
PORT_DIR = "extension/src/port"
@@ -310,9 +310,15 @@ def cmd_check(repo: Path, units: dict[str, Path], args) -> int:
if status not in STATUSES:
print(f"ERROR {label}: {fn} has invalid status {status!r}")
problems += 1
if status in {"PORTED", "ADAPTED", "NEEDS_LIVE"} and not info.get("impl"):
if status in {"PORTED", "RUN_AS_IS", "ADAPTED", "NEEDS_LIVE"} and not info.get("impl"):
print(f"ERROR {label}: {fn} is {status} without impl")
problems += 1
if status == "RUN_AS_IS" and (layer(unit) != "python" or not unit.endswith(".py")):
print(f"ERROR {label}: {fn} is RUN_AS_IS outside a Python script unit")
problems += 1
if status == "RUN_AS_IS" and not info.get("evidence"):
print(f"ERROR {label}: {fn} is RUN_AS_IS without runtime evidence")
problems += 1
if status in {"ADAPTED", "N_A", "DIVERGENT", "NEEDS_LIVE"} and not info.get("note"):
print(f"ERROR {label}: {fn} is {status} without note")
problems += 1