Files
mtgodot-poc/docs/CLIENT-GAP.md
T
shenleiandClaude Sonnet 5 d7711ba5f0 40250 classic: increment 50 (Phase 1 W3) — §8.3 in-game help window
Implement the in-game Help window as a 1:1 migration of uihelp.py's
HelpWindow (single-page: ENABLE_HELP_MULTIPAGE = 0 -> LoadDialogSinglePage).

- project/ui/help_ui.gd (new): loads the real helpwindow.py uiscript via
  UiScript -> UiBuild; _relabel() resolves the <HELP_*>/<CLOSE> placeholders
  (produced when UiManager has no locale callback) from the project Locale
  table (en) with verbatim English fallbacks, covering the original script's
  two duplicate help_02 rows (right/middle camera). open/close/toggle/is_open;
  close_button -> close; ESC handled by the UiManager window stack.
- project/ui/system_menu_ui.gd: help_button changed from the placeholder toast
  to close() + open_help(); adds const HelpUI, an injectable help_ui member,
  _ensure_help() (lazy build, reuses the menu's ui/client/assets_root),
  open_help(), toggle_help(); setup() gains an optional 6th arg (old 5-arg
  callers unaffected).
- project/game_scene.gd: single elif in _unhandled_input — KEY_H without
  ctrl/meta -> system_menu_ui.toggle_help() (Ctrl+H stays free for
  /user_horse_ride).
- project/help_ui_test.gd (new): headless SceneTree test — helpwindow.py load,
  19 verbatim help rows + taskbar labels bound and non-placeholder,
  close_button text, rows() helper, close/open/toggle, ESC stack top,
  SystemMenuUI.open_help() lazy build.

Tests: help_ui_test, system_menu_ui_test, gamescene_test, ui_test all PASS
(plus system_option_ui_test, char_status_ui_test). No build/ in this worktree
so C++ ctest not run (no C++ change). Real-server and visual (.sub textures)
not verified. Docs: CLIENT-GAP.md W3 row + increment 50 bullet;
CLIENT-GAP-FIX.md §8.3 status/改动/测试/验收 + 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:34:36 +09:00

101 KiB
Raw Blame History

40250/ClientVS22 → mtgodot-poc 1:1 功能差距

对照范围:../40250/ClientVS22/(40250 客户端源码及其运行时模块)以及本工程当前实现。 当前工程默认运行 MT_PROTOCOL=classicextension/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(逐条给出 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.gdnet_play.gdplayer_controller.gdproject/world/*、动画 / 战斗相关扩展及测试 依赖 W0 的 EntityStore / world_reset;UI 只消费公开信号,不直接修改网络核心
W3 玩法 UI、输入与本地化 §4–§5、§8、背包 / 快捷栏 / 任务 / 社交 / 输入 / IME / 键位 project/ui/*hud.gdlogin.gdcharselect.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_gamewarpworld_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_dirtybgm_changed 信号、bgm_director.gd、§8.6 键位收敛)已提交 Phase 1 4 工作流并行完成后执行 G1 集成
W1 协议 / 账号会话 🟡 角色槽地址透传、发送结果、unknown / unhandled 错误链路、断线回 LOGIN 已覆盖;m_pending_actor 暂存表成员已就位 §1.5 真实版本包、§1.10 定长文本编码、§2.2 两包合并逻辑;真实双连接、enter delay
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 帮助窗(help_ui.gd = helpwindow.py uiscript + uiScriptLocale.HELP_* 逐字,H 键 / ESC 菜单 help_button 入口,help_ui_test 绿)已落地 §8.6 剩余逐行键位与表情包;帮助窗视觉 / .sub 贴图与真服未验证
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,包括“开始、完成、 阻塞、重新验证”四类状态变化。代码已经提交但文档没有同步时,该批次仍视为未完成

每次同步至少记录:

  • 工作流 / 条目编号、当前状态( 未开始、🟡 开发中、 已完成、⚠️ 阻塞、🔎 待真实验收);
  • 参考源码依据、实际修改文件和公开接口变化;
  • 自动化测试命令及结果;无法运行或被 skip 的测试和原因;
  • 真实服务端 / Mac App 验收证据,仍缺什么;
  • 已知风险、与其他工作流的冲突 / 依赖、下一批次和负责人。

同步位置固定为:本文对应差距表 / 变更记录负责“全局状态”,CLIENT-GAP-FIX.md 对应条目和实施进度 负责“实现细节”。文档由 W0 集成维护者统一合并,工作流提交者只更新自己负责的条目,避免并行编辑造成状态 互相覆盖。

当前 40250 范围调整(增量 45):真实 FreeBSD + MySQL 服务端的完整登录 / 功能验收暂缓, 不作为本阶段交付门槛;公会相关功能暂缓。当前目标 40250 服务端不提供龙魂玩法,龙魂不纳入本阶段 支持范围,已有兼容代码不代表服务端可用;运行时龙魂入口已禁用,但保留 ds_refine 等协议兼容代码。

不做清单(

  • 拍卖行 / auction house:当前 40250 默认构建未发现完整的 ClientVS22 拍卖行 UI / handlerPacket.h 和服务端仍保留 __AUCTION__ 条件编译协议(客户端 CG_AUCTION_CMD=205、 服务端列表包 =206)。当前服务端默认关闭该宏,因此本阶段标注为不做;不能表述为“全源码、 协议和资产均不存在”。参考:Packet.h:158-160
  • 反作弊(anti-cheatCG_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_testnet_classic_session_testnetbridge_testp10_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_testnet_classic_session_testgamescene_testp9_testp2b_testnetbridge_testp8_test 已通过;受限环境下 localhost TCP 目标测试仍自动 skip。真实 40250 换图 / 第二次 连接 E2E、地图 BGM 选择和 MT_CLASSIC_ENTER_DELAY 仍待完成。
  • 2026-09-02 增量 50Phase 1 W3 / §8.3 帮助窗):新增 project/ui/help_ui.gd —— 1:1 迁移 assets/root/uihelp.pyHelpWindow(单页版 ENABLE_HELP_MULTIPAGE = 0LoadDialogSinglePage),布局走真 assets/uiscript/uiscript/helpwindow.pyUiScript → UiBuild),19 条浮动帮助行 + 10 个任务栏贴纸标签 + close_button 的文本逐字对齐 uiScriptLocale.HELP_*locale/locale/en/locale_interface.txt);UiManager 未接 locale 回调时 helpwindow.py 里的 uiScriptLocale.X 被解析成 <X> 占位,_relabel() 用本工程 Locale 表 + 英文兜底补回(含原脚本重名的两个 help_02)。入口:game_scene.gd _unhandled_input 加单条 elifH 且非 Ctrl / 非 Meta → system_menu_ui.toggle_help() Ctrl+H 仍留给 /user_horse_ride);system_menu_ui.gdhelp_button 由占位提示改为 close() + open_help(),新增 _ensure_help() 懒建 / open_help() / toggle_help() 与 可注入的 help_ui 成员(setup() 追加可选第 6 参,旧 5 参调用不受影响)。新增 project/help_ui_test.gdhelpwindow.py 装载 + 19 行逐字绑定 + 任务栏标签 + close_button + open/close/toggle + ESC 栈顶 + SystemMenuUI.open_help() 懒建)。help_ui_test / system_menu_ui_test / gamescene_test / ui_test(附加 system_option_ui_test / char_status_ui_test)全绿;本 worktree 无 build/,未跑 C++ ctest。视觉 / .sub 贴图 与真服未验证。
  • 2026-09-02 增量 49W0 接口冻结批次):为 Phase 1 的 4 个并行工作流一次性落地跨工作流 共享文件的骨架,冻结接口后再拆分。EntityStoreEntityempire / 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() 不再 丢弃 empirereset_for_map_change() 一并清 m_dirtyM2Client:新增 bgm_changed 信号,classic / m2dev 两条 pump 回路都消费 drain_dirty()entity_infotake_bgm_dirty()bgm_changedentity_dict()empire / affect_flags / owner_vid / state_flags 键。ClassicParserBGM 名 / 音量从 GC_MAIN_CHARACTER3_BGM / GC_MAIN_CHARACTER4_BGM_VOL 路由到 mut_map_bgm()(此前直接 丢弃),新增 m_pending_actor 暂存表成员(W1 在 §2.2 填充两包合并逻辑)。Godot 侧新增 project/bgm_director.gdM2Client.bgm_changedAudio.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 → 快捷栏 47、 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 增量 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 已解析并在 NetWorldShopSign Label3D 作为无 .sub 资源 fallbackGC_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,关闭发送 0select_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_materialcull_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_eventsound / 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:171weaponComposite[0]),30 cm 位移夹断退回原始手部位姿。 03150 类网格在 .gr2 里就离骨骼原点 ~3 m(资产缺陷,非变换)。 新增测试 eterngrn_polish_test.gdctest 10/10 + 29 套 GDScript + iOS 全绿。
  • 2026-08-31 增量 38ESC 系统菜单 + 游戏设置窗)system_menu_ui.gdsystemdialog.py 真 uiscriptthinboard 8 键)= uisystem.py SystemDialog.__LoadSystemMenu_Default 的 1:1system_option_buttonsystem_option_ui.open() game_option_buttongame_option_ui.open()change_buttonclient.say(0,"/phase_select") = net.ExitGame()SendChatPacket("/phase_select")),logout_button"/logout" = net.LogOutGame()),mall_button"/in_game_mall"exit_buttonget_tree().quit() help_button → 占位提示,cancel_button / 标题栏 → 关。ESC 现在开这个菜单(game_scene 把 ESC 从直接开 system_option 改成开 system_menu)。 ② game_option_ui.gdgameoptiondialog.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_STATEC++wire.hCGPartySetState{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) + bindget_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_freectest 10/10 + 33 套 GDScript + 导入 + iOS 全绿。
  • 2026-08-31 增量 36(系统设置窗 + 私人商店/选魔石补真 uiscript)系统设置窗 project/ui/system_option_ui.gd —— UiScript+UiBuildassets/uiscript/uiscript/systemoptiondialog.py;控件绑定逐字对照 assets/root/uisystemoption.pymusic/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() 时读回即时生效,.msenvEnvironment_on_main_set 重新 _apply_allgame_sceneESC 呼出(ui_manager 先关最顶层窗并吃掉事件 → 到 game_scene 说明无窗打开)。 新增 system_option_ui_test.gd(装载 + 音量/镜头/雾绑定 + cfg 持久化 + 二次实例读 cfg)。 ② 私人商店窗改走真 uiscript privateshopbuilder.pyboard + TitleBar + NameLine + ItemSlot 5×8=40 grid + Ok/Close):40 格从 _win.nodes 索引,NameLine(text) 上盖 LineEdit,背包候选面板挂窗右侧(原客户端靠主背包拖拽,此处点选)。itemStock 交互模型 (拿起 / 落位 / 价格弹窗 / 排序 + display_pos / 撤下 / 개설 / 철수)不变。测试拆出独立 private_shop_ui_test.gduiscript,缺资产跳过),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.pyitemStock 模型:左侧背包候选(点一件“拿起”)+ 右侧 40 格(shop.SHOP_SLOT_COUNT,5×8); 拿着件点空格 → 价格输入(uiCommon.MoneyInputDialog)→ 落位(AddPrivateShopItemStock + itemStock[targetSlot]=(src));点已占用格 → 撤下(OnSelectItemSlot / DelPrivateShopItemStock)。개설(OkButton) 把 stock 按格号排序、display_pos = 格号、 上限 39PRIVATE_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 一致),真 .subprivateshopbuilder.py uiscript 待后续。
  • 2026-08-31 增量 34SHOP_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.hShopTabHead + 断言 33SHOP_TAB_NAME_MAX=32/SHOP_TAB_COUNT_MAX=3)。 ShopEntrypos(货架内槽位,买位置用),ShopTab{name,coin_type,items} + m_shop_tabsSHOP_GC_START 也改成按固定 40 槽数组填 posSHOP_GC_END 清货架。 shop_items() 仍是 tab 0 的镜像(旧调用不动)。M2Client.get_shop(){vid, open, tabs:[{name, coin_type, items:[{pos,vnum,price,count}]}]}shop_ui.gdtabs>1 显示 货架按钮,买位置 = tabIdx*SHOP_SLOT_COUNT(40)+slotPos1: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.py5×8 grid_table);填格逻辑逐字照 assets/root/uiselectitem.py.RefreshSlot:遍历背包前 INVENTORY_PAGE_SIZE*2 格,只留 item.IsMetinproto.type == ITEM_TYPE_METIN = 10GetItemGrade <= 2= 物品内部名最后一位数字,非数字按 0)的物品,最多 54 个。 点格 → client.script_select_item(inventoryCell)= net.SendSelectItemPacket CG_SCRIPT_SELECT_ITEM 0x0903+ 关窗;ExitButton / 关闭 → script_select_item(0) uiselectitem.Close)。触发链 1:1quest_dialog.parse_script 识别 EventManager [SELECT_ITEM] token → 新增 select_item_requested 信号(对应 PythonEventManager EVENT_TYPE_SELECT_ITEMinterfacemodule.BINARY_OpenSelectItemWindow)→ game_scene 接到 select_item_ui.open()。新增 select_item_ui_test.gdtoken 解析 + 过滤 + 选包 + 关窗发 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.RefreshStatusLevel/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.gdctest 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=0CG_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_TARGETINGGC_CHANGE_SKILL_GROUP 已有 EntityStore 实现却未列入 GameClient 世界分派,现已补路由并由 net_entity_test 复核。CG_DUNGEONCG_WARP、改名 / 符号 / 物品变更等破坏性或独立 连接协议本轮未发送,仍保持“待真服验”。
  • 2026-08-31 增量 5(实现复核):修正文档中已经落后的功能状态。 情侣状态已不再是“整体未做”:GC_LOVER_INFO(70B) / GC_LOVE_POINT_UPDATE(5B) 已接入 EntityStore,并通过 M2Client.get_lover() / lover_changed 驱动常驻 love_uiC++ 状态测试和 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 增量 39CG_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(钓鱼动作消费)NetWorldNetPlay 现在消费 fishing_event 的 START / STOP / REACT / SUCCESS / FAIL 子头,记录方向并尝试驱动远端及 本地主角的钓鱼动作;FISH 子头按参考客户端语义保留为鱼获物品事件,不误当作角色 VID。 combat_fx_test.gdnetbridge_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.gdeterngrn_polish_test.gd 覆盖远近程识别及一次性发射。
  • 2026-08-31 增量 25Atlas 大地图窗口):新增 AtlasUI,复用地图区块 minimap.dds 构建 Atlas 底图;支持 M 键打开 / 关闭、地图名称、玩家位置(全局厘米)显示、 左键拖动和右键复位。无底图资源时仍保留可交互坐标查询;atlas_test.gdgamescene_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_speedNetPlay 在主角 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_UPDATEattack_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:1CG_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/COMMANDcube … 总线 → 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.txtvnum→代号→目录)解析 + 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_changednet.loopback_flow 覆盖建删和改名名称字节 / pid 广播。真机验证(2026-08-31net_e2e MT_E2E_CHARCREATE=1:建号、GC_PLAYER_CREATE_FAILUREGC_EMPIRE 已验。选人页有建号 / 删号弹窗、强制改名弹窗与国家国旗 CG_CHARACTER_DELETE 破坏性,真机未验;强制改名、重名 / 非法名错误码和服务端状态未真服验收;建号缺外形 / 发型选择与属性再分配 UI(现发 shape=0 + 职业基础四维);原版选人台座、镜头动画、OnCreateFailure 完整错误码文案
P0 🟡 技能施放协议 CG_USE_SKILLCG_FLY_TARGETINGCG_ADD_FLY_TARGETINGCG_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_* 可恢复;技能冷却读取对应 .mskCoolTimeFormula 参考端服务端保存 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_ITEMCtrl+右键向当前目标 ITEM_GIVEAlt+右键以数量框发 ITEM_DROP2ITEM_OWNERSHIP 会实时更新地面名条;GC_VIEW_EQUIP 已保存 11 格检查快照并弹出装备查看窗 仍需复刻原鼠标物品态、操作确认 / 错误提示、宝石专用规则,以及装备查看窗的原版槽位图标、3D 纸娃娃与交互
P0 🟡 装备 / 背包数据模型 常规背包维持 90 格;运行时模型已扩展到 24 wear 位置,并将腰带改为独立 16 格;背包右侧动态呈现能力 / 时装 / 戒指 / 腰带和 4×4 腰带格,发包使用原客户端 INVENTORY 全局 cell90 + 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_SPEEDGC_WALK_MODEGC_CHARACTER_POSITIONGC_DIG_MOTION 已恢复到实体状态,NetPlaymoving_speed/100 校准主角步行 / 奔跑预测,挖矿广播可驱动目标朝向 / dig 动画,WALK_MODE 会驱动远端 walk/run 动画选择;真服单会话已发送 CG_CHARACTER_POSITION(5B) 与 CG_SYNC_POSITION(16B),均无断连 / unknown 仍需按原客户端同步窗口、碰撞纠偏触发时机与保留动作处理,而不是只靠距离阈值和 lerp;姿态动作还没有完整 UI / 动画语义
P0 🟡 PVP / 决斗 / 阵营 GC_PVPDUEL_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=114Classic 没有独立的 CG_QUEST_CANCELquest_cancel() 通过 CG_SCRIPT_ANSWER=29 发送 sentinel 255;真服单会话已验证选择包 8B,取消触发 1 条 quest-change 且无断连 / unknown 补原版 CJK IME / 输入校验、QuestButton、立绘、完整 EventManager 标签、世界箭头和屏幕边缘指示;选道具业务回包与完整资源视觉仍需真服 / 资源环境验收
P1 🟡 商店 / 交易 / 仓库 普通 NPC 商店、交易状态和仓库列表已有首版;SHOP_GC_START_EX 多货架已解析(m_shop_tabsget_shop()),shop_ui 带货架按钮 + tabIdx*40+slot 买位置 补图标格网格、数量输入、交易回滚提示、仓库密码 / 改密码、完整拖放规则、coin_type 非金币货币显示
P1 🟡 私人商店 / 道具商城 首版CG_MYSHOP=55CG_MALL_CHECKOUT=69GC_MALL_OPEN=122 / GC_MALL_SET=128 / GC_MALL_DEL=129CGMyShopHead(38) + MyShopItem(13)×N 有尺寸断言;商城状态路由进 EntityStoreprivate_shop_ui / mall_ui 和真服探针均已接入。 仍缺真 .subprivateshopbuilder.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
P1 🟡 组队 邀请、接受、离队;成员信息板(party_ui.gd1: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_changedlove_ui 常驻显示伴侣名和 0–100 爱意值;net.entity_storelove_ui_test.gd 覆盖 补好友分组和完整 messenger 行为;情侣仍需完整交互 / 状态图标与 affect 表现、断线 / 切图生命周期、原版文案和真服务器端到端验收
P1 🟡 公会(暂缓) 信息、成员、技能、公会战、会徽上 / 下载和部分操作已有首版;GC_REQUEST_MAKE_GUILD=82 已解析并以 M2Client.guild_make_requested 通知;公会窗已弹出创建名称输入并发送 CG_ANSWER_MAKE_GUILDCG_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.gdsystemdialog.py + /phase_select//logout//in_game_mall)、私人商店窗 private_shop_ui.gd组队成员板 party_ui.gd 均已按迁移样板做成;这些 Python 文件不在本次 ClientVS22/source/ 树中,仍需用对应 C++ 常量 / 行为和实际资源验收
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_imagemark、真实 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.hm2dev)与 Classic 实现混在一起后的历史统计,不能继续作为当前覆盖结论。

当前协议审计必须按运行时变体分开:

  • 当前默认 Classic:以 extension/src/net/classic/wire_classic.hclassic_parser.cppGameClient 分派和 40250/Server/.../packet.h 逐包核对。Classic 的核心编号与参考端一致, 例如 CG_CHARACTER_CREATE=4CG_MYSHOP=55CG_WARP=65CG_FISHING=82CG_DUNGEON=110GC 侧 GC_PLAYER_CREATE_SUCCESS=8GC_FISHING=89GC_LAND_LIST=130
  • m2dev:只有显式使用 MT_PROTOCOL=m2dev 时才核对 extension/src/net/wire.h;其中的 0x02010x08020x0B01 等编号不是当前 Classic/40250 对照值。
  • CG_HACKCG_STATE_CHECKERGC_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_motionNetWorld 朝向 / 动画元数据处理已覆盖;仍缺真实挖矿动作资源、音效 / 特效和真服验收。
  • 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 源码做静态对照,重点检查 UserInterfaceGameLibEffectLibMilesLibEterLibEterGrnLibSpeedTreeLibPRTerrainLib 中承担实际运行时语义的模块。 它不是简单的功能勾选表,而是把问题分成四类:

  • 明确缺陷:当前代码存在可定位的错误或错误状态转移;
  • 未实现:当前只有接口、占位或协议接线,没有原客户端对应行为;
  • 近似实现:能够运行,但和 ClientVS22 的数据、时序或表现语义不同;
  • 未验证:代码路径存在,但还没有真实 40250 服务器、多账号或受控画面证据。

本次指定的 40250/ClientVS22/source/ 树中没有 game.pyuicharacter.pyuiparty.py 等 Python 脚本;文档中出现的 assets/root/*.py 均属于本工程迁移资产 / 历史脚本依据,不应单独 当作 ClientVS22 源码证据。凡只能由这些脚本确认、而在 ClientVS22/source/ 没有对应 C++ 实现或常量的内容,统一视为“迁移资产依据 / 待验证”,不宣称已完成 1:1 源码对照。

参考客户端的核心差异在于:它不是只有网络收发,而是由 CPythonNetworkStreamNetworkActorManagerCPythonPlayerActorInstanceCPythonEventManagerPythonIMEMapOutdoorEffectLibMilesLib 共同维护 阶段、Actor、动作、地图、脚本、输入、特效和声音状态。

1. P0:连接、协议和生命周期

  1. Classic DirectEnter 已接入,但仍未完成真实双连接验收。 登录入口仍是 Auth → Game; 角色列表保存每槽 lAddr/wPort 后,M2Client.enter_game(index) 会断开第一条游戏连接, 按角色槽地址重连,并在 DirectEnter 的 SELECT 阶段发送 CG_CHARACTER_SELECTMT_CLASSIC_ENTER_DELAY 和真实两次 TCP 连接验收仍待完成。见 classic_session.cppm2_client.cpp

  2. 游戏中断线的回登录链路已接入,真实 TCP 断线仍待验收。 已建立游戏连接后的 Classic 失败发 disconnectedAppFlow 无条件回 LOGIN;认证 / 游戏连接建立前的失败仍发 login_failedReconnectUI 已从 AppFlow 主流程移除,但独立文件仍保留作旧测试 / 可选增强。 见 m2_client.cppapp_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

  5. 客户端版本包是硬编码的。 当前固定发送 metin2.bin 和旧 timestamp,未读取实际 构建版本、文件时间或资源包版本。服务端开启版本校验时,更新客户端后仍可能被当成旧客户端。 见 classic_session.cpp:335

  6. Passpod、HackShield、XTrap、StateChecker 等兼容路径没有实现。 当前选择了不伪造 这些回应;如果目标 40250 服务端启用了其中任意一项,登录或进场可能被断开。应把“不支持” 变成明确的服务器兼容矩阵,而不是让连接表现为随机失败。

  7. 未知包的 Loading 重同步问题已修复第一批。 当前不再扫描后续字节寻找 GC_CHARACTER_ADD 边界;未知 header 记录最近包、清空缓冲并断开,保留 header == 0 填充跳过。真实加密流验收仍待完成。见 classic_stream.cpp

  8. 已知但没有业务处理的包静默丢弃问题已修复第一批。 ClassicParser 现在记录阶段 / header 并返回失败,连接层清空缓冲并断开;可选集成包不会再静默成功。按阶段拆分处理表和 全量业务覆盖仍待完成。见 classic_parser.cpp

  9. 封包覆盖仍停留在结构和尺寸层。 还需要逐包核对 sub-header、动态 body、实体状态、 UI 消费、发送后的锁定状态、失败回滚和重连恢复,不能仅以 Packet.h 中存在 header 作为完成。

  10. 文本编码和固定长度字段没有统一策略。 当前大量使用 UTF-8 Stringstd::stringmemcpy,没有完整复现 ClientVS22 的本地编码转换。CJK 名称、聊天、私聊、公会名可能 按 UTF-8 字节截断或超过服务端字段边界;角色名校验还存在 < 24<= 24 两种规则。

  11. 登录错误、超时、会话失效和服务器列表状态没有完整映射。 当前更多是把底层字符串 直接显示出来,没有复现 ClientVS22 的错误码、阶段专用文案、服务器刷新和超时处理。

2. P0/P1:实体、世界和状态一致性

  1. 未知 VID 的普通 Actor 更新包会直接丢失。 mut_movemut_set_positionmut_change_speedmut_deadmut_stun、物品更新等路径遇到未知实体直接返回,没有 pending state 或重放机制。 角色添加顺序变化时,位置、HP、死亡、动作和装备信息会丢失;GC_SHOP_SIGN 当前有独立的 招牌 fallback,不能与普通 Actor 更新混为一谈。见 entity_store.cpp:123

  2. GC_CHAR_ADDITIONAL_INFO 早于 Spawn 时状态不会被待合并缓存。 mut_char_info() 可以 创建 / 更新 Entity,但新建时不发送 Spawn change;如果后续 Spawn 正常到达,NetWorld 仍会 创建节点,若 Spawn 丢失或顺序异常则实体会长期只有数据没有表现层节点。见 entity_store.cpp:458net_world.gd:128

  3. VID 重用时旧状态可能污染新实体。 mut_spawn() 没有完整重置 dead、stunned、mount、 guild、pk、alignment、parts、shop sign、affect 和上一动作状态,只覆盖了基础出生字段。 见 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

  6. 一次性事件没有可靠的生命周期缓存。 聊天、脚本、特效、飞行物、任务提示等事件主要 通过 drain queue 即时消费;如果 UI 或 GameScene 尚未完成装配,事件可能被消费后无法补发。

  7. Classic Warp 的无条件重连和换图清理已落地首版,仍缺真实 E2E。 ClientVS22 的 RecvWarpPacket() 对所有 GC_WARP 都按 lAddr:wPort 重连;当前 Classic parser 透传 lX/lYM2Client 在重连前发 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

  2. 同步是距离阈值加 lerp。 当前按固定时间或距离发送移动,回包后按距离阈值 snap/lerp 没有完整 sequence、authoritative tick、抖动缓冲、预测回滚、动作保留和服务器纠偏时序。

  3. 普攻节奏仍是简化实现。 范围、攻击间隔、硬直和连击窗口均为固定值或简单按攻速缩放, 未使用 race、武器、动作 duration、命中帧、mount 和 Actor battle state。见 net_play.gd:34

  4. 自动攻击没有复现 ClientVS22 的目标预留逻辑。 参考客户端会持续检查目标存在、死亡、 可攻击、坐骑限制、charge skill、睡眠状态,并预留点击动作;当前主要是检测距离后发攻击包。 参考:ClientVS22/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

  7. 受击状态分裂。 EntityStore.stunnedNetPlay._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、 参考 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

  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

  9. 组队状态是近似定义。 affect 标签仍有暂定映射,队伍技能冷却、队长权限动态变化、离线、 死亡、距离状态和多账号行为还未完成真实验收。

  10. 好友、情侣、公会只有首版状态展示。 Messenger 分组、上线/离线、私聊生命周期、情侣 交互、公会权限、公告、日志、成员管理、公会战应答、领地和公会建筑均不完整或暂缓。

5. P1:任务、NPC 和世界玩法

  1. Quest EventManager 只支持少量标签。 当前支持 ENTERCLEARNEXTDONEQUESTIONINPUTSELECT_ITEMLETTER,但 COLORDELAYWAITIMAGE、 立绘、样式、滚动、多阶段按钮和 CONFIRM_WAIT 等被忽略。见 quest_dialog.gd:102

  2. 任务日志不是完整 Quest 系统。 当前主要是标题、counter 和 clock 文字列表,缺少真实 图标、开始/更新/删除状态、倒计时、QuestButton、世界箭头、边缘指示和点击定位。

  3. 钓鱼、副本、坐骑和观战者只有部分协议入口。 仍缺鱼获落袋、副本入口和计时、DungeonBlock、 坐骑模型与动作、坐骑攻击限制和速度、观战视角以及换图后的完整清理。

  4. PVP、PK 和决斗没有完整规则。 当前有部分关系和字段,但缺 PK 模式上行、名字颜色、 可攻击判断、阵营限制、保护区、目标板、挑战/复仇和禁止攻击逻辑。

6. P1/P2:模型、动画和特效

  1. 本地角色先显示胶囊占位体。 GameScene 初始化阶段先创建 Capsule,真实角色稍后加载, 会造成模型闪烁、相机目标高度变化、碰撞体和阴影重新挂接。见 game_scene.gd:136game_scene.gd:811

  2. SpeedTree 仍是程序化代理。 当前只嗅探 bark/composite 贴图,叶簇 UV 和枝干使用 proxy, 没有真实 .spt 几何、leaf table、LOD、impostor、风参数和原始阴影。见 tree_placeholder.cpp:190PARITY-GAP.md:77

  3. EffectLib 只有粒子近似。 .mse Particle 已映射到 GPUParticles3D,但 .dds 纹理被 程序化渐变代替,.mde 被 box 代替,SimpleLight、MovingType、TexAni、骨骼挂点和完整时间轴 尚未实现。见 effect_player.gd:8effect_player.gd:134

  4. GR2 运行时不是完整 ActorInstance。 缺 race/weapon/sex motion mode、loop/interrupt/ accumulation、action reservation、mount motion、武器轨迹、击退、倒地、起身和完整死亡动作。 参考:ClientVS22/ActorInstanceMotion.cpp

  5. 武器和特效骨骼挂点不完整。 当前主要覆盖右手武器和部分盾牌,缺双持、左手武器、职业挂点、 武器拖尾、骨骼特效、坐骑挂点、轨迹碰撞和装备切换清理。

  6. 材质是固定功能近似。 缺精确 alpha-test ref、多贴图 stage、color operation、透明排序、 角色专属光照和逐 surface 状态;sphere-map 默认 spec_power = 0,没有完整由装备数据驱动。 见 m2_material.h:39

7. P1/P2:地图、环境和相机

  1. 静态地图只完整接入 Building / DungeonBlock。 Effect、Ambience、地图机关、透明物排序、 portal/area、对象脚本状态和对象 LOD 没有完整迁移。见 metin2_world.cpp:367

  2. Pack 资源读取没有统一。 虽然 AssetSource/PackMount 已存在,World、terrain、tree、 water 和 static object 仍有直接 AssetResolver::resolve() 的路径,散文件和 .epk/.eix 运行 结果可能不同。见 metin2_world.cpp:380terrain_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

8. P1/P2UI、输入和本地化

  1. uiscript 可解析不等于控件语义完成。 当前约 80 个脚本可解析,但 ani_imagemark、 真实 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

  4. 系统设置部分只是保存数值。 CPU/GPU tiling 不会改变实际渲染,BGM 更换按钮 disabled, 名字颜色、伤害数字和部分画质设置没有完整接入渲染侧。见 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

9. P1/P2:音频和应用生命周期

  1. 地图 BGM 字段被解析但没有消费。 GC_MAIN_CHARACTER3_BGMGC_MAIN_CHARACTER4_BGM_VOL 中的 BGM 名称、音量被丢弃,当前主要靠登录页和选人页固定调用 play_bgm(),没有按地图和服务端状态自动切换。见 classic_parser.cpp:173

  2. 后台恢复后可能没有 BGM。 后台时 AppLifecycle 调用 stop_bgm(),回前台只恢复客户端和 帧率,没有恢复上一次曲目;stop_bgm() 还清空了 _bgm_name。见 app_lifecycle.gd:60app_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

10. 发布和验证问题

  1. Mac App 不是自包含资源包。 最近构建产物中的 assetsbgm 仍是指向工作区的绝对 符号链接,只复制 .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

  5. 补丁器、Logo、Movie、Web、Discord 和反作弊策略没有形成发布决策。 当前只是“能启动 并进游戏”的工程入口,不是完整的 ClientVS22 发布外壳。

  6. 缺少真实验收门禁。 当前已有单元测试、loopback 和部分单会话验证,但仍缺:完整 40250 真服矩阵、双账号交易/组队/PVP、断线重连、跨服换图、纯 pack 运行、Intel Mac、受控机位 截图和移动端真机性能验证。

当前结论

当前版本应继续定义为:可登录、可进入地图、具备部分玩法和表现的核心功能纵切; 连接骨架和换图清理已经开始实现,但尚未完成真实双连接、换图和完整生命周期验收。 它还不能定义为:ClientVS22 的跨平台等价客户端

当前已完成 G0 公共接口的首版冻结,正在进入附录 C 的并行波 A;已完成项与剩余项以文档顶部的 增量 47/48 和 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。