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

14 KiB
Raw Permalink Blame History

Phase 1 中期评审 — metin2-client(评审时仓库名 mtgodot-poc

本文是一份定格的历史评审记录,不反映现状(2026-09-08 复核)。 其中的规模数字尤其不要当现状读: §2 说 extension/src ≈ 1997 行 / 6 个文件,实际现在是 30029 行 / 68 个文件(网络、世界渲染、 资产管线都是评审之后加的)。现状看 BACKLOG.mdCLIENT-GAP.md

日期:2026-08-29(评审后又做了一轮收尾,见 §4/§5 的「已做」标注)· 平台:macOSApple M2)· Godot 4.7.1 · 对标 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_cheongrin75 骨 / 5 表面 / 3324 顶点,正立、朝 +Z、unit_scale=0.01flip_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 shaderSRC_MIXopaque / alpha / alpha-test)· SRC_SKIN(同 + GPU 蒙皮 vertex)· SRC_ADDadditive)· 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→Transform3D4×4 转置)、build_skeleton/build_skinbuild_parts+build_mesh、Z-up→Y-up make_conv
m2_material.{h,cpp} ~235 4 个 ShaderMaterial shaderSRC_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 mrotate(-90°,X) + scale(0.01))放在 Metin2Model 节点自身 transform。四个测试骨架全部 flip_z=falseflip_winding=false 正确 —— Metin2/Granny 内容对 Godot 右手系不需要额外镜像
  • 动画走 B 方案 + 自做蒙皮_processsample_poseworld[] / skin[]skin[i] = inverse_world[i]·world[i])。Godot 内置蒙皮(Skeleton3D + set_bone_pose)不能用 —— 它把每骨 transform 分解成 position + 单位四元数 + 逐轴 scale,丢掉 Granny 烘在 rig 骨上的 shear(§4)。改为:默认 CPU LBSMetin2Model::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 → 运行时软解 DXTdxt.cpplevel 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,误判)。不是 libgr2tools/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..04Bone_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-29tri_group.material_indexmesh-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.cppbuild_parts(),一个 gr2 mesh 有 N>1 个 tri_groups 就拆成 N 个 Godot surface(各自 material + 索引子区间)。build_mesh 和 CPU 蒙皮路径都按 RenderPart 走。shaman1 mesh→2 surface)、assassin17→18)实测各面贴图正确。 已做
对拍未接线 → 已写 test/compare.py2026-08-29 oracle gate(读 test/m2-numeric.jsonlibgr2 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_shadersregister_types 卸载时调)· alpha 排序 corner caserender_priority 粗排)· 背面剔除写死 cull_backMetin2 有 CULL_NONE 面) 已做(2026-09-08 复核)m2_material.cpp:188-211 已有 cull_disabled shader 变体,角色 / 武器 / 发型经 decide_two_sided() 驱动,见 BACKLOG.md B10 · _frame_model 对披风类框选偏 均不阻塞,见 BACKLOG.mdA3 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_texturesmesh-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.msaMotionFileName + Accumulationget_accumulation+ MotionEventDataget_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_difflibgr2 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.mdA Phase-1 收尾 / B 材质保真 / C 动画运行时 / D 模型组合 / E 渲染功能 / F 平台 / G 资产管线 / H 测试工程 / I libgr2 覆盖面),含条目 ID、粗估规模、排期。