Files
mtgodot-poc/docs/CLIENT-GAP.md
T
shenleiandClaude Sonnet 5 b296457e3c 40250 classic: increment 50 (Phase 1 W1) — §1.5 client version, §1.10 text codec, §2.2 two-packet merge
§1.5 CG_CLIENT_VERSION: send_client_version() now branches on locale. EUROPE
(this project's locale) sends CGClientVersion2 (header 0xf1, 67B, timestamp
"1215955205"); non-EUROPE sends CGClientVersion (0xfd) + CMake-injected
MT_BUILD_TIMESTAMP in C __TIMESTAMP__ form. New ClassicSession::set_executable_name()
(default "metin2.bin"; TODO(W0): M2Client passes OS::get_executable_path().get_file()).
m_version_sent set only after send_fixed() succeeds. wire_classic.h gains
CGClientVersion2 + size-table entry.

§1.10 fixed-length text: new extension/src/net/text_codec.h — to_wire()/
from_wire_str() apply a hard byte cap without splitting a UTF-8 multibyte
sequence; godot::String overloads are __has_include-guarded so mtnet stays
godot-free. classic_session.cpp routes 8 fixed-length name/sign/comment fills
through to_wire(); parser reads GC_CHAR_ADDITIONAL_INFO name via from_wire_str().
New net_text_codec_test (ctest net.text_codec). Locale codepage (CP949/CP1252)
transcoding remains a follow-up; wire bytes are currently UTF-8 (ASCII round-trips).

§2.2 two-packet PC/NPC merge: ClassicParser GC_CHARACTER_ADD drops invisible
races (20025/20038/20039); PC/NPC only stash a bare Entity into m_pending_actor;
everything else spawns immediately with a name from root/npclist.txt (new
npc_names.h + set_npclist_path(); $MT_ASSETS/$M2_ASSETS fallback; TODO(W0):
M2Client passes the asset root). GC_CHAR_ADDITIONAL_INFO with no pending entry
records m_last_error + m_unhandled_headers and drops (never half-creates);
on a hit it merges the pending Entity with the additional-info's 9 fields into
one mut_spawn_full() then erases the pending entry. New pending_actor_count()
test hook; net_classic_session_test updated (version assertions -> 0xf1;
stash -> merge -> spawn, immediate monster spawn, invisible-race drop,
no-pending additional-info is a no-op). Depends on W2 §2.2 steps 4-5
(entity_store mut_char_info touch / mut_shop_sign cleanup) — the merge works
with current W2 code as-is.

Build + ctest 17/17 (16 + net.text_codec); netbridge_test, p10_test green.
Docs: CLIENT-GAP.md W1 row + increment-50 change-log entry; CLIENT-GAP-FIX.md
§1.5/§1.10/§2.2 status + facts and C.5 batch record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
2026-09-02 15:51:48 +09:00

