{ "schema_version": 1, "reference_root": "../40250/Server Client TMP4/ClientVS22/source", "contracts": [ { "id": "network.login.phase_flow", "title": "登录、握手、角色选择和进入游戏阶段流转", "subsystem": "network", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/AccountConnector.cpp", "UserInterface/PythonNetworkStreamPhaseLogin.cpp", "UserInterface/PythonNetworkStreamPhaseHandShake.cpp", "UserInterface/PythonNetworkStreamPhaseSelect.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseLoading.cpp", "UserInterface/PythonNetworkStream.cpp", "EterLib/NetStream.cpp" ], "symbols": [ "CAccountConnector::Connect", "CAccountConnector::__AuthState_RecvPhase", "CAccountConnector::OnConnectFailure", "CPythonNetworkStream::ConnectGameServer", "CPythonNetworkStream::RecvPhasePacket", "CPythonNetworkStream::SetHandShakePhase", "CPythonNetworkStream::SetLoginPhase", "CPythonNetworkStream::SetSelectPhase", "CPythonNetworkStream::SetLoadingPhase", "CPythonNetworkStream::SendEnterGame", "CPythonNetworkStream::SetGamePhase", "CPythonNetworkStream::SendLoginPacket", "CPythonNetworkStream::SendLoginPacketNew", "CPythonNetworkStream::OnConnectFailure" ] }, "implementation": { "files": [ "project/app_flow.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/classic/classic_stream.cpp", "extension/src/net/classic/classic_stream.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_parser.h", "extension/src/net/auth_client.h", "project/ui/loading_screen.gd", "project/ui/reconnect_ui.gd", "project/disconnect_world_reset_test.gd", "extension/src/net/text_codec.h", "extension/src/net/text_codec.cpp", "extension/src/net/classic/wire_classic.h", "extension/tests/net_text_codec_test.cpp", "extension/tools/net_classic_e2e.cpp" ], "symbols": [ "AppFlow::_do_connect", "AppFlow::_enter_character", "M2Client::connect_to_server", "M2Client::retry_login", "M2Client::disconnect_from_server", "M2Client::pump_auth", "M2Client::pump_classic", "M2Client::pump_game", "M2Client::reconnect", "ClassicSession::connect", "ClassicSession::retry_login", "ClassicSession::connect_with_login_key", "ClassicSession::connect_direct_enter", "ClassicSession::on_auth_phase", "ClassicSession::on_auth_packet", "ClassicSession::on_phase", "ClassicSession::on_packet", "ClassicSession::send_auth_login", "ClassicSession::send_login", "ClassicSession::send_login_by_key", "ClassicSession::send_select_char", "ClassicSession::enter_game", "ClassicStream::process", "ClassicStream::handle_control", "ClassicStream::dispatch", "ClassicParser::on_gc", "mtnet::to_wire", "mtnet::from_wire_str", "mtnet::wire_text_fits", "AuthClient::on_phase", "AuthClient::on_packet" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ { "reference": "CPythonNetworkStream/AccountConnector phase callbacks and EterLib socket pump", "implementation": "ClassicSession/ClassicStream plus M2Client Godot signals", "invariant": "auth key, selected slot, phase order, packet side effects and failure cleanup remain equivalent", "tests": [ "build/extension/net_classic_session_test", "build/extension/net_loopback_test" ] } ], "evidence": { "contract": "audit/contracts/network.login.phase-flow.md", "tests": [ "project/live_smoke_test.gd", "project/app_flow_lifecycle_test.gd", "project/app_flow_lifecycle_test.gd (login failure UI owner)", "build/extension/net_classic_session_test", "build/extension/net_classic_stream_test", "build/extension/net_loopback_test", "project/p10_test.gd", "project/disconnect_world_reset_test.gd", "build/extension/net_text_codec_test", "ctest --test-dir build --output-on-failure -j2", "build/extension/net_classic_e2e ", "build/extension/net_classic_e2e (expected login callback exit 5)", "build/extension/net_classic_e2e (expected connect-failed callback exit 5)", "build-debug/extension/net_classic_e2e -> configured game channel (PASS: auth -> four slots -> select -> loading -> PHASE_GAME)", "build-debug/extension/net_classic_e2e with MT_CLASSIC_EXPECT_EMPTY=1 (PASS as expected refusal: slot 0 occupied; probe does not misclassify a partially empty list as an all-empty account)", "build-debug/extension/net_classic_e2e -> configured game channel with MT_CLASSIC_DIRECT=1 (PASS: DirectEnter CG_PLAYER_SELECT -> PHASE_LOADING -> PHASE_GAME; 21 entities, 21 inventory slots)", "build-debug/extension/net_classic_e2e -> MT_CLASSIC_WARP_COMMAND=/warp (PASS: real GC_WARP replacement CG_LOGIN2 -> CG_PLAYER_SELECT -> PHASE_GAME)", "project/live_smoke_test.gd with MT_SMOKE_WARP_COMMAND=/warp (PASS: M2Client world_reset and second entered_game; live report)", "build-debug/extension/net_classic_e2e -> configured game channel (PASS as expected probe result: WRONGPWD callback, exit 5)", "build-debug/extension/net_classic_e2e -> unavailable game channel (PASS as expected probe result: connect-failed callback, exit 5)", "net_classic_session_test (auth peer close -> Offline owner)", "net_classic_session_test (HybridCrypt deployment guard and phase-owner retention)", "project/app_flow_lifecycle_test.gd (PHASE_CLOSE reconnect owner and phase-only LoginPhase fallback)", "project/app_flow_lifecycle_test.gd (explicit PHASE_CLOSE does not synthesize transport disconnected)", "net_classic_session_test (Loading PHASE_CLOSE leave owner and world cleanup)", "net_classic_session_test (PHASE_CLOSE clears the old transport and does not send CG_LOGIN on the closed stream)", "project/app_flow_lifecycle_test.gd (GC_WARP and fast reconnect failure retry-owner invariant)", "project/app_flow_lifecycle_test.gd (empty reconnect list returns to SELECT owner without phantom select)", "project/app_flow_lifecycle_test.gd (pre-game LoginPhase transition replaces SELECT owner)", "project/app_flow_lifecycle_test.gd (LoginPhase retry uses retry_login owner without full connect teardown)", "net_classic_session_test (LoginPhase retry replaces auth transport and sends one new CG_LOGIN3)", "net_classic_session_test (game peer-close retains the same session retry owner, replaces auth transport, and sends one new CG_LOGIN3 with the new password)", "net_classic_session_test (GC_AUTH_SUCCESS clears a pipelined stale PHASE_AUTH without duplicate CG_LOGIN3)", "net_classic_session_test pre-fix GC_AUTH_SUCCESS result authority (FAIL: result=1 with login_key=0 was reported as BESAMEKEY; 1 check)", "net_classic_session_test post-fix GC_AUTH_SUCCESS result authority (PASS: only result/bResult selects rejection; auth transport still cleans up for result=1/login_key=0)", "net_classic_session_test pre-fix PHASE_HANDSHAKE re-entry (FAIL: game/auth owners ignored the phase and game world leave cleanup; 3 checks)", "net_classic_session_test post-fix PHASE_HANDSHAKE re-entry (PASS: game/auth owners return to HandShake, Game world is cleared before on_phase_leave)", "net_classic_session_test (LoginPhase returns after GC_LOGIN_FAILURE and defers a pipelined PHASE_LOGIN to the next tick)", "net_classic_session_test (synchronous auth connect failure returns to Offline and keeps the LoginPhase retry owner)", "project/auth_connect_bridge_test.gd (initial and retry auth connect failure use connect_failed, retain a non-terminal LoginPhase owner, and do not emit login_failed)", "project/auth_peer_close_bridge_test.gd (initial and retry auth peer close retain a non-terminal LoginPhase owner without login_failed or connect_failed)", "net_classic_session_test (DirectEnter game connect failure routes through ClosePhase without credential login failure)", "net_classic_session_test (ordinary auth-to-game connect failure uses the distinct connect-failure callback)", "project/app_flow_lifecycle_test.gd (connect_failed keeps LoginPhase UI and status separate from login_failed)", "net_classic_session_test (DirectEnter PHASE_SELECT emits CG_PLAYER_SELECT before Loading)", "project/app_flow_lifecycle_test.gd and project/p10_test.gd (peer-close/phase-only LoginPhase owner releases GameScene and does not retain ReconnectUI)", "redacted Godot live smoke with MT_SMOKE_PHASE_COMMAND=/logout (PASS functional report: PHASE_GAME -> peer close -> LoginPhase owner; outer log gate retains unrelated shader leak)", "redacted Godot live smoke with MT_SMOKE_PHASE_COMMAND=/logout and MT_NET_TRACE=1 (PASS functional report; no client-observable phase=0/GC_PHASE(PHASE_CLOSE), server fixture produced peer close only)", "net_classic_session_test pre-fix auth-success handoff order (FAIL: game transport must start before auth transport clear; 1 check)", "net_classic_session_test post-fix auth-success handoff order (PASS: game:connecting -> auth:offline on loopback transports)", "net_classic_session_test pre-fix game-login retry process boundary (FAIL: replacement PHASE_LOGIN was consumed in the auth-success frame; 1 check)", "net_classic_session_test post-fix game-login retry process boundary (PASS: replacement game transport is processed on the following pump)", "net_text_codec_test (PASS: 龙驹简体中文免费版 is 27 UTF-8 bytes but 18 GB2312 bytes and round-trips through the 24-byte field)", "project/char_create_delete_test.gd (PASS: CharSelectScreen does not reject the GB2312-fitting Chinese name before native conversion)", "net_classic_session_test pre-fix Loading EnterGame event boundary (FAIL: GC_MAIN_CHARACTER did not trigger CG_ENTERGAME when the compatibility timer was disabled; 1 check)", "net_classic_session_test post-fix Loading EnterGame event boundary (PASS: GC_MAIN_CHARACTER sends CG_CLIENT_VERSION2 then one sequenced CG_ENTERGAME; duplicate main-character packets remain idempotent)", "net_classic_session_test pre-fix DirectEnter failure login-mode reset (FAIL: DirectEnter connect failure retained the stale slot through replacement PHASE_LOGIN; 1 check)", "net_classic_session_test post-fix DirectEnter failure login-mode reset (PASS: ClosePhase owner clears DirectEnter and ordinary replacement PHASE_LOGIN clears the stale slot)", "net_classic_session_test pre-fix PHASE_CLOSE LoginWindow retry owner (FAIL: PHASE_CLOSE did not retain a retry owner for replacement auth transport; 1 check)", "net_classic_session_test post-fix PHASE_CLOSE LoginWindow retry owner (PASS: same session replaces auth transport and sends one new CG_LOGIN3 after PHASE_AUTH)", "net_classic_session_test pre-fix auth PHASE_CLOSE silent owner (FAIL: auth PHASE_CLOSE was reported as game phase closure/auth server closed phase; 1 check)", "net_classic_session_test post-fix auth PHASE_CLOSE silent owner (PASS: auth PHASE_CLOSE keeps AUTH owner without phase_closed or synthetic error)", "40250 PythonNetworkStreamPhaseSelect.cpp reference inspection (PASS: SetSelectPhase runs the previous phase-leave owner before installing SelectPhase)", "net_classic_session_test pre-fix PHASE_SELECT re-entry owner (FAIL: Game -> Select retained the old world and skipped phase-leave notification; 2 checks)", "net_classic_session_test post-fix PHASE_SELECT re-entry owner (PASS: Game -> Select clears the old world and notifies the host before installing SelectPhase)", "40250 PythonNetworkStream.cpp reference inspection (PASS: RecvPhasePacket(PHASE_DEAD) is an explicit no-op)", "net_classic_session_test pre-fix PHASE_DEAD no-op time-sync owner (FAIL: PHASE_DEAD enabled time-sync and changed the next handshake response; 1 check)", "net_classic_session_test pre-fix PHASE_DEAD close-owner preservation (FAIL: PHASE_DEAD cleared the explicit PHASE_CLOSE owner marker; 1 check)", "net_classic_session_test post-fix PHASE_DEAD no-op time-sync owner (PASS: PHASE_DEAD keeps the plain handshake owner and preserves the explicit close marker)" ], "last_test_result": "PASS" }, "remaining": [ "network.login.phase_flow/auth-game-phase-matrix/live-package-order:真实服务端成功认证、四槽角色列表、选角、Loading 到 PHASE_GAME、错误密码 WRONGPWD 回调、game connect failure 和一次建立连接后的 peer close 已验证;认证成功后 game transport 先于 auth transport 清理的 40250 handoff 顺序已有 loopback 修复前失败/修复后通过证据;/logout 探针确认当前服务端 build 只产生 peer close,未产生客户端可观察的 GC_PHASE(PHASE_CLOSE),因此服务器主动 PHASE_CLOSE、空角色、完整服务器包序矩阵和跨阶段 retry 仍缺 live fixture;半包、重复 phase、PHASE_CLOSE 旧 world 和 PHASE_CLOSE/peer-close 不发送旧连接登录包的本地测试已覆盖", "network.login.phase_flow/auth-game-phase-matrix/empty-role-live-fixture:本地全空 LOGIN_SUCCESS4/PHASE_SELECT 和无自动选人已有回归;net_classic_e2e 已增加 MT_CLASSIC_EXPECT_EMPTY=1,当前 live 账号只有部分空槽,正向全空账号证据仍缺", "network.login.phase_flow/login-failure-retry-cleanup:本地 auth peer close -> Offline、auth peer-close bridge 保留非终态 LoginPhase owner、pre-game/in-game peer close、旧 world 清理、游戏 peer-close 后同一 session 替换认证 transport 并用新密码发送 CG_LOGIN3、DirectEnter/普通 LoginPhase game connect failure owner、认证 TCP connect failure -> Offline、认证失败后的同 session retry、认证成功按 bResult 而非 ticket 值判定、认证 connect_failed bridge、登录表单 retry 只替换认证 transport、半包和重复 phase、普通 Select/Game -> Login 的角色列表/world 清理、Game -> Select 的旧 world 清理和 phase-leave 通知、PHASE_DEAD 保持空操作且不改变 time-sync owner、认证/游戏 PHASE_HANDSHAKE 重入 owner、认证 PHASE_CLOSE 保持 AUTH owner 且不设置游戏 phase_closed、SELECT/Loading 的 phase-only LoginPhase owner、LOGIN 阶段失败保留原 LoginPhase UI、PHASE_AUTH 前忽略提前到达的 GC_LOGIN_FAILURE、认证未知应用头保留 AUTH owner、active Loading/Game 未知头按 RecvErrorPacket 清理缓冲、游戏态 PHASE_CLOSE/peer-close 后 AppFlow 回唯一 LoginPhase owner、显式 PHASE_CLOSE 不再伪造 transport disconnected、显式 PHASE_CLOSE 后同一 session 替换认证 transport、Loading PHASE_CLOSE leave owner/world 清理、GC_WARP/快速重连同步连接失败后的 retry owner,以及空重连角色列表回到 SELECT 且不发送过期槽位已有本地回归;真实 Windows LoginWindow.OnConnectFailure、认证其它可选/静默分支、跨阶段失败后的完整 UI retry/真实服务端重试包序、真实重复进入包序、DirectEnter/GC_WARP 失败回退包序和服务器主动 PHASE_CLOSE live 包序仍需测试;40250 的 m_dwChangingPhaseTime 尚未发现实际 enforced check", "network.login.phase_flow/ticket-slot-warp-order:本地回归和实时探针已证明 login key、selected slot、DirectEnter、GC_WARP 的 replacement CG_LOGIN2/CG_PLAYER_SELECT 包序,新连接到达 PHASE_GAME 前不会继承旧的在线标记,且 DirectEnter/GC_WARP 失败回到 LoginPhase 时会清除 DirectEnter 模式;仍缺失败后的真实 GC_WARP 回退包序", "network.login.phase_flow/optional-security-branches:Matrix、Passpod、Panama、HybridCrypt、HS/XTrap 等完整副作用仍未实现;静态/动态可选包已增加显式 deployment guard 和 optional_security_required 信号,不可静默断开", "network.login.phase_flow/real-server-sequence:真实 Ubuntu 40250 服务端的失败账号、DirectEnter、一次成功 GC_WARP 到 PHASE_GAME 和 /logout 后建立连接的 peer close 已验证;服务端源码存在 SetPhase(PHASE_CLOSE) 状态链,但当前 build 的 live trace 没有客户端可观察 phase=0/GC_PHASE(PHASE_CLOSE),空角色、真实服务器主动 PHASE_CLOSE 包序和完整重试包序仍缺 fixture" ], "notes": "按 40250 SetLoadingPhase/SetGamePhase 修复 Loading 遮罩阶段归属和无 SceneTree 选人确认吞包;按 ClearLoginInfo 修复 CG_LOGIN/CG_LOGIN3 成功发送后的密码生命周期;auth-game-phase-matrix 修复 PHASE_CLOSE 回 LoginPhase、Loading/Game phase-leave world 清理、宿主 world_reset 通知、重复 phase handler、auth/game 登录拒绝保留 phase 和登录失败回调;本轮 auth-success handoff 按 CAccountConnector::__AuthState_RecvAuthSuccess -> SetLoginKey -> MainStream.Connect -> AccountConnector.Disconnect 固定为 game transport 先连接、auth transport 后清理,并以 loopback 失败前/后顺序回归;login-failure-retry-cleanup 现已补齐认证端 peer close -> Offline owner、认证 peer-close bridge 保留非终态 LoginPhase owner、认证 PHASE_CLOSE 静默保留 AUTH owner、游戏 peer close 前置通知、旧 world 清理、DirectEnter/普通 LoginPhase game connect failure 的独立 owner、认证 connect_failed bridge、显式重试请求保护、普通 Select/Game -> Login 的旧角色/world 清理、SELECT/Loading 的 phase-only LoginPhase owner、LOGIN 阶段失败保留原 LoginPhase UI、LoginPhase retry 只替换认证 transport 且不触发重复全局 world_reset、PHASE_AUTH 前登录包的阶段隔离、active Loading/Game 未知头按 RecvErrorPacket 清理缓冲并保留 phase owner、游戏态 PHASE_CLOSE/peer-close 后 AppFlow 释放 GameScene 并切换为唯一 LoginPhase owner、显式 PHASE_CLOSE 不再伪造 transport disconnected、Loading PHASE_CLOSE leave owner/world 清理、GC_WARP/快速重连同步连接失败后的 retry owner,以及空重连角色列表回到 SELECT 且不发送过期槽位;同步增加 ClassicSession 析构前 stream 回调解绑,避免最终 Offline 通知写入已析构字段;本轮 legacy-text-wire-encoding 固定 UTF-8 内部与 GB2312/兼容 wire 边界,修正 24-byte 角色名容量和 GCLoginSuccess4 329-byte 布局;本轮 live-package-order 让实时探针观察应用层登录失败和 game remote-disconnect 回调,不把 40250 的失败回调误报为 timeout;本轮 ticket-slot-warp-order 修复 GC_WARP 复用 session 时继承旧 PHASE_GAME 在线标记,并用回归测试核对 DirectEnter/GC_WARP 的 CG_LOGIN2 包序和 login key;本轮 direct-enter-loading-select-packet 按 40250 introloading.LoadingWindow.Open 的实际调用链补发 DirectEnter 的 CG_PLAYER_SELECT,实时探针已到达 PHASE_GAME;本轮 gc-warp-live-package-order 新增 e2e/Godot live 验证入口,确认真实 GC_WARP 的 world_reset、replacement CG_LOGIN2/CG_PLAYER_SELECT、Loading 和第二次 PHASE_GAME 成功,未发现生产连接逻辑差异;本轮 optional-security-branches 为静态可选包和 HybridCrypt 动态包增加显式 deployment guard/optional_security_required,保持 phase owner 但不伪装成已实现 vendor runtime;本轮 peer-close-login-owner 依据真实 /logout peer-close 证据,移除 AppFlow 的自动重连遮罩/旧 GameScene 保留,回归 LoginPhase owner;本轮 phase-close-server-fixture 分离显式 PHASE_CLOSE 与 OnRemoteDisconnect,不再由 native bridge 发出伪造的 disconnected 信号;本轮 empty-role-live-fixture 为真实空角色包序增加 MT_CLASSIC_EXPECT_EMPTY=1 探针模式,当前仍缺全空账号正向 live 证据;本轮 login-form-retry-owner 按 40250 LoginWindow.Connect/AccountConnector.Connect 增加 LoginPhase retry owner 和只替换认证 transport 的 native/UI 回归;本轮 game-connect-immediate-failure-owner 按 CPythonNetworkStream::OnConnectFailure 分离普通 LoginWindow.OnConnectFailure 与 DirectEnter ClosePhase,并新增 M2Client connect_failed signal,GC_WARP 失败不再伪造 disconnected;本轮 auth-connect-bridge-owner 按 CAccountConnector::OnConnectFailure -> Offline -> LoginWindow 分离认证连接失败和凭据失败,保留 ClassicSession 供 retry_login 复用,并以 auth_connect_bridge_test.gd 固定修复前失败/修复后通过证据;本轮 auth-peer-close-bridge-owner 按 CAccountConnector::OnRemoteDisconnect -> Offline 让认证 peer close 不再伪造 login_failed/connect_failed,并以 auth_peer_close_bridge_test.gd 固定旧/新认证 transport 的回归证据。ClassicStream 的 TCP connect deadline、半包、本地 loopback、文本编码回归、真实成功包序、真实失败回调、DirectEnter CG_PLAYER_SELECT 到 PHASE_GAME、真实 GC_WARP 成功 round trip、auth-peer-close owner、auth-peer-close bridge、登录 UI owner、PHASE_CLOSE 旧 world owner、跨阶段 LoginPhase owner、显式 PHASE_CLOSE transport-owner 分离、空角色本地 SELECT owner、auth handshake owner 隔离、认证 PHASE_CLOSE 静默 owner、Loading unknown-header owner、HybridCrypt guard、Loading PHASE_CLOSE leave owner、warp connect failure retry owner、空重连 SELECT owner、pre-game LoginPhase owner、本轮 peer-close LoginPhase owner、login-form-retry-owner、game-connect-immediate-failure-owner 和 auth-connect-bridge-owner 已有本地证据;空角色全空 live fixture、服务器主动 PHASE_CLOSE live 包序、认证其它完整副作用、跨阶段完整 UI retry/真实重复进入包序、GC_WARP 失败回退包序、真实 Windows LoginWindow.OnConnectFailure 和可选安全 runtime 仍使合同保持 PARTIAL。", "fingerprints": { "reference": "f5437f05ed4f07b67fe1650626e3402c15b40d35556be89b4b894756d6867036", "implementation": "4ccd216584c72f075a6bf6b437e6fcecb3868378fb42caff2c70cbe0e263bec8", "tests": "4676ffdf7c24266f79b2f5dad54ed12727c26a144f7be28e5bf2c68f37acfc09" } }, { "id": "movement.keyboard.motion", "title": "键盘 WASD 输入、转向、移动动作和停止", "subsystem": "movement", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayerInputKeyboard.cpp", "UserInterface/PythonPlayerInput.cpp", "UserInterface/InstanceBaseMovement.cpp", "UserInterface/InstanceBaseMotion.cpp", "GameLib/ActorInstanceMotion.cpp", "GameLib/ActorInstanceMotionEvent.cpp" ], "symbols": [ "CPythonPlayer", "CInstanceBase", "SetMotion", "Move" ] }, "implementation": { "files": [ "project/player_controller.gd", "project/net_play.gd", "project/net_world.gd", "project/test_wasd_steering_parity.gd", "project/test_no_auto_move_regression.gd", "project/player_motion_test.gd", "project/keyboard_motion_timeline_test.gd" ], "symbols": [ "PlayerController", "NetPlay", "NetWorld" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/movement.keyboard-motion.md", "tests": [ "project/test_wasd_steering_parity.gd", "project/test_no_auto_move_regression.gd", "project/player_motion_test.gd", "project/keyboard_motion_timeline_test.gd" ], "last_test_result": "PASS" }, "remaining": [ "固定 300 像素 Dst 已按事件创建并在目标到达时消费;锁定/移动技能期间 held WASD 会在解锁后重试;.msa 根运动和当前连续速度/米制推进的时序差异仍未闭合", "OnMove/OnMoving/OnWaiting/OnStop 与当前每帧 signal、NetPlay 节流的事件源逐项证明", "同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定的 held WASD 恢复已覆盖基础路径", "相机旋转副作用和真实角色模型/骨骼在快速反向输入时不翻转", "输入事件到移动请求、停止请求和网络包的完整时间线" ], "fingerprints": { "reference": "299f39fd0c470bfe84c8520b30f23d5f25ffc563cfd277c1f30e5b622627d958", "implementation": "751a83ed849b318ee47472ccb3998dbe54742b76d39169abbfa932b737f5bfbf", "tests": "fa6838d2ca54407b8e8257eba0ed006e5edb2ee78f62cae3a40cb4a0db9fafb6" } }, { "id": "combat.death.exp_drop", "title": "攻击动作、怪物死亡、经验和掉落链路", "subsystem": "combat", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/InstanceBaseBattle.cpp", "UserInterface/InstanceBaseMotion.cpp", "UserInterface/PythonEffectModule.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonPlayer.cpp", "GameLib/ActorInstance.cpp", "GameLib/FlyingData.cpp", "UserInterface/Packet.h" ], "symbols": [ "RecvDeadPacket", "RecvPointChange", "RecvCreateFlyPacket", "RecvDamageInfoPacket", "RecvItemGroundAddPacket", "RecvItemGroundDelPacket", "CInstanceBase::Die", "CActorInstance::Die", "CPythonPlayer::NotifyCharacterDead", "CPythonPlayer::NotifyDeadMainCharacter" ] }, "implementation": { "files": [ "project/net_play.gd", "project/net_world.gd", "project/player_controller.gd", "project/fx/skill_fx.gd", "extension/src/net/entity_store.cpp", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/m2_client.cpp", "project/ui/ground_items.gd", "project/ui/death_ui.gd", "project/ui/quickbar.gd", "project/game_scene.gd", "project/mob_death_drop_test.gd", "project/combat_parity_test.gd", "project/playable_combat_test.gd", "project/netplay_test.gd", "extension/tests/net_state_queue_test.cpp", "extension/tests/net_entity_test.cpp" ], "symbols": [ "NetPlay", "NetWorld", "PlayerController", "SkillFx", "EntityStore::mut_set_point", "EntityStore::mut_dead", "EntityStore::mut_ground_add", "NetWorld::_on_dead", "NetWorld::_on_fly", "NetPlay::_on_points", "NetPlay::_trigger_exp_gain", "GroundItems::_on_added" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/combat.death-exp-drop.md", "tests": [ "project/test_combat_death_pickup_entry_parity.gd", "project/test_exp_fly_parity.gd", "project/test_ground_pickup_parity.gd", "project/mob_death_drop_test.gd", "project/combat_parity_test.gd", "project/playable_combat_test.gd", "project/gamescene_test.gd", "project/netplay_test.gd", "extension/tests/net_state_queue_test.cpp", "extension/tests/net_entity_test.cpp" ], "last_test_result": "PARTIAL: local-main fallback and indexed-fly actor-instance gate regressions pass; live server packet order and protocol race cases remain unverified" }, "remaining": [ "主角 GC_DEAD 的 GameOver/复活 UI 和恢复动画与真实服务端包序的一致状态门", "POINT_* 的真实 effect manager 生命周期、金币浮字资源和全部状态窗口旁路刷新", "真实服务端攻击→伤害→死亡→POINT_EXP→CREATE_FLY→ITEM_GROUND_ADD 的包序记录", "重复死亡、MISS、无效 VID、目标先删除、空 owner/重名、先拾取后 DEL、重连 catch-up 和拾取竞争", "死亡尸体/目标效果/经验球/动作队列在 GC_CHARACTER_DEL、复活和跨图 reset 中的统一清理" ], "notes": "本轮已按 40250 SendClickItemPacket/GetCloseItem/__OnPressItem 修复 party/anti-flag 所有权、DISTANCE_APPROX/300cm 选择、最高 VID tie-break、骑马固定边界和 150cm 点击入口距离门;并修复 RecvCreateFlyPacket 在本地主角 EntityStore 行暂缺时通过 GetMainInstancePtr 语义回退 _local_node,同时要求远端起点/终点实例存在后才创建 indexed fly。相关回归通过;真实服务端死亡包序、重复/乱序/重连/拾取竞争、死亡资源生命周期和跨图清理仍保持 PARTIAL。", "fingerprints": { "reference": "2fd8011f984a11ce6ae2aa357a7a8be5ba838a4cc65885ac5af7b02c8a7ef73a", "implementation": "37890f7f10720c9d567bcf4d22ce699145b0838e0ffd1ab0d89a5e0a8a34426c", "tests": "2ea78de25b5e51361dd6b6256c29254bb29ffc9f2a2a6ba35c8fb03affd73cb3" } }, { "id": "skill.cast.effect_timing", "title": "技能施法、动作事件、服务端结果和特效时序", "subsystem": "skill", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonSkill.cpp", "UserInterface/PythonPlayerSkill.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/InstanceBaseEffect.cpp", "EffectLib/EffectInstance.cpp", "EffectLib/ParticleInstance.cpp", "GameLib/ActorInstanceMotionEvent.cpp" ], "symbols": [ "CPythonSkill", "CPythonPlayer::UseSkill", "CInstanceBase::SetEffect", "CActorInstance::OnMotionEvent" ] }, "implementation": { "files": [ "project/player_skill.gd", "project/ui/skill_table.gd", "project/net_play.gd", "project/net_world.gd", "project/fx/skill_fx.gd", "project/fx/effect_player.gd", "project/ui/player_view.gd", "project/game_scene.gd", "project/ui/quickbar.gd", "project/remote_motion_splash_test.gd", "extension/src/metin2_anim.h", "extension/src/metin2_anim.cpp", "extension/src/net/entity_store.cpp", "extension/src/net/m2_client.cpp", "extension/tests/net_state_queue_test.cpp", "project/combat_fx_test.gd", "project/skill_test.gd", "project/test_skill_state_arg_parity.gd", "project/skill_motion_loop_test.gd", "project/test_combat_death_pickup_entry_parity.gd" ], "symbols": [ "PlayerSkill", "SkillTable", "NetPlay", "SkillFx", "Effect", "NetPlay::start_remote_motion_splash", "NetPlay::_remote_splash_attack_process", "Metin2AnimPlayer::set_motion_loop_count", "Metin2AnimPlayer::_process" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/skill.cast-effect-timing.md", "tests": [ "project/combat_fx_test.gd", "project/skill_test.gd", "project/effect_playback_test.gd", "extension/tests/net_state_queue_test.cpp", "project/test_skill_state_arg_parity.gd", "project/skill_motion_loop_test.gd", "project/net_world_remote_action_test.gd", "project/remote_motion_splash_test.gd", "project/player_motion_test.gd", "project/test_combat_death_pickup_entry_parity.gd", "project/target_effect_test.gd", "project/test_no_auto_move_regression.gd", "project/netplay_test.gd" ], "last_test_result": "PASS: valid MSA skill lock remains motion-driven until motion_bound(wait); timer fallback remains for headless/missing motion; skill/combat/effect/remote-motion regressions passed" }, "remaining": [ "按技能类型逐分支对比", "真实攻击/辅助技能时序测试", "逐技能 MSA 事件到 MSE 的挂点、颜色、混合、飞行/特殊攻击和服务端结果语义" ], "fingerprints": { "reference": "ec50966d172f454a71ce8368aa4f5c088a4b7613f508070397db14e405c07fae", "implementation": "0b5ce6ab0b751378fdad8ae8140471df51f2ab0572d7bd5d209c5b2663975abf", "tests": "cd487545321a14364ab51e2fd39cc232944f68fdeaa4489e1633fd8348036a84" } }, { "id": "ui.equipment.tooltip", "title": "查看装备属性面板和普通提示框生命周期", "subsystem": "ui", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonItem.cpp", "UserInterface/PythonItemModule.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonTextTail.cpp" ], "symbols": [ "RecvItemSetPacket", "CPythonItem", "CPythonNetworkStream::SendItemUseToItemPacket" ] }, "implementation": { "files": [ "project/ui/view_equipment_ui.gd", "project/ui/item_tooltip.gd", "project/ui/item_tooltip_view.gd", "project/ui/item_tooltip_bridge.gd", "project/ui/ui_manager.gd", "project/ui/inventory_ui.gd", "project/ui/shop_ui.gd", "project/ui/exchange_ui.gd", "project/ui/safebox_ui.gd", "project/ui/mall_ui.gd", "project/ui/private_shop_ui.gd", "project/ui/equip_model.gd", "project/ui/player_view.gd", "project/view_equipment_ui_test.gd", "project/item_tooltip_test.gd", "project/inventory_ui_test.gd", "project/test_npc_shop_flow.gd", "project/test_safebox_mall_parity.gd", "project/p8_test.gd", "project/private_shop_ui_test.gd" ], "symbols": [ "ViewEquipmentUI", "ItemTooltip", "ItemTooltipView", "EquipModel" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/ui.equipment-tooltip.md", "tests": [ "project/view_equipment_ui_test.gd", "project/item_tooltip_test.gd", "project/equip_model_test.gd", "project/inventory_ui_test.gd", "project/test_npc_shop_flow.gd" ], "last_test_result": "PASS" }, "remaining": [ "证明背包、商店、交易、仓库、商城仓库、个人摊位和装备窗口快速切换时的全局单面板生命周期", "属性来源/槽位/时装层级/窗口边界定位对比", "重复打开关闭、切换目标、空装备和远程更新测试" ], "fingerprints": { "reference": "510b9218d7276cdc30688f45a1a8c113264cc40a429f0d9e82768b946bcac938", "implementation": "2380ee1375b882e42e374ed25a59ca346cb46ec8f6932922683f193e460ef67d", "tests": "59f2c3750d6845db69795e01ceb51a664e384d8f8a79ef9d80691b8453bd78f6" } }, { "id": "lifecycle.bootstrap_phase_host", "title": "启动、阶段属主、窗口和应用生命周期", "subsystem": "lifecycle", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "EterLib/MSApplication.cpp", "EterLib/MSWindow.cpp", "UserInterface/PythonApplication.cpp", "UserInterface/PythonApplicationEvent.cpp", "UserInterface/PythonApplicationProcedure.cpp", "UserInterface/PythonNetworkStream.cpp", "UserInterface/PythonNetworkStreamPhaseLogin.cpp", "UserInterface/PythonNetworkStreamPhaseSelect.cpp", "UserInterface/PythonNetworkStreamPhaseLoading.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp" ], "symbols": [ "CPythonApplication::Run", "CPythonApplication::Process", "CPythonApplication::Destroy", "CPythonApplication::WindowProcedure", "CPythonNetworkStream::RecvPhasePacket", "CPythonNetworkStream::ClosePhase", "CPythonNetworkStream::SetLoginPhase", "CPythonNetworkStream::SetSelectPhase", "CPythonNetworkStream::SetLoadingPhase", "CPythonNetworkStream::SetGamePhase", "CPythonNetworkStream::__LeaveGamePhase", "CPythonNetworkStream::__ClearNetworkActorManager" ] }, "implementation": { "files": [ "project/client_main.gd", "project/app_flow.gd", "project/login.gd", "project/app_lifecycle.gd", "project/game_scene.gd", "project/audio.gd", "project/net_play.gd", "project/net_world.gd", "project/ui/loading_screen.gd", "project/ui/death_ui.gd", "project/ui/reconnect_ui.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/net_bounds.h" ], "symbols": [ "client_main::_ready", "AppFlow::start", "AppFlow::_goto_login", "AppFlow::_goto_select", "AppFlow::_goto_game", "login::_init", "login::_connect", "login::_on_entered_game", "AppLifecycle::_notification", "M2Client::_process", "M2Client::net_poll", "M2Client::disconnect_from_server", "M2Client::pump_classic", "ClassicSession::on_phase", "ClassicSession::disconnect", "GameScene::_on_world_reset", "NetPlay::clear_for_map_change", "NetWorld::clear_for_map_change", "EntityStore::reset_for_map_change" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "MAPPED", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ { "reference": "Windows message loop, WM_ACTIVATEAPP and WM_CLOSE", "implementation": "Godot MainLoop notifications and AppLifecycle", "invariant": "phase, socket, input, audio, pause and shutdown side effects must remain ordered and observable", "tests": [ "project/app_flow_lifecycle_test.gd" ] } ], "evidence": { "contract": "audit/contracts/lifecycle.bootstrap-phase-host.md", "tests": [ "project/app_flow_lifecycle_test.gd" ], "last_test_result": "PASS: AppFlow close_requested consumer regression; remaining runtime lifecycle branches are PARTIAL" }, "remaining": [ "PHASE_DEAD 不进入 phase_changed dead;DeathUI 已改为只消费 GC_DEAD -> entity_dead,仍需补齐 40250 GameOver/决斗例外、死亡动作和真实包序", "PHASE_CLOSE 的本地 ClassicSession/AppFlow owner 已按 ClosePhase -> SetLoginPhase 回到 Login;仍需补齐真实 runtime 的状态/UI 时序、重复 close 和最终清理证据", "classic SELECT->LOADING 已在阶段回调内按 40250 SetLoadingPhase 清旧 world,并以空 world guard 避免 GC_WARP 重复 reset;NetPlay/NetWorld/特效的完整 owner 顺序和真实 server 包序仍待证明", "_goto_game 等待模型时已移除主动 net_poll,仍需用运行时 fixture 证明模型构建期间不会发生重复 phase 或 entered_game", "close_requested 已由 AppFlow 和旧 login.gd 分别消费并执行断开/解绑/退出;仍未证明真实 socket close 回调、重复 close、Windows 退出确认和最终 manager 销毁顺序", "focus/后台音频、全屏、socket 暂停语义与 40250 仍不等价", "client_main.gd -> AppFlow 与旧 login.gd 是两套启动/生命周期拓扑;需要统一入口或分别证明阶段、关闭、音频和销毁语义一致", "没有 CPythonApplication::Destroy 对应的可观测总清理顺序和重复销毁测试" ], "notes": "本轮完成启动、阶段、Loading、DEAD/CLOSE、焦点/后台、旧 login.gd 入口和销毁调用链审计;按 40250 修正 DeathUI 的 PHASE_DEAD 错误依赖、classic Loading 旧 world 清理、两条入口的 close_requested 消费和幂等退出,以及 _goto_game 重复夺取网络 pump。完整清理 owner、CLOSE 状态语义、跨节点阶段时序、旧入口和销毁顺序仍使合同保持 PARTIAL。", "fingerprints": { "reference": "de448fadf6154e7d7e8375358fc41253fdbf123d141734b366eae631bad04acb", "implementation": "b4525f86e212e902df4899dc11591d4fc2516ce39138227008652ab973465795", "tests": "311276b58ab3a25978e7d0659b0376ced27fccd075f6f70b0e98046e158b166f" } }, { "id": "network.character_select.create_delete", "title": "角色列表、创建、删除、改名、选人", "subsystem": "network", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/Packet.h", "UserInterface/PythonNetworkStream.cpp", "UserInterface/PythonNetworkStreamPhaseLogin.cpp", "UserInterface/PythonNetworkStreamPhaseSelect.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp" ], "symbols": [ "CPythonNetworkStream::SetLoginPhase", "CPythonNetworkStream::__RecvLoginSuccessPacket3", "CPythonNetworkStream::__RecvLoginSuccessPacket4", "CPythonNetworkStream::SetSelectPhase", "CPythonNetworkStream::SendSelectEmpirePacket", "CPythonNetworkStream::SendSelectCharacterPacket", "CPythonNetworkStream::SendCreateCharacterPacket", "CPythonNetworkStream::SendDestroyCharacterPacket", "CPythonNetworkStream::SendChangeNamePacket", "CPythonNetworkStream::__RecvPlayerCreateSuccessPacket", "CPythonNetworkStream::__RecvPlayerCreateFailurePacket", "CPythonNetworkStream::__RecvPlayerDestroySuccessPacket", "CPythonNetworkStream::__RecvPlayerDestroyFailurePacket", "CPythonNetworkStream::__RecvChangeName", "CPythonNetworkStream::ConnectGameServer", "CPythonNetworkStream::__DirectEnterMode_Set", "CPythonNetworkStream::__DirectEnterMode_Initialize" ] }, "implementation": { "files": [ "extension/src/net/classic/wire_classic.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/m2_client.cpp", "project/app_flow.gd", "project/ui/char_select_screen.gd", "project/intro_create_phase.gd" ], "symbols": [ "ClassicParser::parse_login_success", "ClassicParser::on_gc", "ClassicSession::on_phase", "ClassicSession::select_char", "ClassicSession::create_character", "ClassicSession::delete_character", "ClassicSession::change_name", "ClassicSession::send_empire", "ClassicSession::connect_direct_enter", "M2Client::select_character", "M2Client::enter_game", "M2Client::pump_classic", "M2Client::build_char_list", "AppFlow::_on_char_list", "AppFlow::_build_char_list", "CharSelectScreen::_pad_slots", "CharSelectScreen::_do_start" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "MAPPED", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ { "reference": "CPythonNetworkStream 固定槽位数组、phase/window callback", "implementation": "ClassicParser vector、M2Client signal、Godot 选人场景", "invariant": "数组/信号的适配必须保留固定槽位、包处理顺序、成功/失败分支和清理副作用;当前固定 3/4 槽与异常分支尚无证明", "tests": [ "extension/tests/net_classic_session_test.cpp", "extension/tests/net_loopback_test.cpp", "project/char_create_delete_test.gd" ] } ], "evidence": { "contract": "audit/contracts/network.character-select.md", "tests": [ "project/char_create_delete_test.gd", "project/test_intro_select_parity.gd", "extension/tests/net_classic_session_test.cpp", "extension/tests/net_loopback_test.cpp" ], "last_test_result": "PASS" }, "remaining": [ "中文角色名/改名字段已接入 40250 CP936/GB2312-compatible byte cap;仍需补齐其它 locale codepage 的真实 fixture", "补齐创建/删除异常包、创建窗口与选人窗口双回调及连续包顺序测试", "增加 DirectEnter 重复进入、连接失败和中断清理测试,并用真实服务端证明包序", "补齐角色实例管理器的主实例、重复 VID、fade/dead/device/motion 生命周期", "补齐角色 CRUD 之外的 Login/Select/Loading/Game 阶段 allowlist 与错误阶段副作用矩阵(由 network.packet_dispatch_protocol 继续跟踪)" ], "notes": "按 40250 固定槽位、帝国写入时点、未知改名 PID、DirectEnter SelectPhase 分支、角色 CRUD 阶段门禁和中文 GB2312 fixed-name bytes 修复 classic 实现;native 角色/网络回归、角色 UI、选人和生命周期回归通过,非中文 locale、异常双窗口、重复进入、角色实例管理器和真实包序仍使合同保持 PARTIAL。", "fingerprints": { "reference": "a77f684dbef9f1c7cb4ef195ee699df504137062ff1a0e19f0a6e13c9a7c8f3b", "implementation": "51bc523ef7997cca1b8355ea25f0127c4f9b1381ef15a8e5f06a53d7b236a4e2", "tests": "d85f1b2f17296f6c2308730d34a326c3f07f8520a7513a9ccd7bd08ad3b07790" } }, { "id": "network.packet_dispatch_protocol", "title": "包头、分帧、阶段分派、未知包和发送顺序", "subsystem": "network", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/Packet.h", "UserInterface/PythonNetworkStream.cpp", "EterLib/NetPacketHeaderMap.cpp", "EterLib/NetStream.cpp" ], "symbols": [ "CMainPacketHeaderMap::CMainPacketHeaderMap", "CPythonNetworkStream::CheckPacket", "CPythonNetworkStream::RecvErrorPacket", "CPythonNetworkStream::RecvPhasePacket", "CPythonNetworkStream::RecvPingPacket", "CPythonNetworkStream::OnProcess", "CPythonNetworkStream::LoginPhase", "CPythonNetworkStream::SelectPhase", "CPythonNetworkStream::LoadingPhase", "CPythonNetworkStream::GamePhase", "CNetworkPacketHeaderMap::Get" ] }, "implementation": { "files": [ "extension/src/net/classic/classic_stream.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h", "extension/src/net/net_stream.cpp" ], "symbols": [ "ClassicStream::process", "ClassicStream::feed", "ClassicStream::dispatch", "ClassicStream::handle_control", "ClassicParser::on_gc", "packet_size_gc", "is_dynamic_gc", "is_sequence_cg" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "MAPPED", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "40250 Win32 CNetworkStream/phase tick 对应当前 Godot/ClassicStream 的 socket pump;平台替换不改变包头、分帧、allowlist 和失败清理语义。" ], "evidence": { "contract": "audit/contracts/network.packet-dispatch.md", "tests": [ "extension/tests/net_classic_stream_test.cpp", "extension/tests/net_classic_wire_test.cpp", "extension/tests/net_classic_session_test.cpp", ".agents/skills/metin2-40250-parity-audit/scripts/audit_packet_registry.py", ".agents/skills/metin2-40250-parity-audit/scripts/audit_phase_dispatch.py", ".agents/skills/metin2-40250-parity-audit/scripts/audit_sequence_table.py", ".agents/skills/metin2-40250-parity-audit/scripts/audit_source_coverage.py" ], "last_test_result": "PASS" }, "remaining": [ "已按实际非 GAIDEN 条件生成 117 项 header 数值/静态动态类别并与当前 wire 表对齐(117/117);继续补齐每项 sizeof/byte layout 证据", "明确所有协议别名(LOGIN_SUCCESS、CHARACTER_CREATE、ITEM_DEL 等)的 byte layout 和尺寸证据", "继续用真实包序列证明 QUEST_INFO/GUILD 特殊重算与 40250 声明尺寸消费的差异不会错位;动态 handler 截断失败语义已收口", "阶段 payload allowlist 已由 audit_phase_dispatch.py 按数值覆盖;继续补每个 Loading/Game handler 的副作用/拒绝清理矩阵", "验证 GamePhase 在 8192 字节以上缓冲时的继续消费、精确 tick 驱动和完整 burst 事件矩阵", "验证单包 tick 与 burst dispatch 的事件顺序,覆盖 leading zero、body zero、unknown、handler false、underflow、oversize、分片和连续包" ], "notes": "已按 40250 固定头+文本尾语义修复 GC_WHISPER;ClassicSession 已收口 Login/Select/DirectEnter/Loading/Game 的阶段门禁,且由 audit_phase_dispatch.py 证明四阶段 payload case 按数值一致;Loading 的 ITEM_SET2 通过 GamePhase fallback,HybridCrypt 由 ClassicStream 消费后返回,phase/握手/初始密钥协商后 return,KEY_AGREEMENT_COMPLETED 会解密并继续派发同一缓冲区的密文,错误阶段包在当前帧消费后清空剩余 buffer;ClassicStream 已加入 Game 阶段小 burst 的 MAX_RECV_COUNT 边界、已知 handler 失败后的 RecvErrorPacket 清缓冲/owner 保留、CheckPacket 前导零语义,并补齐旧版 85 字节主角色包。按实际非 GAIDEN 条件,主游戏注册表数值/类别已覆盖 117/117,移除 6 个未注册普通流兼容包头,guild-mark 的 SYMBOL_DATA 保留为独立适配;动态 parser 对 SCRIPT、QUEST_INFO、MESSENGER、SHOP、GUILD、DUNGEON、NPC_POSITION、LAND_LIST、CHAT 的截断字段改为失败并复用该清理路径。wire/stream/session/encstream/phase-matrix 回归通过;逐项 sizeof/byte layout、8192 字节阈值、大量 burst 和 QUEST_INFO/GUILD 特殊边界仍使合同保持 PARTIAL。", "fingerprints": { "reference": "4ac673e74894dc9530fa534bc88907d4d79d1066647ed2885167b237df324c51", "implementation": "90ae03258aea10f7f2e452d9209c82c42877eb49199c73b1d13d1de2d74d6e1e", "tests": "a64d32c7e6d21377c0abe5f98f1810387ede81799a2172fcdbbd19c329d1cb54" } }, { "id": "network.security_encoding", "title": "握手、序列号、加密、编码和定长字符串", "subsystem": "network", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseHandShake.cpp", "UserInterface/PythonNetworkStreamPhaseLogin.cpp", "UserInterface/PythonNetworkStreamPhaseSelect.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/AccountConnector.cpp", "EterLib/NetStream.cpp", "EterBase/cipher.cpp", "EterBase/cipher.h", "EterBase/tea.cpp", "EterLocale/StringCodec.cpp", "UserInterface/Locale_inc.h" ], "symbols": [ "CPythonNetworkStream::HandShakePhase", "CPythonNetworkStream::RecvHandshakePacket", "CPythonNetworkStream::RecvHandshakeOKPacket", "CPythonNetworkStream::RecvHybridCryptKeyPacket", "CPythonNetworkStream::RecvHybridCryptSDBPacket", "CPythonNetworkStream::RecvKeyAgreementPacket", "CPythonNetworkStream::RecvKeyAgreementCompletedPacket", "CAccountConnector::__AuthState_RecvHandshake", "CNetworkStream::SetSecurityMode", "CNetworkStream::SendSequence", "CNetworkStream::__SendInternalBuffer", "CNetworkStream::Clear", "CNetworkStream::ActivateCipher", "Cipher::Prepare", "Cipher::Activate", "Ymir_WideCharToMultiByte", "Ymir_MultiByteToWideChar" ] }, "implementation": { "files": [ "extension/src/net/classic/classic_stream.cpp", "extension/src/net/classic/classic_stream.h", "extension/src/net/classic/classic_cipher.cpp", "extension/src/net/classic/classic_cipher.h", "extension/src/net/classic/sequence_table.h", "extension/src/net/classic/wire_classic.h", "extension/src/net/text_codec.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/m2_client.cpp", "extension/src/net/secure_cipher.cpp", "extension/src/net/secure_cipher.h" ], "symbols": [ "mtnet::classic::ClassicStream::handle_control", "mtnet::classic::ClassicStream::server_frame_ms", "mtnet::classic::ClassicStream::emit_bytes", "mtnet::classic::ClassicStream::handle_key_agreement", "mtnet::classic::ClassicStream::disconnect", "mtnet::classic::ClassicCipher::prepare", "mtnet::classic::ClassicCipher::activate", "mtnet::classic::ClassicCipher::encrypt", "mtnet::classic::ClassicCipher::decrypt", "mtnet::classic::is_sequence_cg", "mtnet::classic::SEQUENCE_TABLE", "mtnet::to_wire", "mtnet::from_wire_str", "ClassicSession::send_client_version" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "40250 依赖 Windows EterLocale/codepage、WinSock、EterPack 和可选 anti-cheat provider;当前是 POSIX/Godot port。", "当前 classic 保留了 40250 DH2/CTR 的算法形状,但 UTF-8、steady_clock、Godot 资源链和 libsodium SecureCipher 不能视为 Windows classic 等价物。" ], "evidence": { "contract": "audit/contracts/network.security-encoding.md", "tests": [ "extension/tests/net_classic_cipher_test.cpp", "extension/tests/net_classic_encstream_test.cpp", "extension/tests/net_text_codec_test.cpp", "extension/tests/net_classic_session_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "补齐全局 ELTimer/CTimer server clock owner、auth/game 双 connector 和真实 wire fixture", "补齐 40250 server polarity 的 DH2/CTR 分片、flush 时点、非法长度、重复 agreement、completed 和 reconnect fixture", "实现或明确 legacy TEA、HybridCrypt keys/SDB、Panama pack IV 和 HS/XTrap 的兼容策略与清理", "从 PacketInfo/CMainPacketHeaderMap 生成完整 bSeq 对照矩阵(sequence table 本体已逐字节校验)", "中文 40250 固定字段已接入 CP936/GB2312-compatible conversion 并覆盖 NUL、24-byte cap、不可表示字符和 reverse decode;仍需补齐 CP949/CP1252/CP1258 等其它 locale codepage" ], "notes": "已按 40250 handshake/handshake_ok 修复后续 server clock 二次校准,并通过 sequence table 32768 项逐字节核对;DH2/CTR fixture、全局 clock owner、legacy/optional security 分支、Windows codepage 和完整 bSeq 矩阵仍使合同保持 PARTIAL。", "fingerprints": { "reference": "24cfabc7a6c89246ffeac9aa9e8c94a9e2717910c41273d87a7d17586b95fca0", "implementation": "03324b9b59bb41f2e94ac6b0dc406fcfe01b2a7c3b38c986a49aa12a5b30ec58", "tests": "34b87887c7c0bdb66d00aafe532b07c2ac8d0e967dcee0085f0e980f2a38e4d5" } }, { "id": "network.reconnect_warp", "title": "断线、换图、跨服重连和旧会话清理", "subsystem": "network", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseLoading.cpp", "UserInterface/PythonNetworkStream.cpp", "UserInterface/NetworkActorManager.cpp", "EterLib/NetStream.cpp" ], "symbols": [ "CPythonNetworkStream::RecvWarpPacket", "CPythonNetworkStream::ConnectGameServer", "CPythonNetworkStream::SetLoadingPhase", "CPythonNetworkStream::__LeaveGamePhase", "CNetworkStream::Connect", "CNetworkStream::Clear" ] }, "implementation": { "files": [ "extension/src/net/m2_client.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/entity_store.cpp", "project/game_scene.gd", "project/net_play.gd", "project/net_world.gd", "project/app_flow.gd", "project/ui/reconnect_ui.gd" ], "symbols": [ "ClassicSession::connect_direct_enter", "ClassicSession::connect_warp", "ClassicSession::pump", "M2Client::reconnect", "M2Client::warp_to_game_server", "M2Client::pump_classic", "EntityStore::reset_for_map_change", "AppFlow::_on_disconnected", "AppFlow::_on_char_list", "ReconnectUI::_retry_now" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "MAPPED", "state_transition_order": "PARTIAL", "constants_units": "MAPPED", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "Win32 CNetworkStream::Connect/Clear 对应当前 ClassicStream disconnect/connect;平台替换不应改变 GC_WARP 的 selected slot、login key、DirectEnter 和 Loading 清理顺序。" ], "evidence": { "contract": "audit/contracts/network.reconnect-warp.md", "tests": [ "extension/tests/net_classic_session_test.cpp", "project/app_flow_lifecycle_test.gd", "project/gamescene_test.gd", "project/p9_test.gd", "project/p10_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "补齐真实 GC_WARP、同服/跨服、旧 socket 与新 socket 包序矩阵", "验证 generic reconnect 保留 session 后 selected slot/direct-enter 的真实服务端包序,并覆盖无 ticket/无 slot fallback", "对齐 Loading、Game leave、EntityStore、NetPlay、NetWorld、Ground/Fly/Effect/UI 的清理 owner 和时序", "增加 connect deadline 实际超时、in-flight guard、重试上限/停止、重复 reconnect、重复 warp 和旧会话竞态测试" ], "notes": "已按 40250 同一 CNetworkStream Clear->Connect 语义修复 classic generic reconnect 的 session/selected slot/login key 保留,加入 3000ms 连接 deadline、连接中幂等 guard,并修复多 warp cue 在第一个重连 return 后被整批清空的问题;显式 disconnect 现在先 reset 活动 EntityStore、发 world_reset,再释放 transport,并有独立回归。真实阻塞连接、旧新 socket 竞态、包序和清理 owner 时序仍使合同保持 PARTIAL。", "fingerprints": { "reference": "ca61e74a7ce0c2779443be398bd2ad02aa3a74609abd068ce955377b793706ba", "implementation": "0ff81cfa25396fa829d8e38658fe307ec7b3ec9260cba4b63f598247077eac6c", "tests": "16d39cce884e002d68ef6e4f19c8471d7f96bf468f7842122577c01113820a32" } }, { "id": "network.error_timeout_server_state", "title": "登录错误、超时、频道/服务器状态和恢复", "subsystem": "network", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/AccountConnector.cpp", "UserInterface/ServerStateChecker.cpp", "UserInterface/PythonNetworkStreamPhaseLogin.cpp" ], "symbols": [] }, "implementation": { "files": [ "extension/src/net/auth_client.h", "project/net/channel_status.gd", "project/net/serverinfo.gd", "project/app_flow.gd" ], "symbols": [] }, "equivalence": {}, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/network.error-timeout-server-state.md", "tests": [ "project/app_flow_lifecycle_test.gd", "project/channel_status_test.gd", "MT_PROTOCOL=classic godot --headless --path project --script channel_status_test.gd", "extension/tests/net_loopback_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 auth failure、remote close 和 channel status 完整矩阵", "证明 count 边界、fallback probe 和恢复顺序", "增加重试上限、窗口销毁和 login/reconnect 竞态清理测试" ], "notes": "本轮按 40250 修复认证密码生命周期、两套 transport 的连接 deadline/可写完成判定,以及 ChannelStatus 旧 probe 与重入 callback 清理;频道检测仍保留当前端逐频道 TCP fallback,认证失败/远端关闭/完整恢复与边界矩阵仍未闭合,合同保持 PARTIAL。", "fingerprints": { "reference": "56e0c6a176da6228610bc6b5ce7ad6ca3cca202df2d3eaa50a57593c4c58f2c5", "implementation": "f3c70a4d708138acba6cb7c0893cc0ab6da69d8f878487e23d99c020ac0e3017", "tests": "c329ba998983d0697f38b332c5c65253603912a590f45870990db2e8f33b3e00" } }, { "id": "world.entity_spawn_state_sync", "title": "Actor 创建、追加信息、VID 重用、状态同步", "subsystem": "world", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/NetworkActorManager.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonCharacterManager.cpp", "UserInterface/InstanceBase.cpp" ], "symbols": [ "CNetworkActorManager::AppendActor", "CNetworkActorManager::UpdateActor", "CNetworkActorManager::MoveActor", "CNetworkActorManager::RemoveActor", "CNetworkActorManager::SetMainActorVID", "CNetworkActorManager::__AppendCharacterManagerActor", "CNetworkActorManager::__RemoveCharacterManagerActor", "CPythonNetworkStream::RecvCharacterAppendPacket", "CPythonNetworkStream::RecvCharacterAdditionalInfo", "CPythonNetworkStream::RecvCharacterAppendPacketNew", "CPythonNetworkStream::RecvCharacterUpdatePacket", "CPythonNetworkStream::RecvCharacterDeletePacket", "CPythonCharacterManager::DeleteInstance", "CPythonCharacterManager::DeleteInstanceByFade", "CInstanceBase::SCreateData" ] }, "implementation": { "files": [ "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_parser.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/register_types.cpp", "project/net_world.gd", "project/net_world_main_switch_test.gd", "project/game_scene.gd", "project/net_play.gd", "project/ui/remote_player_view.gd", "project/ui/mob_view.gd" ], "symbols": [ "ClassicParser::handle_character_add", "ClassicParser::handle_character_add2", "ClassicParser::handle_character_additional_info", "ClassicParser::handle_character_update", "ClassicParser::handle_character_delete", "EntityStore::apply", "EntityStore::mut_spawn", "EntityStore::mut_spawn_full", "EntityStore::mut_char_info", "EntityStore::mut_char_update", "EntityStore::mut_affect_flags", "EntityStore::mut_despawn", "NetWorld::_on_spawn", "NetWorld::_on_info", "NetWorld::_on_despawn", "NetWorld::_on_main_set", "NetWorld::_fade_and_free", "NetWorld::clear_for_map_change" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "data_completeness": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "CInstanceBase/CPythonCharacterManager 映射为 EntityStore 数据层 + Godot NetWorld 节点层", "DeleteInstanceByFade 映射为 Godot fade tween + queue_free", "40250 manager update 映射为 EntityStore tick + Godot frame 同步" ], "evidence": { "contract": "audit/contracts/world.entity-spawn-state-sync.md", "tests": [ "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp", "project/net_world_respawn_test.gd", "project/net_world_vis_test.gd", "project/net_world_main_switch_test.gd", "project/gamescene_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 Delete 后迟到 AdditionalInfo、map reset 后迟到包", "证明主 VID 切换对远端 Actor 字典、动态实例和 UI 引用的清理范围", "增加模型失败、升级中删除、重复 Delete、重复 Add 和跨帧 signal 自动测试" ], "notes": "本轮补充了同 VID 普通重建/上下马位置保留、淡出期间同 VID 立即重建、单槽 pending、逐帧可见性、主角 MainSet 通知次数,以及 classic/generic Add/Add2/AdditionalInfo/Update 的完整字段、隐身过滤、unknown VID 和事件粒度回归;net_entity_test、net_classic_session_test、net_world_respawn_test 与 net_world_vis_test 通过,Delete/map reset 后迟到包、旧 dead-list/tween 跨帧并存、主 VID 清理和 Godot 渲染副作用顺序仍未闭合,当前不能视为 40250 等价。", "fingerprints": { "reference": "aee5f08fcc01b8e184946ffc9df293f3e8e333be020eed5b8deefc3dd429d62f", "implementation": "d40006e8de66b5361dc0f24f9d9d3280c39014a4e3aae21d410513652b0a13d3", "tests": "71b61849d68202d3fa637f8023b4f47ca2320d333796a036fde7ea36e3ec3138" } }, { "id": "world.map_load_transition", "title": "地图、区域、地形、加载阶段和场景重建", "subsystem": "world", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "GameLib/MapOutdoor.cpp", "UserInterface/PythonBackground.cpp", "GameLib/MapManager.cpp", "GameLib/MapOutdoorLoad.cpp", "GameLib/MapOutdoorUpdate.cpp", "GameLib/AreaLoaderThread.cpp" ], "symbols": [ "CMapManager::LoadMap", "CMapManager::UnloadMap", "CMapManager::UpdateMap", "CMapManager::Clear", "CMapOutdoor::Load", "CMapOutdoor::Destroy", "CMapOutdoor::Clear", "CMapOutdoor::LoadSetting", "CMapOutdoor::LoadTerrain", "CMapOutdoor::LoadArea", "CMapOutdoor::Update", "CMapOutdoor::AssignTerrainPtr", "CMapOutdoor::UpdateAreaList", "TEMP_CAreaLoaderThread::Request", "TEMP_CAreaLoaderThread::Fetch", "TEMP_CAreaLoaderThread::Shutdown" ] }, "implementation": { "files": [ "extension/src/metin2_world.cpp", "extension/src/metin2_world.h", "project/game_scene.gd", "project/net_play.gd", "project/net_world.gd", "project/app_flow.gd", "extension/src/environment_builder.cpp", "project/ui/atlas_ui.gd" ], "symbols": [ "Metin2World::load_map", "Metin2World::unload_map", "Metin2World::stream_update", "Metin2World::set_focus_position", "Metin2World::_process", "Metin2World::sample_height", "GameScene::setup", "GameScene::_on_world_reset", "GameScene::_on_warp", "GameScene::_reload_map_for_warp", "GameScene::_build_world", "NetPlay::clear_for_map_change", "NetWorld::clear_for_map_change" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "GAP", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "MAPPED", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "CMapOutdoor terrain/area loader 映射为 Metin2World chunk + Godot Node3D", "DirectX loader thread 映射为主线程 stream_queue + stream_budget", "CMapOutdoor environment/sky 映射为 .msenv 解析 + Godot Sun/WorldEnvironment", "原版延迟 delete vector 映射为 chunk root queue_free,需保留可观察卸载边界" ], "evidence": { "contract": "audit/contracts/world.map-load-transition.md", "tests": [ "project/gamescene_test.gd", "project/p9_test.gd", "project/forest_map_render_test.gd", "project/environment_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "跨图 warp 的 Leave/Clear、旧 world 销毁与新 world Load/Enter 顺序", "map-name unload 校验和 load failure 的 hard/fallback/retry 语义", "缺失 environment、textureset、terrain/area/object 的失败与资源释放矩阵", "重复 warp、加载中 reset、旧异步结果回灌和 generation/cancel 边界", "streaming 边界与碰撞、height、water、对象、ambience、实体可见性的逐格测试", "forest_map_render_test 的正确 runner/报告路径纳入可执行回归" ], "notes": "本轮补充了 LoadMap/UnloadMap/UpdateMap 与当前 Metin2World 的 Leave/Enter、初始同步建队列、延迟回收、map-name 校验、双 world 和旧请求 generation 差异;gamescene/p9/environment 测试通过但不覆盖这些边界,合同保持 PARTIAL。", "fingerprints": { "reference": "ec4e3738fddb4c5c4cb57ea40acd3c52726866be1b2446f99435bf2eb6a1e0bf", "implementation": "4366e30239d6ce01bb832e572345d6f2f6c234a6f0c981e0fa71f1d15214dd69", "tests": "ad9e63a216905f67290bfa73137325d354616b6961e9b97f6c9be0b56bf54ebd" } }, { "id": "world.npc_drop_actor_spawn", "title": "NPC、怪物、建筑、掉落物和招牌实体", "subsystem": "world", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNonPlayer.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/NetworkActorManager.cpp", "UserInterface/PythonCharacterManager.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/PythonItem.cpp", "UserInterface/PythonTextTail.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonNetworkStream::RecvCharacterAppendPacket", "CPythonNetworkStream::RecvCharacterAdditionalInfo", "CPythonNetworkStream::RecvCharacterUpdatePacket", "CPythonNetworkStream::RecvCharacterDeletePacket", "CPythonNetworkStream::RecvOwnerShipPacket", "CPythonNetworkStream::RecvItemGroundAddPacket", "CPythonNetworkStream::RecvItemGroundDelPacket", "CPythonNetworkStream::RecvItemOwnership", "CPythonNetworkStream::RecvShopSignPacket", "CNetworkActorManager::AppendActor", "CNetworkActorManager::RemoveActor", "CPythonCharacterManager::__NEW_Pick", "CPythonItem::CreateItem", "CPythonItem::SetOwnership", "CPythonItem::DeleteItem", "CPythonTextTail::SetItemTextTailOwner" ] }, "implementation": { "files": [ "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "project/net_world.gd", "project/ui/mob_view.gd", "project/ui/ground_items.gd", "project/entity_rules.gd", "project/game_scene.gd", "project/metin_spawn_event_system.gd", "project/mob_drop_scatter.gd" ], "symbols": [ "EntityStore::mut_spawn_full", "EntityStore::mut_despawn", "EntityStore::mut_ground_add", "EntityStore::mut_ground_del", "EntityStore::mut_ground_owner", "EntityStore::mut_shop_sign", "M2Client::poll", "NetWorld::_on_spawn", "NetWorld::_on_info", "NetWorld::_update_visibility", "NetWorld::_fade_and_free", "NetWorld::_on_despawn", "NetWorld::_refresh_shop_sign", "GroundItems::_on_added", "GroundItems::_on_removed", "GroundItems::try_pickup", "MobView::build", "MobView::set_anim_state" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [ "CNetworkActorManager/CPythonCharacterManager 映射为 EntityStore + M2Client + NetWorld 两层数据/场景生命周期", "CItemData drop model 与 CPythonTextTail 映射为 ground_items 的 Godot 模型、Label3D 和 owner_tag", "DirectX actor blend-out 映射为 Godot Tween/queue_free,必须保持数据行与可见节点分离" ], "evidence": { "contract": "audit/contracts/world.npc-drop-actor-spawn.md", "tests": [ "project/net_world_vis_test.gd", "project/entity_rules_test.gd", "project/forest_mob_render_test.gd", "project/mob_view_test.gd", "project/net_world_push_test.gd", "project/test_ground_pickup_parity.gd", "project/mob_death_drop_test.gd", "project/test_mob_drop_scatter_parity.gd", "project/mob_winding_test.gd", "project/test_metin_spawn_event_parity.gd", "build/extension/net_entity_test", "build/extension/net_classic_session_test", "build/extension/net_classic_wire_test" ], "last_test_result": "PARTIAL" }, "remaining": [ "修复并回归 net_world_vis_test 的 4 项可见性/裁剪失败,确认节点只建一次、离开范围只删一次", "补齐不可见 race、未知 race/vnum、模型工厂未就绪、真实 drop model/effect/sound 和建筑碰撞的失败矩阵", "验证 ground add/owner/del 与 CG_ITEM_PICKUP→GC_DEL/ITEM_GET/ITEM_UPDATE 的 live 包序、失败、乱序和重连清理", "验证 GC_SHOP_SIGN 空/非空、未知 VID、重复包、私人商店 UI 和换图/断线清理", "把本地掉落散落、活动石头和 owner 保护与 40250 client presentation 分层,不把 custom authority 当 parity" ], "notes": "已完成 40250 actor/ground/shop-sign 调用链和当前端 parser/store/view 对照;合同保持 PARTIAL。net_world_vis_test 复现 4 项可见性生命周期失败,本轮仅登记审计结果,未修改实现。", "fingerprints": { "reference": "a5dad293d8fadb4a794de113f80df9ca52703259aa6341e781490836e121bed3", "implementation": "717ef4134897b3c87fdeb5132085db5bc72394d7faee763b3c393c3147d4f133", "tests": "ff01079b1b3eaebc9d9adac57c57ba18acc7adca12a8aaf9e99417237901a470" } }, { "id": "world.reset_cleanup", "title": "换图、死亡、退出和 UI/实体/特效清理", "subsystem": "world", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStream.cpp", "UserInterface/PythonNetworkStreamPhaseLoading.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonApplicationProcedure.cpp", "UserInterface/PythonEventManager.cpp", "GameLib/MapManager.cpp", "UserInterface/NetworkActorManager.cpp", "UserInterface/FlyingObjectManager.cpp", "EterLib/EffectManager.cpp" ], "symbols": [ "CPythonNetworkStream::SetLoadingPhase", "CPythonNetworkStream::RecvWarpPacket", "CPythonNetworkStream::__DirectEnterMode_Initialize", "CPythonPlayer::Clear", "CFlyingManager::DeleteAllInstances", "CEffectManager::DeleteAllInstances", "CNetworkActorManager::Destroy", "CNetworkActorManager::__RemoveAllActors", "CNetworkActorManager::__RemoveAllGroundItems", "CMapManager::UnloadMap", "CPythonApplication::Destroy" ] }, "implementation": { "files": [ "project/app_flow.gd", "project/game_scene.gd", "project/audio.gd", "project/net_play.gd", "extension/src/net/wire.h", "extension/src/net/entity_store.cpp", "extension/src/net/m2_client.cpp", "project/net_world.gd", "project/fly_object.gd", "project/ground_items.gd", "project/bgm_director.gd" ], "symbols": [ "M2Client::pump_classic", "M2Client::disconnect_from_server", "EntityStore::reset_for_map_change", "GameScene::_on_world_reset", "GameScene::_reload_map_for_warp", "GameScene::_exit_tree", "AppFlow::_exit_tree", "Audio::shutdown", "NetPlay::clear_for_map_change", "NetWorld::clear_for_map_change", "FlyObject::clear_for_map_change", "Metin2World::unload_map", "uninitialize_mtgodot_module" ] }, "equivalence": { "preconditions": "MAPPED", "branch_structure": "PARTIAL", "algorithms_formulas": "MAPPED", "state_transition_order": "PARTIAL", "constants_units": "MAPPED", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "CPythonPlayer/Actor/Flying/Effect manager 映射为 EntityStore + NetPlay + NetWorld + FlyObject + Godot effect registry", "同步删除映射为信号同步清理与 queue_free 延迟释放", "CPythonApplication Destroy 映射为 AppFlow/GameScene/RAII 生命周期" ], "evidence": { "contract": "audit/contracts/world.reset-cleanup.md", "tests": [ "extension/tests/net_entity_test.cpp", "project/fly_reset_test.gd", "project/screen_hp_bar_test.gd", "project/net_world_reset_test.gd", "project/app_flow_lifecycle_test.gd", "project/gamescene_test.gd", "project/rendering_scenario_test.gd", "project/disconnect_world_reset_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "GC_WARP/重连/close/dead 的完整顺序矩阵", "旧地图先清理还是新地图先构造的资源与引用安全证明", "统一 generation 防止 queue_free/模型升级/旧网络回调回灌", "真实断线重连保留 GameScene 时的重复 world_reset/旧数据清理包序", "通用特效 manager、窗口关闭和显式 Destroy 等价测试", "清理 test_exp_fly_parity 旧式单函数 fixture 的 3 个 ObjectDB 泄漏与 SceneTree path warning" ], "notes": "本轮按 SetLoadingPhase、CMapManager::UnloadMap、CSoundManager::Destroy 和 CPythonApplication::Destroy 的清理边界,新增 net_world_reset_test,修复 _upgrade_queue、_chat_tails、target/hover VID、target effect 残留,并将 GameScene 换图改为旧 world 先 unload 再创建新 world;AppFlow、GameScene、Audio 与 GDExtension 退出路径清空模型/动作/NPC/UI/音频缓存,rendering_scenario 的 139/54 泄漏和 Debug 下 2 个 AudioStream 泄漏已修复。FlyInstance handler 自捕获环也已解除,fly_reset_test 的 5 个 ObjectDB 泄漏已清零;显式 disconnect 现在先 reset 活动 EntityStore、发 world_reset 再释放 transport,test_exp_fly_parity 仍有 3 个旧 fixture 警告。真实重连包序、统一 generation 和通用 EffectManager 仍使合同保持 PARTIAL。", "fingerprints": { "reference": "52c08c1d2ec0401a4243d173eb8eb62cf4bef2c5f0d3c83690ec8927c31e06da", "implementation": "a974581290c07c275aa969985266d96862e538943d508da75f4fb86a45f0749a", "tests": "84e92fe6b6cd4859cf6a215294b858f15b3dd1259dfb62b99dfdde2b39ec8955" } }, { "id": "movement.click_collision", "title": "点地、路径目标、地形/Actor 碰撞和阻挡恢复", "subsystem": "movement", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/PythonPlayerInputKeyboard.cpp", "UserInterface/InstanceBaseMovement.cpp", "GameLib/ActorInstanceCollisionDetection.cpp", "GameLib/MapOutdoor.cpp", "GameLib/GameUtil.cpp" ], "symbols": [ "CPythonPlayer::NEW_SetMouseState", "CPythonPlayer::NEW_SetMouseSmartState", "CPythonPlayer::__OnPressSmart", "CPythonPlayer::__OnClickSmart", "CPythonPlayer::NEW_RefreshMouseWalkingDirection", "CPythonPlayer::__OnPressGround", "CPythonPlayer::__OnClickGround", "CInstanceBase::NEW_CanMoveToDestPixelPosition", "CInstanceBase::NEW_MoveToDestPixelPositionDirection", "CInstanceBase::NEW_MoveToDirection", "CInstanceBase::NEW_Stop", "CActorInstance::TestActorCollision", "CActorInstance::AvoidObject", "CActorInstance::IsBlockObject", "CActorInstance::BlockMovement", "CMapOutdoor::GetPickingPointWithRay", "CMapOutdoor::GetHeight", "CMapOutdoor::isAttrOn" ] }, "implementation": { "files": [ "project/player_controller.gd", "project/ui/mouse_controller.gd", "project/hit_collision.gd", "extension/src/metin2_world.cpp", "project/game_scene.gd", "project/net_play.gd", "project/ui/ground_items.gd" ], "symbols": [ "PlayerController::_unhandled_input", "PlayerController::_on_click", "PlayerController::_goto", "PlayerController::_ray_ground", "PlayerController::_get_blocking_actor", "PlayerController::_blocked", "PlayerController::_process", "NetPlay::pick_ground_item", "NetPlay::_process_reserved_action", "GameScene::_on_ground_clicked", "Metin2World::sample_height", "Metin2World::is_blocked" ] }, "equivalence": { "preconditions": "MAPPED", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "MAPPED", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [ "DirectX camera ray/object picking 映射为 Godot project_ray + mesh AABB/ground ray", "CActorInstance sphere collision 映射为 PlayerController 半径/切线/分轴近似 + HitCollision effect geometry", "CPythonPlayer reservation loop 映射为 GDScript _process 预约队列" ], "evidence": { "contract": "audit/contracts/movement.click-collision.md", "tests": [ "project/player_move_test.gd", "project/movement_parity_test.gd", "project/netplay_test.gd", "project/test_click_target_effect_parity.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "实现/证明 MBS_PRESS 与 MBS_CLICK 的 Smart/Auto/Camera/Attack/Skill 完整状态矩阵", "运行时 movable distance 与 item/actor reservation 的完整包序列", "MSM 骨骼碰撞球、坐骑/攻击状态选择、第二个碰撞停止和移动段采样", "对象 Avoid/BlockMovement 与当前 direct/切线/xz 分轴推进的终止语义", "桥/台阶 object picking、terrain height 和 ATTRIBUTE_BLOCK 的射线边界", "实体删除、失焦、死亡、换图中断、连续点击和退出资源泄漏测试" ], "notes": "本轮补充核对了 Smart press/hold/release、click miss 的停止语义、运行时距离门、真实 MSM 碰撞球、第二碰撞 BlockMovement 和对象 picking 的差异;四组移动/点选回归均通过,但 click/target/fly 测试有 3 个 ObjectDB 泄漏和 1 个资源退出警告。当前仍不能用固定半径替代真实 MSM sphere,合同保持 PARTIAL。", "fingerprints": { "reference": "ea99a64535a55097bf4a6d53a1ec7cf5ca027646044211d470814b9a4c9b6942", "implementation": "2e0a5e8f37317910e00a8a92fcaa575dbe1300e3fe89859ab07e4bc23d404edf", "tests": "211cd06c8af452f3c4057ced8676787753556a215af01d953d28e635bd290c63" } }, { "id": "movement.remote_sync_state", "title": "远端移动命令队列、服务器时钟和状态释放", "subsystem": "movement", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/InstanceBase.cpp", "UserInterface/NetworkActorManager.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonNetworkStreamPhaseHandShake.cpp", "GameLib/ActorInstance.cpp", "GameLib/ActorInstanceSync.cpp" ], "symbols": [ "PushTCPState", "StateProcess", "MovementProcess", "TEMP_Push", "RecvCharacterMovePacket", "RecvSyncPositionPacket" ] }, "implementation": { "files": [ "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "project/net_play.gd", "project/net_world.gd", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/tests/net_state_queue_test.cpp", "extension/tests/net_entity_test.cpp", "extension/tests/net_bounds_test.cpp", "project/net_world_remote_action_test.gd" ], "symbols": [ "EntityStore::mut_move", "EntityStore::drain_state_queue", "EntityStore::apply_state_cmd", "EntityStore::tick", "EntityStore::mut_snap_position", "Metin2World::_update_visibility", "M2Client::set_entity_knockdown", "NetWorld::_apply_sync_push", "NetWorld::_sync_push_collision", "NetWorld::_sync_pushed_position" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/movement.remote-sync-state.md", "tests": [ "extension/tests/net_state_queue_test.cpp", "extension/tests/net_entity_test.cpp", "project/netplay_test.gd", "project/movement_parity_test.gd" ], "last_test_result": "PASS" }, "remaining": [ "FUNC_EMOTION 双人目标 VID 从 GC_MOTION/GC_MOVE 的完整资源路径", "技能可取消/grade 的 40250 动作数据权威来源和真实尾事件", "真实地图 IPhysicsWorld/模型资源下 TEMP_Push 与客户端碰撞结果的逐点验证", "远端怪物 combo/skill arg 到具体动作资源的全量映射", "真实 40250 服务器握手时基、乱序/跨图/死亡重建端到端验证" ], "notes": "主队列和时钟链已逐分支审计;远端 FUNC_MOVE 已按 40250 统一 PC/NPC/monster 的穿越 Dst 分支,TEMP_Push 已补 correction serial、100 点碰撞探测、1 秒表现缓动和大位移击倒门。因技能数据权威来源、双人情绪目标、真实资源/地图碰撞和服务器时序差异保持 PARTIAL。", "fingerprints": { "reference": "b2bbc9ce74027fcfb6af30c0d6437b34ef7261cb841e13cef6d883a9275d0453", "implementation": "e57ada209ed9eca1cf991501a69f696eebd154bf1d2b87a99db55c6a42f4683e", "tests": "cf93c0617087617b1d5c506112e5497369ec9ee5f9be3bd14625084b543797d1" } }, { "id": "movement.motion_resource_events", "title": "`.msa`/`.msm` 动作、连击窗口、动作事件和速度", "subsystem": "movement", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/InstanceBaseMotion.cpp", "GameLib/ActorInstanceMotion.cpp", "GameLib/ActorInstanceMotionEvent.cpp", "GameLib/RaceMotionData.cpp", "GameLib/RaceData.cpp", "GameLib/RaceData.h", "GameLib/RaceManager.cpp", "UserInterface/InstanceBaseBattle.cpp" ], "symbols": [ "CRaceData::GetMotionKey", "CRaceData::NEW_RegisterMotion", "CRaceData::GetComboDataPointer", "CRaceMotionData::LoadMotionData", "CRaceMotionData::GetMotionDuration", "CRaceMotionData::GetLoopStartTime", "CRaceMotionData::GetLoopEndTime", "CActorInstance::SetLoopMotion", "CActorInstance::InterceptMotion", "CActorInstance::PushOnceMotion", "CActorInstance::PushLoopMotion", "CActorInstance::ReservingMotionProcess", "CActorInstance::CurrentMotionProcess", "CActorInstance::__MotionEventProcess", "CActorInstance::MotionEventProcess", "CActorInstance::ProcessMotionEventEffectEvent", "CActorInstance::ProcessMotionEventEffectToTargetEvent", "CActorInstance::ProcessMotionEventSpecialAttacking", "CActorInstance::ProcessMotionEventSound", "CActorInstance::ProcessMotionEventFly" ] }, "implementation": { "files": [ "project/ui/player_view.gd", "project/ui/motion_registry.gd", "project/ui/hit_reaction.gd", "project/ui/remote_player_view.gd", "project/ui/mob_view.gd", "project/ui/msa_motion.gd", "formats/msa.cpp", "formats/msa.h", "formats/msm.cpp", "formats/msm.h", "extension/src/metin2_anim.cpp", "extension/src/metin2_anim.h", "project/net_play.gd", "project/game_scene.gd", "project/fx/motion_effect_anchor.gd", "project/hit_collision.gd" ], "symbols": [ "Metin2AnimPlayer::reload", "Metin2AnimPlayer::dispatch_events", "Metin2AnimPlayer::get_motion_data", "Metin2AnimPlayer::get_loop_data", "Metin2AnimPlayer::get_effect_bone_pose", "PlayerView::set_motion_mode", "PlayerView::play_attack_motion", "PlayerView::play_skill_motion", "PlayerView::set_anim_state", "PlayerView::get_move_motion_speeds", "MobView::resolve_motion", "MobView::_load_motlist", "NetPlay::_combo_process", "NetPlay::_begin_hit_windows", "NetPlay::_attack_process", "GameScene::_on_local_motion_event_detailed", "GameScene::_apply_motion_warp" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [ "Granny/CGraphicThingInstance/CActorInstance motion queue 映射为 Metin2AnimPlayer + PlayerView/MobView/HitReaction", "RaceData motion key 与 motlist/MSA 映射为 PC 硬编码 mode directories、NPC motlist 和 combo table parser", "DirectX motion event/effect/sound dispatch 映射为 Godot signal + GameScene 本地事件分派" ], "evidence": { "contract": "audit/contracts/movement.motion-resource-events.md", "tests": [ "project/player_motion_test.gd", "project/keyboard_motion_timeline_test.gd", "project/race_motion_assembly_test.gd", "project/animation_blend_test.gd", "project/animation_cache_test.gd", "project/test_combo_continuous_flow.gd", "project/combat_fx_test.gd", "project/eterngrn_polish_test.gd", "project/target_effect_test.gd", "project/hit_collision_test.gd", "project/motion_effect_anchor_test.gd", "project/mob_view_test.gd", "project/motion_event_payload_test.gd", "project/motion_registry_test.gd", "project/hit_view_test.gd" ], "last_test_result": "PASS: PC playersettingmodule motion registry (registration order/weights), HORSE parent fallback, knockdown/stand-up early-return, MSA MotionDuration authority, LoopData skill timing, motion event payloads, and hot clip/MSA cache reuse regressions passed; NPC/full RaceData/event-consumer parity remains PARTIAL" }, "remaining": [ "扩展已接入的 PC playersettingmodule registry 快照,对比 NPC motlist、horse/wedding/skill 的每个 motion key 和缺失 key 失败语义", "让 MSA MotionDuration、LoopData count/start/end、60 FPS event frame 和 GR2 playback duration 形成固定样本契约", "补齐非主角 Mob/remote PC 的 MotionEvent effect/target/fly/sound 消费与每 actor 清理;WARP 的目标前 270cm/阻挡/朝向分支已接入", "验证同动作重入、动作队列、受击/死亡/晕倒/站起/技能取消、跨图和断线时序", "处理事件测试 fixture 的 ObjectDB/RID 泄漏,并继续验证大 delta、LoopData 事件边界和动作队列" ], "notes": "已完成 40250 RaceData/RaceMotionData/ActorInstanceMotion/ActorInstanceMotionEvent 与当前 MSA/MSM/AnimPlayer/视图/NetPlay 对照;本轮接入 playersettingmodule 驱动的 PC motion registry、复刻注册权重和 HORSE 父模式查找,并修正击倒/起身受击早退;MSA MotionDuration、LoopData 与热切换缓存回归仍通过,合同保持 PARTIAL。", "fingerprints": { "reference": "8ca2f11b20ce32df4fbecae8bed930d97dc14a31194542d07f47de302bfa6139", "implementation": "c3d953416803727f31c26edee73bc8d3c0bc6783daa9942180977373320809c8", "tests": "418a7fc6496f570cd97183fafb4a1964f90a704f55410df9c6bd42a35f6a517f" } }, { "id": "combat.attack_combo_damage", "title": "普攻、连击、命中窗口、伤害和受击", "subsystem": "combat", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayer.cpp", "UserInterface/InstanceBaseBattle.cpp", "GameLib/ActorInstanceBattle.cpp", "GameLib/ActorInstanceWeaponTrace.cpp" ], "symbols": [] }, "implementation": { "files": [ "project/net_play.gd", "project/player_controller.gd", "project/hit_collision.gd", "project/damage_effect.gd", "project/ui/player_view.gd", "project/ui/equip_model.gd", "project/ui/weapon_trace.gd", "project/socket_glow_fx_system.gd" ], "symbols": [] }, "equivalence": {}, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/combat.attack-combo-damage.md", "tests": [ "project/combat_fx_test.gd", "project/combat_parity_test.gd", "project/gamescene_test.gd", "project/weapon_trace_test.gd", "project/weapon_trace_multi_part_parity_test.gd", "project/weapon_trace_endpoint_parity_test.gd", "project/weapon_trace_reach_affect_parity_test.gd", "project/weapon_reach_scale_attack_parity_test.gd", "project/damage_local_main_parity_test.gd", "extension/tests/net_state_queue_test.cpp" ], "last_test_result": "PARTIAL; local damage, multi-part WeaponTrace, bound-box/local-Z/reach-scale and attack-sweep regressions pass" }, "remaining": [ "鼠标 press/release、空格自由攻击、弓 FLY 帧和自动攻击完整状态矩阵", "服务端伤害/死亡/重复命中与客户端命中窗的真实包级 fixture", "换图、断线、缺失动作资源和攻击队列清理测试", "WeaponTrace 已按 bound-box 最大长度、局部 Z 和 reach scale 接入 PlayerView;当前仍未证明 native bone matrix 的同一 composite/rotation 坐标语义、远端/怪物覆盖、真实刀光纹理视觉和服务端动作资源", "socket_glow_fx_system.has_trail 目前只有配置字段,没有进入武器刀光渲染链" ], "notes": "已按 40250 isValidAttacking/HitDataContainer 删除缺失动作攻击数据、空命中窗和缺少 .msm 防御球时的本地合成命中;状态包、命中窗、去重、受击、弓箭 FLY、本地主角伤害飘字和 WeaponTrace 多部件生命周期有回归证据。WeaponTrace 目前通过 weapon_trace.gd 适配层接入,包含武器类型过滤、每个 Weapon/Shield 部件独立 trace、双轨迹时间队列、cubic spline triangle strip、alpha/texture 材质、攻击动作开关、纹理控制入口、bound-box 最大长度/局部 Z/reach scale 和换装清理;AFFECT_GEOMGYEONG 的 1.5/1.0 reach scale 已同时接入刀光与普通攻击扫掠。native 骨骼矩阵坐标语义、远端/怪物覆盖、真实视觉资源及服务端伤害闭环仍不等价。强化配置中的 has_trail 仍是独立未消费字段。", "fingerprints": { "reference": "6b08585e316d89de1e827937e6bf7f4c8b3dc5f6c982f076573ca68c5798a740", "implementation": "0e2acfc1b3813d5e7f04d7e87678637b0f5d3238fdefe5acd459a7946ed43090", "tests": "f76a2ba96b83375afd9c355fd944dca182b3e969367e448f25da08ad0522df1e" } }, { "id": "combat.target_selection_rules", "title": "目标选择、可攻击判定、目标框和安全区", "subsystem": "combat", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/PythonPlayerInput.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseBattle.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonPlayer::SetTarget", "CPythonPlayer::__ChangeTargetToPickedInstance", "CPythonPlayer::__OnClickActor", "CPythonPlayer::__OnPressActor", "CPythonPlayer::__Update_AutoAttack", "CPythonPlayer::NotifyCharacterDead", "CPythonPlayer::NotifyCharacterUpdate", "CInstanceBase::IsAttackableInstance", "CInstanceBase::IsTargetableInstance", "CInstanceBase::CanPickInstance", "CInstanceBase::CanViewTargetHP", "CInstanceBase::IsInSafe", "CPythonNetworkStream::RecvTargetPacket" ] }, "implementation": { "files": [ "project/entity_rules.gd", "project/target_board.gd", "project/ui/target_ui.gd", "project/ui/mouse_controller.gd", "project/player_controller.gd", "project/net_play.gd", "project/net_world.gd", "project/alignment_pk_system.gd" ], "symbols": [ "EntityRules.is_attackable_instance", "EntityRules.can_pick_instance", "NetPlay._is_attackable", "NetPlay._attack_ctx", "NetPlay._on_pick", "NetPlay._change_target_to_picked_instance", "NetPlay._motion_blocks_target_change", "NetPlay._can_change_target", "NetPlay._is_targetable_instance", "NetPlay._reserve_process_click_actor", "NetPlay._on_target_info", "TargetBoard.classify", "TargetBoard.can_view_target_hp", "PlayerController._on_click", "NetWorld._apply_field_updates", "NetWorld.set_target_vid", "NetWorld._on_despawn" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/combat.target-selection-rules.md", "tests": [ "project/entity_rules_test.gd", "project/target_board_test.gd", "project/test_alignment_pk_parity.gd", "project/test_click_target_effect_parity.gd", "project/test_auto_combat_bot_parity.gd", "project/playable_combat_test.gd", "project/netplay_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "完整 .msa 事件源下的 CanChangeTarget 动作时序和缺失动作资源回退验证", "隐身、复活隐身、视锥、门死亡、裁剪/淡出和迟到实体更新的目标生命周期矩阵", "地图 ATTRIBUTE_BANPK 与实体 in_safe 的实时数据权威和移动/换图更新链", "SetTarget 切换时间窗、OnTargeted/OnUntargeted、飞行目标和 CG_TARGET 顺序", "真实服务端目标包、死亡/删除/乱序/断线重连和目标框清理" ], "notes": "已完成目标攻击资格、目标可选中、目标框和安全区的逐分支静态审计;目标入口已统一接入 CanChangeTarget/CanPickInstance、重复目标窗口和 CG_TARGET(0) 清理,合同保持 PARTIAL。", "fingerprints": { "reference": "79cc9ece9c1b7fc69d44e304e1e1c49ef242f727fa027058e1798b52918d4521", "implementation": "0cd8742f2171a9dc9f4b05add49297fe9574142b5382af983d6d502a0ca3abc2", "tests": "07dfa7500946c1dd57346ff4a0d7489b4dd514582d304df4a9af571309dc5953" } }, { "id": "combat.flying_projectile", "title": "飞行物、目标、地形碰撞、命中和回收", "subsystem": "combat", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonFlyModule.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonPlayerEventHandler.cpp", "GameLib/ActorInstanceFly.cpp", "GameLib/FlyingInstance.cpp", "GameLib/FlyingData.cpp", "GameLib/FlyTarget.cpp", "GameLib/FlyingObjectManager.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonPlayerEventHandler::OnSetFlyTarget", "CPythonPlayerEventHandler::OnShoot", "CPythonNetworkStream::RecvAddFlyTargetingPacket", "CPythonNetworkStream::RecvFlyTargetingPacket", "CPythonNetworkStream::RecvCreateFlyPacket", "CPythonNetworkStream::SendShootPacket", "CPythonNetworkStream::SendAddFlyTargetingPacket", "CPythonNetworkStream::SendFlyTargetingPacket", "CFlyingData::LoadScriptFile", "CFlyingInstance::Create", "CFlyingInstance::Update", "CFlyingManager::CreateIndexedFly", "CFlyTarget::NotifyTargetClear" ] }, "implementation": { "files": [ "project/fly_object.gd", "project/world_shot.gd", "project/fx/skill_fx.gd", "project/net_play.gd", "project/net_world.gd", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/entity_store.cpp", "project/fly_test.gd", "project/fly_reset_test.gd", "project/test_exp_fly_parity.gd", "project/fly_target_bounds_test.gd" ], "symbols": [ "FlyObject.FlyData", "FlyObject.FlyData.load_msf", "FlyObject.FlyInstance.create", "FlyObject.FlyInstance.update", "FlyObject.FlyManager.spawn", "FlyObject.FlyManager.clear_for_map_change", "NetWorld._on_fly", "NetWorld._indexed_fly_data", "NetWorld._on_fly_targeting", "NetPlay._emit_swing", "NetPlay._send_fly_target", "NetPlay.send_fly_targeting" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/combat.flying-projectile.md", "tests": [ "project/fly_test.gd", "project/fly_reset_test.gd", "project/test_exp_fly_parity.gd", "project/fly_target_bounds_test.gd", "project/test_click_target_effect_parity.gd", "project/combat_fx_test.gd", "project/fly_data_msf_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "动作事件 FLY 的 .msf 文件到 owner/skill/骨骼起点的完整创建链;索引飞行的 .msf 参数已接入 GC_CREATE_FLY", "FIRE_CRACKER/AUTO_FIRE 的 CreateIndexedFly 分支、spreading/cone/roll/angular velocity 和 attach/tail 表现", "hit-on-another-monster 动态 Actor 碰撞、穿透集合和另一目标回调", "目标包围球中心、目标删除/VID 重用和不可见 shooter 的生命周期矩阵", "GC_FLY_TARGETING/ADD/CREATE 的乱序、真实包序和 sequence 副作用", "bombeffect/attach/tail/爆点资源与回收时序", "真实服务端弓箭 FLY/SHOOT/CREATE_FLY 闭环" ], "notes": "已完成飞行物数据、目标、弹道、背景碰撞、命中/穿透、网络目标追加和地图清理的逐分支静态审计;本轮按 PythonEffectModule/playersettingmodule 接入 .msf 解析、索引资源映射并修正 FLY 枚举,同时解除 FlyInstance handler 自捕获环,fly_reset_test 退出已无 ObjectDB 泄漏。动态 Actor 碰撞、动作事件 FLY 起点/owner/skill、test_exp_fly_parity 旧 fixture 清理和完整特效生命周期仍使合同保持 PARTIAL。", "fingerprints": { "reference": "4c3f953999e53f7c8c8454d8aaaab2630c19438b2236e3053c0cf5f9c8aa3c55", "implementation": "6f961e0a3e2b4d9f41925e299a000a809ad3d62823b373c2984e9e53aa8000d2", "tests": "504420e0e84251b95f9676e72c07ea5cc875fb6c4ad139f79c0bebabf1189f1b" } }, { "id": "combat.affect_status", "title": "受击、眩晕、击倒、状态旗标和状态图标", "subsystem": "combat", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/AffectFlagContainer.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseEffect.cpp", "UserInterface/InstanceBaseBattle.cpp", "UserInterface/NetworkActorManager.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/Packet.h", "GameLib/ActorInstanceBattle.cpp" ], "symbols": [ "CAffectFlagContainer::CopyData", "CAffectFlagContainer::Set", "CAffectFlagContainer::IsSet", "CInstanceBase::SetAffectFlagContainer", "CInstanceBase::__SetNormalAffectFlagContainer", "CInstanceBase::__SetStoneSmokeFlagContainer", "CInstanceBase::__ClearAffects", "CInstanceBase::SetStateFlags", "CInstanceBase::__CanProcessNetworkStatePacket", "CPythonNetworkStream::RecvDamageInfoPacket", "CPythonNetworkStream::RecvAffectAddPacket", "CPythonNetworkStream::RecvAffectRemovePacket", "CActorInstance::__HitGood", "CActorInstance::__HitGreate", "CActorInstance::__HitStone" ] }, "implementation": { "files": [ "project/combat_status_effect_system.gd", "project/ui/hit_reaction.gd", "project/ui/player_view.gd", "project/ui/mob_view.gd", "project/net_play.gd", "project/net_world.gd", "project/game_scene.gd", "project/hud.gd", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/m2_client.cpp", "project/ui/char_status_ui.gd" ], "symbols": [ "EntityStore::mut_affect_flags", "EntityStore::mut_stun", "EntityStore::mut_affect_add", "EntityStore::mut_affect_remove", "NetWorld::_apply_field_updates", "NetWorld::_on_damage", "NetPlay::_can_process_network_state", "NetPlay::_process_attack_success", "HitReaction.good", "HitReaction.greate", "PlayerView.set_affect_flags", "MobView.set_affect_flags" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/combat.affect-status.md", "tests": [ "project/test_combat_status_effect_parity.gd", "project/damage_effect_test.gd", "project/combat_fx_test.gd", "project/test_mob_combat_reaction_parity.gd", "project/test_combat_death_pickup_entry_parity.gd", "project/hit_view_test.gd", "build/extension/net_entity_test", "build/extension/net_classic_session_test" ], "last_test_result": "PARTIAL" }, "remaining": [ "隔离 combat_status_effect_system 的本地权威结算,改为服务端 affect/damage 包驱动", "补齐 affect bit 到附着特效、图标、骨骼、stone smoke 和建筑 no-op 的逐位资源矩阵", "统一远端 stunned/knockdown/skill-cancel 的动作和网络状态门控", "修复 hit_view_test 前向 damage 资源/动作锁时序失败", "补齐重复、乱序、duration、死亡、换图、重连和 VID 重用清理测试" ], "notes": "已完成 40250 AffectFlagContainer、Actor affect/受击动作、damage/affect 协议和当前 native/GDScript 链的逐分支审计;确认本地状态结算器的数据权威错误,且 hit_view_test 有 1 项失败。合同保持 PARTIAL,本轮仅登记审计结果,未修改实现。", "fingerprints": { "reference": "2ad471af8361cb0a640a4fc702c6223734e68948c905eef4f8841c8113e79c7b", "implementation": "26dcade5219459cf5386691f1706c8db04e15d6f03e48a30d869bd223cefb239", "tests": "aed0edc923b5a81de18a2d4545dadccdfd1463e5dd785e80f945d56e0a34b749" } }, { "id": "skill.player_ui_cooldown", "title": "技能栏、快捷键、等级、冷却和禁止条件", "subsystem": "skill", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonSkill.cpp", "UserInterface/PythonPlayerSkill.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonSkill::RegisterSkillTable", "CPythonSkill::SSkillData::GetSkillCoolTime", "CPythonSkill::SSkillData::GetTargetCount", "CPythonSkill::SSkillData::IsCanUseSkill", "CPythonPlayer::ClickSkillSlot", "CPythonPlayer::__CanUseSkill", "CPythonPlayer::__CheckSkillUsable", "CPythonPlayer::__UseSkill", "CPythonPlayer::__RunCoolTime", "CPythonPlayer::SetSkillLevel_", "CPythonPlayer::SetSkillCoolTime", "CPythonPlayer::EndSkillCoolTime", "CPythonNetworkStream::RecvSkillLevelNew", "CPythonNetworkStream::RecvSkillCoolTimeEnd", "CInstanceBase::CanUseSkill" ] }, "implementation": { "files": [ "project/player_skill.gd", "project/ui/skill_table.gd", "project/ui/skill_ui.gd", "project/ui/quickbar.gd", "project/net_play.gd", "project/net_client_fake.gd", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/m2_client.cpp", "extension/src/net/wire.h" ], "symbols": [ "SkillTable.load_file", "SkillTable.cooldown_of", "SkillTable.can_level_up_skill", "PlayerSkill.click_skill_slot", "PlayerSkill.can_use_skill", "PlayerSkill.check_skill_usable", "PlayerSkill.use_skill", "PlayerSkill.use_guild_skill", "Quickbar.restore_from_server", "Quickbar._activate_slot", "Quickbar._activate_guild_skill", "Quickbar._skill_cooldown", "Quickbar._on_cd_end", "NetPlay.skill_context", "EntityStore::mut_skill_levels", "EntityStore::mut_skill_group", "EntityStore::mut_quickslot_set", "EntityStore::mut_skill_cooldown_end" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/skill.player-ui-cooldown.md", "tests": [ "project/player_skill_test.gd", "project/skill_test.gd", "project/netplay_test.gd", "project/test_skill_inventory_parity.gd", "project/test_taskbar_quickslot_parity.gd", "project/test_status_skill_reset_parity.gd", "project/mobile_gesture_test.gd", "project/playable_combat_test.gd", "project/guild_war_skill_test.gd", "project/test_full_skill_flow.gd", "build/extension/net_entity_test", "build/extension/net_classic_session_test" ], "last_test_result": "PARTIAL" }, "remaining": [ "CanUseSkill 五类动作门已补齐;仍需把真实 shape/图形动作字段和拒绝副作用接入端到端回归", "POINT_CASTING_SPEED 已暴露并按 40250 __RunCoolTime 修正;仍需真实点变更/冷却回包时序 fixture", "补齐 skill/group/quickslot/cooldown-end 乱序、重复、断线、重连和换图测试", "建立全部技能 ID/grade/.msk 公式/动作/目标追加和服务端 damage 顺序快照" ], "notes": "已完成 40250 技能数据、三层施放校验、CanUseSkill、casting-speed 冷却公式、快捷栏、普通/公会技能发送入口、skill/group/cooldown 协议和 FUNC_SKILL→CG_USE_SKILL 动作/意图包序的逐分支审计;CanUseSkill 五类动作门、POINT_CASTING_SPEED 桥接和公会技独立路径已实现,但真实拒绝/乱序/跨生命周期 fixture、完整动作资源包序和服务端 damage 顺序仍使合同保持 PARTIAL。", "fingerprints": { "reference": "3803e0c7a0b8492f95084c9fe2b4828c04e831b29f0ca7b538891e3beb6f03fd", "implementation": "bc0d501069c7be30f144a8a0be8fa01570cfc4f7c40bc032b5056f13f5f64bd6", "tests": "dd17c0582f03373c66903290123f327f61662311c0dd44e912b53a59e10bb0ab" } }, { "id": "item.inventory_equipment_state", "title": "背包、装备栏、物品状态、槽位和穿脱装备", "subsystem": "item", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "UserInterface/GameType.h", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonItem.cpp", "GameLib/ItemData.cpp", "GameLib/ItemManager.cpp", "UserInterface/Packet.h" ], "symbols": [ "TItemPos::IsValidCell", "TItemPos::IsEquipCell", "CPythonPlayer::SetItemData", "CPythonPlayer::SetItemCount", "CPythonPlayer::SetItemMetinSocket", "CPythonPlayer::SetItemAttribute", "CPythonPlayer::IsEquipItemInSlot", "CPythonNetworkStream::RecvItemSetPacket", "CPythonNetworkStream::RecvItemSetPacket2", "CPythonNetworkStream::RecvItemUpdatePacket", "CPythonNetworkStream::__RefreshInventoryWindow", "CPythonNetworkStream::SendItemUsePacket", "CPythonNetworkStream::SendItemMovePacket", "CPythonNetworkStream::SendItemDropPacket", "CPythonNetworkStream::SendItemUseToItemPacket", "CItemManager::GetItemDataPointer", "CItemData::IsEquipment" ] }, "implementation": { "files": [ "project/ui/inventory_ui.gd", "project/ui/item_list.gd", "project/ui/equip_model.gd", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_stream.cpp", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/net_bounds.h", "extension/src/net/item_action_rules.h", "extension/tests/net_bounds_test.cpp", "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp", "project/inventory_ui_test.gd", "project/equip_model_test.gd", "project/game_scene.gd", "project/item_tooltip_test.gd", "project/view_equipment_ui_test.gd", "project/item_action_sound_parity_test.gd" ], "symbols": [ "EntityStore::mut_item_set", "EntityStore::mut_item_del", "EntityStore::mut_item_update", "EntityStore::mut_slot", "ClassicParser::on_gc", "ClassicSession::send_item_use", "ClassicSession::send_item_move", "ClassicSession::send_item_drop", "ClassicSession::send_item_use_to_item", "M2Client::move_item", "M2Client::use_item", "M2Client::drop_item", "M2Client::drop_item_count", "M2Client::use_item_to_item", "M2Client::item_action_allowed", "M2Client::is_equip_item_in_slot", "M2Client::set_item_proto", "M2Client::item_action_sound_for_item", "M2Client::emit_item_action_sound", "mtnet::item_action::use_sound_type", "mtnet::item_action::drop_sound_type", "InventoryUI::_to_wire", "InventoryUI::refresh", "InventoryUI::_on_item_highlighted", "InventoryUI::use", "EquipModel::_refresh" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "MAPPED", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/item.inventory-equipment-state.md", "tests": [ "extension/tests/net_bounds_test.cpp", "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp", "project/inventory_ui_test.gd", "project/equip_model_test.gd", "project/item_tooltip_test.gd", "project/view_equipment_ui_test.gd", "project/item_action_sound_parity_test.gd" ], "last_test_result": "PASS" }, "remaining": [ "M2Client native use/move 音效已按 item_proto 分类并接入 Audio;完整 m2dev/经典 SendSequence 包序和服务器拒绝副作用仍需证明;主角行动门与 dedicated/global equipment cell 的交易/商店/攻击中门及 ItemManager 装备分类已补齐", "RecvItemUpdatePacket 的双重刷新已通过 InvChange 快照和 inventory_state_changed 适配;仍未证明所有旧 inventory_changed 监听者都消费了中间状态;Set2 highlight 已通过 item_highlighted 队列和 InventoryUI 消费路径接通", "空槽 Update 虽在 EntityStore 保留,但 get_inventory 只暴露非零 vnum,未覆盖参考端对空槽字段的读取语义", "新装备、龙魂和腰带的经典/m2dev 双协议乱序、拒绝、换图重建测试", "服务器拒绝或拖拽中断后的 UI 回滚与窗口刷新顺序" ], "notes": "本轮回归确认非法 cell/空槽 Update/native 行动门/装备分类/Set2 highlight/未知 vnum 校验和 item_proto 音效分类未回归;完整 sequence、旧监听者中间快照、空槽字段 API、扩展槽位乱序、服务器拒绝回滚和断线回滚仍保持 PARTIAL。view_equipment_ui_test 与 netbridge_test 通过但各有 5 个 ObjectDB 泄漏。", "fingerprints": { "reference": "e68fce79b4828584f177c198f692d4a644a59d7ec5624012cd1c9963a294385c", "implementation": "e6ce2b71c4c49f24330955166450f8f895b09a208a67b1ad42f6f932c7558b82", "tests": "d2cef96677ae633de20ac87e91e8edb7a8e039b3abe49279363c7272fe64d7ce" } }, { "id": "item.consumable_action", "title": "使用、堆叠、拆分、快捷使用和消耗品反馈", "subsystem": "item", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/GameType.h", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayerModule.cpp", "UserInterface/PythonItem.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonPlayer::SetQuickPage", "CPythonPlayer::RequestUseLocalQuickSlot", "CPythonPlayer::RequestAddLocalQuickSlot", "CPythonPlayer::RequestAddToEmptyLocalQuickSlot", "CPythonPlayer::RequestDeleteGlobalQuickSlot", "CPythonPlayer::RequestMoveGlobalQuickSlotToLocalQuickSlot", "CPythonPlayer::MoveItemData", "CPythonNetworkStream::SendItemUsePacket", "CPythonNetworkStream::SendItemUseToItemPacket", "CPythonNetworkStream::SendItemMovePacket", "CPythonNetworkStream::SendShopSellPacket", "CPythonNetworkStream::SendShopSellPacketNew", "CPythonNetworkStream::RecvItemUsePacket", "CPythonNetworkStream::RecvItemUpdatePacket", "CPythonNetworkStream::RecvQuickSlotAddPacket", "CPythonNetworkStream::RecvQuickSlotDelPacket", "CPythonNetworkStream::RecvQuickSlotMovePacket" ] }, "implementation": { "files": [ "project/consumable_system.gd", "project/item_split_system.gd", "project/ui/quickbar.gd", "project/ui/inventory_ui.gd", "project/ui/shop_ui.gd", "project/net_play.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/entity_store.cpp", "extension/tests/net_classic_session_test.cpp", "project/test_consumables_parity.gd", "project/test_item_split_parity.gd", "project/test_taskbar_quickslot_parity.gd", "project/test_skill_inventory_parity.gd", "project/skill_test.gd", "project/mobile_gesture_test.gd", "project/complex_item_drop_test.gd", "project/netplay_test.gd" ], "symbols": [ "ConsumableSystem::toggle_elixir", "ConsumableSystem::process_elixir_recovery", "ConsumableSystem::use_dragon_god_potion", "ConsumableSystem::use_warp_scroll", "ItemSplitSystem::split_item", "ItemSplitSystem::quick_sell_item", "Quickbar::assign", "Quickbar::_activate_slot", "Quickbar::_clear", "Quickbar::_swap", "InventoryUI::move_to", "InventoryUI::use", "ShopUI::_send_sell", "M2Client::quickslot_add", "M2Client::quickslot_del", "M2Client::quickslot_swap", "M2Client::use_item", "M2Client::use_item_to_item", "M2Client::move_item", "M2Client::shop_sell", "ClassicSession::send_item_use", "ClassicSession::send_item_use_to_item", "ClassicSession::send_item_move", "ClassicSession::send_shop_sell", "EntityStore::mut_quickslot_set", "EntityStore::mut_quickslot_del", "EntityStore::mut_quickslot_swap" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/item.consumable-action.md", "tests": [ "project/test_consumables_parity.gd", "project/test_item_split_parity.gd", "project/test_taskbar_quickslot_parity.gd", "project/complex_item_drop_test.gd", "project/netplay_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "快捷栏请求现在等待 GC_QUICKSLOT_* 后重建本地状态;仍需真实拒绝、重复、乱序和断线/换图回包 fixture", "消耗品 ITEM_USE 后 ITEM_UPDATE/AFFECT/POINT/SPECIAL_EFFECT 的真实包序和 UI 反馈", "堆叠拆分已统一为 CG_ITEM_MOVE 请求并等待服务器确认;快捷出售已统一为 CG_SHOP SELL/SELL2,价格、金币和物品变化仍待真实回包 fixture", "消耗品 native action gate 与装备类型门已补齐;物品音效和 SendSequence 的资源/包序证据仍缺", "断线、换图、重复回包、请求失败和延迟期间的状态清理/回滚", "快捷栏乐观更新和页面取模差异已修复;quickbar 资源泄漏、完整生命周期清理和真实服务器确认 fixture 仍待补齐;complex_item_drop_test 的购买模式卖出目标判定已修复" ], "notes": "已将拆分、快捷出售和快捷栏增删交换统一到 request→server/GC 确认路径,并修复购买模式误发出售请求;快捷栏使用、消耗品真实反馈包序、音效/sequence 和断线清理仍使合同保持 PARTIAL。", "fingerprints": { "reference": "1eec38f9a52f1dfc25aae2e709e2bfb202231216366bb14c2be0ec38907fc9f4", "implementation": "7e30ec050cb2f0829696a7a1702b18386ead8cea6866d2c09e51f232f67b4f61", "tests": "d77435893b0cb2bf1b62a39bcbaff81495bdb8df0f55d5685b33d1da7412d93a" } }, { "id": "item.drop_pickup", "title": "地面掉落、拾取、光标、归属和掉落名条", "subsystem": "item", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonItem.cpp", "UserInterface/PythonItem.h", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayerInput.cpp", "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/PythonTextTail.cpp", "GameLib/ItemData.cpp", "GameLib/ItemManager.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonNetworkStream::RecvItemGroundAddPacket", "CPythonNetworkStream::RecvItemGroundDelPacket", "CPythonNetworkStream::RecvItemOwnership", "CPythonItem::CreateItem", "CPythonItem::SetOwnership", "CPythonItem::DeleteItem", "CPythonItem::__Pick", "CPythonItem::GetGroundItemPosition", "CPythonPlayer::SendClickItemPacket", "CPythonPlayer::__OnPressItem", "CPythonPlayerInput::OnMouseButton", "CPythonTextTail::RegisterItemTextTail", "CPythonTextTail::SetItemTextTailOwner" ] }, "implementation": { "files": [ "project/mob_drop_scatter.gd", "project/ui/ground_items.gd", "project/ui/mouse_controller.gd", "project/player_controller.gd", "project/net_play.gd", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp", "project/test_ground_pickup_parity.gd", "project/mob_death_drop_test.gd", "project/p2b_test.gd", "project/text_tail_arrange_test.gd" ], "symbols": [ "GroundItems::_on_added", "GroundItems::_on_removed", "GroundItems::try_pickup", "GroundItems::try_pickup_vid", "GroundItems::pick_at", "GroundItems::clear_for_map_change", "NetPlay::pick_ground_item", "NetPlay::reserve_click_item", "PlayerController::_update_hover_cursor", "EntityStore::mut_ground_add", "EntityStore::mut_ground_del", "EntityStore::mut_ground_owner", "ClassicParser::on_gc", "ClassicSession::send_item_pickup", "M2Client::pickup_item" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "GAP", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/item.drop-pickup.md", "tests": [ "project/test_ground_pickup_parity.gd", "project/mob_death_drop_test.gd", "project/p2b_test.gd", "project/text_tail_arrange_test.gd", "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "使用 CItemData::GetDropModelThing、drop effect、drop sound、法线和掉落动画,而不是通用占位模型", "未知 vnum 拒绝、ItemManager anti-flag、party member 所有权和 observer/native action gate", "模型实际相交 + TextTail Pick 与当前 64px 投影点选的边界测试", "统一 20 像素/3m/5m、server cm 与 Godot m 的距离链,避免双层拾取阈值不一致", "CG_ITEM_PICKUP→GC_ITEM_GROUND_DEL→GC_ITEM_GET/ITEM_SET/UPDATE 的真实包序", "拾取失败、重复请求、ownership 乱序、换图/断线旧 VID 和 queue_free 延迟清理", "修复 p2b_test 退出时的 2 个 ObjectDB 泄漏,并覆盖掉落名条颜色不被近距离刷新覆盖" ], "notes": "本轮完成掉落/拾取/所有权/名条/协议逐分支审计;基础回归通过但资源、权限和反馈链仍不等价,合同保持 PARTIAL。", "fingerprints": { "reference": "26895989dc143ba7f263a5e49bb66659c0b2d55c76cbe32e50bf49adc76c25e1", "implementation": "2c235060d70c557cb9d5d38b60988c4cb851ac63d72d652faa6d24289b06d476", "tests": "2db9bff916c9ea0f835855a3bb02ee51656231a0452060186baa9a5c5e4957e6" } }, { "id": "item.refine_socket_costume", "title": "强化、镶嵌、属性、时装和外观换装", "subsystem": "item", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamModule.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayerModule.cpp", "UserInterface/PythonItem.cpp", "UserInterface/PythonItemModule.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/Packet.h", "GameLib/ItemData.cpp", "GameLib/ItemData.h", "../../Client/Eternexus/root/uirefine.py", "../../Client/Eternexus/root/uiinventory.py", "../../Server/metin2/src/server/game/src/char_item.cpp", "../../Server/metin2/src/server/game/src/item.cpp", "../../Server/metin2/src/server/game/src/item_attribute.cpp", "../../Server/metin2/server/share/conf/item_proto.txt" ], "symbols": [ "CPythonNetworkStream::SendRefinePacket", "CPythonNetworkStream::RecvRefineInformationPacket", "CPythonNetworkStream::RecvRefineInformationPacketNew", "CPythonPlayer::SetItemData", "CPythonPlayer::SetItemCount", "CPythonPlayer::SetItemMetinSocket", "CPythonPlayer::SetItemAttribute", "playerCanRefine", "playerCanAttachMetin", "CItemData::GetSocket", "CItemData::GetSocketCount", "CItemData::GetRefine", "CItemData::HasNextGrade", "CInstanceBase::SetArmor", "CInstanceBase::SetHair", "CInstanceBase::__GetRefinedEffect" ] }, "implementation": { "files": [ "project/ui/refine_ui.gd", "project/refine_effect_system.gd", "project/item_attr_system.gd", "project/item_enchant_system.gd", "project/jewelry_socket_system.gd", "project/costume_system.gd", "project/costume_attr_transfer_system.gd", "project/wardrobe_transmog_system.gd", "project/ui/equip_model.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/net_bounds.h" ], "symbols": [ "RefineUI::_on_ask", "RefineUI::_accept", "RefineUI::cancel_refine", "M2Client::refine", "ClassicSession::send_refine", "EntityStore::mut_refine", "ItemAttrSystem::apply_add_attribute", "ItemAttrSystem::apply_change_attribute", "ItemAttrSystem::apply_blessing_marble", "ItemEnchantSystem::apply_scroll", "JewelrySocketSystem::drill_socket", "JewelrySocketSystem::insert_gem", "CostumeSystem::equip_costume", "CostumeSystem::unequip_costume", "WardrobeTransmogSystem::apply_transmutation" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "GAP", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/item.refine-socket-costume.md", "tests": [ "project/test_refine_parity.gd", "project/test_refine_effect_parity.gd", "project/test_item_attr_parity.gd", "project/test_item_enchant_manager_parity.gd", "project/test_jewelry_socket_parity.gd", "project/test_costume_system_parity.gd", "project/test_costume_attr_transfer_parity.gd", "project/test_wardrobe_transmog_parity.gd", "extension/tests/net_classic_session_test.cpp", "extension/tests/net_entity_test.cpp", "extension/tests/net_bounds_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "覆盖 M2Client::refine(255,255) 通过后的真实取消包和服务器回包", "按 GC_REFINE_INFORMATION(95) 与 GC_REFINE_INFORMATION_NEW(119) 分开解析并覆盖两种包", "将属性/镶嵌操作改为 ItemManager/use subtype + CG_ITEM_USE/ITEM_USE_TO_ITEM 请求和 GC_ITEM_UPDATE 权威回写", "把时装穿戴、属性、过期和 bonus 接入服务端 item/affect/point 与 actor armor/weapon/hair 更新链", "让强化等级/socket/type/subtype 驱动实际装备模型、强化特效 attach/clear,并覆盖资源缺失清理", "补充拒绝、断线、重复/乱序回包、换图、过期和服务端回滚测试", "将 wardrobe/transmog 自定义扩展与 40250 parity 分开标注,除非补齐对应服务端协议和角色部件同步" ], "notes": "本轮已修复 M2Client::refine 对 40250 取消 sentinel(255,255) 的错误拦截,并补充边界/EntityStore 回归;属性、socket、时装和幻化仍绕过服务器权威回包,合同保持 PARTIAL。", "fingerprints": { "reference": "a384bc07cc6d859077733ccff5d9168941e9ca4d27b5f9fd94b1dc01dc41930c", "implementation": "c41748c572b6a1c8ffb7c5ddf01cb47c0a8ee86419fce805270edb4ed512a121", "tests": "79edadc2084f36c3126cbe6586f356f043320fd860cf814c328c3d7c667a7939" } }, { "id": "npc.dialog_shop", "title": "NPC 交互、普通商店、个人商店和购买流程", "subsystem": "npc", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNonPlayer.cpp", "UserInterface/PythonShop.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "UserInterface/PythonNetworkStreamModule.cpp", "UserInterface/PythonPlayerInput.cpp", "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/Packet.h", "../../Client/Eternexus/root/uishop.py", "../../Client/Eternexus/root/uiprivateshopbuilder.py", "../../Client/Eternexus/root/uiinventory.py", "../../Client/Eternexus/root/playersettingmodule.py" ], "symbols": [ "CPythonNetworkStream::RecvShopPacket", "CPythonNetworkStream::SendShopEndPacket", "CPythonNetworkStream::SendShopBuyPacket", "CPythonNetworkStream::SendShopSellPacket", "CPythonNetworkStream::SendShopSellPacketNew", "CPythonNetworkStream::SendOnClickPacket", "CPythonNetworkStream::RecvShopSignPacket", "CPythonShop::SetItemData", "CPythonShop::BuildPrivateShop", "PrivateShopBuilder::OnSelectEmptySlot", "ShopDialog::Open", "ShopDialog::SelectItemSlot", "ShopDialog::UnselectItemSlot" ] }, "implementation": { "files": [ "project/npc_shop_system.gd", "project/private_shop_system.gd", "project/ui/shop_ui.gd", "project/ui/private_shop_ui.gd", "project/net_play.gd", "project/net_world.gd", "project/ui/quest_dialog.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h" ], "symbols": [ "NpcShopSystem::buy_item", "NpcShopSystem::sell_item", "ShopUI::_drop_to_sell", "ShopUI::_buy_slot", "ShopUI::_request_sell", "PrivateShopUI::_ok", "NetPlay::_on_pick", "NetWorld::_refresh_shop_sign", "M2Client::click_npc", "M2Client::shop_buy", "M2Client::shop_sell", "M2Client::open_private_shop", "EntityStore::mut_shop_update_item", "EntityStore::mut_shop_sign" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "GAP", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "GAP", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/npc.dialog-shop.md", "tests": [ "project/test_npc_shop_parity.gd", "project/test_npc_shop_flow.gd", "project/test_npc_dialog_shop_ui_parity.gd", "project/private_shop_ui_test.gd", "project/test_private_shop_bundle_parity.gd", "project/shop_rules_test.gd", "project/p8_test.gd", "project/complex_item_drop_test.gd", "extension/tests/net_classic_session_test.cpp", "extension/tests/net_entity_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "修复 EntityStore::mut_shop_update_item 对 40..119 全局位置的 tab 映射,并补充多货架增量更新测试", "禁止 ShopUI 在购买模式接收拖放出售请求,并补充输入路径回归", "按 shop VID 区分 NPC、他人个人商店和自己的个人商店,复现 40250 的按钮/关闭分支", "把带 shop_sign 的 PC 点击路由到 SendOnClickPacket 并覆盖招牌创建、点击和 GC_END 清理", "为买卖请求补齐动作门控、实际库存/anti flag/商店状态校验和服务器结果回写", "移除 live 路径对硬编码 NPC 库存与本地金币/背包 helper 的权威依赖", "补充 START/START_EX/UPDATE/ERROR/END、拒绝、售罄、断线、重连和乱序包的端到端测试", "补充个人商店死亡/骑乘/战斗/bundle/重复源格校验和服务端拒绝清理测试" ], "notes": "已完成 40250 与当前端的 NPC 点击、脚本对话、普通/多货架商店、买卖、个人商店招牌和摆摊提交逐分支/协议/状态审计。协议骨架和本地 UI 测试存在,但多货架更新、个人商店点击/状态、动作门控及本地直改旁路使合同保持 PARTIAL。", "fingerprints": { "reference": "f9d6038483ca4867d34f526301765a0abe8282cf177ec5e7c7e829b8bc7c5f65", "implementation": "23c313ef8d6c47891c58a5f60bda19ab6e1155ab2af2dda0d759dbcbb90f2dd6", "tests": "ea2f077e6a4bfe0d2a4fda60d1477f55b8a5a5530d67cdb7df8b74e1ce3119c9" } }, { "id": "social.chat_text_tail", "title": "公聊、私聊、聊天气泡、名字尾标和文本布局", "subsystem": "social", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonChat.cpp", "UserInterface/PythonChat.h", "UserInterface/PythonTextTail.cpp", "UserInterface/PythonTextTail.h", "UserInterface/InstanceBaseEffect.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGame.h", "UserInterface/Packet.h", "../../Client/Eternexus/root/game.py", "../../Client/Eternexus/root/uiwhisper.py", "../../Client/Eternexus/root/localeInfo.py", "../../Client/Eternexus/root/colorInfo.py" ], "symbols": [ "CPythonNetworkStream::RecvChatPacket", "CPythonNetworkStream::RecvWhisperPacket", "CPythonChat::AppendChat", "CPythonChat::AppendChatWithDelay", "CPythonChat::AppendWhisper", "CPythonChat::IgnoreCharacter", "CPythonChat::IsIgnoreCharacter", "CPythonTextTail::RegisterChatTail", "CPythonTextTail::RegisterInfoTail", "CPythonTextTail::UpdateAllTextTail", "CInstanceBase::RefreshTextTail", "CInstanceBase::AttachTextTail", "GameWindow::OnRecvWhisper" ] }, "implementation": { "files": [ "project/chat_tail.gd", "project/text_tail.gd", "project/text_tail_arrange.gd", "project/name_tail_layout.gd", "project/ui/chat_ui.gd", "project/whisper_chat_system.gd", "project/net_world.gd", "project/game_scene.gd", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/classic/wire_classic.h" ], "symbols": [ "EntityStore::mut_chat", "EntityStore::mut_whisper", "EntityStore::drain_chat", "M2Client::say", "M2Client::whisper", "ChatUI::_on_submit", "ChatUI::_on_chat", "ChatUI::_on_whisper", "WhisperChatSystem::receive_whisper", "NetWorld::_bubble", "NetWorld::register_info_tail", "NetWorld::_expire_chat_tails", "NetWorld::_reposition_name_tag", "ChatTail::register_chat", "ChatTail::register_info", "TextTail::refresh_title", "TextTail::refresh_level" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "GAP", "algorithms_formulas": "PARTIAL", "state_transition_order": "GAP", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "GAP", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/social.chat-text-tail.md", "tests": [ "project/chat_test.gd", "project/chat_tail_test.gd", "project/text_tail_test.gd", "project/text_tail_arrange_test.gd", "project/name_tail_layout_test.gd", "project/guild_mark_tail_test.gd", "project/test_whisper_chat_parity.gd", "project/chat_mobile_ui_test.gd", "project/test_chat_hyperlink_dice_parity.gd", "build/extension/net_entity_test", "build/extension/net_classic_session_test" ], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 whisper mode 0..5/0xFF 的独立会话、系统/GM/错误颜色、通知和服务端正文语义", "补齐 live WhisperChatSystem 的窗口历史、任务栏按钮、未读闪烁、ignore 命令和发送结果状态", "补齐无 VID INFO/NOTICE/BIG_NOTICE 专用提示、COMMAND 副作用和未知 VID 忽略分支", "复现入站表情解析、跨帝国文字转换、辱骂过滤和 40250 延迟聊天队列", "统一公共聊天颜色和文本前缀处理,发送失败不能生成成功回显", "让聊天/信息尾标使用同一权威实体生命周期,补齐真实字体度量、屏幕投影和死亡/换图清理", "补充重复/乱序/断线/重连、未知类型、超长文本和 whisper 错误端到端测试" ], "notes": "已完成 40250 与当前端的公共聊天、私聊、忽略、聊天气泡、名字/称号/等级/公会名尾标及文本布局逐分支/协议/状态审计。已修正 whisper mode 映射、截断 GC_WHISPER 拒绝逻辑,并将 live ChatUI 的普通/GM whisper 接入独立会话和 ignore 判断;基础网络与静态尾标测试存在,但窗口通知、公共提示/过滤/表情/颜色和 Godot 字体布局差异使合同保持 PARTIAL。", "fingerprints": { "reference": "6ce929fb1b892d5297a2a65bb2200248bdd26022fecdace8f0d258f76c50643a", "implementation": "c3a926547c75038a57c01cb9fb3b2340f78e506744344ed26119a3b48c31bd1b", "tests": "828b09c0d2b7758092e53f4aea7c3abbe42c203a53e2f0f934f2ae7fc9609e36" } }, { "id": "social.party_guild_messenger", "title": "组队、公会、好友、Messenger 和徽章", "subsystem": "social", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonGuild.cpp", "UserInterface/PythonMessenger.cpp", "UserInterface/PythonCharacterManager.cpp", "UserInterface/GuildMarkDownloader.cpp", "UserInterface/GuildMarkUploader.cpp", "UserInterface/MarkManager.cpp", "UserInterface/Packet.h", "../../Client/Eternexus/root/uiparty.py", "../../Client/Eternexus/root/uiguild.py", "../../Client/Eternexus/root/uimessenger.py", "../../Client/Eternexus/root/uiuploadmark.py", "../../Client/Eternexus/root/game.py", "../../Client/Eternexus/root/interfacemodule.py", "../../Client/Eternexus/root/uitaskbar.py" ], "symbols": [ "CPythonNetworkStream::RecvPartyPacket", "CPythonNetworkStream::RecvGuildPacket", "CPythonNetworkStream::RecvMessengerPacket", "CPythonNetworkStream::SendPartyInvitePacket", "CPythonNetworkStream::SendPartyParameterPacket", "CPythonNetworkStream::SendGuildPacket", "CPythonMessenger::OnFriendLogin", "CPythonMessenger::OnFriendLogout", "CPythonMessenger::SetMobile", "CPythonCharacterManager::RefreshAllGuildMark", "CGuildMarkDownloader::__LoginState_RecvMarkIndex", "CGuildMarkDownloader::__LoginState_RecvMarkBlock", "CGuildMarkDownloader::__SendMarkCRCList", "CPythonGuild::UploadMark" ] }, "implementation": { "files": [ "project/party_management_system.gd", "project/guild_skill_system.gd", "project/guild_war_system.gd", "project/guild_war_observer_arena_system.gd", "project/messenger_system.gd", "project/guild_mark_uploader_system.gd", "project/ui/party_ui.gd", "project/ui/guild_ui.gd", "project/ui/friend_ui.gd", "project/game_scene.gd", "project/net_world.gd", "project/app_flow.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/classic_mark_client.cpp", "extension/src/net/classic/classic_mark_client.h", "extension/src/net/classic/wire_classic.h" ], "symbols": [ "PartyManagementSystem::distribute_exp", "PartyManagementSystem::set_member_role", "GuildSkillSystem::donate_exp", "GuildSkillSystem::use_skill", "GuildWarSystem::record_score", "GuildWarSystem::end_war", "MessengerSystem::add_friend", "MessengerSystem::set_friend_state", "GuildMarkUploaderSystem::upload_guild_mark", "PartyUI::_on_invite", "GuildUI::_fill_members", "GuildUI::_fill_war", "FriendUI::_on_add", "M2Client::party_invite", "M2Client::party_answer", "M2Client::party_set_state", "M2Client::add_friend", "M2Client::friend_answer", "M2Client::guild_post_comment", "M2Client::use_guild_skill", "M2Client::download_guild_marks", "M2Client::upload_guild_mark", "EntityStore::mut_party_update", "EntityStore::mut_friend_set", "EntityStore::mut_guild_member", "EntityStore::mut_guild_war" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "GAP", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/social.party-guild-messenger.md", "tests": [ "project/test_party_management_parity.gd", "project/test_messenger_parity.gd", "project/test_guild_mark_uploader_parity.gd", "project/guild_mark_test.gd", "project/guild_mark_tail_test.gd", "project/test_guild_war_parity.gd", "project/test_guild_war_observer_arena_parity.gd", "project/test_guild_skill_parity.gd", "project/guild_creation_ui_test.gd", "project/test_privilege_empire_guild_parity.gd", "project/p8_test.gd", "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp" ], "last_test_result": "PARTIAL" }, "remaining": [ "为 GC_PARTY/GC_MESSENGER/GC_GUILD 全分支补充真实回包、拒绝、重复、乱序和重连测试", "禁止本地 party/guild skill/war helper 作为 live 权威,改为请求后等待服务端 state/affect/point 回写", "统一 live FriendUI 与 MessengerSystem 的会话/忽略/在线/移动状态来源", "补充公会权限拒绝、离会/踢人、公告/技能/金钱回包和邀请清理", "补充公会战声明/开始/结束/list/point 的连续事件与服务端奖励验证", "补充独立 mark client 的握手、index、CRC、压缩 block、损坏、断线、重试和角色刷新测试", "上传会徽/战旗必须验证真实用户资源、权限和服务端完成回包,避免默认占位图成为成功状态" ], "notes": "已完成 40250 与当前端的组队、公会、好友/Messenger、会徽/战旗及相关 UI/native 回包链审计。live native 主路径较完整,但本地队伍/技能/公会战/Messenger/会徽 helper 仍存在直接状态旁路,真实权限拒绝、mark 独立连接和重连清理证据不足,合同保持 PARTIAL。", "fingerprints": { "reference": "5deb754b5059a0d687d0f091f8227ca04c29001764bcc8a341d83e349ad140e1", "implementation": "98b1c1116db4f0dbdbb0e41eedd5aabb2ffcbf72ae1b4d4d0d5449189e776209", "tests": "73b284f6af5967c17fd27e89235a6273b4f5cf76950d7dd215c37be380ee5dea" } }, { "id": "social.exchange_safebox", "title": "玩家交易、仓库、安全箱和金币确认", "subsystem": "social", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonExchange.cpp", "UserInterface/PythonSafeBox.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamPhaseGameItem.cpp", "Client/Eternexus/root/uiexchange.py", "Client/Eternexus/root/uisafebox.py", "Client/Eternexus/root/game.py" ], "symbols": [ "CPythonNetworkStream::RecvExchangePacket", "CPythonNetworkStream::SendExchangeStartPacket", "CPythonNetworkStream::SendExchangeElkAddPacket", "CPythonNetworkStream::SendExchangeItemAddPacket", "CPythonNetworkStream::SendExchangeAcceptPacket", "CPythonNetworkStream::SendExchangeExitPacket", "CPythonNetworkStream::SendSafeBoxCheckinPacket", "CPythonNetworkStream::SendSafeBoxCheckoutPacket", "CPythonNetworkStream::SendSafeBoxItemMovePacket", "CPythonNetworkStream::RecvSafeBoxSetPacket", "CPythonNetworkStream::RecvSafeBoxDelPacket", "CPythonNetworkStream::RecvSafeBoxSizePacket", "CPythonNetworkStream::RecvMallOpenPacket", "CPythonNetworkStream::RecvMallItemSetPacket", "CPythonNetworkStream::RecvMallItemDelPacket", "CPythonExchange", "CPythonSafeBox" ] }, "implementation": { "files": [ "project/player_trade_exchange_system.gd", "project/safebox_storage_keeper_system.gd", "project/ui/exchange_ui.gd", "project/ui/safebox_ui.gd", "project/ui/mall_ui.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h" ], "symbols": [ "M2Client::exchange_start", "M2Client::exchange_add_item", "M2Client::exchange_add_gold", "M2Client::exchange_accept", "M2Client::exchange_cancel", "M2Client::safebox_checkin", "M2Client::safebox_checkout", "M2Client::safebox_move", "M2Client::mall_checkout", "EntityStore::apply", "ExchangeUI", "SafeboxUI", "MallUI", "PlayerTradeExchangeSystem", "SafeboxStorageKeeperSystem" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "GAP", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/social.exchange-safebox.md", "tests": [], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 GC_EXCHANGE/GC_SAFEBOX/GC_MALL 的拒绝、乱序、断线和回包回滚测试", "将交易和仓库 UI 的 optimistic 状态改为由 native 回包重建并验证源位置", "区分并隔离本地 helper,避免将交易结算、密码、库存和金条规则当作客户端 authority", "补齐 exchange 动作门控、真实服务端物品/金币校验和商城 checkout 失败清理" ], "notes": "已完成交易、仓库、安全箱、商城和金币确认的逐分支/协议/状态审计;native 主链存在但与 40250 仍是 PARTIAL,本轮未修改实现。", "fingerprints": { "reference": "927a9d6b8d15700537be48563b13161b23267db74e51b65a81ef7d5dea0181fd", "implementation": "2025bae6f6d7b66a301e02c8db364ac9eb75b507218b7a046961888c21bcc516", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, { "id": "quest.quest_event_navigation", "title": "任务状态、事件、对话、HUD 和导航提示", "subsystem": "quest", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonQuest.cpp", "UserInterface/PythonEventManager.cpp", "UserInterface/PythonEventManager.h", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonNetworkStreamEvent.cpp", "UserInterface/PythonMiniMap.cpp", "Client/Eternexus/root/uiquest.py", "Client/Eternexus/root/interfacemodule.py", "Client/Eternexus/root/uicharacter.py", "Client/Eternexus/root/uiminimap.py", "Client/Eternexus/root/atlasinfo.txt" ], "symbols": [ "CPythonNetworkStream::RecvQuestInfoPacket", "CPythonNetworkStream::RecvQuestConfirmPacket", "CPythonNetworkStream::RecvScriptPacket", "CPythonNetworkStream::SendScriptAnswerPacket", "CPythonNetworkStream::SendScriptButtonPacket", "CPythonQuest::MakeQuest", "CPythonQuest::RegisterQuestInstance", "CPythonQuest::SetQuestClockValue", "CPythonQuest::GetQuestLastTime", "CPythonEventManager::RegisterEventSetFromString", "CPythonEventManager::ProcessEventSet", "CPythonEventManager::Skip", "CPythonEventManager::EndEventProcess", "CPythonMiniMap::AddSignalPoint", "CPythonMiniMap::ClearAllSignalPoint" ] }, "implementation": { "files": [ "project/ui/quest_log.gd", "project/ui/quest_dialog.gd", "project/ui/quest_curtain.gd", "project/ui/char_status_ui.gd", "project/ui/atlas_ui.gd", "project/ui/minimap.gd", "project/atlas_navigation_system.gd", "project/game_scene.gd", "extension/src/net/m2_client.cpp", "extension/src/net/m2_client.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_session.h", "project/biologist_quest_system.gd", "project/dynamic_bounty_event_system.gd", "project/event_manager_system.gd", "project/metin_spawn_event_system.gd" ], "symbols": [ "EntityStore::apply", "M2Client::get_quests", "M2Client::script_button", "M2Client::script_answer", "M2Client::quest_input", "M2Client::quest_confirm", "M2Client::quest_cancel", "QuestLog", "QuestDialog", "QuestCurtain", "AtlasUI", "Minimap", "GameScene::_on_quest_fade_event", "GameScene::_on_quest_camera_event" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "GAP", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/quest.quest-event-navigation.md", "tests": [ "extension/tests/net_entity_test.cpp", "extension/tests/net_classic_session_test.cpp", "extension/tests/net_bounds_test.cpp", "project/quest_test.gd", "project/quest_event_test.gd", "project/test_quest_hud_parity.gd", "project/test_quest_dialog_parity.gd" ], "last_test_result": "PASS" }, "remaining": [ "验证 clock_start_ms 与 40250 CTimer::GetCurrentSecond() 的时间基准、后台恢复和过期任务清理,并增加真实任务日志到 UI 行号映射测试", "补齐真实 server 脚本序列与中断回归;script-button、DONE、ESC/CANCEL answer 语义已闭合", "验证 RUN_CINEMA 的有效 .msc 资源序列、过场消费者和中断清理;验证地图信号在 Minimap/Atlas 的切图与多点生命周期", "补齐对话确认超时、fade/camera/cinema 中断、断线和重连清理测试", "隔离生物学家/悬赏/活动 helper,避免其本地规则冒充 40250 客户端实现" ], "notes": "已完成任务状态、事件脚本、对话、HUD、地图导航和过场链的逐分支/协议/时序审计;GC_QUEST_INFO END、script-button 任务索引 sentinel、DONE close-only/DoneEvent、ESC/CANCEL answer 分支和 RUN_CINEMA 缺失资源分支已按 40250 修复,其他明确差异继续保持 PARTIAL。", "fingerprints": { "reference": "2a149f122184733ce357001e14a2da22fb3f4aa2aa37fb886924d567be8fb916", "implementation": "b1551f6d0a249dd70e825184d6833dcff23225ae8d0f805561e7b78663915984", "tests": "7f3267d7a9c959763bc6739ef2cd307046323cf4b4e7387bd99a04e74cfc9898" } }, { "id": "world.dungeon_activity", "title": "地下城、地图事件、Boss、结果和排行", "subsystem": "world", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "GameLib/MapManager.cpp", "GameLib/GameEventManager.cpp", "GameLib/GameEventManager.h", "UserInterface/Packet.h", "UserInterface/PythonNetworkStreamPhaseGame.h", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayer.h", "UserInterface/PythonEventManager.cpp", "UserInterface/PythonEventManager.h" ], "symbols": [ "CPythonNetworkStream::RecvDungeon", "CPythonPlayer::SetDungeonDestinationPosition", "CPythonPlayer::AlarmHaveToGo", "CPythonPlayer::Update", "CPythonEventManager::ProcessEventSet", "CGameEventManager::ProcessEventScreenWaving" ] }, "implementation": { "files": [ "project/dungeon_state.gd", "project/ui/dungeon_result_ui.gd", "project/game_scene.gd", "project/world_boss_system.gd", "project/dungeon_ranking_system.gd", "project/spider_dungeon_system.gd", "project/spider_dungeon_key_system.gd", "project/flame_dungeon_razador_system.gd", "project/endless_spire_dungeon_system.gd", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/net/classic/classic_session.cpp" ], "symbols": [ "EntityStore::apply", "M2Client::poll", "M2Client::request_dungeon", "ClassicSession::send_dungeon", "DungeonState::_on_dungeon_event", "DungeonState::_process", "DungeonResultUI::show_result", "GameScene::_on_dungeon_result" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/world.dungeon-activity.md", "tests": [], "last_test_result": "PARTIAL" }, "remaining": [ "接通 alarm_requested 到等价的 compass/mse 资源效果,并验证方向、初次提示和 20 秒重复提示", "补齐 GC_DUNGEON/DUNGEON_RESULT 的 live 回包、重复/乱序、拒绝、断线、切图和重连测试", "确认 server cm 到 Godot 世界坐标的轴向、地图基准和到达阈值实机一致", "把 Boss、排行和具体副本 helper 与 40250 client parity 分开,禁止本地 authority 冒充客户端等价实现" ], "notes": "已完成 GC_DUNGEON 目标点、结果窗口、地图事件及本地 Boss/排行/副本 helper 的逐分支、协议、资源和时序审计;目标状态算法接近参考,但 compass 副作用未接通,本地 authority 不能视为 40250 客户端实现。", "fingerprints": { "reference": "4915243dd9acc3a4c1eeb63971e71c5e8454d6bbd57fea4d27c939801a751946", "implementation": "b2ad0a9d4c42a74e886fa73846dbdd9d255c55fdee40618494a4265743f65f70", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, { "id": "world.fishing_mining", "title": "钓鱼、采矿、资源采集和水域条件", "subsystem": "world", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayerInput.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseMotion.cpp", "UserInterface/Packet.h", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "GameLib/MapOutdoorWater.cpp", "GameLib/AreaTerrain.cpp" ], "symbols": [ "CPythonPlayer::NEW_Fishing", "CPythonPlayer::NEW_CancelFishing", "CInstanceBase::GetFishingRot", "CInstanceBase::CanFishing", "CInstanceBase::StartFishing", "CInstanceBase::StopFishing", "CInstanceBase::ReactFishing", "CInstanceBase::CatchSuccess", "CInstanceBase::CatchFail", "CPythonNetworkStream::SendFishingPacket", "CPythonNetworkStream::RecvFishing", "CPythonNetworkStream::RecvDigMotionPacket", "CTerrain::GetWaterHeight", "CMapOutdoor::RenderWater" ] }, "implementation": { "files": [ "project/fishing_mining_system.gd", "project/fishing_rod_reel_system.gd", "project/fishing_cooking_camp_system.gd", "project/mining_refinery_smelt_system.gd", "project/net_play.gd", "project/net_world.gd", "extension/src/metin2_world.cpp", "extension/src/metin2_world.h", "extension/src/water_builder.cpp", "extension/src/water_motion.h", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/net/classic/classic_session.cpp", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h" ], "symbols": [ "M2Client::fishing", "ClassicSession::send_fishing", "EntityStore::apply", "NetPlay::_new_fishing", "NetPlay::cancel_fishing", "NetWorld::_on_fishing_event", "NetWorld::_on_dig_motion", "Metin2World::get_fishing_rotation", "Metin2World::can_fishing_position" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/world.fishing-mining.md", "tests": [], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 CanFishing/持竿/持镐动作门控、服务端拒绝和角色消失/重连测试", "验证 GetFishingRot 的轴向、MapCoord heading 和全地图 water attribute/水高单位", "让 GC_DIG_MOTION 按 count 复现多个 dig motion,并验证目标缺失、动作完成和中断清理", "把钓鱼收益、烹饪、营火、采矿、熔炼和宝石脚本标记为 custom/server authority,禁止冒充 40250 client parity" ], "notes": "已完成钓鱼输入/水面扫描、GC_FISHING、GC_DIG_MOTION、地图水面和当前端生活技能 helper 的逐分支、协议、状态与资源审计;协议骨架和部分算法接近参考,但 live 动作/门控/清理证据不足,本地收益脚本不是 40250 客户端 authority。", "fingerprints": { "reference": "1bdeb0e741ba1796fb80ff5a8aeaa2743dca3236182ce5a46cb6b0c8f8a8f7e2", "implementation": "305d81086fd8f9b4e29ce4eae5437be8ccd22e252290239b2186b3f1d5d448f8", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, { "id": "world.mount_pet_polymorph", "title": "马匹、宠物、变身和骑乘战斗", "subsystem": "world", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayerSkill.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseMotion.cpp", "UserInterface/InstanceBase.h", "GameLib/ActorInstance.cpp", "UserInterface/PythonNetworkStreamPhaseGameActor.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/Packet.h" ], "symbols": [ "CPythonNetworkStream::RecvMountPacket", "CPythonNetworkStream::__RecvCharacterAppendPacket", "CPythonNetworkStream::RecvCharacterUpdatePacket", "CInstanceBase::MountHorse", "CInstanceBase::DismountHorse", "CInstanceBase::CanAttack", "CInstanceBase::CanUseSkill", "CInstanceBase::CanAttackHorseLevel", "CInstanceBase::IsHoldingPickAxe", "CInstanceBase::IsPoly" ] }, "implementation": { "files": [ "project/horse_system.gd", "project/horse_combat_system.gd", "project/horse_level_growth_system.gd", "project/pet_companion_system.gd", "project/classic_pet_system.gd", "project/polymorph_system.gd", "project/polymorph_marble_system.gd", "project/mounted_combat_skill_system.gd", "project/net_play.gd", "project/net_world.gd", "project/ui/equip_model.gd", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "extension/src/net/classic/classic_parser.cpp", "extension/src/net/classic/wire_classic.h" ], "symbols": [ "EntityStore::mut_mount", "EntityStore::mut_char_update", "M2Client::poll", "NetPlay::_refresh_motion_mode", "NetPlay::_can_attack_horse_level", "NetWorld::_apply_field_updates", "EquipModel::_is_mounting", "EquipModel::_is_poly" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "GAP", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "GAP" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/world.mount-pet-polymorph.md", "tests": [], "last_test_result": "PARTIAL" }, "remaining": [ "补齐 mount_vnum/GC_MOUNT 到实际 horse actor/model attach/detach 的 live 链和位置清理", "验证骑乘 motion root、300/1200 度旋转速度、马等级攻击门和 weapon mode 的 live 回包时序", "把变身 affect/shape/装备遮挡接入 native entity 或明确标为缺失,不用本地 helper 代替", "将宠物、马匹成长/耐力/马上技能和伤害脚本与 40250 client parity 分离,禁止 custom authority 冒充等价实现" ], "notes": "已完成 mount_vnum/GC_MOUNT、骑乘动作/攻击门、变身和当前端宠物/马匹 helper 的逐分支、协议、资源和时序审计;网络字段与部分门控存在,但实际坐骑模型挂接和变身 native 闭环缺失,本地宠物/成长/技能规则不是 40250 客户端 authority。", "fingerprints": { "reference": "7340380e8dd27e2dd1c2d16c85b7b99e9f67353b021c3429c9788a91d27ffe04", "implementation": "a76f72fcf3ff39788e76d776687b21531d5e82479e863b526c75c2e95acb6926", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } }, { "id": "render.model_attachment", "title": "Granny 模型、LOD、骨骼、武器、盾牌和发型挂点", "subsystem": "render", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "EterGrnLib/ThingInstance.cpp", "EterGrnLib/ThingInstance.h", "EterGrnLib/LODController.cpp", "EterGrnLib/LODController.h", "EterGrnLib/ModelInstanceUpdate.cpp", "EterGrnLib/ModelInstance.cpp", "EterGrnLib/ModelInstanceMotion.cpp", "EterGrnLib/ModelInstanceModel.cpp", "EterGrnLib/ModelInstanceRender.cpp", "EterGrnLib/ModelInstanceCollisionDetection.cpp", "GameLib/ActorInstanceAttach.cpp", "GameLib/RaceData.cpp", "GameLib/RaceData.h", "GameLib/ItemData.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseRender.cpp" ], "symbols": [] }, "implementation": { "files": [ "extension/src/metin2_model.h", "extension/src/metin2_model.cpp", "extension/src/metin2_anim.cpp", "extension/src/gr2_bridge.cpp", "project/ui/player_view.gd", "project/ui/equip_model.gd", "project/ui/remote_player_view.gd", "project/ui/mob_view.gd", "project/ui/race_spec.gd" ], "symbols": [] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "GAP", "state_transition_order": "PARTIAL", "constants_units": "GAP", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "STATIC_VERIFIED", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/render.model-attachment.md", "tests": [ "project/weapon_attach_test.gd", "project/gpu_lod_attachment_test.gd", "project/gpu_pose_bounds_test.gd", "project/race_motion_assembly_test.gd", "project/equip_model_test.gd", "project/remote_player_test.gd", "project/mob_view_test.gd", "project/rendering_scenario_test.gd", "project/fly_target_bounds_test.gd", "project/mob_winding_test.gd", "project/animation_cache_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "统一 40250 Actor LOD 的 500/1500/2500cm 分档、模型队列和缺失资源回退", "验证 LOD 切换时动作复制、武器/盾牌/发型/盔甲挂点、材质、碰撞和 bounds 同步", "修复 03150 双手武器 run 动作左手挂点偏移,并建立逐动作挂点矩阵", "覆盖非连续 LOD、缺骨骼、重复换装、死亡、换图和资源释放;修复 ObjectDB 泄漏与 exit 139", "补齐变身装备遮挡及 ItemData/RaceData 多模型资源列表等价实现" ], "notes": "已完成 40250 ThingInstance/LODController/ModelInstance/ActorInstanceAttach/RaceData/ItemData 与当前 GR2/Godot 模型链的首轮逐分支审计;确认默认 LOD 常量、LOD 子模型生命周期和 03150 双手武器姿态存在真实差异,合同保持 PARTIAL,未修改实现。", "fingerprints": { "reference": "dc4d5eb93d161677fdb136680fa2d78c0c0b46713c86a85601684e6b0f524718", "implementation": "55dcbfef8cfc0d33df167126a63592c12c7e3f229d451991b7392c4eb9848add", "tests": "53cb669198620dc27a371857d5d0be616f98952bef1446a1e75ca89d04c9cd10" } }, { "id": "render.effect_particle_motion", "title": "MDE/MSE、粒子、动作事件挂点和回收", "subsystem": "render", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "GameLib/RaceMotionData.cpp", "GameLib/RaceMotionDataEvent.h", "GameLib/ActorInstanceMotionEvent.cpp", "EffectLib/EffectManager.cpp", "EffectLib/EffectInstance.cpp", "EffectLib/ParticleInstance.cpp", "UserInterface/PythonEffectModule.cpp" ], "symbols": [] }, "implementation": { "files": [ "formats/msa.cpp", "formats/msa.h", "extension/src/metin2_anim.cpp", "project/fx/effect_player.gd", "project/fx/mde.gd", "project/fx/mse.gd", "project/fx/skill_fx.gd", "project/fx/effect_registry.gd", "project/fx/motion_effect_anchor.gd", "project/fx/target_effect_anchor.gd", "project/game_scene.gd", "project/ui/player_view.gd", "project/ui/remote_player_view.gd", "project/ui/mob_view.gd", "project/net_world.gd" ], "symbols": [ "fmt::MotionEvent", "Metin2AnimPlayer::get_events", "Metin2AnimPlayer::emit_motion_event", "GameScene::_apply_motion_warp" ] }, "equivalence": {}, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/render.effect-particle-motion.md", "tests": [ "project/fx_test.gd", "project/effect_playback_test.gd", "project/motion_effect_anchor_test.gd", "project/target_effect_test.gd", "project/skill_fx_test.gd", "project/effect_color_test.gd", "project/effect_emission_test.gd", "project/effect_faces_test.gd", "project/effect_lie_test.gd", "project/effect_random_rotation_test.gd", "project/effect_render_regression_test.gd", "project/effect_rotation_test.gd", "project/effect_scale_test.gd", "project/effect_space_test.gd", "project/effect_surface_test.gd", "project/effect_texture_animation_test.gd", "project/motion_event_payload_test.gd", "project/motion_event_visibility_test.gd", "project/net_world_motion_event_test.gd", "project/motion_event_warp_test.gd" ], "last_test_result": "PASS" }, "remaining": [ "type 4/6 的生产分支和专用字段消费;type 9 WARP 已接入但远端目标生命周期仍待 live 验证", "SPECIAL_ATTACKING splash、FLY .fly、WARP 的真实服务端状态包和跨图清理", "逐帧、loop、缺字段、删除/死亡/重连边界测试", "motion_effect_anchor_test exit 139 和 ObjectDB 泄漏" ], "notes": "基础 MDE/MSE/粒子/挂点已覆盖,但 40250 ActorInstanceMotionEvent 的完整分支和所有 Actor 消费链仍不等价;保持 PARTIAL。", "fingerprints": { "reference": "7058842aee771df163233b1b3df496c0769d23675eb18e4c9547b243d3220285", "implementation": "7943c156a15667eb5601e05bdb70d75493bd5a3a5b8b395f20fa370eaa3186cc", "tests": "308c2684c98ef86f21e6b91525095b44a28593b0b1d18fa1916c65bc57759a3f" } }, { "id": "world.terrain_water_collision", "title": "高度、属性阻挡、水面、桥梁、拾取射线和地面贴合", "subsystem": "world", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "GameLib/MapOutdoor.cpp", "GameLib/AreaTerrain.cpp", "GameLib/MapOutdoorWater.cpp", "GameLib/TerrainDecal.cpp", "EterLib/AttributeInstance.cpp", "PRTerrainLib/Terrain.cpp" ], "symbols": [ "CMapOutdoor::GetTerrainHeight", "CMapOutdoor::GetHeight", "CMapOutdoor::GetPickingPointWithRay", "CMapOutdoor::GetPickingPointWithRayOnlyTerrain", "CMapOutdoor::isAttrOn", "CMapOutdoor::GetAttr", "CMapOutdoor::GetWaterHeight", "CTerrain::GetHeight", "CTerrain::isAttrOn", "CTerrain::GetWaterHeight", "CAttributeInstance::GetHeight", "CAttributeInstance::Picking", "CMapOutdoor::RenderWater" ] }, "implementation": { "files": [ "extension/src/metin2_world.cpp", "formats/area_data.cpp", "formats/attribute_data.cpp", "extension/src/water_builder.cpp", "extension/src/water_depth.h", "extension/src/water_motion.h", "project/player_controller.gd", "project/net_world.gd", "project/hit_collision.gd", "project/test_bridge_height_parity.gd", "project/test_all_map_height_probe.gd", "project/water_reference_test.gd", "project/fishing_water_test.gd" ], "symbols": [ "fmt::terrain_height_at", "fmt::load_height_map", "fmt::load_attr_map", "fmt::load_water_map", "Metin2World::build_chunk", "Metin2World::sample_height", "Metin2World::sample_attribute", "Metin2World::get_fishing_rotation", "PlayerController::_ray_ground", "NetWorld::_ground_world", "NetWorld::_blocked" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/world.terrain-water-collision.md", "tests": [ "build/formats/formats_map_test", "project/test_bridge_height_parity.gd", "project/test_all_map_height_probe.gd", "project/water_reference_test.gd", "project/fishing_water_test.gd", "project/test_click_target_effect_parity.gd", "project/player_move_test.gd", "project/test_skill_ground_fix.gd" ], "last_test_result": "PASS" }, "remaining": [ "静态 .mdatr 对象与地形统一 ray picking,并核对对象/地形命中优先级", "实现 water id 到具体 layer height 的运行时查询并接入钓鱼目标 Z", "补齐 CTerrain::isAttrOn 的高半字节属性语义和调用方矩阵", "streaming 未加载/跨区块/地图卸载期间的高度、碰撞、水状态和重试时序", "HeightMapShape3D、.mdatr 物理体与逻辑 ATTRIBUTE_BLOCK 的边缘/斜坡/清理一致性" ], "notes": "已完成地形/水面/碰撞/拾取首轮逐分支审计;高度、桥面和水面 happy path 已验证,但对象 ray picking、水层高度、高半字节属性和 streaming/清理边界保持 PARTIAL。", "fingerprints": { "reference": "23634378b4f11a57b203309ffbab1589bdb59240379d74bd8e8a3496829d0589", "implementation": "1ca29934bef9d573f5e877d054f59d87515644f4cc954beb2335b9c17330fed9", "tests": "d8d295894a46aa70ba1366a03847682ec2e54f6de09005d4444513a66dfef62f" } }, { "id": "world.environment_weather", "title": "天空、环境、光照、昼夜、雪和天气", "subsystem": "world", "priority": "P2", "status": "PARTIAL", "reference": { "files": [ "../Client/Eternexus/root/game.py", "../Client/Eternexus/root/servercommandparser.py", "GameLib/MapOutdoorRender.cpp", "GameLib/MapOutdoorRenderHTP.cpp", "GameLib/MapOutdoorRenderSTP.cpp", "GameLib/SnowEnvironment.cpp", "GameLib/SnowParticle.cpp", "GameLib/SnowParticle.h", "EterLib/EnvironmentMap.cpp", "EterLib/EnvironmentMap.h" ], "symbols": [ "MapOutdoor::OnBeginEnvironment", "MapOutdoor::OnSetEnvironmentDataPtr", "MapOutdoor::SetEnvironmentSkyBox", "MapOutdoor::SetEnvironmentLensFlare", "MapOutdoor::RenderSky", "MapOutdoor::RenderCloud", "CSnowEnvironment", "CSnowParticle" ] }, "implementation": { "files": [ "extension/src/environment_builder.cpp", "extension/src/environment_builder.h", "extension/src/metin2_world.cpp", "extension/src/net/entity_store.cpp", "extension/src/net/entity_store.h", "extension/src/net/m2_client.cpp", "formats/environment.cpp", "formats/environment.h", "project/game_scene.gd", "project/weather_daynight_system.gd", "project/fx/weather.gd" ], "symbols": [ "mtgodot::apply_environment", "Metin2World::load_map", "EntityStore::apply_server_command", "M2Client::weather_changed", "GameScene::_build_world", "GameScene::_on_weather_changed", "GameScene::_apply_server_weather", "Weather", "WeatherDayNightSystem" ] }, "equivalence": { "environment_parse_load_cleanup": "PARTIAL", "background_character_lighting": "PARTIAL", "fog_near_far_color_density": "PARTIAL", "sky_gradient_faces_cloud_transition": "PARTIAL", "screen_filter": "GAP", "lens_flare": "GAP", "wind_to_forest": "GAP", "snow_particle_pool_render_order": "PARTIAL", "server_xmas_snow_command": "PARTIAL", "day_night_weather_scene_integration": "PARTIAL", "reload_pause_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/world.environment-weather.md", "tests": [ "project/environment_test.gd", "project/skybox_test.gd", "project/p9_test.gd", "project/test_weather_daynight_parity.gd", "extension/tests/net_entity_test.cpp", "project/gamescene_test.gd", "project/netbridge_test.gd" ], "last_test_result": "PARTIAL: msenv/skybox/weather tests, xmas_snow native command, GameScene map filtering and M2Client signal pass; SnowEnvironment pool, DayMode, filter/lens flare/wind and linear fog remain open" }, "remaining": [ "实现或明确 screen filter、lens flare 和 SpeedTree/树木风更新,不以 metadata 代替运行时效果", "修正/验证 40250 线性 fog near/far/color 与 HTP/STP/terrain 的参数和时序", "补齐 DayMode/PRESERVE_DayMode 正式环境切换;WeatherDayNightSystem 的自动地图预设继续隔离,不能替代服务器 xmas_snow 命令", "对天空 transition、云 plane、SnowEnvironment 池和真实渲染顺序增加参数/screenshot oracle", "增加重复加载、快速切图、暂停恢复、资源缺失和环境/粒子清理测试" ], "notes": "已完成 .msenv、天空/云、双方向光、fog、天气粒子、服务器 xmas_snow 命令和正式场景过滤的逐分支审计;命令链与四张排除地图已有回归,但 SnowEnvironment 池、DayMode、filter/lens flare/wind、线性 fog 仍与 40250 不一致或缺失,故保持 PARTIAL。", "fingerprints": { "reference": "21e01b810af1c9128223aeb2bf953dddac6b63f35d96b758fa2c98754b0424e4", "implementation": "d8fdef03f7b298d2163115d1bd7244becabde51d1df58047595d4462abc94801", "tests": "2271c3ff7a417e846a4896dbbf47cd8e646091f20ca7008589d42640f9ecd13f" } }, { "id": "ui.input_camera_mobile", "title": "鼠标、相机、光标、IME、触屏和移动端窗口", "subsystem": "ui", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonApplicationCamera.cpp", "UserInterface/PythonApplicationCursor.cpp", "UserInterface/PythonPlayerInputMouse.cpp", "UserInterface/PythonIME.cpp", "EterLib/Input.cpp" ], "symbols": [ "CPythonApplicationCamera::__UpdateCamera", "CPythonApplicationCamera::RotateCamera", "CPythonApplicationCamera::PitchCamera", "CPythonApplicationCamera::ZoomCamera", "CPythonPlayerInputKeyboard::NEW_SetMultiDirKeyState", "CPythonPlayerInputMouse::NEW_SetMouseState", "CPythonPlayerInputMouse::NEW_SetMouseSmartState", "CPythonApplicationCursor::SetCursorNum", "CPythonIME::OnUpdate" ] }, "implementation": { "files": [ "project/game_camera.gd", "project/ui/mouse_controller.gd", "project/ui/cursor_manager.gd", "project/ui/mobile/mobile_window_host.gd", "project/ui/mobile/mobile_input_overlay.gd", "project/player_controller.gd" ], "symbols": [ "GameCamera.set_event_camera", "GameCamera._process", "PlayerController._wasd", "PlayerController._input", "PlayerController._unhandled_input", "MouseController.cancel_all", "CursorManager.set_cursor", "MobileUIRoot" ] }, "equivalence": { "keyboard_direction_state": "PARTIAL", "mouse_dispatch_and_smart_priority": "PARTIAL", "camera_normal_and_event_modes": "PARTIAL", "cursor_state_and_visibility": "PARTIAL", "ime": "GAP", "mobile_touch_window_adaptation": "PASS_WITH_ADAPTATION", "focus_pause_exit_cleanup": "PARTIAL" }, "platform_adaptations": [ "Godot mobile touch/joystick/pinch/landscape UI is a platform adaptation and has no direct Windows 40250 branch to compare 1:1." ], "evidence": { "contract": "audit/contracts/ui.input-camera-mobile.md", "tests": [ "project/game_camera_test.gd", "project/mouse_controller_test.gd", "project/input_key_test.gd", "project/mobile_gesture_test.gd", "project/mobile_ui_test.gd", "project/mobile_window_host_test.gd" ], "last_test_result": "PARTIAL: game_camera_test 3 failures; other five tests passed with leak warnings" }, "remaining": [ "统一 Camera 默认值、比例缩放、RESTORE_CAMERA 快照和事件相机锁定状态", "补齐 NEW_SetMouseState 全按钮/智能点击/中键/Ctrl/角色菜单分派矩阵", "补充连续光标、硬件光标显隐、IME 回调和生产场景组合清理测试", "补齐输入失焦、暂停、切图、重连和退出时的统一状态清理证据" ], "notes": "已完成首轮逐分支审计;移动端基础适配测试通过,但相机默认/恢复、鼠标统一分派、光标状态和 IME 仍未达到 40250 等价。", "fingerprints": { "reference": "9fa59e1a4ab5fda8a93338bbea6fa20aa7f776ddde26ebbb732645883033ec73", "implementation": "e5051d7363cb8ffae29a590a388e28a2632b2d831e86250f17bfdb74da5c1b7e", "tests": "efea4d26a5a0f3b1978b4c9fc6f9672ecb84223b1cd76b20b492f0528a780320" } }, { "id": "ui.hud_target_minimap_status", "title": "角色状态、血条、目标框、小地图、文本尾标和快捷栏", "subsystem": "ui", "priority": "P1", "status": "PARTIAL", "reference": { "files": [ "UserInterface/PythonTextTail.cpp", "UserInterface/PythonTextTail.h", "UserInterface/PythonMiniMap.cpp", "UserInterface/PythonMiniMap.h", "UserInterface/PythonPlayer.cpp", "UserInterface/PythonPlayer.h", "UserInterface/PythonApplication.cpp", "UserInterface/PythonApplication.h", "UserInterface/PythonPlayerEventHandler.cpp", "UserInterface/InstanceBase.cpp", "UserInterface/InstanceBaseEffect.cpp", "UserInterface/PythonNetworkStreamPhaseGame.cpp", "UserInterface/uitaskbar.py", "UserInterface/uicharacter.py", "UserInterface/uitarget.py" ], "symbols": [ "CPythonTextTail::UpdateAllTextTail", "CPythonTextTail::UpdateShowingTextTail", "CPythonTextTail::ArrangeTextTail", "CPythonMiniMap::SetScale", "CPythonMiniMap::ScaleUp", "CPythonMiniMap::ScaleDown", "CPythonPlayer::SetStatus", "CPythonPlayer::RequestUseLocalQuickSlot", "CPythonNetworkStream::RecvTargetPacket" ] }, "implementation": { "files": [ "project/hud.gd", "project/net_play.gd", "project/net_world.gd", "project/ui/char_status_ui.gd", "project/ui/target_ui.gd", "project/target_board.gd", "project/ui/minimap.gd", "project/text_tail.gd", "project/text_tail_arrange.gd", "project/chat_tail.gd", "project/ui/ground_items.gd", "project/ui/quickbar.gd" ], "symbols": [ "NetPlay::_on_points", "NetPlay::_on_target_info", "NetWorld::_apply_text_tail_to", "NetWorld::_update_hp_bars", "Minimap::_draw_map", "Quickbar::_on_points_changed", "CharStatusUI::_refresh_status" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "GAP", "state_transition_order": "PARTIAL", "constants_units": "GAP", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "PARTIAL", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/ui.hud-target-minimap-status.md", "tests": [ "project/test_target_board_parity.gd", "project/target_board_test.gd", "project/test_minimap_radar_parity.gd", "project/p9_test.gd", "project/text_tail_test.gd", "project/text_tail_arrange_test.gd", "project/chat_tail_test.gd", "project/chat_test.gd", "project/name_show_test.gd", "project/char_status_ui_test.gd", "project/test_taskbar_quickslot_parity.gd", "project/taskbar_dock_test.gd" ], "last_test_result": "PARTIAL" }, "remaining": [ "在正式 GameScene 创建并注入唯一 TargetUI,移除/隐藏简化目标条并补生产链回归", "只保留一套生产小地图,统一 40250 的 scale、terrain-cell 单位、按钮倍率和切换行为", "集中 points/quickslot/target/party/delete/world-reset 的 UI owner 并覆盖重复创建、死亡、换图、重连、退出清理", "逐帧对照 TextTail UpdateAll/Show/UpdateShowing/Arrange,消除 CanPickInstance、2Hz 和字体度量近似", "补真实资源/网络包驱动的 HUD/marker 测试并修复 Quickbar ObjectDB/RID/resource cleanup" ], "notes": "已完成 PythonTextTail/PythonMiniMap/PythonPlayer/TargetBoard/TaskBar 与当前 GameScene/UI 链的首轮逐分支审计;确认 TargetUI 未在正式 GameScene 注入、HUD 与新 Minimap 重复创建、Minimap scale 常量与 40250 不同,合同保持 PARTIAL,未修改实现。", "fingerprints": { "reference": "b12d02d7f7b9cf93e50239d3685ff5ab4c1220227d94005afe55aa5a1999b700", "implementation": "393de2d6c1d13ab16fb72bef3a8abcda503ee4eb16a4ab40f64f674436304a25", "tests": "1068c2809dff1a923da5f78f132c8802374ec42f7fad72730a15eb0fed84f9a4" } }, { "id": "resource.pack_locale_proto", "title": "EterPack、资源解析、locale、proto、MSA/MSM 和格式转换", "subsystem": "resource", "priority": "P0", "status": "PARTIAL", "reference": { "files": [ "EterPack/EterPackManager.cpp", "EterPack/EterPackCursor.cpp", "EterPack/EterPack.cpp", "EterPack/EterPackPolicy_CSHybridCrypt.cpp", "EterPack/StdAfx.cpp", "EterLocale/StringCodec_Vietnamese.cpp", "EterLocale/Japanese.cpp", "EterLocale/Arabic.cpp", "EterLocale/StringCodec.cpp", "EterLocale/StdAfx.cpp", "UserInterface/PythonPackModule.cpp", "GameLib/ItemData.cpp", "GameLib/ItemManager.cpp", "UserInterface/PythonNonPlayer.cpp", "GameLib/RaceDataFile.cpp" ], "symbols": [ "CEterPackManager::Get", "CEterPackCursor::Read", "packGet", "CItemManager::LoadItemTable", "CPythonNonPlayer::LoadNonPlayerData", "CRaceData::LoadRaceData" ] }, "implementation": { "files": [ "extension/src/pack/pack_mount.cpp", "extension/src/asset_io.cpp", "project/locale.gd", "extension/src/proto/proto.cpp", "formats/texture_set.cpp", "formats/terrain_files.cpp", "formats/msm.cpp", "formats/environment.cpp", "formats/asset_resolver.cpp", "formats/attribute_data.cpp", "formats/property.cpp", "formats/terrain_mesh.cpp", "formats/spt.cpp", "formats/m2_tokvec.cpp", "formats/map_setting.cpp", "formats/splat.cpp", "formats/msa.cpp", "formats/combo_table.cpp", "formats/textscript.cpp", "formats/area_data.cpp", "formats/m2_coord.cpp", "formats/tests/attribute_data_test.cpp", "formats/tests/msm_test.cpp", "formats/tests/combo_table_test.cpp", "formats/tests/map_formats_test.cpp", "formats/tests/msa_test.cpp", "extension/tests/pack_roundtrip_test.cpp", "extension/tests/proto_test.cpp", "extension/tests/proto_item_layout_test.cpp", "project/test_chinese_locale.gd", "project/skill_test.gd" ], "symbols": [ "AssetRoot", "AssetPack", "AssetResolver", "PackMount", "EterPack", "Metin2Proto", "Locale", "MsaMotion", "ComboTable" ] }, "equivalence": { "preconditions": "PARTIAL", "branch_structure": "PARTIAL", "algorithms_formulas": "PARTIAL", "state_transition_order": "PARTIAL", "constants_units": "PARTIAL", "timing_event_sources": "PARTIAL", "resource_data_sources": "PARTIAL", "protocol_side_effects": "MAPPED", "interruption_failure_cleanup": "PARTIAL" }, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/resource.pack-locale-proto.md", "tests": [ "extension/tests/pack_roundtrip_test.cpp", "extension/tests/proto_test.cpp", "extension/tests/proto_item_layout_test.cpp", "formats/tests/msm_test.cpp", "formats/tests/msa_test.cpp", "formats/tests/combo_table_test.cpp", "formats/tests/map_formats_test.cpp", "project/test_chinese_locale.gd", "project/skill_test.gd" ], "last_test_result": "PASS" }, "remaining": [ "把 pack/loose/AssetResolver/Locale/Proto 统一到实际运行时虚拟读取入口", "确认 40250 EterPack 原格式、搜索模式、overlay、CRC/cache 与当前格式替换的边界", "CP125x/Vietnamese/Arabic/Japanese 等 locale 编码和坏字节行为", "proto/MSA/MSM/技能/模型在 pack-only/PCK-only/缺文件/坏文件下的错误传播", "切换语言、重载资源、patch 覆盖和资源缓存失效的自动测试" ], "notes": "proto 和格式解析已核对并有测试;主资源入口、EterPack 原格式、locale 编码和失败回退存在材料差异,保持 PARTIAL。", "fingerprints": { "reference": "ecc3507b4bdd39d894826c16226c618d3f1ac82688589e16f3694f35cec22897", "implementation": "31c9548b53a2154f452ff50f47127536ca74bfb7eb7f450c16b3101dc9c53ec5", "tests": "f156b2178727e31985a7aa11bbd50b1c911c5c47c1cbb560246cb92b18d6b912" } }, { "id": "audio.bgm_ambience_3d", "title": "BGM、区域环境音、角色音效、3D 监听器和前后台恢复", "subsystem": "audio", "priority": "P2", "status": "PARTIAL", "reference": { "files": [ "MilesLib/SoundManager3D.cpp", "MilesLib/SoundManager.cpp", "MilesLib/SoundManager2D.cpp", "MilesLib/SoundManagerStream.cpp", "UserInterface/PythonSoundManagerModule.cpp", "GameLib/MapOutdoor.cpp", "GameLib/Area.cpp", "GameLib/Area.h" ], "symbols": [ "CSoundManager::Create", "CSoundManager::Update", "CSoundManager::PlaySound3D", "CSoundManager::PlayAmbienceSound3D", "CSoundManager::PlayCharacterSound3D", "CSoundManager::FadeInMusic", "CSoundManager::FadeOutMusic", "CSoundManager::SaveVolume", "CSoundManager::RestoreVolume", "CArea::TAmbienceInstance::UpdateLoopSound", "CArea::TAmbienceInstance::UpdateOnceSound", "CArea::TAmbienceInstance::UpdateStepSound" ] }, "implementation": { "files": [ "project/audio.gd", "project/bgm_director.gd", "project/app_lifecycle.gd", "project/game_scene.gd", "formats/environment.cpp", "formats/area_data.cpp", "extension/src/metin2_world.cpp" ], "symbols": [ "Audio.play_bgm", "Audio._play_3d", "Audio.update_ambience_sources", "Audio.play_character_sound_3d", "Audio.save_volume", "Audio.restore_volume", "Audio.shutdown", "BgmDirector._select", "AppLifecycle._enter_background", "GameScene._exit_tree", "GameScene._process" ] }, "equivalence": { "sound_manager_lifecycle": "PARTIAL", "bgm_state_machine": "PARTIAL", "volume_curve": "PASS_WITH_ADAPTATION", "listener_and_3d_scale": "PARTIAL", "character_motion_sound": "PARTIAL", "area_ambience": "PARTIAL", "background_save_restore": "PARTIAL" }, "platform_adaptations": [ "Godot AudioStreamPlayer/AudioStreamPlayer3D replaces Miles provider/driver; exact attenuation and hardware-provider behavior is a platform adaptation." ], "evidence": { "contract": "audit/contracts/audio.bgm-ambience-3d.md", "tests": [ "project/audio_miles_test.gd", "project/ambience_test.gd", "project/bgm_test.gd", "project/test_bgm_jukebox_parity.gd", "project/app_flow_lifecycle_test.gd", "project/system_option_ui_test.gd", "project/rendering_scenario_test.gd" ], "last_test_result": "PARTIAL: focused BGM/SFX background mute-restore and Debug exit cleanup pass; coverage does not prove Miles instance pool or multi-instance music state" }, "remaining": [ "验证真实 3D 播放实例不会绕过 master SFX,并补设备/实例路径的后台静音回归", "验证 BGM 多实例淡入/限幅淡出/全量淡出/加载失败回滚状态机", "验证 3D attenuation、实例池耗尽、远端动作音效和完整销毁顺序", "验证流式地图快速切换、环境声来源删除和退出时无悬挂播放器" ], "notes": "已完成首轮逐分支审计;基础音量/监听器/环境声/动作音效路径存在,后台 BGM/SFX 同时静音恢复与退出 Audio 缓存清理已有回归,但 Miles 实例池和 BGM 逐帧状态机仍不等价。", "fingerprints": { "reference": "b76a647a0c58e49b1b3bd47c1eb89c1d6508d8abed6eddb8092ab09e4c5ddbae", "implementation": "e2cb3ea4a6dec8d7e2113f15e8419f3dae0f9fef4a78267ae42bd24ef1ed88cf", "tests": "03cb7dda64dc2cf49320e2715614290790c4086e7bec1349d65c1937a69ce024" } }, { "id": "lifecycle.platform_release", "title": "Windows/桌面窗口、导出、资源自包含、崩溃和发布门禁", "subsystem": "lifecycle", "priority": "P2", "status": "PARTIAL", "reference": { "files": [ "EterLib/MSApplication.cpp", "EterLib/MSWindow.cpp", "UserInterface/PythonApplication.cpp", "UserInterface/PythonApplicationProcedure.cpp", "UserInterface/CheckLatestFiles.cpp", "EterPack/EterPackManager.cpp" ], "symbols": [ "CMSApplication::MessageLoop", "CMSApplication::WindowProcedure", "CMSWindow::Create", "CMSWindow::OnSize", "CPythonApplication::Process", "CPythonApplication::WindowProcedure", "CheckLatestFilesEntry" ] }, "implementation": { "files": [ "build.sh", "build-macos-client.sh", "build-android.sh", "export-android.sh", "project/export_presets.cfg", "project/client_main.gd", "project/app_flow.gd", "project/app_lifecycle.gd", "project/asset_root.gd", "project/asset_pack.gd" ], "symbols": [ "AppFlow.start", "AppFlow._goto_game", "AppLifecycle._notification", "AssetRoot.path", "AssetPack.ensure" ] }, "equivalence": { "windows_window_and_message_loop": "GAP", "application_update_render_exit_order": "PARTIAL", "focus_fullscreen_and_close": "PARTIAL", "resource_self_containment": "PARTIAL", "latest_file_crc_update_check": "GAP", "exception_and_crash_handling": "GAP", "macos_android_export": "PASS_WITH_ADAPTATION", "windows_export": "GAP" }, "platform_adaptations": [ "Godot MainLoop/Window and platform export replace the reference Win32 CMSApplication/CMSWindow; this is acceptable only as an explicit platform adaptation, not as Windows 1:1 parity." ], "evidence": { "contract": "audit/contracts/lifecycle.platform-release.md", "tests": [ "project/app_flow_lifecycle_test.gd", "project/bgm_test.gd", "project/dds_lifecycle_test.gd", "godot --headless --path project --editor --quit", "godot --headless --path project --export-release macOS /tmp/mtgodot-platform-audit.app" ], "last_test_result": "PARTIAL: focus activation/audio save-restore, AppFlow, DDS, editor validation and macOS export passed; Windows export/window/CRC/crash paths absent" }, "remaining": [ "增加 Windows 导出预设、GDExtension 构建和整包启动/退出验证", "补窗口 resize/全屏/关闭/IME/native input 的平台测试;焦点激活音量路径已有回归", "补资源版本/CRC/缺包/坏包/路径大小写门禁", "补异常处理、crash report、正常退出和 native 资源释放证据" ], "notes": "已完成首轮逐分支审计;AppFlow、AssetPack、焦点激活音量保存恢复和 macOS/Android 适配存在,但 Windows 原生窗口/导出、全屏与 resize、CRC 更新检查、Eter 异常处理和完整发布门禁仍缺失。", "fingerprints": { "reference": "aad501a1d500710629402dd2be24703a6b61f87b17323624d32c5057efe2100f", "implementation": "addf830b10a8d794a8a26308f07014f347e510eb97982390df7f8b74c931d348", "tests": "e1b36cb9802208f74c3387529e9684ae65a415b8a57cea0af313025b3c1453fa" } }, { "id": "extension.current_only_features", "title": "没有 40250 参考实现的新增玩法和平台扩展", "subsystem": "extension", "priority": "P3", "status": "EXCLUDED", "reference": { "files": [], "symbols": [] }, "implementation": { "files": [ "project/offline_bazaar_market_system.gd", "project/mail_system.gd", "project/dynamic_bounty_event_system.gd", "project/dragon_soul_system.gd", "project/solo_guild_domain_system.gd" ], "symbols": [] }, "equivalence": {}, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/function-inventory.md", "tests": [], "last_test_result": "NOT_RUN" }, "remaining": [], "notes": "完整功能清单首批合同;尚未完成 40250 逐分支等价审计。", "exclusion_reason": "在指定 40250 ClientVS22 source 中没有可达对应行为;记录该当前端扩展以防止漏审,但不计入 40250 等价率。", "fingerprints": { "reference": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "implementation": "8184e1ac57157516cbdb98a191e9318ccdfe55b5c6cea888c377d5d18910432b", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } } ] }