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
This commit is contained in:
shenlei
2026-09-08 17:15:17 +09:00
co-authored by Claude Opus 5
parent c3a6fb973e
commit fb4d2d222b
14 changed files with 165 additions and 98 deletions
+4 -4
View File
@@ -1,7 +1,7 @@
<!-- generated-by: gsd-doc-writer -->
# 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
+6 -6
View File
@@ -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 | iOSiPhone 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。预留 23 天。详见 `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 导出 presetGradle+ 一加 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 生命周期 | 🟡 | SM | ✅ `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 smokesuspend/resume/reconnect 绑定 + 幂等 + 信号)通过,ctest 9/9,iOS 编过。渲染上下文丢失由 Godot Vulkan 自恢复,GPU 资源全 RenderingServer 托管,无需处理。⬜ 剩真机验证后台 30s→掐连接→回前台重连、iOS 内存告警实际触发。 |
| F6 | 打包 | ⬜ | SM | 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、网络、玩法逻辑、eterpackPhase
### 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 周)
+4 -4
View File
@@ -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_testinventory_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_testequip_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 窗口上才拾取
+46 -46
View File
@@ -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 <old> <new>``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/P2UI、输入和本地化
@@ -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` 不再直接处理应用暂停 / 恢复通知;旧
+16 -7
View File
@@ -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` 的连通性(见上)。可达后立即能测:
> **本节 14 项已于 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*}` ·
+9 -1
View File
@@ -7,6 +7,14 @@
> 标记:🌐 需服务器端到端 · 💻 离线可做(mock server / 合成封包 / 假 client
> 更新日期:2026-08-30 · 自用,无发布门禁
> **P0P10 首版全部完成**`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 / 商店 / 公会),少搬业务规则。
+22 -6
View File
@@ -11,7 +11,7 @@
| | |
|---|---|
| 状态 | Phase 1macOS基本完成,见 [`MIDREVIEW.md`](./MIDREVIEW.md)未完成工作分层清单见 [`BACKLOG.md`](./BACKLOG.md) |
| 状态 | **已被工作范围超越(2026-09-08 复核)**Phase 1macOS)完成,见 [`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 1macOS**(当前)→ Phase 2Android(一加 13 / Vulkan)· iOSiPhone 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` P0P10 |
SpeedTree 植被、地形(Metin2 户外地图)、UI / Python 脚本层、eterpackPhase 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/ GDExtensionC++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) T2T5。
对标 [`reference/steps/M1-static-render.md`](./reference/steps/M1-static-render.md) T2T5。
| 任务 | 完成判据 |
|---|---|
| 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"建议 |
+6 -2
View File
@@ -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 的「已做」标注)· 平台:macOSApple 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` 走。shaman1 mesh→2 surface)、assassin17→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 2GPU 路径跑人群 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. 建议
+3 -3
View File
@@ -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 未按受控帧标定
**粗估**:把下面全部做到「目视等价」,单人 **≈ 36 个月**(不含 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` 分层 | SM | 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 时自动加载 `<base>_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 梯队(大工程 / 可并行 / 部分可选)**
+1 -1
View File
@@ -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 的神兽国村庄第三人称画面
+45 -15
View File
@@ -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 <sodium.h>`, `#include <zstd.h>` and
`#include <lzo/lzo1x.h>` all still resolve (the last via a 1-line shim header,
see below).
No source file changed: `#include <sodium.h>`, `#include <zstd.h>`,
`#include <lzo/lzo1x.h>` and `#include <cryptopp/…>` 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.
+1 -1
View File
@@ -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 逻辑" 的跨平台方案可行,进入正式移植规划。
- **门禁失败要区分两类**
+1 -1
View File
@@ -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) |
+1 -1
View File
@@ -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-34 个 `>=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 确定性 = 0byte-identical);跨实现地板 = mat 4.6e-5 / vtx 6.5e-5;跨版本一档待另一个 granny2.dll。