Files
mtgodot-poc/docs/BACKLOG.md
T
shenandClaude Sonnet 5 47baf6c0c6 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
2026-08-31 20:02:12 +09:00

206 lines
30 KiB
Markdown
Raw 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.
# mtgodot-poc — 未完成工作清单
> 更新: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)(距参考图的保真化差距)。
>
> POC 的核心问题——**Godot + 自研 loader 能否正确读 + 渲 + 播 Metin2 `.gr2` 骨骼模型**——
> 在 macOS 上已答「能」:数值对拍 Granny ≤6.5e-5`warrior` `dance_1` 的 CPU 与 GPU(自写着色器)
> 蒙皮两条路径已**抽帧核对**t=7 / t=19)一致;`sura`/`assassin`/`shaman`(含 v7)加载渲染正常(未逐帧对拍)。
> 本文件是「离一个可用客户端还差什么」的分层清单,供正式移植排期。
>
> **状态**:✅ 已做 · 🟡 部分 · ⬜ 未做 · ⏭ 推迟到正式移植(不影响 Phase 1 中期评审 / Phase 2 bring-up · ❌ 本 POC 明确不做
>
> **规模**(粗估,单人 / 对本代码库熟悉后,置信度低 —— 排期时须自行细化):
> S ≈ 1–2 天 · M ≈ 1–2 周 · L ≈ 3 周+ · XL ≈ 独立子项目
>
> ID 稳定,可在 commit / issue 里引用(`A1` `B3` …)。B 段取代
> [`GODOT-POC-PLAN.md`](./GODOT-POC-PLAN.md) §M2.5 T2.5.6 的差距清单(那处不再单独维护)。
---
## A. Phase 1 收尾(SM
| ID | 项 | 状态 | 规模 | 说明 / 依赖 |
|---|---|---|---|---|
| A1 | 发型 mesh 实际挂接 | 🟡 | M | ✅ CPU 路径:`Metin2Model::set_hair_gr2()` + `_load_hair()` —— 加载发型 gr2,骨骼**按名重映射**到 base skeletonwarrior 74/74 匹配),hair mesh 并入 `cpu_skin` 每帧蒙皮,`alpha_scissor` 镂空材质。✅ GPU 蒙皮路径的 hair(`_build_gpu_mesh` 并入 + `ARRAY_BONES` remap,无需 shader 改)。✅ 武器挂点(C5);`.msm target_skin` 换色。 |
| A2 | anim player 与 model 共享逐帧矩阵 | ✅ | S | `Metin2Model::current_skin_matrices()` 缓存完整 affine deformer 矩阵:reload 初始化为 bind poseCPU/GPU 路径逐帧刷新;发型 / 武器 C++ 桥接可直接复用。 |
| A3 | `_frame_model` 相机框选 | ✅ | S | 已改为按所有 surface 顶点的 1%–99% 分位盒取景并加 8% 边距;assassin headless 冒烟得到稳定 `trimmed-mesh` 框选,孤立几何和远端挂点不再拖偏相机。 |
| A4 | 静态(无动画)展示路径统一 | 🟡 | S | 无 anim player 时走 `Skeleton3D` bind pose(与动画时的 `cpu_skin`/`gpu_skin` 不同代码路径)。实测 M1 bind pose 正确(`skin_matrix ≈ I`,见 `test/shear-bones-survey.md`),非 bug;为一致性 + 少一条代码路径,宜统一走蒙皮路径的 bind pose。 |
| A5 | CPU 路径每帧全量重建 mesh | 🟡 | S–M | `cpu_mesh->clear_surfaces()+add_surface_from_arrays` 每帧全量(**疑似** 30 角色 24fps 的主因 —— 未 profile 确认,见 F4)。改成原地 `Mesh::surface_update_vertex_region`,或人群直接切 `MTGODOT_GPUSKIN=1`。 |
| A6 | `ShaderMaterial` 静态 Shader 退出泄漏 | ✅ | S | Shader cache 由扩展在 scene-level terminator 显式 `unref()`CPU/GPU headless 退出不再报告该静态引用泄漏。 |
| A7 | GPU 蒙皮跑 stress harness | 🟡 | S | 已确认 `_run_stress` 会让每个实例进入 bone-texture GPU LBS,且 headless 2 角色冒烟成功;报告文案已纠正。headless dummy renderer 的帧数无性能意义,仍缺前台 Metal 人群基线。 |
| A8 | loader 健壮性 | 🟡 | S | 解压失败、section 越界与 `build_fileinfo` 失败现会严格传播;新增 4 类损坏输入 CTest,全量真实语料仍为 9166/9166。仍缺 0 骨 / 索引越界 / NaN 的桥接层用例及 H1 draw 冒烟。 |
| A9 | `reload()` 遗留蒙皮状态 | ✅ | S | 重载/切模型前现在清空 `cpu_mesh`、GPU 标志、bone texture、parts/material cache 与 `.msm` 派生状态,避免复用旧模型资源或 CPU/GPU 双重蒙皮。 |
| A10 | 动画 mesh 裁剪盒 | ✅ | S | CPU LBS 每帧按有限的变形后顶点刷新 `custom_aabb`;GPU 路径继续使用保守扩展盒。 |
---
## B. 材质 / 观感保真(M2.5M;取代 GODOT-POC-PLAN §M2.5 T2.5.6
**现状**:贴图 = gr2 material 绑定(`Mesh::material_textures[matidx]`mesh-local`use_gr2_materials=true` 默认)
→ 找不到时 `_resolve_texture` 文件名启发式(face/hair/stem 关键字 + 目录扫描)。blend / cull / alpha
全靠 `guess_blend()` 按**表面名关键字**猜。着色器 `SRC_MIX` / `SRC_SKIN`skinned/
`SRC_ADD` / `SRC_ADD_SKIN`additive;静态与 GPU 蒙皮均覆盖)。
**前置 B1**libgr2 已有 `MaterialInfo{name, diffuse_texture, all_textures, map_count, alpha_blend, two_sided}` +
`Mesh::material_textures`,其中 blend/two-sided 仍是名字、贴图数与 ExtendedData 的不完整推断;任意 D3D stage 状态不在 GR2 中。
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| B1 | libgr2 暴露 blend / cull / alpha 数据 | 🟡 | M | Metin2 gr2 **不带 D3DRS** —— 客户端靠 Name(`Blend*` + MapCount>1) + ExtendedData("Two-sided")。libgr2 `MaterialInfo` 已加 `map_count` / `alpha_blend` / `two_sided`(名字 + map 推断,`static_object.cpp` 已用来切 `TRANSPARENCY_ALPHA` / `CULL_DISABLED`)。gr2fuzz 9166/9166 不回归。⬜ 精确解析 ExtendedData 变体,并由 B2 端口原客户端实际使用的固定功能材质分支。 |
| B2 | 原客户端固定功能材质状态兼容 | ⬜ | M | 现在只近似单 stage `MODULATE(TEXTURE, DIFFUSE)`。先盘点 `GameLib/ActorInstanceRender.cpp``EterGrnLib/ModelInstanceRender.cpp``EterGrnLib/Material.cpp` 在目标资产上实际触发的 blend/alpha-test/sphere-map/多贴图分支,再扩 `SRC_MIX`GR2 不承载任意 D3D stage 状态,不按未经证实的 op 清单猜实现。 |
| B3 | alpha-test(镂空) | 🟡 | S | hair 表面名 → `AlphaTest`B4 里 `decide_blend` 仍保留这条,GR2 材质 flag 不区分镂空 vs 不透明)。⬜ 改成贴图 alpha 分布判定;阈值硬编码 0.5,应取材质 `D3DCMP_*` ref。 |
| B4 | blend 模式取自材质 | ✅ 首版 | S | `metin2_model` `decide_blend()`**优先 `gr2::MaterialInfo.alpha_blend`**`dump_materials` 端口 EterGrnLib Material.cpp:233 = Name `Blend*` + MapCount>1),仅 hair 镂空 / effect 加法这两类 GR2 没编码的走表面名兜底。`decide_two_sided()``MaterialInfo.two_sided` ‖ hair/cloak/cape/skirt/leaf 名。`match_material()` 按 diffuse_texture/name basename 回查。两处(body + weapon)都改。m2_material 加 `cull_disabled` shader 变体(`shader_for(add,skin,two_sided)`)。⬜ 精确 ExtendedData 变体、alpha-test ref 取材质 `D3DCMP_*`。 |
| B5 | 每材质多张贴图(spec / mask / …) | ⬜ | M | 现在只解析 diffuse。`Mesh::material_textures` + libgr2 材质 API 扩成 map。 |
| 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` 死字段)。需给角色也按材质切。 |
| B11 | winding 逐模型验 | ⬜ | S | `flip_winding` 默认 false(4 个骨架验过,未全量)。 |
---
## C. 动画运行时(SL
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| C1 | root motion`Accumulation` | 🟡 | S | `.msa` 已解析、`get_accumulation()` 暴露;未作用到节点 transform。 |
| C2 | 动画混合 / crossfade | 🟡 | M | ✅ `Metin2AnimPlayer.blend_time`(默认 0.15s):切 `anim_path` 时旧 clip 冻结,逐骨对旧/新 `world_pose` slerp+lerp`skin = invWorld · world_blended` 重建;`mul4x3` 复用 libgr2 语义。权重曲线改为 **`2w²−w³`ease-in),对齐客户端 `GrannySetControlEaseInCurve(t0,t1,0,0,1,1)` Hermite**(原用 smoothstep)。⬜ shear 骨过渡期精确保持、受控帧核对时长、非循环 clip 结束回退(现 `playing=false` 定格,`apply_pose` 已 clamp,够用)。 |
| C3 | additive 动画 / 部分身体 mask | ⬜ | M | 上半身动作叠在移动上等。 |
| C4 | `.msa` loop / 一次性动作 | 🟡 | S | 已增加整段 `loop` 属性:false 时夹到末帧、停止并发 `playback_finished`;实际 `.msa LoopData`count/cancel/start/end)已解析并由 `get_loop_data()` 暴露,真实资产冒烟通过。剩余:按原客户端规则执行片段循环次数/取消窗口;整段 loop/once 仍需由动作注册层指定,不能从 `LoopData` 猜。 |
| C5 | 挂点系统 | 🟡 | M | ✅ 武器挂手骨:`Metin2Model::set_weapon_gr2()` / `set_weapon_bone()`(默认 `equip_right_hand`),子 `MeshInstance3D` 每帧取 `gr2::sample_pose``world_pose[bone]``Metin2AnimPlayer::apply_pose` 驱动;对齐客户端 `GetBoneMatrixPointer`)。⬜ 特效挂骨、左手/双持/盾、`.mse` trail。 |
| C6 | motion event 类型语义 | 🟡 首版 | M | `game_scene._on_local_motion_event` 分派(对齐 `RaceMotionData EMotionEventType`):有 `sound``_audio.play_at`;有 `effect``fx.spawn_at(basename)``EffectPosition`type 2 `SCREEN_WAVING``cam.shake`type 3 `SCREEN_FLASHING` → 屏闪 ColorRecttype 6/9 `FLY/WARP` → 钩子(skill_fx 已单独处理飞行物)。`eterngrn_polish_test.gd` 覆盖。⬜ `.msa` type 数字与运行时枚举的历史偏差核对、武器拖尾(C 未列,见 WeaponTrace)、投掷点精确。 |
| C7 | 播放速率(数据驱动) | ⬜ | S | `time_scale` 有,未按动作类型 / 装备驱动。 |
| C8 | IK / look-at / 踩地校正 | ⬜ | L | 无。 |
| C9 | LOD 选择 | 🟡 | M | ✅ 距离切 mesh`lod_distances` 默认 `[18,42,90]`m,重建 `cpu_mesh`+材质+蒙皮)。**+ 淡入淡出**:切档时把旧 mesh 快照进 `LodGhost` MeshInstance3D(冻结),shader `lod_fade` uniformmix/skin)在 ~0.18s 内旧 1→0 / 新 0→1`LODController::BlendRenderWithOneTexture`);距离阈值加 15% 迟滞防抖。⬜ GPU skin 路径的 LOD、受控帧核对。 |
---
## D. 模型组合(SL
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| D1 | 多部件装配(身体 + 武器 + 发型 + 时装 + 翅膀,共享骨架) | ⬜ | L | `.msm` + 物品系统。核心:多 `MeshInstance3D` 绑同一骨架 + 骨名重映射;部位遮挡 / 隐藏规则。 |
| D2 | 武器模型(独立 `.gr2` 挂手骨) | 🟡 | M | ✅ 右手单持跑通(见 C5rigid mesh 分支 `build_mesh` 已验,warrior + `00040.gr2` idle/run 跟手,贴图按材质绑定名解析)。⬜ 左手/双持/盾、`.msm`/物品系统给路径、`_lod` 变体。 |
| D3 | 贴图换色(`SourceSkin``TargetSkin` | 🟡 | S | ✅ 发型:`Metin2Model.hair_skin`TargetSkin dds 覆盖发型 albedo,对齐 `SetMaterialImagePointer`);`get_hair_options()` 返回绝对 `source_skin`/`target_skin``formats.msm_hair` CTest。⬜ 护甲/时装部位的 skin 表。 |
| D4 | 装备 / 时装换装系统 | ⬜ | L | 材质槽、部位互斥、隐藏规则。D1 的上层。 |
---
## E. 渲染功能(SXL
> `E7``E10`(特效 / 地形 / 水 / 植被)+ 地图加载 / 环境 / 第三人称场景的完整实施规格见
> [`SHINSOO-WORLD-RENDERING.md`](./SHINSOO-WORLD-RENDERING.md)W0W8 / R1 / R2,≈3.55.5 月起,
> Phase 2 三设备 bring-up 之后启动;自用,无正式门禁,W0 可提前并行)。对应的地图 / 环境 / 资产解析
> 工作项见下方 `E11``E13`,以及 `G7` / `H7` / `I5`。
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| E1 | 阴影调优 | 🟡 | SM | `DirectionalLight3D` shadow 已开,出图看着正常;range / cascade / bias 未按 `unit_scale=0.01` 系统性调,需验漏影 / 摩尔纹。✅ 分层(PARITY §3.3):树 + `shadowflag≠1` 静态物体不投实时阴影(靠烘焙 `shadowmap.dds`),`shadowflag=1` + 角色投实时;`tree_shadows`/`static_shadows` 开关。 |
| E2 | 环境 | 🟡 | M | 角色演示:procedural sky + sky ambient。**世界(W5**`extension/src/environment_builder.cpp``.msenv``Sun`(方向光)+ `WorldEnv`Gradient 三段天空 / Material.Ambient / 深度雾 foglevel / Filmic+adjustment)。⬜ Character 光、云层、lens flare、昼夜。SHINSOO §9-W5。 |
| E3 | 后处理 | 🟡 | M | tonemapFilmic+ adjustment 调色 + glowintensity 0.35+ SSAOradius 1.2+ MSAA 4× 已开。⬜ 按受控参考帧标定曝光/对比/饱和/glow/SSAOTAA/FXAA 按平台取舍。 |
| E4 | 雾 | 🟡 | S | 角色演示 `MTGODOT_FOG=1`。世界(W5):`environment_builder``.msenv foglevel` 设深度雾 begin/end + 雾色。⬜ 与 `.wtr` 水雾 / 高度雾绑定。 |
| E5 | 透明排序 | 🟡 | M | `render_priority` 启发式。自重叠 alpha(头发 / 飘带)可能排错;需按深度排序或 OIT。 |
| E6 | Decal / 投影器 | ⬜ | S | 血迹 / 阴影贴花。 |
| E7 | 特效 / 粒子系统(`.mse`) | ⏭ | LXL | 真实资产是 ASCII Group/List 文本,原客户端 `EffectLib/EffectData.cpp``ParticleSystemData.cpp``EffectMesh.cpp``SimpleLightData.cpp` 已有 parser/语义真值。W7 首版尚未端口。任务是把 Particle/Mesh/Light/时间事件和挂点映射到 Godot,不是逆向私有二进制;先做最终机位必要子集,不阻塞 Phase 2 bring-up。SHINSOO §9-W7。 |
| E8 | 地形 | 🟡 | XL | ✅ 几何(W1):`formats/terrain_mesh.cpp` + `Metin2World`A1 20/20 `ArrayMesh` + UV`sample_height` 对拍 areadata z)。✅ splat 数据(W2):`formats/splat.cpp` 端口 `RAW_GenerateSplat``terrain_splat.cpp` 用 2 张 RGBA 权重图 + `Texture2DArray` 出图。⚠️ 当前 `min(8, splat.layers.size())`,而 A1 单区块最多 13 个非零层,会静默丢层;UV 也仍是错误的 `128/scale`,应改为区块归一化 U=`8*UScale`、V=`-8*VScale`。✅ 流式首版:逐区块节点 + 3×3 装卸 + `get_perf()`。⬜ 全活动图层、原始 UV/offset、源分辨率与 mip、动态角色阴影落地形、碰撞/LOD/worker/Mobile。SHINSOO §9-W1..W2/W8。 |
| E9 | 水 | 🟡 | L | W7`water_builder.cpp` 已按 `water.wtr` 逐层生成行 span 掩膜网格,`SRC_WATER` 仍是程序化波纹/菲涅耳。原客户端 parity 缺口是 `special/water/01..30.dds` 约 70ms 翻页、相机空间 UV、按水深逐顶点 alpha 和轻微高度动画。反射/折射/泡沫/normal map 属于参考帧确认后的增强;行列合并只减少几何量,不会减少现有每层一个 surface 的 draw call。SHINSOO §9-W7。 |
| E10 | SpeedTree 植被 | 🟡 | L | `.spt` 几何 loader 位于闭源 IDV `.lib`;实物为带符号的 COFF x86-64,不能链进 macOS/移动端且分发需确认授权。真实资产共 118 个。✅ R2 proxy`formats/spt.cpp` 嗅探 bark/composite atlas`tree_placeholder.cpp` 使用真实 DDS、确定性枝干、多组交叉叶簇和风摆;按 treefile 缓存共享 mesh + `MultiMeshInstance3D`A1 368 棵 / 14 树种),并已移除非原版的逐实例随机 yaw/scale。⬜ 用 `SpeedTreeRT.h` + `.lib` 验证 Windows x64 离线 exporter,再补真实 branch/frond/leaf、LOD/impostor。SHINSOO §9-W4。 |
| E11 | 地图文本格式解析(MapSetting / TextureSet / `.msenv` | ✅ | M | `formats/map_setting.cpp` + `texture_set.cpp` + `environment.cpp``.msenv` Group/List 文本树:方向光 / 材质 / `foglevel` / SkyBox+cloud / Gradient list / LensFlare)。`m2_tokvec` = `LoadMultipleTextData` 等价。CTest `formats.map_formats` + `tools/map_probe` 过真实 A14×5 / 17 层 / `foglevel 6` / 10 行 Gradient)。 |
| E12 | 区块二进制 + AreaData 解析(`height.raw` / `tile.raw` / `attr.atr` / `water.wtr` / `areadata.txt` / `areaambiencedata.txt` | ✅ | M | `formats/terrain_files.cpp`magic 2634/5426 + 尺寸校验,真值取自 `Terrain.cpp`+ `area_data.cpp`(含 `areaproperty` / `areaambiencedata`)。A1 全 20 区块 / 975 对象过 `map_probe`areadata `position` = **地图全局 cm**W3 修正,SHINSOO §5.6)。 |
| E13 | Property CRC 注册表 + 五类对象实例化(Tree / Building / Effect / Ambience / DungeonBlock | 🟡 | L | ✅ 注册表(`formats/property.cpp`A1 1330 CRC136/136 命中)。✅ Building/DungeonBlock`static_object.cpp` `get_static_mesh()`(复用 `gr2_bridge`,跨实例共享 mesh`CULL_BACK` + leaf/fence 关键字 alpha-scissor+ `place_objects()`A1 601 实例 / 119 mesh / 0 缺;位置=**地图全局 cm**,`ShadowFlag` / `portal_ids` meta / `.mdatr` 计数)。✅ Tree(W4,见 E10)。✅ `ypr` 轴共轭 bug 已修(`object_basis_godot()`,roll=朝向不再翻滚,影响 66% A1 物体;CTest 钉约定)。⬜ Effect/Ambience、逐建筑朝向对原客户端复核、`.mdatr` 碰撞体、LOD。SHINSOO §9-W3。 |
---
## F. 平台(Phase 2 —— 三设备 bring-upMac / 一加 13 / iPhone 16,自用,ML
| 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),发布前须换。 |
| 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`。 |
| 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。 |
---
## G. 资产管线(SM
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| G1 | eterpack 读取器 | 🟡 | ML | ✅ **本 fork 不是老 `.eix/.epk`** —— 是 `PackLib/Pack.cpp` 的单文件包(header + XChaCha20 加密索引 + zstd 数据,可选逐文件加密,硬编码 `PACK_KEY`)。`extension/src/pack/eterpack.{h,cpp}``EterPack`(读,name-field 从 header 反推以兼容真实 `PackMaker.exe` 包)+ `write_pack`(写)+ `packtool` CLIpack/unpack/list,跨平台 PackMaker 替代)。`pack.roundtrip` CTest + 真实 45 文件 `textureset/` 目录明文/加密往返 `diff` 一致(含 CP949 中文名 + 嵌套目录)。⬜ 接进 `AssetResolver`(从包解析而非散文件)+ mmap 大包。 |
| G2 | 跨实例资产缓存 / 去重 | 🟡 | M | 角色:每实例重建。世界(W8):`Metin2World``PropertyRegistry` / `AssetResolver` / `StaticMeshCache`(建筑 gr2→meshA1 119 份)/ DDS 解码缓存跨区块装卸复用。SHINSOO §9-W8。 |
| G3 | 异步 / 多线程加载 | 🟡 | M | 角色 `Metin2Model` 全同步。世界(W8):`Metin2World` 流式装载分摊到 `stream_budget` 个区块/帧(主线程),未上 worker thread。SHINSOO §9-W8。 |
| G4 | `.dds` 原生 import 路径 | 🟡 | S | M1 T1.3 定为运行时 `dxt.cpp`level 0 + `generate_mipmaps()`)。Godot 原生 import 分支没接。 |
| G5 | GPU 压缩纹理 / mip 质量 / sRGB 通盘过 | ⬜ | SM | 现在软解 RGBA8 上传,GPU 端不压缩;移动端显存 / 带宽要压(BC7 / ASTC)。 |
| G6 | DXT 软解补 level-N mip | 🟡 | S | mtgodot `dxt.cpp` 只解 level 0xrender 版有完整 mip 链,可回移)。 |
| G7 | 跨 pack 虚拟路径索引与覆盖优先级(`AssetResolver` | 🟡 | M | ✅ `formats/asset_resolver.cpp`:规范化(`\``/` / 去盘符 / 小写 / `.``..`+ `ymir work/` 后缀索引 + `by_rel` + pack 优先级(基础包在前,`metin2_patch_*` 在后,`stable_sort` + 先注册者胜)+ 冲突计数。A1 索引 54212 文件、17/17 地表贴图可解析。⬜ 冲突明细导出 + 优先级快照进测试。是 G1(eterpack)的运行时前置。SHINSOO §4.2 / §9-W0。 |
---
## H. 测试 / 工程(SM
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| H1 | 全语料过 Godot loader 的 fuzz | ⬜ | SM | `gr2fuzz` 只覆盖 libgr2 解析(9166/9166)。缺「把每个 `.gr2` 塞进 `Metin2Model` + 蒙皮 + draw」的冒烟(对标 xrender 的 `render_fuzz`)。 |
| H2 | 全 race / 怪物 / NPC / 坐骑抽样 | 🟡 | S | 现在只抽查 ~6 个 PC 模型 + `anim_probe` 4 个 race。 |
| H3 | 带容差的 golden 视觉回归 + CI | 🟡 | M | `compare.py` 已对 Godot 非零退出、超时、旧/缺截图、0 surface、缺动画自检和 NaN 作硬失败;CPU/GPU 像素差仍是 advisory。缺校准后的结构化像素 / 直方图门禁 + 可截图的 CI renderer。 |
| H4 | 扩展侧 dump 骨骼矩阵 / 顶点做对拍 | ⬜ | S | 现在数值对拍靠 libgr2 层的 `oracle_diff`。加 `MTGODOT_DUMP=<path>` 从扩展导出,闭合「mtgodot == Granny」全链。 |
| H5 | Android NDK 上构建 tools | ⬜ | S | `gr2dump` / `gr2fuzz` / `oracle_diff` 目前 macOS。 |
| H6 | 逐材质 oracle 对拍(纸面确认) | ⬜ | S | `material_textures` 已按渲染结果验(warrior/sura/assassin/shaman 全对),缺 `run-diff-suite.sh` 级别的逐材质数值确认。 |
| H7 | A1 地图格式 / 数值 / golden 回归 | ⏭ | M | 地图数据无外部 oracle(不像 `.gr2` 有 Granny DLL):移植 `Terrain.cpp`/`Area.cpp` 公式做自洽 + `minimap.dds` 目视 + 受控机位 golden。`formats/tests/map_formats_test.cpp` 进 CTest`world-a1-r1/r2.png``compare.py`。SHINSOO §11。 |
---
## I. libgr2 覆盖面(SM
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| I1 | >4 骨权重 / 8-bone-weight | ✅ | S | `libgr2::Mesh` 已记录源 `BoneWeights` 槽数,`gr2fuzz` 纳入直方图。9166 文件中的 3118 个 skinned mesh 全部声明 4 槽,当前语料无需 `ARRAY_FLAG_USE_8_BONE_WEIGHTS`。新资产若出现其它槽数会在报告中显现。 |
| I2 | 多骨架 `.gr2` | ⬜ | SM | libgr2 的 `extract_mesh` **会**按 BoneBindings 悬空最少给每个 mesh 选对骨架。缺口在桥接:`Metin2Model` 只把 `fi.skeletons[0]` 实例化成 `Skeleton3D` —— 绑到 `skeletons[1]`(武器 / 挂点骨架)的 mesh 会错绑。需按 mesh 的实际骨架建多个 Skeleton3D 或合并。 |
| I3 | 变形目标 / morph | ⬜ | S | libgr2 不解析 `granny_mesh.MorphTargets`。Metin2 用骨骼驱动面部,大概率 N/A —— 确认后标 ❌ 或补。 |
| I4 | 游戏相机 | 🟡 | M | 世界(W6):`project/world_demo.gd` 第三人称跟随(yaw/pitch/dist + 沿视线 `sample_height` 防穿)+ WASD + 贴地 + `attr.atr` 阻挡(逐轴)+ wait/walk/run 状态机 + NPC `Label3D`。⬜ 鼠标转视角交互壳、遮挡淡出、机位 preset;`SpringArm3D`/物理射线要完整覆盖建筑,先依赖 E13 的 `.mdatr`/静态对象碰撞体,当前地形碰撞只能解决地形防穿。SHINSOO §9-W6。 |
| I5 | 地图 / 全局 / Godot 坐标统一转换模块(`mtgodot::coord` | 🟡 | S | ✅ `formats/m2_coord.{h,cpp}` 纯数学核心:`position_to_godot` / `direction_to_godot` / `tile_id` / `tile_dir` / `chunk_origin_cm` / `height_raw_to_cm`,与 `make_conv``-90°X` + `0.01`)一致,CTest 覆盖。✅ `ypr_basis`Metin2 空间,D3D 端口)+ `object_basis_godot`(共轭到 Godot Y-up,放置用;修了漏轴转换 bug),CTest 覆盖。⬜ extension 侧 godot 薄封装。SHINSOO §6。 |
---
## 已明确不做(本 POC`GODOT-POC-PLAN.md` §01
UI / Python 脚本层、多角色 AI、网络、玩法逻辑、eterpack(Phase 1 用散文件 —— 但 G1 是正式移植必做)。
`.mse` 特效 / 地形 / 水 / SpeedTree 在 E 段标 `⏭`:正式移植必做、独立立项,但**不影响 Phase 1 中期评审结论**,也不阻塞 Phase 2 的三设备 bring-up。
---
## 排期
> 规模粗估、置信度低(见开头);下面的「周」是相对量级,不是承诺。
### Sprint 1 —— 小项收尾(~1 周)
已完成:`A3` · `A6` · `F8` · `I1`,并补完 `A9`/`A10` 正确性问题。
剩余:`A5`CPU 路径原地更新)· `A7`(前台 Metal stress 基线)· `A8`+`H1`(桥接层畸形用例与全语料 draw 冒烟)。
### Sprint 2 —— 组合基础(~2 周)
`A2` 已完成。剩余:`A1`(发型挂接,M)· `A4`(静态路径统一)· `I2`(多骨架排查)。
### Phase 2 —— 平台(三设备 bring-up,自用)
`F1``F7`:一加 13Vulkan+ iPhone 16Metal,静态链接)跑起来 + 三端一致 + 两台真机性能留档(旗舰基线)+ 生命周期 + 触屏相机。**无 Compatibility / 老机、无对外 go/no-go。**
### Phase 2 之后(正式移植,粗排,各阶段数周–数月)
1. **材质保真** `B1`(扩 libgr2 API)→ `B2``B11` · `H6`
2. **动画 + 组合** `C2`/`C3`/`C4`/`C5`/`C6` · `D1``D4`
3. **资产管线** `G1``G3` · `G5`(移动端纹理压缩)。
4. **独立子项目([`SHINSOO-WORLD-RENDERING.md`](./SHINSOO-WORLD-RENDERING.md)Phase 2 bring-up 之后)**
`G7`+`E11`+`E12`+`I5`W0 路径 / 格式 / 坐标)→ `E8`(地形 geometry+splat)→ `E13`Property 对象)→
`E10`(树)→ `E2`(环境)→ `I4`(相机)= R1;再 `E9`(水)+ `E7`(必要 `.mse`+ UI + `H7` = R2。
5. **打磨** `C8`IK)· `C9`LOD)· `E1`/`E2`/`E3`/`E5`(阴影 / 环境 / 后处理 / 透明排序)· `H3`CI)· `I4`(游戏相机)。