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
+103 -51
View File
@@ -1,19 +1,28 @@
# mtgodot-poc
「**Godot 4 + 自研资源 loader**」跨平台方案的渲染 Demoroute ①)
用一个 C++ GDExtension 复用 [`xrender-poc`](../xrender-poc)`libgr2`,把 Metin2 的
`.gr2` 骨骼资源渲染进 Godot,用 Godot 内置渲染器。
「**Godot 4 + 自研资源 loader**」跨平台方案的渲染 Demo。
用一个 C++ GDExtension 把 Metin2`.gr2` 骨骼资源渲染进 Godot,用 Godot 内置渲染器。
`xrender-poc`(自研引擎 + bgfx RHI)是**两条并行的跨平台基座候选**,共用 `libgr2`
> **2026-08-29:并入 `xrender-poc`。** 原先与本仓库并行的 bgfx 自研引擎方案(`xrender-poc`
> 经中期评审([`docs/MIDREVIEW.md`](docs/MIDREVIEW.md)**停止开发**,其可复用部分——
> `libgr2`gr2 v6/v7 读取器,已对拍 Granny)、`formats`.msa/.msm)、`oracle`Granny 真值工具)、
> `tools`libgr2 校验工具)、`docs/reference`(格式逆向笔记)——已 **vendored 进本仓库**。
> bgfx 侧的渲染器 / 平台壳 / 第三方库全部弃用(Godot 取代)。bgfx demo 的参考截图留在
> `test/bgfx-reference/` 供交叉核对。`xrender-poc` 目录本身已删除(2026-08-30)。
判定分两阶段:
自用项目(内部研究,不对外发布),分两阶段推进
| 阶段 | 平台 | 里程碑 | 产出 |
|---|---|---|---|
| **Phase 1**(当前) | macOS | M0' · M1 · M2 · M2.5 | 中期评审(不下 go/no-go |
| Phase 2 | iOS · Android | M3 | go / no-go |
| **Phase 1**(当前) | macOS | M0' · M1 · M2 · M2.5 | 中期评审 |
| Phase 2 | Android(一加 13 / Vulkan)· iOSiPhone 16 / Metal | M3 | 三设备各一次 bring-up + 性能留档 |
完整计划见 [`docs/GODOT-POC-PLAN.md`](docs/GODOT-POC-PLAN.md)
自用、不对外发布,**无正式 go/no-go 门禁**;三台目标设备都是现代硬件,**全程不涉及 GLES3 / Compatibility 渲染器**,不做中低端 / 老机器
完整计划见 [`docs/GODOT-POC-PLAN.md`](docs/GODOT-POC-PLAN.md);离可用客户端还差什么见
[`docs/BACKLOG.md`](docs/BACKLOG.md)(分层未完成清单);把参考图那种整张地图画面做出来的实施规格见
[`docs/SHINSOO-WORLD-RENDERING.md`](docs/SHINSOO-WORLD-RENDERING.md)(正式移植,Phase 2 bring-up 之后);
距参考图「目视等价」还差哪些保真化工作见 [`docs/PARITY-GAP.md`](docs/PARITY-GAP.md)。
---
@@ -26,18 +35,26 @@
`test/golden/m1-bindpose.png`)。多骨架:`warrior_lord`(v6,74)、`assassin`(v6,95,17 表面)、
`shaman_lord`(**v7**,93) 均正确加载渲染。
- [x] **M2** — 骨骼动画。`Metin2AnimPlayer` `_process``gr2::sample_pose`(跨文件)
逐骨 `Skeleton3D.set_bone_pose`(局部),Godot GPU 蒙皮。`general/wait` 头 / 护甲
对齐正确(`test/golden/m2-wait.png`);`get_bone_global_pose` vs `conv(world)` 全骨 ≤2.6e-5
CPU-LBS 参考路径(`MTGODOT_CPUSKIN=1`)与 GPU 蒙皮一致;`selfcheck` 0 NaN
⚠️ `dance_1` 等表情动作头 / 颈塌陷 —— **libgr2 曲线解码 bugxrender-poc bgfx demo 同样复现**
(见 `docs/MIDREVIEW.md` §4)。
- [~] **M2.5** — 材质与观感保真。`ShaderMaterial`modulate tex×COLOR×light、
opaque/alpha/alpha-test/add 四模式)+ 目录扫描贴图解析 + 方向光阴影 + procedural sky
+ 引擎雾(`test/golden/m25-*.png`)。**门禁未闭**:多材质槽 → 贴图/混合的精确映射需
给 libgr2 加材质 API(见 `docs/GODOT-POC-PLAN.md` §M2.5 T2.5.6 差距清单)。
**默认 CPU 线性混合蒙皮**(把 libgr2 的 `Σ w·invWorld·world` 直接作用到顶点,每帧重建 mesh)。
`get_bone_global_pose` vs `conv(world)` 全骨 ≤2.6e-5`selfcheck` 0 NaN`dance_1` 等所有动作
头/护甲/躯干正确,与 xrender-poc bgfx demo 一致
`MTGODOT_GPUSKIN=1` 走**自写蒙皮顶点着色器**`m2_material` `SRC_SKIN`:逐骨完整 4×3 矩阵
存 RGBAF 纹理,绕过 `Skeleton3D` —— Godot 内置 GPU 蒙皮会正交化、丢 Granny 烘在 rig 骨上的
shearwarrior 7 根 / **sura 16 根**,见 `test/shear-bones-survey.md`)。人群场景用它。
**`.msa`**`anim_path``.msa` → motion `.gr2` + `Accumulation` + `MotionEventData`
`get_events()` / `motion_event` 信号,带循环回绕)+ `LoopData` 元数据;播放器 `loop=false`
可播一次并发 `playback_finished`(片段循环次数执行仍在 backlog C4)。
**`.msm`**`gr2_path``.msm` → 自动加载 `BaseModelFileName`(含散包目录扫描)+
`get_hair_options()` 发型目录(发型 mesh 挂接留 Phase 2)。
- [x] **M2.5** — 材质与观感保真。`ShaderMaterial`modulate tex×COLOR×light、
opaque/alpha/alpha-test/add 四模式)+ 方向光阴影 + procedural sky + 引擎雾。
**贴图走 gr2 material 绑定**`build_parts()``tri_groups[].material_index` 把一个 gr2 mesh
拆成多个 Godot surface,各 surface 取 `Mesh::material_textures[matidx]`mesh-local)→
同目录大小写不敏感查 `.dds`;找不到才回退文件名启发式。shaman(1→2 面)/assassin(17→18 面)
各面贴图正确。MODULATE2X 等 stage op 仍只近似(差距清单见 `docs/GODOT-POC-PLAN.md` §M2.5 T2.5.6)。
- [~] **中期评审** — [`docs/MIDREVIEW.md`](docs/MIDREVIEW.md)。结论:桥接层工程量小、
风险基本出清,**建议进入 Phase 2**。收尾 3 项:`compare.py` 对拍、`dump_materials`
Granny 对拍闭合 M2.5 门禁、前台/真机性能重测
风险基本出清,**建议进入 Phase 2**。`compare.py` 已有进程/截图/结构硬门禁,像素差仍为
advisory;收尾重点是前台/真机性能重测与桥接层全语料 draw 冒烟(默认已是 CPU LBS
50 角色压力采样:`test/godot-macos-stress.json`(后台窗口 30fps 节流,仅构建成本
~165ms/角色 和 100 角色破顶到 52ms 是可信信号)。
@@ -49,10 +66,10 @@
| 组件 | 版本 / 位置 | 说明 |
|---|---|---|
| Godot | **4.7.1**`/opt/homebrew/bin/godot`Homebrew cask | 编辑器 + headless |
| godot-cpp | submodule `extension/godot-cpp` @ `master`pin `101ae38` | master 默认 targets Godot 4.7 API`GODOTCPP_DEFAULT_API_VERSION=4.7`);无 `4.6/4.7` 分支,只有 bundled `extension_api-4-7.json` |
| godot-cpp | submodule `extension/godot-cpp` @ `101ae38034304346a46ea9ea84ae156d3e860496` | 精确 gitlink 锁定(`.gitmodules` 不跟踪移动分支);bundled `extension_api-4-7.json` |
| Xcode | 26.4.1 | macOS 构建 |
| CMake | 已装;**不需要 SCons** | godot-cpp 走 CMake 路径 |
| libgr2 | `../xrender-poc/libgr2`sibling-dir 引用) | 见下「libgr2 依赖」 |
| libgr2 / formats / oracle / tools | vendored 进本仓库 | 见下「vendored 库」 |
**导出模板**M0' T0.4 / 真正 export 才需要):编辑器内 `Editor → Manage Export Templates → Download`,或 `godot --headless --install-export-templates`。~600MB,版本须与编辑器一致(4.7.1)。
@@ -66,19 +83,38 @@ git submodule update --init --recursive # 拉 godot-cpp(首次)
./build.sh Release # template_release
```
首次会编译 godot-cpp(几分钟)。
首次会编译 godot-cpp(几分钟)。扩展构建自动带上 vendored `libgr2` + `formats`
只想构建 `libgr2` / `formats` / 校验工具(不碰 godot-cpp):
```bash
cmake -B build-tools -DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_TOOLS=ON
cmake --build build-tools -j8
./build-tools/tools/gr2fuzz "$PWD/assets" # 全量解析冒烟
./build-tools/tools/gr2dump <file.gr2>
```
## 运行
```bash
# 完整客户端:登录 → 选人 → 进游戏(用仓库内 assets/)
./run-client.command # 或双击
# = MT_ASSETS=$PWD/assets godot --path project res://client_main.tscn
# 打包 .app(需先装 Godot 4.7.1 macOS 导出模板)
./build-macos-client.sh [debug|release] # -> build/export/mtgodot-poc.app
# 编辑器打开
godot -e --path project
# 模型查看器 harness(旧 main_scene
godot --path project res://main.tscn
# headless 冒烟测试(验证 GDExtension 加载 + 类注册)
godot --headless --path project --quit-after 3
# 跑一个真实 gr2 过 libgr2(可选)
MTGODOT_PROBE_GR2="$PWD/../m2dev-client-main/assets/PC/ymir work/pc/warrior/warrior_cheongrin.gr2" \
MTGODOT_PROBE_GR2="$PWD/assets/PC/ymir work/pc/warrior/warrior_cheongrin.gr2" \
godot --headless --path project --quit-after 3
```
@@ -88,22 +124,23 @@ MTGODOT_PROBE_GR2="$PWD/../m2dev-client-main/assets/PC/ymir work/pc/warrior/warr
---
## libgr2 依赖
## vendored 库(并入自 xrender-poc2026-08-29
M0' 直接用 **sibling-directory 引用**`extension/CMakeLists.txt`
`XRENDER_POC_DIR` 默认 `../../xrender-poc``add_subdirectory``libgr2`
两个 repo 都在 `.../mt/` 下时开箱即用。
| 目录 | 内容 | 谁用 |
|---|---|---|
| `libgr2/` | gr2 v6/v7 只读读取器(header/section/Oodle1/类型树/骨架/网格/曲线/材质)。零第三方依赖。9166 语料 fuzz 0 崩溃;bind pose + 蒙皮对拍 Granny 2.9.12 ≤6.5e-5。含 `dump_materials()`。 | `extension``xrender::libgr2`)、`tools` |
| `formats/` | `.msa`(动作)/ `.msm`(模型 + 发型)/ textscript 解析。 | M2 T2.2/T2.3 起(retarget + motion event |
| `oracle/` | Wine + MinGW 交叉编译的 Granny 真值工具(`oracle.c` + 脚本 + `RUNBOOK.md`)。`oracle.exe` / `granny2_x64.dll` 不入库(体积 + RAD IP),`build-wine.sh` 本地重建。 | M2 数值门禁、M2.5 材质对拍 |
| `tools/` | `gr2dump`(转储)/ `gr2fuzz`(全量解析冒烟)/ `oracle_diff`libgr2 vs oracle 逐字段对拍)/ `anim_probe`(每骨 world 3×3 的 det/shear/scale 探针,查蒙皮走样)。 | libgr2 回归 |
| `docs/reference/` | xrender-poc 的格式逆向笔记(M0M4 施工文档 + PLAN)。见 `docs/reference/README.md`。 | 查格式细节 |
| `test/bgfx-reference/` | bgfx demo 的参考截图 + `noise_floor.json` / `m2-numeric.json` / `assets.list` 基线。 | 与 Godot 输出交叉核对 |
换机 / CI 时二选一:
- `git submodule add <xrender-poc url> third_party/xrender-poc` 后传 `-DXRENDER_POC_DIR=third_party/xrender-poc`
- vendor 一份 `libgr2/` 进本仓库
CMake:顶层 `add_subdirectory(libgr2)` + `add_subdirectory(formats)`(跨平台,不碰 godot-cpp);
`extension` `target_link_libraries(... xrender::libgr2)``tools`
`-DMTGODOT_BUILD_TOOLS=ON` 开启。
> ⚠️ 本仓库依赖 `../xrender-poc/libgr2` 里新增的 `dump_materials()`
> `src/gr2_material.cpp` + `include/gr2/gr2.h` + `CMakeLists.txt` 三处,纯附加)
> 该改动目前只在 xrender-poc 工作树里,**未提交**(那个仓库无提交历史,保持原样)。
> 需要时由 xrender-poc 的维护者按其流程纳入。
`formats/`msa/msm)到 **M2** 才需要,届时同样方式接入。
> `libgr2/README.md` 里的 **oodle1.c clean-room 提醒**依然适用:`src/oodle1.c` 是泄露 SDK 端口,
> 仅限内部研究 / 非发布 / 非商用;对外发布前必须 clean-room 重写
---
@@ -111,27 +148,42 @@ M0' 直接用 **sibling-directory 引用**`extension/CMakeLists.txt` 里
```
mtgodot-poc/
CMakeLists.txt 顶层macOS only 守卫)
build.sh 便捷构建
CMakeLists.txt 顶层add_subdirectory(libgr2/formats/[tools]/[extension])
build.sh 便捷构建(扩展)
libgr2/ ← vendoredgr2 读取器(xrender::libgr2
formats/ ← vendoredmsa/msm/textscript
oracle/ ← vendoredGranny 真值工具(exe/dll 不入库)
tools/ ← vendoredgr2dump / gr2fuzz / oracle_diff
extension/
CMakeLists.txt mtgodot SHARED + godot-cpp + libgr2
godot-cpp/ submodule @ master
CMakeLists.txt mtgodot SHARED + godot-cppconsume xrender::libgr2
godot-cpp/ submodule @ 101ae38034304346a46ea9ea84ae156d3e860496
src/
register_types.{h,cpp} GDExtension 入口,注册 Metin2Model
metin2_model.{h,cpp} M0' 占位节点(证明类注册 + libgr2 链接)
register_types.{h,cpp} GDExtension 入口
metin2_model.{h,cpp} gr2 → Skeleton3D + ArrayMesh + Skin + 材质
metin2_anim.{h,cpp} 每帧 sample_pose → Skeleton3Dselfcheck
gr2_bridge.{h,cpp} libgr2 POD → Godot 类型;basis + 单位换算
m2_material.{h,cpp} Metin2 风格 ShaderMaterialmix / add
dxt.{h,cpp} DDS DXT1/3/5 软解
assets/ Metin2 资产(.gr2/.dds/locale/OutdoorA1/…,gitignored;换位置设 MT_ASSETS
bgm/ 背景音乐(assets/ 同级,gitignored
project/ Godot 4.7 工程
project.godot
main.tscn 根 Node3D + main.gd
main.gd M0' harness:建 env/light/cam/cube + 探测扩展
assets/.gdignore 让 Godot 不 import .gr2/.dds
bin/mtgodot.gdextension 扩展描述符(dylib 构建产物落这里)
export_presets.cfg macOS preset
client_main.tscn/.gd 完整客户端入口(main_scene)→ AppFlow 登录串场
app_flow.gd LOGIN → SELECT → GAME 状态机
main.tscn / main.gd 旧模型查看器 harness
asset_root.gd AssetRoot:资源目录解析(MT_ASSETS / res://../assets / .app 内外)
docs/
GODOT-POC-PLAN.md 开发计划(M0'M4 + 风险 + 验收)
steps/ (按需)
test/golden/ 对拍截图(M1 起)
MIDREVIEW.md Phase 1 中期评审
BACKLOG.md 离可用客户端的分层未完成清单
reference/ ← xrender-poc 的格式逆向笔记(archived
test/
golden/ Godot 对拍截图
bgfx-reference/ bgfx demo 参考截图 + 数值基线
*.json noise_floor / m2-numeric / stress
```
## 授权
`libgr2` 全自研,不含 Granny SDK 代码。本仓库内部研究用途,不对外公开。
`libgr2` 全自研,不含 Granny SDK 代码 —— 例外:`libgr2/src/oodle1.c` 是泄露 SDK 的解码路径端口
(见 `libgr2/README.md`,仅限内部研究 / 非发布,对外发布前必须 clean-room 重写)。
本仓库内部研究用途,不对外公开。