Files
mtgodot-poc/docs/BACKLOG.md
T
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

31 KiB
Raw Blame History

metin2-client — 未完成工作清单

更新:2026-08-29 · 配套 GODOT-POC-PLAN.md(计划)、MIDREVIEW.md(中期评审)、 SHINSOO-WORLD-RENDERING.md(世界渲染实施)、PARITY-GAP.md(距参考图的保真化差距)。

POC 的核心问题——Godot + 自研 loader 能否正确读 + 渲 + 播 Metin2 .gr2 骨骼模型—— 在 macOS 上已答「能」:数值对拍 Granny ≤6.5e-5warrior 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 §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 🟡 SM 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_aabbGPU 路径继续使用保守扩展盒。

B. 材质 / 观感保真(M2.5M;取代 GODOT-POC-PLAN §M2.5 T2.5.6

现状:贴图 = gr2 material 绑定(Mesh::material_textures[matidx]mesh-localuse_gr2_materials=true 默认) → 找不到时 _resolve_texture 文件名启发式(face/hair/stem 关键字 + 目录扫描)。blend / cull / alpha 全靠 guess_blend()表面名关键字猜。着色器 SRC_MIX / SRC_SKINskinned/ SRC_ADD / SRC_ADD_SKINadditive;静态与 GPU 蒙皮均覆盖)。

前置 B1libgr2 已有 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.cppEterGrnLib/ModelInstanceRender.cppEterGrnLib/Material.cpp 在目标资产上实际触发的 blend/alpha-test/sphere-map/多贴图分支,再扩 SRC_MIXGR2 不承载任意 D3D stage 状态,不按未经证实的 op 清单猜实现。
B3 alpha-test(镂空) 🟡 S hair 表面名 → AlphaTestB4 里 decide_blend 仍保留这条,GR2 材质 flag 不区分镂空 vs 不透明)。 改成贴图 alpha 分布判定;阈值硬编码 0.5,应取材质 D3DCMP_* ref。
B4 blend 模式取自材质 首版 S metin2_model decide_blend()优先 gr2::MaterialInfo.alpha_blenddump_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 顶点色 排查=不适用 全资产 .gr2PC/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_SKINspec_map/spec_power/spec_enable,复现 Material.cpp:305 __ApplySpecularRenderState:不透明面、相机空间 reflect().xyspheremap.jpgEMISSION += sphere*tex.a*power)。Metin2Model.specular_power 属性,默认 0 休眠。 装备层已接:project/ui/equip_model.gd:283-288 _apply_body()item_protospecular 列并 set("specular_power", sp / 100.0) 只驱动身体/护甲一个部位(武器 / 发型 / 时装未接),且 power 未按受控参考帧标定(PARITY §0 门禁)。SRC_ADD / SRC_ADD_SKIN 已覆盖 additive 发光的一种,且 additive 动画面不再漏蒙皮。
B10 背面剔除 / 双面 首版 S 世界静态对象(W3/B1)已按 two_sided + 贴图名切 CULL_DISABLED 角色也已按材质切:m2_material.cpp:188-211 shader_for()two_sidedcull_back replace 成 cull_disabledopaque/blend × 普通/蒙皮 四个变体各自缓存),metin2_model.cpp:1182(身体)/ :769(武器)经 decide_two_sided() 驱动,_load_hair() 直接置 true。 判据仍是启发式 —— gr2::MaterialInfo.two_sided 按材质名(2side/twoside/double/leaf/tree/ivy/fence+ 表面名兜底(hair/cloak/cape/skirt/ribbon/leaf),不是真 ExtendedData 的 "Two-sided"。
B11 winding 逐模型验 S flip_winding 默认 false(4 个骨架验过,未全量)。

C. 动画运行时(SL

ID 状态 规模 说明
C1 root motionAccumulation 🟡 S .msa 已解析、get_accumulation() 暴露;未作用到节点 transform。
C2 动画混合 / crossfade 🟡 M Metin2AnimPlayer.blend_time(默认 0.15s):切 anim_path 时旧 clip 冻结,逐骨对旧/新 world_pose slerp+lerpskin = 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 LoopDatacount/cancel/start/end)已解析并由 get_loop_data() 暴露,真实资产冒烟通过。剩余:按原客户端规则执行片段循环次数/取消窗口;整段 loop/once 仍需由动作注册层指定,不能从 LoopData 猜。
C5 挂点系统 🟡 M 武器挂手骨:Metin2Model::set_weapon_gr2() / set_weapon_bone()(默认 equip_right_hand),子 MeshInstance3D 每帧取 gr2::sample_poseworld_pose[bone]Metin2AnimPlayer::apply_pose 驱动;对齐客户端 GetBoneMatrixPointer)。 特效挂骨、左手/双持/盾、.mse trail。
C6 motion event 类型语义 🟡 首版 M game_scene._on_local_motion_event 分派(对齐 RaceMotionData EMotionEventType):有 sound_audio.play_at;有 effectfx.spawn_at(basename)EffectPositiontype 2 SCREEN_WAVINGcam.shaketype 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 距离切 meshlod_distances 默认 [18,42,90]m,重建 cpu_mesh+材质+蒙皮)。+ 淡入淡出:切档时把旧 mesh 快照进 LodGhost MeshInstance3D(冻结),shader lod_fade uniformmix/skin)在 ~0.18s 内旧 1→0 / 新 0→1LODController::BlendRenderWithOneTexture);距离阈值加 15% 迟滞防抖。 GPU skin 路径的 LOD、受控帧核对。

