diff --git a/docs/ANDROID-TESTING.md b/docs/ANDROID-TESTING.md index be80a140..a79c92b2 100644 --- a/docs/ANDROID-TESTING.md +++ b/docs/ANDROID-TESTING.md @@ -1,7 +1,7 @@ # Android 真机测试指南 -本文说明如何在 Android 真机上安装 `mtgodot-poc`、生成并导入资源包、验证资源挂载,以及进行 40250 客户端测试。 +本文说明如何在 Android 真机上安装本客户端(APK 产物名仍是 `mtgodot-poc.apk`,包名 `org.internal.mtgodotpoc`)、生成并导入资源包、验证资源挂载,以及进行 40250 客户端测试。 ## 1. 当前测试范围与已知前置条件 @@ -10,7 +10,7 @@ 当前仓库的资源部署存在一个需要特别注意的限制: - `export-android.sh --install` 会把资源推到 `/sdcard/Android/data/org.internal.mtgodotpoc/files/assets.zip`。 -- `project/asset_pack.gd` 首先查找 `user://assets.zip`,当前没有显式把上述外部目录加入候选路径。 +- `project/asset_pack.gd` 的候选顺序是 `MT_ASSETS_ZIP` 环境变量 → `user://assets.zip` → `OS.get_user_data_dir()/assets.zip` → 可执行文件目录 → `res://../assets.zip`(见 `_find_local_zip()`);上述外部目录不在候选路径里。 - 因此,Debug 真机测试应把外部目录中的压缩包再复制到应用私有 `files/` 目录,确保客户端的 `user://assets.zip` 能命中。 另外,40250 classic 网络栈当前由进程环境变量 `MT_PROTOCOL=classic` 选择。Android 通过 Activity 启动 APK 时不能直接继承 Mac 终端的环境变量;如果 APK 尚未增加 Android 侧的协议配置入口或将 classic 设为移动端默认值,则只能完成 APK、资源和 UI 验证,不能据此宣称已经完成真实 40250 登录验收。 @@ -34,7 +34,7 @@ 进入仓库并设置环境变量。路径按本机安装位置调整: ```bash -cd /Users/shenlei/Work/mt/mtgodot-poc +cd /Users/shenlei/Work/mt/metin2-client export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" export ANDROID_HOME="$ANDROID_SDK_ROOT" @@ -369,7 +369,7 @@ shasum -a 256 build/export/mtgodot-poc.apk \ 首次测试按以下顺序执行: ```bash -cd /Users/shenlei/Work/mt/mtgodot-poc +cd /Users/shenlei/Work/mt/metin2-client ./pack-assets.sh ./export-android.sh Debug diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 49ad0a5c..36c75fa5 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -1,4 +1,4 @@ -# mtgodot-poc — 未完成工作清单 +# metin2-client — 未完成工作清单 > 更新:2026-08-29 · 配套 [`GODOT-POC-PLAN.md`](./GODOT-POC-PLAN.md)(计划)、[`MIDREVIEW.md`](./MIDREVIEW.md)(中期评审)、 > [`SHINSOO-WORLD-RENDERING.md`](./SHINSOO-WORLD-RENDERING.md)(世界渲染实施)、[`PARITY-GAP.md`](./PARITY-GAP.md)(距参考图的保真化差距)。 @@ -55,8 +55,8 @@ | B6 | 贴图采样设置(filter / wrap / mip / 各向异性) | ⬜ | S | 现在 shader 里写死 `filter_linear_mipmap, repeat_enable`。 | | B7 | 顶点色 | ✅ 排查=不适用 | — | 全资产 `.gr2`(PC/monster2/effect)无 `DiffuseColor`/`Color0` 顶点成员(grep assets = 0)。Metin2 逐顶点色 = 地形雾(`MapOutdoorRenderSTP` 运行时动态 VB,非资产,Godot `Environment` 雾已覆盖)+ `.mse` `colorfactor`/粒子色(属 E7/§1.2)。libgr2 / `build_mesh` 无需动。详见 PARITY §2.6。 | | B8 | 切线帧(若确有法线贴图资产) | ⬜ | M | PNT332 无切线。xrender 时期结论:Metin2 资产**基本不用**法线贴图 —— 先排查有没有,再决定是否做(`SurfaceTool::generate_tangents()` 或从 UV 算)。 | -| B9 | 镜面 / 自发光 | 🟡 | M | Sphere-map 高光管线已端口(`SRC_MIX`/`SRC_SKIN` 加 `spec_map`/`spec_power`/`spec_enable`,复现 `Material.cpp:305 __ApplySpecularRenderState`:不透明面、相机空间 `reflect().xy` 查 `spheremap.jpg`、`EMISSION += sphere*tex.a*power`)。`Metin2Model.specular_power` 属性,默认 0 休眠 → 需装备层用 `item_proto bSpecular/100` 逐部位驱动。`SRC_ADD` / `SRC_ADD_SKIN` 已覆盖 additive 发光的一种,且 additive 动画面不再漏蒙皮。 | -| B10 | 背面剔除 / 双面 | 🟡 | S | 世界静态对象(W3/B1)已按 `two_sided` + 贴图名切 `CULL_DISABLED`。角色 `m2_material` shader 仍写死 `cull_back`(`MaterialDesc::two_sided` 死字段)。需给角色也按材质切。 | +| B9 | 镜面 / 自发光 | 🟡 | M | Sphere-map 高光管线已端口(`SRC_MIX`/`SRC_SKIN` 加 `spec_map`/`spec_power`/`spec_enable`,复现 `Material.cpp:305 __ApplySpecularRenderState`:不透明面、相机空间 `reflect().xy` 查 `spheremap.jpg`、`EMISSION += sphere*tex.a*power`)。`Metin2Model.specular_power` 属性,默认 0 休眠。✅ 装备层已接:`project/ui/equip_model.gd:283-288` `_apply_body()` 读 `item_proto` 的 `specular` 列并 `set("specular_power", sp / 100.0)`。⬜ 只驱动身体/护甲一个部位(武器 / 发型 / 时装未接),且 power 未按受控参考帧标定(PARITY §0 门禁)。`SRC_ADD` / `SRC_ADD_SKIN` 已覆盖 additive 发光的一种,且 additive 动画面不再漏蒙皮。 | +| B10 | 背面剔除 / 双面 | ✅ 首版 | S | 世界静态对象(W3/B1)已按 `two_sided` + 贴图名切 `CULL_DISABLED`。✅ 角色也已按材质切:`m2_material.cpp:188-211` `shader_for()` 对 `two_sided` 把 `cull_back` replace 成 `cull_disabled`(opaque/blend × 普通/蒙皮 四个变体各自缓存),`metin2_model.cpp:1182`(身体)/ `:769`(武器)经 `decide_two_sided()` 驱动,`_load_hair()` 直接置 true。⬜ 判据仍是启发式 —— `gr2::MaterialInfo.two_sided` 按材质名(`2side`/`twoside`/`double`/`leaf`/`tree`/`ivy`/`fence`)+ 表面名兜底(`hair`/`cloak`/`cape`/`skirt`/`ribbon`/`leaf`),不是真 `ExtendedData` 的 "Two-sided"。 | | B11 | winding 逐模型验 | ⬜ | S | `flip_winding` 默认 false(4 个骨架验过,未全量)。 | --- @@ -117,14 +117,14 @@ | ID | 项 | 状态 | 规模 | 说明 | |---|---|---|---|---| -| F0 | vendored 原生依赖 | ✅ | S | libsodium / libzstd / miniLZO 从 Homebrew 改为**源码内建**(`extension/third_party/`,pinned submodule + vendored miniLZO)—— 同一棵树可交叉编 NDK / iOS SDK。别名 `mt3p::sodium` / `mt3p::zstd` / `mt3p::minilzo`,无源码改动,macOS 9/9 CTest + 真实 item/mob_proto 解码逐字一致。详见 `docs/THIRD-PARTY.md`。miniLZO 是 GPL —— 自用可接受(同 oodle1.c),发布前须换。 | +| F0 | vendored 原生依赖 | ✅ | S | libsodium / libzstd / miniLZO 从 Homebrew 改为**源码内建**(`extension/third_party/`,pinned submodule + vendored miniLZO)—— 同一棵树可交叉编 NDK / iOS SDK。2026-09-02 起第四个:**Crypto++ 8.4.0**(vendored 源码,`MT_PROTOCOL=classic` 的 DH2/CTR cipher 用,公有领域许可)。别名 `mt3p::sodium` / `mt3p::zstd` / `mt3p::minilzo` / `mt3p::cryptopp`,无源码改动,macOS 9/9 CTest + 真实 item/mob_proto 解码逐字一致。详见 `docs/THIRD-PARTY.md`。miniLZO 是 GPL —— 自用可接受(同 oodle1.c),发布前须换。 | | F1 | iOS(iPhone 16 / Metal) | 🟡 | M | ✅ 交叉编译打通:`./build-ios.sh` 出 `libmtgodot.ios.template_{debug,release}.a`(arm64 / minos 15.0),全依赖(libsodium/zstd/miniLZO/libgr2/formats/mtnet/godot-cpp)随之交叉编过;iOS 强制 extension 为 STATIC(不能 dlopen),`.gdextension` 加了 `[dependencies] ios.*` 归档清单。⬜ 剩 Godot 4.7 iOS 导出模板 + 导出 preset 链 `project/bin/ios/*.a` + 免费 provisioning 签名 + iPhone 16 真机跑 + Release/strip。预留 2–3 天。详见 `docs/PLATFORMS.md`。 | | F2 | Android(一加 13 / Vulkan) | 🟡 | M | ✅ CMake 已接 `CMAKE_SYSTEM_NAME==Android`(SHARED `.so` + `arm64` 名字后缀);`./build-android.sh` 走 NDK 自带 `android.toolchain.cmake`(`arm64-v8a` / `android-24`)。⬜ 本机无 NDK —— 装 NDK 后 `./build-android.sh` 出 `.so`,再 Godot Android 导出 preset(Gradle)+ 一加 13 真机跑。**只验 Vulkan**,不做 Compatibility(GLES3) / 老机。详见 `docs/PLATFORMS.md`。 | | F3 | 三端一致性 | ⬜ | S | 同工程 / 同 t / 同机位截图,iPhone 16 · 一加 13 · Mac 对齐。 | | F4 | 移动端性能 / 帧预算 | 🟡 | M | ✅ **GPU 纹理压缩**:`extension/src/texture_util.{h,cpp}` `make_color_texture()` —— 移动端(`OS.has_feature("mobile")`)或 `MTGODOT_TEXCOMP=1` 时对颜色贴图运行时 ASTC 8x8(~2bpp vs RGBA8 32bpp),编码失败回退 RGBA8;桌面默认关,逐调用点保留原 mipmap 标志 → OFF 时与旧路径逐字节一致。接入:角色皮肤(`_load_dds`)、建筑 albedo(`static_object`)、树皮/树冠(`tree_placeholder`);splat 控制图 / 阴影图 / 水 / HUD 保持 RGBA8。macOS+iOS 编过,ctest 9/9,桌面渲染无变化,`MTGODOT_TEXCOMP=1` 角色渲染正常。⬜ 剩:一加 13 / iPhone 16 单 + N 角色 bench(`MTGODOT_GPUSKIN=1` 人群)、构建成本(~170ms/角色,A5/G2)、移动端画质核对。 | | F5 | App 生命周期 | 🟡 | S–M | ✅ `project/app_lifecycle.gd`(AppLifecycle 节点):把 `NOTIFICATION_APPLICATION_PAUSED/RESUMED` / `FOCUS_IN/OUT` / `OS_MEMORY_WARNING` / `WM_GO_BACK_REQUEST` 收成信号,默认动作 = 后台暂停树 + 停 BGM + 降 `max_fps`,前台还原;`bind(m2client, audio)` 一键接。`M2Client`(C++):`_notification` 收 PAUSED/RESUMED → `suspend()/resume()`(挂起时不 pump socket),`reconnect()` 用存好的凭据重登;新信号 `suspended`/`resumed`。已接 `login.gd`(回前台若在游戏中自动重连)+ `world_demo.gd`。headless smoke(suspend/resume/reconnect 绑定 + 幂等 + 信号)通过,ctest 9/9,iOS 编过。渲染上下文丢失由 Godot Vulkan 自恢复,GPU 资源全 RenderingServer 托管,无需处理。⬜ 剩真机验证后台 30s→掐连接→回前台重连、iOS 内存告警实际触发。 | | F6 | 打包 | ⬜ | S–M | macOS / iOS / Android export preset、资产打包策略、iOS 签名。自用,无商店上架。 | -| F8 | 实体点选精度 | ✅ 首版 | S | `player_controller._ray_pick_t()`:射线 vs 每个 pickable 下所有 `MeshInstance3D` 的**世界 AABB 合并 + slab 相交**(逐实体精确,大怪 / 小怪不再被固定 1.4 m 半径误判;`grow(0.15)` 宽容度)。无网格回退旧胶囊近似。对齐 `EterGrnLib/ModelInstanceCollisionDetection::Intersect` 的**结果**,不复刻逐三角。`eterngrn_polish_test.gd` 覆盖(命中 t / 未命中 -1 / 擦边 / 无网格回退)。⬜ 逐三角 / 骨骼包围体、`GetHeight`。 | +| F9 | 实体点选精度 | ✅ 首版 | S | `player_controller._ray_pick_t()`:射线 vs 每个 pickable 下所有 `MeshInstance3D` 的**世界 AABB 合并 + slab 相交**(逐实体精确,大怪 / 小怪不再被固定 1.4 m 半径误判;`grow(0.15)` 宽容度)。无网格回退旧胶囊近似。对齐 `EterGrnLib/ModelInstanceCollisionDetection::Intersect` 的**结果**,不复刻逐三角。`eterngrn_polish_test.gd` 覆盖(命中 t / 未命中 -1 / 擦边 / 无网格回退)。⬜ 逐三角 / 骨骼包围体、`GetHeight`。 | | F7 | 触屏输入 / 手势相机 | ✅ | S | `game_camera.gd`:单指拖拽环绕(8px deadzone,低于视作点击)/ 双指捏合缩放;`player_controller.gd`:单指轻点(≤12px 位移 & ≤0.35s)= 点地 / 点选,拖拽或双指手势时不触发。`project.godot` 关 `emulate_mouse_from_touch`(避免与轻点重复触发),桌面鼠标路径不变。两脚本 `--check-only` + 项目导入无错。 | | F8 | `godot-cpp` 钉稳定版 | ✅ | S | gitlink 精确锁定 `101ae38034304346a46ea9ea84ae156d3e860496`,`.gitmodules` 已移除移动的 `master` branch 配置;目标 API 固定为 Godot 4.7。 | @@ -183,7 +183,7 @@ UI / Python 脚本层、多角色 AI、网络、玩法逻辑、eterpack(Phase ### Sprint 1 —— 小项收尾(~1 周) -已完成:`A3` · `A6` · `F8` · `I1`,并补完 `A9`/`A10` 正确性问题。 +已完成:`A3` · `A6` · `F8`(`godot-cpp` 钉版)· `I1`,并补完 `A9`/`A10` 正确性问题。 剩余:`A5`(CPU 路径原地更新)· `A7`(前台 Metal stress 基线)· `A8`+`H1`(桥接层畸形用例与全语料 draw 冒烟)。 ### Sprint 2 —— 组合基础(~2 周) diff --git a/docs/CLIENT-GAP-FIX.md b/docs/CLIENT-GAP-FIX.md index 86949f56..9684f154 100644 --- a/docs/CLIENT-GAP-FIX.md +++ b/docs/CLIENT-GAP-FIX.md @@ -10,7 +10,7 @@ > **[待读参考]** 并给出应读的参考文件,而不是臆造。 > > 路径约定:`REF/` = `/Users/shenlei/Work/mt/40250/ClientVS22/source/`, -> `POC/` = `/Users/shenlei/Work/mt/mtgodot-poc/`。 +> `POC/` = `/Users/shenlei/Work/mt/metin2-client/`。 > > 创建 / 修订日期:2026-09-04 @@ -7880,7 +7880,7 @@ dx² + dy² > s_fLimitDistance → 不播 project/inventory_ui_test.gd —— FakeProto.item() 补 sub_type=0 / anti_flags=0 / wear_flags=1<<4 / limits=[] 让 iv.use(5)(vnum 19 武器)仍过新前置门 -测试:M2_ASSETS=/Users/shenlei/Work/mt/mtgodot-poc/assets ctest --test-dir build → 21/21 +测试:M2_ASSETS=/Users/shenlei/Work/mt/metin2-client/assets ctest --test-dir build → 21/21 (无 C++ 改动);56 个 GDScript 回归全 PASS(新增 equip_rules_test;inventory_test / inventory_ui_test 保持绿)。 @@ -7961,7 +7961,7 @@ dx² + dy² > s_fLimitDistance → 不播 weapon_gr2 / shield_gr2 / hair_gr2、脱时装武器重解析、婚纱(桩返回 201)遮武器保头发、 婚纱换捧花(vnum 50201)_last_weapon_vnum 恢复为 50201 -测试:M2_ASSETS=/Users/shenlei/Work/mt/mtgodot-poc/assets ctest --test-dir build → 21/21 +测试:M2_ASSETS=/Users/shenlei/Work/mt/metin2-client/assets ctest --test-dir build → 21/21 (无 C++ 改动);57 个 GDScript 回归全 PASS(新增 part_hiding_test;equip_model_test / equip_rules_test / inventory_ui_test 保持绿)。 @@ -9083,7 +9083,7 @@ dx² + dy² > s_fLimitDistance → 不播 🔎 真机目视:鼠标悬停某 NPC / 怪 → 其头顶名字立即强显(即便未开「总显名字」且超 3500 距)、 移开 → 复原;鼠标压 UI / 悬停地面物品时不强显任何角色名。 参考(sole baseline = /Users/shenlei/Work/mt/40250/ClientVS22/source/;运行时 python = - mtgodot-poc/assets/root/game.py): + metin2-client/assets/root/game.py): root/game.py:1519 GameWindow.OnRender() root/game.py:1531 textTail.UpdateAllTextTail() root/game.py:1533 if True == wndMgr.IsPickedWindow(self.hWnd) —— 鼠标在 3D game 窗口上才拾取 diff --git a/docs/CLIENT-GAP.md b/docs/CLIENT-GAP.md index 577f980a..91d33e9c 100644 --- a/docs/CLIENT-GAP.md +++ b/docs/CLIENT-GAP.md @@ -1,4 +1,4 @@ -# 40250/ClientVS22 → mtgodot-poc 1:1 功能差距 +# 40250/ClientVS22 → metin2-client 1:1 功能差距 > 对照范围:`../40250/ClientVS22/`(40250 客户端源码及其运行时模块)以及本工程当前实现。 > 当前工程默认运行 `MT_PROTOCOL=classic`;`extension/src/net/wire.h` 仅属于显式选择的 m2dev @@ -9,7 +9,7 @@ > 渲染细节另见 `PARITY-GAP.md`,动画、材质和资产管线另见 `BACKLOG.md`,网络实现另见 > `CLIENT-PORT.md`。 > -> **本文末「ClientVS22 实现细节审计(2026-09-04)」每一条的具体修改细节见 +> **本文末「ClientVS22 实现细节审计(2026-09-02)」每一条的具体修改细节见 > [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md)**(逐条给出 40250/ClientVS22 的参考 > 源码位置、常量与算法,以及本工程要改的文件/函数;其附录 A 列出本节中 > 需要订正的条目,分「参考行为不符 / 表述需加限定 / 低估了当前差距」三档)。 @@ -1848,7 +1848,7 @@ ## 结论 -`mtgodot-poc` 已经不是早期“只能登录、移动和攻击”的骨架:登录到 PHASE_GAME、基础战斗、 +`metin2-client` 已经不是早期“只能登录、移动和攻击”的骨架:登录到 PHASE_GAME、基础战斗、 背包 / 装备、技能 UI、任务、聊天、组队 / 好友、商店 / 交易 / 仓库、精炼, 地图和部分特效都已有可运行首版。 @@ -2065,7 +2065,7 @@ enum,不存在 `namespace CG` / `namespace GC`。本文此前的“68 + 109 = ### 审计口径 -本节以当前 `mtgodot-poc` 源码和 `40250/ClientVS22` 源码做静态对照,重点检查 +本节以当前 `metin2-client` 源码和 `40250/ClientVS22` 源码做静态对照,重点检查 `UserInterface`、`GameLib`、`EffectLib`、`MilesLib`、`EterLib`、`EterGrnLib`、 `SpeedTreeLib` 和 `PRTerrainLib` 中承担实际运行时语义的模块。 它不是简单的功能勾选表,而是把问题分成四类: @@ -2091,14 +2091,14 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 角色列表保存每槽 `lAddr/wPort` 后,`M2Client.enter_game(index)` 会断开第一条游戏连接, 按角色槽地址重连,并在 DirectEnter 的 SELECT 阶段发送 `CG_CHARACTER_SELECT`。 `MT_CLASSIC_ENTER_DELAY` 和真实两次 TCP 连接验收仍待完成。见 - [`classic_session.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp)、 - [`m2_client.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp)。 + [`classic_session.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/classic/classic_session.cpp)、 + [`m2_client.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/m2_client.cpp)。 2. **游戏中断线的回登录链路已接入,真实 TCP 断线仍待验收。** 已建立游戏连接后的 Classic 失败发 `disconnected`,`AppFlow` 无条件回 LOGIN;认证 / 游戏连接建立前的失败仍发 `login_failed`。`ReconnectUI` 已从 AppFlow 主流程移除,但独立文件仍保留作旧测试 / 可选增强。 - 见 [`m2_client.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp)、 - [`app_flow.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_flow.gd)。 + 见 [`m2_client.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/m2_client.cpp)、 + [`app_flow.gd`](/Users/shenlei/Work/mt/metin2-client/project/app_flow.gd)。 3. **跨服或换图的场景重建已完成首版,但还没有真实服务器验收。** Classic `GC_WARP` 已按 `lAddr:wPort` 重连;`world_reset` 会清理旧实体、掉落物、PVP / 决斗、目标和交互窗, @@ -2108,11 +2108,11 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 4. **选人和进入游戏吞掉底层发送失败(已修复第一批)。** `select_char()` / DirectEnter 选人 / `enter_game()` 现在透传 `send_fixed()` 结果,`CG_ENTERGAME` 成功后执行 flush; GDScript 入口优先调用 `enter_game(index)`。失败提示和真实发送失败注入仍待补充。 - 见 [`classic_session.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp)。 + 见 [`classic_session.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/classic/classic_session.cpp)。 5. **客户端版本包是硬编码的。** 当前固定发送 `metin2.bin` 和旧 timestamp,未读取实际 构建版本、文件时间或资源包版本。服务端开启版本校验时,更新客户端后仍可能被当成旧客户端。 - 见 [`classic_session.cpp:335`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp:335)。 + 见 [`classic_session.cpp:335`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/classic/classic_session.cpp:335)。 6. **Passpod、HackShield、XTrap、StateChecker 等兼容路径没有实现。** 当前选择了不伪造 这些回应;如果目标 40250 服务端启用了其中任意一项,登录或进场可能被断开。应把“不支持” @@ -2121,12 +2121,12 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 7. **未知包的 Loading 重同步问题已修复第一批。** 当前不再扫描后续字节寻找 `GC_CHARACTER_ADD` 边界;未知 header 记录最近包、清空缓冲并断开,保留 `header == 0` 填充跳过。真实加密流验收仍待完成。见 - [`classic_stream.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_stream.cpp)。 + [`classic_stream.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/classic/classic_stream.cpp)。 8. **已知但没有业务处理的包静默丢弃问题已修复第一批。** `ClassicParser` 现在记录阶段 / header 并返回失败,连接层清空缓冲并断开;可选集成包不会再静默成功。按阶段拆分处理表和 全量业务覆盖仍待完成。见 - [`classic_parser.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_parser.cpp)。 + [`classic_parser.cpp`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/classic/classic_parser.cpp)。 9. **封包覆盖仍停留在结构和尺寸层。** 还需要逐包核对 sub-header、动态 body、实体状态、 UI 消费、发送后的锁定状态、失败回滚和重连恢复,不能仅以 `Packet.h` 中存在 header 作为完成。 @@ -2144,17 +2144,17 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `mut_dead`、`mut_stun`、物品更新等路径遇到未知实体直接返回,没有 pending state 或重放机制。 角色添加顺序变化时,位置、HP、死亡、动作和装备信息会丢失;`GC_SHOP_SIGN` 当前有独立的 招牌 fallback,不能与普通 Actor 更新混为一谈。见 - [`entity_store.cpp:123`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:123)。 + [`entity_store.cpp:123`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/entity_store.cpp:123)。 2. **`GC_CHAR_ADDITIONAL_INFO` 早于 Spawn 时状态不会被待合并缓存。** `mut_char_info()` 可以 创建 / 更新 Entity,但新建时不发送 Spawn change;如果后续 Spawn 正常到达,NetWorld 仍会 创建节点,若 Spawn 丢失或顺序异常则实体会长期只有数据没有表现层节点。见 - [`entity_store.cpp:458`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:458)、 - [`net_world.gd:128`](/Users/shenlei/Work/mt/mtgodot-poc/project/net_world.gd:128)。 + [`entity_store.cpp:458`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/entity_store.cpp:458)、 + [`net_world.gd:128`](/Users/shenlei/Work/mt/metin2-client/project/net_world.gd:128)。 3. **VID 重用时旧状态可能污染新实体。** `mut_spawn()` 没有完整重置 dead、stunned、mount、 guild、pk、alignment、parts、shop sign、affect 和上一动作状态,只覆盖了基础出生字段。 - 见 [`entity_store.cpp:75`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:75)。 + 见 [`entity_store.cpp:75`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/entity_store.cpp:75)。 4. **点数组 API 对部分更新可能残留旧值。** `mut_set_points()` 只复制本次传入数量,不清理 上一次更长数组的尾部;不过当前固定长度 Classic 包路径传入 `POINT_MAX_NUM`,该路径不会 @@ -2163,7 +2163,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 5. **实体数据字段不完整。** empire、称号、公会名、公会徽章、名字颜色、alignment 颜色、 target highlight、attackable、weapon mode、sex/job/race motion mode、affect 图标和 剩余时间没有形成完整统一的实体状态。`mut_char_info()` 当前还明确忽略 `empire`。 - 见 [`entity_store.cpp:477`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:477)。 + 见 [`entity_store.cpp:477`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/entity_store.cpp:477)。 6. **一次性事件没有可靠的生命周期缓存。** 聊天、脚本、特效、飞行物、任务提示等事件主要 通过 drain queue 即时消费;如果 UI 或 GameScene 尚未完成装配,事件可能被消费后无法补发。 @@ -2185,7 +2185,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `isLock` / `IsUsingMovingSkill`)未过时存 `_reserved_ground` + `RESERVED_GROUND_DELAY` 0.1 s 延迟重试(≈ `__ReserveClickGround` / `NEW_IsEmptyReservedDelayTime`),`moving_skill` 只转向。 WASD 分支置 `_is_going = false`(≈ `NEW_MoveToDirection`)。见 - [`player_controller.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/player_controller.gd) + + [`player_controller.gd`](/Users/shenlei/Work/mt/metin2-client/project/player_controller.gd) + `project/player_move_test.gd`。**增量 93 补完 Actor-Actor 碰撞 + 坐骑转向速度**: `player_controller._actor_blocked(next_pos)` 近似 `CActorInstance::TestActorCollision` (`ActorInstanceCollisionDetection.cpp`)+ `CInstanceBase::CheckAdvancing` @@ -2243,7 +2243,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 + `CInstanceBase::__GetBowRange` `InstanceBase.cpp:694`)。仍未接: `AttackingData` / `HitPosition` 驱动的命中帧 / 硬直 / 无敌帧的真实动态圆柱扫掠(§3.5, 当前 reach + 正面弧近似)、mount 与武器规则、服务端合法性校准。见 - [`net_play.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/net_play.gd:200)。 + [`net_play.gd`](/Users/shenlei/Work/mt/metin2-client/project/net_play.gd:200)。 4. **自动攻击目标预留逻辑已移植(增量 75)。** ~~当前主要是检测距离后发攻击包~~ —— `net_play.gd` 新增 `enum ReservedMode` + `_update_auto_attack()`(照 `PythonPlayer.cpp:207`,检查目标存在 / @@ -2362,8 +2362,8 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `vel.y`= `-Y`;怪物球体碰撞 / `CullingMgr.ForInRange` / 建筑体素 r=1 判定未移植(客户端 飞行物只做命中"时机",伤害服务端裁决);无 `.fly` 数据文件加载器 → 合成箭参数。仍待:真服 `CG_FLY_TARGETING` z 分量、`.fly` 数据文件解析。见 - [`fly_object.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/fly_object.gd) / - [`game_scene.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd) + [`fly_object.gd`](/Users/shenlei/Work/mt/metin2-client/project/fly_object.gd) / + [`game_scene.gd`](/Users/shenlei/Work/mt/metin2-client/project/game_scene.gd) `_queue_shot` / `_on_local_motion_event`。 7. **受击状态分裂。** ~~`EntityStore.stunned` 和 `NetPlay._hitstun_until` 是两套互不统一的状态~~ @@ -2443,7 +2443,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `mtnet::bounds::skill_index`(与 `use_skill` 同一谓词)。升级结果由 `GC_SKILL_LEVEL` 经 `skills_changed` 驱动刷新,不本地伪造。`skill_test.gd` +`§3.10` 段。 C++ **21/21** + **13** GDScript 全绿。见 - [`m2_client.cpp:961`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp:961)、 + [`m2_client.cpp:961`](/Users/shenlei/Work/mt/metin2-client/extension/src/net/m2_client.cpp:961)、 参考 [`PythonSkill.cpp:1833`](/Users/shenlei/Work/mt/40250/ClientVS22/source/UserInterface/PythonSkill.cpp:1833)、 [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md) §3.10 / C.5。 @@ -2505,10 +2505,10 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `game_scene` 对 `ch_type==0` 创建 `RemotePlayerView`,复用真实 `PlayerView` race / GR2 / MSA; 远端 `EquipModel` 只消费 `awPart[ARMOR,WEAPON,HEAD,HAIR]`,`net_world` 按四字段调用 setter, 不读取本地 `get_equipment()`。所有职业 / race、变身字段、远端真服换装和精确视觉仍待验收。见 - [`inventory_ui.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/inventory_ui.gd) / - [`equip_rules.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/equip_rules.gd) / - [`part_hiding.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/part_hiding.gd) / - [`equip_model.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/equip_model.gd),详见 + [`inventory_ui.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/inventory_ui.gd) / + [`equip_rules.gd`](/Users/shenlei/Work/mt/metin2-client/project/equip_rules.gd) / + [`part_hiding.gd`](/Users/shenlei/Work/mt/metin2-client/project/part_hiding.gd) / + [`equip_model.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/equip_model.gd),详见 [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md) §4.1 / C.5。 2. **物品拖放已有全局首版,但还不是完整原版鼠标物品态。** 已有全局持有物品、鼠标跟随图标、 @@ -2529,7 +2529,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `/safebox_close`(`cmd.cpp:350`,对齐原版关窗即服务器侧关闭);改密走 `/safebox_change_password `(`cmd.cpp:354`,各 1..6 位)三段密码 ConfirmationDialog; `mall_ui.close()` 同步补 `/mall_close`。仍缺 45 格网格、分页、指定目标格、仓库内移动、重试 - 限制和原版互斥规则。见 [`safebox_ui.gd:7`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/safebox_ui.gd:7)。 + 限制和原版互斥规则。见 [`safebox_ui.gd:7`](/Users/shenlei/Work/mt/metin2-client/project/ui/safebox_ui.gd:7)。 5. **交易结束 / 失败提示已补(增量 129),事务状态仍缺。** 已核实 `GC_END` 对成功与取消发同码 (`exchange.cpp` `EXCHANGE_END`→`Cancel()`),包级不可区分——`EntityStore.ExchangeState` 新增 @@ -2551,7 +2551,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 置灰 + 激活守卫)、`affect_added`/`affect_removed`(affect 220 = 变身,技能槽置灰)、 `inventory_changed`(物品槽 `get_item` 为空 / vnum==0 时置灰并显示 `×vnum`,覆盖换武器 / 服务器回收)。仍缺特殊技能冷却。见 - [`quickbar.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/quickbar.gd)。 + [`quickbar.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/quickbar.gd)。 9. **组队状态是近似定义。** affect 标签仍有暂定映射,队伍技能冷却、队长权限动态变化、离线、 死亡、距离状态和多账号行为还未完成真实验收。 @@ -2599,8 +2599,8 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 `iConfirmEndTime` 的 `timeGetTime`、`.msc` 事件文件加载(`RUN_CINEMA` 换文件)、`MakeNextButton` NEXT vs DONE 文案区分 + `DoneEvent()` 回调、`camera_event_requested` / `fade_event_requested` / `cinema_requested` 消费端与 §7.6 相机模式对接、物品名颜色 `(1.0,0.2,0.2)`。见 - [`quest_dialog.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/quest_dialog.gd) / - [`quest_event_test.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/quest_event_test.gd)。 + [`quest_dialog.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/quest_dialog.gd) / + [`quest_event_test.gd`](/Users/shenlei/Work/mt/metin2-client/project/quest_event_test.gd)。 2. **任务日志已补 5 行窗口、滚动、默认图标、倒计时和独立 QuestButton 条带,但不是完整 Quest 系统。** 增量 101 按参考端 `QUEST_MAX_NUM=5` 固定显示 5 行;超过 5 条滚动到离散的 @@ -2899,13 +2899,13 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 1. **本地角色先显示胶囊占位体。** GameScene 初始化阶段先创建 Capsule,真实角色稍后加载, 会造成模型闪烁、相机目标高度变化、碰撞体和阴影重新挂接。见 - [`game_scene.gd:136`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd:136)、 - [`game_scene.gd:811`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd:811)。 + [`game_scene.gd:136`](/Users/shenlei/Work/mt/metin2-client/project/game_scene.gd:136)、 + [`game_scene.gd:811`](/Users/shenlei/Work/mt/metin2-client/project/game_scene.gd:811)。 2. **SpeedTree 仍是程序化代理。** 当前只嗅探 bark/composite 贴图,叶簇 UV 和枝干使用 proxy, 没有真实 `.spt` 几何、leaf table、LOD、impostor、风参数和原始阴影。见 - [`tree_placeholder.cpp:190`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/tree_placeholder.cpp:190)、 - [`PARITY-GAP.md:77`](/Users/shenlei/Work/mt/mtgodot-poc/docs/PARITY-GAP.md:77)。 + [`tree_placeholder.cpp:190`](/Users/shenlei/Work/mt/metin2-client/extension/src/tree_placeholder.cpp:190)、 + [`PARITY-GAP.md:77`](/Users/shenlei/Work/mt/metin2-client/docs/PARITY-GAP.md:77)。 3. **EffectLib 已从粒子近似推进到 `.mde` 网格子集。** `.mse` Particle 已映射到 `GPUParticles3D`,现在会读取首个 `TextureFiles` 的实际 `.dds`、位置、发射形状、方向 / 速度; @@ -2914,8 +2914,8 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 接入 SimpleLight 的范围 / 生命周期曲线和 TexAni 基础帧延迟 / 顺逆序;增量 66 接入统一的 MovingType 直接段 / 二次 Bezier 位置曲线,并让粒子、网格、Light 共用。仍缺 TexAni 随机起始帧 / 每粒子语义、完整 emitter advanced / cycle、完整时间轴、骨骼挂点和真实资源逐项验收。见 - [`effect_player.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/fx/effect_player.gd)、 - [`mde.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/fx/mde.gd)。 + [`effect_player.gd`](/Users/shenlei/Work/mt/metin2-client/project/fx/effect_player.gd)、 + [`mde.gd`](/Users/shenlei/Work/mt/metin2-client/project/fx/mde.gd)。 4. **GR2 运行时不是完整 ActorInstance。** 缺 race/weapon/sex motion mode、loop/interrupt/ accumulation、action reservation、mount motion、武器轨迹、击退、倒地、起身和完整死亡动作。 @@ -2926,18 +2926,18 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 6. **材质是固定功能近似。** 缺精确 alpha-test ref、多贴图 stage、color operation、透明排序、 角色专属光照和逐 surface 状态;sphere-map 默认 `spec_power = 0`,没有完整由装备数据驱动。 - 见 [`m2_material.h:39`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/m2_material.h:39)。 + 见 [`m2_material.h:39`](/Users/shenlei/Work/mt/metin2-client/extension/src/m2_material.h:39)。 ### 7. P1/P2:地图、环境和相机 1. **静态地图只完整接入 Building / DungeonBlock。** Effect、Ambience、地图机关、透明物排序、 portal/area、对象脚本状态和对象 LOD 没有完整迁移。见 - [`metin2_world.cpp:367`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/metin2_world.cpp:367)。 + [`metin2_world.cpp:367`](/Users/shenlei/Work/mt/metin2-client/extension/src/metin2_world.cpp:367)。 2. **Pack 资源读取没有统一。** 虽然 `AssetSource`/`PackMount` 已存在,World、terrain、tree、 water 和 static object 仍有直接 `AssetResolver::resolve()` 的路径,散文件和 `.epk/.eix` 运行 - 结果可能不同。见 [`metin2_world.cpp:380`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/metin2_world.cpp:380)、 - [`terrain_splat.cpp:133`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/terrain_splat.cpp:133)。 + 结果可能不同。见 [`metin2_world.cpp:380`](/Users/shenlei/Work/mt/metin2-client/extension/src/metin2_world.cpp:380)、 + [`terrain_splat.cpp:133`](/Users/shenlei/Work/mt/metin2-client/extension/src/terrain_splat.cpp:133)。 3. **地形 splat 虽已支持 16 层,仍非完整固定功能复现。** alpha 含义、texture wrap/filter、 多 stage、patch geometry LOD、skirt、patch 碰撞和 baked shadow mask 叠加规则仍未完成。 @@ -2954,7 +2954,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 6. **相机不是固定基线机位。** 当前依赖脚本中的 FOV、yaw、pitch、距离和 AABB 射线,缺 `CameraProcedure` 等价模式、灵敏度持久化、平滑碰撞、树木遮挡淡出、目标 Actor 状态和可回放 - 的受控机位。见 [`PARITY-GAP.md:195`](/Users/shenlei/Work/mt/mtgodot-poc/docs/PARITY-GAP.md:195)。 + 的受控机位。见 [`PARITY-GAP.md:195`](/Users/shenlei/Work/mt/metin2-client/docs/PARITY-GAP.md:195)。 ### 8. P1/P2:UI、输入和本地化 @@ -2968,7 +2968,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 3. **帮助窗口已有首版,仍缺视觉等价验收。** `help_ui.gd` 已装载 `helpwindow.py` 的布局和帮助 文案,H 键及 ESC 菜单入口已接通;仍需 `.sub` 贴图、字体描边和真分辨率视觉验收。见 - [`help_ui.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/help_ui.gd)。 + [`help_ui.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/help_ui.gd)。 4. **系统设置部分只是保存数值。** CPU/GPU tiling 不会改变实际渲染,BGM 更换按钮 disabled, 部分画质设置没有完整接入渲染侧;名字颜色本身已 1:1(增量 110)、头顶称号色 / 公会名 / @@ -2980,7 +2980,7 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 「empire 名字颜色」开关尚未接到 `net_world.set_empire_name_mode(bool)`(默认关)、 `EnablePKTitle` 开关尚未接到 `net_world.set_pk_title_enabled(bool)`(默认开)、 `TextTail_SetLivingTime`(气泡停留秒数)尚无运行期钩子(默认 5000)。见 - [`system_option_ui.gd:143`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/system_option_ui.gd:143)。 + [`system_option_ui.gd:143`](/Users/shenlei/Work/mt/metin2-client/project/ui/system_option_ui.gd:143)。 5. **鼠标物品态和光标已有首版,复杂窗口语义仍不完整。** `mouse_controller.gd` 已提供鼠标跟随 物品、投放到背包 / 快捷栏、无效区域 / 右键 / ESC / 失焦取消;`cursor_manager.gd` 已提供 @@ -3152,19 +3152,19 @@ Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移 9. **任务导航和完整资源视觉仍未完成。** 增量 101 已接入默认 / 服务端 icon、5 行滚动、倒计时和 QuestButton 事件参数;仍缺世界箭头、屏幕边缘指示、多列定位、真实 `.sub` 状态图和服务端 - `iStartTime` 生命周期。见 [`quest_log.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/quest_log.gd)。 + `iStartTime` 生命周期。见 [`quest_log.gd`](/Users/shenlei/Work/mt/metin2-client/project/ui/quest_log.gd)。 ### 9. P1/P2:音频和应用生命周期 1. **地图 BGM 已有首版消费,仍缺真服 / 完整 MilesLib 验收。** `GC_MAIN_CHARACTER3_BGM` 和 `GC_MAIN_CHARACTER4_BGM_VOL` 已路由到 `bgm_changed`,`bgm_director.gd` 按服务端名称、 `musicInfo.fieldMusic` 回落和默认音量选曲;仍缺完整声音事件模型及真实 40250 地图验证。见 - [`bgm_director.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/bgm_director.gd)。 + [`bgm_director.gd`](/Users/shenlei/Work/mt/metin2-client/project/bgm_director.gd)。 2. **后台 BGM 恢复已有首版,仍缺真机验收。** `AppLifecycle` 后台保存并静音音量,回前台恢复 音量和当前曲目状态,不再清空 `_bgm_name`;Android/iOS 前后台切换和系统回收 TCP 后的恢复 仍未在真机验证。见 - [`app_lifecycle.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_lifecycle.gd)。 + [`app_lifecycle.gd`](/Users/shenlei/Work/mt/metin2-client/project/app_lifecycle.gd)。 3. **生命周期单一属主代码已完成,仍缺平台验收。** 生产入口由 `AppFlow` 持有唯一 `AppLifecycle`,`GameScene` 不再创建,`M2Client` 不再直接处理应用暂停 / 恢复通知;旧 diff --git a/docs/CLIENT-PORT.md b/docs/CLIENT-PORT.md index 3f184dc9..95bc777a 100644 --- a/docs/CLIENT-PORT.md +++ b/docs/CLIENT-PORT.md @@ -1,6 +1,12 @@ # 客户端移植 —— 从素材渲染器到能联机的客户端 -> 起点:`mtgodot-poc` 的渲染纵切「差不多能用」(见 [`PARITY-GAP.md`](./PARITY-GAP.md))。 +> **适用范围(2026-09-08 复核)**:本文描述的是 **m2dev fork 协议**后端。该后端**已不是默认**—— +> `project/client_main.gd:14-15` 在 `MT_PROTOCOL` 为空时无条件兜底成 `classic`,当前默认走 +> 40250 经典协议。迁移方案与 classic 后端的协议细节见 [`CLIENT-40250-PORT.md`](./CLIENT-40250-PORT.md)。 +> 本文仍然有效的部分:m2dev 后端自身的握手 / 封包 / 相位编排(`MT_PROTOCOL=m2dev` 时仍可切回), +> 以及 §2 记录的移植进度。**不要**把本文的封包号或 `Packet.h` 依据用到 classic 后端上。 + +> 起点:本仓库(`metin2-client`)的渲染纵切「差不多能用」(见 [`PARITY-GAP.md`](./PARITY-GAP.md))。 > 本文是把它变成**能连服务端玩**的客户端的分层计划。分水岭是**网络层**——别的玩家、 > 物品、战斗、聊天都以封包为数据源。 > @@ -9,8 +15,8 @@ ## 0. 目标服务端 -- LAN:`192.168.21.203`(这台 Mac 能路由到,但握手端口目前 **connection refused** —— - 服务端进程没起 / 端口和默认不同,需确认)。 +- LAN:`192.168.21.203`。~~握手端口 connection refused~~ —— **已解决(2026-08-30)**:真机 + 端到端已打通到 PHASE_GAME,见本文 §3。 - 默认端口(`assets/root/serverinfo.py`):auth `11000`,频道/游戏 `11011/11021/11031/11041`。 - 测试账号:`admin` / `123456789`。 @@ -217,7 +223,10 @@ AEAD 加密 session token。 ## 4. 下一步 -1. 打通到 `.203` 的连通性(见上)。可达后立即能测: +> **本节 1–4 项已于 2026-08-30 全部完成**(见 §3 与 [`CLIENT-ROADMAP.md`](./CLIENT-ROADMAP.md) +> 的 P0–P10)。保留为当时的计划记录,不要再当作待办执行。 + +1. ~~打通到 `.203` 的连通性~~ ✅。可达后立即能测: - `./build/extension/net_probe 192.168.21.203 11000 admin 123456789` → 完整握手 + `CGLogin3` → `login_key` - GDScript 里 `M2Client.connect_to_server(...)` → 一路到 `char_list` / `entered_game` 2. **`GCLoginSuccess3` 布局对真实字节校准**:`net_probe` / `M2Client` 收到后 dump 原始字节, @@ -228,15 +237,15 @@ AEAD 加密 session token。 `Metin2World` 当场景。里程碑「角色连上、站在地图里、别的实体在动」。 4. 输入(点地移动 → `CG_MOVE`)、聊天(`CG_CHAT`/`GC_CHAT` 接 HUD)。 -## 4. 风险 +## 5. 风险 - **协议版本漂移**:`Packet.h` 结构体要和服务端 build 一致。冻结一对 client/server commit。 -- **端到端测试阻塞**:目前握手端口 refused,走不到 `CGLogin3` 之前的验证。 +- ~~**端到端测试阻塞**:目前握手端口 refused,走不到 `CGLogin3` 之前的验证。~~ 已解除(§3)。 - **移动端 libsodium**:Android/iOS 要静态 vendor(本身可移植,非阻塞)。 - 自用范围可跳过:商城 / GM 工具 / 部分社交系统。**反作弊(`CG_HACK` / `ProcessCRC` / `ProcessScanner`)已在 `CLIENT-GAP.md`「不做清单」标注 ❌ 不做**,除非服务端强制校验。 -## 5. 移植面(客户端源码) +## 6. 移植面(客户端源码) `EterBase/SecureCipher` · `EterLib/{NetStream,ControlPackets,NetPacketHeaderMap}` · `UserInterface/{Packet.h, AccountConnector, PythonNetworkStream, PythonNetworkStreamPhase*}` · diff --git a/docs/CLIENT-ROADMAP.md b/docs/CLIENT-ROADMAP.md index 4a5815cb..642845c0 100644 --- a/docs/CLIENT-ROADMAP.md +++ b/docs/CLIENT-ROADMAP.md @@ -7,6 +7,14 @@ > 标记:🌐 需服务器端到端 · 💻 离线可做(mock server / 合成封包 / 假 client) > 更新日期:2026-08-30 · 自用,无发布门禁 > **P0–P10 首版全部完成**;`192.168.21.203` 已联通,`net_e2e` 端到端到 PHASE_GAME。里程碑见文末。 +> +> **适用范围(2026-09-08 复核)**:本文整篇以 **m2dev fork 协议**为基线。该后端已不是默认—— +> `project/client_main.gd:14-15` 默认走 40250 `classic`(见 [`CLIENT-40250-PORT.md`](./CLIENT-40250-PORT.md))。 +> 玩法拆解、UI 与阶段规划仍然适用;但文末「通用做法」第 4 条「封包号 / 结构以 +> `m2dev-client-src-main/src/UserInterface/Packet.h` 为准」**只适用于 `MT_PROTOCOL=m2dev`**。 +> classic 后端的封包依据是 `40250/ClientVS22/source/UserInterface/Packet.h` + +> `extension/src/net/classic/wire_classic.h`。上面「已联通」也是指 m2dev 联调服; +> 40250 真服(FreeBSD + MySQL)尚未搭起,见 `CLIENT-40250-PORT.md` §7.1。 ## 已就绪的地基(这些不重复做) @@ -348,5 +356,5 @@ PHASE_GAME —— 收到 121 个实体、HP/SP/等级、20 格背包、好友列 1. **每个 P 先做 💻 部分**:`wire.h` 加封包 + `EntityStore` 解析 + 合成封包单测(`net_entity_test` 风格)+ 假 client 驱动 UI(`netbridge_test` 风格)。 2. 🌐 端到端用 `build/extension/net_e2e`(真机跑 auth→game→选人→进游戏);`MT_NET_TRACE=1` 逐包 header/len/consumed、`MT_NET_DUMP=1` 逐包 hex,对字节用。 3. UI 一律走 P1 的 uiscript 装载器,不手搓布局。 -4. 封包号 / 结构以 `../m2dev-client-src-main/src/UserInterface/Packet.h` 为准,`static_assert` 尺寸;**真机字节和参考头文件不符时以真机为准**(已踩:`GC_LOGIN_SUCCESS4`、`bType`、`CG_CLIENT_VERSION`)。 +4. 封包号 / 结构:`MT_PROTOCOL=m2dev` 时以 `../m2dev-client-src-main/src/UserInterface/Packet.h` 为准,**默认的 `classic` 后端**以 `../40250/ClientVS22/source/UserInterface/Packet.h` + `extension/src/net/classic/wire_classic.h` 为准,`static_assert` 尺寸;**真机字节和参考头文件不符时以真机为准**(已踩:`GC_LOGIN_SUCCESS4`、`bType`、`CG_CLIENT_VERSION`)。 5. 玩法逻辑尽量**服务器驱动、客户端只渲染**(尤其 quest / 商店 / 公会),少搬业务规则。 diff --git a/docs/GODOT-POC-PLAN.md b/docs/GODOT-POC-PLAN.md index 1f8e209e..4c8aaab9 100644 --- a/docs/GODOT-POC-PLAN.md +++ b/docs/GODOT-POC-PLAN.md @@ -11,7 +11,7 @@ | | | |---|---| -| 状态 | Phase 1(macOS)基本完成,见 [`MIDREVIEW.md`](./MIDREVIEW.md);未完成工作分层清单见 [`BACKLOG.md`](./BACKLOG.md) | +| 状态 | **已被工作范围超越(2026-09-08 复核)**。Phase 1(macOS)完成,见 [`MIDREVIEW.md`](./MIDREVIEW.md);Phase 2 的 iOS / Android 交叉编译与 APK 导出也已完成(`BACKLOG.md` F1/F2、[`PLATFORMS.md`](./PLATFORMS.md)),故文中「Phase 2 本轮不做」不再成立。§01「明确排除」清单已大面积失效,见该节的复核注。当前工作以 [`BACKLOG.md`](./BACKLOG.md)、[`CLIENT-GAP.md`](./CLIENT-GAP.md)、[`RENDERING-REPAIR-PLAN.md`](./RENDERING-REPAIR-PLAN.md) 为准 | | 技术栈 | Godot **4.7.1** stable · GDExtension(`godot-cpp` 精确提交 `101ae38`,4.7 API)· C++20 | | 目标平台 | **Phase 1:macOS**(当前)→ Phase 2:Android(一加 13 / Vulkan)· iOS(iPhone 16 / Metal)。自用,三台现代设备,不涉及 GLES3 / Compatibility | | 判定周期 | Phase 1 ≈ 3.5–5 周(单人)→ **中期评审**;Phase 2 = 三设备各一次 bring-up + 性能留档(自用,无对外 go/no-go) | @@ -48,6 +48,22 @@ ### 明确排除(两个阶段都不做) +> **⚠️ 本清单已失效(2026-09-08 复核)。** 下列 9 项中 8 项后来都做了 —— POC 判定通过后项目 +> 继续做成了完整客户端,范围扩张是有意的(`SHINSOO-WORLD-RENDERING.md` 的「定位」一节最早 +> 记录了 UI 这一条)。逐项现状: +> +> | 原「不做」项 | 现状 | 落点 | +> |---|---|---| +> | SpeedTree 植被 | 🟡 已做 proxy | `formats/spt.cpp`,`BACKLOG` E10 | +> | 地形(Metin2 户外地图) | ✅ 已做 | `Metin2World` + `formats/terrain_mesh.cpp`,`BACKLOG` E8/E11/E12 | +> | UI / Python 脚本层 | ✅ 已做 | `project/ui/*`(uiscript 装载器 + 多个 1:1 窗口移植) | +> | eterpack | ✅ 已做 | `mtpack` + `packtool`,`BACKLOG` G1 | +> | GPU 蒙皮优化 | ✅ 已做 | `MTGODOT_GPUSKIN=1`,`m2_material` `SRC_SKIN` | +> | LOD 策略 | ✅ 已做 | `lod_distances` + crossfade,`BACKLOG` C9 | +> | 多角色 AI | ⬜ 仍不做 | —— | +> | 网络 | ✅ 已做 | `mtnet`(m2dev + 40250 classic 双后端) | +> | 玩法 | ✅ 大量已做 | `CLIENT-GAP.md` / `CLIENT-ROADMAP.md` P0–P10 | + SpeedTree 植被、地形(Metin2 户外地图)、UI / Python 脚本层、eterpack(Phase 1 用散文件)、GPU 蒙皮优化、LOD 策略、多角色 AI、网络、玩法。 > 地形与 `.mse` 特效在 Phase 1 也不做,但 **M2.5 会做通用 `ShaderMaterial` + 混合模式的地基**,为后续正式移植时的地形 / 特效渲染铺路。 @@ -76,7 +92,7 @@ SpeedTree 植被、地形(Metin2 户外地图)、UI / Python 脚本层、ete ## 02 · 架构总览 ``` -mtgodot-poc/ +metin2-client/ # 建仓时叫 mtgodot-poc,后更名 extension/ GDExtension(C++,godot-cpp 4.7) src/ register_types.cpp 注册 Metin2Model / Metin2AnimPlayer @@ -160,13 +176,13 @@ mtgodot-poc/ **目标**:warrior + 另 2 种骨架的 bind pose + 贴图在 Godot 里正确显示,多部件组装。 -对标 [`steps/M1-static-render.md`](./steps/M1-static-render.md) T2–T5。 +对标 [`reference/steps/M1-static-render.md`](./reference/steps/M1-static-render.md) T2–T5。 | 任务 | 完成判据 | |---|---| | T1.1 `gr2_bridge`:`libgr2::Skeleton` → `Skeleton3D`(bone 名 / parent / rest `Transform3D`);basis + 单位缩放在此处理,约定写进注释 | 骨架层级树与 `gr2dump --members` 一致;三种骨架的骨骼数都正确 | | T1.2 `libgr2::Mesh` → `ArrayMesh`(`ARRAY_VERTEX/NORMAL/TANGENT/TEX_UV/BONES/WEIGHTS`)+ `Skin`(bind 矩阵);处理 >4 权重 | 白模轮廓 = Blender 里同模型;无破面;三种骨架都过 | -| T1.3 贴图路径决策(`.dds` 原生导入 vs 运行时 `dds_loader`),实现选定方案;warrior 贴图(实测 512×512 DXT3 5mip) | UV 无错位 / 无镜像;决策写进 `extension/README.md` | +| T1.3 贴图路径决策(`.dds` 原生导入 vs 运行时 `dds_loader`),实现选定方案;warrior 贴图(实测 512×512 DXT3 5mip) | UV 无错位 / 无镜像;决策写进 `extension/README.md`(**该文件未产出**,2026-09-08 复核;决策实际记录在 `MIDREVIEW.md` §3「贴图」与 `extension/src/dxt.cpp` 注释里) | | T1.4 材质:`StandardMaterial3D`(albedo 贴图 + unshaded 先看轮廓),可切白模 | 贴图目视正确 | | T1.5 多部件(body / weapon / hair)组装,武器挂 `BoneAttachment3D` | 部件数正确、武器跟手骨 | | T1.6 `test/compare.py`:与 bgfx demo 同机位截图 | 几何一致(允许色彩空间 / 光照差异) | @@ -179,7 +195,7 @@ mtgodot-poc/ **目标**:从 GDExtension 每帧驱动 `Skeleton3D` 播动画,含跨文件 retarget 与 motion event。 -对标 [`steps/M2-anim-skinning.md`](./steps/M2-anim-skinning.md)。 +对标 [`reference/steps/M2-anim-skinning.md`](./reference/steps/M2-anim-skinning.md)。 | 任务 | 完成判据 | |---|---| @@ -279,7 +295,7 @@ mtgodot-poc/ |---|---| | `mtgodot-poc/` 仓库:GDExtension 源 + Godot 4.7.1 demo 工程 | 新仓库 | | macOS 导出配置 | `project/export_presets.cfg` | -| 与 bgfx demo 的几何 / 动画对拍报告 | `test/godot-snapshot.json` | +| 与 bgfx demo 的几何 / 动画对拍报告 | ~~`test/godot-snapshot.json`~~ **未产出**(2026-09-08 复核)。实际落地的是 `test/compare.py` + `test/compare-report.json`(进程 / 截图 / 结构硬门禁),与 bgfx 的比对停留在 `test/bgfx-reference/` 的人工交叉核对 | | 材质差距清单 | 中期评审报告内 | | 50 角色压力采样(供 Phase 2 A/B 决策) | `test/godot-macos-stress.json` | | **中期评审报告** | 桥接层工作量实测 + 观感对比截图 + 剩余风险 + "是否进入 Phase 2"建议 | diff --git a/docs/MIDREVIEW.md b/docs/MIDREVIEW.md index 1c8d8980..b834e387 100644 --- a/docs/MIDREVIEW.md +++ b/docs/MIDREVIEW.md @@ -1,5 +1,9 @@ -# Phase 1 中期评审 — mtgodot-poc +# Phase 1 中期评审 — metin2-client(评审时仓库名 mtgodot-poc) +> **本文是一份定格的历史评审记录,不反映现状(2026-09-08 复核)。** 其中的规模数字尤其不要当现状读: +> §2 说 `extension/src` ≈ 1997 行 / 6 个文件,实际现在是 **30029 行 / 68 个文件**(网络、世界渲染、 +> 资产管线都是评审之后加的)。现状看 [`BACKLOG.md`](./BACKLOG.md) 和 [`CLIENT-GAP.md`](./CLIENT-GAP.md)。 +> > 日期:2026-08-29(评审后又做了一轮收尾,见 §4/§5 的「已做」标注)· 平台:macOS(Apple M2)· Godot 4.7.1 > · 对标 [`GODOT-POC-PLAN.md`](./GODOT-POC-PLAN.md) §00 Phase 1 > 结论先行:**桥接层工程量小、风险已基本出清,建议进入 Phase 2(一加 13 / iPhone 16 三设备 bring-up,自用)**。M2.5 材质门禁已闭(贴图走 gr2 material 绑定 + 逐 tri_group 拆 surface);仅剩逐材质 oracle 纸面对拍这一可选项。 @@ -51,7 +55,7 @@ | ~~多材质槽内拆分~~ **→ 已做**:`gr2_bridge.cpp` 加 `build_parts()`,一个 gr2 mesh 有 N>1 个 `tri_groups` 就拆成 N 个 Godot surface(各自 material + 索引子区间)。`build_mesh` 和 CPU 蒙皮路径都按 `RenderPart` 走。shaman(1 mesh→2 surface)、assassin(17→18)实测各面贴图正确。 | 已做 | | ~~对拍未接线~~ **→ 已写 `test/compare.py`(2026-08-29)** | oracle gate(读 `test/m2-numeric.json`,libgr2 vs Granny 23/23 ≤6.5e-5)+ 每 case headless 跑 mtgodot,**判据**:进程退出码 0、截图存在、`surfaces>0`、动画 selfcheck `NaN/Inf=0`(CPU 与 GPU 两条路径各一遍)。产出 `.cpu.png`/`.gpu.png` + `test/compare-report.json`。渲染器 / 相机不同,不做与 bgfx-reference 的像素比。**缺**:带容差的自动视觉回归门禁(BACKLOG H3)。 | 基本完成 | | **性能** | 前台窗口 M2 重测(`test/godot-macos-stress.json`,默认 CPU LBS):1 角色 46fps、10 角色 36fps、30 角色 24fps。CPU 路径瓶颈是每帧 `cpu_mesh` 全量重建(不是 LBS 数学)。构建仍 ~170ms/角色(单线程无缓存)。**GPU 蒙皮着色器已实现**(`MTGODOT_GPUSKIN=1`,每帧只更新一张骨骼纹理),人群场景应切它 —— stress harness 未用 GPU 路径重跑。 | Phase 2:GPU 路径跑人群 bench + 多线程构建 + eterpack 共享资产 + 一加 13 / iPhone 16 实测。 | -| 剩余小项 | ~~ShaderMaterial 静态 Shader 退出泄漏~~ 已修(`cleanup_material_shaders`,`register_types` 卸载时调)· alpha 排序 corner case(`render_priority` 粗排)· 背面剔除写死 `cull_back`(Metin2 有 `CULL_NONE` 面)· `_frame_model` 对披风类框选偏 | 均不阻塞,见 [`BACKLOG.md`](./BACKLOG.md)(`A3` `A6` `B10` `E5`)。法线用混合矩阵上 3×3 不逆转置 —— **与 Granny runtime 一致,是正确行为不是风险** | +| 剩余小项 | ~~ShaderMaterial 静态 Shader 退出泄漏~~ 已修(`cleanup_material_shaders`,`register_types` 卸载时调)· alpha 排序 corner case(`render_priority` 粗排)· ~~背面剔除写死 `cull_back`(Metin2 有 `CULL_NONE` 面)~~ **已做(2026-09-08 复核)**:`m2_material.cpp:188-211` 已有 `cull_disabled` shader 变体,角色 / 武器 / 发型经 `decide_two_sided()` 驱动,见 `BACKLOG.md` B10 · `_frame_model` 对披风类框选偏 | 均不阻塞,见 [`BACKLOG.md`](./BACKLOG.md)(`A3` `A6` `B10` `E5`)。法线用混合矩阵上 3×3 不逆转置 —— **与 Granny runtime 一致,是正确行为不是风险** | ## 5. 建议 diff --git a/docs/PARITY-GAP.md b/docs/PARITY-GAP.md index 6ac6ba20..337b8710 100644 --- a/docs/PARITY-GAP.md +++ b/docs/PARITY-GAP.md @@ -18,7 +18,7 @@ **现状**(`world-a1-persp.png` / `world-a1-r2.png`): - ✅ 地貌 / 道路网 / 建筑布局 / 水域形状 = **认得出是神兽国 A1** - ✅ 第三人称角色(带发型)在地形上跑、HUD 布局在位、`.msenv` 驱动的天空/光/雾 -- ❌ 观感**不等价**:树仍是 proxy(虽已用真实 bark/composite atlas)、无 `.mse` 特效、水面已用真 30 帧序列但深度系数未标定、HUD 已用真实九宫格贴图但布局未按受控帧对齐、光照/色调未对受控帧校准、机位随手放、角色材质 sphere-map 高光管线已端口但默认休眠(无装备层驱动 power) +- ❌ 观感**不等价**:树仍是 proxy(虽已用真实 bark/composite atlas)、无 `.mse` 特效、水面已用真 30 帧序列但深度系数未标定、HUD 已用真实九宫格贴图但布局未按受控帧对齐、光照/色调未对受控帧校准、机位随手放、角色材质 sphere-map 高光已由装备层驱动(`equip_model.gd:283-288` 用 `item_proto specular/100`)但只覆盖身体/护甲一个部位、power 未按受控帧标定 **粗估**:把下面全部做到「目视等价」,单人 **≈ 3–6 个月**(不含 Phase 2 移动端 bring-up; 需在地形/水面正确性修复和 `.spt` 转换可行性验证后重估)。`.spt` 当前缺少可分发的跨平台 reader; @@ -115,7 +115,7 @@ TreeSize/TreeVariance。当前实现已移除这些随机变换;若替代树 | 2.4 | 贴图换色 `SourceSkin→TargetSkin` | ✅ **首版已做**:`Metin2Model.hair_skin` —— 设了就用该 dds 当发型 albedo(代替 gr2 sibling 的 SourceSkin),对齐客户端 `SetMaterialImagePointer(part, SourceSkin, load(TargetSkin))`(`ActorInstanceData.cpp:152/225`)。`get_hair_options()` 现返回解析成绝对路径的 `source_skin` / `target_skin`。`formats.msm_hair` CTest 钉了 HairData 解析(同 gr2+SourceSkin、不同 TargetSkin)。demo 用红色变体 | ⬜ 非发型部位(护甲/时装)的 skin 表;`warrior_cheongrin` 头盔挡住发型,换色需换模型目视 | S(首版完成) | D3 | | 2.5 | 固定功能材质状态覆盖 | 单 stage `MODULATE(TEX,DIFFUSE)` 近似;**sphere-map 分支已按源码端口**(见 §2.7)。**blend / two-sided 改读 `gr2::MaterialInfo`**:`metin2_model::decide_blend()` 优先 `alpha_blend`(`dump_materials` 端口 `Material.cpp:233` = Name `Blend*` + MapCount>1),`decide_two_sided()` 读 `two_sided`(+ hair/cloak 名兜底),`m2_material` 加 `cull_disabled` shader 变体。仅 hair 镂空 / effect 加法这两类 GR2 没编码的仍按表面名 | 复现最终机位实际触发的 alpha-test ref、多贴图组合 | 逐分支盘点 `EterGrnLib/Material.cpp`;GR2 不承载任意 D3D stage 状态 | M | B2/B4 | | 2.6 | 顶点色 | ✅ **排查完毕:不适用**。全资产 `.gr2`(PC / monster2 / effect)**零** `DiffuseColor`/`Color0` 顶点成员(grep 整个 assets 目录 = 0)。Metin2 的「逐顶点颜色」实际是:① 地形雾 —— `MapOutdoorRenderSTP` 运行时动态 VB 里 `dwDiffuse`/`dwSpecular` 存雾色/雾系数(非资产,已由 Godot `Environment` 雾覆盖);② `.mse` 特效 `colorfactor` / 粒子 `TimeEventColor`(属 §1.2 `.mse` 端口)。libgr2 无需改,`build_mesh` 无需补 `ARRAY_COLOR` | — | B7 | -| 2.7 | 镜面 / 球面反射(护甲金属光) | 🟡 **管线已端口**:`SRC_MIX` / `SRC_SKIN` 加 `spec_map`/`spec_power`/`spec_enable`。按 `EterGrnLib/Material.cpp:305 __ApplySpecularRenderState` 复现:仅不透明面(客户端 alpha-blend 时回退纯 diffuse)、相机空间 `reflect(VERTEX,NORMAL).xy` 查 sphere map、`MODULATEALPHA_ADDCOLOR` = `EMISSION += sphere.rgb * tex.a * power`(`tex.a` = 护甲高光遮罩,`power` = `D3DRS_TEXTUREFACTOR.a`)。sphere map = 全局 `ETC/ymir work/special/spheremap.jpg`(客户端 index 0),`Metin2Model::_load_sphere_map()` 沿模型目录上溯解析。`Metin2Model.specular_power` 属性。**默认 0 = 完全休眠**,与之前逐字节一致 | 端口 `__ApplySpecularRenderState`(✅);接装备层用 `item_proto bSpecular/100` 逐部位驱动(⬜,无物品系统);受控帧标定 power(⬜,§0 门禁) | M | B9 | +| 2.7 | 镜面 / 球面反射(护甲金属光) | 🟡 **管线已端口**:`SRC_MIX` / `SRC_SKIN` 加 `spec_map`/`spec_power`/`spec_enable`。按 `EterGrnLib/Material.cpp:305 __ApplySpecularRenderState` 复现:仅不透明面(客户端 alpha-blend 时回退纯 diffuse)、相机空间 `reflect(VERTEX,NORMAL).xy` 查 sphere map、`MODULATEALPHA_ADDCOLOR` = `EMISSION += sphere.rgb * tex.a * power`(`tex.a` = 护甲高光遮罩,`power` = `D3DRS_TEXTUREFACTOR.a`)。sphere map = 全局 `ETC/ymir work/special/spheremap.jpg`(客户端 index 0),`Metin2Model::_load_sphere_map()` 沿模型目录上溯解析。`Metin2Model.specular_power` 属性。**默认 0 = 完全休眠**,与之前逐字节一致 | 端口 `__ApplySpecularRenderState`(✅);接装备层用 `item_proto bSpecular/100` 驱动(✅ 身体/护甲:`project/ui/equip_model.gd:283-288` `_apply_body()`;⬜ 武器 / 发型 / 时装等其余部位未接);受控帧标定 power(⬜,§0 门禁) | M | B9 | | 2.8 | 角色专属光(`DirectionalLight.Character`) | 只用 `Background` 光 | 角色颜色与原客户端 Character light 接近 | 给 `SRC_MIX`/`SRC_SKIN` 加 character-light uniform,或用 light `cull_mask` 分层 | S–M | E2 | | 2.9 | 动画混合 / crossfade | ✅ **首版已做**:`Metin2AnimPlayer.blend_time`(默认 0.15s)。切 `anim_path` 时保留旧 clip 冻结在当前时间,每帧对旧/新 `sample_pose` 的 `world_pose` 逐骨 slerp(rot)+lerp(pos/scale)(smoothstep 权重),再 `skin = invWorld · world_blended` 重建;武器挂点跟混合后的手。shear 骨在 ≤blend_time 过渡内退化为无 shear(文档许可)。目视 wait→run 连续无 T-pose 闪 | ⬜ 受控帧核对过渡时长 | M(首版完成) | C2 | | 2.10 | LOD | ✅ **首版已做**:`Metin2Model` reload 时自动加载 `_lod_01/02/03.gr2`(同 75 骨骼、抽面 mesh),`_process` 按相机距离切换渲染 mesh(`lod_distances` 属性,默认 `[18,42,90]`m)。切换时重建 `cpu_mesh` + 重解材质 + 重蒙皮;骨架共享故蒙皮不受影响。烟测:`d=12→L1 d=21→L2 d=30→L3`(`wait.msa` 播放中不崩),4 级渲染均干净。⬜ 淡入淡出过渡、GPU 蒙皮路径的 LOD | M(首版完成)| C9 | @@ -237,7 +237,7 @@ pivot、z-index、九宫格边距、贴图/字体路径、字号、描边、颜 7. 1.1 `.spt` reader/converter 通过授权与技术验证后批量离线转 Mesh,并接入统一 `TreeDescriptor` 8. 4.1–4.2 原客户端 30 帧水纹 + 深度 alpha;4.3–4.5 只按受控参考帧选择性增强 9. 5.1 云层 + 1.3 lens flare -10. ✅ 2.7 sphere-map 高光(管线已端口,默认休眠);⬜ 2.5 其余固定功能材质分支 / 2.8 角色专属光 +10. ✅ 2.7 sphere-map 高光(管线已端口 + 装备层已驱动身体部位;逐部位与 power 标定仍缺);⬜ 2.5 其余固定功能材质分支 / 2.8 角色专属光 11. 3.6 instanced grass(仅在取证确认后) **第 3 梯队(大工程 / 可并行 / 部分可选)** diff --git a/docs/SHINSOO-WORLD-RENDERING.md b/docs/SHINSOO-WORLD-RENDERING.md index 3a804c20..eb92c85f 100644 --- a/docs/SHINSOO-WORLD-RENDERING.md +++ b/docs/SHINSOO-WORLD-RENDERING.md @@ -1,7 +1,7 @@ # 神兽国 A1 场景最终渲染开发文档 > 状态:实施规格(Implementation Specification) -> 目标工程:`mtgodot-poc` +> 目标工程:`metin2-client` > 资源来源:仓库内 `assets/`(gitignored,`MT_ASSETS` 可覆盖) > 原客户端行为参考:同级仓库 `m2dev-client-src-main/src` > 目标参考图:`ingame-shinsoo.png`,1920×1080 的神兽国村庄第三人称画面 diff --git a/docs/THIRD-PARTY.md b/docs/THIRD-PARTY.md index 34b14772..28fe4ce1 100644 --- a/docs/THIRD-PARTY.md +++ b/docs/THIRD-PARTY.md @@ -1,22 +1,25 @@ # Third-party native dependencies -The GDExtension links three native libraries that are **not** part of Godot or -godot-cpp. Phase 1 (macOS) pulled them from Homebrew; that stops working the -moment we cross-compile for the Android NDK or the iOS SDK (BACKLOG F1/F2), so as -of 2026-08-30 all three are built from source as part of our own CMake build. +The GDExtension links four native libraries that are **not** part of Godot or +godot-cpp. Phase 1 (macOS) pulled the first three from Homebrew; that stops +working the moment we cross-compile for the Android NDK or the iOS SDK (BACKLOG +F1/F2), so as of 2026-08-30 they are built from source as part of our own CMake +build. Crypto++ joined later (2026-09-02) with the `MT_PROTOCOL=classic` 40250 +backend and was vendored the same way from the start. Everything lives under `extension/third_party/` and is wired by -`extension/third_party/CMakeLists.txt`, which exposes three aliases: +`extension/third_party/CMakeLists.txt`, which exposes four aliases: -| Alias | Backing target | Consumed by | -|-----------------|------------------|------------------------| -| `mt3p::sodium` | `sodium` | `mtnet`, `mtpack`, `mtproto` | -| `mt3p::zstd` | `libzstd_static` | `mtpack` | -| `mt3p::minilzo` | `minilzo` | `mtproto` | +| Alias | Backing target | Consumed by | +|-------------------|------------------|------------------------------| +| `mt3p::sodium` | `sodium` | `mtnet`, `mtpack`, `mtproto` | +| `mt3p::zstd` | `libzstd_static` | `mtpack` | +| `mt3p::minilzo` | `minilzo` | `mtproto` | +| `mt3p::cryptopp` | `cryptopp` | `mtnet` (classic backend) | -No source file changed: `#include `, `#include ` and -`#include ` all still resolve (the last via a 1-line shim header, -see below). +No source file changed: `#include `, `#include `, +`#include ` and `#include ` all still resolve (the LZO +one via a 1-line shim header, see below). ## libsodium — via `robinlinden/libsodium-cmake` @@ -60,11 +63,38 @@ see below). or commercial use. (item_proto/mob_proto are the only LZO consumers; a clean-room LZO1X decompressor is the eventual fix.) +## Crypto++ — vendored source (not a submodule) + +* **Files:** `extension/third_party/cryptopp/` (~6.6 MB), copied from the 40250 + client tree (`ClientVS22/extern/include/cryptopp`), `CRYPTOPP_VERSION 840` = + **8.4.0**. The 40250 server links this exact version + (`Server/.../extern/cryptopp_8_4_0.tar.gz`), so the version is pinned by + wire compatibility, not preference. +* **Why:** the `MT_PROTOCOL=classic` backend's `_IMPROVED_PACKET_ENCRYPTION_` + cipher — DH2 key agreement + hint-selected block ciphers (Twofish default) in + CTR mode. Only consumer is `extension/src/net/classic/classic_cipher.{h,cpp}`. + The m2dev backend does not use it (that one is libsodium/XChaCha20). +* **No upstream CMakeLists**, so `cryptopp/CMakeLists.txt` is ours: it globs the + library TUs and drops the cryptest / benchmark / adhoc / FIPS-selftest ones + (`adhoc`, `bench1..3`, `datatest`, `dlltest`, `fipsalgt`, `fipstest`, `pch`, + `regtest1..4`, `test`, `validat0..10`). +* **ASM disabled everywhere** (`CRYPTOPP_DISABLE_ASM` / `_SSSE3` / `_AESNI`) so + one pure-C++ build works on macOS, the Android NDK and the iOS SDK; the classic + cipher needs no AES-NI / SHA-ext. Android additionally compiles the NDK's + `sources/android/cpufeatures/cpu-features.c` in, because upstream `cpu.cpp` + includes `cpu-features.h` unconditionally under `__ANDROID__`. +* Built with `-w` (upstream is not warning-clean under `-Wall`) and + `-fno-strict-aliasing`. +* **License:** **public domain** — the compilation carries Wei Dai's copyright, + but every individual file is placed in the public domain by its authors + (`extension/third_party/cryptopp/License.txt`). Ship-safe; unlike miniLZO it + imposes no obligation before a public or commercial release. + ## Rebuilding / fetching `./build.sh` auto-runs `git submodule update --init --recursive` if any of the -three (godot-cpp, zstd, libsodium-cmake+libsodium) is missing. miniLZO needs no -fetch. +three submodules (godot-cpp, zstd, libsodium-cmake+libsodium) is missing. +miniLZO and Crypto++ are vendored source and need no fetch. Homebrew `libsodium` / `zstd` / `lzo` are no longer referenced by the build and can be uninstalled. diff --git a/docs/reference/PLAN.md b/docs/reference/PLAN.md index 28b5d3e9..2ba97c23 100644 --- a/docs/reference/PLAN.md +++ b/docs/reference/PLAN.md @@ -44,7 +44,7 @@ PoC 的价值是**降低不确定性**,go 和 no-go 都是有效产出。 > 剩余是真机验证(性能 / 生命周期 / mediump)+ Android 端。 > **画质改进 pass(档 1 + 档 2)已完成**:MSAA x4 / mip 链 / sRGB 线性着色 / 半球环境 + 3 方向光 / 双面; > libgr2 加 `granny_material` 链解析 → 逐 tri_group 正确贴图(原来整模型套一张瞎猜的 dds)+ alpha-test 镂空。 -> 无回归(gr2fuzz 9166 · render_fuzz 9166 · oracle 23/23)。细节见 `docs/steps/M1-static-render.md`「画质改进 pass」。 +> 无回归(gr2fuzz 9166 · render_fuzz 9166 · oracle 23/23)。细节见 `docs/reference/steps/M1-static-render.md`「画质改进 pass」。 - **里程碑 M3 全部门禁通过** = "C++ + bgfx、复用 EterGrnLib 逻辑" 的跨平台方案可行,进入正式移植规划。 - **门禁失败要区分两类**: diff --git a/docs/reference/README.md b/docs/reference/README.md index d30ea16f..3f9f2d62 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -21,7 +21,7 @@ still authoritative: **Relative paths inside these files** (`../libgr2`, `oracle/`, `test/golden/…`, `engine/…`, `app/…`) refer to the old xrender-poc layout. The mapping: -| old (xrender-poc) | now (mtgodot-poc) | +| old (xrender-poc) | now (metin2-client; the repo was created as `mtgodot-poc` and renamed later — the `mtgodot-poc.app` / `.apk` artifact names and the `org.internal.mtgodotpoc` bundle id did **not** change) | |---|---| | `libgr2/` | `libgr2/` (vendored, unchanged) | | `formats/` | `formats/` (vendored) | diff --git a/docs/reference/steps/M0-gr2-reader.md b/docs/reference/steps/M0-gr2-reader.md index 13629a4a..9b085801 100644 --- a/docs/reference/steps/M0-gr2-reader.md +++ b/docs/reference/steps/M0-gr2-reader.md @@ -216,7 +216,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump) - **主**:`libgr2` dump vs 真 Granny —— ✅ **达成**。`oracle probe` 的 `GrannyGetFileInfo` 与 `gr2dump` 逐字段一致;`tools/oracle_diff` 对拍 Granny **2.9.12** 的 `GrannyGetWorldPose4x4Array` + `GrannyDeformVertices`:23 用例(13 模型 bind + 8 动画帧,含 v7 / 双 root / 刚体)骨骼矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`test/m2-numeric.json`)。oracle 在 macOS + Wine + MinGW 跑(`oracle/RUNBOOK.md`)。 - **自洽**:T5 bind pose 自洽 —— ✅ `warrior_cheongrin` `6.1e-5` PASS。全量 2013/2017 < 1e-3;4 个 `>=1e-1` **经 oracle 证实 libgr2 正确**(assassin 对 Granny 3.1e-5),是自洽不变量本身对这几个双-root 资产不成立,非 libgr2 bug。 - **fuzz**:T9b 全量 9166 —— ✅ **零崩溃 + 零谓词失败**。 -- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`../../libgr2/README.md`](../../libgr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。 +- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`libgr2/README.md`](../../../libgr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。 - **产出**:✅ `test/{fuzz-report,m2-numeric,noise_floor}.json` + `test/assets.list` + `test/oracle_dumps/`。 - **noise_floor**:✅ `test/noise_floor.json`。同-DLL 确定性 = 0(byte-identical);跨实现地板 = mat 4.6e-5 / vtx 6.5e-5;跨版本一档待另一个 granny2.dll。