- 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>
152 lines
9.7 KiB
Markdown
152 lines
9.7 KiB
Markdown
---
|
|
name: metin2-40250-parity-audit
|
|
description: Port, fix, or verify 1:1 parity between the Metin2 40250 Windows C++ client and this Godot client. Use for any 40250 comparison, 1:1 parity, missing original-client behavior, parity regression, or continuation of the client port/audit. Work is done by transliterating 40250 source units, not by hunting behavior differences.
|
|
---
|
|
|
|
# Metin2 40250 1:1 port
|
|
|
|
## Principle
|
|
|
|
metin2-client is the cross-platform build of the 40250 Windows client. Apart from rendering and
|
|
platform APIs, every gameplay algorithm, branch, constant, state order, timing source, data
|
|
source, protocol side effect and cleanup path must be the 40250 one.
|
|
|
|
The 40250 source is the specification. Do not design behavior; transcribe it. Consequences:
|
|
|
|
- **The unit of work is a 40250 source unit** (one `.cpp` file, or one cohesive function group in a
|
|
very large file such as `PythonNetworkStreamPhaseGame.cpp`), not a behavior branch. A round reads
|
|
the whole unit and fixes every divergence in it.
|
|
- **Current-client logic with no 40250 counterpart is a defect**, not an adaptation: delete it
|
|
(e.g. an invented `clampf(0.25, 3.0)` on move speed, a periodic idle `FUNC_WAIT` resend, a
|
|
client-side boss AI). "The reference has no mechanism for X, so we added one" is never a
|
|
platform adaptation.
|
|
- **Unreachable code is not an implementation, and tests of it are not evidence.** Code that the
|
|
runtime never loads (e.g. a `*_system.gd` referenced only by its own `test_*_parity.gd`) must be
|
|
deleted or wired in by porting the real 40250 caller, never cited as parity.
|
|
|
|
## Code layout: mirror 40250
|
|
|
|
The logic layer has the 40250 structure, not a new architecture. `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/` | Being evaluated as embedded CPython 2.7 running the scripts unchanged (`docs/PYTHON-EMBED-EVAL.md`). **Do not port or translate this layer until that evaluation is decided.** 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:
|
|
|
|
- Port a whole unit into its mirror file. Where 40250 calls a platform class (`CGraphicThingInstance`,
|
|
`CStateManager`, `CSoundManager`, ...), call a same-named adapter interface declared under
|
|
`extension/src/platform/`; never inline Godot calls into ported logic.
|
|
- 40250 singletons (`CPythonPlayer::Instance()`, `CPythonCharacterManager`, `CPythonNetworkStream`)
|
|
stay singletons owned by the extension. GDScript does not hold gameplay state.
|
|
- GDScript (`net_play.gd`, `net_world.gd`, `game_scene.gd`, `player_controller.gd`) and
|
|
`extension/src/net/entity_store.cpp` are migration sources. In the commit that ports a unit, delete
|
|
the old logic it replaces there and leave only glue: node creation, forwarding Godot input to the
|
|
ported input handlers, and reading ported state to place nodes.
|
|
- Keep 40250 types and units (`TPixelPosition` in cm, `DWORD` ms from `ELTimer_GetMSec`, degrees).
|
|
Convert to Godot space only in the adapter.
|
|
- `port_map.py check` prints `LEGACY` for a `logic` function whose `impl` is outside its mirror file.
|
|
|
|
Mark each ported function with a one-line tag at its definition:
|
|
`// 40250: CInstanceBase::SetMoveSpeed`. Tag real counterparts only. In mirror files the tag is
|
|
optional, because the file and method name already map one to one.
|
|
|
|
## Round workflow
|
|
|
|
1. **Pick a unit** from the current batch in `audit/remediation-roadmap.md`, or from
|
|
`port_map.py queue` (logic layer by default, ordered by user-visible gameplay; see Priority). Run
|
|
`port_map.py init <unit>` and `port_map.py show <unit>` to get the function list.
|
|
2. **Read the whole reference unit** under the reference root (see `references/project-map.md`;
|
|
use `grep -a`, many files contain CP949 bytes). Read every current counterpart, found by the
|
|
`40250:` tags, the port-map entry, or `references/project-map.md`.
|
|
3. **Copy the unit into its mirror file** and, for each reference function, compare it with the
|
|
old counterpart statement by statement: preconditions and early returns, branches, formulas,
|
|
constants and units, state writes and their order, timing/event source, data source
|
|
(proto/msa/msm/txt, never hardcoded), packets sent, cleanup. Then:
|
|
- keep the 40250 version; record each old behavior that differed as a divergence found;
|
|
- delete the old logic it replaces and rewire callers to the mirror class;
|
|
- mark `N_A` only for pure platform plumbing (D3D state, Python binding glue, Win32), with a reason.
|
|
4. **Test what changed**: a focused test using the reference's own boundary values for each changed
|
|
formula/branch (fails before, passes after). Do not write tests for unchanged or trivial code.
|
|
Run only the tests touching the changed files; the full suite runs once per batch.
|
|
5. **Record**: update the unit's port-map entry (per-function status) and append one line to
|
|
`audit/history.jsonl`. Touch `audit/manifest.json` and `audit/contracts/*.md` only when a
|
|
contract's status changes. Do not write narrative into `remediation-roadmap.md`; only reorder or
|
|
tick the queue.
|
|
6. **Commit** the unit (code + test + port-map) as one commit.
|
|
|
|
A round that finds no divergence still records the per-function result so the unit is never re-read
|
|
without a hash change.
|
|
|
|
## Port-map (dedup and coverage)
|
|
|
|
`audit/port-map/<Lib>/<File>.json`, one file per reference source unit (per-file to avoid merge
|
|
conflicts between parallel rounds). Schema in `references/audit-schema.md`. Function statuses:
|
|
|
|
- `TODO` — not yet compared.
|
|
- `PORTED` — current counterpart is statement-equivalent; `impl` names it.
|
|
- `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.
|
|
- `NEEDS_LIVE` — code ported, but equivalence depends on real-server/Windows evidence.
|
|
|
|
A function is revisited only if its reference or implementation hash changed or new evidence
|
|
contradicts its status. This replaces the old per-branch "do not repeat" prose.
|
|
|
|
## Priority
|
|
|
|
Order the queue by what a player sees, P0 first:
|
|
|
|
1. Actor/movement: `InstanceBase*.cpp`, `GameLib/ActorInstance*.cpp`, `PythonPlayer*.cpp`,
|
|
`PythonPlayerEventHandler.cpp`, `PythonCharacterManager*.cpp`.
|
|
2. Combat/skill: `ActorInstanceBattle.cpp`, `ActorInstanceAttach*`, `FlyingObject*`,
|
|
`PythonPlayerSkill.cpp`, `PythonSkill.cpp`.
|
|
3. Game-phase packet handlers: `PythonNetworkStreamPhaseGame*.cpp`, `PythonNetworkStreamEvent.cpp`.
|
|
4. Items/inventory/UI data: `PythonItem.cpp`, `PythonExchange.cpp`, `PythonSafeBox.cpp`, `PythonShop.cpp`,
|
|
then the Python UI scripts.
|
|
5. World/map/terrain/effects, then audio, weather, platform.
|
|
|
|
Login/phase edge cases whose only open item is real-server evidence are `NEEDS_LIVE` and go to a
|
|
separate live-verification batch; do not spend porting rounds on them.
|
|
|
|
## Parallel rounds
|
|
|
|
Independent units may run in parallel, one git worktree per unit. Two parallel rounds must not
|
|
edit the same implementation file; if a unit's counterpart overlaps a running round, queue it.
|
|
Port-map files are per unit and `history.jsonl` is append-only, so merges are mechanical.
|
|
|
|
## Contracts and verification states
|
|
|
|
`audit/manifest.json` contracts remain the behavior-level roll-up (status meanings and gates in
|
|
`references/audit-schema.md`). A contract may reach `STATIC_VERIFIED` only when every reference
|
|
unit it lists has no `TODO`/`DIVERGENT` functions, and `TEST_VERIFIED` when its tests also pass.
|
|
Never cite a test of unreachable code, and never mark visual, timing-feel, driver or Windows-only
|
|
behavior verified from code inspection alone.
|
|
|
|
## Tools
|
|
|
|
- `scripts/port_map.py status | queue [--layer logic|python|platform] | init <unit> | show <unit> | check`
|
|
— function inventory (active `.vcxproj` sources + the 40250 Python root), progress per library and
|
|
per layer, and tag/port-map/layout consistency. Run `check` before every commit that touches
|
|
`audit/port-map/`.
|
|
- `scripts/py_embed_spike.py` (Python 2.7) — runs the 40250 `system.py` bootstrap with stub native
|
|
modules; used by the embedded-Python evaluation.
|
|
- `scripts/audit_ledger.py refresh --write | report --write | validate` — run once per batch, not
|
|
per round. Missing reference, implementation or test files are an error; `evidence.tests` holds
|
|
paths only, command lines go in `evidence.commands`.
|
|
- `scripts/audit_packet_registry.py`, `audit_phase_dispatch.py`, `audit_sequence_table.py` — run
|
|
when a round touches `extension/src/net/`.
|
|
- `scripts/audit_source_coverage.py` — maps active 40250 sources (from the `.vcxproj` files) to
|
|
contracts.
|
|
- Reference root: `MT_40250_SOURCE` env var, else `reference_root` in `audit/manifest.json`
|
|
(relative to the repo root).
|
|
|
|
## Completion report
|
|
|
|
Per round: unit, functions ported/fixed/deleted/marked `N_A`, divergences found (one line each:
|
|
reference vs old current behavior), tests run, commit hash, remaining `TODO`/`DIVERGENT`/`NEEDS_LIVE`
|
|
in the unit. Keep it short.
|