- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
27 KiB
network.packet_dispatch_protocol
Scope
核对 40250 的完整 GC 包注册表、静态/动态分帧、阶段 allowlist、包头 padding、未知包与已知未处理包的副作用、一次 pump 的消费数量,以及 ping/sequence 的发送顺序。这里的“已实现”要求完整调用链的分支、消费长度、状态变化和失败清理都等价,只有同名 case 或能找到解析函数不能算等价。
Reference call chain
UserInterface/Packet.hUserInterface/PythonNetworkStream.cpp::CMainPacketHeaderMapUserInterface/PythonNetworkStream.cpp::CheckPacketUserInterface/PythonNetworkStream.cpp::RecvErrorPacketUserInterface/PythonNetworkStream.cpp::RecvPhasePacketUserInterface/PythonNetworkStream.cpp::RecvPingPacketUserInterface/PythonNetworkStream.cpp::OnProcessUserInterface/PythonNetworkStreamPhaseLogin.cpp::LoginPhaseUserInterface/PythonNetworkStreamPhaseSelect.cpp::SelectPhaseUserInterface/PythonNetworkStreamPhaseLoading.cpp::LoadingPhaseUserInterface/PythonNetworkStreamPhaseGame.cpp::GamePhaseEterLib/NetPacketHeaderMap.cppEterLib/NetStream.cpp
CMainPacketHeaderMap 的有效表实际有 104 项静态、13 项动态,共 117 个有效 GC 注册项。源码中另有两行被注释掉的 Set(SLOW_TIMER、旧 TARGET_CREATE),不计入运行时注册表。动态项包括 QUEST_INFO、DUEL_START、CHAT、SYNC_POSITION、SHOP、SCRIPT、MESSENGER、GUILD、DUNGEON、NPC_POSITION、LAND_LIST、HYBRIDCRYPT_KEYS 和 HYBRIDCRYPT_SDB;WHISPER 在 40250 主表中是静态注册项,文本尾部由 RecvWhisperPacket 在固定头消费后再按 wSize 消费。
Current call chain
extension/src/net/classic/classic_stream.cpp::processextension/src/net/classic/classic_stream.cpp::feedextension/src/net/classic/classic_stream.cpp::dispatchextension/src/net/classic/classic_stream.cpp::handle_controlextension/src/net/classic/classic_session.cpp::on_packetextension/src/net/classic/classic_parser.cpp::on_gcextension/src/net/classic/wire_classic.h::packet_size_gcextension/src/net/classic/wire_classic.h::is_dynamic_gcextension/src/net/classic/wire_classic.h::is_sequence_cgextension/src/net/net_stream.cpp
Reference branch matrix
| 场景 | 40250 实现 | 当前端实现 | 结论 |
|---|---|---|---|
| 包头查表 | CMainPacketHeaderMap::Get 只接受主表中的 117 个 GC header;未注册 header 清空接收缓冲并 PostQuitMessage(0) |
当前主游戏流已按数值覆盖参考 117/117 个 header;仅独立 guild-mark SYMBOL_DATA=133 保留额外动态适配;未知普通流 header 回调错误并断流 |
PARTIAL:注册数值和静态/动态类别已对齐,sizeof/别名布局和应用级退出副作用仍未完全证明 |
| 静态包 | 使用 sizeof(TPacket...) 的完整包长;不足保留缓冲等待 |
packet_size_gc 当前有 104 个静态 case,按数值对应参考 104 个静态注册项;不足保留缓冲等待 |
PARTIAL:当前表达式与参考 sizeof 的逐项布局证明仍未完成 |
| 动态包 | 先读取 TDynamicSizePacketHeader,以 wire wSize 判断完整包;随后由具体 handler 再消费字段尾部 |
以 DynHead.size 分帧;QUEST_INFO 按 flag 重算实际服务端尾部,GUILD/SKILL_INFO 保留 40250 服务端少写一字节的兼容长度;字段尾部不足时 parser 返回失败 |
PARTIAL:特殊包的边界、过大值、声明值与实际消费值仍不是同一算法,但截断 handler 已与 Recv() 失败路径对齐 |
WHISPER |
主表标为静态;先消费 TPacketGCWhisper,再按 wSize - sizeof(header) 消费文本 |
packet_size_gc 使用固定 GCWhisperHead,静态分派先校验完整 wSize,回调 parser 时去掉 [header][wSize] |
MAPPED:固定头、文本尾和后续包边界已对齐 |
| 前导零 | CheckPacket 只在检查下一个 header 时剥掉连续零;遇到第一个非零停止;全是零则等待更多数据 |
dispatch 只剥掉当前前导零 run,零耗尽时等待下一次读取,遇到非零后再查表 |
MAPPED:已覆盖 zero-only、zero-run+valid packet 回归 |
| 阶段 allowlist | Login、Select、Loading、Game 各自 switch;未命中走 RecvDefaultPacket/RecvErrorPacket;Loading 的 default 才委托一次 GamePhase |
ClassicSession::on_packet 已收口 Login/Select/DirectEnter;Loading 使用 Loading 专属集并回退 Game 集;Game 使用显式 Game 集;错误阶段在当前帧后清空剩余接收缓冲 |
PARTIAL:完整 117 项映射、handler 失败清理和所有分支仍未逐项证明 |
| Login allowlist | PHASE/LOGIN_SUCCESS3/4/FAILURE/EMPIRE/MATRIX/PASSPOD/LOGIN_KEY/PING/HYBRID* |
可解析的 GC header 由统一 parser 尝试处理;可选集成包明确 false,其他不按 Login 表拒绝 | PARTIAL |
| Select allowlist | PHASE/EMPIRE/LOGIN_SUCCESS3/4/CREATE/DELETE/CHANGE_NAME/HANDSHAKE/HYBRID*/KEY_AGREEMENT*/POINT_CHANGE/PING;PLAYER_POINT_CHANGE 只消费不更新角色属性 |
Select 专用门禁已存在;GC_PLAYER_POINT_CHANGE 在 Select/DirectEnter 中只消费不写入 EntityStore,Loading/Game 才进入 parser |
MAPPED |
| Loading allowlist | PHASE/MAIN_CHARACTER*/CHARACTER_UPDATE/PLAYER_POINTS/POINT_CHANGE/ITEM_SET(20)/PING/QUICKSLOT_ADD/HYBRID*;default 进入一次 GamePhase |
loading_phase_packet 先收 Loading 专属包;21 号 ITEM_SET2 和其他 GamePhase 包走一次 game_phase_packet fallback;HybridCrypt 由 ClassicStream 消费后返回 |
MAPPED:阶段专属包、GamePhase fallback 和控制包 owner 已按数值/调用链对齐 |
| Game allowlist | 只在 GamePhase switch 中派发游戏包,包含实体、战斗、道具、任务、商店、公会、仓库、效果等分支 | game_phase_packet 显式复刻 GamePhase 非控制分支;未命中不再进入统一 parser |
PARTIAL:每个分支的副作用和 RecvDefaultPacket 清理仍未闭合 |
| 一次 pump | OnProcess 每次只运行当前 phase process 一次;GamePhase 按 dwRecvCount++ >= MAX_RECV_COUNT-1 与 8192 字节阈值继续;PHASE/握手类控制包立即 return |
ClassicStream::dispatch 在 Game 阶段缓冲低于 8192 字节时消费 3 个完整包后 return;phase/握手/初始密钥协商后立即 return,KEY_AGREEMENT_COMPLETED 会先解密已缓存字节并继续派发;Loading→Game fallback 仍不设 Game 限额 |
PARTIAL:大于 8192 字节的阈值、精确 tick 驱动和全量 burst 事件矩阵仍未完全证明 |
| 已知但 handler 失败 | phase handler 失败后调用 RecvErrorPacket,记录并清空接收缓冲;不由该分支直接断 socket |
on_packet == false 报错后消费当前帧、清空剩余接收缓冲并保留 socket/phase owner;未知 header、底层 framing 错误仍走断流 |
MAPPED:已对齐清缓冲与 owner 保留,应用层对可选集成错误的提示仍是适配差异 |
| 未知 header | 清空接收缓冲并退出消息循环 | on_error 后断流到 Offline |
PARTIAL:应用级退出副作用不同 |
| 断包/乱序 | Peek 失败则保留缓冲等待;具体 handler 自己决定二次 Recv 失败 |
framing 缓冲保留,但动态 underflow、未知 header 和 handler false 直接断流 | PARTIAL |
| Ping/Pong | 消费一个 GC_PING;发送 CG_PONG;安全模式额外 SendSequence() |
handle_control 消费 blank;send_fixed 在 sequence 模式附加序号 |
MAPPED:该单分支的发送顺序和 sequence 条件一致 |
Concrete table audit
40250 注册表已核对的结构性事实
CMainPacketHeaderMap共有 117 个有效Set(HEADER_GC_...),其中静态 104、动态 13;两行注释掉的Set不计入,数量由audit_packet_registry.py从当前参考源自动解析。- 其中 13 个使用
DYNAMIC_SIZE_PACKET,其余 104 个使用STATIC_SIZE_PACKET。 HEADER_GC_WHISPER是静态注册,不在动态表;文本是 handler 在固定头之后单独读取。HEADER_GC_SLOW_TIMER和HEADER_GC_TARGET_CREATE在 40250 源码中是注释掉的Set,不能仅因为Packet.h中存在结构体就视为有效注册项。GamePhase中出现的OBSERVER_*、攻击等分支不能反向证明它们已进入CMainPacketHeaderMap;分帧有效性以 map 为准。
当前端逐项核对结果
- 当前
packet_size_gc有 104 个 GC 尺寸 case;按数值已覆盖参考端 117 个注册项中的全部 104 个静态项,名称使用了LOGIN_SUCCESS_NEWSLOT、CHARACTER_CREATE_SUCCESS、CHARACTER_POINTS等协议别名;逐项sizeof/byte layout仍需机器化证明。 - 当前
is_dynamic_gc有 14 个 GC 分支,除 40250 的 13 项动态集合外只额外包含已登记的SYMBOL_DATA兼容路径;这不能与 40250 主表直接视为完全等价。 - 当前 parser 有 112 个 GC case,数量大于主表并不代表覆盖正确,因为部分 case 是 optional integration 或其他版本兼容分支,且缺少 phase gate。
- 当前
packet_size_gc有 104 个明确静态 case,is_dynamic_gc有 14 个 case;按数值与类别已覆盖参考 117/117 个注册项,唯一额外数值是独立 guild-mark 连接使用的HDR_GC_SYMBOL_DATA=133,必须继续作为显式兼容差异保留。 - 当前
QUEST_INFO和GUILD/SKILL_INFO的特殊尺寸修正是当前端为服务端兼容而加入的行为;需要以真实 40250 包序列证明它不会改变声明尺寸、剩余包边界和失败清理。
Equivalence matrix
| 项目 | 状态 | 证据 |
|---|---|---|
| Preconditions | PARTIAL | 静态/动态长度检查存在;动态 handler 的固定字段/记录尾部不足已返回失败,但动态最小头和所有声明值边界尚未完全机器化 |
| Branch structure | PARTIAL | 静态/动态主分支存在;当前额外兼容分支和统一 parser 改变了 40250 的分派分支 |
| Algorithms/formulas | PARTIAL | QUEST_INFO/GUILD 重算长度,40250 CheckPacket 以声明 wSize 做完整性判断 |
| State transition order | PARTIAL | 当前一次 dispatch 可消费完整 burst;40250 每个 OnProcess tick 只运行一次 phase process |
| Constants/units | PARTIAL | 当前尺寸单位均为 wire bytes,104 个静态 case/13 个主表动态 case 的数值类别已对齐,但与 40250 sizeof 别名的逐项证明未完成 |
| Timing/event sources | PARTIAL | feed 立即 dispatch 且 socket process drain;40250 由 phase tick 驱动单包消费 |
| Resource/data sources | MAPPED | 两侧主调用链、Packet/HeaderMap、wire table 均已定位 |
| Protocol side effects | PARTIAL | Ping/Pong/sequence 一致;未知包、handler false、错误 phase 的清理/退出不同 |
| Interruption/failure/cleanup | PARTIAL | 已知 handler false 现在清缓冲并保留 owner;动态字段截断会进入该路径;unknown/动态 framing underflow/底层 I/O 仍断流或终止当前连接,和 40250 的应用级退出副作用尚未完全统一 |
Required parity tests
- 从 40250
CMainPacketHeaderMap自动生成 117 项(header, sizeof, dynamic)清单,与当前packet_size_gc/is_dynamic_gc逐项比较;数值/类别覆盖已达到 117/117,下一步必须补齐别名的实际sizeof/byte layout对照,不能依赖人工推断。 - 对 13 个动态包分别覆盖:只有固定头、
wSize == header、wSize < header、声明长度大于缓冲区、声明长度后紧跟下一个包、handler 实际消费尾部不足。 - 单独覆盖
WHISPER的“固定头成功但文本尾部不完整”两段式行为,并确认文本后续包边界。 - 对 Login/Select/Loading/Game 的每个 allowlist 做允许与拒绝矩阵,确认错误阶段不会调用世界/角色/道具副作用。
- 同一缓冲区放入 1、2、N 个完整包,分别记录 40250 phase tick 与当前 dispatch 的事件顺序和消费数量。
- 覆盖 leading zero、packet body zero、未知 header、已知但 handler false、dynamic underflow、静态截断、连续包和错误后剩余缓冲。
Evidence run
build/extension/net_classic_stream_test、build/extension/net_classic_wire_test、build/extension/net_classic_session_test:PASS,已覆盖固定头、文本尾分片、后续静态包边界、前导零、错误阶段拒绝,以及已知 handler 失败后保留 phase owner 并继续处理下一 tick。.agents/skills/metin2-40250-parity-audit/scripts/audit_packet_registry.py:报告有效参考注册表117/104/13,当前packet_size_gc=104、is_dynamic_gc=14,数值覆盖117/117,额外数值仅为已登记的 guild-markHDR_GC_SYMBOL_DATA=133;报告仍为PARTIAL,因为该独立适配和sizeof/byte layout证明尚未闭合。.agents/skills/metin2-40250-parity-audit/scripts/audit_phase_dispatch.py --strict:按非 GAIDEN 数值比较 Login/Select/Loading/Game 四个阶段,排除由 ClassicStream 消费的控制包和独立 observer stream 后,四阶段均无缺失或额外 payload case;该结果只证明阶段分支结构,不证明每个 handler 的副作用。build/extension/net_classic_session_test、build/extension/net_classic_stream_test:PASS,新增 Loading 专属/ Game fallback、Game 阶段拒绝 Loading 主角色包、HYBRIDCRYPT 丢弃消费和 phase 控制包后的 pipelined packet 边界回归。build/extension/net_classic_stream_test:PASS,新增 Game 阶段四个小包只消费前三个、下一 tick 消费第四个的MAX_RECV_COUNT回归;旧版 85 字节HEADER_GC_MAIN_CHARACTER=15也已加入分帧/解析测试。- 以上测试尚未覆盖 117 项逐包尺寸/布局生成矩阵、每个 GamePhase 分支的允许/拒绝矩阵和 8192 字节以上 burst,因此不能将本合同提升为
MAPPED或TEST_VERIFIED。
Audit/fix round 2026-09-22 — dynamic handler truncation
沿 40250 的动态包调用链复核 CheckPacket -> Recv*Packet -> RecvErrorPacket 后,修复了当前 parser 在“包头/声明长度已经被分帧,但具体字段尾部不足”时错误返回成功的问题:
SCRIPT、QUEST_INFO、MESSENGER、SHOP、GUILD、DUNGEON、NPC_POSITION、LAND_LIST、CHAT的固定字段、变长记录和 flag-driven 字段现在不足即返回失败,不再以空数据、部分记录或默认值继续产生世界副作用。QUEST_INFO仍按 40250 服务端实际写入的 flag-driven 尾部计算帧长;GUILD/SKILL_INFO仍保留服务端声明 22、实际写入 21 字节的已证实兼容分支。两者是协议适配差异,不伪装成主表算法完全相同。ClassicStream现将这些 parser 失败统一交给已修复的RecvErrorPacket适配:消费当前帧、清空同批剩余数据、保留阶段 owner;下一帧仍可继续处理。
回归通过:net_classic_stream_test、net_classic_session_test。新增了 handler 失败后的清理流水线和截断动态 SCRIPT 回归;parser 同时收紧了消息、公会、商店、任务、地城、NPC 位置、土地列表和聊天的尾部检查。完整 13 项动态包声明长度矩阵、逐项 sizeof/byte layout、8192 字节以上 burst 和应用退出副作用仍保持 PARTIAL。
Audit/fix round 2026-09-22 — Select point-change side effect
复核 40250 PythonNetworkStreamPhaseSelect.cpp::SelectPhase 后确认,
HEADER_GC_PLAYER_POINT_CHANGE 在 SelectPhase 中只执行一次固定长度 Recv 并立即返回,
不会调用 RecvPointChange(),因此不会把选角阶段收到的旧点数写入 CPythonPlayer。
当前端此前虽然已经有 Select/DirectEnter allowlist,但会把该包继续交给
phase-neutral ClassicParser::on_gc,错误地更新 EntityStore。现已在
ClassicSession::on_packet 的 Select/DirectEnter 分支加入等价的消费后返回;同一包在
Loading/Game 仍按 40250 的对应 handler 正常更新点数。新增选角阶段“消费但无世界副作用”
回归测试,native session 测试通过。
Loading/Game 完整 allowlist、逐项 sizeof/byte layout、动态声明长度矩阵和大 burst
仍保持 PARTIAL。
Audit/fix round 2026-09-22 — phase branch matrix and Loading fallback
新增 audit_phase_dispatch.py,按实际非 GAIDEN 的 header 数值自动比较 40250
LoginPhase/SelectPhase/LoadingPhase/GamePhase 与当前 session gate。报告明确排除
ClassicStream 已消费的 ping、phase、握手、密钥协商和 HybridCrypt 控制包,以及不在主
CMainPacketHeaderMap 中的 observer stream 包,避免把协议层 owner 差异误报成 payload
缺失。当前四阶段 payload case 均为数值一致。
该矩阵同时发现并修正了两处调用链差异:Loading 不再把 21 号 ITEM_SET2 当作
Loading 专属分支,而是与 40250 一样通过一次 GamePhase fallback;Login/Select/Loading
的 HybridCrypt 不再进入 session allowlist,而由 ClassicStream 在分帧后消费并立即返回。
net_classic_session_test、net_classic_stream_test 和 phase 矩阵审计通过。
handler 副作用、逐项 sizeof/byte layout、动态声明长度矩阵和大 burst 仍保持
PARTIAL。
Audit/fix round 2026-09-21 — Loading/Game phase-local dispatch
沿 PythonNetworkStreamPhaseLoading.cpp::LoadingPhase 与
PythonNetworkStreamPhaseGame.cpp::GamePhase 的真实 switch/default 路径补齐当前端的阶段门禁:
Loading先接受主角色、点数、物品、快捷栏和 HybridCrypt 包;其他包只有命中 GamePhase 明确分支时才进入 parser,复刻 Loading 的一次 GamePhase fallback。InGame只接受 GamePhase 明确列出的实体、战斗、道具、任务、商店、公会、仓库、效果等分支;登录成功、角色主初始化等迟到包不再改写世界状态。- HybridCrypt 动态包按 40250 的“消费后返回”处理,不再误交给当前没有 vendor crypto handler 的通用 parser。
PHASE、握手和初始密钥协商控制包消费后立即结束当前 dispatch,避免后续包在旧阶段 owner 下被提前处理;KEY_AGREEMENT_COMPLETED是例外,必须先解密同一缓冲区中的后续密文并继续派发。
回归通过:net_classic_session_test、net_classic_stream_test、net_classic_encstream_test。已补上 GamePhase 小 burst 的 MAX_RECV_COUNT 边界、旧版 85 字节主角色包、KEY_AGREEMENT_COMPLETED 后已缓存密文继续派发,以及已知 handler 失败后的 RecvErrorPacket 清缓冲/owner 保留;8192 字节以上缓冲的继续消费、逐项布局、动态包声明长度和可选集成错误提示仍保持 PARTIAL。
Audit/fix round 2026-09-21 — numeric registry alignment
读取 40250 Windows 工程的实际条件分支后确认:GAIDEN 未定义,_IMPROVED_PACKET_ENCRYPTION_ 由 EterBase/ServiceDefs.h 启用。审计脚本现在会先计算非 GAIDEN 的包头数值,再比较当前 wire 表的静态/动态类别。
- 当前主游戏流按数值覆盖参考端全部
117/117个有效注册项,静态项为104,主表动态项为13。 - 移除了不在 40250
CMainPacketHeaderMap中的普通流包头:攻击12、旧更新24、观察者96/97/98和 Panama151;它们仍可作为 parser 历史兼容代码存在,但不会再被主流分帧接受。 HDR_GC_SYMBOL_DATA=133仅保留给独立 guild-mark 下载连接,并由审计报告明确标为额外适配,不再混入主游戏注册表的等价结论。
验证:audit_packet_registry.py 报告数值覆盖 117/117、类别无 mismatch;主流 packet_size_gc=104、is_dynamic_gc 含 13 个参考动态项加 1 个独立标志适配。逐项 sizeof/字节布局和动态包实际消费仍待继续闭合。
Implementation fix round 2026-09-21
按 40250 CMainPacketHeaderMap::Set(HEADER_GC_WHISPER, sizeof(TPacketGCWhisper), STATIC_SIZE_PACKET) 和 RecvWhisperPacket() 的两段式读取修复当前 classic framing:GC_WHISPER 不再归入通用动态 GC,固定头先按静态尺寸等待,随后按 wSize 等待文本尾;传给 phase-neutral parser 时去掉 [header][wSize],保持 [type][name][text] 数据形状,并验证文本后紧跟静态包不会被吞掉。
回归:net_classic_wire_test、net_classic_stream_test、net_classic_session_test、audit_packet_registry.py、ctest --test-dir build --output-on-failure(23/23)和 git diff --check 均通过。逐项 sizeof/byte layout、burst 大缓冲、handler 失败清理和 SYMBOL_DATA 独立连接边界仍未完全闭合,合同保持 PARTIAL。
Active EterLib transport-core review
EterLib/NetStream.cpp/.h实现 TCP connect/clear/disconnect、收发 buffer、Peek/Recv/Send/SendFlush、packet sequence 和可选 TEA security;NetPacketHeaderMap将 header 映射为 packet type,NetAddress负责地址/端口序列化。NetDatagram/NetDatagramReceiver/NetDatagramSender提供 UDP socket、bind、recv/peek、缓冲区和清理;NetDevice管理网络设备初始化/销毁。当前端classic_session/parser 已覆盖主要 classic stream,但没有证明 EterLib 的 buffer partial read、sequence、断线清理、UDP 辅助路径和 header map 规则均在所有调用方闭环。- 本轮完成 EterLib 网络底层活跃文件静态核对;已有 native wire/session 测试只覆盖当前适配层,合同继续为
PARTIAL。
Active UserInterface packet bridge review
PythonNetworkDatagram.cpp/h和PythonNetworkDatagramModule.cpp维护 state→header 的 datagram sender 注册、删除、选择、处理/检查 packet、bind 和状态包;它不是普通 stream 的 parser 别名,sender 生命周期和 header map 必须同步。PythonNetworkStream.h列出的接口覆盖角色/物品/商店/交易/任务/公会/飞行/聊天等全部 CG 请求以及相应 phase/GC 处理入口;PythonNetworkStreamPhaseOffline.cpp::OffLinePhase是离线 phase 的清理/转移入口。- 当前端 classic session 已有主要 packet registry 和适配测试,但没有证明所有 UI bridge 请求的状态门控、sender 注册、partial read、offline 清理、重复/未知 header 和副作用顺序都与 40250 一致;本轮纳入 active source 证据,合同保持
PARTIAL。
Status
PARTIAL。
Audit round 2026-09-21 — registry/phase/sequence refresh
按当前工作树重新运行包注册表、阶段分派和发送序列表审计:
audit_packet_registry.py:40250 有效主游戏注册表为 117 项,其中静态 104、动态 13;当前端主流按数值覆盖 117/117,静态/动态类别无 mismatch。当前额外的HDR_GC_SYMBOL_DATA=133仍只属于独立 guild-mark 连接适配,未计入主游戏流等价结论。audit_phase_dispatch.py:Login、Select、Loading、Game 四阶段的 payload case 按数值均无缺失或额外分支;脚本排除了由ClassicStream消费的 transport/control 包和独立 observer stream,结果只证明阶段结构,不证明 handler 的世界副作用等价。audit_sequence_table.py:当前端与 40250 的 32768 字节 sequence table SHA-256 完全一致。audit_source_coverage.py:活动 40250 Visual Studio 源文件 524 个,显式归属 142 个、静态复核 364 个、未归属行为 0 个;这说明清单覆盖门通过,但不代表这些合同已完成等价验证。
本轮没有发现可以在不改变已登记适配语义的前提下安全修复的新代码差异。合同继续保持 PARTIAL,待办仍是逐项 sizeof/字节布局证明、13 个动态包的声明长度与截断矩阵、8192 字节以上 burst/单 tick 消费顺序、每个 handler 的副作用和错误阶段清理,以及 unknown header 的应用级退出副作用取证。
Audit evidence reconciliation 2026-09-22T07:20Z
按当前工作树重新执行 packet/phase 结构审计:audit_packet_registry.py 报告参考有效注册表 117 项(静态 104、动态 13),当前主游戏流数值覆盖 117/117,类别无 mismatch;额外 HDR_GC_SYMBOL_DATA=133 仍只属于独立 guild-mark 连接适配。audit_phase_dispatch.py 的 Login/Select/Loading/Game payload case 均无缺失或额外数值。两项结果仅证明注册表和阶段分支结构,不能替代逐项 sizeof/byte layout、动态包边界、8192 字节 burst、handler 副作用与失败清理证明,因此合同继续 PARTIAL。
Audit round 2026-09-20 — registry and dynamic-boundary recheck
本轮重新执行 40250 CMainPacketHeaderMap 数值/类别对照和四阶段 dispatch 矩阵:
audit_packet_registry.py仍为参考主表117项、静态104、动态13;当前主游戏流按数值覆盖117/117,类别无 mismatch。额外的HDR_GC_SYMBOL_DATA=133仅作为独立公会图标下载适配保留,不能计入主流 1:1 等价。audit_phase_dispatch.py --strict的 Login/Select/Loading/Game payload case 均无缺失或额外数值;控制包、HybridCrypt 和 observer 专用包仍由各自 owner 消费。net_classic_wire_test、net_classic_stream_test、net_classic_session_test均通过,固定包、WHISPER 两段式尾部、前导零、已知 handler 失败清缓冲和已确认的 GUILD/SKILL_INFO 短写兼容没有回归。
本轮没有把“注册表覆盖”和“phase case 数值一致”升级为完整等价。仍未闭合的是 13 个动态包的声明长度/实际字段尾部全矩阵、逐项 sizeof/byte layout 自动证明、8192 字节以上 burst 的单 tick 消费边界,以及主流与独立 SYMBOL_DATA 连接的运行时隔离证据;合同继续为 PARTIAL。
Audit/fix round 2026-09-20 — login/select phase dispatch gate
沿 40250 LoginPhase、SelectPhase、LoadingPhase 的 switch/default 分支核对后,当前 classic session 增加了 Login 与 Select 的显式 GC allowlist:
- Connecting 阶段允许真实游戏连接先收到
LOGIN_SUCCESS3/4,随后仍由PHASE_SELECT接管;这保留了 DirectEnter 的实际连接顺序。 - LoggingIn 只接受 LoginPhase 的登录成功/失败、帝国、登录 key、可选认证集;CharSelect 只接受 SelectPhase 的角色 CRUD、帝国、登录成功和 point-change 集。
- DirectEnter 在 host stage 已显示 Loading、协议仍为 SelectPhase 时沿用 Select allowlist,普通 Loading 继续由 GamePhase fallback 处理游戏包。
- 错误阶段的游戏 spawn 包现在在到达
ClassicParser前被拒绝,不能改写 EntityStore;增加 Login/Select 反例测试。
阶段 allowlist 未命中现在按 40250 RecvErrorPacket 语义在当前帧消费后清空剩余接收缓冲并保留 socket/phase owner;真正的 parser/handler 失败仍由适配层 on_packet == false 断开 socket。Loading/Game 全量 allowlist、117 项机器映射、burst tick 和特殊动态包边界仍需继续审计,合同保持 PARTIAL。
Audit/fix round 2026-09-20 — character CRUD phase gate
本轮沿 LoginPhase/SelectPhase 的 default/error 分支复核发现,当前端 phase-neutral parser 会让角色创建、删除和改名回包绕过阶段 owner。该差异已在 ClassicSession::on_packet 收口:角色 CRUD/改名只允许 CharSelect,或允许 DirectEnter 在协议仍处于 SelectPhase 的 Loading + m_direct_enter 窗口;其他阶段返回明确错误,不再写入角色槽或发 UI 事件。
验证通过:net_classic_session_test、net_classic_stream_test、net_classic_wire_test、net_loopback_test、char_create_delete_test.gd、test_intro_select_parity.gd、p10_test.gd 和 git diff --check。
完整四阶段 allowlist、117-header 机器映射、burst tick、动态包边界和其他世界/道具副作用仍未闭合,合同保持 PARTIAL。