M1+M2: gr2 -> Skeleton3D/ArrayMesh/Skin + libgr2-driven animation
- gr2_bridge: gr2 Mat4 (row-major) -> Godot Transform3D (4x4 transpose);
build_skeleton (rest from local_transform, initial_placement folded into
roots), build_skin (bind i -> bone i, pose = inverse_world), build_mesh
(surface per gr2 mesh, ARRAY_BONES = skeleton idx via bone_bindings,
rigid meshes bound to bone_bindings[0]).
- dxt: DDS DXT1/3/5 decoder ported from xrender-poc.
- Metin2Model: load_gr2 -> Skeleton3D + MeshInstance3D + StandardMaterial3D
(runtime DDS albedo, face/body split by surface name). Z-up cm -> Y-up m
conversion on the node transform (unit_scale, flip_z, flip_winding props).
- Metin2AnimPlayer: _process samples a (separate) anim .gr2 with
gr2::sample_pose(model.skeleton, anim, t) -> set_bone_global_pose per bone;
Godot GPU-skins via skin_matrix = global_pose(i) * bind_pose(i) == gr2
deformer matrix. selfcheck() NaN-scans all anims.
Verified: warrior_cheongrin renders (75 bones, 5 surfaces, 3324 verts,
textured, upright). dance01 (dur 28.3s, 74 tracks) plays, 0 NaN over 25
samples, distinct poses at t=2/9/16/23. Screenshots in test/golden/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