1028 lines
103 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 40250/ClientVS22 → mtgodot-poc 1:1 功能差距
> 对照范围:`../40250/ClientVS22/`40250 客户端源码及其运行时模块)以及本工程当前实现。
> 当前工程默认运行 `MT_PROTOCOL=classic``extension/src/net/wire.h` 仅属于显式选择的 m2dev
> 后端,当前 40250/ClientVS22 对照应优先使用 `extension/src/net/classic/wire_classic.h`。
>
> 本文按“协议、数据、交互、表现均与原客户端等价”的严格 1:1 标准审计;
> `CLIENT-ROADMAP.md` 中的“P0–P10 首版完成”只表示主要纵向流程已有首版,不等于 1:1 完成。
> 渲染细节另见 `PARITY-GAP.md`,动画、材质和资产管线另见 `BACKLOG.md`,网络实现另见
> `CLIENT-PORT.md`。
>
> **本文末「ClientVS22 实现细节审计(2026-09-02)」每一条的具体修改细节见
> [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md)**(逐条给出 40250/ClientVS22 的参考
> 源码位置、常量与算法,以及本工程要改的文件/函数;其附录 A 列出本节中
> 需要订正的条目,分「参考行为不符 / 表述需加限定 / 低估了当前差距」三档)。
>
> 更新日期:2026-09-02(对照 40250/ClientVS22 复核)
>
> **状态列图例**:✅ 达到本文末“1:1 完成判据” · 🟡 有首版 / 部分语义 · ⬜ 未实现 ·
> ❌ 明确不做(超出 40250/ClientVS22 参考范围,除非另立需求)。
## 开发执行约定:并行工作流与进度同步
本文按“可并行开发”维护,不要求按章节号从 1 到 10 串行实现。章节号只表示差距
归属;实际开发按工作流拆分,使用稳定接口、测试替身和集成门禁降低互相等待。
所有工作流都以 `40250/ClientVS22/` 的源码和当前 Classic 协议为唯一对照基线。
### 并行拆分
| 工作流 | 负责范围 | 主文件边界 | 依赖 / 集成点 |
|---|---|---|---|
| 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 验证,真实服务端留在集成门禁 |
| 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 / 地图切换钩子;不得把渲染占位逻辑写入协议层 |
| W5 打包、平台与验收 | §10、Mac App 自包含资源、架构 / 签名、发布配置、验收脚本 | `project/export_presets.cfg`、打包 / 发布脚本、`extension/CMakeLists.txt`、发布验收文档 | 可与 W1–W4 并行;集成阶段消费最终构建产物,不能用“能启动”代替资源和签名验收 |
### 并行规则与集成门禁
1. **G0:接口冻结后再分工。** W0 先定义并记录 `enter_game``warp``world_reset`、实体快照、
UI 信号和测试 fixture;接口变更必须在两份差距文档中先登记影响范围,再通知相关工作流。
2. **按文件边界并行。** 同一文件只能有一个工作流作为主维护者。需要改共享文件时,提交“接口变更”
给 W0 集成,不在多个分支中各自复制一份核心实现。
3. **允许用替身解耦。** W1 可用 loopbackW2/W3 可用固定实体和包 fixture,W4 可用最小地图 / 资源,
W5 可用 debug 构建先验证流程;替身通过后仍必须在集成门禁替换为真实链路。
4. **G1:接口集成。** 每个工作流先通过自己的单元 / 回环 / GDScript 回归,再由 W0 合并共享接口,
执行 C++ 扩展构建、原生网络测试和受影响的 Godot 测试。
5. **G2:真实验收。** 连接、DirectEnter、Warp 换图、双账号玩法、Mac App 资源包、架构 / 签名和
视觉 / 输入体验必须按对应验收项逐条记录;受限环境下的 `skip` 只能标为“未验证”,不能标为通过。
6. **合并顺序不等于开发顺序。** 只要依赖接口已经冻结,W1–W5 可以同时开发;只有触及共享状态或
集成门禁时才回到 W0 排队。
### 当前并行进度(2026-09-02
| 工作流 | 状态 | 当前已知进展 | 下一步 |
|---|---|---|---|
| W0 公共基础 / 集成 | 🟡 | DirectEnter、Classic Warp endpoint、`world_reset`、场景重建首版已落地;增量 49 接口冻结批次(`Entity` 补字段 + `mut_spawn_full/mut_ownership/mut_map_bgm/drain_dirty``bgm_changed` 信号、`bgm_director.gd`、§8.6 键位收敛)已提交 | Phase 1 4 工作流并行完成后执行 G1 集成 |
| W1 协议 / 账号会话 | 🟡 | 增量 50:§1.5 版本包按 locale 分支(EUROPE 发 `CG_CLIENT_VERSION2` 0xf1 + `"1215955205"`,其余发 0xfd + `MT_BUILD_TIMESTAMP`)、可执行名 setter;§1.10 `text_codec.h` UTF-8 安全定长文本裁剪 + `net_text_codec_test`,会话侧所有定长 name/sign/comment 填充改走 `to_wire()`;§2.2 `GC_CHARACTER_ADD` 按 invisible-race 丢弃 / PC·NPC 暂存 `m_pending_actor` / 其余即时 `mut_spawn`(名字取自 npclist.txt),`GC_CHAR_ADDITIONAL_INFO` 无暂存则记录并丢弃、命中则合并 9 字段 `mut_spawn_full` 后清表 | 真实 locale codepage 转码、真实双连接、enter delay;GDScript 名称长度校验统一(Phase 2/W3 |
| W2 实体 / 世界 / 战斗 | 🟡 | 地图实体 / 掉落清理、地图重建首版及离线回归已覆盖;`drain_dirty` / `mut_spawn_full` 骨架可用 | §2.1 两层模型 + 可见性、§2.3 `mut_spawn→mut_spawn_full`、§2.4 点数数组尾部、§2.5 应用顺序 + affect_flags |
| W3 玩法 UI / 输入 | 🟡 | 多数窗口、背包 / 任务 / 社交、键位和输入已有首版;§8.6 数字键 / F1–F4 / Ctrl+数字入口已收敛 | §8.3 帮助窗;§8.6 剩余逐行键位与表情包 |
| W4 渲染 / 资产 / 音频 | 🟡 | GR2 / `.msa` / `.mse`、地图 / 水体 / 天气 / BGM 部分链路已有;`bgm_director.gd` 消费点已就位 | §9.1 地图 BGM 曲目解析 + 音量、§9.2 后台恢复曲目、§9.3 生命周期单一属主 |
| W5 打包 / 发布 / 验收 | ⬜ | 已有 Godot 导出配置和构建入口 | 自包含资源包、Intel / Apple Silicon、签名和 Mac App 验收 |
### 进度同步是交付条件
开发过程中的每个批次都必须同步本文和 [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md),包括“开始、完成、
阻塞、重新验证”四类状态变化。代码已经提交但文档没有同步时,该批次仍视为**未完成**。
每次同步至少记录:
- 工作流 / 条目编号、当前状态(⬜ 未开始、🟡 开发中、✅ 已完成、⚠️ 阻塞、🔎 待真实验收);
- 参考源码依据、实际修改文件和公开接口变化;
- 自动化测试命令及结果;无法运行或被 `skip` 的测试和原因;
- 真实服务端 / Mac App 验收证据,仍缺什么;
- 已知风险、与其他工作流的冲突 / 依赖、下一批次和负责人。
同步位置固定为:本文对应差距表 / 变更记录负责“全局状态”,`CLIENT-GAP-FIX.md` 对应条目和实施进度
负责“实现细节”。文档由 W0 集成维护者统一合并,工作流提交者只更新自己负责的条目,避免并行编辑造成状态
互相覆盖。
> **当前 40250 范围调整(增量 45**:真实 FreeBSD + MySQL 服务端的完整登录 / 功能验收暂缓,
> 不作为本阶段交付门槛;公会相关功能暂缓。当前目标 40250 服务端不提供龙魂玩法,龙魂不纳入本阶段
> 支持范围,已有兼容代码不代表服务端可用;运行时龙魂入口已禁用,但保留 `ds_refine` 等协议兼容代码。
>
> ### 不做清单(❌)
>
> - **拍卖行 / auction house**:当前 40250 默认构建未发现完整的 ClientVS22 拍卖行 UI / handler
> 但 `Packet.h` 和服务端仍保留 `__AUCTION__` 条件编译协议(客户端 `CG_AUCTION_CMD=205`、
> 服务端列表包 `=206`)。当前服务端默认关闭该宏,因此本阶段标注为不做;不能表述为“全源码、
> 协议和资产均不存在”。参考:[Packet.h:158-160](/Users/shenlei/Work/mt/40250/ClientVS22/source/UserInterface/Packet.h:158)。
> - **反作弊(anti-cheat**`CG_HACK`(105 / 0x69) 客户端异常上报,以及桌面外壳的
> `ProcessCRC` / `ProcessScanner` / 内存扫描等。自用客户端不需要,**标注为不做**,
> 除非服务端强制校验才回头做最小上报。
> - **龙魂精炼(当前 40250 目标服务端)**:目标服务端不提供该玩法,本阶段不实现 / 不验收;
> 客户端保留的兼容代码不计入当前支持范围。
>
> ### 变更记录
>
> 以下记录保留当时的增量快照;其中旧协议缺口数量、旧路径名称以及 `0x` 协议编号可能来自当时的
> m2dev / 旧实现,不代表当前 Classic 审计口径。当前结论以“协议覆盖审计”和 `MT_PROTOCOL` 变体
> 说明为准;历史记录中的 `CG_QUEST_CANCEL` 也不代表 Classic 存在同名独立 header。
>
> - **2026-09-02 增量 46W0/W1 公共基础第一批实现)**Classic 角色槽已保存 `lAddr/wPort`
> 新增 `M2Client.enter_game(index)` 并接入 ClientVS22 DirectEnter 的第二次游戏连接入口;
> 选人 / 登录 / 进场发送结果已透传,未知 header 不再 Loading 启发式重同步,已知未处理包不再
> 静默成功;已建立游戏连接后的断线回到 LOGIN。`net_classic_stream_test`、
> `net_classic_session_test`、`netbridge_test`、`p10_test` 已通过(受限环境下本地 TCP bind 子测试自动
> skip)。真实双连接 E2E、
> `MT_CLASSIC_ENTER_DELAY` 移除和版本包 `0xf1` 仍未完成。
> - **2026-09-02 增量 47W0/W2 公共基础第一批实现)**Classic `GC_WARP` 已改为严格按
> `lAddr:wPort` 重连,不再用 `addr==0` 推断“同服原地移动”;连接会保留 login key / 已选角色槽,
> 新增 `world_reset` 信号,并在 Godot 侧清理网络实体、地面掉落、PVP / 决斗、目标、交互窗和
> 地图局部状态后重建地图、HUD 小地图、Atlas、环境和玩家位置。`net_entity_test`、
> `net_classic_session_test`、`gamescene_test`、`p9_test`、`p2b_test`、`netbridge_test`、
> `p8_test` 已通过;受限环境下 localhost TCP 目标测试仍自动 skip。真实 40250 换图 / 第二次
> 连接 E2E、地图 BGM 选择和 `MT_CLASSIC_ENTER_DELAY` 仍待完成。
> - **2026-09-02 增量 50Phase 1 W1**:落地 `CLIENT-GAP-FIX.md` §1.5 / §1.10 / §2.2。
> §1.5`ClassicSession::send_client_version()` 改为 locale 分支——EUROPE(本项目 locale)发
> `CG_CLIENT_VERSION2`header `0xf1``CGClientVersion2` 67Btimestamp 固定 `"1215955205"`),
> 非 EUROPE 发 `CG_CLIENT_VERSION``0xfd`+ CMake 注入的 `MT_BUILD_TIMESTAMP`C `__TIMESTAMP__`
> 形态);新增 `set_executable_name()`(默认 `"metin2.bin"``// TODO(W0)` 由 M2Client 传
> `OS::get_executable_path().get_file()`);版本包只在 `send_fixed()` 成功后置 `m_version_sent`。
> `wire_classic.h` 补 `CGClientVersion2` 结构 + 尺寸表项。§1.10:新增 `extension/src/net/text_codec.h`
> —— `to_wire()/from_wire_str()` 做 UTF-8 安全的按字节裁剪(不切多字节序列,硬字节上限),
> `godot::String` 重载用 `__has_include` 守卫使 mtnet 仍无 godot 依赖;`classic_session.cpp` 里
> create/change name、whisper、私店招牌、公会等级名 / 公告、`ANSWER_MAKE_GUILD`、好友增删共 8 处
> 定长填充改走 `to_wire()`parser 侧 `GC_CHAR_ADDITIONAL_INFO` 名字读取走 `from_wire_str()`
> 新增 `net_text_codec_test`CJK 24B 收下 / 27B 截到 24B / 多字节边界 / ASCII 往返 / 读端悬挂字节)。
> 真实 locale codepageCP949 / CP1252)转码仍为后续项,当前 wire 字节即 UTF-8(ASCII 往返一致)。
> §2.2`ClassicParser` `GC_CHARACTER_ADD` —— `is_invisible_race`20025/20038/20039)直接丢弃;
> `bType==PC||NPC` 仅把裸 `Entity`vid/race/type/坐标/朝向/速度/state·affect flag)暂存进
> `m_pending_actor`,不碰世界模型;其余(怪 / 石 / 门 / 物件)即时 `mut_spawn()`,名字取自
> `root/npclist.txt`race→code,新增 `npc_names.h` + `set_npclist_path()` setter`$MT_ASSETS`/
> `$M2_ASSETS` 回退,`// TODO(W0)` 由 M2Client 传资源根,无表时名字为空)。`GC_CHAR_ADDITIONAL_INFO`
> —— 无对应暂存则写 `m_last_error` + `m_unhandled_headers` 并丢弃(绝不半创建);命中则把暂存
> `Entity` 与附加信息 9 字段(name/guild/level/alignment/pk_mode/empire/parts[armor/weapon/hair]/
> mount_vnum)合并为一条,`mut_spawn_full()` 落地后 `erase` 暂存项。新增 `pending_actor_count()`
> 测试钩子;`net_classic_session_test` 更新(版本包断言改 `0xf1`/`CGClientVersion2`;新增两包
> 暂存 → 合并 → 落地、怪即时生成、invisible-race 丢弃、无暂存附加信息为空操作的覆盖)。
> 依赖 W2 §2.2 步骤 45`mut_char_info` touch-create / `mut_shop_sign` 裸 `Entity` 清理)——
> 本次合并逻辑与当前 W2 代码兼容。C++ `ctest` 17/17(原 16 + `net.text_codec`)、`netbridge_test`、
> `p10_test` 全绿。
> - **2026-09-02 增量 49W0 接口冻结批次)**:为 Phase 1 的 4 个并行工作流一次性落地跨工作流
> 共享文件的骨架,冻结接口后再拆分。`EntityStore``Entity` 补 `empire / affect_flags /
> owner_vid / state_flags` 字段;新增 `mut_spawn_full()`(§2.3 两包合并的单次落地入口)、
> `mut_ownership()`(§2.5)、`mut_map_bgm()` + `take_bgm_dirty()/bgm_name()/bgm_volume()`
> (§9.1)、`drain_dirty()`(§2.1/§2.5 裸字段更新的可见性重镜像);`mut_char_info()` 不再
> 丢弃 `empire``reset_for_map_change()` 一并清 `m_dirty`。`M2Client`:新增 `bgm_changed`
> 信号,classic / m2dev 两条 pump 回路都消费 `drain_dirty()` → `entity_info` 与
> `take_bgm_dirty()` → `bgm_changed``entity_dict()` 补 `empire / affect_flags /
> owner_vid / state_flags` 键。`ClassicParser`BGM 名 / 音量从
> `GC_MAIN_CHARACTER3_BGM` / `GC_MAIN_CHARACTER4_BGM_VOL` 路由到 `mut_map_bgm()`(此前直接
> 丢弃),新增 `m_pending_actor` 暂存表成员(W1 在 §2.2 填充两包合并逻辑)。Godot 侧新增
> `project/bgm_director.gd``M2Client.bgm_changed` → `Audio.play_bgm`,把地图 BGM 消费点
> 从 `net_world.gd` 拆出,避免 W2/W4 抢改同一文件),`game_scene.gd` 装配并 `bind()`
> `game_scene.gd` 键位按 §8.6 / §4.8 收敛:数字键 1–4 → 快捷栏 0–3、F1–F4 → 快捷栏 4–7、
> Ctrl+1..9 → `_emote()` 统一入口(表情包与本地表情动作由 W3 在 §8.6 补全)。
> C++ `ctest` 16/16、`netbridge_test` / `gamescene_test` / `p9_test` / `netplay_test` /
> `system_menu_ui_test` / `skill_test` / `combat_fx_test` / `skill_fx_test` /
> `player_motion_test` 全绿。BGM→曲目解析与音量应用、§2.2 合并逻辑、§8.6 剩余键位为 Phase 1
> 各工作流范围。
> - **2026-09-02 增量 48(并行开发协议)**:本文件与 `CLIENT-GAP-FIX.md` 改为“公共基础 +
> W1–W5 并行工作流 + 集成门禁”的执行格式,不再把附录 C 的章节顺序当成唯一开发顺序;明确
> 共享文件主维护者、接口冻结、测试替身、真实验收和冲突处理规则。新增硬性要求:每个批次在开始、
> 完成、阻塞或重新验证时,都必须把状态、改动文件、测试证据、风险和下一步同步回两份文档。
> - **2026-09-01 增量 45P8 交互边界与龙魂运行时范围)**:补齐 NPC 点击路由、Cube NPC
> 前置 / 配方材料 / 金币校验与制作状态、商店购买 / 出售数量和错误反馈、交换重复 / 满槽 /
> 金币边界、仓库 / 商城密码与满包回滚、个人摊位标准 Godot 拖放路径;缺失可选 `.sub` 贴图
> 安全降级。龙魂入口运行时禁用,公会和真实服务器验收继续按当前范围暂缓。
> - **2026-09-01 增量 44(交付范围调整)**:真实 FreeBSD + MySQL 完整验收、公会相关功能均
> 暂缓;当前目标 40250 服务端不提供龙魂玩法,龙魂从本阶段支持范围排除。保留本地协议 / 状态 / UI
> 首版与离线回归结果,后续恢复服务端条件后再单独推进 deferred 项。
> - **2026-09-01 增量 43(队伍角色 / 能量 / 私店招牌复核)**:对照 40250 服务端
> `CParty::Update` 修正队伍角色 6 为 `HASTE`(迅捷),补齐 `affects[7]` 的服务端槽位语义,
> 并在 `party_ui.gd` 按 Leadership 10/20/25/35/40 门控攻击/坦克/迅捷/辅助/宗师/防御,
> Leadership 18 门控组队治疗;`p8_test.gd` 覆盖低 Leadership 的禁用路径。Cube HUD 已有
> `POINT_ENERGY` 快照显示;`GC_SHOP_SIGN` 已解析并在 `NetWorld` 以 `ShopSign` Label3D
> 作为无 `.sub` 资源 fallback`GC_SYMBOL_DATA` 也已有会徽下载链路。剩余仍是原版资源、真
> `.sub` 拖放 / 视觉和 Cube NPC 交互;live FreeBSD + MySQL E2E 暂缓。
> - **2026-09-01 增量 42(选魔石窗口闭环复核)**:确认 `GC_SCRIPT` 的 `[SELECT_ITEM]` 已由
> `QuestDialog` 转发到 `SelectItemUI`;选择窗按原 `uiselectitem.RefreshSlot` 过滤魔石 / grade、
> 映射背包格并发送 `CG_SCRIPT_SELECT_ITEM`,关闭发送 0`select_item_ui_test.gd` 已覆盖。仍需
> 完整原版资源、图标和 CJK 输入环境下的视觉验收。
> - **2026-09-01 增量 41(创建公会 UI 闭环)**`GC_REQUEST_MAKE_GUILD` 触发公会创建窗口,
> 对应 `CG_ANSWER_MAKE_GUILD` 已接入名称输入、12 字上限、空名 / 控制字符校验和发送失败提示;
> 新增 `guild_creation_ui_test.gd` 覆盖请求、校验和合法名称应答。服务端敏感词 / 重名错误及实际建会
> 状态仍需真服验收。
> - **2026-09-01 增量 40(强制改名 UI 闭环)**`SimplePlayer.change_name` 已从 classic / ClientVS22
> 角色列表透传到 `M2Client.char_list`;选人页按 40250 原版语义,在角色带该标志时点击“开始”
> 弹出改名窗,校验名称后发送 `CG_CHANGE_NAME`,成功的 `GC_CHANGE_NAME` 刷新槽位并清除标志,
> `AppFlow` 接通请求、成功和失败回传,新增 `char_create_delete_test.gd` 回归覆盖。P1 行不再把
> 改名 UI 列为未实现;仍需真服验证强制改名、重名 / 非法名错误码和服务端状态。
> - **2026-08-31 增量 30EterGrnLib 对齐复核)**:查代码确认以下已落地,表内相关行同步。
> ① **材质 blend / two-sided 改读 GR2 材质信息**`metin2_model::decide_blend/decide_two_sided`
> 优先 `gr2::MaterialInfo.alpha_blend/two_sided` = `dump_materials` 端口 `Material.cpp:233`
> body + weapon 两处;`m2_material` 加 `cull_disabled` shader 变体)。
> ② **LOD 淡入**`LodGhost` 冻结旧 mesh + `lod_fade` uniform0.18s,距离阈值 15% 迟滞)。
> ③ **crossfade 权重改 ease-in `2w²−w³`**,对齐 `GrannySetControlEaseInCurve(0,0,1,1)`。
> ④ **`.msa` motion event 类型分派**`game_scene._on_local_motion_event`sound / effect /
> SCREEN_WAVING 震屏 / SCREEN_FLASHING 屏闪 / FLY·WARP 钩子)。
> ⑤ **角色接触阴影**`project/ui/char_shadow.gd`:强制 `cast_shadow=ON` + 脚下渐变 Decal
> `player_view` / `mob_view` 调用)。
> ⑥ **点选精度**`player_controller._ray_pick_t`:射线 vs 子 mesh 合并世界 AABB slab 相交,
> 无 mesh 回退胶囊)。
> ⑦ **武器挂点复核**:隔离探针实测 weapon MI 原点 == `equip_right_hand` 骨骼原点
> Δ≤0.06 m(旧「偏 10 m」为 cpu_skin / MapCoord 修复前的失效结论);新增 grip
> pre-transform `weapon_pre = mul4x3(weaponInvWorld[0], weaponLocal[0])`(对齐
> `ModelInstanceUpdate.cpp:171` 的 `weaponComposite[0]`),30 cm 位移夹断退回原始手部位姿。
> `03150` 类网格在 .gr2 里就离骨骼原点 ~3 m(资产缺陷,非变换)。
> 新增测试 `eterngrn_polish_test.gd``ctest 10/10` + 29 套 GDScript + iOS 全绿。
> - **2026-08-31 增量 38ESC 系统菜单 + 游戏设置窗)**:
> ① **`system_menu_ui.gd`**`systemdialog.py` 真 uiscriptthinboard 8 键)= `uisystem.py`
> `SystemDialog.__LoadSystemMenu_Default` 的 1:1`system_option_button` → `system_option_ui.open()`
> `game_option_button` → `game_option_ui.open()``change_button` → `client.say(0,"/phase_select")`
> = `net.ExitGame()` 里 `SendChatPacket("/phase_select")`),`logout_button` → `"/logout"`
> = `net.LogOutGame()`),`mall_button` → `"/in_game_mall"``exit_button` → `get_tree().quit()`
> `help_button` → 占位提示,`cancel_button` / 标题栏 → 关。ESC 现在开这个菜单(`game_scene`
> 把 ESC 从直接开 system_option 改成开 system_menu)。
> ② **`game_option_ui.gd`**`gameoptiondialog.py` 真 uiscript= `uigameoption.py`
> `block_{exchange,party,guild,whisper,friend,party_request}_button`(toggle) →
> `client.say(0,"/setblockmode " + str(mask ^ bit))``EBlockAction` 位 1<<0..1<<5,本地
> `_block_mode` 跟踪 + 持久化,服务器回包同步待补);`pvp_{peace,revenge,guild,free}`(radio) →
> `"/pkmode {0,1,4,2}"``__OnClickPvPMode*`);`name_color`/`target_board`/`view_chat`/
> `always_show_name`/`show_damage`/`salestext` radio → 持久化 `user://system_option.cfg [gameopt]`
> `systemSetting.Set*Flag` 等价;渲染侧钩子待补)。
> 新增 `system_menu_ui_test.gd`(两窗装载 + `/setblockmode` 位运算 + `/pkmode` + `/phase_select`/
> `/logout`/`/in_game_mall` + 显示开关持久化 + 二次实例读 cfg)。C++ 无改动;`ctest 10/10` +
> 34 套 GDScript + 导入 + iOS 全绿。
> - **2026-08-31 增量 37(组队成员信息板 + `CG_PARTY_SET_STATE`**
> ① **C++**`wire.h` 加 `CGPartySetState{u16 hdr,u16 len,u32 pid,u8 role,u8 on}`(断言 10
> + `EPartyRole` 枚举(NORMAL 0/LEADER 1/ATTACKER 2/TANKER 3/BUFFER 4/SKILL_MASTER 5/
> HASTE 6/DEFENDER 7);`game_client.send_party_set_state(pid,role,on)`
> `M2Client.party_set_state(pid,role,on)` + bind`get_party()` dict 补 `state`(完整角色字节)
> 和 `affects[7]`。
> ② **`party_ui.gd`** 从「名字 + 一条 HP」重做成 1:1 迁移 `assets/root/uiparty.py`
> `PartyMemberInfoBoard` + `partymemberinfoboard.py` 布局:每员 strip = 角色状态按钮
> (队长可点 → 弹角色菜单 普通/攻击/坦克/迅捷/辅助/宗师/防御 → `party_set_state`;普通 =
> 清当前角色 `on=false`+ 踢出 → `party_leave` = `SendPartyRemovePacket`+ 名字(+★队长)
> + HP gauge + `affects[7]` 非零 → 附加效果 chip(tooltip 带值,槽位名暂定)。点名字 →
> `set_target(vid)`。顶部:EXP 分配开关(`party_set_distribute` 0 不均分 / 1 均分)+
> 组队治疗(`party_use_skill(PARTY_SKILL_HEAL=1, 0)`)。队长判定 = `get_party()` 里本地
> `get_main_vid()` 那条的 `leader`。角色菜单按 40250 服务端 Leadership 门控:攻击≥10、坦克/迅捷≥20、
> 辅助≥25、宗师≥35、防御≥40;治疗要求 Leadership≥18。
> ③ **修 `p8_test` 隐患**`_init` 原来 `_run()` 未 `await``_run` 第一个 `await process_frame`
> 之后的所有断言(含增量 34 的 `shop_ex` 多货架)从未真正生效 —— 改成 `await _run()` 后
> 暴露 `shop_ui.refresh()` 的 `queue_free` 延迟 bug(旧 row 当帧未消失 → `get_child_count`
> 多计),已改 `remove_child` + `queue_free`。
> `ctest 10/10` + 33 套 GDScript + 导入 + iOS 全绿。
> - **2026-08-31 增量 36(系统设置窗 + 私人商店/选魔石补真 uiscript**
> ① **系统设置窗** `project/ui/system_option_ui.gd` —— `UiScript`+`UiBuild` 装
> `assets/uiscript/uiscript/systemoptiondialog.py`;控件绑定逐字对照
> `assets/root/uisystemoption.py``music/sound_volume_controller`(sliderbar) →
> `snd.SetMusicVolume/SetSoundVolume`(接 `Audio.master_bgm/master_sfx` + 即时改在播 BGM 声道);
> `camera_short/long`(radio) → `game_camera.max_dist`(近 11 / 远 20,并夹当前 dist);
> `fog_level0/1/2`(浓/中/淡 radio) → `Environment.fog_enabled` + `fog_density`
> `[0.055/0.018/0.004]``tiling_cpu/gpu`+`apply`(原 CPU/GPU 分块渲染)Godot 渲染器无对应,
> 保留占位。设置持久化到 `user://system_option.cfg`= 原 `systemSetting` 配置文件),
> `setup()` 时读回即时生效,`.msenv` 换 `Environment` 后 `_on_main_set` 重新 `_apply_all`。
> `game_scene`ESC 呼出(`ui_manager` 先关最顶层窗并吃掉事件 → 到 `game_scene` 说明无窗打开)。
> 新增 `system_option_ui_test.gd`(装载 + 音量/镜头/雾绑定 + cfg 持久化 + 二次实例读 cfg)。
> ② **私人商店窗改走真 uiscript** `privateshopbuilder.py`board + TitleBar + NameLine +
> `ItemSlot` 5×8=40 grid + Ok/Close):40 格从 `_win.nodes` 索引,`NameLine`(text) 上盖
> `LineEdit`,背包候选面板挂窗右侧(原客户端靠主背包拖拽,此处点选)。`itemStock` 交互模型
> (拿起 / 落位 / 价格弹窗 / 排序 + display_pos / 撤下 / 개설 / 철수)不变。测试拆出独立
> `private_shop_ui_test.gd`uiscript,缺资产跳过),`shop_cube_mall_test` 私人商店段移除。
> ③ **选魔石窗** `select_item_ui.gd` 早已走真 uiscript `selectitemwindow.py`(增量 33),无需改。
> C++ 无改动;`ctest 10/10` + 33 套 GDScript + 导入 + iOS 全绿。
> - **2026-08-31 增量 35(私人商店开设窗 = `PrivateShopBuilder` 交互模型)**`private_shop_ui.gd`
> 从「前 5 行 checkbox」重做成 1:1 复刻 `assets/root/uiprivateshopbuilder.py` 的
> `itemStock` 模型:左侧背包候选(点一件“拿起”)+ 右侧 40 格(`shop.SHOP_SLOT_COUNT`5×8);
> 拿着件点空格 → 价格输入(`uiCommon.MoneyInputDialog`)→ 落位(`AddPrivateShopItemStock` +
> `itemStock[targetSlot]=(src)`);点已占用格 → 撤下(`OnSelectItemSlot` /
> `DelPrivateShopItemStock`)。개설(OkButton) 把 stock 按格号排序、`display_pos = 格号`、
> 上限 39`PRIVATE_SHOP_ITEM_MAX_NUM` = `TPacketCGMyShop::bCount` cap)打包成
> `M2Client.open_private_shop(sign, [{vnum,count,inv_cell,price,display_pos}])`
> = `TPacketCGMyShop` + `TShopItemTable`×N,字段 1:1)。`shop_cube_mall_test` 私人商店段
> 重写(拿起 / 落位 / 价格弹窗 / 排序 + display_pos / 撤下 / 철수)。C++ 无改动;
> `ctest 10/10` + 31 套 GDScript + 导入 + iOS 全绿。
> NOTE: 面板仍手绘(与 mall_ui / cube_ui 一致),真 `.sub` 版 `privateshopbuilder.py` uiscript 待后续。
> - **2026-08-31 增量 34`SHOP_GC_START_EX` 多货架商店)**`entity_store` 新增
> `SHOP_GC_START_EX`(10) 解析 —— `TPacketGCShopStartEx{u32 owner_vid, u8 tab_count}`
> + `tab_count` × `{ShopTabHead(char name[32]+u8 coin_type=33B), ShopItem[40]}``wire.h`
> 加 `ShopTabHead` + 断言 33`SHOP_TAB_NAME_MAX=32`/`SHOP_TAB_COUNT_MAX=3`)。
> `ShopEntry` 加 `pos`(货架内槽位,买位置用),`ShopTab{name,coin_type,items}` +
> `m_shop_tabs``SHOP_GC_START` 也改成按固定 40 槽数组填 `pos``SHOP_GC_END` 清货架。
> `shop_items()` 仍是 tab 0 的镜像(旧调用不动)。`M2Client.get_shop()` → `{vid, open,
> tabs:[{name, coin_type, items:[{pos,vnum,price,count}]}]}`。`shop_ui.gd``tabs>1` 显示
> 货架按钮,买位置 = `tabIdx*SHOP_SLOT_COUNT(40)+slotPos`1:1 对齐 `uishop.py`
> `GetIndexFromSlotPos`)。`net_entity_test` 加双货架合成包断言(名称 / coin_type / 槽位保留 /
> tab0 镜像 / END 清空),`p8_test` 加多货架 UI + `buy pos 5` / `buy pos 43` 断言。
> `ctest 10/10` + 31 套 GDScript + 导入 + iOS 全绿。
> - **2026-08-31 增量 33(选魔石窗,样板第 2 例)**:新增 `project/ui/select_item_ui.gd`
> ——`UiScript`+`UiBuild` 装载 `assets/uiscript/uiscript/selectitemwindow.py`5×8
> `grid_table`);填格逻辑逐字照 `assets/root/uiselectitem.py.RefreshSlot`:遍历背包前
> `INVENTORY_PAGE_SIZE*2` 格,只留 `item.IsMetin``proto.type == ITEM_TYPE_METIN` = 10
> 且 `GetItemGrade <= 2`= 物品**内部名**最后一位数字,非数字按 0)的物品,最多 54 个。
> 点格 → `client.script_select_item(inventoryCell)`= `net.SendSelectItemPacket`
> `CG_SCRIPT_SELECT_ITEM` 0x0903+ 关窗;ExitButton / 关闭 → `script_select_item(0)`
> `uiselectitem.Close`)。触发链 1:1`quest_dialog.parse_script` 识别 EventManager
> `[SELECT_ITEM]` token → 新增 `select_item_requested` 信号(对应 `PythonEventManager`
> `EVENT_TYPE_SELECT_ITEM` → `interfacemodule.BINARY_OpenSelectItemWindow`)→
> `game_scene` 接到 `select_item_ui.open()`。新增 `select_item_ui_test.gd`token 解析 +
> 过滤 + 选包 + 关窗发 0);`ctest 10/10` + 31 套 GDScript + 导入 + iOS 全绿。
> - **2026-08-31 增量 32(角色状态窗 = 1:1 窗口迁移样板)**:新增
> `project/ui/char_status_ui.gd`——直接用 `UiScript`+`UiBuild` 装载
> `assets/uiscript/uiscript/characterwindow.py` 真布局;数值绑定逐字对照
> `assets/root/uicharacter.py.RefreshStatus`Level/Exp/RestExp、HP/SP、
> STR/DEX/HTH/INT、ATT=`(min|max)+ATT_BONUS+ATTACKER_BONUS`、
> DEF=`DEF_GRADE(+DEF_BONUS)`、MATT=`MAG_ATT+(min|max)MAGIC_WEP`、MDEF、
> ASPD/MSPD/CSPD/EREPointTypes 索引取自 40250/ClientVS22 `Packet.h`)。加点 / 按钮发
> `client.say(0,"/stat ht"|"/stat- ht" …)`,与原 `statusPlusCommandDict` /
> `statusMinusCommandDict` 完全一致;`POINT_STAT>0` 才显示加点按钮。STATUS/SKILL/
> EMOTICON/QUEST 四页做 `SetState` 页签切换(技能 / 表情 / 任务的数据仍在各自专用窗)。
> 名称 / 帮会 / 职业头像取 `get_entity(get_main_vid())`。`game_scene` 挂 V / C 热键。
> 新增 `char_status_ui_test.gd``ctest 10/10` + 30 套 GDScript + 导入 + iOS 全绿。
> 这是「原 `interfacemodule.py` 逐窗口迁移」的样板,后续选道具网格 / 商店 START_EX /
> 私人商店 39 格照抄这套(uiscript 装载 + 逐字数值绑定 + 原聊天命令 / 封包)。
> - **2026-08-31 增量 31(真服 E2E 二轮扫,单会话)**:以
> `MT_NET_TRACE=1 MT_E2E_SWEEP=1 ./build/extension/net_e2e` 完成一次
> auth→选人→PHASE_GAME 会话;出站明文 / 加密字节均已落盘。10 个安全探针全部发送成功、
> 450ms 后仍在线、`unknown_header=0``CG_CHARACTER_POSITION`(5B)、
> `CG_SYNC_POSITION`(16B)、`CG_SCRIPT_SELECT_ITEM`(8B)、脚本取消(Classic 为
> `CG_SCRIPT_ANSWER=29` + sentinel `255`)、
> `CG_PARTY_USE_SKILL`(9B)、`CG_FLY_TARGETING`/`CG_ADD_FLY_TARGETING`(16B)、
> `CG_FISHING`(5B)、`CG_SHOOT`(5B)、`CG_USE_SKILL`(12B)。脚本取消触发 1 条 quest-change
> 钓鱼收到真服 `Please choose a Fishing Pole.`(无鱼竿的预期业务拒绝),其余
> 无业务事件但均无断连 / 无解析错误。被动 `GC_LAND_LIST` 436B 在登录洪流中真服收到并解析。
> 扫描暴露一个代码缺口:`GC_FLY_TARGETING`/`GC_ADD_FLY_TARGETING` 与
> `GC_CHANGE_SKILL_GROUP` 已有 `EntityStore` 实现却未列入 `GameClient` 世界分派,现已补路由并由
> `net_entity_test` 复核。`CG_DUNGEON`、`CG_WARP`、改名 / 符号 / 物品变更等破坏性或独立
> 连接协议本轮未发送,仍保持“待真服验”。
> - **2026-08-31 增量 5(实现复核)**:修正文档中已经落后的功能状态。
> **情侣状态**已不再是“整体未做”:`GC_LOVER_INFO`(70B) / `GC_LOVE_POINT_UPDATE`(5B)
> 已接入 `EntityStore`,并通过 `M2Client.get_lover()` / `lover_changed` 驱动常驻
> `love_ui`C++ 状态测试和 `love_ui_test.gd` 均有覆盖;仍缺完整情侣交互与真服验收。
> **角色 / 装备渲染**已补本地主角的身体盔甲 / 时装 parts、头盔 / 假发 / 发型、右手武器、
> 左手盾牌、`item_proto.specular/100` 球面高光、角色 LOD 迟滞 + 0.18s 淡入、动作切换
> ease-in、`.msa` 音效 / 特效 / 震屏 / 屏闪事件和角色接触阴影首版。**地图 / 材质**行同步
> 记录 16 层 splat、原 UV / 源分辨率、水纹序列 / 水深 alpha、terrain patch 剔除和物体
> Y-up 旋转修复。协议审计总缺口仍为 **24 个 header**,但 `GC_LOVER_*` 已从“声明未消费”
> 清单移除;输入行修正为已支持 F1–F4 与数字键 1–9。
> 本次同时补充“参考模块覆盖索引”,把协议表之外的聊天、TextTail、HUD、UI 运行时、输入 / 相机、
> 登录串场和桌面外壳差距单独列出。
> - **2026-08-31 增量 6(位置同步上行)**:补齐 `CG_CHARACTER_POSITION`(0x0A60) /
> `CG_SYNC_POSITION`(0x0303) 的 wire 结构、尺寸断言、`GameClient` 发送接口和
> `M2Client.character_position()` / `sync_positions()` API;回环测试覆盖姿态包、最多 16 个
> 坐标元素、字段值和空批次 / 超限拒绝。两项已从“缺失 header”移入“已声明但待核”——仍需
> 接入原版的自动碰撞纠偏时机、姿态动作和真服务器端到端验收。
> - **2026-08-31 增量 7(任务应答协议)**:补齐 `CG_SCRIPT_SELECT_ITEM`(0x0903) /
> `CG_QUEST_CANCEL`(0x0906) 的 wire 结构、尺寸断言、`GameClient` 发送接口和
> `M2Client.script_select_item()` / `quest_cancel()` API;回环测试验证选择值与取消包。
> 同时让 `QuestDialog` 支持基础 `[INPUT]` 文本框和 Escape 取消;缺失 header 清单由 22 项
> 降为 20 项。任务窗口仍需接入选道具网格、原版 IME / 输入校验和完整 `EventManager` 标签语义。
> - **2026-08-31 增量 8(组队技能上行)**:补齐 `CG_PARTY_USE_SKILL`(0x0705) 的 wire
> 结构、尺寸断言、`GameClient.send_party_use_skill()` 和 `M2Client.party_use_skill()`
> 回环测试验证技能索引 / 目标 VID。缺失 header 清单由 20 项降为 19 项;队伍技能的
> 冷却、权限和队员世界标记仍待补齐。
> - **2026-08-31 增量 9(飞行目标广播)**:补齐 `GC_FLY_TARGETING`(0x0411) /
> `GC_ADD_FLY_TARGETING`(0x0412) 的 20B wire 结构;`EntityStore` 持久化 shooter 的目标
> VID / 坐标并产生追加标记,`M2Client.fly_targeting` 信号向 Godot 暴露;C++ 实体测试覆盖
> 设置目标、追加目标和坐标回退。缺失 header 清单由 19 项降为 17 项。
> - **2026-08-31 增量 10(技能组切换)**:补齐 `GC_CHANGE_SKILL_GROUP`(0x021D) 的 5B
> wire 结构、`EntityStore` 技能组状态 / dirty 标记、`M2Client.get_skill_group()` 和
> `skill_group_changed` 信号;切换会同时使技能快照失效,C++ 实体测试覆盖状态更新。
> 缺失 header 清单由 17 项降为 16 项。
> - **2026-08-31 增量 11(角色改名)**:补齐 `CG_CHANGE_NAME`(0x010B) /
> `GC_CHANGE_NAME`(0x010C) 的固定长度 wire 结构、`GameClient` 改名发送与角色槽位更新、
> `M2Client.change_name()` 及 `char_name_changed` 信号;回环测试覆盖名称字节和 pid 广播。
> 缺失 header 清单由 16 项降为 14 项。
> - **2026-08-31 增量 12(公会创建请求)**:补齐 `GC_REQUEST_MAKE_GUILD`(0x0731) 的
> 无负载包解析,`GameClient` 产生请求事件,`M2Client.guild_make_requested` 向 Godot 暴露,
> 回环测试覆盖事件消费。缺失 header 清单由 14 项降为 13 项。
> - **2026-08-31 增量 13(主动传送请求)**:补齐 `CG_WARP`(0x0305) 的 4B wire 结构、
> `GameClient.send_warp()` 和 `M2Client.request_warp()`;回环测试验证上行请求及现有
> `GC_WARP` 目标回传。缺失 header 清单由 13 项降为 12 项。
> - **2026-08-31 增量 14(挖矿动作广播)**:补齐 `GC_DIG_MOTION`(0x0308) 的 13B wire
> 结构、`EntityStore` 挖矿动作事件、`M2Client.dig_motion` 信号和 `NetWorld` 朝向 / 动画
> 元数据处理;实体、回环和 Godot 桥接测试覆盖。缺失 header 清单由 12 项降为 11 项。
> - **2026-08-31 增量 15(公会符号下载)**:补齐 `CG_SYMBOL_CRC`(0x0723) / `GC_SYMBOL_DATA`(0x0732)
> 的 16B / 8B 结构和原始数据流;`MarkClient` 支持 CRC 请求与变长符号文件接收,
> `M2Client.download_guild_symbol()` / `get_guild_symbol()` / `guild_symbol_ready` 已接入,
> 回环测试覆盖 CRC 字段、guild id 和数据长度。缺失 header 清单由 11 项降为 9 项。
> - **2026-08-31 增量 16(钓鱼协议)**:补齐 `CG_FISHING`(0x0B01) / `GC_FISHING`(0x0B10)
> 的 5B / 10B wire 结构、旋转方向换算、`EntityStore` 事件和 `M2Client.fishing()` /
> `fishing_event`;回环与 Godot 桥接测试覆盖。缺失 header 清单由 9 项降为 7 项。
> - **2026-08-31 增量 17(副本协议)**:补齐 `CG_DUNGEON`(0x0B02) / `GC_DUNGEON`(0x0B11)
> 的 4B / 5B wire 结构,支持目的地坐标变长 body,接入 `M2Client.request_dungeon()` /
> `dungeon_event`;实体、回环和 Godot 桥接测试覆盖。缺失 header 清单由 7 项降为 5 项。
> - **2026-08-31 增量 18(领地与观战者)**:补齐 `GC_LAND_LIST`(0x0B12) 的 4B 头 + 24B
> 元素变长解析,以及 `GC_OBSERVER_ADD/REMOVE/MOVE`(0x0B200x0B22) 的 12B / 8B wire
> 结构;`EntityStore` 保存领地和观战者状态,`M2Client.get_land_areas()` /
> `get_observers()` 与 `land_areas_changed` / `observer_event` 已接入;小地图现在消费观战者
> 状态并绘制紫色菱形标记。实体、回环、Godot 桥接和 `p9_test.gd` 覆盖。缺失 header 清单由
> 5 项降为 **1 项**(仅剩 `CG_TEXT`,参考端未发现实际发送方)。
> - **2026-09-01 增量 39`CG_TEXT` wire 尺寸覆盖)**:按 40250 `TPacketCGText` /
> `CInputHandshake::Analyze` 补齐 `CG_TEXT` 的 1 字节固定前缀和 classic `packet_size_cg()`
> 表项,并加入 wire 回归断言。该包后续文本由服务端按换行特殊解析,仅作为握手阶段管理通道;
> Cube 继续使用已验证的 `CG_CHAT`/`CHAT_TYPE_COMMAND`,不把管理命令暴露为普通游戏 API。
> 主网络层缺失 header 清单由 1 项降为 0 项。
> - **2026-08-31 增量 19(钓鱼动作消费)**`NetWorld` 和 `NetPlay` 现在消费
> `fishing_event` 的 START / STOP / REACT / SUCCESS / FAIL 子头,记录方向并尝试驱动远端及
> 本地主角的钓鱼动作;FISH 子头按参考客户端语义保留为鱼获物品事件,不误当作角色 VID。
> `combat_fx_test.gd`、`netbridge_test.gd` 覆盖动作分发。
> - **2026-08-31 增量 20(钓鱼结果日志)**`ChatUI` 消费 `GC_FISHING` 的 SUCCESS / FAIL /
> FISH 事件,将命中、失败和鱼获物品写入系统 / 战斗日志;`chat_test.gd` 覆盖结果路由。
> - **2026-08-31 增量 21(小地图领地标记)**:小地图继续消费 `GC_LAND_LIST`,把全局厘米坐标
> 的领地矩形绘制为公会金色 / 中性青色边框;`p9_test.gd` 补充领地数据供 headless 重绘路径
> 自检。观战者紫色菱形与领地边框均已接入,仍缺原版 minimap 贴图和 Atlas 交互。
> - **2026-08-31 增量 22(小地图缩放)**`minimap.gd` 新增 `set_scale()` / `get_scale()`
> 对齐原 `CPythonMiniMap::SetScale` 的像素 / 米缩放语义;鼠标滚轮在小地图区域内调整缩放,
> `p9_test.gd` 验证缩放会改变世界坐标投影。仍缺原版贴图、Atlas 拖动和坐标查询窗口。
> - **2026-08-31 增量 23(飞行目标消费)**`NetWorld` 为每个 shooter 保存
> `FLY_TARGETING` / `ADD_FLY_TARGETING` 目标队列;`GC_CREATE_FLY` 在终点 VID 缺失时回退到
> 队列中的目标 VID 或全局厘米坐标,补上原版 `SetFlyTarget` / `AddFlyTarget` 的关键语义。
> `netbridge_test.gd` 覆盖无终点 VID 的投射物创建;真实弹道模型、命中时机和特效仍待补齐。
> - **2026-08-31 增量 24(弓技能发射时机)**`SkillTable.is_ranged()` 根据
> `weapon_limit=BOW` 识别远程技能;`GameScene` 为弓技能暂存 `CG_SHOOT`,在 `.msa` 的 FLY
> motion event 到达时发送,超时自动清理,避免近战技能误发。`skill_test.gd` 与
> `eterngrn_polish_test.gd` 覆盖远近程识别及一次性发射。
> - **2026-08-31 增量 25Atlas 大地图窗口)**:新增 `AtlasUI`,复用地图区块
> `minimap.dds` 构建 Atlas 底图;支持 M 键打开 / 关闭、地图名称、玩家位置(全局厘米)显示、
> 左键拖动和右键复位。无底图资源时仍保留可交互坐标查询;`atlas_test.gd` 与
> `gamescene_test.gd` 覆盖窗口装配和输入路径。
> - **2026-08-31 增量 26(队伍地图标记)**:圆形小地图和 Atlas 从 `get_party()` / 实体快照
> 区分队员,绘制绿色队员点、队长环和 Atlas 队员标记;`p9_test.gd` / `atlas_test.gd` 覆盖
> 队伍标记绘制路径。队伍世界标记已不再是纯 UI 列表,仍需按原版颜色和离线状态逐项校准。
> - **2026-08-31 增量 27(输入差距复核)**:根据 `game_camera.gd` 当前实现修正文档:桌面右键
> 拖拽环绕、滚轮缩放和触屏单指 / 双指手势已经存在,不再列为缺失功能;剩余差距改为相机
> 模式、灵敏度 / 键位持久化及窗口冲突规则。
> - **2026-08-31 增量 28(移动速度校准)**`GC_CHANGE_SPEED` 已暴露实体的
> `moving_speed``NetPlay` 在主角 `entity_info` 到达时调用 `PlayerController.set_server_speed()`
> 以原版 100 为基准缩放本地步行 / 奔跑预测(0.253.0 限幅),`netplay_test.gd` 覆盖 150 → 1.5×。
> - **2026-08-31 增量 29(攻击速度校准)**`GC_CHARACTER_ADD[2]` / `GC_CHARACTER_UPDATE`
> 的 `attack_speed` 已进入 `EntityStore` / `M2Client.entity_dict()`;本地主角的
> `POINT_ATT_SPEED` 也通过 `get_points()` 暴露。`NetPlay` 按 40250/ClientVS22 的 `attack_speed / 100.0`
> 动作速率换算自动攻击间隔(0.25–3.0 倍限幅,100 → 0.6 秒),并由 `netplay_test.gd` /
> `net_entity_test` 覆盖实体字段和 150 → 0.4 秒。仍需用真实 `.msa` motion duration、武器 / 坐骑
> 规则和服务端攻击合法性逐帧校准。
> - **2026-08-31 增量 4(真机验证)**`net_e2e` 加 post-2026-08-30 协议探针(env 门禁
> `MT_E2E_CHARCREATE` / `MT_E2E_MYSHOP` / `MT_E2E_CUBE`)。真服 `192.168.21.203`
> 确认 1:1`CG_CHARACTER_CREATE`(77B/u16 job) + `GC_PLAYER_CREATE_FAILURE`(5B) +
> `GC_EMPIRE`(5B) + `GUILD_GC_SKILL_INFO`(17B body) + `GUILD_GC_WAR` + `CG_MYSHOP`
> (38B+13B×N) 全部被真服接受 / 解析正确,无 unknown header。Cube/MALL 送出路径通但
> 需 NPC 交互;`CG_CHARACTER_DELETE` / 龙魂精炼 / 会徽上传(破坏性)仍未真机验。
> - **2026-08-31 增量 3**:两个 ⬜ 项落地首版 —— **私人商店 / 道具商城**
> `CG_MYSHOP` / `GC_MALL_OPEN|SET|DEL` / `CG_MALL_CHECKOUT` + `private_shop_ui` /
> `mall_ui`)、**Cube 제작**`GC_CHAT/COMMAND` 的 `cube …` 总线 → `CubeState` +
> `cube_ui`)。能量值已由 `POINT_ENERGY` 快照接入 HUD;仍缺原版拖放 UI 细节。
> **拍卖行**默认构建未见完整 UI / handler,但协议受 `__AUCTION__` 条件编译控制;当前目标服务端
> 默认关闭 → 标注 ❌ 不做(见上「不做清单」)。
> **反作弊**`CG_HACK` + `ProcessCRC`/`ProcessScanner`)→ 标注 ❌ 不做。
> **「协议覆盖审计」段**改为逐条清单:当时按 `wire.h` vs `Packet.h` 核对,主网络层
> 仍缺 **24 个 header**(位置同步上行 / 战斗广播 / 任务 / 社交 / 改名 / 世界玩法整组;
> `CG_HACK` 已移入「不做」),另有 3 个「已声明未消费 / 待核」。
> - **2026-08-31 增量 2**
> - 角色创建 / 删除流程完成首版并接近 1:1(原为“暂不纳入”)——见新表行
> 「选人 / 建号 / 删号」。
> - 怪物 / NPC 真模型渲染修复:`root/npclist.txt`vnum→代号→目录)解析 +
> `net_world.catch_up()` 补拉协程化 `setup()` 漏掉的初始 spawn 洪流。世界玩法行
> 的“NPC / 怪物首版”由“能出占位胶囊”提升为“能出真模型 + 贴图 + 动作 + 名字牌 /
> 血条”。
> - `GC_EMPIRE`(0x0109) 已暴露为 `M2Client.get_empire()`;选人页显示国家名 + 国旗。
> - 关键 1:1 差距表新增“状态”列。
## 结论
`mtgodot-poc` 已经不是早期“只能登录、移动和攻击”的骨架:登录到 PHASE_GAME、基础战斗、
背包 / 装备、技能 UI、任务、聊天、组队 / 好友、商店 / 交易 / 仓库、精炼,
地图和部分特效都已有可运行首版。
当前的主要差距已从“有没有功能”转为:
1. 部分关键协议只实现了表现包或收包,没有完成原客户端的完整上行语义;
2. 数据模型已扩到 24 个 wear 位置和独立 16 格腰带,并接入部分装备 / 时装换模,但原版
costume / belt 窗口、拖放、tooltip 和部位规则仍不完整;
3. 多数玩法窗口是简化实现,未复现原 Python UI 的完整行为;
4. EffectLib、SpeedTree、材质和世界表现仍有明确占位或近似;
5. 打包资产路径、桌面配置和输入体验尚未形成原客户端等价闭环。
因此当前应称为“核心功能纵切完成”,不能称为 40250/ClientVS22 的 1:1 客户端。
## 已具备的首版能力
- 登录、服务器 / 频道选择、频道负载状态、断线回登录、选人、**建号 / 删号**、进入游戏;
- Metin2 风选人页(背景 / 3D 角色 / 职业书法字 / 国家 + 国旗 / 四维条 / ◀▶ 槽位 / 建删弹窗);
- 基础移动、攻击、选目标、生命 / 法力 / 经验、伤害飘字、affect、死亡与复活窗口;
- 背包、基础装备换模、地面物品、技能窗口和快捷栏读取;
- 聊天、私聊、基础任务对话和任务日志;
- 组队、好友、情侣名 / 爱意值状态、NPC 商店、交易、仓库;
- 公会、公会技能、公会战、会徽上 / 下载、精炼(公会相关验收暂缓);
- 地形、建筑、水体、**NPC / 怪物真模型(vnum→npclist.txt→目录)**、小地图、频道、昼夜和天气首版;
- GR2 模型 / 动画、换装首版、武器 / 盾挂点、距离 LOD / 淡入、角色接触阴影、
`item_proto` 驱动的球面高光和 `.mse` 粒子首版。
这些项目只说明已有入口和基础闭环;是否 1:1 仍以下表为准。
## 关键 1:1 差距
| 优先级 | 状态 | 模块 | 当前实现 | 需要迁移的原客户端语义 |
|---|---|---|---|---|
| P1 | 🟡 | 选人 / 建号 / 删号 / 改名 | Classic 当前使用 `CG_CHARACTER_CREATE=4` / `CG_CHARACTER_DELETE=5` / `CG_CHANGE_NAME=106` / `GC_PLAYER_CREATE_SUCCESS=8` / `GC_PLAYER_CREATE_FAILURE=9` / `GC_PLAYER_DELETE_SUCCESS=10` / `GC_PLAYER_DELETE_WRONG_SOCIAL_ID=11` / `GC_CHANGE_NAME=107`;全部有尺寸断言;`GameClient` 发送接口、角色槽位补丁和 `drain_*_events()``M2Client` 同名建删改 API、4 个建删结果信号及 `char_name_changed``net.loopback_flow` 覆盖建删和改名名称字节 / pid 广播。**真机验证(2026-08-31`net_e2e MT_E2E_CHARCREATE=1`**:建号、`GC_PLAYER_CREATE_FAILURE``GC_EMPIRE` 已验。选人页有建号 / 删号弹窗、强制改名弹窗与国家国旗 | `CG_CHARACTER_DELETE` 破坏性,真机未验;强制改名、重名 / 非法名错误码和服务端状态未真服验收;建号缺外形 / 发型选择与属性再分配 UI(现发 `shape=0` + 职业基础四维);原版选人台座、镜头动画、`OnCreateFailure` 完整错误码文案 |
| P0 | 🟡 | 技能施放协议 | `CG_USE_SKILL``CG_FLY_TARGETING``CG_ADD_FLY_TARGETING``CG_SHOOT` 已有尺寸断言、原生发送接口和 loopback 字节验证;服务端 `GC_FLY_TARGETING` / `GC_ADD_FLY_TARGETING` 已进入 `EntityStore`,通过 `M2Client.fly_targeting` 暴露,且已补入 `GameClient` 世界分派;`GC_CHANGE_SKILL_GROUP` 会更新 `get_skill_group()` 并使技能快照失效;快捷栏先发 `use_skill(skill_id,target_vid)`,再同步 `CG_MOVE` 技能动作;弓技能会在 `.msa` FLY 事件发送 `CG_SHOOT`。真服单会话已发送 `CG_USE_SKILL` / `CG_FLY_TARGETING` / `CG_ADD_FLY_TARGETING` / `CG_SHOOT`,均无断连 / unknown | 仍需实现范围 / 多目标选择器来自动驱动 `ADD_FLY_TARGETING`;冷却、目标合法性、真实弹道和客户端表现也须按技能类型细分;尚未在真服观察到对应 GC 飞行目标推送 |
| P0 | 🟡 | 快捷栏持久化 | 当前有 36 槽(4 页 × 9 格)、F1–F4 切页和 1–9 激活;拖放 / 清除 / 交换会分别发送 `CG_QUICKSLOT_ADD/DEL/SWAP`,服务器 `GC_*` 可恢复;技能冷却读取对应 `.msk``CoolTimeFormula` | 参考端服务端保存 36 槽,但客户端本地显示为 4 页 × 8 格(32 槽);可执行类型只有 `INVENTORY(1)` / `SKILL(2)` / `EMOTION(3)`,没有“命令类型”,当前 type=3/4 映射不兼容。参考端数字键默认只激活本地槽 0–3,Ctrl+1–9 发放表情,F1–F4 激活本地槽 4–7;当前 4×9 切页 / 1–9 全激活是自定义行为,仍需补物品拖放来源、鼠标物品态及完整刷新 |
| P0 | 🟡 | 物品操作 | 已有移动、使用、丢弃、拾取;Shift 选“来源→目标”发 `ITEM_USE_TO_ITEM`Ctrl+右键向当前目标 `ITEM_GIVE`Alt+右键以数量框发 `ITEM_DROP2``ITEM_OWNERSHIP` 会实时更新地面名条;`GC_VIEW_EQUIP` 已保存 11 格检查快照并弹出装备查看窗 | 仍需复刻原鼠标物品态、操作确认 / 错误提示、宝石专用规则,以及装备查看窗的原版槽位图标、3D 纸娃娃与交互 |
| P0 | 🟡 | 装备 / 背包数据模型 | 常规背包维持 90 格;运行时模型已扩展到 24 wear 位置,并将腰带改为独立 16 格;背包右侧动态呈现能力 / 时装 / 戒指 / 腰带和 4×4 腰带格,发包使用原客户端 `INVENTORY` 全局 cell`90 + wear` / `152 + belt`),按 `values[0]` 的原腰带等级规则锁格。`EquipModel` 已把本地主角 `parts[]`(时装优先)和装备槽接到身体 shape / TargetSkin、头盔 / 假发 / 发型、右手武器、左手盾牌,并用 `item_proto.specular/100` 驱动护甲球面高光 | 仍需复刻原 costume / belt 窗口的槽位贴图、分页、tooltip 和鼠标物品态;补齐饰品 / 翅膀等多部件遮挡规则、远端角色同等换装以及逐部位高光 / 卸装复位,不以当前兼容性扩展面板代替原 UI |
| P0 | 🟡 | 移动与战斗同步 | 有 `GC_MOVE`、本地预测、攻击;`M2Client.character_position()` 可发送姿态回报,`M2Client.sync_positions()` 可按原包批量发送最多 16 个坐标,`M2Client.request_warp()` 可发送 `CG_WARP` 主动传送请求;`GC_SYNC_POSITION` 批量坐标纠偏、`GC_CHANGE_SPEED``GC_WALK_MODE``GC_CHARACTER_POSITION``GC_DIG_MOTION` 已恢复到实体状态,`NetPlay``moving_speed/100` 校准主角步行 / 奔跑预测,挖矿广播可驱动目标朝向 / dig 动画,`WALK_MODE` 会驱动远端 walk/run 动画选择;真服单会话已发送 `CG_CHARACTER_POSITION`(5B) 与 `CG_SYNC_POSITION`(16B),均无断连 / unknown | 仍需按原客户端同步窗口、碰撞纠偏触发时机与保留动作处理,而不是只靠距离阈值和 lerp;姿态动作还没有完整 UI / 动画语义 |
| P0 | 🟡 | PVP / 决斗 / 阵营 | `GC_PVP``DUEL_START` 已进入状态层,并以 `pvp_changed` / `duel_started` 信号提供给 Godot;挑战 / 战斗 / 复仇关系会在世界实体上显示标签;实体已有部分 empire / alignment / pk 字段 | 仍需 PK 模式上行、原版名字颜色、可攻击判定、阵营规则和原版目标板 / 决斗 UI |
| P1 | 🟡 | Actor 战斗运行时 | 有骨骼动画、基础 combo / 硬直 / 相机抖动;动作切换按 Granny `EaseInCurve(0,0,1,1)``2w²−w³` 逐骨 crossfade;角色 mesh 支持距离 LOD、15% 迟滞和约 0.18s 新旧 mesh 淡入(`LodGhost` + `lod_fade` uniform);右手武器 / 左手盾按 `weaponComposite[0] = weaponInvWorld[0]·weaponLocal[0]` grip 复合挂 `equip_right_hand`(探针实测 Δ≤0.06 m,非标准米级偏移文件夹断回退);`.msa` motion event 已分派声音、EffectPosition 特效、震屏和屏闪(FLY / WARP 留钩子);玩家 / 怪物强制实时投影并附脚底接触阴影 Decal;实体 `attack_speed` 与本地 `POINT_ATT_SPEED` 已按原版 100 基准影响攻击请求间隔 | 仍需用真实 `.msa` motion duration、武器 / 坐骑规则和服务端合法性校准攻击节奏;另补 combo 取消窗口、精确受击 / 击退、角色碰撞、同步碰撞、武器拖尾、剩余 motion event / 骨骼挂点语义、GPU skin LOD、双持 / 左手武器和目标锁定相机;`03150` 类偏移网格需 per-weapon 偏移数据或修 .gr2 |
| P1 | 🟡 | 飞行物与命中特效 | `GC_FLY_TARGETING` / `GC_ADD_FLY_TARGETING` 保存 shooter 的目标队列和 VID / 坐标;`GC_CREATE_FLY` 可消费队列并生成 tween 发光球 | 对齐 `FlyingObjectManager`:真实技能弹道模型、命中时机 / 命中特效、消失条件、声音与 EffectLib 特效 |
| P1 | 🟡 | 任务 / NPC | 支持 `[ENTER]``[CLEAR]``[NEXT]``[DONE]`、基础 `[QUESTION]``[INPUT]``[SELECT_ITEM]` 文本流程和任务日志;`SelectItemUI` 按原 `uiselectitem.RefreshSlot` 过滤魔石 / grade、映射背包格并发送 `CG_SCRIPT_SELECT_ITEM=114`Classic 没有独立的 `CG_QUEST_CANCEL``quest_cancel()` 通过 `CG_SCRIPT_ANSWER=29` 发送 sentinel `255`;真服单会话已验证选择包 8B,取消触发 1 条 quest-change 且无断连 / unknown | 补原版 CJK IME / 输入校验、QuestButton、立绘、完整 EventManager 标签、世界箭头和屏幕边缘指示;选道具业务回包与完整资源视觉仍需真服 / 资源环境验收 |
| P1 | 🟡 | 商店 / 交易 / 仓库 | 普通 NPC 商店、交易状态和仓库列表已有首版;**`SHOP_GC_START_EX` 多货架已解析(`m_shop_tabs``get_shop()`),`shop_ui` 带货架按钮 + `tabIdx*40+slot` 买位置** | 补图标格网格、数量输入、交易回滚提示、仓库密码 / 改密码、完整拖放规则、`coin_type` 非金币货币显示 |
| P1 | 🟡 | 私人商店 / 道具商城 | **首版**`CG_MYSHOP=55``CG_MALL_CHECKOUT=69``GC_MALL_OPEN=122` / `GC_MALL_SET=128` / `GC_MALL_DEL=129``CGMyShopHead`(38) + `MyShopItem`(13)×N 有尺寸断言;商城状态路由进 `EntityStore``private_shop_ui` / `mall_ui` 和真服探针均已接入。 | 仍缺真 `.sub``privateshopbuilder.py` uiscript、资源视觉和鼠标真拖放物品态 |
| — | ❌ | 拍卖行 / auction house | 不做 | 当前 40250 默认构建未发现完整 ClientVS22 拍卖行 UI / handler;但客户端 `Packet.h` 保留 `__AUCTION__` 下的 `CG_AUCTION_CMD=205`,服务端也保留列表包 `=206`。当前默认服务端关闭该宏,因此本阶段不做;若启用需另行验收,不能称为“协议不存在” |
| P1 | 🟡 | Cube (제작) / 能量系统 | **首版**Cube 走 `GC_CHAT`/`CHAT_TYPE_COMMAND` 文本总线(对齐 `ServerCommand()`),`EntityStore::apply_server_command` 解析 `cube open/close/info/success/fail/r_list/m_info`(配方 `v,c` 列表 + `@`/`&`/`|`/`/` 分层材料 + 金币)→ `CubeState` + `drain_cube_events()``GameClient::send_cube_make/material_info/result_list``/cube make|mInfo|rList`);`M2Client` `get_cube`/`cube_make`/`cube_request_*` + `cube_opened`/`cube_closed`/`cube_changed`/`cube_result` 信号;`ui/cube_ui.gd`(配方列表 + 材料/金币 + 제작)。`net.entity_store` + `net.loopback_flow` 覆盖。真机(2026-08-31`/cube rList` 送出不断连,但服务器只在玩家站到 cube NPC 前才回应 → `cube open=0`,需 NPC 交互完整验。`POINT_ENERGY` 已由 HUD 快照显示;仍缺原 `uiCube.py` 材料格 3D 图标 / 拖放 / 결과 애니、NPC 前完整验 send 命令字 |
| P1 | 🟡 | 组队 | 邀请、接受、离队;**成员信息板(`party_ui.gd`1:1 迁移 `uiparty.PartyMemberInfoBoard`):每员角色状态按钮(队长弹菜单 → `CG_PARTY_SET_STATE=75` 分配 攻/坦/迅捷/辅/宗/防 或踢人)+ 名字(★队长) + HP gauge + `affects[7]` 附加效果 chip;顶部 EXP 分配开关(`party_set_distribute`+ 组队治疗(`party_use_skill(1,0)`)**;角色 / 治疗控件已按 40250 服务端 Leadership 规则本地门控(攻击≥10、坦克/迅捷≥20、辅助≥25、宗师≥35、防御≥40,治疗≥18);真服单会话 `CG_PARTY_USE_SKILL`(9B) 后仍在线;小地图 / Atlas 绿色队员点 + 队长环 | 仍需真版 `.sub` 图标 / 颜色、队伍技能冷却 / 权限、离线颜色语义和真实两账号组队验收 |
| P1 | 🟡 | 好友 / 情侣 | 好友列表、上线状态和私聊入口已有;`GC_LOVER_INFO`(70B) / `GC_LOVE_POINT_UPDATE`(5B) 已有尺寸断言、`EntityStore::LoverInfo` 状态、`M2Client.get_lover()` / `lover_changed``love_ui` 常驻显示伴侣名和 0–100 爱意值;`net.entity_store``love_ui_test.gd` 覆盖 | 补好友分组和完整 messenger 行为;情侣仍需完整交互 / 状态图标与 affect 表现、断线 / 切图生命周期、原版文案和真服务器端到端验收 |
| P1 | 🟡 | 公会(暂缓) | 信息、成员、技能、公会战、会徽上 / 下载和部分操作已有首版;`GC_REQUEST_MAKE_GUILD=82` 已解析并以 `M2Client.guild_make_requested` 通知;公会窗已弹出创建名称输入并发送 `CG_ANSWER_MAKE_GUILD``CG_GUILD_SYMBOL_CRC=113` / `GC_GUILD_SYMBOL_DATA=133` 已支持原始符号文件下载 | 本阶段暂缓完整权限 / 等级页、公告与日志、成员管理、公会战应答、积分板、领地和公会建筑;恢复公会范围后再做完整真服验收 |
| — | ❌ | 龙魂精炼 | 当前目标 40250 服务端不提供该玩法;本阶段不纳入支持或验收,已有兼容代码仅保留作协议参考 | 若未来服务端增加该玩法,再另立协议、UI 和验收需求 |
| P1 | 🟡 | 世界玩法 | 有 `CG_WARP=65` 主动请求、`GC_WARP=65` 处理、`NetWorld` / `GameScene` 的无条件 `lAddr:wPort` 重连与换图清理首版、`CG_FISHING=82` / `GC_FISHING=89` 钓鱼方向与状态事件、`NetPlay` 钓鱼动作分发、`ChatUI` 鱼获 / 失败日志、`CG_DUNGEON=110` / `GC_DUNGEON=110` 副本请求与状态事件、`GC_DIG_MOTION=134` 挖矿动作广播、`GC_LAND_LIST=130` 领地状态、`GC_OBSERVER_ADD/REMOVE/MOVE=9698` 观战者状态、频道、时间、NPC marker、天气粒子和 mount_vnum 字段;真服登录洪流已收到并解析 `GC_LAND_LIST`(436B),单会话发送 `CG_FISHING`(5B) 后收到“Please choose a Fishing Pole.”业务拒绝且连接保持 | 补完整钓鱼 UI / 鱼获背包落地、副本计时 / 入口 UI、坐骑真模型 / 骑乘动作 / 移速、地图 BGM / 天气配置、地形贴花、DungeonBlock;换图首版已落地,仍需真实 40250 端到端验收 |
| P1 | 🟡 | 小地图 / Atlas | 可画玩家、实体、NPC、任务标记、观战者紫色菱形和 `GC_LAND_LIST` 领地边框;支持 `set_scale()` / 鼠标滚轮缩放;Atlas 支持底图、地图名称、玩家坐标、拖动和坐标查询 | 补原 minimap / Atlas 贴图细节、提示、队员 / NPC 分类、世界箭头、地图驱动天气配置和原版窗口视觉 |
| P1 | 🟡 | EffectLib | `.mse` Particle 已映射到 `GPUParticles3D` | 粒子 `.dds` 目前被程序化径向渐变替代,`.mde` 用 box 占位;需补 EffectMesh、SimpleLight、TexAni、MovingType、骨骼挂点和完整时间轴 |
| P1 | 🟡 | SpeedTree | `.spt` 只嗅探真实 bark / composite 贴图,几何为程序化 proxy | 迁移或离线转换真实树干、叶片、frond、LOD、billboard、风和阴影;当前不能作为 1:1 树木 |
| P1 | 🟡 | 地图 / 材质渲染 | A1 地形、静态物、水和 `.msenv` 环境已有首版;splat 已支持每区块最多 16 个活动层、原客户端 UV / offset 与最大源分辨率,地形拆 patch 做视锥 / 距离剔除;水使用原 30 帧 DDS 序列、水深 alpha 和轻微高度动画;静态物 Y-up 旋转共轭已修正。GR2 blend / two-sided 优先读材质信息,球面高光管线已接 `item_proto.specular`;角色有实时投影 + 接触阴影 | 补其余固定功能材质与多贴图分支、精确 alpha-test / blend / cull、透明排序、逐 patch 几何 LOD、baked / realtime 阴影 mask、血迹 / 施法阵等 Decal、云层和 lens flare;按受控参考帧校准水深、LOD、阴影、光照和材质 |
| P1 | 🟡 | UI 控件语义 | 80 个 `uiscript` 可解析和构建,覆盖约 20 类基础节点 | 解析成功不等于窗口完成;需补动画图片、slot 拖放、scrollbar/listbox 行为、tooltip、radio group、候选窗、原层级 / 锚点 / 焦点 / modal 规则 |
| P1 | 🟡 | UI 窗口行为 | 背包、技能、任务、聊天、社交和商店等有简化窗口;腰带背包、扩展装备和装备查看已有兼容窗口;**选人页已按 Metin2 风重做**;**角色状态窗(`char_status_ui.gd`)已按迁移资产中的 `characterwindow.py` 布局与 `uicharacter.py.RefreshStatus` 逻辑实现,并使用原 `/stat` 加点命令作为迁移样板** | **选魔石窗** `select_item_ui.gd`、**系统设置窗** `system_option_ui.gd`、**游戏设置窗** `game_option_ui.gd``/setblockmode` + `/pkmode` 真命令 + 显示开关持久化)、**ESC 系统菜单** `system_menu_ui.gd``systemdialog.py` + `/phase_select`/`/logout`/`/in_game_mall`)、**私人商店窗** `private_shop_ui.gd`、**组队成员板** `party_ui.gd` 均已按迁移样板做成;这些 Python 文件不在本次 `ClientVS22/source/` 树中,仍需用对应 C++ 常量 / 行为和实际资源验收 | 按 `ClientVS22/source/UserInterface` 的窗口装配、消息、控件和资源行为逐个复核视觉 / 交互细节:帮助窗、Atlas、商城 / Cube uiscript、Guild Building、game_option 的名字颜色 / 伤害数字等渲染侧钩子 |
| P2 | 🟡 | 输入 / 快捷键 | WASD、点地、触屏、右键拖拽环绕、滚轮缩放、双指捏合、I/K/J/O/G/L/Z、当前自定义的 F1–F4 切页和数字键 1–9 激活已接;实体点选从固定半径提升为优先使用子 mesh 合并世界 AABB,无码型实体才回退胶囊近似 | 对齐 ClientVS22 的数字键 14、Ctrl+19 表情、F1–F4 本地槽位 4–7、空格连续攻击、显示名字、鼠标物品态、PK 模式、正式客户端截图和窗口上下文规则;键位重绑定 / 相机灵敏度持久化不是参考端已有能力 |
| P2 | 🟡 | CJK 输入与本地化 | 支持 KEY→VALUE 查表和基础字体注入 | 补 `PythonIME` 中 / 日 / 韩候选窗、复数 / 性别规则、字体自动切换、完整 item / mob 名称与 tooltip 本地化 |
| P2 | 🟡 | 音频 | 有 BGM crossfade、UI 音效池和 3D 音效 | 补地图 BGM 自动选择、`.msenv` 环境循环音、武器 / 技能 / 怪物 / NPC 声音事件和原距离衰减规则 |
| P2 | 🟡 | 客户端外壳 | Godot 工程已有 macOS / iOS / Android 构建脚本 | 补 `config.exe` 对应的分辨率、显示、音量、阴影、语言和鼠标配置;Movie / Web / Logo、补丁器约束、Discord RPC 按发布目标取舍 |
| P0 发布 | 🟡 | Pack 资产统一读取 | `PackMount` / `AssetSource` 已实现并有 roundtrip 测试 | `Metin2World` / `Metin2Model` 等仍有直接 `AssetResolver::resolve()` 的散文件路径;必须统一切到 `AssetSource::read/to_path()`,否则 `.epk/.eix` 打包客户端不等价 |
## 参考模块覆盖索引
下表以 `40250/ClientVS22/source/UserInterface/` 的模块职责为索引,补充上表未展开的基础能力。
“已有”只表示当前有可运行首版;仍需满足本文末的 1:1 完成判据。
| 参考模块 / 能力域 | 当前实现 | 与 40250/ClientVS22 的差距 | 优先级 |
|---|---|---|---|
| `AccountConnector` / `PythonNetworkStreamPhase*` 登录与相位 | auth → game → 选人 → LOADING → PHASE_GAME 已打通;`LoadingScreen`、频道状态探针、Classic DirectEnter 和 Classic `GC_WARP` 重连 / 换图清理均有首版 | 登录 / 选服 / 选频道的原版错误码、表单校验、超时与会话失效文案,真实双连接 / 换图验收、服务器列表刷新和所有相位边界清理仍未逐项对齐 | P1 |
| `PythonChat` / `PythonTextTail` / `PythonPlayer` | 聊天四标签、私聊 / 组队 / 公会 / 喊话路由,实体气泡、名字、HP 条、伤害飘字和本地气泡已有 | 缺称号、帮会名、名字颜色和目标高亮框;`GC_DAMAGE_INFO`、经验 / 金钱变化的原版日志行、notice / big-notice 排版、历史滚动和 `InsultChecker` 过滤未完整迁移 | P1 |
| `PythonPlayer` HUD / affect / target | HP、SP、经验、等级、目标血条、死亡复活窗和 affect 色块条已绑定 | 原版状态栏布局、图标贴图、持续时间倒计时、目标板按钮 / 交互、所有 point 类型和战斗日志格式仍是简化版 | P1 |
| `PythonPlayerInput*` / `CameraProcedure` / `PythonApplicationCursor` | WASD、点地、触屏点选 / 单指相机拖动 / 双指捏合、桌面右键拖拽与滚轮缩放、网格 AABB 点选、相机遮挡淡出和碰撞已有;`GC_CHANGE_SPEED` 会校准本地移动速度 | 缺原版光标状态(talk / attack / buy / sell / pick 等)、相机模式与灵敏度配置持久化、键位配置、窗口冲突和空格连续攻击语义 | P1 |
| `EterPythonLib` / `PythonApplicationModule` UI 运行时 | 80 个真实 `uiscript` 可解析;约 20 类控件、窗口栈、modal、拖动标题栏和通用弹窗已有 | `ani_image``mark`、真实 `grid_table` / scrollbar / list 行为、`.dds` UI 贴图、鼠标物品态、焦点 / 锚点 / 层级和原版九宫格视觉仍未完全对齐 | P1 |
| `PythonEffect` / `PythonFly` / `EffectLib` | `.mse` Particle → `GPUParticles3D`、技能 / 服务器特效触发、基础飞行球和动作事件已有 | `.dds` 粒子纹理、`.mde` mesh、SimpleLight、MovingType / TexAni 时间轴、骨骼挂点、真实弹道 / 命中事件和声音仍缺 | P1 |
| `PythonMiniMap` / `PythonBackground` | 圆形小地图、实体 / NPC / 任务点、观战者、领地边框、队员 / 队长标记、鼠标滚轮缩放;Atlas 已有区块底图、拖动、坐标查询、地图名称和玩家 / 队员标记;地形 / 建筑 / 水体 / 天气和昼夜首版已有 | 缺原 minimap / Atlas 贴图细节、世界箭头、地图驱动天气配置和原版窗口视觉 | P1 |
| `PythonSystem` / `MovieMan` / `PythonApplicationWebPage` / `Discord` | Godot macOS / iOS / Android 构建脚本、基础窗口和音频设置入口已有 | 缺 `config.exe` 的分辨率 / 阴影 / 语言 / 鼠标配置持久化、Movie / Logo / Web 页面、补丁器约束及按发布目标取舍的 Discord RPC | P2 |
| `ProcessCRC` / `ProcessScanner` / `PythonProfiler` | 反作弊明确列为不做 | 若服务端未来强制校验,只补最小协议上报;性能采样、崩溃上报和诊断工具不属于当前 1:1 交付范围 | — |
## 协议覆盖审计
`40250/ClientVS22/source/UserInterface/Packet.h` 是一个按 “To Server / From Server” 分段的全局
enum,不存在 `namespace CG` / `namespace GC`。本文此前的“68 + 109 = 177”以及“当前 0 个 header”
是把旧版 `wire.h`m2dev)与 Classic 实现混在一起后的历史统计,不能继续作为当前覆盖结论。
当前协议审计必须按运行时变体分开:
- **当前默认 Classic**:以 `extension/src/net/classic/wire_classic.h``classic_parser.cpp`
`GameClient` 分派和 `40250/Server/.../packet.h` 逐包核对。Classic 的核心编号与参考端一致,
例如 `CG_CHARACTER_CREATE=4``CG_MYSHOP=55``CG_WARP=65``CG_FISHING=82`
`CG_DUNGEON=110`GC 侧 `GC_PLAYER_CREATE_SUCCESS=8``GC_FISHING=89`
`GC_LAND_LIST=130`
- **m2dev**:只有显式使用 `MT_PROTOCOL=m2dev` 时才核对 `extension/src/net/wire.h`;其中的
`0x0201``0x0802``0x0B01` 等编号不是当前 Classic/40250 对照值。
- `CG_HACK``CG_STATE_CHECKER``GC_RESPOND_CHANNELSTATUS` 等条件能力应单独列为“已排除 / 待
服务器变体验证”,不能从一个总数中静默扣除。
- “header 已声明”只代表 wire / size 层存在,不代表 parser、实体状态、UI 消费或真实服务器流程
已完成;本文以这些层级分别记录状态,不再用单一缺失数量表示全部协议覆盖。
### 账号 / 命令通道
| header | 值 | 说明 |
|---|---|---|
| `CG_TEXT` | 64 (`0x40`) | 40250 Server 的管理文本包,当前 Classic 已有 1 字节固定前缀尺寸表;后续文本由服务端按换行特殊解析。它不在 ClientVS22 客户端 `Packet.h` 的普通 To Server 命名项中,因此不计入 ClientVS22 客户端 header 覆盖数。Cube 继续使用 `CG_CHAT` / `CHAT_TYPE_COMMAND`,不伪造管理命令入口。 |
### 已接线但仍需验收
- `CG_CHARACTER_POSITION`(28 / `0x1C`) / `CG_SYNC_POSITION`(8 / `0x08`)wire、发送接口和
loopback 字节测试已覆盖;真服单会话已分别发送 5B / 16B,均保持在线且无 unknown;仍缺原版
自动触发语义、姿态动作链和服务端位置纠偏回包验收。
- `CG_SCRIPT_SELECT_ITEM`(114 / `0x72`)wire、发送接口和 loopback 字节测试已覆盖;真服单会话
已发送 8B 且无 unknown;基础 `[INPUT]` / `[SELECT_ITEM]` 已接入。Classic 没有独立的
`CG_QUEST_CANCEL` headerEscape 取消通过 `CG_SCRIPT_ANSWER=29` 发送 sentinel `255`
当前实现和测试均覆盖该路径;选择窗的背包魔石网格、grade 过滤和选择回包已由
`select_item_ui_test.gd` 覆盖;仍缺原版输入校验、CJK IME 和有脚本时的真实任务流程验收。
- `CG_PARTY_USE_SKILL`(76 / `0x4C`)wire、发送接口和 loopback 字节测试已覆盖;真服单会话已发送
9B 且保持在线 / 无 unknown,但无队伍技能业务回包;仍缺冷却 / 权限、UI 入口和有效队伍真服
验收。
- `GC_FLY_TARGETING`(71 / `0x47`) / `GC_ADD_FLY_TARGETING`(69 / `0x45`)wire、EntityStore 状态、
`M2Client.fly_targeting` 信号和 `NetWorld` 目标队列消费已覆盖;本轮发现并修复
`GameClient` 未分派这两个 GC header 的 bug;真服本轮未主动推送,仍缺真实弹道模型、命中时机
和服务端广播验收。
- `GC_CHANGE_SKILL_GROUP`(112 / `0x70`)wire、EntityStore 状态、dirty 失效和
`M2Client.skill_group_changed` 已覆盖;本轮发现并修复 `GameClient` 未分派该 header 的 bug
仍缺技能组选择 UI、服务端权限和真服推送验收。
- `CG_CHANGE_NAME`(106 / `0x6A`) / `GC_CHANGE_NAME`(107 / `0x6B`)wire、角色槽位更新、
`M2Client.change_name()``char_name_changed` 已覆盖;选人页按 `change_name` 标志接入
原版“点击开始先改名”流程,并对重名 / 非法名错误码给出状态文案;仍缺真服验收。
- `GC_REQUEST_MAKE_GUILD`(82 / `0x52`):无负载 wire 解析和 `guild_make_requested` 信号已覆盖;公会窗已
完成创建窗口、12 字上限、空名 / 控制字符校验和 `CG_ANSWER_MAKE_GUILD` 应答;仍缺服务端敏感词 /
重名错误回包、实际建会状态和真服验收。
- `CG_GUILD_SYMBOL_CRC`(113 / `0x71`) / `GC_GUILD_SYMBOL_DATA`(133 / `0x85`)16B / 8B wire、`MarkClient` CRC 请求、
变长数据接收和 `M2Client.guild_symbol_ready` 已覆盖;仍缺本地符号缓存失效策略、UI 预览和
真服验收。
- `CG_FISHING`(82 / `0x52`) / `GC_FISHING`(89 / `0x59`)5B / 10B wire、旋转方向换算、
`EntityStore` 事件、`M2Client.fishing_event`、远端 / 本地主角动作分发和 ChatUI 结果日志已
覆盖;真服单会话已发送 5B,收到“Please choose a Fishing Pole.”业务拒绝且无断连 / unknown,
证明包布局和错误路径可达;仍缺持竿后的完整钓鱼 UI、鱼获背包落地和成功流程验收。
- `CG_DUNGEON`(110 / `0x6E`) / `GC_DUNGEON`(110 / `0x6E`)4B / 5B wire、目的地坐标变长 body、
`EntityStore` 事件和 `M2Client.dungeon_event` 已覆盖;仍缺副本计时 / 入口 UI、地图生命周期
清理和真服验收。
- `GC_LAND_LIST`(130 / `0x82`)4B 固定头 + 24B `LandPacketElement` 变长解析、领地状态、
`M2Client.get_land_areas()` / `land_areas_changed` 和小地图领地矩形边框已覆盖;真服登录洪流
已收到并解析 436B;仍缺领地贴图 / 建筑和有实际领地变更时的推送验收。
- `GC_OBSERVER_ADD/REMOVE/MOVE`(9698 / `0x600x62`)12B / 8B wire、持久观战者状态、
`M2Client.get_observers()` / `observer_event` 和小地图紫色菱形标记已覆盖;仍缺真服验收。
- `CG_WARP`(65 / `0x41`)4B wire、`GameClient.send_warp()``M2Client.request_warp()` 和回环
请求 / `GC_WARP` 回传测试已覆盖;仍缺传送确认 UI、冷却 / 失败提示和真服验收。
- `GC_DIG_MOTION`(134 / `0x86`)13B wire、`EntityStore` 事件、`M2Client.dig_motion`
`NetWorld` 朝向 / 动画元数据处理已覆盖;仍缺真实挖矿动作资源、音效 / 特效和真服验收。
- `GC_ITEM_DROP`(23 / `0x17`)ClientVS22 仍保留声明,但客户端没有注册 / 消费;实际掉落流程使用
`GC_ITEM_GROUND_ADD`(26 / `0x1A)`40250 Server 的掉落发送路径也使用后者。当前可以不实现
`GC_ITEM_DROP`,但应标记为“保留声明 / 未消费”,不能宣称两个包已经逐字节等价。
- `GC_LOGIN_KEY`(118 / `0x76`):游戏阶段登录 key 包;`net_e2e` 真机 auth→PHASE_GAME 全程已打通,
但仍需将 auth 成功、游戏连接和登录 key 的实际变体在真服矩阵中单独记录,不能用旧的
`0x0107` / `GC_AUTH_SUCCESS=0x0108` 表述替代。
“header 已声明”也不能直接视为完成:还需检查封包尺寸、变长 body、所有 sub-header、
`EntityStore` 状态、GDScript 信号、UI 消费和真服务器端到端行为。
## 建议迁移顺序
### M1:服务器语义正确
1. 把远程 `SHOOT` 时机、范围目标与技能类型冷却接入现有技能协议;
2. 快捷栏的 `INVENTORY / SKILL / EMOTION` 类型映射、数字键 / Ctrl 表情 / F1–F4 槽位语义、
物品拖放与特殊冷却规则;
3. 细化 24 wear / 腰带 / 装备查看的原版窗口表现,并完成完整物品操作;
4. 移动同步、PVP / 决斗和 Actor 战斗状态机;
5. 用 loopback + 真服抓包为每类协议建立双向验收。
### M2:主要玩法完整
1. 任务完整标签 / 输入 / 选道具 / 指引;
2. 商店 START_EX、仓库密码、商城、私人商店、Cube(拍卖行 40250/ClientVS22 无,除非另起协议);
3. 组队角色、情侣完整交互、领地与公会建筑;公会完整页面暂缓;
4. 钓鱼、副本、坐骑、观战者和 Atlas。
### M3UI 与表现 1:1
1. 以原 `interfacemodule.py` 为窗口清单,逐窗口迁移行为;
2. 完成 tooltip、拖放、光标、IME、快捷键和系统设置;
3. 完成 EffectLib、SpeedTree、材质、地图表现和声音事件;
4. 固定原客户端参考机位、角色、地图、时间和 UI 状态,建立截图 / 录屏签核。
### M4:发布闭环
1. 所有资产调用方统一使用 `AssetSource`,验证纯 pack 运行;
2. 覆盖目标平台构建、前后台、输入和性能;
3. 按服务器要求决定 Discord / Web / Movie 等桌面能力是否迁移(**反作弊 `ProcessCRC` /
`ProcessScanner` 已标注 ❌ 不做**,除非服务端强制校验)。
## 1:1 完成判据
一个功能只有同时满足以下条件才标记为完成。当前 40250 阶段对已明确暂缓的真服、公会和龙魂项不作完成承诺:
1. 原客户端涉及的 CG / GC 包和 sub-header 全部覆盖,并有尺寸断言;
2. 数据状态能正确恢复、重连、切图和清理;
3. UI 操作、错误提示、拖放、快捷键和窗口互斥与原客户端一致;
4. 模型、动画、特效、声音和名字 / tooltip 表现一致;
5. loopback、真服务器端到端、受控截图或交互录屏至少有一种自动门禁和一种人工签核。
在达到以上判据前,应继续使用“首版”“部分完成”描述,避免把“能解析布局”“有一个按钮”或
“能播放动作”写成 1:1 完成。
---
## ClientVS22 实现细节审计(2026-09-02
### 审计口径
本节以当前 `mtgodot-poc` 源码和 `40250/ClientVS22` 源码做静态对照,重点检查
`UserInterface``GameLib``EffectLib``MilesLib``EterLib``EterGrnLib`
`SpeedTreeLib``PRTerrainLib` 中承担实际运行时语义的模块。
它不是简单的功能勾选表,而是把问题分成四类:
- **明确缺陷**:当前代码存在可定位的错误或错误状态转移;
- **未实现**:当前只有接口、占位或协议接线,没有原客户端对应行为;
- **近似实现**:能够运行,但和 `ClientVS22` 的数据、时序或表现语义不同;
- **未验证**:代码路径存在,但还没有真实 40250 服务器、多账号或受控画面证据。
本次指定的 `40250/ClientVS22/source/` 树中没有 `game.py``uicharacter.py``uiparty.py`
Python 脚本;文档中出现的 `assets/root/*.py` 均属于本工程迁移资产 / 历史脚本依据,不应单独
当作 ClientVS22 源码证据。凡只能由这些脚本确认、而在 `ClientVS22/source/` 没有对应 C++
实现或常量的内容,统一视为“迁移资产依据 / 待验证”,不宣称已完成 1:1 源码对照。
参考客户端的核心差异在于:它不是只有网络收发,而是由
`CPythonNetworkStream``NetworkActorManager``CPythonPlayer``ActorInstance`
`CPythonEventManager``PythonIME``MapOutdoor``EffectLib``MilesLib` 共同维护
阶段、Actor、动作、地图、脚本、输入、特效和声音状态。
### 1. P0:连接、协议和生命周期
1. **Classic DirectEnter 已接入,但仍未完成真实双连接验收。** 登录入口仍是 Auth → Game
角色列表保存每槽 `lAddr/wPort` 后,`M2Client.enter_game(index)` 会断开第一条游戏连接,
按角色槽地址重连,并在 DirectEnter 的 SELECT 阶段发送 `CG_CHARACTER_SELECT`
`MT_CLASSIC_ENTER_DELAY` 和真实两次 TCP 连接验收仍待完成。见
[`classic_session.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp)、
[`m2_client.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp)。
2. **游戏中断线的回登录链路已接入,真实 TCP 断线仍待验收。** 已建立游戏连接后的
Classic 失败发 `disconnected``AppFlow` 无条件回 LOGIN;认证 / 游戏连接建立前的失败仍发
`login_failed``ReconnectUI` 已从 AppFlow 主流程移除,但独立文件仍保留作旧测试 / 可选增强。
见 [`m2_client.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp)、
[`app_flow.gd`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_flow.gd)。
3. **跨服或换图的场景重建已完成首版,但还没有真实服务器验收。** Classic `GC_WARP` 已按
`lAddr:wPort` 重连;`world_reset` 会清理旧实体、掉落物、PVP / 决斗、目标和交互窗,
`GameScene` 会重建地图、HUD 小地图、Atlas、环境和玩家位置。仍缺真实换图服务器矩阵、
地图 BGM 自动选择、飞行物 / 特效的完整清理和加载阶段逐项截图验收。
4. **选人和进入游戏吞掉底层发送失败(已修复第一批)。** `select_char()` / DirectEnter
选人 / `enter_game()` 现在透传 `send_fixed()` 结果,`CG_ENTERGAME` 成功后执行 flush
GDScript 入口优先调用 `enter_game(index)`。失败提示和真实发送失败注入仍待补充。
见 [`classic_session.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp)。
5. **客户端版本包是硬编码的。** 当前固定发送 `metin2.bin` 和旧 timestamp,未读取实际
构建版本、文件时间或资源包版本。服务端开启版本校验时,更新客户端后仍可能被当成旧客户端。
见 [`classic_session.cpp:335`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_session.cpp:335)。
6. **Passpod、HackShield、XTrap、StateChecker 等兼容路径没有实现。** 当前选择了不伪造
这些回应;如果目标 40250 服务端启用了其中任意一项,登录或进场可能被断开。应把“不支持”
变成明确的服务器兼容矩阵,而不是让连接表现为随机失败。
7. **未知包的 Loading 重同步问题已修复第一批。** 当前不再扫描后续字节寻找
`GC_CHARACTER_ADD` 边界;未知 header 记录最近包、清空缓冲并断开,保留 `header == 0`
填充跳过。真实加密流验收仍待完成。见
[`classic_stream.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_stream.cpp)。
8. **已知但没有业务处理的包静默丢弃问题已修复第一批。** `ClassicParser` 现在记录阶段 /
header 并返回失败,连接层清空缓冲并断开;可选集成包不会再静默成功。按阶段拆分处理表和
全量业务覆盖仍待完成。见
[`classic_parser.cpp`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_parser.cpp)。
9. **封包覆盖仍停留在结构和尺寸层。** 还需要逐包核对 sub-header、动态 body、实体状态、
UI 消费、发送后的锁定状态、失败回滚和重连恢复,不能仅以 `Packet.h` 中存在 header 作为完成。
10. **文本编码和固定长度字段没有统一策略。** 当前大量使用 UTF-8 `String``std::string`
`memcpy`,没有完整复现 ClientVS22 的本地编码转换。CJK 名称、聊天、私聊、公会名可能
按 UTF-8 字节截断或超过服务端字段边界;角色名校验还存在 `< 24``<= 24` 两种规则。
11. **登录错误、超时、会话失效和服务器列表状态没有完整映射。** 当前更多是把底层字符串
直接显示出来,没有复现 ClientVS22 的错误码、阶段专用文案、服务器刷新和超时处理。
### 2. P0/P1:实体、世界和状态一致性
1. **未知 VID 的普通 Actor 更新包会直接丢失。** `mut_move``mut_set_position``mut_change_speed`
`mut_dead``mut_stun`、物品更新等路径遇到未知实体直接返回,没有 pending state 或重放机制。
角色添加顺序变化时,位置、HP、死亡、动作和装备信息会丢失;`GC_SHOP_SIGN` 当前有独立的
招牌 fallback,不能与普通 Actor 更新混为一谈。见
[`entity_store.cpp:123`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:123)。
2. **`GC_CHAR_ADDITIONAL_INFO` 早于 Spawn 时状态不会被待合并缓存。** `mut_char_info()` 可以
创建 / 更新 Entity,但新建时不发送 Spawn change;如果后续 Spawn 正常到达,NetWorld 仍会
创建节点,若 Spawn 丢失或顺序异常则实体会长期只有数据没有表现层节点。见
[`entity_store.cpp:458`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:458)、
[`net_world.gd:128`](/Users/shenlei/Work/mt/mtgodot-poc/project/net_world.gd:128)。
3. **VID 重用时旧状态可能污染新实体。** `mut_spawn()` 没有完整重置 dead、stunned、mount、
guild、pk、alignment、parts、shop sign、affect 和上一动作状态,只覆盖了基础出生字段。
见 [`entity_store.cpp:75`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:75)。
4. **点数组 API 对部分更新可能残留旧值。** `mut_set_points()` 只复制本次传入数量,不清理
上一次更长数组的尾部;不过当前固定长度 Classic 包路径传入 `POINT_MAX_NUM`,该路径不会
产生部分更新。后续 UI 或其他部分更新调用者仍可能读到旧状态。
5. **实体数据字段不完整。** empire、称号、公会名、公会徽章、名字颜色、alignment 颜色、
target highlight、attackable、weapon mode、sex/job/race motion mode、affect 图标和
剩余时间没有形成完整统一的实体状态。`mut_char_info()` 当前还明确忽略 `empire`
见 [`entity_store.cpp:477`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/entity_store.cpp:477)。
6. **一次性事件没有可靠的生命周期缓存。** 聊天、脚本、特效、飞行物、任务提示等事件主要
通过 drain queue 即时消费;如果 UI 或 GameScene 尚未完成装配,事件可能被消费后无法补发。
7. **Classic Warp 的无条件重连和换图清理已落地首版,仍缺真实 E2E。** ClientVS22 的
`RecvWarpPacket()` 对所有 `GC_WARP` 都按 `lAddr:wPort` 重连;当前 Classic parser 透传
`lX/lY``M2Client` 在重连前发 `world_reset`,并保留 login key / 已选角色槽。`GameScene`
已重建地图和局部 UI;仍需真实服务确认第二次连接、重连后的 `GC_MAIN_CHARACTER` 定位、
地图 BGM / 任务 / 飞行物 / EffectLib 清理。m2dev 后端仍保留自己的 `addr==0` 原地移动语义,
不应与 Classic 对照混用。
### 3. P1:移动、战斗和技能
1. **本地移动是直线位置修改。** 当前控制器直接修改 X/Z,只做地形阻挡检查,没有 Actor 碰撞、
路径寻路、坡度处理、斜坡碰撞、行动预留、移动打断和坐骑状态。见
[`player_controller.gd:221`](/Users/shenlei/Work/mt/mtgodot-poc/project/player_controller.gd:221)。
2. **同步是距离阈值加 lerp。** 当前按固定时间或距离发送移动,回包后按距离阈值 snap/lerp,
没有完整 sequence、authoritative tick、抖动缓冲、预测回滚、动作保留和服务器纠偏时序。
3. **普攻节奏仍是简化实现。** 范围、攻击间隔、硬直和连击窗口均为固定值或简单按攻速缩放,
未使用 race、武器、动作 duration、命中帧、mount 和 Actor battle state。见
[`net_play.gd:34`](/Users/shenlei/Work/mt/mtgodot-poc/project/net_play.gd:34)。
4. **自动攻击没有复现 ClientVS22 的目标预留逻辑。** 参考客户端会持续检查目标存在、死亡、
可攻击、坐骑限制、charge skill、睡眠状态,并预留点击动作;当前主要是检测距离后发攻击包。
参考:[`ClientVS22/PythonPlayer.cpp:207`](/Users/shenlei/Work/mt/40250/ClientVS22/source/UserInterface/PythonPlayer.cpp:207)。
5. **攻击动作、命中时机和网络包没有严格绑定。** 当前 combo 只在 0/1/2 之间循环,动作播放、
hit frame、范围、角度、武器轨迹、击退和受击动作没有由真实 motion data 驱动。
6. **弓箭发射仍有固定超时兜底。** 当前优先消费 `.msa` 的 FLY 事件,但 pending skill 仍由约
1.5 秒定时器兜底;不同技能、攻速、动作长度和网络延迟下可能偏离真实事件,连续释放还可能
覆盖 pending skill。见 [`game_scene.gd:220`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd:220)。
7. **受击状态分裂。** `EntityStore.stunned``NetPlay._hitstun_until` 是两套互不统一的状态,
服务端 stun、客户端硬直、远程受击动作和恢复时机可能不一致。
8. **技能缺少统一合法性检查。** 当前主要检查 skill id 是否大于零,没有统一验证技能归属、等级、
master、cooldown、消耗、目标类型、目标数量、距离、角度、当前动作、PK 和坐骑限制。
`target_vid` 等参数还可能先被转换为无符号整数。
9. **公共网络 API 缺少参数边界检查。** `attack`、交易、仓库、物品和技能接口存在负数、窗口、
cell、display position、count 等参数直接 cast 的情况,服务端拒绝不能替代客户端自身的协议校验。
10. **技能升级的传输方式已与参考端一致,但门控和状态流程不完整。** ClientVS22 本身也通过
`/skillup <skillIndex>` 发包;当前缺少参考端的可升级条件门控(技能组、等级上限、技能点、
死亡 / 变身 / 骑乘等状态)、加号按钮显示规则、失败提示和升级后的技能状态刷新。见
[`m2_client.cpp:850`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp:850)、
参考 [`PythonSkill.cpp:1833`](/Users/shenlei/Work/mt/40250/ClientVS22/source/UserInterface/PythonSkill.cpp:1833)。
### 4. P1:物品、装备、商店和社交
1. **24 个 wear 位置进入数据层,但没有完整视觉和规则。** costume、翅膀、饰品、双持、左手、
anti-flag、遮挡、职业/性别变体、部位隐藏、卸装复位、远程角色换装和逐部位高光仍不完整。
2. **物品拖放不是原版鼠标物品态。** 缺少全局持有物品、鼠标跟随图标、跨窗口拖放、拖动取消、
多格占用、stack split、装备预览和统一失败恢复。
3. **物品 tooltip 只覆盖基础信息。** sockets、attributes、anti flags、wear flags、requirements、
duration、refine、value、绑定状态、装备对比和本地化描述没有完整迁移。
4. **Safebox 是列表和自动找空位。** 当前 deposit 使用下一个空位,缺少 45 格网格、分页、指定
目标格、仓库内移动、金币存取、密码修改、重试限制、完整错误状态和原版互斥规则。
见 [`safebox_ui.gd:7`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/safebox_ui.gd:7)。
5. **交易缺少事务状态。** 当前是两列文字列表,没有锁定、接受后修改自动取消接受、物品占用、
服务器回滚、交易失败恢复、对方物品变化提示和交易中的背包锁定。
6. **NPC 商店仍是基础列表。** 缺少物品格、图标、购买确认、货币类型、库存限制、busy 状态、
精确错误提示和对应音效。
7. **私人商店是兼容实现。** 虽然 `CG_MYSHOP` 已能发送,但原版库存模型、拖放、价格编辑、
开店前检查、库存锁定、撤下回滚和其他玩家查看流程尚未完整迁移。
8. **快捷栏类型映射和键位语义不完整。** 参考端可执行类型只有 `INVENTORY(1)``SKILL(2)`
`EMOTION(3)`,没有“命令类型”;当前 `m2_client.cpp` 仍把 3/4 映射为 command/emotion
与参考端不兼容。当前还缺表情执行、物品拖入、鼠标物品态、特殊技能冷却、死亡 / 变身 /
换武器后的完整刷新。见 [`m2_client.cpp:878`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/m2_client.cpp:878)。
9. **组队状态是近似定义。** affect 标签仍有暂定映射,队伍技能冷却、队长权限动态变化、离线、
死亡、距离状态和多账号行为还未完成真实验收。
10. **好友、情侣、公会只有首版状态展示。** Messenger 分组、上线/离线、私聊生命周期、情侣
交互、公会权限、公告、日志、成员管理、公会战应答、领地和公会建筑均不完整或暂缓。
### 5. P1:任务、NPC 和世界玩法
1. **Quest EventManager 只支持少量标签。** 当前支持 `ENTER``CLEAR``NEXT``DONE`
`QUESTION``INPUT``SELECT_ITEM``LETTER`,但 `COLOR``DELAY``WAIT``IMAGE`
立绘、样式、滚动、多阶段按钮和 `CONFIRM_WAIT` 等被忽略。见
[`quest_dialog.gd:102`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/quest_dialog.gd:102)。
2. **任务日志不是完整 Quest 系统。** 当前主要是标题、counter 和 clock 文字列表,缺少真实
图标、开始/更新/删除状态、倒计时、QuestButton、世界箭头、边缘指示和点击定位。
3. **钓鱼、副本、坐骑和观战者只有部分协议入口。** 仍缺鱼获落袋、副本入口和计时、DungeonBlock、
坐骑模型与动作、坐骑攻击限制和速度、观战视角以及换图后的完整清理。
4. **PVP、PK 和决斗没有完整规则。** 当前有部分关系和字段,但缺 PK 模式上行、名字颜色、
可攻击判断、阵营限制、保护区、目标板、挑战/复仇和禁止攻击逻辑。
### 6. P1/P2:模型、动画和特效
1. **本地角色先显示胶囊占位体。** GameScene 初始化阶段先创建 Capsule,真实角色稍后加载,
会造成模型闪烁、相机目标高度变化、碰撞体和阴影重新挂接。见
[`game_scene.gd:136`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd:136)、
[`game_scene.gd:811`](/Users/shenlei/Work/mt/mtgodot-poc/project/game_scene.gd:811)。
2. **SpeedTree 仍是程序化代理。** 当前只嗅探 bark/composite 贴图,叶簇 UV 和枝干使用 proxy,
没有真实 `.spt` 几何、leaf table、LOD、impostor、风参数和原始阴影。见
[`tree_placeholder.cpp:190`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/tree_placeholder.cpp:190)、
[`PARITY-GAP.md:77`](/Users/shenlei/Work/mt/mtgodot-poc/docs/PARITY-GAP.md:77)。
3. **EffectLib 只有粒子近似。** `.mse` Particle 已映射到 `GPUParticles3D`,但 `.dds` 纹理被
程序化渐变代替,`.mde` 被 box 代替,SimpleLight、MovingType、TexAni、骨骼挂点和完整时间轴
尚未实现。见 [`effect_player.gd:8`](/Users/shenlei/Work/mt/mtgodot-poc/project/fx/effect_player.gd:8)、
[`effect_player.gd:134`](/Users/shenlei/Work/mt/mtgodot-poc/project/fx/effect_player.gd:134)。
4. **GR2 运行时不是完整 ActorInstance。** 缺 race/weapon/sex motion mode、loop/interrupt/
accumulation、action reservation、mount motion、武器轨迹、击退、倒地、起身和完整死亡动作。
参考:[`ClientVS22/ActorInstanceMotion.cpp`](/Users/shenlei/Work/mt/40250/ClientVS22/source/GameLib/ActorInstanceMotion.cpp:1)。
5. **武器和特效骨骼挂点不完整。** 当前主要覆盖右手武器和部分盾牌,缺双持、左手武器、职业挂点、
武器拖尾、骨骼特效、坐骑挂点、轨迹碰撞和装备切换清理。
6. **材质是固定功能近似。** 缺精确 alpha-test ref、多贴图 stage、color operation、透明排序、
角色专属光照和逐 surface 状态;sphere-map 默认 `spec_power = 0`,没有完整由装备数据驱动。
见 [`m2_material.h:39`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/m2_material.h:39)。
### 7. P1/P2:地图、环境和相机
1. **静态地图只完整接入 Building / DungeonBlock。** Effect、Ambience、地图机关、透明物排序、
portal/area、对象脚本状态和对象 LOD 没有完整迁移。见
[`metin2_world.cpp:367`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/metin2_world.cpp:367)。
2. **Pack 资源读取没有统一。** 虽然 `AssetSource`/`PackMount` 已存在,World、terrain、tree、
water 和 static object 仍有直接 `AssetResolver::resolve()` 的路径,散文件和 `.epk/.eix` 运行
结果可能不同。见 [`metin2_world.cpp:380`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/metin2_world.cpp:380)、
[`terrain_splat.cpp:133`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/terrain_splat.cpp:133)。
3. **地形 splat 虽已支持 16 层,仍非完整固定功能复现。** alpha 含义、texture wrap/filter、
多 stage、patch geometry LOD、skirt、patch 碰撞和 baked shadow mask 叠加规则仍未完成。
4. **水面是序列纹理加近似深度。** 反射、折射、岸线泡沫、法线、精确 blend/filter/wrap、透明
排序和水深标定仍缺。当前浅水透明度已被项目文档标记为未校准。
5. **`.msenv` 只完成基础天空、雾和光照。** 云层实际渲染、环境循环音、lens flare、日夜插值、
天气切换、颜色曲线和原始 ambient/diffuse/specular 关系没有完整实现。
6. **相机不是固定基线机位。** 当前依赖脚本中的 FOV、yaw、pitch、距离和 AABB 射线,缺
`CameraProcedure` 等价模式、灵敏度持久化、平滑碰撞、树木遮挡淡出、目标 Actor 状态和可回放
的受控机位。见 [`PARITY-GAP.md:195`](/Users/shenlei/Work/mt/mtgodot-poc/docs/PARITY-GAP.md:195)。
### 8. P1/P2UI、输入和本地化
1. **uiscript 可解析不等于控件语义完成。** 当前约 80 个脚本可解析,但 `ani_image``mark`
真实 grid/list/scroll、tooltip、item cursor、focus、anchor、modal、layer 和完整 nine-patch
语义仍缺。
2. **大量窗口是手工 Panel。** 交易、仓库、商店、公会、任务、好友、聊天和系统窗口仍大量使用
固定坐标和固定尺寸,没有完整复现原 `Interface.MakeInterface()``.sub` 资源、字体描边、
rect/anchor/z-index 和分辨率缩放。
3. **帮助窗口未实现。** ESC 菜单中的帮助按钮仍只显示“帮助窗暂未实现”。见
[`system_menu_ui.gd:91`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/system_menu_ui.gd:91)。
4. **系统设置部分只是保存数值。** CPU/GPU tiling 不会改变实际渲染,BGM 更换按钮 disabled
名字颜色、伤害数字和部分画质设置没有完整接入渲染侧。见
[`system_option_ui.gd:143`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/system_option_ui.gd:143)。
5. **鼠标物品态和光标状态缺失。** Talk、Attack、Buy、Sell、Pick、NPC 交互光标、鼠标跟随物品、
拖放取消和窗口焦点屏蔽规则没有完成。
6. **键位实现与参考端不一致。** 当前已接 WASD、点地、相机拖动、滚轮、触屏、F1–F4 和数字键,
但当前把 F1–F4 用作切页、数字 1–9 全部激活快捷栏;参考端是数字 1–4 激活本地槽 0–3、
Ctrl+1–9 发放表情、F1–F4 激活本地槽 4–7。另缺空格连续攻击、显示名字、鼠标物品态、窗口
上下文屏蔽和正式截图。键位重绑定、冲突检测及独立相机灵敏度设置不是 ClientVS22 已有能力,
不应伪装成参考端缺失项。
7. **CJK 输入不等价。** 当前使用 Godot `LineEdit`,没有 `PythonIME` 候选窗、composition、
候选确认、输入法切换、特殊文本框行为、复数/性别规则和自动字体切换。
8. **聊天和头顶信息不完整。** 缺 notice/big notice、经验和金币日志、私聊窗口生命周期、历史
滚动、频道过滤、InsultChecker、empire 名字颜色、称号、公会名、PK 颜色、目标框和屏幕空间血条。
9. **任务日志图标仍是占位。** 当前任务列表主要显示文字,真实 icon、状态变化、倒计时和任务导航
尚未接入。见 [`quest_log.gd:8`](/Users/shenlei/Work/mt/mtgodot-poc/project/ui/quest_log.gd:8)。
### 9. P1/P2:音频和应用生命周期
1. **地图 BGM 字段被解析但没有消费。** `GC_MAIN_CHARACTER3_BGM`
`GC_MAIN_CHARACTER4_BGM_VOL` 中的 BGM 名称、音量被丢弃,当前主要靠登录页和选人页固定调用
`play_bgm()`,没有按地图和服务端状态自动切换。见
[`classic_parser.cpp:173`](/Users/shenlei/Work/mt/mtgodot-poc/extension/src/net/classic/classic_parser.cpp:173)。
2. **后台恢复后可能没有 BGM。** 后台时 `AppLifecycle` 调用 `stop_bgm()`,回前台只恢复客户端和
帧率,没有恢复上一次曲目;`stop_bgm()` 还清空了 `_bgm_name`。见
[`app_lifecycle.gd:60`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_lifecycle.gd:60)、
[`app_lifecycle.gd:75`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_lifecycle.gd:75)。
3. **生命周期所有权重复。** `M2Client` 自己处理通知,同时 GameScene 又创建 `AppLifecycle` 绑定
同一个客户端,暂停、恢复、内存警告和关闭的责任边界不清晰;目前即使部分操作幂等,也没有
明确的单一生命周期协调者。
4. **MilesLib 事件模型没有迁移。** 当前只有 BGM crossfade、UI 音效池和通用 3D 音效,缺少
ambience、character sound、sound scale、精确距离衰减、fade/stop 策略、武器/技能/怪物/NPC
动作事件和 `.mss` 支持。参考:[`ClientVS22/MilesLib/SoundManager.h`](/Users/shenlei/Work/mt/40250/ClientVS22/source/MilesLib/SoundManager.h:1)。
### 10. 发布和验证问题
1. **Mac App 不是自包含资源包。** 最近构建产物中的 `assets``bgm` 仍是指向工作区的绝对
符号链接,只复制 `.app` 到另一台 Mac 不能保证资源可用。
2. **主程序和 GDExtension 架构不一致。** App 主程序是 Universal,但 GDExtension 只有 arm64
Intel Mac 可能无法加载核心扩展。
3. **当前是 Ad-hoc 签名,不是正式分发签名。** 没有 Developer ID、完整 hardened runtime、
notarization 和 stapled ticket,不能直接当作面向用户的正式安装包。
4. **发布构建仍包含 Demo 默认账号密码。** `AppFlow` 中默认值是 `admin / 123456789`
不能保留在正式客户端。见 [`app_flow.gd:32`](/Users/shenlei/Work/mt/mtgodot-poc/project/app_flow.gd:32)。
5. **补丁器、Logo、Movie、Web、Discord 和反作弊策略没有形成发布决策。** 当前只是“能启动
并进游戏”的工程入口,不是完整的 ClientVS22 发布外壳。
6. **缺少真实验收门禁。** 当前已有单元测试、loopback 和部分单会话验证,但仍缺:完整 40250
真服矩阵、双账号交易/组队/PVP、断线重连、跨服换图、纯 pack 运行、Intel Mac、受控机位
截图和移动端真机性能验证。
### 当前结论
当前版本应继续定义为:**可登录、可进入地图、具备部分玩法和表现的核心功能纵切**;
连接骨架和换图清理已经开始实现,但尚未完成真实双连接、换图和完整生命周期验收。
它还不能定义为:**ClientVS22 的跨平台等价客户端**。
当前已完成 G0 公共接口的首版冻结,正在进入附录 C 的并行波 A;已完成项与剩余项以文档顶部的
增量 47/48 和 [`CLIENT-GAP-FIX.md`](CLIENT-GAP-FIX.md)“实施进度 / 进度看板”为准。后续仍需要完成以下闭环:
1. 完成 Classic 直连/Auth/Warp 的真实双连接验收,并补齐阶段清理和回登录后的完整生命周期;
2. 未知 VID、VID 重用、事件缓存和完整实体生命周期;
3. 真实 Actor 战斗状态机、动作命中时序和技能合法性;
4. 物品拖放、装备规则、Safebox、交易和快捷栏事务状态;
5. 所有资源调用统一切换到 `AssetSource`,并验证纯 pack 运行;
6.`ClientVS22` 的 UI、EffectLib、SpeedTree、MilesLib 模块建立逐项验收;
7. 完成受控地图/机位/角色/时间/天气/分辨率契约,再进行截图签核;
8. 完成 Mac 自包含资源、双架构扩展、正式签名和 notarization。