{ "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", "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", "build/extension/net_classic_e2e", "build-debug/extension/net_classic_e2e", "project/auth_connect_bridge_test.gd", "project/auth_peer_close_bridge_test.gd", "project/char_create_delete_test.gd" ], "last_test_result": "PASS", "commands": [ "project/app_flow_lifecycle_test.gd (login failure UI owner)", "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)" ] }, "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": "d5551c625ebdda0a4887cf2f95704d9220b8affb72a63c868b476825a63c7652", "implementation": "be1421c9c311c980829da8db8edf39ca930661fca905813b29549598bd457c17", "tests": "25948f6674be9cb048cffd40496576917aa0a1379b05e7ed1e1218bd3c005020" } }, { "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", "GameLib/GameUtil.cpp", "EterLib/Camera.cpp", "GameLib/ActorInstanceBattle.cpp", "GameLib/ActorInstance.cpp" ], "symbols": [ "CPythonPlayer", "CInstanceBase", "SetMotion", "Move" ] }, "implementation": { "files": [ "project/player_controller.gd", "project/game_camera.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": [ { "description": "40250 CPythonPlayer::NEW_MoveToDirection m_isCmrRot 相机跟随:D3D CCamera::Roll 的正方向与 Godot yaw 增大(视线转向左)手性相反,_camera_auto_rotate() 对折叠后的 fRotRat 取正号而非参考实现的负号。", "invariant": "按住某一方向键且通过移动门时,camera.yaw 持续朝该方向收敛:正前/正后 fRotRat=0(不转),正左/右 fRotRat=±90(最大速率),对角线线性减半;速率恒为 CAMERA_AUTO_ROTATE_SPEED_DEG(20.0)*dt*fRotRat/90,从不反向或越界。", "test": "project/keyboard_motion_timeline_test.gd" } ], "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 根运动和当前连续速度/米制推进的时序差异仍未闭合", "OnWaiting(走/跑动作中本帧无位移时 100ms+1.0 相对 m_kPPosPrevWaiting 发 FUNC_WAIT,InstanceBase.cpp:1930)未移植;站立期补发已删除", "同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定/私店/情绪的 held WASD 恢复已覆盖基础路径", "真实角色模型/骨骼在快速反向输入时不翻转的模型级测试", "输入事件到移动请求、停止请求和网络包的完整时间线" ], "fingerprints": { "reference": "9aa911197f1b86861f389d86ac2ab66e7b60d6f88a5c672e7378d8018ea9aa4d", "implementation": "0ffdde0d7e930f1c74d5f4f3cda01f41f56d582069525c6519f161f068358e22", "tests": "2c79ef6e727717527f54e611629ce15154cb78c785b37ada4970d1156caf1bfd" } }, { "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": "153c730059c1f648861675a4a6c20d10d473da4c9bf5636fea5a251a1f230536", "implementation": "d2a3bd4b0c303dc61fd8e7e3e469b35bdae757c8ff5af4e7c6640d843a12376a", "tests": "bc8e463dd84921b8bdadc7e3b6d2104ea4b7b5955ae57f37428406261088a127" } }, { "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": "651b0e0b0757f5fd8b69ba48615bae16eec646d769369014a25450dbc0526a71", "implementation": "156dadceb2ac0c9ec0816718ff24d8fdebc3b0595b03c9353003d3e68df6a654", "tests": "7943c3f2372e06b7c1f6d34e8f7ad27b7d7e475236685bd41ea08216cc6a5c2a" } }, { "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": "b0e387fadfd0c1146d0047af68152bb019b5538ce7acc5e50b1e0f476aae14c6", "implementation": "db411e1ce47a22608c1d3dbab7ee77105ec68298f9a159611ec66c3bc6ce7fc2", "tests": "5de299be840bc0d46f5eea3acd55af332dd3e1c71472e9ae428e7762bc7f72b8" } }, { "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": "a85227931ded79ea5c9e92eaf647a076c9abbabd794ca12ad30e918259af24f4", "implementation": "4534f16d0850b5375c979164705648cc413d050781b4dce224acc6ab87200d70", "tests": "e865f9782600d3d2027db84beb10913a22a22c751938b707768e7c838d894a1a" } }, { "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": "c22be1a3fb1e435df139e05df8650840d229d36509af0e58c78ee39fcd189a3b", "implementation": "9b8136a0062b3268f0bda3d9f13e52e3a6a5aa2d105c22fb16cd38afb56e5c18", "tests": "f56863d12c0cb2b8dcf535c2db8cab9b432d2dadd22f2bf133f0ec0630d7dafd" } }, { "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": "12dba8cb33b427a4b226415919450d863c4f768bf419732be25e9b4a75e1c3e3", "implementation": "0be8e78eb0a0f6da7e1af10a3aa77fa43e93915fefc1bd7fc60c117174a57404", "tests": "b69b109040b958aaa09d1f1aea549884b286f0e4d04acd4574c8b322169be8e9" } }, { "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": "dc1fd5bb77c7730498d75a566afca07f34a91771c4ebaf5983aff83ee1e6ad4a", "implementation": "b1a5d95818f5d0e4e21da62f9262cbd560f452ff42a9b0a2db5ac0e4120d5f18", "tests": "f6a16e75e4a98ee53f2757dd3ecc05b715cc353ee98c40365e624f7e9386039d" } }, { "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": "3fc038ea59c95ea4d3038fe2737d8254966212f41125b8657728aa085a308404", "implementation": "c326fcfd8d6a82905ea54d4b95cb75f4e1148e04dadfd6b88e8d8eb2779e9a68", "tests": "9dfa17b7705966830888646381d7abe70653a2b381383144489c2f0d456899e5" } }, { "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", "extension/tests/net_loopback_test.cpp" ], "last_test_result": "PARTIAL", "commands": [ "MT_PROTOCOL=classic godot --headless --path project --script channel_status_test.gd" ] }, "remaining": [ "补齐 auth failure、remote close 和 channel status 完整矩阵", "证明 count 边界、fallback probe 和恢复顺序", "增加重试上限、窗口销毁和 login/reconnect 竞态清理测试" ], "notes": "本轮按 40250 修复认证密码生命周期、两套 transport 的连接 deadline/可写完成判定,以及 ChannelStatus 旧 probe 与重入 callback 清理;频道检测仍保留当前端逐频道 TCP fallback,认证失败/远端关闭/完整恢复与边界矩阵仍未闭合,合同保持 PARTIAL。", "fingerprints": { "reference": "9cfeebabb54d8e929d21b8c0aaa8d2bd99d590a2cb3c4c78d4a6e1b87134bebe", "implementation": "c203e25d94a95cf8c342692525a4f8a2bf8d5a1885f209b52e4e884be291bf4d", "tests": "00827ebf36e6201623153a716a44e6e0c7cf7d8a01c0383fc1b874b23e35e3e6" } }, { "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": "4960a363710b99cbed12a559744231a7a7d7da56b703c34af07685bcdb8511d2", "implementation": "3c451e6d2d11aa01842d3ba8e9b28f974780016395cc20c3fb81652bb381511f", "tests": "720b1c6a90f62614e9867f51a9dd039b695c4141d2f8d568e9298637620a1779" } }, { "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": "cfe7d501420c271736030771877026db2c7859559dbab56d2dd82574b2785c71", "implementation": "005ad13d71771e0812307492cd0a07b263641cb317baf957254922a4d461eb1b", "tests": "67a02ec9abd6ee1ead261c0e1353115e69b34934975dbb071b2faab05c436e57" } }, { "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/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", "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", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 actor/ground/shop-sign 调用链和当前端 parser/store/view 对照;合同保持 PARTIAL。net_world_vis_test 复现 4 项可见性生命周期失败,本轮仅登记审计结果,未修改实现。", "fingerprints": { "reference": "97dfe247272a6098ce623ceae986732205de3273cb1199377f969faeec8d0ba0", "implementation": "bdc38d8ef47e8a8f2676f6e9a080c7924a56b88ceb546affeb281dc57243a1d1", "tests": "702ac283f682d937fa8a02c70ca929942b68cacffc7adc686a5779e45f10f178" } }, { "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", "GameLib/FlyingObjectManager.cpp", "EffectLib/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/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": "a992eac9ed117a1da3e0ae27324208a08be18fe1f9ce6855cfacdccf692c4c47", "implementation": "6b8ec07635f0f519aa92dc4639b70c3f1cf06b09f425ce72d01db7e685bf2dca", "tests": "d3cc153e1d36bb4f390b88efc623e3cd56d1703626d7b9f325f9ca3c457adf1d" } }, { "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": "5f27304415d1e0539673c738ed20ad44995be7e4787f87d2b2edd5232b7cb73b", "implementation": "f104a00d240f2c9233fb1a4a86ee24a0415ed863dcd5464386dd5b2ae45f32e3", "tests": "091582b21c6be94f4923de7a58e6ea55c614808bfb643622ee4651c5dd465861" } }, { "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": "6a1609a0a2ae34ef4725e7e274b37881638b6b0757e0759e08cbaed9eb65602b", "implementation": "d6f852e6bf1fefcc54fb1b365180e38c457d65b9af6b0478d669b48b8f784ca2", "tests": "9ec8668cc4acb5790f16b6140e5b5639196c153063631b08b36745f64397fcd2" } }, { "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": "d344ff65fa4c8a72a2c268a16fb268e6526121fa05e2d4359b7c40df4eb75af4", "implementation": "07f074eb38d30cb0bcab4a55e3ccd1091162faf156425818742843e5c0ed0557", "tests": "5815d9275c50109a98b6f21175c4ab274a0eb7cd4b4849dcf4b973a0b4c395d6" } }, { "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" ], "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 目前只有配置字段,没有进入武器刀光渲染链", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "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": "abf0577e3430979fbf4e1ea85fa637b2f59e798bf57e257060528940045eef43", "implementation": "6f93c4bcb3aee4af48beee7cff2eb4c5923a3b0bb32eae0f2b132f2873524e8f", "tests": "228e1bb5ebb0ef52822956e3d33d9c51d55cc2090c323300b466f937249e5d3c" } }, { "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" ], "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_click_target_effect_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 顺序", "真实服务端目标包、死亡/删除/乱序/断线重连和目标框清理", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成目标攻击资格、目标可选中、目标框和安全区的逐分支静态审计;目标入口已统一接入 CanChangeTarget/CanPickInstance、重复目标窗口和 CG_TARGET(0) 清理,合同保持 PARTIAL。", "fingerprints": { "reference": "d1c911b8bba1ccd48228a794568e3bc13ffa77ecd7d124d4e442ed9fecfdfb85", "implementation": "47ddc51bc30196640a8e27ac2286405bc6bd5fc2a25d80b636884382fc0d468a", "tests": "3b20fa82a05fdf13597b631c6463f15d6a303dd7eddf43ed6f65d3e873bce756" } }, { "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": "dcbded9cf3a69f372d4d507d3b8cced19a0d89bfe4dde01cb46a9d10211398b7", "implementation": "b92babba3d38dcf71b391d58c320f3c8b6b5af3c26b7a2065c80ac2585a11cc0", "tests": "b6106f8af47ea86f4c289e56455da78642f2ee35e074dbd073a640171e55aec8" } }, { "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/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/damage_effect_test.gd", "project/combat_fx_test.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 重用清理测试", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 AffectFlagContainer、Actor affect/受击动作、damage/affect 协议和当前 native/GDScript 链的逐分支审计;确认本地状态结算器的数据权威错误,且 hit_view_test 有 1 项失败。合同保持 PARTIAL,本轮仅登记审计结果,未修改实现。", "fingerprints": { "reference": "92ddbb559ef4966bcecc9358beb85f7fd8edec830f833cde7067f0c35eaa2aee", "implementation": "0012356c81b291e8bff2f96561efad74ac45ba624c91986ce53309b25abf76cc", "tests": "1c7ab5e0776d4e144a37d2d2e659c02eb259aa0b5caec4111b50b673ef39b112" } }, { "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/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 顺序快照", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 技能数据、三层施放校验、CanUseSkill、casting-speed 冷却公式、快捷栏、普通/公会技能发送入口、skill/group/cooldown 协议和 FUNC_SKILL→CG_USE_SKILL 动作/意图包序的逐分支审计;CanUseSkill 五类动作门、POINT_CASTING_SPEED 桥接和公会技独立路径已实现,但真实拒绝/乱序/跨生命周期 fixture、完整动作资源包序和服务端 damage 顺序仍使合同保持 PARTIAL。", "fingerprints": { "reference": "c38e930585edcd35a8e0911f10f3fb6694a39bc7735a3bac0b3d4eea157c9bcb", "implementation": "233c5cd7ee94ba2fa9ecb1a833a3ecad3e805ade4a87333243480199b18e7d4b", "tests": "e3b00c87a54d1af4effeef310d90de4c72dacf7a7323bab005f6850ace556bb5" } }, { "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": "319ef546037833afca533d60488f0f5e9618e9da26bd67699ad16cefa413986a", "implementation": "b8bc1a86bd1781601e4faa66724fe3674bb5b6658d82f6e8c8b57bbae83b261e", "tests": "b486458e209c415e1373ae96e83a6be7c558aca8afb99822baba350c89fc24be" } }, { "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/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_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_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 的购买模式卖出目标判定已修复", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已将拆分、快捷出售和快捷栏增删交换统一到 request→server/GC 确认路径,并修复购买模式误发出售请求;快捷栏使用、消耗品真实反馈包序、音效/sequence 和断线清理仍使合同保持 PARTIAL。", "fingerprints": { "reference": "f364d4bbba79ef514f5f8aeccbf34a3d6adea8fcacf4cb82664476b6c29ebbfe", "implementation": "20983090cb18e010bcd3fd674166491d589f32335253d4b19241c556431cf3b2", "tests": "360f28348ea118717464fd3ebe179791a95377b658f60364ff7286a041059d97" } }, { "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": "d63b259e06f98b6ebb425576e8d428da4bf96bae3c297cea11f2cb5606132dfd", "implementation": "6404d8f5e418bd7b92ad6ad6896da67cdc1041581331a08ee42d491231228957", "tests": "0713f0aeef0e53fdf447537c2a8e785559269b45ca63825c4b507382fbc95019" } }, { "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/item_attr_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_item_attr_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 分开标注,除非补齐对应服务端协议和角色部件同步", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "本轮已修复 M2Client::refine 对 40250 取消 sentinel(255,255) 的错误拦截,并补充边界/EntityStore 回归;属性、socket、时装和幻化仍绕过服务器权威回包,合同保持 PARTIAL。", "fingerprints": { "reference": "6cc67309be64986008984215add763db78e2990b665274500cf4e76661b782e8", "implementation": "0dceaccac8bd37f946846dfd7bebf7cb38563896dc2d24ec7eed3560854e2b31", "tests": "785b8529135d9169573531c379e0997d61a61acfffb40dbf1bc8f715ee3813de" } }, { "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/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_flow.gd", "project/test_npc_dialog_shop_ui_parity.gd", "project/private_shop_ui_test.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/重复源格校验和服务端拒绝清理测试", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 与当前端的 NPC 点击、脚本对话、普通/多货架商店、买卖、个人商店招牌和摆摊提交逐分支/协议/状态审计。协议骨架和本地 UI 测试存在,但多货架更新、个人商店点击/状态、动作门控及本地直改旁路使合同保持 PARTIAL。", "fingerprints": { "reference": "2fa18ff8979d5c03e2daf44129702e3a1b26bb9e6f7b3b585f6fa55ac898e765", "implementation": "4adc63160de3b1b21f51c11d6c512d7f550c976a50c49be46784c967eca574ca", "tests": "c4a51e1a265294adaf10693271297d832ba2cc7ef6de6e6125bded961cea7594" } }, { "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/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", "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 错误端到端测试", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 与当前端的公共聊天、私聊、忽略、聊天气泡、名字/称号/等级/公会名尾标及文本布局逐分支/协议/状态审计。已修正 whisper mode 映射、截断 GC_WHISPER 拒绝逻辑,并将 live ChatUI 的普通/GM whisper 接入独立会话和 ignore 判断;基础网络与静态尾标测试存在,但窗口通知、公共提示/过滤/表情/颜色和 Godot 字体布局差异使合同保持 PARTIAL。", "fingerprints": { "reference": "2b70c64e18344d4f465354239899289d5df2f7b9c38e165c66d553f790e2859e", "implementation": "cd02d44596b377534a4200e0ef7002b3c6de8e7c328fbdd96afc7e9ed8a6cb8f", "tests": "83f508464e76c7b6eee0796af45dedabeea3fbe2a2c235ad5efbad753c39842e" } }, { "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/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.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/guild_mark_test.gd", "project/guild_mark_tail_test.gd", "project/guild_creation_ui_test.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、损坏、断线、重试和角色刷新测试", "上传会徽/战旗必须验证真实用户资源、权限和服务端完成回包,避免默认占位图成为成功状态", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 40250 与当前端的组队、公会、好友/Messenger、会徽/战旗及相关 UI/native 回包链审计。live native 主路径较完整,但本地队伍/技能/公会战/Messenger/会徽 helper 仍存在直接状态旁路,真实权限拒绝、mark 独立连接和重连清理证据不足,合同保持 PARTIAL。", "fingerprints": { "reference": "c0ba3dce2da7c2bd6c1f934ebd66eedaf0d37bc0d3bf99b22e401b939a150bc4", "implementation": "0be944c07b20317ca684d3219d86421e4e8a34e3610daf587ca0c1fb7671a660", "tests": "0377c649a21cdd049d7a76e9b241ed11eba0e16ba057f7cec27f583702c2cf94" } }, { "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/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": "NOT_RUN" }, "remaining": [ "补齐 GC_EXCHANGE/GC_SAFEBOX/GC_MALL 的拒绝、乱序、断线和回包回滚测试", "将交易和仓库 UI 的 optimistic 状态改为由 native 回包重建并验证源位置", "区分并隔离本地 helper,避免将交易结算、密码、库存和金条规则当作客户端 authority", "补齐 exchange 动作门控、真实服务端物品/金币校验和商城 checkout 失败清理", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成交易、仓库、安全箱、商城和金币确认的逐分支/协议/状态审计;native 主链存在但与 40250 仍是 PARTIAL,本轮未修改实现。", "fingerprints": { "reference": "8497886941be7e41b6b1960b22fc8e07b1c0fe65c4a032b13d662191b06a0404", "implementation": "23a59fe47d21a602f5baff19828f9db8f4bd89495bcd1a14922a7476ee5906a2", "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/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" ], "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 客户端实现", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成任务状态、事件脚本、对话、HUD、地图导航和过场链的逐分支/协议/时序审计;GC_QUEST_INFO END、script-button 任务索引 sentinel、DONE close-only/DoneEvent、ESC/CANCEL answer 分支和 RUN_CINEMA 缺失资源分支已按 40250 修复,其他明确差异继续保持 PARTIAL。", "fingerprints": { "reference": "e05974ea414ca0d1bdcec3298119380ec35bfd20637a9c8e48588246347fc364", "implementation": "71343d79bbcf40cf9437adac21bc5d6cef35b568e390445f5824ad9bba35e28f", "tests": "a65c31ca4ade9fe4359f1238ed7080a5a50c3a336b75a9d9ee2a1e2f7faeb02a" } }, { "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.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", "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": "NOT_RUN" }, "remaining": [ "接通 alarm_requested 到等价的 compass/mse 资源效果,并验证方向、初次提示和 20 秒重复提示", "补齐 GC_DUNGEON/DUNGEON_RESULT 的 live 回包、重复/乱序、拒绝、断线、切图和重连测试", "确认 server cm 到 Godot 世界坐标的轴向、地图基准和到达阈值实机一致", "把 Boss、排行和具体副本 helper 与 40250 client parity 分开,禁止本地 authority 冒充客户端等价实现", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 GC_DUNGEON 目标点、结果窗口、地图事件及本地 Boss/排行/副本 helper 的逐分支、协议、资源和时序审计;目标状态算法接近参考,但 compass 副作用未接通,本地 authority 不能视为 40250 客户端实现。", "fingerprints": { "reference": "66b5e63a273e45873bf4c4b90c8b658a9f073ba425de6f275d622b3c47fbac99", "implementation": "f962e7e5f470f447024073009b195aacf877cfbcbf624aeb88cf13f9e3646dc0", "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/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": "NOT_RUN" }, "remaining": [ "补齐 CanFishing/持竿/持镐动作门控、服务端拒绝和角色消失/重连测试", "验证 GetFishingRot 的轴向、MapCoord heading 和全地图 water attribute/水高单位", "让 GC_DIG_MOTION 按 count 复现多个 dig motion,并验证目标缺失、动作完成和中断清理", "把钓鱼收益、烹饪、营火、采矿、熔炼和宝石脚本标记为 custom/server authority,禁止冒充 40250 client parity", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成钓鱼输入/水面扫描、GC_FISHING、GC_DIG_MOTION、地图水面和当前端生活技能 helper 的逐分支、协议、状态与资源审计;协议骨架和部分算法接近参考,但 live 动作/门控/清理证据不足,本地收益脚本不是 40250 客户端 authority。", "fingerprints": { "reference": "770f14f2d2bfdf025b471dbc90280f891671ea898faef52d4112684934f5bdb7", "implementation": "e9c3c25e1e61db656451f5a091ecf1365f54f01ee7a94b8098f5e625d67dded8", "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/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": "NOT_RUN" }, "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 冒充等价实现", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 mount_vnum/GC_MOUNT、骑乘动作/攻击门、变身和当前端宠物/马匹 helper 的逐分支、协议、资源和时序审计;网络字段与部分门控存在,但实际坐骑模型挂接和变身 native 闭环缺失,本地宠物/成长/技能规则不是 40250 客户端 authority。", "fingerprints": { "reference": "5ef243a1ff134dfc8afff02136d104daf0a0ac12eca781a1c476b53f7da9ff19", "implementation": "e69e964433af7177390e558ef32087425386bf6923a9408d457c48644c4fc6ad", "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" ], "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": "b99b2b4f028e0e5d107b3b1726f3985f08cf08fa46497aa14bd3046d52f4008f", "implementation": "2fedab89ead277ee74f94bda5ee2e5bdc4810fe3a2897f250e5911c0be81df04", "tests": "74ebcaa45ad2a714c654a1e76045df3594d858b3c83f265789e39cf6d8bba1e1" } }, { "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": "49b3c3d00945f76e589ca5e99bdf4077edde0885da1a5eadc4a1d5659dfff3c9", "implementation": "1e7106b1936bf6ac79849544cc8dd473bb0a421b3666d98e99721f0f84fc4825", "tests": "cb6e83d8a0c3e1696d6638eb582a6c95f48fc3c19e3fbfb9c285ba5906f3aa52" } }, { "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": "8c4f137698ddf9d9347d11e257673bfb92c30afd72f1000174355ef051bbe698", "implementation": "0492040f92b685f42e6d553cb50bdf6e67ff22bf757a886f22b9ee230df6912b", "tests": "a2a38be2cfb31518ed7f30aab7a2aeb324b71dce353271fa4ea1f330d21195f3" } }, { "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/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", "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", "增加重复加载、快速切图、暂停恢复、资源缺失和环境/粒子清理测试", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成 .msenv、天空/云、双方向光、fog、天气粒子、服务器 xmas_snow 命令和正式场景过滤的逐分支审计;命令链与四张排除地图已有回归,但 SnowEnvironment 池、DayMode、filter/lens flare/wind、线性 fog 仍与 40250 不一致或缺失,故保持 PARTIAL。", "fingerprints": { "reference": "1cffc55eb356a522049e4f7bc36595ea968ad1e0ccd7780486064a3e250cc36b", "implementation": "81e6a81489baf9f90e830458212910db6feaa713508d173236d2d255446aae53", "tests": "fa1ee00144332c33a93da2f94371a0b3602c46271356aa78b243d782aaa71c65" } }, { "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": "b48b2a0d06fb8cd312626fbd8c91381ff2f385744a32d15a771c83a96c7d687f", "implementation": "d67243d6e4c881a79c37af554eeaeca5374c3a73caf7d6e96f8e8abdcabce9bf", "tests": "eb158440ac1fcfce57259aad60a090493d2cebaeab7e380c3513cb34563a90fd" } }, { "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", "../../Client/Eternexus/root/uitaskbar.py", "../../Client/Eternexus/root/uicharacter.py", "../../Client/Eternexus/root/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": "837dea4d51a3f13d195eae67568bdc4788e62d09cf54bfee010807d1d3bc0941", "implementation": "a07fb29e8e2686640bd6cf699ea904e14069e1368585f83cb916cb83e6114b00", "tests": "fd53e0750b11c9d06bfc1613b53ed864f712732719c2c495a19b30fed6663c9c" } }, { "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": "c7172ae239ffed8543153c95496d53d8c92dcd6b94d7addacd324f7dbdf61ee7", "implementation": "313cb99fea6b2d3c7cdec918aad2c328d81b42fd75ee582bf301241b6507621d", "tests": "40416fb46b72679bfba5de491b7945b0cf95d08f524c9786589625f3641e6da3" } }, { "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/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、实例池耗尽、远端动作音效和完整销毁顺序", "验证流式地图快速切换、环境声来源删除和退出时无悬挂播放器", "2026-09-22 删除了只被自身测试引用的 *_system.gd 及其 test_*_parity.gd;原来引用它们的实现/证据不再成立,需按 40250 源码单元重新移植" ], "notes": "已完成首轮逐分支审计;基础音量/监听器/环境声/动作音效路径存在,后台 BGM/SFX 同时静音恢复与退出 Audio 缓存清理已有回归,但 Miles 实例池和 BGM 逐帧状态机仍不等价。", "fingerprints": { "reference": "1a8a9232bcc63724814d15df99f211ddfe67a28fcb2052bb4c84ad79373bdfef", "implementation": "f4c3fed1f7cbf08c1032c536927a689a9a1b71b1c431de2afb3c0041ee577315", "tests": "96a0171f670842fc52b665ae9fcae3cb30ef0046ef147836ca7f586e21646aab" } }, { "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" ], "last_test_result": "PARTIAL: focus activation/audio save-restore, AppFlow, DDS, editor validation and macOS export passed; Windows export/window/CRC/crash paths absent", "commands": [ "godot --headless --path project --editor --quit", "godot --headless --path project --export-release macOS /tmp/mtgodot-platform-audit.app" ] }, "remaining": [ "增加 Windows 导出预设、GDExtension 构建和整包启动/退出验证", "补窗口 resize/全屏/关闭/IME/native input 的平台测试;焦点激活音量路径已有回归", "补资源版本/CRC/缺包/坏包/路径大小写门禁", "补异常处理、crash report、正常退出和 native 资源释放证据" ], "notes": "已完成首轮逐分支审计;AppFlow、AssetPack、焦点激活音量保存恢复和 macOS/Android 适配存在,但 Windows 原生窗口/导出、全屏与 resize、CRC 更新检查、Eter 异常处理和完整发布门禁仍缺失。", "fingerprints": { "reference": "eadc7263dd115a86e31792b5ca4b90175287aef13f3c47e0aa7870081868a8bb", "implementation": "84417dac55504e5f626731fd2d3c78decec8808187fa70b427fdf83f66adf040", "tests": "4d5d05a6a2b8c7c40d273ed26615013c3f3cfc0dd2dc15d947c9894bcb961ad3" } }, { "id": "extension.current_only_features", "title": "没有 40250 参考实现的新增玩法和平台扩展", "subsystem": "extension", "priority": "P3", "status": "EXCLUDED", "reference": { "files": [], "symbols": [] }, "implementation": { "files": [], "symbols": [] }, "equivalence": {}, "platform_adaptations": [], "evidence": { "contract": "audit/contracts/function-inventory.md", "tests": [], "last_test_result": "NOT_RUN" }, "remaining": [], "notes": "2026-09-22 所列当前端独有功能(*_system.gd,仅被自身测试引用)已全部删除;按 SKILL.md,没有 40250 对应的逻辑属于缺陷,不再在此登记新条目。", "exclusion_reason": "在指定 40250 ClientVS22 source 中没有可达对应行为;记录该当前端扩展以防止漏审,但不计入 40250 等价率。", "fingerprints": { "reference": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "implementation": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } } ] }