Files
mtgodot-poc/.agents/skills/metin2-40250-parity-audit/SKILL.md
T
shenleiandClaude Opus 5 9d0e50de8f 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>
2026-09-22 18:06:32 +09:00

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.