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
+128
View File
@@ -0,0 +1,128 @@
# M3 · iOS + Android 真机
> 总纲:[`../PLAN.md`](../PLAN.md) §04 / §06 / §07。
> 本文件是 M3 的详细施工文档。与 [M2](./M2-anim-skinning.md) 尾段并行。
---
## 目标
把 [M2](./M2-anim-skinning.md) 的工程原样交叉编译到 iOS + Android 真机跑基准场景,
证明三端渲染一致、移动端性能达标、生命周期(context loss / 后台)稳。
## 前置
- [M2](./M2-anim-skinning.md) 门禁 1–2 通过(骨骼矩阵 + 蒙皮顶点对拍)。
- [M1](./M1-static-render.md) T0b 的交接结论(决定移动端窗口壳用什么)。
- **资产上真机的方案**(见下 T1)—— 不是"原样交叉编译"就完事。
## 交付物
| 产物 | 位置 |
|---|---|
| iOS `.app`Xcode / CMake iOS toolchain | `platform/ios/` |
| Android APKNDK / Gradle 或纯 CMake + `native_app_glue` | `platform/android/` |
| 三端 CImacOS 原生 + iOS 模拟器 + Android 模拟器) | `.github/` 或等价 |
| 真机性能采集报告 | `test/m3-perf.json` |
| 三端快照互拍报告 | `test/m3-snapshot.json` |
---
## 任务分解
### T1 · 资产上真机方案(PLAN §04 / §08
- 9166 个散装 `.gr2` + `.dds` 不能直接堆进 APK / `.bundle`(体积、iOS 限制)。二选一:
- **A(推荐)**M3 前提前接 `EterPack`(现排 M4),资产走 `.eix/.epk``CMappedFile` 的 mmap / `AAsset_getBuffer` 路径。
- **B(最小)**:只把 `test/assets.list` 里那 ~15 个 + 依赖贴图打进去。
- **完成判据**:真机上 `libgr2` 能加载 warrior + dance_1,路径与桌面散文件结果一致。
### T2 · 平台壳
- **窗口 / 生命周期**:按 M1 T0b 结论——sokol_app(若交接成了)或各平台最小原生壳(iOS `MTKView` + `CADisplayLink`Android `NativeActivity` / `GLSurfaceView`)。
- **iOS**:全静态链接(无 `dlopen`);`.bundle` 打包资产;`Info.plist` / 签名走开发证书(不涉及上架)。
- **Android**NDK `arm64-v8a` + `armeabi-v7a``AAssetManager` 经 JNI 注入(复用 MobileSource `AndroidMain.cpp` 形态)。
- bgfx `init.type`iOS = `Metal`Android = `OpenGLES`(或 `Vulkan`,先 GLES3 稳)。
### T3 · Android context lossPLAN §08 高危,确定项)
- 切后台 → GL context 连同 GPU 资源可能失效。
-`SUSPENDED` / `RESUMED`(或 `onSurfaceDestroyed/Created`)里按 bgfx 的重置流程:`bgfx::reset` + 必要时重建 `sg_*` / bgfx 资源句柄。
- **测试**:切后台 ×100`adb shell input keyevent KEYCODE_HOME` + 回前台脚本循环)。
- **完成判据**:100 次无崩、无黑屏、无资源泄漏(`adb shell dumpsys meminfo` 稳定)。
### T4 · iOS 后台 / drawable 生命周期(PLAN §10.1
- 切后台 / 锁屏 / 来电 → 丢 `CAMetalDrawable`
- `applicationWillResignActive` 暂停渲染循环;`didBecomeActive` 恢复;`nextDrawable` 返回 nil 时跳过该帧不崩。
- **完成判据**:后台 / 锁屏 / 来电各 ×20 恢复正常。
### T5 · mediump 精度专项(PLAN §08 / §10.1
- 移动 GPU 上 `mediump` 存不下 60 骨链的骨骼矩阵 / 大坐标 → 抖动 / 爆顶点。
- 顶点着色器里骨骼矩阵与位置强制 `highp`;必要时把模型原点归一(减去包围盒中心)。
- **测试**:highp 前后对比截图,确认抖动 / 爆顶点消失。
- **完成判据**:highp 版无可见抖动。
### T6 · 性能采集(PLAN §07 性能层)
- **基准硬件写死具体机型**iPhone 11A13+ Pixel 6Mali-G78+ 一台 Adreno 机。
- **两种角色**:① 简化(≈5k 三角、≈60 骨、单 draw call);② **满配**(多部件 + 武器 + 时装 + 挂点特效,1–2 万三角、多 draw call)。每种 1 → 8 → 20 → 50 个。
- 指标:帧时间 p50/p99、FPS、draw call、GPU 显存(Xcode GPU report / Android GPU Inspector / `adb dumpsys meminfo`)、冷启动到首帧、单 `.gr2` 加载耗时、峰值 RSS。
- 写进 `test/m3-perf.json`,逐项标注是否越线。
### T7 · 三端快照互拍 + CI
- 确定性场景在 macOS / iOS / Android 各渲一帧,两两 SSIM。
- 三端之间应比各自与 oracle 更接近(同 `shaderc` 源、同逻辑)。
- CIiOS 模拟器 + Android 模拟器构建并启动,断言"到达首帧 + 连续 N 帧无 bgfx / Metal / GLES 验证层报错"(开 `BGFX_DEBUG_*`、Metal API validation、GLES `KHR_debug`)。
---
## 门禁(go / no-go
- 三端渲染**差异分类通过**(不是"逐像素一致",见 PLAN §07 视觉层:SSIM ≥ 0.98 直接过;0.950.98 进分类;< 0.95 fail)。
- 性能:简化角色 1 个 ≥ 60fps、20 个 ≥ 30fps**满配角色 1 个 ≥ 60fps、8 个 ≥ 30fps**;冷启动 < 3s;基准 RSS < 300MB;单角色加载 < 30ms。
- 无 bgfx / 图形验证层报错。
- **Android 切后台 ×100 恢复正常**;**iOS 后台 / 锁屏 / 来电恢复正常**。
- **mediump 精度专项通过**highp 后无抖动)。
## 验证
- 三端快照互拍矩阵(T7)。
- 真机性能逐项对通过线(T6)。
- 生命周期压力测试脚本可复现(T3 / T4)。
## 本步风险(从 PLAN §08 筛)
| 风险 | 缓解 |
|---|---|
| Android GL context 丢失后 GPU 资源需重建(确定项) | T3 在 suspend/resume 走 bgfx resetM3 就做别拖 |
| iOS 全静态 + 后台丢 drawable | T2/T4 全静态链接 + drawable nil 跳帧 |
| 9166 散装资产上真机方式没定 | T1 二选一,M3 交付显式包含 |
| 移动 GPU mediump 精度不足 | T5 骨骼矩阵 / 位置用 highp |
| `shaderc` 跨编译 metal / 300_es 行为差异 | 单一 `.sc` 源;开验证层;T7 三端快照对拍 |
| 深度范围 / Y 翻转后端差异 | `bgfx::getCaps()->homogeneousDepth` / `originBottomLeft` 抹平 |
| sokol_app 交接在 iOS/Android 也不成 | 用各平台最小原生壳(T2 备选) |
## DoD 清单
- [~] iOS **模拟器**跑起来,播 dance_1CPU 蒙皮 + bgfx Metal,输出与 macOS 同 t **像素级一致**(同 `.bin` 着色器、同 `demo_core`)。`test/golden/m3-ios-*.png`。**真机**(差异分类、后台/锁屏/来电、mediump、perf)待设备。
- [ ] Android 真机 —— **脚手架就绪(`platform/android/`)未构建**:本机无 NDK。
- [ ] `test/m3-perf.json` —— 待真机
- [ ] Android 切后台 ×100 —— 待设备(`android_main.cpp``APP_CMD_TERM/INIT_WINDOW` 分支已写 shutdown/reinit
- [ ] iOS 后台 / 锁屏 / 来电 —— 待设备(`ios_main.mm``applicationWillResignActive`/`DidBecomeActive` 暂停/恢复 CADisplayLink 已写)
- [ ] mediump → highp 专项 —— `vs_pnt_skinned.sc` 已写(骨骼矩阵 + 位置全 `highp`);对比测试待真机 GPU
- [ ] CI 三端 job —— 待
- [ ] **M3 全绿 = PLAN §01 判定"方案可行"** —— iOS 一端已验证(编译 + 运行 + 渲染一致);Android 端 + 真机压测待设备
## M3 实现结果(本轮)
| 项 | 结果 |
|---|---|
| **平台无关核心** | `app/demo_core.{h,cpp}` —— 加载 gr2 + 建场景 + 采样姿势 + 画,无窗口/输入依赖。桌面壳(`app/main.cpp` GLFW+ iOS 壳(`platform/ios/ios_main.mm` UIKit+ Android 壳(`platform/android/android_main.cpp` native_app_glue)都调它。 |
| **iOS 壳(T2** | `ios_main.mm``UIApplicationMain``MetalView``+layerClass = CAMetalLayer`)→ `CADisplayLink``demo::frame`。bgfx `nwh = CAMetalLayer*``init.type = Metal`。生命周期回调(T4 骨架)已接。 |
| **iOS 构建 + 运行** | `cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator`**BUILD SUCCEEDED**`xcrun simctl` 装 + 跑 iPhone 16 Pro 模拟器:libgr2 加载 warrior + dance_1、DXT3/DXT1 解码、bgfx Metal init、CADisplayLink 跑 120 帧无崩、`requestScreenShot` 出图。渲染结果与桌面同 t 一致。 |
| **交叉编译坑** | ① `BGFX_CONFIG_VIDEO` 默认 ON`video_mtl.cpp` 在 iOS 模拟器编不过(`CVMetalTextureCache` 不可用)→ 关掉。② shaderc 跑不了目标平台 → `xrender_compile_shaders(... PREBUILT <dir>)` 用宿主机预编译的 `metal .bin`Metal 字节码 macOS/iOS 通用)。③ iOS 不建 GLFW`third_party/CMakeLists.txt``NOT IOS` 守卫)。 |
| **Android 壳(T2** | `platform/android/``android_main.cpp`native_app_glue + `ANativeWindow` → bgfx GLES nwh + `AAssetManager` 解资产)、`CMakeLists.txt`NDK,链 `xr_democore` + bgfx GLES)、`build.gradle` + `AndroidManifest.xml``NativeActivity`)。**未构建**(本机无 NDK`platform/android/README.md` 记了装 NDK 后的步骤)。 |
| **T5 GPU 蒙皮 shader** | `app/shaders/vs_pnt_skinned.sc``u_bones[64]` 混合,骨骼矩阵 + 位置 + 法线全 `highp`。**未接进 demo**demo 走 CPU 蒙皮 = M2 数值门禁路径),留 M4 CPU/GPU 一致性 + 真机 mediump 对比。 |