Commit Graph
31 Commits
Author SHA1 Message Date
shenleiandClaude Opus 5 fb4d2d222b docs: 文档审计——修正失效链接与过期结论
按 docs/ 目录逐篇核对,只改与事实/路径不符的部分,不动尚未验证的计划条目:

- `docs/steps/**` 在早前整理时已迁到 `docs/reference/steps/**`,全仓的旧路径
  引用(PLAN.md / README.md / M0-gr2-reader.md 及 libgr2 的头注释)一并改正。
- CLIENT-GAP.md / CLIENT-GAP-FIX.md / BACKLOG.md / MIDREVIEW.md:把已经落地的
  条目从「待办」改为已完成,删掉与代码现状矛盾的描述。
- ANDROID-TESTING.md / CLIENT-PORT.md / CLIENT-ROADMAP.md / GODOT-POC-PLAN.md /
  SHINSOO-WORLD-RENDERING.md / PARITY-GAP.md:同上,另补当前实际的构建/测试入口。
- THIRD-PARTY.md:补齐实际在用的第三方来源与许可说明。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
2026-09-08 17:15:17 +09:00
shenandshen c3a6fb973e fix(rendering): checkpoint main-character recovery and visual regression work
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.
2026-09-08 08:52:30 +08:00
shenleiandClaude Code 8092ff44e2 装备校验链/快捷栏/公会/好友情侣/交易仓库五项差距修复(增量 129)
- 装备校验:can_equip 按 CanEquipNow→FindEquipCell→EquipItem 精确重排,
  新增 find_equip_cell() 1:1 复刻(RING/UNIQUE 占用翻转、COSTUME/BELT/DS),
  帝国 antiflag 降级为 tooltip 提示,LIMIT_PCBANG/REAL_TIME* 语义与倒计时文案
- 快捷栏:死亡全槽禁用+激活守卫、变身(affect 220)技能置灰、
  物品槽失配置灰(服务器回收/换武器后刷新)
- 公会:GUILD_AUTH_* 权限位门控踢人/公告/技能升级,/gskillup、
  被宣战应答弹窗(/war·/nowar)、资金存取行(40250 服务端已禁用仍保留 1:1)
- 好友/情侣:messenger 三固定组(好友/公会/情侣),lover_login/logout/
  near/far/divorce 命令事件→在线/在身边状态与离婚隐藏
- 交易/仓库:ExchangeState.last_notice(ALREADY/LESS_GOLD)结束浮层提示;
  仓库改密 /safebox_change_password 三段输入;关窗补 /safebox_close·/mall_close;
  仓库金钱经核实 40250 无 wire 协议,UI 定界只读
- app_flow:重连时 guild_marks_ready 重复连接加 is_connected 守卫
- 相机 look_at 零向量守卫(首帧/传送落点)
- 测试:equip/love/guild 断言更新,79 项中 78 绿(game_camera_test 为
  HEAD 既有失败);真服冒烟 LIVE_SMOKE PASS(192.168.21.203 全链路)