D. 模型组合(SL

ID 状态 规模 说明
D1 多部件装配(身体 + 武器 + 发型 + 时装 + 翅膀,共享骨架) L .msm + 物品系统。核心:多 MeshInstance3D 绑同一骨架 + 骨名重映射;部位遮挡 / 隐藏规则。
D2 武器模型(独立 .gr2 挂手骨) 🟡 M 右手单持跑通(见 C5;rigid mesh 分支 build_mesh 已验,warrior + 00040.gr2 idle/run 跟手,贴图按材质绑定名解析)。 左手/双持/盾、.msm/物品系统给路径、_lod 变体。
D3 贴图换色(SourceSkinTargetSkin 🟡 S 发型:Metin2Model.hair_skinTargetSkin dds 覆盖发型 albedo,对齐 SetMaterialImagePointer);get_hair_options() 返回绝对 source_skin/target_skinformats.msm_hair CTest。 护甲/时装部位的 skin 表。
D4 装备 / 时装换装系统 L 材质槽、部位互斥、隐藏规则。D1 的上层。

E. 渲染功能(SXL

E7E10(特效 / 地形 / 水 / 植被)+ 地图加载 / 环境 / 第三人称场景的完整实施规格见 SHINSOO-WORLD-RENDERING.mdW0W8 / R1 / R2,≈3.55.5 月起, Phase 2 三设备 bring-up 之后启动;自用,无正式门禁,W0 可提前并行)。对应的地图 / 环境 / 资产解析 工作项见下方 E11E13,以及 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。世界(W5extension/src/environment_builder.cpp.msenvSun(方向光)+ WorldEnvGradient 三段天空 / 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.cppParticleSystemData.cppEffectMesh.cppSimpleLightData.cpp 已有 parser/语义真值。W7 首版尚未端口。任务是把 Particle/Mesh/Light/时间事件和挂点映射到 Godot,不是逆向私有二进制;先做最终机位必要子集,不阻塞 Phase 2 bring-up。SHINSOO §9-W7。
E8 地形 🟡 XL 几何(W1):formats/terrain_mesh.cpp + Metin2WorldA1 20/20 ArrayMesh + UVsample_height 对拍 areadata z)。 splat 数据(W2):formats/splat.cpp 端口 RAW_GenerateSplatterrain_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 W7water_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 proxyformats/spt.cpp 嗅探 bark/composite atlastree_placeholder.cpp 使用真实 DDS、确定性枝干、多组交叉叶簇和风摆;按 treefile 缓存共享 mesh + MultiMeshInstance3DA1 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.cppmagic 2634/5426 + 尺寸校验,真值取自 Terrain.cpp+ area_data.cpp(含 areaproperty / areaambiencedata)。A1 全 20 区块 / 975 对象过 map_probeareadata position = 地图全局 cmW3 修正,SHINSOO §5.6)。
E13 Property CRC 注册表 + 五类对象实例化(Tree / Building / Effect / Ambience / DungeonBlock 🟡 L 注册表(formats/property.cppA1 1330 CRC136/136 命中)。 Building/DungeonBlockstatic_object.cpp get_static_mesh()(复用 gr2_bridge,跨实例共享 meshCULL_BACK + leaf/fence 关键字 alpha-scissor+ place_objects()A1 601 实例 / 119 mesh / 0 缺;位置=地图全局 cmShadowFlag / portal_ids meta / .mdatr 计数)。 TreeW4,见 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。2026-09-02 起第四个:Crypto++ 8.4.0vendored 源码,MT_PROTOCOL=classic 的 DH2/CTR cipher 用,公有领域许可)。别名 mt3p::sodium / mt3p::zstd / mt3p::minilzo / mt3p::cryptopp,无源码改动,macOS 9/9 CTest + 真实 item/mob_proto 解码逐字一致。详见 docs/THIRD-PARTY.md。miniLZO 是 GPL —— 自用可接受(同 oodle1.c),发布前须换。
F1 iOSiPhone 16 / Metal 🟡 M 交叉编译打通:./build-ios.shlibmtgodot.ios.template_{debug,release}.aarm64 / 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==AndroidSHARED .so + arm64 名字后缀);./build-android.sh 走 NDK 自带 android.toolchain.cmakearm64-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)、建筑 albedostatic_object)、树皮/树冠(tree_placeholder);splat 控制图 / 阴影图 / 水 / HUD 保持 RGBA8。macOS+iOS 编过,ctest 9/9,桌面渲染无变化,MTGODOT_TEXCOMP=1 角色渲染正常。 剩:一加 13 / iPhone 16 单 + N 角色 benchMTGODOT_GPUSKIN=1 人群)、构建成本(~170ms/角色,A5/G2)、移动端画质核对。
F5 App 生命周期 🟡 SM project/app_lifecycle.gdAppLifecycle 节点):把 NOTIFICATION_APPLICATION_PAUSED/RESUMED / FOCUS_IN/OUT / OS_MEMORY_WARNING / WM_GO_BACK_REQUEST 收成信号,默认动作 = 后台暂停树 + 停 BGM + 降 max_fps,前台还原;bind(m2client, audio) 一键接。M2ClientC++):_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 签名。自用,无商店上架。
F9 实体点选精度 首版 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.godotemulate_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):Metin2WorldPropertyRegistry / 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.cpplevel 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 进 CTestworld-a1-r1/r2.pngcompare.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_basisMetin2 空间,D3D 端口)+ object_basis_godot(共轭到 Godot Y-up,放置用;修了漏轴转换 bug),CTest 覆盖。 extension 侧 godot 薄封装。SHINSOO §6。

