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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
2026-09-08 17:15:17 +09:00

85 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 纸面对拍这一可选项。
---
## 1. 做到了什么(M0'M2.5
| 里程碑 | 状态 | 证据 |
|---|---|---|
| **M0'** 脚手架 | ✅ | GDExtension 加载、`Metin2Model`/`Metin2AnimPlayer` 注册、`libgr2` 静态链接、Godot 4.7 工程 Metal Forward+ 出画 |
| **M1** 静态渲染 | ✅ | `warrior_cheongrin`75 骨 / 5 表面 / 3324 顶点,正立、朝 +Z、`unit_scale=0.01``flip_z/flip_winding=false`、贴图正确(`test/golden/m1-bindpose.png`)。多骨架:`warrior_lord`(v6,74)、`assassin`(v6,95,17 表面)、`shaman_lord`(**v7**,93) 均加载渲染正确 |
| **M2** 骨骼动画 | ✅ | `gr2::sample_pose`(跨文件)→ 蒙皮,**两条路径都用 libgr2 的 `skin` 矩阵完整仿射(shear 保留)**:默认 **CPU LBS**(每帧重建 mesh);`MTGODOT_GPUSKIN=1` 走**自写蒙皮顶点着色器**`m2_material` `SRC_SKIN`,逐骨 4×3 矩阵存 RGBAF 纹理,绕过 `Skeleton3D`)。`dance_1` 两条路径头/护甲/躯干都正确、与 xrender-poc bgfx demo 一致。`selfcheck` 0 NaN。~~旧 `Skeleton3D::set_bone_pose` 路径~~ 已删(Godot 内置蒙皮正交化丢 shear,见 §4)。 |
| **M2.5** 材质与观感 | ✅ 门禁已闭(材质精确映射);观感打磨留 Phase 2 | 4 个 `ShaderMaterial` shader`SRC_MIX`opaque / alpha / alpha-test)· `SRC_SKIN`(同 + GPU 蒙皮 vertex)· `SRC_ADD`additive)· `SRC_ADD_SKIN`。贴图 = **gr2 material 绑定**`Mesh::material_textures[matidx]`mesh-local)→ 找不到才 `_resolve_texture` 文件名启发式;`build_parts()``tri_group.material_index` 把一个 gr2 mesh 拆成 N 个 surfaceshaman 1→2、assassin 17→18、warrior 5→5,实测各面贴图正确)。blend/cull/alpha 靠 `guess_blend()` 按表面名启发式(effect→add、hair→alpha-test、cloak/cape/skirt→alpha)。方向光阴影 + procedural sky + `MTGODOT_FOG=1` 引擎雾(`test/golden/m25-*.png`)。 |
## 2. 桥接层工程量(实测)
`extension/src` 现 ≈ **1997 行**(不含 godot-cpp;含端口自 xrender-poc 的 DXT 解码 ~180 行)。
中期评审时约 1100 行,评审后收尾又加了 ~700(`build_parts` 拆 surface、`SRC_SKIN` GPU 蒙皮着色器、
`.msa`/`.msm` 解析、路径解析、逐 surface 材质缓存)。
| 文件 | 行 | 职责 |
|---|---|---|
| `metin2_model.{h,cpp}` | ~870 | `Metin2Model` 节点、`.gr2`/`.msm` 加载、`build_parts` 拆 surface、贴图解析、材质、CPU/GPU 蒙皮、属性 |
| `metin2_anim.{h,cpp}` | ~420 | `Metin2AnimPlayer`、每帧 `sample_pose` → CPU/GPU 蒙皮、`.msa` 解析 + motion event 派发、`selfcheck` |
| `gr2_bridge.{h,cpp}` | ~290 | gr2 Mat4→`Transform3D`4×4 转置)、`build_skeleton`/`build_skin``build_parts`+`build_mesh`、Z-up→Y-up `make_conv` |
| `m2_material.{h,cpp}` | ~235 | 4 个 `ShaderMaterial` shader`SRC_MIX` / `SRC_SKIN` / `SRC_ADD` / `SRC_ADD_SKIN`+ 退出前 `cleanup_material_shaders` |
| `dxt.{h,cpp}` | ~180 | DDS DXT1/3/5 软解(端口,level 0 |
| `register_types.{h,cpp}` | ~40 | GDExtension 入口 |
| libgr2vendored)新增 `gr2_material.cpp` + `Mesh::material_textures` | ~150 | `dump_materials()` + 逐 mesh MaterialBindings→贴图名 |
**耗时**M0'–M2.5 一次会话跑通,评审后收尾一次会话。与计划 §05 估的「3.5–5 周单人」相比 **核心桥接远比预估轻** ——
`libgr2` 已完成、Godot 的 `Skeleton3D`/`ArrayMesh`/`Skin` 直接可用;蒙皮因 Godot 内置路径丢 shear(见 §4
改为自己做(CPU LBS 默认 + 自写顶点着色器),也只是 ~200 行。
## 3. 关键技术结论
- **坐标/单位**gr2 行主序 Mat4 → Godot `Transform3D` 就是一次 **4×4 转置**gr2 平移在第 4 行 = Godot `.origin`)。Z-up cm → Y-up m`rotate(-90°,X)` + `scale(0.01)`)放在 `Metin2Model` 节点自身 transform。四个测试骨架全部 `flip_z=false``flip_winding=false` 正确 —— **Metin2/Granny 内容对 Godot 右手系不需要额外镜像**
- **动画走 B 方案 + 自做蒙皮**:`_process``sample_pose``world[]` / `skin[]``skin[i] = inverse_world[i]·world[i]`)。**Godot 内置蒙皮(`Skeleton3D` + `set_bone_pose`)不能用** —— 它把每骨 transform 分解成 position + 单位四元数 + 逐轴 scale,丢掉 Granny 烘在 rig 骨上的 shear(§4)。改为:默认 **CPU LBS**`Metin2Model::cpu_skin`,每帧把 `skin[]` 逐顶点作用、重建 `ArrayMesh`);`MTGODOT_GPUSKIN=1` 走**自写蒙皮顶点着色器**`SRC_SKIN`,逐骨完整 4×3 矩阵存 RGBAF 纹理)。`set_bone_global_pose` / `Skeleton3D` 蒙皮路径已删。不烘 `Animation` 资源(A 方案走 `Skeleton3D` 骨骼、同样丢 shear,不用)。
- **贴图**Godot 4 `Image.load()` 不支持 `.dds` → 运行时软解 DXT`dxt.cpp`level 0 + `generate_mipmaps()`)。`.gr2``.gdignore` + 绝对路径加载,绕开 Godot import。
- **移动端预判**:目标设备定为 MacMetal+ 一加 13Vulkan+ iPhone 16Metal),均现代硬件,**不涉及 Compatibility(GLES3)**。CPU LBS 路径(默认)+ `ShaderMaterial` + `ArrayMesh` 无平台特性风险。自写蒙皮着色器用了 `texelFetch` + RGBAF 纹理 + `BONE_INDICES` —— Vulkan / Metal 都支持,Phase 2 在一加 13 / iPhone 16 各跑一次确认(出问题则人群场景回落 CPU 或改 uniform 数组传骨骼)。
## 4. 剩余风险 / 门禁开口
| 项 | 影响 | 出路 |
|---|---|---|
| ~~libgr2 曲线解码 bug —— 头 / 颈塌陷~~ **→ 已定位并修复(2026-08-29,误判)**。不是 libgr2`tools/anim_probe` 全时间轴扫 `dance_1` 240 帧,libgr2 的 `world[]` 全部良态(0 奇异 / 0 NaN,Head/Neck 是干净刚体),且 `oracle/run-diff-suite.sh` 已对拍真 Granny @ t=0/4.7/11.3/20 ≤4.1e-5(含 `d=2` 四元数曲线)。真因:**Granny 给每个 Metin2 模型的 ~7 根骨(`bone_front_01..04``Bone_shoulder_01..04` —— 前襟条 + 护肩片)烘了 shear + 非均匀缩放**world det 低到 0.065),而 Godot `Skeleton3D::set_bone_pose` 把 transform 分解成 position + 单位四元数 + 逐轴 scale**shear 被静默丢弃**TRS 往返误差 0.07–0.94)→ 那几根骨塌。`wait` 同样有这 7 根坏骨,只是动得少不明显(不是 `dance_1` 专属)。 | **已消除**:① CPU 线性混合蒙皮转默认路径(`Metin2Model::cpu_skin`),直接把 libgr2 的 `skin[]` 逐顶点作用、绕过 `Skeleton3D`;② `MTGODOT_GPUSKIN=1` 走自写蒙皮顶点着色器(`SRC_SKIN` / `SRC_ADD_SKIN`,逐骨完整 4×3 矩阵存 RGBAF 纹理),不正交化、shear 保留。旧 `set_bone_pose` 路径已删。headless 对拍:`dance_1` @ t=7/19 两条路径头/颈/躯干与 xrender-poc bgfx demo 一致。 | 已完成。代价:CPU 路径每帧变顶点 + 重建 mesh(人群切 GPU 着色器路径)。`anim_probe` 扫全 racewarrior 7 根、**sura 16 根** shear 骨(`test/shear-bones-survey.md`),故自写着色器是必需、非优化。 |
| ~~M2.5 材质精确映射未闭~~ **→ 基本闭合(2026-08-29**。`tri_group.material_index`**mesh-local** 索引(进 `Mesh::MaterialBindings`),不是全局 `Materials[]` —— 原来用 `dump_materials()[matidx]` 才把 body/face 搞混。改用 libgr2 的 `Mesh::material_textures[matidx]`(与 `MaterialBindings` 平行、xrender-poc session 里已按渲染结果验过 warrior/sura/assassin/shaman 全对)。`use_gr2_materials` 默认开、文件名启发式降为回退。 | 已消除。剩:跑一次 `oracle/run-diff-suite.sh` 级别的逐材质对拍做纸面确认(可选,观感已对);MODULATE2X/ADDSIGNED 等 stage op 仍只近似 |
| ~~多材质槽内拆分~~ **→ 已做**`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` 面)~~ **已做(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. 建议
**进入 Phase 2(一加 13 / iPhone 16 三设备 bring-up,自用)。**
理由:Phase 1 要证明的三件事里,①gr2→Godot 场景对象、②动画运行时移植,**已用可运行代码 + 截图 + 0-NaN 自检证明可行且工程量小**;③观感还原:自定义 shader、逐 tri_group 材质、阴影、sky、fog、多骨架已就位,M2.5 门禁(材质精确映射)已闭,剩余是观感打磨(多 stage op / cull / 阴影调优),不构成方案性风险。
Phase 2 前的收尾清单 —— 大部分已在 2026-08-29 做完:
1.`test/compare.py` —— oracle gate + 每 case 退出码 / 截图 / `surfaces>0` / `NaN=0` 判据(CPU + GPU 两路)。`compare-report.json`
2. ✅ M2.5 材质门禁 —— `build_parts()``tri_group.material_index` 拆 surface + 用 `Mesh::material_textures`mesh-local)取贴图。shaman/assassin 多材质面正确。逐材质 oracle 纸面对拍可选。
3.~~修 libgr2 曲线~~ 误判 —— CPU 蒙皮转默认(见 §4)。
4. ✅ 前台性能重测(`test/godot-macos-stress.json`):30 角色 24fps;瓶颈是每帧 mesh 全量重建。
5.`anim_probe` 全 race 扫(`test/shear-bones-survey.md`):warrior 7 根、**sura 16 根** shear 骨;assassin/shaman 干净。据此**已实现自写 GPU 蒙皮顶点着色器**(`SRC_SKIN`,逐骨完整 4×3 矩阵)—— `MTGODOT_GPUSKIN=1` 现在走它、不再走坏的 `Skeleton3D`。warrior `dance_1` GPU 路径与 CPU 一致(`test/compare-shots/*.gpu.png`)。
6.`.msa` 接线:`anim_path``.msa``MotionFileName` + `Accumulation``get_accumulation`+ `MotionEventData``get_events()` + `motion_event` 信号,带循环回绕)。`throw.msa` 事件 t≈0.83 触发。
7. 🟡 `.msm` 接线:`gr2_path``.msm` → 解析 + 解析 `BaseModelFileName`(含 eterpack 散包目录扫描)自动加载 base model + `get_hair_options()` 发型目录(75 项)。`warrior_w.msm` 实测。**发型 mesh 实际挂接仍 ⬜** —— 需 base 骨架的逐帧矩阵(与 anim player 共享),属 Phase 2 模型组合。
> Godot route 桥接层已验证正确:数值真值 = `oracle_diff`libgr2 vs 真 Granny 2.9.1223 用例 ≤6.5e-5),
> 两条蒙皮路径都逐顶点作用这批 `skin[]` 矩阵;CPU 与 GPU 渲染 `wait` / `dance_1` 与 xrender bgfx demo 抽帧一致。
> 原 #3「libgr2 曲线 bug」是误判,真因是 Godot 内置蒙皮(`set_bone_pose` / `Skeleton3D`)正交化丢 shear,已用自做蒙皮规避。
> (旧文里的「`get_bone_global_pose` vs `conv(world)` ≤2.6e-5」是已删的 `Skeleton3D` 校验路径的数字,不再适用。)
不建议现在做的:地形、`.mse` 特效、eterpack、UI/Python —— 均按计划留到正式移植。
> 「离一个可用客户端还差什么」的完整分层清单见 [`BACKLOG.md`](./BACKLOG.md)A Phase-1 收尾 /
> B 材质保真 / C 动画运行时 / D 模型组合 / E 渲染功能 / F 平台 / G 资产管线 / H 测试工程 /
> I libgr2 覆盖面),含条目 ID、粗估规模、排期。