- docs/CLIENT-GAP.md 同步增量 129

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kUFvQGYusuY3dkfGitmAe
2026-09-07 21:43:30 +09:00
shenandshen 61272b75e9 客户端问题修改 2026-09-07 14:53:36 +08:00
shenlei b19d5b5dd3 feat: complete mobile UI implementation 2026-09-04 19:54:33 +09:00
shenlei 9a4a044da5 Complete remaining client gap features 2026-09-04 13:25:05 +09:00
shenlei 13ccb8d02d 完善客户端功能并同步差距文档 2026-09-03 18:40:01 +09:00
shenleiandClaude Sonnet 5 f93a172296 40250 classic: G1 integrate W4 (§9.1 map BGM selection, §9.2 background volume save/restore)
Doc conflicts resolved by W0: all four 增量 50 change-log bullets kept, W4 C.5
batch record appended.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 16:13:17 +09:00
shenleiandClaude Sonnet 5 804744c28f 40250 classic: G1 integrate W3 (§8.3 in-game help window)
Doc conflicts resolved by W0: kept all four 增量 50 change-log bullets and
appended the W3 C.5 batch record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 16:10:48 +09:00
shenleiandClaude Sonnet 5 61ddf791fa 40250 classic: G1 integrate W2 (§2.1 data-layer EntityStore + net_world visibility, §2.3/§2.4/§2.5)
Doc conflicts in CLIENT-GAP.md / CLIENT-GAP-FIX.md resolved by W0: keep both
增量 50 W1 + W2 change-log bullets and C.5 batch records.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 16:08:59 +09:00
shenleiandClaude Sonnet 5 ce715c7c38 40250 classic: G1 integrate W1 (§1.5 client version, §1.10 text codec, §2.2 two-packet merge)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 16:07:17 +09:00
shenleiandClaude Sonnet 5 3487df2a16 40250 classic W2: EntityStore data-layer only + net_world visibility (增量 50)
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
2026-09-02 16:06:06 +09:00
shenleiandClaude Sonnet 5 b296457e3c 40250 classic: increment 50 (Phase 1 W1) — §1.5 client version, §1.10 text codec, §2.2 two-packet merge
§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
2026-09-02 15:51:48 +09:00
shenleiandClaude Sonnet 5 d7711ba5f0 40250 classic: increment 50 (Phase 1 W3) — §8.3 in-game help window
Implement the in-game Help window as a 1:1 migration of uihelp.py's
HelpWindow (single-page: ENABLE_HELP_MULTIPAGE = 0 -> LoadDialogSinglePage).

- project/ui/help_ui.gd (new): loads the real helpwindow.py uiscript via
  UiScript -> UiBuild; _relabel() resolves the <HELP_*>/<CLOSE> placeholders
  (produced when UiManager has no locale callback) from the project Locale
  table (en) with verbatim English fallbacks, covering the original script's
  two duplicate help_02 rows (right/middle camera). open/close/toggle/is_open;
  close_button -> close; ESC handled by the UiManager window stack.
