Metin2 game client (P0–P11) + mobile asset pipeline

Networked client on the existing Godot 4.7 + libgr2 renderer:
- net: m2dev wire protocol (libsodium KX + XChaCha20), auth/select/game
  phases, EntityStore world model, ~all GC/CG headers. char create/delete,
  private shop / mall / cube, SHOP_GC_START_EX, guild, party (+ CG_PARTY_SET_STATE),
  quests, dragon soul, refine, safebox, exchange.
- UI: in-game windows migrated 1:1 from the reference uiscript/root .py —
  char status (/stat), inventory+equipment, select-item ([SELECT_ITEM] quest
  token), system-option + game-option + ESC system menu, private-shop 39-grid,
  party info board, shop tabs, atlas, minimap, quickbar, chat, …
- EterGrnLib polish: GR2 material blend/two-sided, LOD crossfade, motion-event
  dispatch, contact shadow, ray-AABB picking, weapon grip pre-transform.

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

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

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EJxkHiNKS4kybHS3XKyAJ
This commit is contained in:
shen
2026-08-31 20:02:12 +09:00
co-authored by Claude Sonnet 5
parent f4917a2b3b
commit 47baf6c0c6
414 changed files with 69568 additions and 385 deletions
+45 -29
View File
@@ -1,7 +1,8 @@
# Phase 1 中期评审 — mtgodot-poc
> 日期:2026-08-29 · 平台:macOSApple M2)· Godot 4.7.1 · 对标 [`GODOT-POC-PLAN.md`](./GODOT-POC-PLAN.md) §00 Phase 1
> 结论先行:**桥接层工程量小、风险已基本出清,建议进入 Phase 2(移动端)**。M2.5 门禁的"材质精确映射"仍有一处需要 oracle 的开口,不阻塞。
> 日期: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 纸面对拍这一可选项。
---
@@ -11,54 +12,69 @@
|---|---|---|
| **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** 骨骼动画 | ✅(渲染路径)/ ⚠️(一条 upstream bug | `general/wait`2.67s / 75 track)经 `gr2::sample_pose`(跨文件)→ 逐骨 Godot **局部 pose**`set_bone_pose`)→ GPU 蒙皮,头 / 护甲对齐正确(`test/golden/m2-wait.png`)。**数值验证**`get_bone_global_pose(i)` vs `conv(world[i])` 全 75 骨 ≤ **2.6e-5**CPU-LBS 参考路径(`MTGODOT_CPUSKIN=1`,与 xrender `skin_mesh` 同式)与 GPU 蒙皮同结果。`selfcheck` 0 NaN。**但**`dance_1` 等表情动作会让头 / 颈塌陷 —— **libgr2 曲线解码 bug,在 xrender-poc bgfx demo 里同样复现**见 §4 |
| **M2.5** 材质与观感 | 🟡 门禁未闭 | `ShaderMaterial`modulate tex×COLOR×lightopaque/alpha/alpha-test/add 四模式)+ 目录扫描贴图解析(warrior 5/5、assassin 17/17、shaman 1/1 表面命中)+ 方向光阴影 + procedural sky + 引擎雾(`test/golden/m25-*.png`)。`libgr2::dump_materials()` 已加(见 §4 |
| **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. 桥接层工程量(实测)
新增 C++**1100**(含端口自 xrender-poc 的 DXT 解码 160 行)
`extension/src`**1997**不含 godot-cpp含端口自 xrender-poc 的 DXT 解码 ~180 行)
中期评审时约 1100 行,评审后收尾又加了 ~700(`build_parts` 拆 surface、`SRC_SKIN` GPU 蒙皮着色器、
`.msa`/`.msm` 解析、路径解析、逐 surface 材质缓存)。
| 文件 | 行 | 职责 |
|---|---|---|
| `gr2_bridge.{h,cpp}` | ~260 | gr2 Mat4→`Transform3D`4×4 转置)、`build_skeleton`/`build_skin`/`build_mesh`、Z-up→Y-up `make_conv` |
| `metin2_model.{h,cpp}` | ~470 | `Metin2Model` 节点、`load_gr2`、贴图解析、材质、属性 |
| `metin2_anim.{h,cpp}` | ~230 | `Metin2AnimPlayer`、每帧 `sample_pose`→骨骼、`selfcheck` |
| `m2_material.{h,cpp}` | ~130 | Metin2 风格 `ShaderMaterial`(两个 shaderblend_mix / blend_add |
| `dxt.{h,cpp}` | ~160 | DDS DXT1/3/5 软解(端口) |
| libgr2 `gr2_material.cpp`xrender-poc | ~110 | `dump_materials()` 类型树遍历(附加) |
| `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 估的 Phase 1「3.55 周单人」相比**核心桥接远比预估轻** —— 因为 `libgr2` 已完成、Godot 的 `Skeleton3D`/`ArrayMesh`/`Skin`/GPU 蒙皮直接可用,且关键恒等式(`skin_matrix = global_pose(i)·bind_pose(i) == gr2 deformer 矩阵`)成立,不需要自研蒙皮。剩余预估工作量集中在 M2.5 材质精修 + 对拍。
**耗时**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[]``skel.set_bone_global_pose(i, conv(world[i]))`Skin bind = `conv(inverse_world[i])`。Godot 的 GPU 蒙皮矩阵 `global_pose(i)·bind(i)` 恒等于 gr2 deformer 矩阵。**不需要烘焙 `Animation` 资源** 即可保真(A 方案仅在移动端多角色 CPU 压不住时才需要)。
- **贴图**Godot 4 `Image.load()` 不支持 `.dds` → 运行时软解 DXT`dxt.cpp`)。`.gr2``.gdignore` + 绝对路径加载,绕开 Godot import。
- **移动端预判更好**M2.5 的 `ShaderMaterial` + `ArrayMesh` + `Skeleton3D` 都在 Godot Compatibility(GLES3) 支持范围内 —— Phase 2 老安卓机兜底比"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 —— 部分动画头 / 颈塌陷**`dance_1` 等表情,`rot:Old(d=2,dim=4)` 二次四元数 B 样条)。**这不是 Godot route 的问题** —— 已用 xrender-poc 的 bgfx demo 在同一动画 / 同一 t 复现完全一致的塌陷(`/tmp/xr-*.png`),且 mtgodot 的 CPU-LBS 参考路径(与 xrender 同式)也复现。`wait` / `run` 等动画正常。xrender-poc M2「对拍 Granny ≤4.6e-5」的 23 用例未覆盖到这个可视化用例。 | 表情动作不可用;idle / 移动动画正常。当前 demo 默认 `general/wait` | libgr2 `gr2_anim.cpp``Curve::eval` degree-2 分支 vs Granny `OldCurve` B 样条基对拍修正(需 Granny oracle)。两条 route 共用修法。**属 xrender-poc/libgr2 的活,不阻塞 Godot route 结论** |
| **M2.5 材质精确映射未闭**M2.5 门禁开口)| `libgr2::dump_materials()` 已能取材质名 + `FromFileName`warrior 4 材质 / 2 有 diffuse 名),但 material_index→贴图 的映射**未对拍 Granny**,实测驱动 warrior 时 body 表面误取 face 贴图。当前默认走文件名+目录扫描启发式(观感正确),material 路径 `use_gr2_materials` 开关默认关 | 需要 Granny oracle 逐材质对拍(与 xrender-poc M1 T5 同一缺口,共用修法)。这是 Phase 1 收尾的最后一项 |
| **对拍未接线** | 与 xrender-poc bgfx demo / oracle 的逐帧几何/骨骼矩阵对拍脚本(`test/compare.py`)未写 | 需先构建 xrender-poc bgfx demo(首次 ~10 min)。中期评审后、进 Phase 2 前补 |
| **性能数字被节流** | 后台窗口下 Godot 限 30fps1–50 角色帧时间都贴在 33ms 天花板;100 角色破顶到 52ms。资产构建 **~165ms/角色**(单线程,无跨模型缓存),100 角色加载 16.5s(`test/godot-macos-stress.json`) | 真机 / 前台窗口下重测(Phase 2)。构建成本需 Phase 2 做多线程 + eterpack 共享贴图 |
| **多材质槽内拆分** | `build_mesh` 目前一个 gr2 mesh = 一个 surface,未按 `tri_groups[].material_index` 再拆 | 有 material API 对拍后一起做 |
| ShaderMaterial 静态 Shader 退出泄漏、alpha 排序 corner case、法线未逆转置 | 均为已知小项,见 `GODOT-POC-PLAN.md` §M2.5 T2.5.6 | 不阻塞 |
| ~~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` 面)· `_frame_model` 对披风类框选偏 | 均不阻塞,见 [`BACKLOG.md`](./BACKLOG.md)`A3` `A6` `B10` `E5`)。法线用混合矩阵上 3×3 不逆转置 —— **与 Granny runtime 一致,是正确行为不是风险** |
## 5. 建议
**进入 Phase 2iOS + Android)。**
**进入 Phase 2一加 13 / iPhone 16 三设备 bring-up,自用)。**
理由:Phase 1 要证明的三件事里,①gr2→Godot 场景对象、②动画运行时移植,**已用可运行代码 + 截图 + 0-NaN 自检证明可行且工程量小**;③观感还原的基础设施(自定义 shader、阴影、sky、fog、多骨架已就位,只差"材质精确映射"这一处需 oracle 的收尾,不构成方案性风险。
理由:Phase 1 要证明的三件事里,①gr2→Godot 场景对象、②动画运行时移植,**已用可运行代码 + 截图 + 0-NaN 自检证明可行且工程量小**;③观感还原自定义 shader、逐 tri_group 材质、阴影、sky、fog、多骨架已就位,M2.5 门禁(材质精确映射)已闭,剩余是观感打磨(多 stage op / cull / 阴影调优),不构成方案性风险。
Phase 2 前的收尾清单1.52 周)
1. `test/compare.py`,与 xrender-poc bgfx demo 同机位 / 同 t 对拍几何 + 骨骼矩阵
2. `libgr2::dump_materials` 的 Granny oracle 对拍,闭合 M2.5 门禁
3. **修 libgr2 `Curve::eval` degree-2 四元数 B 样条**(对拍 Granny oracle)—— 这条其实是 xrender-poc/libgr2 的活,两条 route 共享收益;修好后表情动作即可用
4. 前台窗口 / 真机重测性能,给 Phase 2 的动画 A/B 一个可信基线
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 本身的桥接层已验证正确`wait` 渲染与 xrender bgfx demo 一致,骨骼矩阵 ≤2.6e-5CPU/GPU 蒙皮一致)。#3 是把已知的 upstream 曲线 bug 补上,不是 Godot route 的缺陷。
> 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、粗估规模、排期。