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
206 lines
30 KiB
Markdown
206 lines
30 KiB
Markdown
# 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 收尾(S–M)
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 / 依赖 |
|
||
|---|---|---|---|---|
|
||
| A1 | 发型 mesh 实际挂接 | 🟡 | M | ✅ CPU 路径:`Metin2Model::set_hair_gr2()` + `_load_hair()` —— 加载发型 gr2,骨骼**按名重映射**到 base skeleton(warrior 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 pose,CPU/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.5,M;取代 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. 动画运行时(S–L)
|
||
|
||
| 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` → 屏闪 ColorRect;type 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` uniform(mix/skin)在 ~0.18s 内旧 1→0 / 新 0→1(`LODController::BlendRenderWithOneTexture`);距离阈值加 15% 迟滞防抖。⬜ GPU skin 路径的 LOD、受控帧核对。 |
|
||
|
||
---
|
||
|
||
## D. 模型组合(S–L)
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 |
|
||
|---|---|---|---|---|
|
||
| D1 | 多部件装配(身体 + 武器 + 发型 + 时装 + 翅膀,共享骨架) | ⬜ | L | `.msm` + 物品系统。核心:多 `MeshInstance3D` 绑同一骨架 + 骨名重映射;部位遮挡 / 隐藏规则。 |
|
||
| D2 | 武器模型(独立 `.gr2` 挂手骨) | 🟡 | M | ✅ 右手单持跑通(见 C5;rigid 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. 渲染功能(S–XL)
|
||
|
||
> `E7`–`E10`(特效 / 地形 / 水 / 植被)+ 地图加载 / 环境 / 第三人称场景的完整实施规格见
|
||
> [`SHINSOO-WORLD-RENDERING.md`](./SHINSOO-WORLD-RENDERING.md)(W0–W8 / R1 / R2,≈3.5–5.5 月起,
|
||
> Phase 2 三设备 bring-up 之后启动;自用,无正式门禁,W0 可提前并行)。对应的地图 / 环境 / 资产解析
|
||
> 工作项见下方 `E11`–`E13`,以及 `G7` / `H7` / `I5`。
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 |
|
||
|---|---|---|---|---|
|
||
| E1 | 阴影调优 | 🟡 | S–M | `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 | tonemap(Filmic)+ adjustment 调色 + glow(intensity 0.35)+ SSAO(radius 1.2)+ MSAA 4× 已开。⬜ 按受控参考帧标定曝光/对比/饱和/glow/SSAO;TAA/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`) | ⏭ | L–XL | 真实资产是 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` 过真实 A1(4×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 CRC,136/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-up:Mac / 一加 13 / iPhone 16,自用,M–L)
|
||
|
||
| 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 | iOS(iPhone 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。预留 2–3 天。详见 `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 导出 preset(Gradle)+ 一加 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 生命周期 | 🟡 | S–M | ✅ `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 smoke(suspend/resume/reconnect 绑定 + 幂等 + 信号)通过,ctest 9/9,iOS 编过。渲染上下文丢失由 Godot Vulkan 自恢复,GPU 资源全 RenderingServer 托管,无需处理。⬜ 剩真机验证后台 30s→掐连接→回前台重连、iOS 内存告警实际触发。 |
|
||
| F6 | 打包 | ⬜ | S–M | 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. 资产管线(S–M)
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 |
|
||
|---|---|---|---|---|
|
||
| G1 | eterpack 读取器 | 🟡 | M–L | ✅ **本 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` CLI(pack/unpack/list,跨平台 PackMaker 替代)。`pack.roundtrip` CTest + 真实 45 文件 `textureset/` 目录明文/加密往返 `diff` 一致(含 CP949 中文名 + 嵌套目录)。⬜ 接进 `AssetResolver`(从包解析而非散文件)+ mmap 大包。 |
|
||
| G2 | 跨实例资产缓存 / 去重 | 🟡 | M | 角色:每实例重建。世界(W8):`Metin2World` 的 `PropertyRegistry` / `AssetResolver` / `StaticMeshCache`(建筑 gr2→mesh,A1 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 通盘过 | ⬜ | S–M | 现在软解 RGBA8 上传,GPU 端不压缩;移动端显存 / 带宽要压(BC7 / ASTC)。 |
|
||
| G6 | DXT 软解补 level-N mip | 🟡 | S | mtgodot `dxt.cpp` 只解 level 0(xrender 版有完整 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. 测试 / 工程(S–M)
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 |
|
||
|---|---|---|---|---|
|
||
| H1 | 全语料过 Godot loader 的 fuzz | ⬜ | S–M | `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 覆盖面(S–M)
|
||
|
||
| ID | 项 | 状态 | 规模 | 说明 |
|
||
|---|---|---|---|---|
|
||
| I1 | >4 骨权重 / 8-bone-weight | ✅ | S | `libgr2::Mesh` 已记录源 `BoneWeights` 槽数,`gr2fuzz` 纳入直方图。9166 文件中的 3118 个 skinned mesh 全部声明 4 槽,当前语料无需 `ARRAY_FLAG_USE_8_BONE_WEIGHTS`。新资产若出现其它槽数会在报告中显现。 |
|
||
| I2 | 多骨架 `.gr2` | ⬜ | S–M | 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`:一加 13(Vulkan)+ iPhone 16(Metal,静态链接)跑起来 + 三端一致 + 两台真机性能留档(旗舰基线)+ 生命周期 + 触屏相机。**无 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`(游戏相机)。
|