- project/ui/system_menu_ui.gd: help_button changed from the placeholder toast
  to close() + open_help(); adds const HelpUI, an injectable help_ui member,
  _ensure_help() (lazy build, reuses the menu's ui/client/assets_root),
  open_help(), toggle_help(); setup() gains an optional 6th arg (old 5-arg
  callers unaffected).
- project/game_scene.gd: single elif in _unhandled_input — KEY_H without
  ctrl/meta -> system_menu_ui.toggle_help() (Ctrl+H stays free for
  /user_horse_ride).
- project/help_ui_test.gd (new): headless SceneTree test — helpwindow.py load,
  19 verbatim help rows + taskbar labels bound and non-placeholder,
  close_button text, rows() helper, close/open/toggle, ESC stack top,
  SystemMenuUI.open_help() lazy build.

Tests: help_ui_test, system_menu_ui_test, gamescene_test, ui_test all PASS
(plus system_option_ui_test, char_status_ui_test). No build/ in this worktree
so C++ ctest not run (no C++ change). Real-server and visual (.sub textures)
not verified. Docs: CLIENT-GAP.md W3 row + increment 50 bullet;
CLIENT-GAP-FIX.md §8.3 status/改动/测试/验收 + C.5 batch record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 15:34:36 +09:00
shenleiandClaude Sonnet 5 9732f2799e 40250 classic: increment 50 (W4) — map BGM selection + background volume save/restore
§9.1 map BGM selection (bgm_director.gd):
- server bgm_changed(name, volume): non-empty name plays it as field music;
  empty name falls back to musicInfo.fieldMusic (METIN2THEMA = "M2BG.mp3")
- volume < 0 (server unspecified, GC_MAIN_CHARACTER3_BGM) -> default
  1/5*0.1 = 0.02; otherwise server volume; volume applied before play_bgm()
  fade-in (matches game.py:190 SetMusicVolume/SetSoundVolume then FadeInMusic)
- connect M2Client.world_reset -> re-run selection on map change
- new musicinfo.gd: METIN2THEMA/loginMusic/createMusic/selectMusic/fieldMusic
  constants + save/load_last_play_field_music -> user://BGM/lastplay.inf

§9.2 background/foreground volume save-restore:
- audio.gd: save_volume()/restore_volume() (backup fields echo reference
  m_fBackupMusicVolume / m_fBackupSoundVolume) + set_music_volume()
- app_lifecycle.gd: _enter_background() uses save_volume() + mute instead of
  stop_bgm(); _exit_background() calls restore_volume(); _bgm_name never cleared

§9.3 lifecycle ownership: DOC ONLY — design confirmed, deferred to Phase 2 / W0
(AppFlow sole lifecycle owner; M2Client drops NOTIFICATION_* keeps suspend/resume).
game_scene.gd / app_flow.gd / m2_client.* left untouched per workflow constraints.

Tests: bgm_test.gd (new) PASS; gamescene_test / netplay_test / p9_test green.
Docs: CLIENT-GAP.md increment 50 bullet + W4 progress row; CLIENT-GAP-FIX.md
§9.1 §9.2 状态/改动/测试/验收, §9.3 deferred note, C.5 batch record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 15:31:58 +09:00
shenleiandClaude Sonnet 5 f32064c74a 40250 classic: parallel-dev checkpoint through increment 49 (W0 interface freeze)
Captures the uncommitted parallel-development work (increments 46-49) on the
40250 classic client port, folded into docs/CLIENT-GAP.md + CLIENT-GAP-FIX.md.

Increment 49 (W0 interface-freeze batch, Phase 1 prerequisite) lands the
cross-workflow shared-file scaffolding so the 4 Phase 1 workflows can run in
isolated worktrees without contention:

- entity_store.{h,cpp}: Entity gains empire/affect_flags/owner_vid/state_flags;
  new mut_spawn_full() (single-shot two-packet merge, §2.3), mut_ownership()
  (§2.5), mut_map_bgm() + take_bgm_dirty()/bgm_name()/bgm_volume() (§9.1),
  drain_dirty() (§2.1/§2.5 bare-field-update queue); mut_char_info() now stores
  empire; reset_for_map_change() clears m_dirty.
- m2_client.cpp: new bgm_changed(name, volume) signal; classic + m2dev pump
  loops drain drain_dirty() -> entity_info and take_bgm_dirty() -> bgm_changed;
  entity_dict() exposes the 4 new keys.
- classic/classic_parser.{h,cpp}: GC_MAIN_CHARACTER3_BGM /
  GC_MAIN_CHARACTER4_BGM_VOL route bgm_name/bgm_volume to mut_map_bgm() (was
  discarded); new m_pending_actor staging map (W1 fills §2.2 merge logic).
- project/bgm_director.gd (new) + game_scene.gd: BGM consumption split out of
  net_world.gd so W2/W4 don't collide; §8.6/§4.8 keybind convergence (digits
  1-4 -> quickslots 0-3, F1-F4 -> quickslots 4-7, Ctrl+1..9 -> _emote()).

Tests: cmake --build build clean; ctest 16/16; 15 GDScript regressions green
(netbridge, gamescene, netplay, p9, p10, p2b, p8, system_menu_ui, skill,
combat_fx, skill_fx, player_motion, char_status_ui, chat, inventory), no skips.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 15:13:01 +09:00
shenlei 70f200e885 Merge remote-tracking branch 'origin/main' into feat/40250-classic-net 2026-09-02 09:13:00 +09:00
shenlei 519311f8fe Ignore classic build artifacts 2026-09-02 09:11:44 +09:00
shenandshen 44bdcb0781 Fix 40250 classic login and world loading 2026-09-02 07:03:25 +08:00
shenlei 8f61990a83 Implement 40250 classic client port 2026-09-01 18:49:28 +09:00
shenandClaude Sonnet 5 2277b1dd60 Vendor Metin2 assets (assets/ + bgm/)
Committed to this self-hosted private repo so a clone builds on any machine
without a separate asset sync. Copyrighted Ymir art — do not publish or
redistribute. 54198 files, ~2.2 GB. asset_index.txt / assets.zip stay
gitignored (derived: bake_asset_index.gd / pack-assets.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
2026-08-31 20:04:32 +09:00
shenandClaude Sonnet 5 47baf6c0c6 Metin2 game client (P0–P11) + mobile asset pipeline
Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
  phases, EntityStore world model, ~all GC/CG headers. char create/delete,
  private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
  quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
  char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
  token), system-option + game-option + ESC system menu, private-shop 39-grid,
  party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
  dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.

