# m2dev-client → mtgodot-poc 1:1 功能差距 > 对照范围:`../m2dev-client-src-main/`(客户端源码)、`../m2dev-client-main/` >(可执行客户端与配置)以及本工程当前实现。 > > 本文按“协议、数据、交互、表现均与原客户端等价”的严格 1:1 标准审计; > `CLIENT-ROADMAP.md` 中的“P0–P10 首版完成”只表示主要纵向流程已有首版,不等于 1:1 完成。 > 渲染细节另见 `PARITY-GAP.md`,动画、材质和资产管线另见 `BACKLOG.md`,网络实现另见 > `CLIENT-PORT.md`。 > > 更新日期:2026-09-01(增量 45) > > **状态列图例**:✅ 达到本文末“1:1 完成判据” · 🟡 有首版 / 部分语义 · ⬜ 未实现 · > ❌ 明确不做(超出 m2dev-client 参考范围,除非另立需求)。 > **当前 40250 范围调整(增量 45)**:真实 FreeBSD + MySQL 服务端的完整登录 / 功能验收暂缓, > 不作为本阶段交付门槛;公会相关功能暂缓。当前目标 40250 服务端不提供龙魂玩法,龙魂不纳入本阶段 > 支持范围,已有兼容代码不代表服务端可用;运行时龙魂入口已禁用,但保留 `ds_refine` 等协议兼容代码。 > > ### 不做清单(❌) > > - **拍卖行 / auction house**:m2dev-client 无此系统 —— `0x08xx` 段只有 > `SHOP`/`MYSHOP`/`SAFEBOX_*`/`MALL_*`,全源码 / 协议头 / 资产零 `auction` 命中。 > 要做即“新造协议 + 配套服务端”,不属 1:1 移植。**标注为不做**,除非后续明确提需求。 > - **反作弊(anti-cheat)**:`CG_HACK`(0x0B03) 客户端异常上报,以及桌面外壳的 > `ProcessCRC` / `ProcessScanner` / 内存扫描等。自用客户端不需要,**标注为不做**, > 除非服务端强制校验才回头做最小上报。 > - **龙魂精炼(当前 40250 目标服务端)**:目标服务端不提供该玩法,本阶段不实现 / 不验收; > 客户端保留的兼容代码不计入当前支持范围。 > > ### 变更记录 > > - **2026-09-01 增量 45(P8 交互边界与龙魂运行时范围)**:补齐 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 / m2dev > 角色列表透传到 `M2Client.char_list`;选人页按 40250 原版语义,在角色带该标志时点击“开始” > 弹出改名窗,校验名称后发送 `CG_CHANGE_NAME`,成功的 `GC_CHANGE_NAME` 刷新槽位并清除标志, > `AppFlow` 接通请求、成功和失败回传,新增 `char_create_delete_test.gd` 回归覆盖。P1 行不再把 > 改名 UI 列为未实现;仍需真服验证强制改名、重名 / 非法名错误码和服务端状态。 > - **2026-08-31 增量 30(EterGrnLib 对齐复核)**:查代码确认以下已落地,表内相关行同步。 > ① **材质 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` uniform,0.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 增量 38(ESC 系统菜单 + 游戏设置窗)**: > ① **`system_menu_ui.gd`**(`systemdialog.py` 真 uiscript,thinboard 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/ER,EPointTypes 索引取自 m2dev `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)、`CG_QUEST_CANCEL`(4B)、 > `CG_PARTY_USE_SKILL`(9B)、`CG_FLY_TARGETING`/`CG_ADD_FLY_TARGETING`(16B)、 > `CG_FISHING`(5B)、`CG_SHOOT`(5B)、`CG_USE_SKILL`(12B)。`CG_QUEST_CANCEL` 触发 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`(0x0B20–0x0B22) 的 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 增量 25(Atlas 大地图窗口)**:新增 `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.25–3.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` 按 m2dev 的 `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 细节。 > **拍卖行**经核查 m2dev-client 无此系统 → 标注 ❌ 不做(见上「不做清单」)。 > **反作弊**(`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. 打包资产路径、桌面配置和输入体验尚未形成原客户端等价闭环。 因此当前应称为“核心功能纵切完成”,不能称为 `m2dev-client` 的 1:1 客户端。 ## 已具备的首版能力 - 登录、服务器 / 频道选择、频道负载状态、断线重连、选人、**建号 / 删号**、进入游戏; - Metin2 风选人页(背景 / 3D 角色 / 职业书法字 / 国家 + 国旗 / 四维条 / ◀▶ 槽位 / 建删弹窗); - 基础移动、攻击、选目标、生命 / 法力 / 经验、伤害飘字、affect、死亡与复活窗口; - 背包、基础装备换模、地面物品、技能窗口和快捷栏读取; - 聊天、私聊、基础任务对话和任务日志; - 组队、好友、情侣名 / 爱意值状态、NPC 商店、交易、仓库; - 公会、公会技能、公会战、会徽上 / 下载、精炼(公会相关验收暂缓); - 地形、建筑、水体、**NPC / 怪物真模型(vnum→npclist.txt→目录)**、小地图、频道、昼夜和天气首版; - GR2 模型 / 动画、换装首版、武器 / 盾挂点、距离 LOD / 淡入、角色接触阴影、 `item_proto` 驱动的球面高光和 `.mse` 粒子首版。 这些项目只说明已有入口和基础闭环;是否 1:1 仍以下表为准。 ## 关键 1:1 差距 | 优先级 | 状态 | 模块 | 当前实现 | 需要迁移的原客户端语义 | |---|---|---|---|---| | P1 | 🟡 | 选人 / 建号 / 删号 / 改名 | `CG_CHARACTER_CREATE`(0x0201) / `CG_CHARACTER_DELETE`(0x0202) / `CG_CHANGE_NAME`(0x010B) / `GC_PLAYER_CREATE_SUCCESS`(0x020C) / `GC_PLAYER_CREATE_FAILURE`(0x020D) / `GC_PLAYER_DELETE_SUCCESS`(0x020E) / `GC_PLAYER_DELETE_WRONG_SOCIAL_ID`(0x020F) / `GC_CHANGE_NAME`(0x010C) 全部有尺寸断言;`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` | 仍需补命令 / 表情快捷槽、物品拖放来源、鼠标物品态及原 UI 的完整快捷栏交互;冷却还未叠加原客户端的施法速度与所有特殊技能规则 | | 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`,`quest_cancel()` 发送取消包;真服单会话已发送 `CG_SCRIPT_SELECT_ITEM`(8B) 与 `CG_QUEST_CANCEL`(4B),取消触发 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`(0x0802) `CGMyShopHead`(38) + `MyShopItem`(13)×N 有尺寸断言 + `GameClient::send_open_private_shop` / `send_close_private_shop`(=`SHOP_CG_END`) + loopback 字节级;`GC_MALL_OPEN`(0x0841)/`GC_MALL_SET`(0x0842)/`GC_MALL_DEL`(0x0843) 路由进 `EntityStore`(`m_mall[135]` + `mall_open/size/slot/dirty`),`CG_MALL_CHECKOUT`(0x0840) `CGMallCheckout`(8) 发送;`M2Client` `open/close_private_shop`、`get_mall_items`/`mall_checkout` + `mall_opened`/`mall_changed` 信号;`ui/private_shop_ui.gd`(**已重做成 `PrivateShopBuilder.itemStock` 模型:40 格 grid + 拿起 / 落位 / 逐件价格弹窗 / 点占用格撤下 / 개설按格号排序 + display_pos + 上限 39**)、`ui/mall_ui.gd`(列表 + 取出)。**真机验证(2026-08-31,`net_e2e MT_E2E_MYSHOP=1`)**:`CG_MYSHOP` 38B head + 13B item×1 被真服接受,无断连 / 无 shop error,`SHOP_CG_END` 关店正常。`CG_MALL_CHECKOUT` 送出不断连,但 mall 需点 NPC 才 open。他人开店 `SHOP_GC_START_EX` 多货架已解析(见增量 34)。`GC_SHOP_SIGN` 已在 `NetWorld` 提供 `ShopSign` Label3D fallback,`GC_SYMBOL_DATA` 已接会徽下载;仍缺真 `.sub` 版 `privateshopbuilder.py` uiscript、资源视觉和鼠标真拖放物品态 | | — | ❌ | 拍卖行 / auction house | 不做 | m2dev-client 无此系统(`0x08xx` 段只有 shop/myshop/safebox/mall,全源码 / 协议 / 资产零 `auction` 命中)。需自定义协议 + 服务端支持才能实现,**标注为不做**,除非后续明确提需求 | | 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` 0x0704 分配 攻/坦/迅捷/辅/宗/防 或踢人)+ 名字(★队长) + 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`(0x0731) 已解析并以 `M2Client.guild_make_requested` 通知;公会窗已弹出创建名称输入并发送 `CG_ANSWER_MAKE_GUILD`;`CG_SYMBOL_CRC` / `GC_SYMBOL_DATA` 已支持原始符号文件下载 | 本阶段暂缓完整权限 / 等级页、公告与日志、成员管理、公会战应答、积分板、领地和公会建筑;恢复公会范围后再做完整真服验收 | | — | ❌ | 龙魂精炼 | 当前目标 40250 服务端不提供该玩法;本阶段不纳入支持或验收,已有兼容代码仅保留作协议参考 | 若未来服务端增加该玩法,再另立协议、UI 和验收需求 | | P1 | 🟡 | 世界玩法 | 有 `CG_WARP` 主动请求、`GC_WARP` 同服 / 跨服处理、`CG_FISHING` / `GC_FISHING` 钓鱼方向与状态事件、`NetWorld` / `NetPlay` 钓鱼动作分发、`ChatUI` 鱼获 / 失败日志、`CG_DUNGEON` / `GC_DUNGEON` 副本请求与状态事件、`GC_DIG_MOTION` 挖矿动作广播、`GC_LAND_LIST` 领地状态、`GC_OBSERVER_ADD/REMOVE/MOVE` 观战者状态、频道、时间、NPC marker、天气粒子和 mount_vnum 字段;真服登录洪流已收到并解析 `GC_LAND_LIST`(436B),单会话发送 `CG_FISHING`(5B) 后收到“Please choose a Fishing Pole.”业务拒绝且连接保持;同服 `GC_WARP` 直接挪玩家,跨服 `addr≠0` 由 `M2Client` 复用 login key 直连目标 game server,断点重连也走该快路径 | 补完整钓鱼 UI / 鱼获背包落地、副本计时 / 入口 UI、坐骑真模型 / 骑乘动作 / 移速、地形贴花、DungeonBlock 和地图天气配置;`CG_DUNGEON` / `CG_WARP` 本轮为避免副作用未发送,跨服换图仍需真服端到端验收与场景生命周期清理 | | 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` 加点命令做成,作为 1:1 窗口迁移样板** | **选魔石窗** `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` 均已按样板做成 | 按样板逐个复现原 `Interface.MakeInterface()` 装配与视觉 / 交互细节:帮助窗、Atlas、商城 / Cube 真 uiscript、Guild Building、game_option 的名字颜色 / 伤害数字等渲染侧钩子(拍卖行参考端无此窗口)| | P2 | 🟡 | 输入 / 快捷键 | WASD、点地、触屏、右键拖拽环绕、滚轮缩放、双指捏合、I/K/J/O/G/L/Z、F1–F4 快捷栏切页和数字键 1–9 激活已接;实体点选从固定半径提升为优先使用子 mesh 合并世界 AABB,无码型实体才回退胶囊近似 | 对齐空格连续攻击、显示名字、鼠标物品态、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` 打包客户端不等价 | ## 参考模块覆盖索引 下表以 `m2dev-client-src-main/src/UserInterface/` 的模块职责为索引,补充上表未展开的基础能力。 “已有”只表示当前有可运行首版;仍需满足本文末的 1:1 完成判据。 | 参考模块 / 能力域 | 当前实现 | 与 m2dev-client 的差距 | 优先级 | |---|---|---|---| | `AccountConnector` / `PythonNetworkStreamPhase*` 登录与相位 | auth → game → 选人 → LOADING → PHASE_GAME 已打通;`LoadingScreen`、`ReconnectUI`、跨服 `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 交付范围 | — | ## 协议覆盖审计 以 `m2dev-client-src-main/src/UserInterface/Packet.h` 为基准(`namespace CG` 68 个 + `namespace GC` 109 个 = **177** 个协议键)。增量 2/3 已补 `CG_CHARACTER_CREATE` / `CG_CHARACTER_DELETE` / `GC_PLAYER_CREATE_SUCCESS` / `GC_PLAYER_CREATE_FAILURE` / `GC_PLAYER_DELETE_SUCCESS` / `GC_PLAYER_DELETE_WRONG_SOCIAL_ID` / `CG_MYSHOP` / `CG_MALL_CHECKOUT` / `GC_MALL_OPEN` / `GC_MALL_SET` / `GC_MALL_DEL` 共 11 个,并把已声明的 `GC_EMPIRE` 接到 `M2Client.get_empire()`。增量 5 又把此前仅声明的 `GC_LOVER_INFO` / `GC_LOVE_POINT_UPDATE` 接入状态、信号和 UI;增量 6 又补了 `CG_CHARACTER_POSITION` / `CG_SYNC_POSITION` 的上行构造和 API。这两项不再计入缺失清单, 故当前总数从 24 降为 22 个 header;增量 7 再补任务选择 / 取消上行,增量 8 补队伍技能上行, 增量 9 补飞行目标广播,增量 10 补技能组切换,增量 11 补角色改名,增量 12 补公会创建请求, 增量 13 补主动传送,增量 14 补挖矿动作,增量 15 补会徽符号下载,增量 16 补钓鱼协议, 增量 17 补副本协议,增量 18 补领地与观战者,增量 39 补 `CG_TEXT`,当前为 **0 个 header**。 排除仅命名不同、功能已覆盖的别名(`GC_CHAR_ADDITIONAL_INFO`=`GC_CHAR_ADD_INFO`、 `GC_TARGET`=`GC_TARGET_INFO`、`GC_TARGET_CREATE_NEW`=`GC_TARGET_CREATE`、 `GC_CHARACTER_UPDATE2`=`GC_CHARACTER_UPDATE`、`GC_OWNERSHIP`=`GC_ITEM_OWNERSHIP`、 `GC_SEPCIAL_EFFECT`=`GC_SPECIAL_EFFECT`、`GC_REFINE_INFORMATION_NEW`=`GC_REFINE_INFO_NEW`), 以及 `CG_STATE_CHECKER` / `GC_RESPOND_CHANNELSTATUS`(`project/net/channel_status.gd` 单独实现) 和 `CG_HACK`(反作弊,见「不做清单」), **当前主网络层仍缺 0 个 header**(脚本核对 `wire.h` vs `Packet.h`): ### 账号 / 命令通道 | header | 值 | 说明 | |---|---|---| | `CG_TEXT` | 0x0011 | 已补 classic wire 尺寸表;这是握手阶段的管理命令,固定前缀为 1 字节,后续文本直到换行由服务端特殊解析。Cube 仍借 `CG_CHAT`/`CHAT_TYPE_COMMAND` 实现,不伪造管理命令入口 | ### 已声明但待核(不计入上面 1 个) - `CG_CHARACTER_POSITION`(0x0A60) / `CG_SYNC_POSITION`(0x0303):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已分别发送 5B / 16B,均保持在线且无 unknown;仍缺原版 自动触发语义、姿态动作链和服务端位置纠偏回包验收。 - `CG_SCRIPT_SELECT_ITEM`(0x0903) / `CG_QUEST_CANCEL`(0x0906):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已分别发送 8B / 4B,取消触发 1 条 quest-change 且无 unknown;基础 `[INPUT]` / `[SELECT_ITEM]` / Escape 取消已接入,选择窗的背包魔石网格、 grade 过滤和选择回包已由 `select_item_ui_test.gd` 覆盖;仍缺原版输入校验、CJK IME 和有脚本时 的真实任务流程验收。 - `CG_PARTY_USE_SKILL`(0x0705):wire、发送接口和 loopback 字节测试已覆盖;真服单会话已发送 9B 且保持在线 / 无 unknown,但无队伍技能业务回包;仍缺冷却 / 权限、UI 入口和有效队伍真服 验收。 - `GC_FLY_TARGETING`(0x0411) / `GC_ADD_FLY_TARGETING`(0x0412):wire、EntityStore 状态、 `M2Client.fly_targeting` 信号和 `NetWorld` 目标队列消费已覆盖;本轮发现并修复 `GameClient` 未分派这两个 GC header 的 bug;真服本轮未主动推送,仍缺真实弹道模型、命中时机 和服务端广播验收。 - `GC_CHANGE_SKILL_GROUP`(0x021D):wire、EntityStore 状态、dirty 失效和 `M2Client.skill_group_changed` 已覆盖;本轮发现并修复 `GameClient` 未分派该 header 的 bug; 仍缺技能组选择 UI、服务端权限和真服推送验收。 - `CG_CHANGE_NAME`(0x010B) / `GC_CHANGE_NAME`(0x010C):wire、角色槽位更新、 `M2Client.change_name()` 和 `char_name_changed` 已覆盖;选人页按 `change_name` 标志接入 原版“点击开始先改名”流程,并对重名 / 非法名错误码给出状态文案;仍缺真服验收。 - `GC_REQUEST_MAKE_GUILD`(0x0731):无负载 wire 解析和 `guild_make_requested` 信号已覆盖;公会窗已 完成创建窗口、12 字上限、空名 / 控制字符校验和 `CG_ANSWER_MAKE_GUILD` 应答;仍缺服务端敏感词 / 重名错误回包、实际建会状态和真服验收。 - `CG_SYMBOL_CRC`(0x0723) / `GC_SYMBOL_DATA`(0x0732):16B / 8B wire、`MarkClient` CRC 请求、 变长数据接收和 `M2Client.guild_symbol_ready` 已覆盖;仍缺本地符号缓存失效策略、UI 预览和 真服验收。 - `CG_FISHING`(0x0B01) / `GC_FISHING`(0x0B10):5B / 10B wire、旋转方向换算、 `EntityStore` 事件、`M2Client.fishing_event`、远端 / 本地主角动作分发和 ChatUI 结果日志已 覆盖;真服单会话已发送 5B,收到“Please choose a Fishing Pole.”业务拒绝且无断连 / unknown, 证明包布局和错误路径可达;仍缺持竿后的完整钓鱼 UI、鱼获背包落地和成功流程验收。 - `CG_DUNGEON`(0x0B02) / `GC_DUNGEON`(0x0B11):4B / 5B wire、目的地坐标变长 body、 `EntityStore` 事件和 `M2Client.dungeon_event` 已覆盖;仍缺副本计时 / 入口 UI、地图生命周期 清理和真服验收。 - `GC_LAND_LIST`(0x0B12):4B 固定头 + 24B `LandPacketElement` 变长解析、领地状态、 `M2Client.get_land_areas()` / `land_areas_changed` 和小地图领地矩形边框已覆盖;真服登录洪流 已收到并解析 436B;仍缺领地贴图 / 建筑和有实际领地变更时的推送验收。 - `GC_OBSERVER_ADD/REMOVE/MOVE`(0x0B20–0x0B22):12B / 8B wire、持久观战者状态、 `M2Client.get_observers()` / `observer_event` 和小地图紫色菱形标记已覆盖;仍缺真服验收。 - `CG_WARP`(0x0305):4B wire、`GameClient.send_warp()`、`M2Client.request_warp()` 和回环 请求 / `GC_WARP` 回传测试已覆盖;仍缺传送确认 UI、冷却 / 失败提示和真服验收。 - `GC_DIG_MOTION`(0x0308):13B wire、`EntityStore` 事件、`M2Client.dig_motion` 和 `NetWorld` 朝向 / 动画元数据处理已覆盖;仍缺真实挖矿动作资源、音效 / 特效和真服验收。 - `GC_ITEM_DROP`(0x0513):疑似被 `GC_ITEM_GROUND_ADD`(0x0515) 取代,暂按等价处理,未逐字节核。 - `GC_LOGIN_KEY`(0x0107):auth 握手用;`net_e2e` 真机 auth→PHASE_GAME 全程已打通,功能等价 (可能走 `GC_AUTH_SUCCESS` 0x0108),未单独立项。 “header 已声明”也不能直接视为完成:还需检查封包尺寸、变长 body、所有 sub-header、 `EntityStore` 状态、GDScript 信号、UI 消费和真服务器端到端行为。 ## 建议迁移顺序 ### M1:服务器语义正确 1. 把远程 `SHOOT` 时机、范围目标与技能类型冷却接入现有技能协议; 2. 快捷栏的命令 / 表情 / 物品拖放语义与特殊冷却规则; 3. 细化 24 wear / 腰带 / 装备查看的原版窗口表现,并完成完整物品操作; 4. 移动同步、PVP / 决斗和 Actor 战斗状态机; 5. 用 loopback + 真服抓包为每类协议建立双向验收。 ### M2:主要玩法完整 1. 任务完整标签 / 输入 / 选道具 / 指引; 2. 商店 START_EX、仓库密码、商城、私人商店、Cube(拍卖行 m2dev-client 无,除非另起协议); 3. 组队角色、情侣完整交互、领地与公会建筑;公会完整页面暂缓; 4. 钓鱼、副本、坐骑、观战者和 Atlas。 ### M3:UI 与表现 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 完成。