layout: restructure into src/ tests/ android/ scripts/ tools/
- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
(+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten
No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -1,16 +1,16 @@
|
||||
# Android 原生客户端测试
|
||||
|
||||
当前 APK 由 `build-android-native.sh` 构建,使用 SDL3 + Vulkan;应用包名 `org.metin2port.client`,ABI 为 `arm64-v8a`,最低 API 24。
|
||||
当前 APK 由 `android/build.sh` 构建,使用 SDL3 + Vulkan;应用包名 `org.metin2port.client`,ABI 为 `arm64-v8a`,最低 API 24。
|
||||
|
||||
## 安装和资源
|
||||
|
||||
```sh
|
||||
./build-android-native.sh Release --install
|
||||
./push-android-client.sh /path/to/40250/Client
|
||||
./android/build.sh Release --install
|
||||
./android/push-client.sh /path/to/40250/Client
|
||||
adb shell am start -n org.metin2port.client/.MainActivity
|
||||
```
|
||||
|
||||
`push-android-client.sh` 将 40250 `Client` 目录复制到应用私有目录。APK 本身包含 `libmain.so`、`libSDL3.so`、着色器和 Python 2.7 标准库,不包含约 1.4 GB 的 40250 客户端资源。使用中文资源时运行 `./push-android-client.sh --locale zh`。
|
||||
`android/push-client.sh` 将 40250 `Client` 目录复制到应用私有目录。APK 本身包含 `libmain.so`、`libSDL3.so`、着色器和 Python 2.7 标准库,不包含约 1.4 GB 的 40250 客户端资源。使用中文资源时运行 `./android/push-client.sh --locale zh`。
|
||||
|
||||
## 连服务器
|
||||
|
||||
@@ -21,4 +21,4 @@ adb shell am start -n org.metin2port.client/.MainActivity \
|
||||
|
||||
默认不传参数时连接进程内假服。测试真服前确认模拟器/设备能访问相应主机与端口。用 `adb logcat` 查看崩溃和启动错误。`adb devices -l` 没有设备时,先连接模拟器或开启真机 USB 调试。
|
||||
|
||||
更详细的构建依赖与命令见 [native_render/README.md](../native_render/README.md)。
|
||||
更详细的构建依赖与命令见 [src/host/README.md](../src/host/README.md)。
|
||||
|
||||
+17
-17
@@ -260,7 +260,7 @@
|
||||
switch(`FUNC_WAIT` 1.0 阈值、`FUNC_COMBO/ATTACK/MOB_SKILL` 50.0 阈值 + `m_kMovAfterFunc`
|
||||
到达后动作、`skip_collision`);释放门按 `m_server_frame_ms`(新 `set_server_frame_ms`,
|
||||
为 0 时立即释放 = 旧行为)+ `__CanProcessNetworkStatePacket`(dead / knock_down hold
|
||||
队列)。`tick()` 每帧先 `process_states()`。新增 `extension/tests/net_state_queue_test.cpp`;
|
||||
队列)。`tick()` 每帧先 `process_states()`。新增 `tests/port/net_state_queue_test.cpp`;
|
||||
`net_entity_test` 的 `FUNC_ATTACK` 断言按新语义改写;C++ 20/20 + GDScript 回归全绿。
|
||||
留给增量 78:net_play.gd 上行 6 事件回调 + 节流常量、net_world.gd 去掉自有 lerp、
|
||||
从 `GC_HANDSHAKE` `lDelta` 接真实服务器帧钟。
|
||||
@@ -884,7 +884,7 @@
|
||||
| 工作流 | 负责条目 | 主要修改范围 | 不得越界修改 |
|
||||
|---|---|---|---|
|
||||
| W0 公共基础 / 集成 | §0、共享会话、实体接口、场景生命周期、文档 | `extension/src/net/m2_client.*`、`entity_store.*`、`classic_session.*`、`project/game_scene.gd`、两份差距文档 | 其他工作流不得各自复制网络核心或场景 reset;接口改动必须回 W0 |
|
||||
| W1 协议 / 账号 | §1、Classic wire / parser、登录、选人、DirectEnter、断线 | `extension/src/net/classic/*`、`extension/tests/net_classic_*`、登录 / 选人专属脚本 | 不直接重写 W0 的公共信号和 EntityStore 生命周期 |
|
||||
| W1 协议 / 账号 | §1、Classic wire / parser、登录、选人、DirectEnter、断线 | `extension/src/net/classic/*`、`tests/port/net_classic_*`、登录 / 选人专属脚本 | 不直接重写 W0 的公共信号和 EntityStore 生命周期 |
|
||||
| W2 实体 / 世界 / 战斗 | §2–§3、移动、动作、技能、目标和换图后的实体表现 | `project/net_world.gd`、`net_play.gd`、`player_controller.gd`、`project/world/*`、动画 / 战斗扩展和测试 | 不把 UI 状态写入协议层;共享 reset 只通过 W0 接口接入 |
|
||||
| W3 玩法 UI / 输入 | §4–§5、§8、背包、任务、社交、IME、键位 | `project/ui/*`、`hud.gd`、`login.gd`、`charselect.gd`、UI 测试和本地化资源 | 不修改 Classic parser;缺少真服时使用 fixture,并注明未验证 |
|
||||
| W4 渲染 / 资产 / 音频 | §6–§7、§9、模型、特效、地图、材质、天气、BGM、生命周期表现 | `extension/src/{gr2_bridge,metin2_world,terrain_splat,m2_material,environment_builder}.*`、`project/fx/*`、音频 / 渲染专属脚本和测试 | 不把渲染 fallback 塞进网络状态机;场景钩子由 W0 提供 |
|
||||
@@ -1392,14 +1392,14 @@ return true;
|
||||
作为 follow-up。
|
||||
3. ⬜ **不在本批次**:`charselect.gd` / `guild_creation_ui.gd` 的名字长度校验统一为
|
||||
`<= 24` 字节属 GDScript(W3 / Phase 2);本批次只交付 C++ 侧编码。已在下方登记。
|
||||
4. ✅ 回归:新增 `extension/tests/net_text_codec_test.cpp`(`ctest` `net.text_codec`),
|
||||
4. ✅ 回归:新增 `tests/port/net_text_codec_test.cpp`(`ctest` `net.text_codec`),
|
||||
覆盖 CJK 名恰好 24 字节(接受)、25/27 字节(截断到 24,绝不 25/26)、
|
||||
多字节边界截断(切进 3 字节序列中间丢整字)、ASCII 往返、`from_wire_str` 遇 NUL 停 /
|
||||
悬空前导字节裁除 / 无终止符满字段整读。`extension/CMakeLists.txt` 注册。
|
||||
|
||||
**W1 增量 50 已落地(🔎 待真实验收)**
|
||||
|
||||
- 改动:`extension/src/net/text_codec.h`(新)、`extension/tests/net_text_codec_test.cpp`(新)、
|
||||
- 改动:`extension/src/net/text_codec.h`(新)、`tests/port/net_text_codec_test.cpp`(新)、
|
||||
`wire_classic.h`(include)、`classic_session.cpp`(8 处 to_wire 路由)、
|
||||
`classic_parser.cpp`(ADDITIONAL_INFO from_wire_str)、`extension/CMakeLists.txt`(测试注册)。
|
||||
- 测试:`ctest` `net.text_codec` 通过(新增,套件 16 → 17);`net.classic_session` 通过。
|
||||
@@ -1966,7 +1966,7 @@ affect 图标与剩余时间没有统一状态。
|
||||
时 hold 队列不丢包)。走路时长优先用服务器 `dur_ms`(≈ `SNetworkActorData::UpdatePosition`
|
||||
的插值窗口),缺省才按 `dist / moving_speed` 推。
|
||||
- `tick()` 每帧先 `process_states()` 再推进插值。
|
||||
- 回归:新增 `extension/tests/net_state_queue_test.cpp`(帧钟释放门、帧钟 0 立即释放、
|
||||
- 回归:新增 `tests/port/net_state_queue_test.cpp`(帧钟释放门、帧钟 0 立即释放、
|
||||
`FUNC_WAIT` 1.0 阈值、`FUNC_COMBO` 50.0 阈值 + 到达后动作、knock_down / dead hold 门、
|
||||
多条到期命令一次排空按序);`net_entity_test` 的 `FUNC_ATTACK` 断言按新语义改写
|
||||
(近距 <50 瞬移+动作、远距 ≥50 先走后打)。C++ 20/20(含新 `net.state_queue`)、
|
||||
@@ -2717,7 +2717,7 @@ main->IsFishingMode() → OnCannotUseSkill("REMOVE_FISHING
|
||||
隐式转换后再截断。
|
||||
3. ✅ 按参考语义保留必要特殊值:`set_target(0)` 清目标、`shop_sell(count=0)` 选择旧版单件包、
|
||||
`move_item(count=0)` 保留整堆语义;没有用“把负数改成 1”掩盖调用方错误。
|
||||
4. ✅ 回归:新增 `extension/tests/net_bounds_test.cpp` / CTest `net.bounds`,覆盖每个共享边界的
|
||||
4. ✅ 回归:新增 `tests/port/net_bounds_test.cpp` / CTest `net.bounds`,覆盖每个共享边界的
|
||||
端点与越界值;`cmake --build build/extension -j2` 通过,`ctest --test-dir build
|
||||
--output-on-failure` 为 18/18。相关 Godot 离线回归(`netbridge_test`、`p8_test`、
|
||||
`shop_cube_mall_test`、`guild_refine_test`、`dragon_soul_test`、`guild_mark_test`、
|
||||
@@ -5594,7 +5594,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
project/ui/shop_ui.gd —— 实例 / 原型 `ANTIFLAG_SELL`、`IsValuableItem` 首次确认、
|
||||
普通 / `Is1GoldItem` 售价公式和失败音效;
|
||||
project/ui/private_shop_ui.gd —— `ANTIFLAG_GIVE/MYSHOP` 拦截;
|
||||
extension/tests/proto_item_layout_test.cpp、project/shop_rules_test.gd —— 新增布局 / 出售规则回归。
|
||||
tests/port/proto_item_layout_test.cpp、project/shop_rules_test.gd —— 新增布局 / 出售规则回归。
|
||||
测试:`cmake --build build/extension -j2`、`ctest --test-dir build/extension --output-on-failure`;
|
||||
`inventory_ui_test.gd`、`mouse_controller_test.gd`、`p8_test.gd`、`shop_rules_test.gd`、
|
||||
`private_shop_ui_test.gd`、`complex_item_drop_test.gd` 均通过。
|
||||
@@ -5636,7 +5636,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
对齐武器攻击 / 魔攻、防具防御公式、限制、applies、属性、魂石孔和精炼关联;
|
||||
project/ui/inventory_ui.gd、view_equipment_ui.gd、shop_ui.gd、safebox_ui.gd、mall_ui.gd、
|
||||
exchange_ui.gd、private_shop_ui.gd —— 接入同一个提示构造器;
|
||||
extension/tests/proto_item_layout_test.cpp、project/item_tooltip_test.gd —— 锁定布局和提示内容。
|
||||
tests/port/proto_item_layout_test.cpp、project/item_tooltip_test.gd —— 锁定布局和提示内容。
|
||||
测试:`cmake --build build/extension -j2`;C++ CTest 全套(首次 `net.classic_encstream` 瞬时失败,
|
||||
定向重跑通过,需在集成前再重跑全套);`inventory_ui_test.gd`、`item_tooltip_test.gd` 通过。
|
||||
验收:未验证——鱼竿 / 镐经验、龙魂计时、技能书 / 变身特殊分支、装备对比、CJK / 真 `.sub`、
|
||||
@@ -5758,7 +5758,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
extension/src/net/net_bounds.h(新)——窄整数、快捷栏 / 商店 / 私人商店 / 交易 / Cube / 龙魂 / 点数组边界
|
||||
extension/src/net/m2_client.cpp —— 连接端口、角色创建、聊天 / 技能 / 社交 / 商店 / 仓库 / 私人商店 /
|
||||
Cube / 公会 / 精炼 / 物品操作在 static_cast 前统一拒绝溢出和非法 Variant;移除静默截断 / 负数归一化
|
||||
extension/tests/net_bounds_test.cpp(新)、extension/CMakeLists.txt —— CTest net.bounds
|
||||
tests/port/net_bounds_test.cpp(新)、extension/CMakeLists.txt —— CTest net.bounds
|
||||
测试:cmake --build build/extension -j2 通过;ctest --test-dir build --output-on-failure → 18/18;
|
||||
Godot 离线回归按独立 --log-file 串行执行,受影响脚本均 exit 0 且输出 PASS。无新增 skip。
|
||||
验收:未验证——无真实 40250 服务端的完整业务包回执;Godot 测试中的 macOS 证书探测 / 资源释放提示
|
||||
@@ -5814,7 +5814,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
其余 npclist 命名 + mut_spawn);GC_CHAR_ADDITIONAL_INFO(无 pending → m_last_error +
|
||||
drop;命中 → 合并 9 字段 + mut_spawn_full + erase);ADDITIONAL_INFO 名字读 from_wire_str
|
||||
extension/CMakeLists.txt —— string(TIMESTAMP)→MT_BUILD_TIMESTAMP 定义;net.text_codec 测试注册
|
||||
extension/tests/net_text_codec_test.cpp(新)、net_classic_session_test.cpp(版本断言 +
|
||||
tests/port/net_text_codec_test.cpp(新)、net_classic_session_test.cpp(版本断言 +
|
||||
两包合并断言重写)
|
||||
接口/数据结构变化:ClassicSession +2 setter +2 成员;ClassicParser +2 public 方法
|
||||
+1 private 方法 +3 私有成员(m_npclist_path / m_npc_names / m_npc_names_loaded);
|
||||
@@ -5857,7 +5857,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
Entity 创建,find-or-return;mut_char_update() guild/alignment/pk_mode 变化也推 {Info};
|
||||
+mut_affect_flags(vid,lo,hi)(v=lo|(hi<<32),变化则 m_dirty);+匿名 dbg_ignored 助手
|
||||
extension/src/net/entity_store.h —— +mut_affect_flags 声明
|
||||
extension/tests/net_entity_test.cpp —— +§2.1 数据层断言块(远端 ADD 填行+入队 Spawn、
|
||||
tests/port/net_entity_test.cpp —— +§2.1 数据层断言块(远端 ADD 填行+入队 Spawn、
|
||||
UPDATE 改写、未知 VID 五个 mut_* 既不建行也不缓存)
|
||||
project/net_world.gd —— +CHAR_STAGE_VIEW_BOUND/AFFECT_SHOW_ALWAYS/WALL_RACES 常量;
|
||||
+_update_visibility()(每帧 __IsVisibleActor 判定,建/淡出节点,_fading 防抖);
|
||||
@@ -5983,7 +5983,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
project/ui/quest_dialog.gd —— 识别 COLOR/COLOR256、ITEM/MOB、WINDOW_SIZE、居中、CONFIRM_WAIT、
|
||||
IMAGE/TITLE_IMAGE 元数据和 DELAY/WAIT;game_scene.gd 传入 proto / assets_root;
|
||||
project/ui/friend_ui.gd —— 确认框答复后主动隐藏,避免连续模态框残留;
|
||||
extension/tests/net_entity_test.cpp、net_classic_session_test.cpp、project/quest_test.gd ——
|
||||
tests/port/net_entity_test.cpp、net_classic_session_test.cpp、project/quest_test.gd ——
|
||||
交易 sockets/attrs、任务标签、任务时钟回归。
|
||||
测试:`cmake --build build/extension -j2` 通过;`ctest --test-dir build/extension -R
|
||||
'net.entity|net.classic_session' --output-on-failure` 2/2 通过;`p8_test.gd`、`item_tooltip_test.gd`、
|
||||
@@ -6452,10 +6452,10 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
duration, arg, time)`。
|
||||
extension/src/net/classic/classic_parser.cpp —— HDR_GC_MOVE:`mut_move(vid,
|
||||
rot*5.0f, func, x, y, duration, arg, time)`。
|
||||
extension/tests/net_entity_test.cpp —— 旧 FUNC_ATTACK 贴齐单测拆成「近」(dir_len
|
||||
tests/port/net_entity_test.cpp —— 旧 FUNC_ATTACK 贴齐单测拆成「近」(dir_len
|
||||
30<50 → 贴齐 + `func==FUNC_ATTACK`)+「远」(dir_len 170>=50 → 先 `func==FUNC_MOVE`
|
||||
走,`tick()` 后到达 Dst + `func==FUNC_ATTACK`)。
|
||||
extension/tests/net_state_queue_test.cpp(新增,5 组)——① 服务器帧钟释放门(未来
|
||||
tests/port/net_state_queue_test.cpp(新增,5 组)——① 服务器帧钟释放门(未来
|
||||
chk_time 挂队;帧钟追上才释放;帧钟 0 立即释放);② FUNC_WAIT 1.0 阈值(贴齐 vs 走 +
|
||||
after WAIT + skip_collision);③ FUNC_COMBO 50.0 阈值 + 到达后动作 + arg 锁存;
|
||||
④ `__CanProcessNetworkStatePacket` 门(knock_down / dead 挂队,恢复即释放;未知 vid 不崩);
|
||||
@@ -6557,11 +6557,11 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
· `drain_state_queue` 帧钟门改为:`m_server_frame_ms != 0 && chk_time >
|
||||
m_server_frame_ms && chk_time - m_server_frame_ms <= STATE_QUEUE_MAX_WAIT_MS`
|
||||
才 break;超过 cap 的领先量当异帧钟 `dwTime`,立即放行。
|
||||
extension/tests/net_classic_stream_test.cpp ——
|
||||
tests/port/net_classic_stream_test.cpp ——
|
||||
· 新增「server frame clock (§3.2)」段:握手前 `server_frame_ms()==0` +
|
||||
`!has_server_clock()`;喂一个 `hs.time=500000, delta=20` 的握手后
|
||||
`has_server_clock()` 且帧钟 ∈ [500020, 505020);连读两次单调不减。
|
||||
extension/tests/net_state_queue_test.cpp ——
|
||||
tests/port/net_state_queue_test.cpp ——
|
||||
· 新增第 6 段:`set_server_frame_ms(1000)`,`chk_time == 1000+cap` 仍 gated;
|
||||
另一实体 `chk_time == 1000+cap+1` 立即放行(防呆阀边界)。
|
||||
测试:`ctest --test-dir build` → 20/20(`net.classic_stream` / `net.state_queue` 覆盖新逻辑);
|
||||
@@ -7090,7 +7090,7 @@ dx² + dy² > s_fLimitDistance → 不播
|
||||
extension/src/net/m2_client.cpp —— use_skill(int skill_id, int target_vid)(形参增量 52 起即带符号)
|
||||
护栏:skill_id <= 0 || !bounds::u32(skill_id) → !mtnet::bounds::skill_index(skill_id)
|
||||
(target_vid 仍 >= 0 && bounds::u32;cast_skill() 的 motion_idx ∈ [0,0x7f] 不动)
|
||||
extension/tests/net_bounds_test.cpp —— +4 CHECK(SKILL_MAX_NUM==255 / 端点 1、254 /
|
||||
tests/port/net_bounds_test.cpp —— +4 CHECK(SKILL_MAX_NUM==255 / 端点 1、254 /
|
||||
越界 0、255、-1 / 拒 0x7fffffff+1)
|
||||
project/ui/skill_table.gd ——
|
||||
· _load_cooltime → _load_msk(调用点 line 152),+解析 .msk NeedSPFormula → entry["need_sp_formula"]
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
| 工作流 | 负责范围 | 主文件边界 | 依赖 / 集成点 |
|
||||
|---|---|---|---|
|
||||
| W0 公共基础与集成 | 连接阶段、共享网络状态、实体公共接口、场景生命周期、文档 | `extension/src/net/m2_client.*`、`entity_store.*`、`classic_session.*`、`project/game_scene.gd`、本目录两份差距文档 | 先冻结信号、状态结构、reset / enter / warp 接口;其他工作流不得同时重写这些共享文件 |
|
||||
| W1 协议与账号会话 | §1、Classic wire / parser、登录 / 选人 / DirectEnter / 断线、协议错误 | `extension/src/net/classic/*`、对应 `extension/tests/net_classic_*`、登录和选人专属脚本 | 依赖 W0 的会话接口;用 loopback / mock 验证,真实服务端留在集成门禁 |
|
||||
| W1 协议与账号会话 | §1、Classic wire / parser、登录 / 选人 / DirectEnter / 断线、协议错误 | `extension/src/net/classic/*`、对应 `tests/port/net_classic_*`、登录和选人专属脚本 | 依赖 W0 的会话接口;用 loopback / mock 验证,真实服务端留在集成门禁 |
|
||||
| W2 实体、世界与战斗 | §2–§3、实体两层模型、移动、动作、技能、目标和换图后的世界状态 | `project/net_world.gd`、`net_play.gd`、`player_controller.gd`、`project/world/*`、动画 / 战斗相关扩展及测试 | 依赖 W0 的 EntityStore / `world_reset`;UI 只消费公开信号,不直接修改网络核心 |
|
||||
| W3 玩法 UI、输入与本地化 | §4–§5、§8、背包 / 快捷栏 / 任务 / 社交 / 输入 / IME / 键位 | `project/ui/*`、`hud.gd`、`login.gd`、`charselect.gd`、UI 测试和本地化资源 | 依赖稳定的 M2Client 信号和数据快照;缺少服务端时使用 fixture,不阻塞 W1/W2 |
|
||||
| W4 渲染、资产与音频 | §6–§7、§9、GR2 / EffectLib / SpeedTree、地图、材质、天气、BGM、生命周期表现 | `extension/src/{gr2_bridge,metin2_world,terrain_splat,m2_material,environment_builder}.*`、`project/fx/*`、音频和渲染专属脚本 / 测试 | 依赖 W0 的场景 reset / 地图切换钩子;不得把渲染占位逻辑写入协议层 |
|
||||
@@ -739,7 +739,7 @@
|
||||
> (`m_server_frame_ms == 0` = 帧钟未对齐 → 立即释放 = 退回增量 76 时序,无回归)+
|
||||
> `__CanProcessNetworkStatePacket`(`dead` / `knock_down` 时挂队不应用,恢复即释放)。
|
||||
> `SNetworkActorData::UpdatePosition` 式线性插值只保留给 §2.1 可见性、不再直接写节点位置的
|
||||
> 工作留给增量 78。新增 `extension/tests/net_state_queue_test.cpp`(5 组:帧钟释放门 / FUNC_WAIT
|
||||
> 工作留给增量 78。新增 `tests/port/net_state_queue_test.cpp`(5 组:帧钟释放门 / FUNC_WAIT
|
||||
> 1.0 阈值 / FUNC_COMBO 50.0 阈值 + 到达后动作 / knock_down·dead 门 / 多条到期按序 drain);
|
||||
> `net_entity_test` 的 FUNC_ATTACK 断言按新语义拆成近(贴齐)/ 远(先走后打)两例。
|
||||
> `ctest --test-dir build` → 20/20(新增 `net.state_queue`);`netbridge_test` /
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
### 3. 屏幕刷新率(ProMotion 120Hz)与 40250 逻辑时钟(60 FPS)失步
|
||||
* **代码位置**:
|
||||
- `project/project.godot`(未配置 `max_fps`)
|
||||
- `extension/src/port/EterBase/Timer.cpp:109-116`
|
||||
- `src/port/EterBase/Timer.cpp:109-116`
|
||||
* **机制危害**:
|
||||
- MacBook 屏幕通常为 120Hz ProMotion。当前 `project.godot` 没有设置帧率上限,Godot 默认以 120 FPS 运行 `_process`。
|
||||
- 这导致上述所有 C++ 内存封包、显存频繁重建、线程唤醒**以每秒 120 次的频率翻倍运行**。
|
||||
@@ -99,7 +99,7 @@
|
||||
---
|
||||
|
||||
### 5. 双线程每帧条件变量往返切换(Ping-Pong Jitter)
|
||||
* **代码位置**:`extension/src/platform/ScriptLib/PythonBoot.cpp:597, 612-616`
|
||||
* **代码位置**:`src/platform/ScriptLib/PythonBoot.cpp:597, 612-616`
|
||||
* **机制危害**:
|
||||
Godot 主线程与 Python Script 线程通过 `g_fiber.cv.notify_all()` 与 `cv.wait()` 互斥锁交替执行。主线程渲染完必须挂起等待 Script 线程完成 `Process()`,若 macOS 系统调度器将其中一个线程调度到了能效核(E-Core)或与其他系统后台任务竞争,会造成帧间隔不均匀(Frame Time Jitter)。
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
|
||||
### 独立 Vulkan/MoltenVK 原型(2026-09-25)
|
||||
|
||||
新增 `native_render/`:SDL3 创建独立窗口,Vulkan 创建交换链、顶点缓冲、管线与逐 draw 提交;macOS 通过 MoltenVK 映射到 Metal。它直接读取录制设备的 `Render3DDraw` 结构,也能回放 Godot 客户端显式捕获的一帧。构建和回放命令见 `native_render/README.md`。现有 Godot 客户端运行路径未切换。
|
||||
新增 `src/host/`:SDL3 创建独立窗口,Vulkan 创建交换链、顶点缓冲、管线与逐 draw 提交;macOS 通过 MoltenVK 映射到 Metal。它直接读取录制设备的 `Render3DDraw` 结构,也能回放 Godot 客户端显式捕获的一帧。构建和回放命令见 `src/host/README.md`。现有 Godot 客户端运行路径未切换。
|
||||
|
||||
#### 第一阶段:基础提交与每帧 CPU 展开基线
|
||||
Apple M4 上,合成的 64 draw × 333 三角形(63,936 个展开顶点)运行 60 帧,平均帧间隔 16.64 ms,CPU 几何准备 6.80 ms。真实 64 怪测试捕获的一帧(`64-mob.mtdr`,v1)为 51 draw、51,231 个索引;回放 60 帧,平均帧间隔 16.81 ms,CPU 几何准备 5.96 ms。
|
||||
@@ -217,7 +217,7 @@ Apple M4 上,合成的 64 draw × 333 三角形(63,936 个展开顶点)运
|
||||
|
||||
#### 第三阶段:深度缓冲、DDS 贴图、法线/UV 与 D3D8 固定管线状态(Version 3 捕获)
|
||||
1. **深度与固定管线状态**:新增 `VK_FORMAT_D32_SFLOAT` 深度附件,按 `(cull_mode, z_enable/z_write, alpha_blend/dest_blend)` 缓存 `VkPipeline`,在 `native.vert` / `native.frag`(128 字节 Push Constants,满足桌面与 Android Vulkan 最小保证)中实现 D3D8 方向光(`light0` + 环境光 + 自发光)、`texture_factor` 调制与 `alpha_test` 裁剪。
|
||||
2. **自包含 DDS 贴图回放**:捕获格式升级为 Version 3(向下兼容 v1/v2),在 `MT_NATIVE_CAPTURE_PATH` 触发时自动从 40250 pack 提取被引用的 `.dds` 原始字节写入 `.mtdr`;`mt_native_render` 复用 `extension/src/dxt.cpp` 在首帧软解 DXT1/3/5 并上传至 `VK_IMAGE_TILING_OPTIMAL` 纹理与描述符集。
|
||||
2. **自包含 DDS 贴图回放**:捕获格式升级为 Version 3(向下兼容 v1/v2),在 `MT_NATIVE_CAPTURE_PATH` 触发时自动从 40250 pack 提取被引用的 `.dds` 原始字节写入 `.mtdr`;`mt_native_render` 复用 `src/host/dxt.cpp` 在首帧软解 DXT1/3/5 并上传至 `VK_IMAGE_TILING_OPTIMAL` 纹理与描述符集。
|
||||
3. **修复无 `MT_PROFILE_FRAME` 时 64 怪测试 `dog_screen=(-1,-1)` 失败的根因**:
|
||||
- 根因一:`CPythonNetworkStream::GamePhase()` 每帧最多消费 3 个网络包,64 怪场景的进图突发包(约 147 个包,含末尾 `GC_PING`)需要约 49 帧排空;此前未开启 `MT_PROFILE_FRAME` 时仅等待 30 帧就点击地面移动,导致客户端在回复 `CG_PONG`(254)前先发出了 `CG_CHARACTER_MOVE`(7),伪造服务端报 `expected CG header 254, got 7` 并主动断开连接清空角色。
|
||||
- 根因二:`settled` 闭包未检查 `now.x >= 0.0`,连续两帧 `(-1, -1)` 距离为 `0.0 < 0.5` 会提前退出等待。
|
||||
@@ -237,16 +237,16 @@ Apple M4 上,合成的 64 draw × 333 三角形(63,936 个展开顶点)运
|
||||
#### 第四阶段:硬件 GPU 时间戳、40250 地形 HTP、2D UI/字形/小地图遮罩、GPU 骨骼蒙皮与实时游戏主循环接入(Version 4 捕获)
|
||||
|
||||
1. **分离真实 GPU 执行耗时(`gpu_ms`)与垂直同步等待(`submit_ms`)**:
|
||||
- 在 `native_render/main.cpp` 中接入 `VkQueryPool`(`VK_QUERY_TYPE_TIMESTAMP`),在 `vkCmdBeginRenderPass`(`TOP_OF_PIPE`)与 `vkCmdEndRenderPass`(`BOTTOM_OF_PIPE`)记录硬件时间戳并换算为毫秒级 `gpu_ms`。
|
||||
- 在 `src/host/main.cpp` 中接入 `VkQueryPool`(`VK_QUERY_TYPE_TIMESTAMP`),在 `vkCmdBeginRenderPass`(`TOP_OF_PIPE`)与 `vkCmdEndRenderPass`(`BOTTOM_OF_PIPE`)记录硬件时间戳并换算为毫秒级 `gpu_ms`。
|
||||
- 新增 `--no-vsync`(优先选用 `VK_PRESENT_MODE_IMMEDIATE_KHR` / `MAILBOX_KHR`),将交换链垂直同步等待从 `submit_ms` 中剥离。
|
||||
2. **40250 原生硬件变换地形块(HTP)渲染**:
|
||||
- 在 `extension/src/platform/GameLib/MapOutdoorRenderHTP.cpp` 中实现 `CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch()` 与 `__HardwareTransformPatch_RenderPatchSplat(...)`(由 `SetNativeTerrainRenderEnabled(true)` 或 `MT_NATIVE_TERRAIN=1` 启用,不影响 Godot 默认测试路径)。
|
||||
- 在 `extension/src/platform/EterLib/RecordingDevice.cpp` 中支持 D3D8 `D3DTSS_TCI_CAMERASPACEPOSITION` 相机空间纹理矩阵生成地形基础层与 Alpha Splat 层 UV,并将 `m_textures[0]` 混入 `geometry_key` 实现跨帧持久化缓存。
|
||||
- 在 `src/platform/GameLib/MapOutdoorRenderHTP.cpp` 中实现 `CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch()` 与 `__HardwareTransformPatch_RenderPatchSplat(...)`(由 `SetNativeTerrainRenderEnabled(true)` 或 `MT_NATIVE_TERRAIN=1` 启用,不影响 Godot 默认测试路径)。
|
||||
- 在 `src/platform/EterLib/RecordingDevice.cpp` 中支持 D3D8 `D3DTSS_TCI_CAMERASPACEPOSITION` 相机空间纹理矩阵生成地形基础层与 Alpha Splat 层 UV,并将 `m_textures[0]` 混入 `geometry_key` 实现跨帧持久化缓存。
|
||||
3. **2D `UIRenderCommand` 全量渲染(含字形图集与小地图圆角遮罩)**:
|
||||
- 支持 `behind_3d` 与前景两趟正交 2D 渲染,涵盖 `Bar`、`GradientBar`、`Line`、`Image`、裁剪矩形(`clip_x1..y2`)、`mem:<id>@<revision>` 动态字形图集(`"MTRA"` 原始 RGBA 编码)、`.tga`(含未压缩与 RLE)解码,以及 `CPythonMiniMap` 的双纹理(`tex0` + `mask_tex`)圆角遮罩混合。
|
||||
- 相邻同状态、同纹理的 UI 图元与字形自动合并为单次 `vkCmdDrawIndexed` 批次提交(64 怪场景 1,597 个 UI/字形四边形成批为 31 个 Vulkan 批次)。
|
||||
4. **GPU 线性混合骨骼蒙皮(GPU Skeletal Skinning)**:
|
||||
- 在 `extension/src/platform/EterGrnLib/GrannyRuntime.cpp` 中,当启用 GPU 蒙皮(`--gpu-skinning` 或 `MT_GPU_SKINNING=1`)时,`GrannyDeformVertices` 不再逐帧在 CPU 上对每个顶点做矩阵加权变形,而是按不可变的源网格指针(`SourceVertices`)提取一次静态绑定姿态(Bind-Pose)顶点、法线、4 骨骼索引(`bone_indices`)与 4 骨骼权重(`bone_weights`),仅将当前实例的骨骼矩阵调色板(`bone_count * 16` 个 `float`)写入切片表。
|
||||
- 在 `src/platform/EterGrnLib/GrannyRuntime.cpp` 中,当启用 GPU 蒙皮(`--gpu-skinning` 或 `MT_GPU_SKINNING=1`)时,`GrannyDeformVertices` 不再逐帧在 CPU 上对每个顶点做矩阵加权变形,而是按不可变的源网格指针(`SourceVertices`)提取一次静态绑定姿态(Bind-Pose)顶点、法线、4 骨骼索引(`bone_indices`)与 4 骨骼权重(`bone_weights`),仅将当前实例的骨骼矩阵调色板(`bone_count * 16` 个 `float`)写入切片表。
|
||||
- 在 `RecordingDevice.cpp` 中,所有共享同一 `.gr2` 源网格的怪物/角色实例共享同一个 `geometry_key` 且 `geometry_revision` 恒为 `1`;`native.vert` 通过 `set = 1, binding = 0` 的 `BonePalette` SSBO 在顶点着色器中完成 4 骨骼线性混合蒙皮。
|
||||
5. **实时游戏主循环直接接入(`--live-client`)**:
|
||||
- 当 `mt_native_render` 在主构建目录 `build/` 下构建时,直接链接 `port_platform`、`mtpython` 与 `FakeLoginServer`,通过 `--live-client <ClientDir>` 在原生 SDL3 + Vulkan 窗口内直接启动 40250 `system.py`,完成登录、选人、进图、刷出 1~64 只怪物、转发 SDL3 键鼠事件,并可导出包含 3D 骨骼权重、地形、2D UI 与字形页的 Version 4 `.mtdr` 捕获文件。
|
||||
@@ -277,13 +277,13 @@ Apple M4 上,合成的 64 draw × 333 三角形(63,936 个展开顶点)运
|
||||
- 支持 `SDL_WINDOW_RESIZABLE` 与 `VK_ERROR_OUT_OF_DATE_KHR` / `VK_SUBOPTIMAL_KHR` 交换链自动重建,并同步调用 `PythonBoot::SetUISize`。
|
||||
- 接入 macOS `ImageIO` 解码器,支持从 40250 pack 直接解码 `.jpg` / `.png` / `.bmp` 登录背景与 UI 贴图;并在未显式设置 `VK_ICD_FILENAMES` 时自动探测 Homebrew `MoltenVK_icd.json`。
|
||||
2. **补齐剩余 3D 场景视觉要素**:
|
||||
- **水面(Water)**:移植 `extension/src/platform/GameLib/MapOutdoorWater.cpp`(`CMapOutdoor::RenderWater` 与 `DrawWater`),支持 30 帧循环水面贴图、相机空间水面变换矩阵与基于高度差的顶点 Alpha 混合。
|
||||
- **天空盒与动态云层(Skybox & Clouds)**:移植 `extension/src/platform/EterLib/SkyBox.cpp`(`CSkyObjectQuad`、`CSkyObject`、`CSkyBox::Render`、`CSkyBox::RenderCloud`),并在 `RecordingDevice.cpp` 中支持 Stage 0 二维纹理矩阵变换(`D3DTTFF_COUNT2`)与 `D3DTOP_MODULATEINVALPHA_ADDCOLOR`(19)云层着色模式。
|
||||
- **水面(Water)**:移植 `src/platform/GameLib/MapOutdoorWater.cpp`(`CMapOutdoor::RenderWater` 与 `DrawWater`),支持 30 帧循环水面贴图、相机空间水面变换矩阵与基于高度差的顶点 Alpha 混合。
|
||||
- **天空盒与动态云层(Skybox & Clouds)**:移植 `src/platform/EterLib/SkyBox.cpp`(`CSkyObjectQuad`、`CSkyObject`、`CSkyBox::Render`、`CSkyBox::RenderCloud`),并在 `RecordingDevice.cpp` 中支持 Stage 0 二维纹理矩阵变换(`D3DTTFF_COUNT2`)与 `D3DTOP_MODULATEINVALPHA_ADDCOLOR`(19)云层着色模式。
|
||||
- **SpeedTree 森林与树木(SpeedTreeLib)**:实现 `SpeedTreeForest.cpp`、`SpeedTreeForestDirectX8.cpp` 与 `SpeedTreeWrapper.cpp`,读取 40250 `.spt` 属性与 `"TreeSize"` 参数生成带树皮贴图与树叶交叉十字网格的 Z-up 顶点/索引缓冲并参与渲染。
|
||||
- **多级纹理高光(Specular Sphere-Map)与完整混合因子**:在 `PythonApplication.cpp` 中实现 `CGrannyMaterial::CreateSphereMap` 与 `TranslateSpecularMatrix`,在 `native.vert` / `native.frag` 中实现球面环境映射 UV 生成与 `D3DTOP_MODULATEALPHA_ADDCOLOR`(18)高光叠加,并补齐完整 D3D8 `src_blend` / `dest_blend` 因子映射。
|
||||
- **修复退出纯虚函数崩溃**:移除基类析构函数 `CSkyObject::~CSkyObject()` 对纯虚函数 `Destroy()` 的调用,以及 `CSpeedTreeForest::~CSpeedTreeForest()` 对纯虚函数 `Clear()` 的调用(移入派生类 `CSpeedTreeForestDirectX8::~CSpeedTreeForestDirectX8()`),解决客户端退出时 `libc++abi: Pure virtual function called!` 崩溃。
|
||||
3. **音频播放接入(SDL3 Audio + AudioToolbox)**:
|
||||
- 在 `native_render/main.cpp` 中实现 `NativeAudioEngine`,每帧排空 `DrainAudioCommands()`,通过 macOS `AudioToolbox`(`AudioFileOpenWithCallbacks` + `ExtAudioFileRead`)直接从内存解包 `.wav` / `.mp3` 为 44.1 kHz 双声道浮点 PCM,送入 `SDL_AudioStream` 完成 2D/3D 音效混音与 BGM 淡入淡出循环播放。
|
||||
- 在 `src/host/main.cpp` 中实现 `NativeAudioEngine`,每帧排空 `DrainAudioCommands()`,通过 macOS `AudioToolbox`(`AudioFileOpenWithCallbacks` + `ExtAudioFileRead`)直接从内存解包 `.wav` / `.mp3` 为 44.1 kHz 双声道浮点 PCM,送入 `SDL_AudioStream` 完成 2D/3D 音效混音与 BGM 淡入淡出循环播放。
|
||||
4. **完整键盘/IME 文本输入与原生软件光标**:
|
||||
- 补齐 `F1`–`F12`、`Tab`、`Backspace`、`Return`、`Delete`、`Shift`/`Ctrl`/`Alt`、方向键、数字小键盘及符号键的 `SDL_Scancode -> DIK_*` 与 `VK_*`(`PythonBoot::UIIMEKeyDown`)映射,接入 `SDL_EVENT_TEXT_INPUT` 转发 UTF-8 字符至 `PythonBoot::UIChar`。
|
||||
- 启用 40250 `CURSOR_MODE_SOFTWARE`(`OnMouseUpdate` / `OnMouseRender`),当游戏软件光标激活时自动隐藏系统光标并渲染 40250 原生彩色游戏光标。
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
# 平台构建
|
||||
|
||||
当前客户端使用 SDL3 + Vulkan 原生渲染,目标平台是 macOS arm64 和 Android arm64。核心 40250 移植代码在 `extension/src/port` 与 `extension/src/platform`,与 UI 引擎无关。
|
||||
当前客户端使用 SDL3 + Vulkan 原生渲染,目标平台是 macOS arm64 和 Android arm64。核心 40250 移植代码在 `src/port` 与 `src/platform`,与 UI 引擎无关。
|
||||
|
||||
| 平台 | 构建入口 | 产物 |
|
||||
| --- | --- | --- |
|
||||
| macOS | `cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release -DMT_BUILD_NATIVE_RENDER=ON -DCMAKE_PREFIX_PATH=/opt/homebrew`,然后 `cmake --build build-release --target mt_native_render -j8` | `build-release/native_render/mt_native_render` |
|
||||
| Android | `./build-android-native.sh Release` | `android-native/app/build/outputs/apk/debug/app-debug.apk` |
|
||||
| macOS | `cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release -DMT_BUILD_NATIVE_RENDER=ON -DCMAKE_PREFIX_PATH=/opt/homebrew`,然后 `cmake --build build/release --target mt_native_render -j8` | `build/release/src/host/mt_native_render` |
|
||||
| Android | `./android/build.sh Release` | `android/app/build/outputs/apk/debug/app-debug.apk` |
|
||||
|
||||
macOS 的 Vulkan loader 通过 MoltenVK 使用 Metal。Android APK 使用 SDLActivity 和 `libmain.so`。40250 `Client` 资源在 macOS 通过 `--live-client` 指定,在 Android 通过 `push-android-client.sh` 放进应用目录。
|
||||
macOS 的 Vulkan loader 通过 MoltenVK 使用 Metal。Android APK 使用 SDLActivity 和 `libmain.so`。40250 `Client` 资源在 macOS 通过 `--live-client` 指定,在 Android 通过 `android/push-client.sh` 放进应用目录。
|
||||
|
||||
原生客户端的详细配置与验收步骤见 [native_render/README.md](../native_render/README.md) 和 [ANDROID-TESTING.md](ANDROID-TESTING.md)。
|
||||
原生客户端的详细配置与验收步骤见 [src/host/README.md](../src/host/README.md) 和 [ANDROID-TESTING.md](ANDROID-TESTING.md)。
|
||||
|
||||
+33
-33
@@ -2,15 +2,15 @@
|
||||
|
||||
本文是移植工作的总入口,换一台电脑继续开发时先读这份。更新日期:2026-09-29。
|
||||
|
||||
**当前架构**:原生 SDL3 + Vulkan 客户端(`native_render/`),macOS 上经 MoltenVK 走 Metal,Android 上是 SDLActivity
|
||||
外壳加原生 `libmain.so`。40250 的 C++ 逻辑逐文件照抄在 `extension/src/port/`,平台类实现在 `extension/src/platform/`,
|
||||
**当前架构**:原生 SDL3 + Vulkan 客户端(`src/host/`),macOS 上经 MoltenVK 走 Metal,Android 上是 SDLActivity
|
||||
外壳加原生 `libmain.so`。40250 的 C++ 逻辑逐文件照抄在 `src/port/`,平台类实现在 `src/platform/`,
|
||||
40250 的 `root/*.py`、`uiscript/` 由内嵌 CPython 2.7.18 原样运行,资源直接读 40250 的 EPK。运行时不依赖 Godot;
|
||||
Godot 时期(2026-09-22 至 09-27)的批次记录已移到 `docs/archive/PORT-HISTORY.md`。
|
||||
|
||||
- 方法细则:`.agents/skills/metin2-40250-parity-audit/SKILL.md`(每轮的做法、状态定义、工具)
|
||||
- 批次队列:`audit/remediation-roadmap.md`
|
||||
- 逐函数进度:`audit/port-map/**`;每轮一行记录:`audit/history.jsonl`
|
||||
- 构建与启动:根目录 `README.md`、`native_render/README.md`;Android 真机测试:`docs/ANDROID-TESTING.md`
|
||||
- 构建与启动:根目录 `README.md`、`src/host/README.md`;Android 真机测试:`docs/ANDROID-TESTING.md`
|
||||
- 渲染差距与门禁:`docs/PARITY-GAP.md`;中文语言包:`docs/ZH-LOCALE.md`;移动端 UI:`docs/MOBILE-UI-IMPLEMENTATION.md`
|
||||
- Python UI 评估:`docs/PYTHON-EMBED-EVAL.md`
|
||||
- 旧方案 `docs/CLIENT-REFACTOR-40250-PLAN.md`("形式上不照搬类文件")已被本文取代
|
||||
@@ -19,7 +19,7 @@ Godot 时期(2026-09-22 至 09-27)的批次记录已移到 `docs/archive/POR
|
||||
|
||||
metin2-client 是 40250 Windows 客户端的跨平台版本。**首要目标是 Android arm64 原生客户端**(2026-09-28 定),
|
||||
**macOS arm64 是开发和验收平台**;iOS arm64 保持交叉编译干净,之后再做。**Linux 和 Windows 不做**——不验收、
|
||||
不作为任何步骤的阻塞项。`script/port_gate.sh` 里的 mingw-w64 / Linux 交叉编译只留作可移植性门禁,不代表支持这些平台。
|
||||
不作为任何步骤的阻塞项。`scripts/port_gate.sh` 里的 mingw-w64 / Linux 交叉编译只留作可移植性门禁,不代表支持这些平台。
|
||||
|
||||
除渲染和平台 API 外,所有玩法算法、分支、常量、状态顺序、计时来源、数据来源、封包副作用和清理路径都以 40250
|
||||
为准。**40250 源码就是规格,不设计行为,只照抄。**
|
||||
@@ -38,7 +38,7 @@ metin2-client 是 40250 Windows 客户端的跨平台版本。**首要目标是
|
||||
| 2026-09-22 | 数据源统一为 40250(`Client/pack` 的 EPK 和 `Client/Eternexus` 的 root/uiscript/locale),不用 m2dev 资源 | 两者有实质差异,见 `docs/archive/PORT-HISTORY.md` |
|
||||
| 2026-09-22 | 照抄时保持 40250 的**宽度和溢出语义**(Win32 下 `long` 为 32 位),序列化结构用定宽类型并 `static_assert` 大小 | 64 位平台 `long` 是 8 字节,机械照抄会破坏 proto/封包/EPK 布局和 TEA |
|
||||
| 2026-09-23 | 网络层用 40250 经典协议(1 字节包头 + 包长表 + 序列字节 + Crypto++ DH2/CTR),m2dev 分支协议放弃 | 真服 192.168.21.203 跑的是 40250 服务端 |
|
||||
| 2026-09-27/28 | **宿主换成原生 SDL3 + Vulkan**(`native_render/`),Godot 工程、GDExtension、`project/*.gd` 全部删除 | D3D8 固定管线按 40250 语义直译比经 Godot 适配更接近原版;去掉一层引擎,移动端体积和功耗更可控 |
|
||||
| 2026-09-27/28 | **宿主换成原生 SDL3 + Vulkan**(`src/host/`),Godot 工程、GDExtension、`project/*.gd` 全部删除 | D3D8 固定管线按 40250 语义直译比经 Godot 适配更接近原版;去掉一层引擎,移动端体积和功耗更可控 |
|
||||
| 2026-09-28 | Android 原生客户端优先,iOS 延后 | 目标用户在手机上 |
|
||||
| 2026-09-28 | 代码页转换用内置 WindowsBestFit 表(`mt_codepage`),不再用 iconv | Android API 24 没有可用的 iconv;CP949/GBK 结果与 Windows 一致 |
|
||||
| 2026-09-29 | 移动端专有功能(触控、自动狩猎、账号注册、帧率设置)以 PORT 模块形式注入,不改 40250 脚本 | 40250 脚本字节保持不变,账本 `RUN_AS_IS` 仍成立 |
|
||||
@@ -47,30 +47,30 @@ metin2-client 是 40250 Windows 客户端的跨平台版本。**首要目标是
|
||||
|
||||
| 层 | 40250 单元 | 做法 | 位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `logic` | `UserInterface/`、`GameLib/`、`EterLib` 的网络/计时/文本解析、`EterPack`、`EterLocale`、`EterBase` 的纯逻辑单元(`tea`、`lzo`、`cipher`、`Random`、`Stl`、`Timer`、`Poly/`) | 逐文件照抄 C++ | `extension/src/port/<Lib>/<File>.{h,cpp}` |
|
||||
| `python` | `root/*.py`、`uiscript/`、`UserInterface/*Module.cpp`、`EterPythonLib/`、`ScriptLib/` | 脚本原样运行;`*Module.cpp`、窗口系统、`CPythonLauncher` 照抄 | `extension/src/port/<Lib>/` + `extension/third_party/cpython-2.7.18/` |
|
||||
| `platform` | Direct3D/`Grp*`、Granny、Miles、SpeedTree、特效/地形渲染、Win32 窗口/输入/IME、线程、反作弊、`EterBase` 的文件/OS 单元(`CPostIt`、`CRC32`、`Debug`、`FileBase`、`FileDir`、`FileLoader`、`MappedFile`、`TempFile`、`Utils`、`error`;`CRC32`、`Utils` 里的纯函数在 platform 实现里照抄) | 适配层,接口与 40250 调用方看到的一致,按可观察输出核对 | `extension/src/platform/` + `native_render/` |
|
||||
| `logic` | `UserInterface/`、`GameLib/`、`EterLib` 的网络/计时/文本解析、`EterPack`、`EterLocale`、`EterBase` 的纯逻辑单元(`tea`、`lzo`、`cipher`、`Random`、`Stl`、`Timer`、`Poly/`) | 逐文件照抄 C++ | `src/port/<Lib>/<File>.{h,cpp}` |
|
||||
| `python` | `root/*.py`、`uiscript/`、`UserInterface/*Module.cpp`、`EterPythonLib/`、`ScriptLib/` | 脚本原样运行;`*Module.cpp`、窗口系统、`CPythonLauncher` 照抄 | `src/port/<Lib>/` + `third_party/cpython-2.7.18/` |
|
||||
| `platform` | Direct3D/`Grp*`、Granny、Miles、SpeedTree、特效/地形渲染、Win32 窗口/输入/IME、线程、反作弊、`EterBase` 的文件/OS 单元(`CPostIt`、`CRC32`、`Debug`、`FileBase`、`FileDir`、`FileLoader`、`MappedFile`、`TempFile`、`Utils`、`error`;`CRC32`、`Utils` 里的纯函数在 platform 实现里照抄) | 适配层,接口与 40250 调用方看到的一致,按可观察输出核对 | `src/platform/` + `src/host/` |
|
||||
|
||||
```
|
||||
extension/src/port/UserInterface/InstanceBase.cpp # 40250 同名文件的照抄
|
||||
extension/src/port/GameLib/ActorInstance.cpp
|
||||
extension/src/port/ScriptLib/PythonLauncher.cpp
|
||||
extension/src/port/EterPythonLib/PythonWindow*.cpp
|
||||
extension/src/port/UserInterface/*Module.cpp # app/net/player/chr/... Python 模块
|
||||
extension/src/port/EterLib/GrpBase.h # 平台类的 40250 头文件同样照抄在镜像路径(接口)
|
||||
extension/src/platform/ # 这些平台类的实现(CGraphicThingInstance、CSoundManager 等)
|
||||
extension/src/platform/EterLib/RecordingDevice.cpp # IDirect3DDevice8 的记录实现:把 D3D8 调用录成绘制命令
|
||||
extension/src/platform/ScriptLib/PythonBoot.cpp # 解释器启动、RunMainScript、脚本线程、PORT 模块注入
|
||||
extension/third_party/cpython-2.7.18/ # 静态库
|
||||
native_render/main.cpp # SDL3 宿主:窗口/输入/触控、VulkanWindow 渲染器、帧率策略
|
||||
native_render/android_perf.h # Android 帧率、ADPF、电量/温度(JNI 到 MainActivity)
|
||||
android-native/ # Gradle 工程:SDLActivity 外壳 MainActivity、APK 打包
|
||||
src/port/UserInterface/InstanceBase.cpp # 40250 同名文件的照抄
|
||||
src/port/GameLib/ActorInstance.cpp
|
||||
src/port/ScriptLib/PythonLauncher.cpp
|
||||
src/port/EterPythonLib/PythonWindow*.cpp
|
||||
src/port/UserInterface/*Module.cpp # app/net/player/chr/... Python 模块
|
||||
src/port/EterLib/GrpBase.h # 平台类的 40250 头文件同样照抄在镜像路径(接口)
|
||||
src/platform/ # 这些平台类的实现(CGraphicThingInstance、CSoundManager 等)
|
||||
src/platform/EterLib/RecordingDevice.cpp # IDirect3DDevice8 的记录实现:把 D3D8 调用录成绘制命令
|
||||
src/platform/ScriptLib/PythonBoot.cpp # 解释器启动、RunMainScript、脚本线程、PORT 模块注入
|
||||
third_party/cpython-2.7.18/ # 静态库
|
||||
src/host/main.cpp # SDL3 宿主:窗口/输入/触控、VulkanWindow 渲染器、帧率策略
|
||||
src/host/android_perf.h # Android 帧率、ADPF、电量/温度(JNI 到 MainActivity)
|
||||
android/ # Gradle 工程:SDLActivity 外壳 MainActivity、APK 打包
|
||||
```
|
||||
|
||||
运行结构(每帧):
|
||||
|
||||
```
|
||||
SDL3 宿主主循环(native_render/main.cpp run_live_client)
|
||||
SDL3 宿主主循环(src/host/main.cpp run_live_client)
|
||||
├─ SDL 事件 → 40250 的窗口消息/输入接口(鼠标、键盘、IME 文本、触控摇杆/按钮)
|
||||
├─ PythonBoot::AppFrame():交给脚本线程跑一次 CPythonApplication::Process()(宿主等待,两者不并发)
|
||||
│ └─ system.py / app.Loop() → UpdateGame / RenderGame / 窗口系统 Render
|
||||
@@ -80,18 +80,18 @@ SDL3 宿主主循环(native_render/main.cpp run_live_client)
|
||||
|
||||
逻辑层规则:
|
||||
|
||||
- 40250 调用平台类的地方,调用 `extension/src/platform/` 下同名的适配接口,ported 代码里不直接写 SDL/Vulkan/JNI 调用。
|
||||
- 40250 调用平台类的地方,调用 `src/platform/` 下同名的适配接口,ported 代码里不直接写 SDL/Vulkan/JNI 调用。
|
||||
闭包里的 40250 头文件**不分逻辑层和平台层,一律照抄到镜像路径**(`port/EterLib/GrpBase.h` 等),作为平台类的接口,
|
||||
这样原来的相对 include(`../eterLib/StdAfx.h`)保持不变;`platform/` 只放这些类的 `.cpp` 实现,不另写同名头文件。
|
||||
- 平台层对渲染的适配止于 D3D8 接口:40250 的 `Grp*`、地形、水、SpeedTree、特效照抄后调用 `IDirect3DDevice8`,
|
||||
由 `RecordingDevice` 记录,`native_render` 按 D3D8 固定管线语义(光照、雾、纹理阶段、alpha 测试/混合、填充规则)直译成 Vulkan。
|
||||
由 `RecordingDevice` 记录,`src/host` 按 D3D8 固定管线语义(光照、雾、纹理阶段、alpha 测试/混合、填充规则)直译成 Vulkan。
|
||||
- 照抄只做机械转换,用 `port_copy.py copy <Lib/File>`:CP949 转 UTF-8、CRLF 转 LF、`#include "..."` 路径改成磁盘上的
|
||||
大小写;含非 ASCII 字符串字面量的文件拒绝照抄,需手工处理。其他任何改动都是手工修改,行上标 `// PORT:` 并写原因,
|
||||
`port_copy.py diff` 列出全部手工修改。
|
||||
- 40250 源码里的系统头保持原样 include,由 `port/common/shim/` 提供替身:`shim/sdk/`(`d3d8.h`、`d3dx8.h`、`mss.h`,
|
||||
所有平台都用,只含值类型和不透明接口)、`shim/win32/`(`windows.h`、`winsock.h`、`mmsystem.h` 等,仅非 Windows 平台用)。
|
||||
- 40250 的单例(`CPythonPlayer`、`CPythonCharacterManager`、`CPythonNetworkStream`)由移植代码持有,生命周期与 40250
|
||||
相同;宿主(`native_render`)不持有玩法状态,只通过 `PythonBoot` 和平台接口交互。
|
||||
相同;宿主(`src/host`)不持有玩法状态,只通过 `PythonBoot` 和平台接口交互。
|
||||
- **保持 40250 的宽度和溢出语义,不是机械保留 C++ 类型名。** 40250 可执行文件目标是 32 位 Win32(ILP32):
|
||||
`long`/`unsigned long` 和指针都是 32 位;移植目标则可能是 LP64 或 LLP64。`port/common/Win32Types.h`
|
||||
只为 `BYTE`/`WORD`/`DWORD`/`LONG`/`BOOL`/`UINT` 等 Win32 **标量别名**提供定宽定义,不能重定义 C++ 关键字 `long`:
|
||||
@@ -107,10 +107,10 @@ PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 4
|
||||
|
||||
- 由 `PythonBoot::RunMainScript` 注入的 Python 模块:`mt_autohunt`(自动狩猎,`AutoHuntScript.inc`)、`mt_register`
|
||||
(客户端内账号注册)、`mt_framerate`(系统选项里的"画面帧率"行)。它们包装 40250 窗口类的方法,不修改脚本文件。
|
||||
- 触控控制(`native_render/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
||||
- 触控控制(`src/host/touch_controller.h`):摇杆、攻击/技能按钮,转成 40250 的输入接口。
|
||||
- 帧率与功耗(`platform/EterBase/FrameRateMode.h` + `main.cpp` 的 `FrameRatePolicy`):30 / 60(默认)/ 显示器最高三档,
|
||||
无输入一段时间降到 30 fps(自动狩猎时除外),Android 上配合 `setFrameRate` 和 ADPF。
|
||||
- 性能遥测:`--perf-log` 写 CSV(`native_render/perf_log.h`、`platform/EterBase/PerfCounters.h`)。
|
||||
- 性能遥测:`--perf-log` 写 CSV(`src/host/perf_log.h`、`platform/EterBase/PerfCounters.h`)。
|
||||
|
||||
## 4. 路线
|
||||
|
||||
@@ -148,14 +148,14 @@ PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 4
|
||||
- 资源、proto、root/uiscript/locale 全部来自 40250 `Client`:`Client/pack` 的经典 `.eix/.epk`(103 个包加 `root`,
|
||||
52,609 个路径,先注册的包优先),经移植的 `EterPack`/`EterPackManager` 读取;`item_proto`/`mob_proto` 用移植的
|
||||
`CLZO` + TEA 读(记录 156 / 255 字节)。
|
||||
- 中文:`push-android-client.sh --locale zh` 推送 `build/zh-locale` 生成的 `locale_zh`,译名来源见 `docs/ZH-LOCALE.md`。
|
||||
- 中文:`android/push-client.sh --locale zh` 推送 `build/zh-locale` 生成的 `locale_zh`,译名来源见 `docs/ZH-LOCALE.md`。
|
||||
- 环境变量:
|
||||
- `MT_40250_CLIENT`:40250 的 `Client` 目录(`pack/`、`Eternexus/`)。native 测试的 CMake 缓存变量默认读它,
|
||||
再退回 `${MT_40250_SOURCE}/../../Client`;
|
||||
- `MT_ASSETS_STRICT=1`:native 测试在资源缺失时失败(返回 1),不按跳过(77)处理。
|
||||
- 来源清单:`tools/asset_manifest.py generate|verify <Client>` 记录 `pack/Index`、全部 `.eix/.epk` 和
|
||||
`Eternexus/root/*.msm` 的路径、大小、sha256,结果在 `audit/assets/40250-client.json`。
|
||||
- `Client/root` 的 `RUN_AS_IS` 账本指向 `pack://<script>.py`,`script/verify_root_pack.py` 逐个解码 `root.epk`
|
||||
- `Client/root` 的 `RUN_AS_IS` 账本指向 `pack://<script>.py`,`scripts/verify_root_pack.py` 逐个解码 `root.epk`
|
||||
核对参考源码和账本 SHA-256。
|
||||
|
||||
## 6. 进度与 port-map
|
||||
@@ -187,7 +187,7 @@ PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 4
|
||||
- macOS:`brew install cmake sdl3 vulkan-headers vulkan-loader molten-vk shaderc`;构建见根目录 README
|
||||
(`-DMT_BUILD_NATIVE_RENDER=ON`,目标 `mt_native_render`)。
|
||||
- Android:NDK 27.2(`ANDROID_NDK_HOME` 指向它)、openjdk@21、Android SDK;`adb` 用 `/opt/homebrew/bin/adb`。
|
||||
`./build-android-native.sh Release --install` 构建安装 APK,`./push-android-client.sh [--locale zh] <Client>` 推送资源
|
||||
`./android/build.sh Release --install` 构建安装 APK,`./android/push-client.sh [--locale zh] <Client>` 推送资源
|
||||
(APK 只含代码,资源在应用数据目录)。启动前先唤醒手机。
|
||||
- Python 3:用于 `port_map.py` 和其他审计脚本。Python 2.7.18 只在复核 `py_embed_spike.py` 时需要。
|
||||
- 真服账号、密码不写进仓库或 audit 账本。
|
||||
@@ -196,12 +196,12 @@ PORT 扩展(40250 没有对应物,只为移动端/平台而加;不能改 4
|
||||
|
||||
```bash
|
||||
./script/port_gate.sh macos # port_platform 构建 + ctest '^port\.';另有 android/ios/windows/linux 交叉编译门禁
|
||||
ctest --test-dir build-native --output-on-failure
|
||||
node script/native_mac_acceptance.mjs # macOS 原生客户端验收
|
||||
ctest --test-dir build/native --output-on-failure
|
||||
node scripts/native_mac_acceptance.mjs # macOS 原生客户端验收
|
||||
```
|
||||
|
||||
性能/功耗:客户端加 `--perf-log`(可选 `--show-fps`、`--fps-mode 0|1|2`、`--fps-cap N`),Android 上用
|
||||
`tools/pull_perf_logs.sh` 拉 CSV,`tools/perf_summary.py` 汇总(含按帧率档位/空闲分组的电流和功耗)。插着 USB 时电量读数
|
||||
`tools/perf/pull_perf_logs.sh` 拉 CSV,`tools/perf/perf_summary.py` 汇总(含按帧率档位/空闲分组的电流和功耗)。插着 USB 时电量读数
|
||||
没有意义,真实功耗需无线 adb 并拔线。
|
||||
|
||||
新会话恢复工作:
|
||||
|
||||
@@ -63,12 +63,12 @@
|
||||
## 采用方案 A 后的结构
|
||||
|
||||
```
|
||||
extension/src/port/ScriptLib/PythonLauncher.cpp # CPythonLauncher,几乎原样
|
||||
extension/src/port/EterPythonLib/PythonWindow*.cpp # 窗口树、焦点、命中、事件:逻辑照搬
|
||||
extension/src/port/UserInterface/*Module.cpp # net/player/chr/... 模块,调用已移植的 CPython* 单例
|
||||
extension/src/platform/ui/ # 适配层:CGraphicImageInstance/CGraphicTextInstance
|
||||
src/port/ScriptLib/PythonLauncher.cpp # CPythonLauncher,几乎原样
|
||||
src/port/EterPythonLib/PythonWindow*.cpp # 窗口树、焦点、命中、事件:逻辑照搬
|
||||
src/port/UserInterface/*Module.cpp # net/player/chr/... 模块,调用已移植的 CPython* 单例
|
||||
src/platform/ui/ # 适配层:CGraphicImageInstance/CGraphicTextInstance
|
||||
# 的绘制 -> Godot RenderingServer/CanvasItem
|
||||
extension/third_party/cpython-2.7.18/ # 静态库
|
||||
third_party/cpython-2.7.18/ # 静态库
|
||||
```
|
||||
|
||||
Godot 这边只剩一个宿主 Control:把输入转发给 `CPythonWindowManager`,每帧调用它的 Update/Render。
|
||||
@@ -96,7 +96,7 @@ Godot 这边只剩一个宿主 Control:把输入转发给 `CPythonWindowManage
|
||||
|
||||
## 下一步
|
||||
|
||||
1. ~~把 CPython 2.7.18 放进 `extension/third_party/`,共用源码/静态模块清单,但为各平台分别生成或维护
|
||||
1. ~~把 CPython 2.7.18 放进 `third_party/`,共用源码/静态模块清单,但为各平台分别生成或维护
|
||||
`pyconfig.h`~~,静态链接进 `libmtgodot`;
|
||||
源码和三平台配置已完成(2026-09-23,批次 2P step 1):`mtpython` 静态库(`-DMTGODOT_EMBED_PYTHON=ON`)、
|
||||
`tools/py_embed/gen_pyconfig.sh`、ctest `python.embed`;macOS/Android/iOS 编译通过。
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
The native client links four vendored libraries, all built from source by our own
|
||||
CMake build (the Android NDK and iOS SDK sysroots have none of them). They live under
|
||||
`extension/third_party/` and are wired by `extension/third_party/CMakeLists.txt`.
|
||||
`third_party/` and are wired by `third_party/CMakeLists.txt`.
|
||||
libsodium and zstd (the m2dev-era net/pack layers) were removed on 2026-09-29 together
|
||||
with those layers.
|
||||
|
||||
@@ -17,7 +17,7 @@ with those layers.
|
||||
|
||||
## miniLZO — vendored source (not a submodule)
|
||||
|
||||
* **Files:** `extension/third_party/minilzo/{minilzo.c,minilzo.h,lzoconf.h,lzodefs.h}`
|
||||
* **Files:** `third_party/minilzo/{minilzo.c,minilzo.h,lzoconf.h,lzodefs.h}`
|
||||
copied verbatim from **lzo-2.10** (`minilzo.c` sha1 `019debb3…`), plus
|
||||
`README.LZO`, `COPYING`, `AUTHORS` as required by the LZO license.
|
||||
* Upstream LZO has no git repo (oberhumer.com tarball only), and miniLZO is a
|
||||
@@ -27,14 +27,14 @@ with those layers.
|
||||
miniLZO's API (`lzo_init`, `lzo1x_decompress_safe`, `LZO_E_OK`, `lzo_uint`) is a
|
||||
strict subset of full LZO and covers everything the CLZO path uses.
|
||||
* **License:** **GPLv2**. Acceptable for this internal, non-published,
|
||||
non-commercial project on the same footing as `libgr2/src/oodle1.c` — but LZO
|
||||
non-commercial project on the same footing as `src/gr2/src/oodle1.c` — but LZO
|
||||
must be removed, replaced, or commercially licensed before any public release
|
||||
or commercial use. (item_proto/mob_proto are the only LZO consumers; a
|
||||
clean-room LZO1X decompressor is the eventual fix.)
|
||||
|
||||
## Crypto++ — vendored source (not a submodule)
|
||||
|
||||
* **Files:** `extension/third_party/cryptopp/` (~6.6 MB), copied from the 40250
|
||||
* **Files:** `third_party/cryptopp/` (~6.6 MB), copied from the 40250
|
||||
client tree (`ClientVS22/extern/include/cryptopp`), `CRYPTOPP_VERSION 840` =
|
||||
**8.4.0**. The 40250 server links this exact version
|
||||
(`Server/.../extern/cryptopp_8_4_0.tar.gz`), so the version is pinned by
|
||||
@@ -56,12 +56,12 @@ with those layers.
|
||||
`-fno-strict-aliasing`.
|
||||
* **License:** **public domain** — the compilation carries Wei Dai's copyright,
|
||||
but every individual file is placed in the public domain by its authors
|
||||
(`extension/third_party/cryptopp/License.txt`). Ship-safe; unlike miniLZO it
|
||||
(`third_party/cryptopp/License.txt`). Ship-safe; unlike miniLZO it
|
||||
imposes no obligation before a public or commercial release.
|
||||
|
||||
## CPython 2.7.18 — vendored source (not a submodule)
|
||||
|
||||
* **Files:** `extension/third_party/cpython-2.7.18/` (~25 MB), the official
|
||||
* **Files:** `third_party/cpython-2.7.18/` (~25 MB), the official
|
||||
`Python-2.7.18.tgz` (sha256 `b62c0e7937551d0cc02b8fd5cb0f544f9405bafc9a54d3808ed4594812edef43`)
|
||||
trimmed of what an embedded interpreter never builds or ships: `Doc Demo Tools
|
||||
Mac PCbuild`, `Modules/{_ctypes,expat,zlib,_sqlite}`, the stdlib test trees and
|
||||
@@ -110,7 +110,7 @@ with those layers.
|
||||
|
||||
## FreeType 2.13.3 — vendored source, trimmed (not a submodule)
|
||||
|
||||
* **Files:** `extension/third_party/freetype-2.13.3/` (~5 MB): the official
|
||||
* **Files:** `third_party/freetype-2.13.3/` (~5 MB): the official
|
||||
`freetype-2.13.3.tar.xz` (source URL and sha256 in the directory's
|
||||
`CMakeLists.txt` header) cut down to `include/`, `src/{base,sfnt,truetype,smooth,raster,psnames,gzip}`,
|
||||
`LICENSE.TXT` and `docs/FTL.TXT`.
|
||||
|
||||
+8
-8
@@ -3,8 +3,8 @@
|
||||
## 语言包(2026-09-28 起为正式 pack)
|
||||
|
||||
```sh
|
||||
cmake --build build-native --target locale_proto_patch eterpack_pack
|
||||
python3 script/build_zh_locale.py
|
||||
cmake --build build/native --target locale_proto_patch eterpack_pack
|
||||
python3 scripts/build_zh_locale.py
|
||||
```
|
||||
|
||||
生成器复制 40250 `Eternexus/locale_en/locale/en` 的文件结构到暂存目录
|
||||
@@ -13,7 +13,7 @@ python3 script/build_zh_locale.py
|
||||
中经审核的译文(同样校验占位符);报告在
|
||||
`build/zh-locale/zh-locale-report.json`。职业与帝国说明采用龙驹中文文本。
|
||||
|
||||
暂存目录随后由 `eterpack_pack`(`extension/tests/eterpack_pack.cpp`,直接调用移植的 40250
|
||||
暂存目录随后由 `eterpack_pack`(`tests/port/eterpack_pack.cpp`,直接调用移植的 40250
|
||||
`CEterPack::Put` / `EncryptIndexFile`)打成 `locale_zh.eix/.epk`,格式与原版 `locale_en` 等
|
||||
相同:231 个文件全部为 COMPRESS(LZO),索引经 LZO + TEA 加密。工具写完后以只读方式
|
||||
重新打开 pack,逐个经 `CEterPack::Get` 读回并与源文件比对。用同一工具重打原版
|
||||
@@ -30,11 +30,11 @@ python3 script/build_zh_locale.py
|
||||
macOS 启动:
|
||||
|
||||
```sh
|
||||
build-native/native_render/mt_native_render \
|
||||
build/native/src/host/mt_native_render \
|
||||
--live-client build/zh-locale/Client --login-screen --width 1024 --height 768
|
||||
```
|
||||
|
||||
Android:`./push-android-client.sh --locale zh`(默认推送 `build/zh-locale/Client`,符号链接按
|
||||
Android:`./android/push-client.sh --locale zh`(默认推送 `build/zh-locale/Client`,符号链接按
|
||||
目标文件复制,并以 `locale_zh.cfg` 写入设备上的 `locale.cfg`)。
|
||||
|
||||
物品与怪物原型从 40250 原版解压,按线上 40250 服务端 `gb2312name` 的 VNUM 快照
|
||||
@@ -75,11 +75,11 @@ Android:`./push-android-client.sh --locale zh`(默认推送 `build/zh-locale
|
||||
## 服务端适配进度(2026-09-28)
|
||||
|
||||
服务端 `newcibn/locale_string.txt` 原有 733 个中文键,和 40250 英文参考字典的
|
||||
762 个键比对后缺少 36 个。`script/build_zh_server_locale.py` 保留原字典的 733 个键
|
||||
762 个键比对后缺少 36 个。`scripts/build_zh_server_locale.py` 保留原字典的 733 个键
|
||||
及其原始 GBK 译文,补齐 36 个 40250 键,并修复两条丢失 `%d` 参数的译文;旧字典
|
||||
特有的 7 个键继续保留。生成文件有 769 个键,全部译文按 GBK 编码,格式参数与
|
||||
韩文调用键逐条一致。生成产物在 `build/zh-locale/server/newcibn/locale_string.txt`;
|
||||
`python3 script/test_build_zh_server_locale.py` 验证键和旧译文保持不变。
|
||||
`python3 scripts/test_build_zh_server_locale.py` 验证键和旧译文保持不变。
|
||||
|
||||
已将该文件部署到 `/usr/metin2/server/share/locale/newcibn/locale_string.txt`,远端
|
||||
SHA-256 为 `7f16a8c7b9d08774ec90c4a86b80b17364b072c0af9e2b76a0ea9df027adaef8`。
|
||||
@@ -90,7 +90,7 @@ SHA-256 为 `7f16a8c7b9d08774ec90c4a86b80b17364b072c0af9e2b76a0ea9df027adaef8`
|
||||
|
||||
只读导出的线上数据库中,`item_proto` 有 5,743 条、`mob_proto` 有 1,334 条,
|
||||
`gb2312name` 全部能按 GBK 解码,但分别有 1,608 和 346 个名称不含汉字。
|
||||
`script/audit_zh_server_names.py` 将线上 VNUM 与旧中文名称表比对,发现物品 1 处、
|
||||
`scripts/audit_zh_server_names.py` 将线上 VNUM 与旧中文名称表比对,发现物品 1 处、
|
||||
怪物/NPC 20 处同 VNUM 不同名(例如 VNUM 101 在线上是 `Wild Dog`,旧表是`幼狼`)。
|
||||
旧表并非完整的 40250 名称表,因此本轮没有批量修改数据库。客户端已改用本次
|
||||
只读导出的服务端名称快照,见 `data/zh_40250/README.md`,两侧 VNUM 名称由此一致。
|
||||
|
||||
@@ -54,7 +54,7 @@ xrender-poc/
|
||||
reuse/
|
||||
EterImageLib/ 从 ../MobileSource 冻结拷入:DXTCImage.{h,cpp} + StdAfx + 依赖的最小集
|
||||
EterBase/ (拉伸)CFileBase / CMappedFile —— demo 初期可先用裸 fopen
|
||||
libgr2/
|
||||
src/gr2/
|
||||
include/gr2.h
|
||||
src/gr2_file.cpp header + section table + fixup 重定位
|
||||
src/gr2_typetree.cpp 自描述 data_type_definition 遍历器
|
||||
@@ -82,7 +82,7 @@ xrender-poc/
|
||||
compile.cmake shaderc → metal + bin2c
|
||||
tools/
|
||||
gr2dump/main.cpp CLI:结构化 dump + glTF 导出
|
||||
oracle/ Windows-only,见 PLAN §07(demo 阶段可先跳,用 Blender io_scene_gr2 当参照)
|
||||
tools/granny-oracle/ Windows-only,见 PLAN §07(demo 阶段可先跳,用 Blender io_scene_gr2 当参照)
|
||||
cmake/
|
||||
macos.cmake
|
||||
CMakeLists.txt
|
||||
|
||||
@@ -38,7 +38,7 @@ PoC 不是"移植一小部分客户端",而是把跨平台方案里所有真
|
||||
PoC 的价值是**降低不确定性**,go 和 no-go 都是有效产出。
|
||||
|
||||
> **进度(2026-08-29)**:M0 完成(9166/9166 解析零崩溃)。M1/M2 代码完成,
|
||||
> **数值保真已对拍真 Granny 2.9.12**(骨骼世界矩阵 + 蒙皮顶点 ≤ 6.5e-5,见 `oracle/`)。
|
||||
> **数值保真已对拍真 Granny 2.9.12**(骨骼世界矩阵 + 蒙皮顶点 ≤ 6.5e-5,见 `tools/granny-oracle/`)。
|
||||
> M3 iOS 端已交叉编译 + 模拟器运行 + 渲染与桌面一致;Android 脚手架待 NDK + 真机。
|
||||
> **PoC 的核心技术问题(能否脱离 Granny 正确读 + 播 Metin2 gr2)= 已用数值证据回答「能」**。
|
||||
> 剩余是真机验证(性能 / 生命周期 / mediump)+ Android 端。
|
||||
@@ -262,7 +262,7 @@ bgfx 自己不管窗口。计划做法:`sokol_app` 拿到原生 window handle
|
||||
|
||||
### M0 第一件事:锁定 oracle 用的 Granny 版本
|
||||
|
||||
资产是文件格式 v6(约 2.6–2.9 era),但 `m2dev-client-src` 头文件写 2.11.8。**oracle 必须用实际能正确加载这批资产的那个 `granny2.dll` 版本**去 dump,否则是拿错误的参考去校 libgr2。M0 起手要确认:这批 gr2 在哪个 Granny 版本下 `GrannyGetFileInfo` 返回完全正常(骨骼数、顶点数、动画时长都合理),把那个 DLL 固定进 `oracle/`。
|
||||
资产是文件格式 v6(约 2.6–2.9 era),但 `m2dev-client-src` 头文件写 2.11.8。**oracle 必须用实际能正确加载这批资产的那个 `granny2.dll` 版本**去 dump,否则是拿错误的参考去校 libgr2。M0 起手要确认:这批 gr2 在哪个 Granny 版本下 `GrannyGetFileInfo` 返回完全正常(骨骼数、顶点数、动画时长都合理),把那个 DLL 固定进 `tools/granny-oracle/`。
|
||||
|
||||
---
|
||||
|
||||
@@ -348,7 +348,7 @@ bgfx 自己不管窗口。计划做法:`sokol_app` 拿到原生 window handle
|
||||
|
||||
**oracle 自身要先过自检**(在被信任之前):加载一个 gr2、dump t=0 的骨骼矩阵,应与该 gr2 存的 bind pose(`InverseWorld4x4` 求逆)一致。过不了这条,下游全建在沙子上 —— 这是 M0 的隐性前置门禁。
|
||||
|
||||
这套 dump 是 Windows-only,永远留在 Windows。`oracle/` 的搭建(把 `EterGrnLib` 从完整客户端里单独抽出来编、两层矩阵 dump、确定态改造)**从 M0 起,是关键路径**,需要一个会 Windows / D3D 构建的人,[§09 工作量表](#工作量)里单列。
|
||||
这套 dump 是 Windows-only,永远留在 Windows。`tools/granny-oracle/` 的搭建(把 `EterGrnLib` 从完整客户端里单独抽出来编、两层矩阵 dump、确定态改造)**从 M0 起,是关键路径**,需要一个会 Windows / D3D 构建的人,[§09 工作量表](#工作量)里单列。
|
||||
|
||||
### 数值层
|
||||
|
||||
@@ -409,7 +409,7 @@ bgfx 自己不管窗口。计划做法:`sokol_app` 拿到原生 window handle
|
||||
|
||||
| 领域 | 风险 | 影响 / 缓解 | 严重度 |
|
||||
|---|---|---|---|
|
||||
| **oracle** | granny2.dll 版本与资产格式不匹配(资产 v6,src 头写 2.11.8) | 拿错误参考校 libgr2,全盘失真。**M0 起手锁定"能正确加载这批资产的 DLL 版本"**,固定进 `oracle/`(见 [§05](#m0-第一件事锁定-oracle-用的-granny-版本))。 | **高** |
|
||||
| **oracle** | granny2.dll 版本与资产格式不匹配(资产 v6,src 头写 2.11.8) | 拿错误参考校 libgr2,全盘失真。**M0 起手锁定"能正确加载这批资产的 DLL 版本"**,固定进 `tools/granny-oracle/`(见 [§05](#m0-第一件事锁定-oracle-用的-granny-版本))。 | **高** |
|
||||
| gr2 | **section 全部 Oodle1 压缩**(M0 T1 实测确认,非可选);个别文件可能混 Oodle0 / BitKnit | Oodle0/1 有公开重实现(~200 行 LZ 变体),M0 T2 必做;BitKnit 无公开实现 —— 9166 全样本 0 个,若真遇到 → Windows 侧 Granny SDK 离线转未压缩。 | 中 |
|
||||
| gr2 | 自描述类型树递归 / 未知类型 / 重定位表 | 写通用类型树遍历器(不假设布局)+ 与 SDK dump 逐字段对拍。 | 中 |
|
||||
| 动画 | Granny 曲线压缩格式多样(关键帧数组、D3/D4nK 量化、B 样条拟合) | 三层退路:① M0 统计实际用到的子类型,多数只需实现关键帧 + 简单量化;② `granny_curve*.cpp` 当算法参照;③ **子类型超范围就走"oracle 离线烘焙成密集关键帧"(每骨每帧一个 TRS),libgr2 只读烘焙格式,PoC 完全绕开曲线解码,代价是动画数据变大 + 一个烘焙工具**。加了 ③ 后此项可视为"中"。 | **高** → 中(有 ③ 兜底) |
|
||||
@@ -461,7 +461,7 @@ xrender-poc/
|
||||
sokol/ 仅 sokol_app.h
|
||||
glm/
|
||||
reuse/ EterBase/ EterPack/ EterImageLib/ ← 冻结快照 + 本项目修复提交(非引用)
|
||||
libgr2/
|
||||
src/gr2/
|
||||
include/ src/ gr2_file.* gr2_types.* gr2_skeleton.*
|
||||
gr2_mesh.* gr2_anim.* gr2_decompress.*
|
||||
engine/ rhi.*(包 bgfx::) skinning.* animation.* material.* scene.* camera.*
|
||||
@@ -474,7 +474,7 @@ xrender-poc/
|
||||
+ vs_pnt_skinned.sc(新写)
|
||||
shaders.cmake 可移植编译步骤:shaderc → metal / 300_es / s_5_0 → bin2c
|
||||
platform/ macos/ ios/ android/ (壳工程 + toolchain)
|
||||
oracle/ Windows-only:锁定版 granny2.dll + EterGrnLib
|
||||
tools/granny-oracle/ Windows-only:锁定版 granny2.dll + EterGrnLib
|
||||
dump 两层骨骼矩阵 + 蒙皮顶点 + 确定态截图;坐标约定写死
|
||||
tools/ gr2dump/ (M0 CLI) gr2fuzz/ snapshot_compare/ anim_bake/(曲线退路)
|
||||
test/ assets.list(M0 产出) golden/ oracle_dumps/ noise_floor.json
|
||||
@@ -486,7 +486,7 @@ xrender-poc/
|
||||
|
||||
从 **M0** 起,两件事并行:
|
||||
|
||||
1. **锁定 oracle 的 Granny 版本** —— 确认这批 v6 资产在哪个 `granny2.dll` 下 `GrannyGetFileInfo` 完全正常,固定进 `oracle/`。
|
||||
1. **锁定 oracle 的 Granny 版本** —— 确认这批 v6 资产在哪个 `granny2.dll` 下 `GrannyGetFileInfo` 完全正常,固定进 `tools/granny-oracle/`。
|
||||
2. **写 `gr2dump`** —— 输出 `warrior_cheongrin*.gr2` 的结构化 dump(骨架 / 网格 / 曲线)+ glTF;结构化 dump 与 oracle 逐字段对拍(这是主验证),glTF 仅供 Blender 目视骨架和 bind pose。
|
||||
|
||||
这是单块价值最高、且不依赖任何 RHI / 平台决策的第一块砖。
|
||||
|
||||
@@ -9,8 +9,8 @@ still authoritative:
|
||||
|
||||
| file | what's still useful |
|
||||
|---|---|
|
||||
| `steps/M0-gr2-reader.md` | the entire `.gr2` v6/v7 format breakdown that `libgr2` implements — header, sections, Oodle1, self-describing type tree, `granny_transform`, `OldCurveType` B-splines, the "明确不做" table. **`libgr2` is vendored here now** (`../../libgr2`). |
|
||||
| `steps/00-oracle.md` | how the Wine + MinGW Granny oracle works — the ground truth for `libgr2` numeric verification. Oracle harness vendored at `../../oracle/`. |
|
||||
| `steps/M0-gr2-reader.md` | the entire `.gr2` v6/v7 format breakdown that `libgr2` implements — header, sections, Oodle1, self-describing type tree, `granny_transform`, `OldCurveType` B-splines, the "明确不做" table. **`libgr2` is vendored here now** (`../../src/gr2`). |
|
||||
| `steps/00-oracle.md` | how the Wine + MinGW Granny oracle works — the ground truth for `libgr2` numeric verification. Oracle harness vendored at `../../tools/granny-oracle/`. |
|
||||
| `steps/M1-static-render.md` | material-binding chain (`granny_material` → `.Maps[].Map` → `granny_texture.FromFileName`), DDS/DXT decode, the bgfx-era gotchas. The material logic maps 1:1 onto `m2_material.cpp` here. |
|
||||
| `steps/M2-anim-skinning.md` | CPU LBS formula, `sample_pose` retarget-by-bone-name, the Granny normal-transform (plain 3×3, **no** inverse-transpose) — same math the GDExtension uses. Numeric gates vs Granny (≤6.5e-5). |
|
||||
| `steps/M3-mobile.md` | iOS/Android notes from the bgfx shells — historical; Godot handles platform now. |
|
||||
@@ -18,14 +18,14 @@ still authoritative:
|
||||
| `PLAN.md` | the bgfx-route master plan. §01 (go/no-go criteria, "工程性延期 vs 方案性死路"), §05 (format findings), §07 (visual-diff layering) are still the shared vocabulary. |
|
||||
| `DEMO-PLAN.md` | bgfx RHI demo design record. Mostly superseded; kept for the shader/material-stage analysis. |
|
||||
|
||||
**Relative paths inside these files** (`../libgr2`, `oracle/`, `test/golden/…`,
|
||||
**Relative paths inside these files** (`../libgr2`, `tools/granny-oracle/`, `test/golden/…`,
|
||||
`engine/…`, `app/…`) refer to the old xrender-poc layout. The mapping:
|
||||
|
||||
| old (xrender-poc) | now (metin2-client; the repo was created as `mtgodot-poc` and renamed later — the `mtgodot-poc.app` / `.apk` artifact names and the `org.internal.mtgodotpoc` bundle id did **not** change) |
|
||||
|---|---|
|
||||
| `libgr2/` | `libgr2/` (vendored, unchanged) |
|
||||
| `src/gr2/` | `src/gr2/` (vendored, unchanged) |
|
||||
| `formats/` | `formats/` (vendored) |
|
||||
| `oracle/` | `oracle/` (vendored; `oracle.exe` + `granny2_x64.dll` rebuilt locally) |
|
||||
| `tools/granny-oracle/` | `tools/granny-oracle/` (vendored; `oracle.exe` + `granny2_x64.dll` rebuilt locally) |
|
||||
| `tools/{gr2dump,gr2fuzz,oracle_diff}` | `tools/` (build with `-DMTGODOT_BUILD_TOOLS=ON`) |
|
||||
| `engine/`, `app/`, `platform/`, `third_party/` | **dropped** — Godot + the GDExtension in `extension/` replace them |
|
||||
| `test/golden/*.png` (bgfx shots) | `test/bgfx-reference/` (cross-check images) |
|
||||
|
||||
@@ -36,14 +36,14 @@ oracle 分成两半,混在一起会误判工期:
|
||||
|
||||
| 产物 | 位置 | 轨 |
|
||||
|---|---|---|
|
||||
| `granny_probe.exe`(读 gr2 → 打 FileInfo) | `oracle/` | Lite |
|
||||
| `oracle` CLI(`oracle dump <model.gr2> <anim.gr2> <t> -o out.bin`) | `oracle/` | Lite |
|
||||
| 最小 D3D9 渲染器(骨架+mesh→PNG,不依赖 EterGrnLib) | `oracle/render/` | Lite |
|
||||
| 锁定的 `granny2.dll` + 判定记录 | `oracle/vendor/` + `oracle/GRANNY-VERSION.md` | Lite |
|
||||
| dump 二进制格式规范(含坐标约定) | `oracle/FORMAT.md` | Lite |
|
||||
| `granny_probe.exe`(读 gr2 → 打 FileInfo) | `tools/granny-oracle/` | Lite |
|
||||
| `oracle` CLI(`oracle dump <model.gr2> <anim.gr2> <t> -o out.bin`) | `tools/granny-oracle/` | Lite |
|
||||
| 最小 D3D9 渲染器(骨架+mesh→PNG,不依赖 EterGrnLib) | `tools/granny-oracle/render/` | Lite |
|
||||
| 锁定的 `granny2.dll` + 判定记录 | `tools/granny-oracle/vendor/` + `tools/granny-oracle/GRANNY-VERSION.md` | Lite |
|
||||
| dump 二进制格式规范(含坐标约定) | `tools/granny-oracle/FORMAT.md` | Lite |
|
||||
| 噪声地板测量 | `test/noise_floor.json` | Lite |
|
||||
| 自检脚本(CI Windows runner) | `oracle/selfcheck.*` | Lite |
|
||||
| `oracle_etergrn` 静态库 + 层② dump | `oracle/full/` | Full(延后)|
|
||||
| 自检脚本(CI Windows runner) | `tools/granny-oracle/selfcheck.*` | Lite |
|
||||
| `oracle_etergrn` 静态库 + 层② dump | `tools/granny-oracle/full/` | Full(延后)|
|
||||
|
||||
---
|
||||
|
||||
@@ -74,7 +74,7 @@ T1(锁 DLL + granny_probe) ─┬─▶ T3(层① dump) ─┬─▶ T5(蒙皮
|
||||
|
||||
- 先写 `granny_probe.exe`(~30 行):`GrannyReadEntireFileFromMemory` → `GrannyGetFileInfo` → 打印 `Skeletons/Meshes/Materials/Textures/Animations` 的 count、`FromFileName`、每 skeleton 的骨骼数。
|
||||
- 收集候选 `granny2.dll`(**2.4.x 优先**,再 2.6.x / 2.9.x / 2.11.x 兜底),各链一遍 `granny_probe` 跑 `warrior_cheongrin.gr2` + 3 个 zone 静态件 + 1 个 `action/*.gr2`。
|
||||
- 选返回全部合理的那个,`git`-track 进 `oracle/vendor/granny2.dll`,判定写 `oracle/GRANNY-VERSION.md`(列出每个候选的输出)。
|
||||
- 选返回全部合理的那个,`git`-track 进 `tools/granny-oracle/vendor/granny2.dll`,判定写 `tools/granny-oracle/GRANNY-VERSION.md`(列出每个候选的输出)。
|
||||
- **AC**(选定 DLL 下):
|
||||
- [ ] `warrior_cheongrin.gr2`:`Skeletons==1`、`Meshes>=1`、`Animations==0`、`FromFileName` 是可读路径串、骨骼数 ∈ [20, 120](人形合理范围)—— 与 [M0 T4](./M0-gr2-reader.md) 期望值一致。
|
||||
- [ ] `action/dance_1.gr2`:`Animations==1`、`Duration` ∈ (0, 60] 秒。
|
||||
@@ -173,8 +173,8 @@ T1(锁 DLL + granny_probe) ─┬─▶ T3(层① dump) ─┬─▶ T5(蒙皮
|
||||
|
||||
## DoD 清单(Oracle-Lite)
|
||||
|
||||
- [ ] `granny_probe.exe` + `oracle/vendor/granny2.dll` 锁定 + `GRANNY-VERSION.md`(含候选对比)
|
||||
- [ ] `oracle dump <model.gr2> <anim.gr2> <t>` 可用;`oracle/FORMAT.md` 写清二进制布局 + 坐标约定
|
||||
- [ ] `granny_probe.exe` + `tools/granny-oracle/vendor/granny2.dll` 锁定 + `GRANNY-VERSION.md`(含候选对比)
|
||||
- [ ] `oracle dump <model.gr2> <anim.gr2> <t>` 可用;`tools/granny-oracle/FORMAT.md` 写清二进制布局 + 坐标约定
|
||||
- [ ] T3 正确性锚:t=0 层① == bind pose(`< 1e-4`)
|
||||
- [ ] T5 正确性锚:bind pose 蒙皮 == 原始顶点
|
||||
- [ ] T6 确定态截图两次一致 + 视觉 sanity + 换 t 有变化
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
| 产物 | 位置 |
|
||||
|---|---|
|
||||
| `libgr2` 静态库(demo 子集) | `libgr2/` |
|
||||
| `libgr2` 静态库(demo 子集) | `src/gr2/` |
|
||||
| `gr2dump` CLI(结构化 dump + `--sections` + 可选 glTF) | `tools/gr2dump/` |
|
||||
| `gr2fuzz`(全量 ~9166 解析 + 谓词 + 直方图) | `tools/gr2fuzz/` |
|
||||
| 格式变体直方图报告 | `test/fuzz-report.json` |
|
||||
@@ -79,7 +79,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
|
||||
|
||||
### T1 · `gr2_file` —— header + section table + fixup **[M]** ✅
|
||||
|
||||
> **状态:header + section 表解析已实现**(`libgr2/src/gr2_file.cpp`),`gr2dump --sections` 跑通全部 9166 个文件、0 崩溃。
|
||||
> **状态:header + section 表解析已实现**(`src/gr2/src/gr2_file.cpp`),`gr2dump --sections` 跑通全部 9166 个文件、0 崩溃。
|
||||
> 剩下的是 **section 解压(Oodle1,见 T2)**和 **fixup 重定位**(需展开后的数据)。
|
||||
|
||||
**实测结论**(写进代码注释,替换旧假设):
|
||||
@@ -105,7 +105,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
|
||||
|
||||
### T2 · `gr2_decompress` **[M]** —— 已实现并验证
|
||||
|
||||
> **状态:done。** `libgr2/src/oodle1.c` —— 从泄露 SDK 的 `granny_oodle1_compression.cpp` + `radlz.c` + `radarith.c` + `arithbit.c` 端口了**解码路径**(自适应算术编码 + LZ)。约 450 行 C。
|
||||
> **状态:done。** `src/gr2/src/oodle1.c` —— 从泄露 SDK 的 `granny_oodle1_compression.cpp` + `radlz.c` + `radarith.c` + `arithbit.c` 端口了**解码路径**(自适应算术编码 + LZ)。约 450 行 C。
|
||||
|
||||
- `format==0` → memcpy;`format==2`(Oodle1)→ `gr2_oodle1_decompress()`;`format==1`(Oodle0)/ `format==4`(BitKnit,全样本 0 个)→ 留桩报错。
|
||||
- 3-block 结构:`stop0/1/2` = section 的 `First16Bit` / `First8Bit` / `ExpandedDataSize`(32 位 / 16 位 / 8 位 marshalling 区各一块,共享算术流)。
|
||||
@@ -213,10 +213,10 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
|
||||
|
||||
## 门禁(go / no-go)
|
||||
|
||||
- **主**:`libgr2` dump vs 真 Granny —— ✅ **达成**。`oracle probe` 的 `GrannyGetFileInfo` 与 `gr2dump` 逐字段一致;`tools/oracle_diff` 对拍 Granny **2.9.12** 的 `GrannyGetWorldPose4x4Array` + `GrannyDeformVertices`:23 用例(13 模型 bind + 8 动画帧,含 v7 / 双 root / 刚体)骨骼矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`test/m2-numeric.json`)。oracle 在 macOS + Wine + MinGW 跑(`oracle/RUNBOOK.md`)。
|
||||
- **主**:`libgr2` dump vs 真 Granny —— ✅ **达成**。`oracle probe` 的 `GrannyGetFileInfo` 与 `gr2dump` 逐字段一致;`tools/oracle_diff` 对拍 Granny **2.9.12** 的 `GrannyGetWorldPose4x4Array` + `GrannyDeformVertices`:23 用例(13 模型 bind + 8 动画帧,含 v7 / 双 root / 刚体)骨骼矩阵 ≤ 4.6e-5、蒙皮顶点 ≤ 6.5e-5(`test/m2-numeric.json`)。oracle 在 macOS + Wine + MinGW 跑(`tools/granny-oracle/RUNBOOK.md`)。
|
||||
- **自洽**:T5 bind pose 自洽 —— ✅ `warrior_cheongrin` `6.1e-5` PASS。全量 2013/2017 < 1e-3;4 个 `>=1e-1` **经 oracle 证实 libgr2 正确**(assassin 对 Granny 3.1e-5),是自洽不变量本身对这几个双-root 资产不成立,非 libgr2 bug。
|
||||
- **fuzz**:T9b 全量 9166 —— ✅ **零崩溃 + 零谓词失败**。
|
||||
- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`libgr2/README.md`](../../../libgr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。
|
||||
- **变体可实现**:✅ 无 BitKnit / 无 Oodle0;曲线全 `OldCurveType`,degree ≤ 2,T7b 全覆盖。不做的变体见 [`src/gr2/README.md`](../../../src/gr2/README.md)「明确不做」(均 0 出现,坏输入干净报错不崩)。
|
||||
- **产出**:✅ `test/{fuzz-report,m2-numeric,noise_floor}.json` + `test/assets.list` + `test/oracle_dumps/`。
|
||||
- **noise_floor**:✅ `test/noise_floor.json`。同-DLL 确定性 = 0(byte-identical);跨实现地板 = mat 4.6e-5 / vtx 6.5e-5;跨版本一档待另一个 granny2.dll。
|
||||
|
||||
@@ -279,7 +279,7 @@ T4,T5,T6(+可选 T7b) ──▶ T8(gr2dump)
|
||||
**bind pose 自洽 4 个异常**(`>=1e-1`):`assassin.gr2`(PC + season1,同)、`warrior_rabbit1_backup.gr2`、`hair_11_1.gr2`。
|
||||
这 4 个都是**多 root skeleton**(如 assassin:`assasin_low` + `Bip01` 两个 root),且 model 的 `InitialPlacement` 非单位。
|
||||
|
||||
> **后续更正(oracle 对拍后)**:这不是 libgr2 的 bug。`assassin` bind pose 的 **骨骼世界矩阵 + 蒙皮顶点与真 Granny 2.9.12 逐字段一致(3.1e-5 / 4.6e-5)**(见 [00-oracle](./00-oracle.md) / `oracle/RUNBOOK.md`)。
|
||||
> **后续更正(oracle 对拍后)**:这不是 libgr2 的 bug。`assassin` bind pose 的 **骨骼世界矩阵 + 蒙皮顶点与真 Granny 2.9.12 逐字段一致(3.1e-5 / 4.6e-5)**(见 [00-oracle](./00-oracle.md) / `tools/granny-oracle/RUNBOOK.md`)。
|
||||
> `world[i]·InverseWorld4x4[i] ≈ I` 这个**自洽不变量本身**对这几个资产不成立 —— 它们的 `InverseWorld4x4` 是相对「不含 InitialPlacement 的参考」烘的,而 `world = local链 · InitialPlacement`。是资产属性,不是读取错误。
|
||||
> 自洽检查因此是个**比 oracle 对拍弱的信号**:能抓转置 / 手系 bug,但对「InverseWorld 参考系不一致」会误报。`warrior_cheongrin` 等单 root 模型不受影响(IP 为单位,两者等价)。
|
||||
|
||||
|
||||
@@ -107,17 +107,17 @@
|
||||
| rigid + deformable mesh 混合 | `engine/skinning.cpp` 按 `mesh.rigid` 分支(单骨刚体 vs 4 权重混合)|
|
||||
| 非单位 scaleshear 让 `world·invBind` 近似失效 | 全程完整仿射 4x4(`mul4x3` 保 3x3 + 平移);法线用 skin 3x3(scaleshear 严格应逆转置,M2 先近似,记 M4 收紧)|
|
||||
| ε 拍脑袋 | 待 oracle T8 的 noise_floor × 10 |
|
||||
| ~~数值门禁未跑~~ | **已跑**:macOS + Wine + MinGW 交叉编译的 `oracle.exe`(Granny 2.9.12)vs libgr2,骨骼矩阵 + 蒙皮顶点全 ≤ 6.5e-5(float 累积误差量级)。`oracle/RUNBOOK.md` / `tools/oracle_diff` |
|
||||
| ~~数值门禁未跑~~ | **已跑**:macOS + Wine + MinGW 交叉编译的 `oracle.exe`(Granny 2.9.12)vs libgr2,骨骼矩阵 + 蒙皮顶点全 ≤ 6.5e-5(float 累积误差量级)。`tools/granny-oracle/RUNBOOK.md` / `tools/oracle_diff` |
|
||||
|
||||
## DoD 清单
|
||||
|
||||
- [x] `.msm`/`.msa` 解析 dump 与源文本核对通过(`formats/textscript` token 树 + `msa`/`msm`)
|
||||
- [x] **骨骼世界矩阵 vs oracle 层① `max|Δ| ≤ 4.6e-5`** —— `oracle/run-diff-suite.sh` 跑 **23 用例**(warrior_cheongrin/lord ×4 LOD、assassin 双 root、shaman_lord **v7**、redthief2、snakeman、rabbit_backup、ox_01;dance_1/attack/run/wait @ 多个 t)。`test/m2-numeric.json`。
|
||||
- [x] **骨骼世界矩阵 vs oracle 层① `max|Δ| ≤ 4.6e-5`** —— `tools/granny-oracle/run-diff-suite.sh` 跑 **23 用例**(warrior_cheongrin/lord ×4 LOD、assassin 双 root、shaman_lord **v7**、redthief2、snakeman、rabbit_backup、ox_01;dance_1/attack/run/wait @ 多个 t)。`test/m2-numeric.json`。
|
||||
- [x] **蒙皮顶点 vs oracle `‖Δ‖ ≤ 6.5e-5`**(同上,vs `GrannyDeformVertices`;法线用同 3x3,Granny 也不做逆转置 —— `engine/skinning.cpp`)
|
||||
- [x] `test/noise_floor.json`:同-DLL 确定性 0,跨实现地板 mat 4.6e-5 / vtx 6.5e-5,ε=1e-3
|
||||
- [x] **`app/render_fuzz` 全量渲染烟测**:9166 个 `.gr2` 全过真管线(bgfx + `Scene::build` + DXT 解码 + CPU 蒙皮 + `submit`;skinned 4055 / anim-only 5109 / rigid 2)——**0 崩溃 / 0 空场景 / 0 NaN 姿势**(`test/render-fuzz.json`)。
|
||||
- **顺带修的 libgr2 bug**:`redthief_general/{back,front}_damage.gr2` 的 finger track 控制点在文件里就是 `NaN`。`read_old_curve` 现在遇到非有限 knot/control 整条曲线弃(回退 bind),不把 NaN 灌进蒙皮链。
|
||||
- 抽样截图里「模型扭曲」的都是 **render_fuzz 把某物种的动画 retarget 到别的 rig**(如怪物动画播到 warrior 身上,或 warrior dance 播到怪物身上)—— 按名 retarget 到 bind pose 比例不同的骨架,肢体会拉长。**不是 libgr2/engine bug**(同 rig 的 warrior + dance_1/attack/run/wait 已对拍 Granny 1e-5)。render_fuzz 已改成只在 rig 匹配时才应用动画,其余画 bind pose。
|
||||
- 抽样截图里「模型扭曲」的都是 **render_fuzz 把某物种的动画 retarget 到别的 rig**(如怪物动画播到 warrior 身上,或 warrior dance 播到怪物身上)—— 按名 retarget 到 bind pose 比例不同的骨架,肢体会拉长。**不是 src/gr2/engine bug**(同 rig 的 warrior + dance_1/attack/run/wait 已对拍 Granny 1e-5)。render_fuzz 已改成只在 rig 匹配时才应用动画,其余画 bind pose。
|
||||
- [~] 挂点武器变换 —— `gr2::sample_pose` 出每骨世界矩阵,武器 = `world[handBone]·mount`;未接进 demo(无 .msm 武器数据),逻辑就绪
|
||||
- [x] LOD 0–3 共享骨架:`warrior_cheongrin` lod_01/02/03 与 base 均 75 骨、骨骼名 + ParentIndex 逐项一致 → 切 LOD 骨骼索引不变、无跳变
|
||||
- [x] `xrender-demo` 按 `Space` 播 `dance_1`:CPU LBS 每帧更新 dynamic VB,姿势连贯(`test/golden/m2-*.png`)
|
||||
|
||||
Reference in New Issue
Block a user