Portable asset IO (A1) — all extension/libgr2/formats/mtproto reads routed
through godot::FileAccess (res:// PCK works on iOS/Android); standalone-lib
*_path() kept for the non-Godot CTests. AssetResolver + PropertyRegistry
switched to a baked index (bake_asset_index.gd) instead of std::filesystem.

Mobile builds: build-{android,ios}.sh, export-android.sh, pack-assets.sh,
gen-debug-keystore.sh. Assets ship as a zip mounted at runtime by
project/asset_pack.gd (adb push now; HTTP download is a drop-in later).

ctest 10/10, 34 GDScript suites, macOS/iOS/Android all build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
2026-08-31 20:02:12 +09:00
Claude f4917a2b3b Fix cull mode + isolate head-collapse to a libgr2 curve bug
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
2026-08-29 10:47:01 +09:00
Claude 30d6a81476 Drop accidentally-committed .cache/clangd from tracking
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
2026-08-29 09:52:34 +09:00
Claude 0ecdca821e Refresh warrior golden; note libgr2 dump_materials dependency
The dump_materials() addition this repo needs lives uncommitted in the
sibling xrender-poc working tree (that repo has no commit history; left
as-is). Documented in README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
2026-08-29 09:52:04 +09:00
Claude 528a6390ea Phase 1 mid-review + stress harness
- docs/MIDREVIEW.md: what M0'-M2.5 proved, bridge-layer LOC (~1100),
  key technical conclusions (4x4 transpose, animation method B, no
  handedness flip needed), remaining risks, recommendation = enter Phase 2
  with a 3-item wrap-up (compare.py, dump_materials oracle, refocused perf).
- harness MTGODOT_STRESS=N: grid of N animated warriors, vsync off, dumps
  test/godot-macos-stress.json. Frame times are throttle-capped in a
  background window (~30fps to 50 chars, 52ms at 100); the reliable signals
  are build cost ~165ms/char and the 100-char breakout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
2026-08-29 09:46:54 +09:00
Claude 83c48f0c14 M2.5: wire libgr2 dump_materials (opt-in), keep filename heuristic default
- 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
2026-08-29 09:41:32 +09:00
Claude eeffa2a730 M2.5: ShaderMaterial + texture resolution + fog; multi-skeleton verified
- m2_material: Metin2-style ShaderMaterial. blend_mix shader (opaque /
  alpha-blend / alpha-test-cutout via `mode` uniform) + blend_add shader
  (additive glow). modulate(tex, COORD) * COLOR, diffuse_lambert.
- Metin2Model: material_mode prop ("metin2" ShaderMaterial | "standard");
  _resolve_texture does filename-convention + case-insensitive dir scan of
  *.dds (face/hair keyword slots, stem match, fallback); set_surface_texture
  override; guess_blend heuristic (hair->cutout, cape->alpha, effect->add).
  Texture cache. Fixed opaque path to NOT alpha-discard (was hiding bodies).
- harness: AABB-based camera framing, engine depth fog (MTGODOT_FOG=1),
  MTGODOT_ANIM_T fixed-pose capture (needs explicit anim reload first),
  MTGODOT_AUTOSHOT batch screenshot.

Verified: warrior 5/5 surfaces textured under the shader (t=9 dance pose,
lit, fogged). assassin 17/17 textured via dir scan. shaman_lord (gr2 v7)
renders. Screenshots test/golden/m25-*.png, m2c-t*.png.

Gap (M2.5 gate not closed): libgr2 POD API exposes no material/texture-name/
blend data, so multi-slot -> texture/blend mapping is heuristic only. Needs
a libgr2 material API (gr2_material.cpp walking the type tree) — same gap as
xrender-poc M1 T5. Documented in docs/GODOT-POC-PLAN.md §M2.5 T2.5.6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
2026-08-29 09:35:01 +09:00
Claude 27e39993f7 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
2026-08-29 09:23:29 +09:00
Claude 67109990ec README: record M0' T0.1–T0.3 verified, T0.4 pending export templates
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
2026-08-29 09:10:15 +09:00
Claude c52b8657c4 M0' scaffold: Godot 4.7 + GDExtension + libgr2 wired up (macOS)
- 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
2026-08-29 09:09:55 +09:00