Files
mtgodot-poc/.agents/skills/metin2-40250-parity-audit/SKILL.md
T
shenleiandClaude Opus 5 ad6ea9ad7e skill: switch 40250 parity skill to file-by-file 1:1 porting
Unit of work is a 40250 source unit; current-only logic is a defect;
unreachable code is not evidence. Adds per-function port-map schema,
priority by player-visible gameplay, parallel worktree rules, and a
corrected project map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:12:16 +09:00

7.5 KiB

name, description
name description
metin2-40250-parity-audit 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.

Porting target per layer

40250 layer How to port Where
Platform-independent C++ logic (packet structs/handlers, sequence table, formulas, state machines in UserInterface/, GameLib/; EterPack, EterLocale) Transliterate C++ -> C++, keeping class/function structure and names so each function has one counterpart extension/src/
40250 C++ logic that currently lives in GDScript Port function by function, same names in snake_case, same order of statements existing .gd owner
Python UI (assets/root/*.py, uiscript/) Translate per .py file into its GDScript window, keeping method structure project/ui/
Direct3D, Granny, Miles, Win32 input/window, IME, DirectX math Platform adapter; equivalence by observable output extension/, project/

Mark every ported function with a one-line tag at its definition so coverage can be scanned mechanically: // 40250: CInstanceBase::SetMoveSpeed (C++) or # 40250: CInstanceBase::SetMoveSpeed (GDScript). Tag only real counterparts.

Round workflow

  1. Pick a unit from the queue in audit/remediation-roadmap.md (ordered by user-visible gameplay; see Priority). Skip units whose port-map entries are all PORTED/ADAPTED/N_A and whose reference hash is unchanged.
  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. For each reference function, compare 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:
    • transliterate it where missing or divergent;
    • delete current-only logic it replaces;
    • 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/audit_ledger.py refresh --write | report --write | validate — run once per batch, not per round. Missing reference files are an error, not a silent fingerprint.
  • 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.