The embedded 40250 script layer needs its own interpreter on five platforms.
2.7 is end-of-life, so nothing can be fetched from the target SDKs and the
source is vendored (official 2.7.18 tarball, trimmed to 25MB; Lib/ stays for
step 3's python27.zip).
cpython-2.7.18/CMakeLists.txt builds one `mtpython` static library from exactly
the 133 objects the reference libpython2.7.a contains, off by default behind
-DMTGODOT_EMBED_PYTHON=ON. The source list and the built-in module table
(config/config.c, 39 entries) are shared; pyconfig.h is a probe result and is
not, so each platform keeps its own under config/<platform>/, regenerated by
tools/py_embed/gen_pyconfig.sh and checked against the shared table.
Three vendor patches, documented in docs/THIRD-PARTY.md: configure/configure.ac
learn arm64 on macOS, and posixmodule.c undefines the process-control calls it
hard-defines past pyconfig.h when building for iOS.
macOS (ctest python.embed: every builtin imports, codecs and pickle work off the
vendored Lib/), Android arm64 and iOS arm64 build. Linux needs a Linux host and
Windows needs a MinGW-vs-MSVC decision; both are noted for step 4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tools/asset_manifest.py records path/size/sha256 of pack/Index, every .eix/.epk
and Eternexus/root/*.msm (audit/assets/40250-client.json). The gate script sets
MT_ASSETS_STRICT=1, verifies the manifest, runs the native pack/proto tests and
four Godot tests, and fails on a missing client, a mismatch or a skip.
MT_40250_CLIENT now comes from the environment for the native tests too.
remote_player_test accepts both weighted WAIT variants (wait/wait_1).
PORT-PLAN §5 records the 2D status.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
proto.cpp reads the 40250 MIPX/MMPT tables with the ported CLZO/TEA (156-byte
TItemTable, 255-byte SMobTable), replacing the m2dev XChaCha20 236/335 reader.
Names follow CItemData::GetName / GetMonsterName (szLocaleName, cp1252).
GDScript callers load AssetRoot.locale_file() = pack://locale/en/<name>, which
makes the EterPack chain runtime-reachable; port-map entries for the functions
a coverage run hit move to PORTED/ADAPTED.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix the per-function inventory against the clang-derived platform skeleton:
strip comments, accept indented qualified definitions in namespace blocks,
long parameter/initializer lists and all-caps qualified ctors, and skip
constructor initializer entries. Split EterBase by unit: tea/lzo/cipher/
Random/Stl/Timer/Poly become logic, the file/OS units stay platform; drop
the 8 obsolete platform stubs and regenerate audit/slices.
Baseline: logic 3343, python 5175, platform 2091 (10609), 0 done.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Mirror the remaining non-Python 2V3 closure headers (98 more, 173 total): EffectLib,
EterGrnLib, SpeedTreeLib, SphereLib, PRTerrainLib, EterImageLib, EterBase/Poly, cipher/tea,
EterLib Grp*/IME/Input/MSWindow/StateManager, GameLib actor/map/race headers, EterPack.
- SDK shims: granny.h, SpeedTreeRT.h, imm.h, Dimm.h (the SDK MIDL header, not mirrored),
dinput/d3dx8 additions; D3D8 render-state enums, FVF macros and the COM resource hierarchy;
more Win32 GDI/handle types with layout static_asserts.
- PORT edits for MSVC-permissive code (typename, friend visibility, <algorithm>, pointer→uintptr_t)
and PRTerrainLib/StdAfx.h dropping the ScriptLib/Python include.
- port_logic links the vendored cryptopp/minilzo.
- extension/src/platform: port_platform target with 103 generated stub files (1917 function
stubs, 146 static members) from the new platform_stub.py (clang -ast-dump-filter);
MT_PLATFORM_STUB() marks every unimplemented body. libmtgodot links port_platform and
port_gate.sh builds it.
Gate: macOS, Android, iOS, Windows (mingw) PASS; Linux BLOCKED (no toolchain).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
76 closure headers (EterBase, EterLocale, EterPack, EterLib, GameLib, MilesLib,
EffectLib, SphereLib, EterImageLib) copied by the new port_copy.py (CP949->UTF-8,
LF, include-path case only); platform-class headers go to the mirror path too, as
the interface platform/ implements. Six manual `// PORT:` edits (Random, Singleton,
Pool, Stl, Utils, FlyTarget).
- common/D3D8Types.h: D3D8 value types with SDK values/layout, opaque COM interfaces
- common/shim/sdk (d3d8/d3dx8/mss) on every platform, common/shim/win32 off Windows
- Win32Types/Win32Crt: HRESULT, GUID, LARGE_INTEGER, LOGFONT/TEXTMETRIC, _atoi64,
CRITICAL_SECTION on std::recursive_mutex
- header gate prepends each library's StdAfx.h (40250's precompiled header)
port_gate: macOS, Android, iOS, Windows (mingw) PASS; Linux BLOCKED (no toolchain).
ScriptLib/EterPythonLib/UserInterface wait for 2P (their StdAfx includes Python).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
D3DXVECTOR2/3/4, D3DXMATRIX, D3DXQUATERNION, D3DXPLANE, D3DXCOLOR with
d3dx8math.inl operator semantics, plus the D3DX* functions the 2V3 slice
logic uses. Covered by port.common; port_gate passes on macOS, Android,
iOS and Windows (mingw).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Handle late main-character data, guarded model construction, bounded retries and map correction. Validate player model sources and add lifecycle and real-asset visual regressions.
Include pending material and character-selection changes, updated A1 screenshot, and the detailed rendering repair plan. Hair occlusion, full UI parity and final macOS package acceptance remain unfinished.
CLIENT-GAP-FIX §2.1/§2.3/§2.4/§2.5 (+ §2.2 steps 4-5):
- §2.1 two-layer model: EntityStore is data-only. Entries are created only by
mut_spawn / mut_spawn_full / mut_spawn_main and removed only by mut_despawn /
main-vid table clear. Every other mut_* now does find-or-return with a
MT_NET_TRACE-gated debug log; no pending / replay buffer. Visibility moves to
net_world.gd::_update_visibility() — per frame it walks client.get_entities()
and mirrors is_main / AFFECT_SHOW_ALWAYS(bit 7) / IsWall(14201-14204) /
planar-distance < CHAR_STAGE_VIEW_BOUND (200*100 cm), building a node on entry
and fading + freeing it past +10 while keeping the data row (_fading guard).
Gate is fail-open when the data layer is not queryable (no get_entities /
_main_vid == 0 / no pos_cm) so existing fake-client tests are unaffected.
- §2.3 one overwrite rule: mut_spawn_full() does a clean whole-row value replace
(m_ents[vid] = e), no preservation of prior move state on VID reuse; mut_spawn()
now delegates to it. net_world._on_spawn destroys + recreates a node when the
vid already has one; mount/dismount rebuild keeps the old global_position.
- §2.4 points bounds: mut_set_points() clamps counts > POINT_MAX_NUM (255) and
zeroes the tail of a short snapshot; mut_set_point() rejects type >= 255.
- §2.5 field apply order + affect flags: new EntityStore::mut_affect_flags(vid,
lo, hi) assembles v = lo | (hi << 32) and queues a dirty tick; mut_char_update
guild/alignment/pk changes now also push Info. net_world._apply_field_updates()
applies fields in the reference call order (NetworkActorManager.cpp:470).
- §2.2 steps 4-5: mut_char_info() / mut_shop_sign() drop the touch()-creates-
entity path. Steps 1-3 (m_pending_actor two-packet merge) remain W1-owned.
Tests: ./build.sh Debug exit 0; ctest 16/16 (net.entity_store gains a §2.1
data-layer assertion block). New project/net_world_vis_test.gd for scene-node
visibility. GDScript regressions green: netbridge_test, gamescene_test,
netplay_test, p9_test, combat_fx_test, player_motion_test, net_world_vis_test.
Follow-up for W1: call mut_affect_flags(vid, affect_flag[0], affect_flag[1])
from classic_parser.cpp after GC_CHARACTER_ADD[2] / GC_CHARACTER_UPDATE
(mut_char_update has no affect param). wire_classic.h already has the wanted
static_assert on GCPoints.points.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
§1.5 CG_CLIENT_VERSION: send_client_version() now branches on locale. EUROPE
(this project's locale) sends CGClientVersion2 (header 0xf1, 67B, timestamp
"1215955205"); non-EUROPE sends CGClientVersion (0xfd) + CMake-injected
MT_BUILD_TIMESTAMP in C __TIMESTAMP__ form. New ClassicSession::set_executable_name()
(default "metin2.bin"; TODO(W0): M2Client passes OS::get_executable_path().get_file()).
m_version_sent set only after send_fixed() succeeds. wire_classic.h gains
CGClientVersion2 + size-table entry.
§1.10 fixed-length text: new extension/src/net/text_codec.h — to_wire()/
from_wire_str() apply a hard byte cap without splitting a UTF-8 multibyte
sequence; godot::String overloads are __has_include-guarded so mtnet stays
godot-free. classic_session.cpp routes 8 fixed-length name/sign/comment fills
through to_wire(); parser reads GC_CHAR_ADDITIONAL_INFO name via from_wire_str().
New net_text_codec_test (ctest net.text_codec). Locale codepage (CP949/CP1252)
transcoding remains a follow-up; wire bytes are currently UTF-8 (ASCII round-trips).
§2.2 two-packet PC/NPC merge: ClassicParser GC_CHARACTER_ADD drops invisible
races (20025/20038/20039); PC/NPC only stash a bare Entity into m_pending_actor;
everything else spawns immediately with a name from root/npclist.txt (new
npc_names.h + set_npclist_path(); $MT_ASSETS/$M2_ASSETS fallback; TODO(W0):
M2Client passes the asset root). GC_CHAR_ADDITIONAL_INFO with no pending entry
records m_last_error + m_unhandled_headers and drops (never half-creates);
on a hit it merges the pending Entity with the additional-info's 9 fields into
one mut_spawn_full() then erases the pending entry. New pending_actor_count()
test hook; net_classic_session_test updated (version assertions -> 0xf1;
stash -> merge -> spawn, immediate monster spawn, invisible-race drop,
no-pending additional-info is a no-op). Depends on W2 §2.2 steps 4-5
(entity_store mut_char_info touch / mut_shop_sign cleanup) — the merge works
with current W2 code as-is.
Build + ctest 17/17 (16 + net.text_codec); netbridge_test, p10_test green.
Docs: CLIENT-GAP.md W1 row + increment-50 change-log entry; CLIENT-GAP-FIX.md
§1.5/§1.10/§2.2 status + facts and C.5 batch record.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
Rendering fixes (real):
- m2_material shaders: cull_disabled -> cull_back (inside-of-mesh faces were
z-winning and darkening/hiding the head under the ShaderMaterial path).
- drop `* COLOR` from the shader (meshes carry no ARRAY_COLOR).
Animation: convert gr2 global poses to Godot *local* bone poses and set via
set_bone_pose (was set_bone_global_pose). MTGODOT_VERIFY=1 confirms
get_bone_global_pose(i) == conv(world[i]) for all 75 bones (<=2.6e-5).
Head/upper-armor collapse on dance_1 is NOT a Godot-route bug:
- reproduced identically in xrender-poc's bgfx demo at the same anim/t
- reproduced by the new MTGODOT_CPUSKIN=1 reference path (same LBS math as
xrender's validated skin_mesh)
- `general/wait` / `run` render correctly in all three
=> upstream libgr2 Curve::eval (degree-2 quaternion B-spline) vs Granny.
Demo default animation switched to general/wait; documented in MIDREVIEW §4
and README. Debug scaffolding gated behind MTGODOT_VERIFY / MTGODOT_CPUSKIN.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
- Metin2Model stores gr2::dump_materials() and can resolve surface textures
via tri_groups[0].material_index -> material.diffuse_texture basename,
gated behind use_gr2_materials (default off): the mapping is unverified and
currently regresses warrior (body surfaces pick the face texture), so the
filename-convention + dir-scan path stays the default.
- Property + setter for use_gr2_materials so it can be A/B'd once a Granny
oracle exists. Logged: "gr2 materials: N (M with diffuse name)".
warrior/assassin/shaman render unchanged from the heuristic path.
docs/GODOT-POC-PLAN.md §M2.5 T2.5.6 gap list already covers the open item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
- extension/: godot-cpp submodule (master, API 4.7), Metin2Model node
registered, links xrender-poc/libgr2 via sibling-dir reference.
- project/: Godot 4.7 project — orbit camera + DirectionalLight3D (shadows)
+ WorldEnvironment/procedural sky + placeholder cube; main.gd probes the
extension and can run libgr2 on a real .gr2 (MTGODOT_PROBE_GR2).
- build.sh one-shot build into project/bin/.
- docs/GODOT-POC-PLAN.md (phased: Phase 1 macOS -> M0'/M1/M2/M2.5).
Verified on this machine:
[mtgodot] Metin2Model registered OK — libgr2 linked
probe_gr2(warrior_cheongrin.gr2) -> gr2 OK: format_version=6 sections=8
Metal Forward+ renders the scene (test/golden/m0-scaffold.png).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