已明确不做(本 POCGODOT-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 · F8godot-cpp 钉版)· I1,并补完 A9/A10 正确性问题。 剩余:A5CPU 路径原地更新)· A7(前台 Metal stress 基线)· A8+H1(桥接层畸形用例与全语料 draw 冒烟)。

Sprint 2 —— 组合基础(~2 周)

A2 已完成。剩余:A1(发型挂接,M)· A4(静态路径统一)· I2(多骨架排查)。

Phase 2 —— 平台(三设备 bring-up,自用)

F1F7:一加 13Vulkan+ iPhone 16Metal,静态链接)跑起来 + 三端一致 + 两台真机性能留档(旗舰基线)+ 生命周期 + 触屏相机。无 Compatibility / 老机、无对外 go/no-go。

Phase 2 之后(正式移植,粗排,各阶段数周–数月)

  1. 材质保真 B1(扩 libgr2 API)→ B2B11 · H6
  2. 动画 + 组合 C2/C3/C4/C5/C6 · D1D4
  3. 资产管线 G1G3 · G5(移动端纹理压缩)。
  4. 独立子项目(SHINSOO-WORLD-RENDERING.mdPhase 2 bring-up 之后) G7+E11+E12+I5W0 路径 / 格式 / 坐标)→ E8(地形 geometry+splat)→ E13Property 对象)→ E10(树)→ E2(环境)→ I4(相机)= R1;再 E9(水)+ E7(必要 .mse+ UI + H7 = R2。
  5. 打磨 C8IK)· C9LOD)· E1/E2/E3/E5(阴影 / 环境 / 后处理 / 透明排序)· H3CI)· I4(游戏相机)。