CInstanceBase::SetMoveSpeed only guards moving_speed > 1100 -> 0; the local player's set_server_speed() was additionally floor/ceil-clamping the scale to an arbitrary [0.25, 3.0] band with no basis in the reference or in this project's already-fixed remote-entity equivalent (EntityStore::motion_move_speed). A heavy haste stack or slow debuff on the local player would silently diverge from what everyone else sees. Removed the extra clamp; kept the moving_speed<=0 early-return guard against pre-spawn/malformed values. audit: movement.keyboard.motion/server-speed-scale-clamp closed with reference citation, regression test, and full related-suite pass; contract stays PARTIAL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4642 lines
220 KiB
JSON
4642 lines
220 KiB
JSON
{
|
||
"schema_version": 1,
|
||
"reference_root": "../40250/Server Client TMP4/ClientVS22/source",
|
||
"contracts": [
|
||
{
|
||
"id": "network.login.phase_flow",
|
||
"title": "登录、握手、角色选择和进入游戏阶段流转",
|
||
"subsystem": "network",
|
||
"priority": "P0",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/AccountConnector.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseLogin.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseHandShake.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseSelect.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseLoading.cpp",
|
||
"UserInterface/PythonNetworkStream.cpp",
|
||
"EterLib/NetStream.cpp"
|
||
],
|
||
"symbols": [
|
||
"CAccountConnector::Connect",
|
||
"CAccountConnector::__AuthState_RecvPhase",
|
||
"CAccountConnector::OnConnectFailure",
|
||
"CPythonNetworkStream::ConnectGameServer",
|
||
"CPythonNetworkStream::RecvPhasePacket",
|
||
"CPythonNetworkStream::SetHandShakePhase",
|
||
"CPythonNetworkStream::SetLoginPhase",
|
||
"CPythonNetworkStream::SetSelectPhase",
|
||
"CPythonNetworkStream::SetLoadingPhase",
|
||
"CPythonNetworkStream::SendEnterGame",
|
||
"CPythonNetworkStream::SetGamePhase",
|
||
"CPythonNetworkStream::SendLoginPacket",
|
||
"CPythonNetworkStream::SendLoginPacketNew",
|
||
"CPythonNetworkStream::OnConnectFailure"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/app_flow.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"extension/src/net/classic/classic_stream.cpp",
|
||
"extension/src/net/classic/classic_stream.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/classic_parser.h",
|
||
"extension/src/net/auth_client.h",
|
||
"project/ui/loading_screen.gd",
|
||
"project/ui/reconnect_ui.gd",
|
||
"project/disconnect_world_reset_test.gd",
|
||
"extension/src/net/text_codec.h",
|
||
"extension/src/net/text_codec.cpp",
|
||
"extension/src/net/classic/wire_classic.h",
|
||
"extension/tests/net_text_codec_test.cpp",
|
||
"extension/tools/net_classic_e2e.cpp"
|
||
],
|
||
"symbols": [
|
||
"AppFlow::_do_connect",
|
||
"AppFlow::_enter_character",
|
||
"M2Client::connect_to_server",
|
||
"M2Client::retry_login",
|
||
"M2Client::disconnect_from_server",
|
||
"M2Client::pump_auth",
|
||
"M2Client::pump_classic",
|
||
"M2Client::pump_game",
|
||
"M2Client::reconnect",
|
||
"ClassicSession::connect",
|
||
"ClassicSession::retry_login",
|
||
"ClassicSession::connect_with_login_key",
|
||
"ClassicSession::connect_direct_enter",
|
||
"ClassicSession::on_auth_phase",
|
||
"ClassicSession::on_auth_packet",
|
||
"ClassicSession::on_phase",
|
||
"ClassicSession::on_packet",
|
||
"ClassicSession::send_auth_login",
|
||
"ClassicSession::send_login",
|
||
"ClassicSession::send_login_by_key",
|
||
"ClassicSession::send_select_char",
|
||
"ClassicSession::enter_game",
|
||
"ClassicStream::process",
|
||
"ClassicStream::handle_control",
|
||
"ClassicStream::dispatch",
|
||
"ClassicParser::on_gc",
|
||
"mtnet::to_wire",
|
||
"mtnet::from_wire_str",
|
||
"mtnet::wire_text_fits",
|
||
"AuthClient::on_phase",
|
||
"AuthClient::on_packet"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [
|
||
{
|
||
"reference": "CPythonNetworkStream/AccountConnector phase callbacks and EterLib socket pump",
|
||
"implementation": "ClassicSession/ClassicStream plus M2Client Godot signals",
|
||
"invariant": "auth key, selected slot, phase order, packet side effects and failure cleanup remain equivalent",
|
||
"tests": [
|
||
"build/extension/net_classic_session_test",
|
||
"build/extension/net_loopback_test"
|
||
]
|
||
}
|
||
],
|
||
"evidence": {
|
||
"contract": "audit/contracts/network.login.phase-flow.md",
|
||
"tests": [
|
||
"project/live_smoke_test.gd",
|
||
"project/app_flow_lifecycle_test.gd",
|
||
"project/app_flow_lifecycle_test.gd (login failure UI owner)",
|
||
"build/extension/net_classic_session_test",
|
||
"build/extension/net_classic_stream_test",
|
||
"build/extension/net_loopback_test",
|
||
"project/p10_test.gd",
|
||
"project/disconnect_world_reset_test.gd",
|
||
"build/extension/net_text_codec_test",
|
||
"ctest --test-dir build --output-on-failure -j2",
|
||
"build/extension/net_classic_e2e <redacted-live-account>",
|
||
"build/extension/net_classic_e2e <redacted-live-account-invalid-password> (expected login callback exit 5)",
|
||
"build/extension/net_classic_e2e <redacted-live-account-unavailable-game-port> (expected connect-failed callback exit 5)",
|
||
"build-debug/extension/net_classic_e2e <redacted-live-account> -> configured game channel (PASS: auth -> four slots -> select -> loading -> PHASE_GAME)",
|
||
"build-debug/extension/net_classic_e2e <redacted-live-account> 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 <redacted-live-account> -> 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 <redacted-live-account> -> 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 <redacted-live-account-invalid-password> -> configured game channel (PASS as expected probe result: WRONGPWD callback, exit 5)",
|
||
"build-debug/extension/net_classic_e2e <redacted-live-account> -> unavailable game channel (PASS as expected probe result: connect-failed callback, exit 5)",
|
||
"net_classic_session_test (auth peer close -> Offline owner)",
|
||
"net_classic_session_test (HybridCrypt deployment guard and phase-owner retention)",
|
||
"project/app_flow_lifecycle_test.gd (PHASE_CLOSE reconnect owner and phase-only LoginPhase fallback)",
|
||
"project/app_flow_lifecycle_test.gd (explicit PHASE_CLOSE does not synthesize transport disconnected)",
|
||
"net_classic_session_test (Loading PHASE_CLOSE leave owner and world cleanup)",
|
||
"net_classic_session_test (PHASE_CLOSE clears the old transport and does not send CG_LOGIN on the closed stream)",
|
||
"project/app_flow_lifecycle_test.gd (GC_WARP and fast reconnect failure retry-owner invariant)",
|
||
"project/app_flow_lifecycle_test.gd (empty reconnect list returns to SELECT owner without phantom select)",
|
||
"project/app_flow_lifecycle_test.gd (pre-game LoginPhase transition replaces SELECT owner)",
|
||
"project/app_flow_lifecycle_test.gd (LoginPhase retry uses retry_login owner without full connect teardown)",
|
||
"net_classic_session_test (LoginPhase retry replaces auth transport and sends one new CG_LOGIN3)",
|
||
"net_classic_session_test (game peer-close retains the same session retry owner, replaces auth transport, and sends one new CG_LOGIN3 with the new password)",
|
||
"net_classic_session_test (GC_AUTH_SUCCESS clears a pipelined stale PHASE_AUTH without duplicate CG_LOGIN3)",
|
||
"net_classic_session_test pre-fix GC_AUTH_SUCCESS result authority (FAIL: result=1 with login_key=0 was reported as BESAMEKEY; 1 check)",
|
||
"net_classic_session_test post-fix GC_AUTH_SUCCESS result authority (PASS: only result/bResult selects rejection; auth transport still cleans up for result=1/login_key=0)",
|
||
"net_classic_session_test pre-fix PHASE_HANDSHAKE re-entry (FAIL: game/auth owners ignored the phase and game world leave cleanup; 3 checks)",
|
||
"net_classic_session_test post-fix PHASE_HANDSHAKE re-entry (PASS: game/auth owners return to HandShake, Game world is cleared before on_phase_leave)",
|
||
"net_classic_session_test (LoginPhase returns after GC_LOGIN_FAILURE and defers a pipelined PHASE_LOGIN to the next tick)",
|
||
"net_classic_session_test (synchronous auth connect failure returns to Offline and keeps the LoginPhase retry owner)",
|
||
"project/auth_connect_bridge_test.gd (initial and retry auth connect failure use connect_failed, retain a non-terminal LoginPhase owner, and do not emit login_failed)",
|
||
"project/auth_peer_close_bridge_test.gd (initial and retry auth peer close retain a non-terminal LoginPhase owner without login_failed or connect_failed)",
|
||
"net_classic_session_test (DirectEnter game connect failure routes through ClosePhase without credential login failure)",
|
||
"net_classic_session_test (ordinary auth-to-game connect failure uses the distinct connect-failure callback)",
|
||
"project/app_flow_lifecycle_test.gd (connect_failed keeps LoginPhase UI and status separate from login_failed)",
|
||
"net_classic_session_test (DirectEnter PHASE_SELECT emits CG_PLAYER_SELECT before Loading)",
|
||
"project/app_flow_lifecycle_test.gd and project/p10_test.gd (peer-close/phase-only LoginPhase owner releases GameScene and does not retain ReconnectUI)",
|
||
"redacted Godot live smoke with MT_SMOKE_PHASE_COMMAND=/logout (PASS functional report: PHASE_GAME -> peer close -> LoginPhase owner; outer log gate retains unrelated shader leak)",
|
||
"redacted Godot live smoke with MT_SMOKE_PHASE_COMMAND=/logout and MT_NET_TRACE=1 (PASS functional report; no client-observable phase=0/GC_PHASE(PHASE_CLOSE), server fixture produced peer close only)",
|
||
"net_classic_session_test pre-fix auth-success handoff order (FAIL: game transport must start before auth transport clear; 1 check)",
|
||
"net_classic_session_test post-fix auth-success handoff order (PASS: game:connecting -> auth:offline on loopback transports)",
|
||
"net_classic_session_test pre-fix game-login retry process boundary (FAIL: replacement PHASE_LOGIN was consumed in the auth-success frame; 1 check)",
|
||
"net_classic_session_test post-fix game-login retry process boundary (PASS: replacement game transport is processed on the following pump)",
|
||
"net_text_codec_test (PASS: 龙驹简体中文免费版 is 27 UTF-8 bytes but 18 GB2312 bytes and round-trips through the 24-byte field)",
|
||
"project/char_create_delete_test.gd (PASS: CharSelectScreen does not reject the GB2312-fitting Chinese name before native conversion)",
|
||
"net_classic_session_test pre-fix Loading EnterGame event boundary (FAIL: GC_MAIN_CHARACTER did not trigger CG_ENTERGAME when the compatibility timer was disabled; 1 check)",
|
||
"net_classic_session_test post-fix Loading EnterGame event boundary (PASS: GC_MAIN_CHARACTER sends CG_CLIENT_VERSION2 then one sequenced CG_ENTERGAME; duplicate main-character packets remain idempotent)",
|
||
"net_classic_session_test pre-fix DirectEnter failure login-mode reset (FAIL: DirectEnter connect failure retained the stale slot through replacement PHASE_LOGIN; 1 check)",
|
||
"net_classic_session_test post-fix DirectEnter failure login-mode reset (PASS: ClosePhase owner clears DirectEnter and ordinary replacement PHASE_LOGIN clears the stale slot)",
|
||
"net_classic_session_test pre-fix PHASE_CLOSE LoginWindow retry owner (FAIL: PHASE_CLOSE did not retain a retry owner for replacement auth transport; 1 check)",
|
||
"net_classic_session_test post-fix PHASE_CLOSE LoginWindow retry owner (PASS: same session replaces auth transport and sends one new CG_LOGIN3 after PHASE_AUTH)",
|
||
"net_classic_session_test pre-fix auth PHASE_CLOSE silent owner (FAIL: auth PHASE_CLOSE was reported as game phase closure/auth server closed phase; 1 check)",
|
||
"net_classic_session_test post-fix auth PHASE_CLOSE silent owner (PASS: auth PHASE_CLOSE keeps AUTH owner without phase_closed or synthetic error)",
|
||
"40250 PythonNetworkStreamPhaseSelect.cpp reference inspection (PASS: SetSelectPhase runs the previous phase-leave owner before installing SelectPhase)",
|
||
"net_classic_session_test pre-fix PHASE_SELECT re-entry owner (FAIL: Game -> Select retained the old world and skipped phase-leave notification; 2 checks)",
|
||
"net_classic_session_test post-fix PHASE_SELECT re-entry owner (PASS: Game -> Select clears the old world and notifies the host before installing SelectPhase)",
|
||
"40250 PythonNetworkStream.cpp reference inspection (PASS: RecvPhasePacket(PHASE_DEAD) is an explicit no-op)",
|
||
"net_classic_session_test pre-fix PHASE_DEAD no-op time-sync owner (FAIL: PHASE_DEAD enabled time-sync and changed the next handshake response; 1 check)",
|
||
"net_classic_session_test pre-fix PHASE_DEAD close-owner preservation (FAIL: PHASE_DEAD cleared the explicit PHASE_CLOSE owner marker; 1 check)",
|
||
"net_classic_session_test post-fix PHASE_DEAD no-op time-sync owner (PASS: PHASE_DEAD keeps the plain handshake owner and preserves the explicit close marker)"
|
||
],
|
||
"last_test_result": "PASS"
|
||
},
|
||
"remaining": [
|
||
"network.login.phase_flow/auth-game-phase-matrix/live-package-order:真实服务端成功认证、四槽角色列表、选角、Loading 到 PHASE_GAME、错误密码 WRONGPWD 回调、game connect failure 和一次建立连接后的 peer close 已验证;认证成功后 game transport 先于 auth transport 清理的 40250 handoff 顺序已有 loopback 修复前失败/修复后通过证据;/logout 探针确认当前服务端 build 只产生 peer close,未产生客户端可观察的 GC_PHASE(PHASE_CLOSE),因此服务器主动 PHASE_CLOSE、空角色、完整服务器包序矩阵和跨阶段 retry 仍缺 live fixture;半包、重复 phase、PHASE_CLOSE 旧 world 和 PHASE_CLOSE/peer-close 不发送旧连接登录包的本地测试已覆盖",
|
||
"network.login.phase_flow/auth-game-phase-matrix/empty-role-live-fixture:本地全空 LOGIN_SUCCESS4/PHASE_SELECT 和无自动选人已有回归;net_classic_e2e 已增加 MT_CLASSIC_EXPECT_EMPTY=1,当前 live 账号只有部分空槽,正向全空账号证据仍缺",
|
||
"network.login.phase_flow/login-failure-retry-cleanup:本地 auth peer close -> Offline、auth peer-close bridge 保留非终态 LoginPhase owner、pre-game/in-game peer close、旧 world 清理、游戏 peer-close 后同一 session 替换认证 transport 并用新密码发送 CG_LOGIN3、DirectEnter/普通 LoginPhase game connect failure owner、认证 TCP connect failure -> Offline、认证失败后的同 session retry、认证成功按 bResult 而非 ticket 值判定、认证 connect_failed bridge、登录表单 retry 只替换认证 transport、半包和重复 phase、普通 Select/Game -> Login 的角色列表/world 清理、Game -> Select 的旧 world 清理和 phase-leave 通知、PHASE_DEAD 保持空操作且不改变 time-sync owner、认证/游戏 PHASE_HANDSHAKE 重入 owner、认证 PHASE_CLOSE 保持 AUTH owner 且不设置游戏 phase_closed、SELECT/Loading 的 phase-only LoginPhase owner、LOGIN 阶段失败保留原 LoginPhase UI、PHASE_AUTH 前忽略提前到达的 GC_LOGIN_FAILURE、认证未知应用头保留 AUTH owner、active Loading/Game 未知头按 RecvErrorPacket 清理缓冲、游戏态 PHASE_CLOSE/peer-close 后 AppFlow 回唯一 LoginPhase owner、显式 PHASE_CLOSE 不再伪造 transport disconnected、显式 PHASE_CLOSE 后同一 session 替换认证 transport、Loading PHASE_CLOSE leave owner/world 清理、GC_WARP/快速重连同步连接失败后的 retry owner,以及空重连角色列表回到 SELECT 且不发送过期槽位已有本地回归;真实 Windows LoginWindow.OnConnectFailure、认证其它可选/静默分支、跨阶段失败后的完整 UI retry/真实服务端重试包序、真实重复进入包序、DirectEnter/GC_WARP 失败回退包序和服务器主动 PHASE_CLOSE live 包序仍需测试;40250 的 m_dwChangingPhaseTime 尚未发现实际 enforced check",
|
||
"network.login.phase_flow/ticket-slot-warp-order:本地回归和实时探针已证明 login key、selected slot、DirectEnter、GC_WARP 的 replacement CG_LOGIN2/CG_PLAYER_SELECT 包序,新连接到达 PHASE_GAME 前不会继承旧的在线标记,且 DirectEnter/GC_WARP 失败回到 LoginPhase 时会清除 DirectEnter 模式;仍缺失败后的真实 GC_WARP 回退包序",
|
||
"network.login.phase_flow/optional-security-branches:Matrix、Passpod、Panama、HybridCrypt、HS/XTrap 等完整副作用仍未实现;静态/动态可选包已增加显式 deployment guard 和 optional_security_required 信号,不可静默断开",
|
||
"network.login.phase_flow/real-server-sequence:真实 Ubuntu 40250 服务端的失败账号、DirectEnter、一次成功 GC_WARP 到 PHASE_GAME 和 /logout 后建立连接的 peer close 已验证;服务端源码存在 SetPhase(PHASE_CLOSE) 状态链,但当前 build 的 live trace 没有客户端可观察 phase=0/GC_PHASE(PHASE_CLOSE),空角色、真实服务器主动 PHASE_CLOSE 包序和完整重试包序仍缺 fixture"
|
||
],
|
||
"notes": "按 40250 SetLoadingPhase/SetGamePhase 修复 Loading 遮罩阶段归属和无 SceneTree 选人确认吞包;按 ClearLoginInfo 修复 CG_LOGIN/CG_LOGIN3 成功发送后的密码生命周期;auth-game-phase-matrix 修复 PHASE_CLOSE 回 LoginPhase、Loading/Game phase-leave world 清理、宿主 world_reset 通知、重复 phase handler、auth/game 登录拒绝保留 phase 和登录失败回调;本轮 auth-success handoff 按 CAccountConnector::__AuthState_RecvAuthSuccess -> SetLoginKey -> MainStream.Connect -> AccountConnector.Disconnect 固定为 game transport 先连接、auth transport 后清理,并以 loopback 失败前/后顺序回归;login-failure-retry-cleanup 现已补齐认证端 peer close -> Offline owner、认证 peer-close bridge 保留非终态 LoginPhase owner、认证 PHASE_CLOSE 静默保留 AUTH owner、游戏 peer close 前置通知、旧 world 清理、DirectEnter/普通 LoginPhase game connect failure 的独立 owner、认证 connect_failed bridge、显式重试请求保护、普通 Select/Game -> Login 的旧角色/world 清理、SELECT/Loading 的 phase-only LoginPhase owner、LOGIN 阶段失败保留原 LoginPhase UI、LoginPhase retry 只替换认证 transport 且不触发重复全局 world_reset、PHASE_AUTH 前登录包的阶段隔离、active Loading/Game 未知头按 RecvErrorPacket 清理缓冲并保留 phase owner、游戏态 PHASE_CLOSE/peer-close 后 AppFlow 释放 GameScene 并切换为唯一 LoginPhase owner、显式 PHASE_CLOSE 不再伪造 transport disconnected、Loading PHASE_CLOSE leave owner/world 清理、GC_WARP/快速重连同步连接失败后的 retry owner,以及空重连角色列表回到 SELECT 且不发送过期槽位;同步增加 ClassicSession 析构前 stream 回调解绑,避免最终 Offline 通知写入已析构字段;本轮 legacy-text-wire-encoding 固定 UTF-8 内部与 GB2312/兼容 wire 边界,修正 24-byte 角色名容量和 GCLoginSuccess4 329-byte 布局;本轮 live-package-order 让实时探针观察应用层登录失败和 game remote-disconnect 回调,不把 40250 的失败回调误报为 timeout;本轮 ticket-slot-warp-order 修复 GC_WARP 复用 session 时继承旧 PHASE_GAME 在线标记,并用回归测试核对 DirectEnter/GC_WARP 的 CG_LOGIN2 包序和 login key;本轮 direct-enter-loading-select-packet 按 40250 introloading.LoadingWindow.Open 的实际调用链补发 DirectEnter 的 CG_PLAYER_SELECT,实时探针已到达 PHASE_GAME;本轮 gc-warp-live-package-order 新增 e2e/Godot live 验证入口,确认真实 GC_WARP 的 world_reset、replacement CG_LOGIN2/CG_PLAYER_SELECT、Loading 和第二次 PHASE_GAME 成功,未发现生产连接逻辑差异;本轮 optional-security-branches 为静态可选包和 HybridCrypt 动态包增加显式 deployment guard/optional_security_required,保持 phase owner 但不伪装成已实现 vendor runtime;本轮 peer-close-login-owner 依据真实 /logout peer-close 证据,移除 AppFlow 的自动重连遮罩/旧 GameScene 保留,回归 LoginPhase owner;本轮 phase-close-server-fixture 分离显式 PHASE_CLOSE 与 OnRemoteDisconnect,不再由 native bridge 发出伪造的 disconnected 信号;本轮 empty-role-live-fixture 为真实空角色包序增加 MT_CLASSIC_EXPECT_EMPTY=1 探针模式,当前仍缺全空账号正向 live 证据;本轮 login-form-retry-owner 按 40250 LoginWindow.Connect/AccountConnector.Connect 增加 LoginPhase retry owner 和只替换认证 transport 的 native/UI 回归;本轮 game-connect-immediate-failure-owner 按 CPythonNetworkStream::OnConnectFailure 分离普通 LoginWindow.OnConnectFailure 与 DirectEnter ClosePhase,并新增 M2Client connect_failed signal,GC_WARP 失败不再伪造 disconnected;本轮 auth-connect-bridge-owner 按 CAccountConnector::OnConnectFailure -> Offline -> LoginWindow 分离认证连接失败和凭据失败,保留 ClassicSession 供 retry_login 复用,并以 auth_connect_bridge_test.gd 固定修复前失败/修复后通过证据;本轮 auth-peer-close-bridge-owner 按 CAccountConnector::OnRemoteDisconnect -> Offline 让认证 peer close 不再伪造 login_failed/connect_failed,并以 auth_peer_close_bridge_test.gd 固定旧/新认证 transport 的回归证据。ClassicStream 的 TCP connect deadline、半包、本地 loopback、文本编码回归、真实成功包序、真实失败回调、DirectEnter CG_PLAYER_SELECT 到 PHASE_GAME、真实 GC_WARP 成功 round trip、auth-peer-close owner、auth-peer-close bridge、登录 UI owner、PHASE_CLOSE 旧 world owner、跨阶段 LoginPhase owner、显式 PHASE_CLOSE transport-owner 分离、空角色本地 SELECT owner、auth handshake owner 隔离、认证 PHASE_CLOSE 静默 owner、Loading unknown-header owner、HybridCrypt guard、Loading PHASE_CLOSE leave owner、warp connect failure retry owner、空重连 SELECT owner、pre-game LoginPhase owner、本轮 peer-close LoginPhase owner、login-form-retry-owner、game-connect-immediate-failure-owner 和 auth-connect-bridge-owner 已有本地证据;空角色全空 live fixture、服务器主动 PHASE_CLOSE live 包序、认证其它完整副作用、跨阶段完整 UI retry/真实重复进入包序、GC_WARP 失败回退包序、真实 Windows LoginWindow.OnConnectFailure 和可选安全 runtime 仍使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "79759dc3fcc9e1a4d9b779e53484b1d3abfb2b822e26d1c45d7364e9dba5f7a3",
|
||
"implementation": "be1421c9c311c980829da8db8edf39ca930661fca905813b29549598bd457c17",
|
||
"tests": "d6d994c08fabd5100f5d9f84e32948ff16aaea4ec2f7b4b8407ff7f2136c9b17"
|
||
}
|
||
},
|
||
{
|
||
"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 根运动和当前连续速度/米制推进的时序差异仍未闭合",
|
||
"OnMove/OnMoving/OnWaiting/OnStop 与当前每帧 signal、NetPlay 节流的事件源逐项证明",
|
||
"同步、私店、受击、死亡、传送、情绪以及多种锁门叠加顺序;移动技能/普通锁定/私店/情绪的 held WASD 恢复已覆盖基础路径",
|
||
"真实角色模型/骨骼在快速反向输入时不翻转的模型级测试",
|
||
"输入事件到移动请求、停止请求和网络包的完整时间线"
|
||
],
|
||
"fingerprints": {
|
||
"reference": "20c74407914aba18dde8f76f7b86917372a56cc7333b2ca8bfeee76c39e357e4",
|
||
"implementation": "ce5cc765134cfa04f7cac39ca268cd7bec773f72ea17b3b529f276fb7226b580",
|
||
"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": "6a3545f5d1f424e1404c3dd6a66f49dfa28a105b639fce5f8009860fa50928b6",
|
||
"implementation": "2fd306a9f4b85b78fd75fbffb3512cf1939fed11a3ac11a29f5808d890936565",
|
||
"tests": "f066c45415ecd31e61da172c633c1fd7d29f89a8d6a8c5a7a13ca2f47b946af8"
|
||
}
|
||
},
|
||
{
|
||
"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": "e390db94ec7e4c5d0d6518beff0319fe512af840ebe31d07d6136d48dd21687e",
|
||
"implementation": "b46796eab17505be66bb99b605f082d781fbda00b06bde98297dd9cd6faea565",
|
||
"tests": "ec3c2c7f212a4d0af4da04590dd61ebefc8e200258877c9cdf45195f8913b107"
|
||
}
|
||
},
|
||
{
|
||
"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": "7dd24059f8bdd75e76798702c0b2e951452232ab65ce20eb91ad155312087026",
|
||
"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": "7538cd86aa2937e6545ddf2d16bd75eba40cefcd4c9a2aa87b8e367906bfb3a5",
|
||
"implementation": "0845a372bd9f71cf3b2537dd537b85ff7c18c6af6a0efe81c67c0c646e1b15ca",
|
||
"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": "19428229f676c622ba55bf9b59d788730fc0751b9cf10ecf3006fe5b88df66e4",
|
||
"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": "70235b4c609e89c03f033249fcd53dd4e775f579f4fae8c9cc86cc1dad3a77d3",
|
||
"implementation": "0be8e78eb0a0f6da7e1af10a3aa77fa43e93915fefc1bd7fc60c117174a57404",
|
||
"tests": "b45313ce3f4ab491face17e762d6715ca8d2d802175eb28c37ab138be844ed46"
|
||
}
|
||
},
|
||
{
|
||
"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": "40701de0c80d0226ca751b34ef81a6f96f2cb005f8012a5c1f7ed5148ec5e4d6",
|
||
"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": "3797fce63b45cc47556f83c588699d6bf04000ff1bf821fa3b568b20006fac6c",
|
||
"implementation": "de467a517c36ed61f0fd0bb94a6f2df85644d57690c50e198ec6c9e5c4e8ddd0",
|
||
"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",
|
||
"MT_PROTOCOL=classic godot --headless --path project --script channel_status_test.gd",
|
||
"extension/tests/net_loopback_test.cpp"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"补齐 auth failure、remote close 和 channel status 完整矩阵",
|
||
"证明 count 边界、fallback probe 和恢复顺序",
|
||
"增加重试上限、窗口销毁和 login/reconnect 竞态清理测试"
|
||
],
|
||
"notes": "本轮按 40250 修复认证密码生命周期、两套 transport 的连接 deadline/可写完成判定,以及 ChannelStatus 旧 probe 与重入 callback 清理;频道检测仍保留当前端逐频道 TCP fallback,认证失败/远端关闭/完整恢复与边界矩阵仍未闭合,合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "cbfb26c28228d50ea6af675e69f7766d06a6292574bd913e5db5d56979bedd4e",
|
||
"implementation": "c203e25d94a95cf8c342692525a4f8a2bf8d5a1885f209b52e4e884be291bf4d",
|
||
"tests": "0977a59547cbe394ab546237bd1ababe4e3069d946487b91ede25ea3e4dc5778"
|
||
}
|
||
},
|
||
{
|
||
"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": "e59399f499670d966befae34478d84bb3ae4699da942c4707c1bc059c84c4788",
|
||
"implementation": "80a2daf3ea649b43f2e882068103c04774fdd4bec021fca67f6a1b0d15f85b93",
|
||
"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": "e985648a82b4de1f2d28872b21ffc2b885622ee94f9eb0dc03aa1d6dfc4ff3b8",
|
||
"implementation": "7c308d9ca6c888fa85f23d8d3465a6f41c6d8caae1092c0fac72271a7b9a64d7",
|
||
"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/metin_spawn_event_system.gd",
|
||
"project/mob_drop_scatter.gd"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::mut_spawn_full",
|
||
"EntityStore::mut_despawn",
|
||
"EntityStore::mut_ground_add",
|
||
"EntityStore::mut_ground_del",
|
||
"EntityStore::mut_ground_owner",
|
||
"EntityStore::mut_shop_sign",
|
||
"M2Client::poll",
|
||
"NetWorld::_on_spawn",
|
||
"NetWorld::_on_info",
|
||
"NetWorld::_update_visibility",
|
||
"NetWorld::_fade_and_free",
|
||
"NetWorld::_on_despawn",
|
||
"NetWorld::_refresh_shop_sign",
|
||
"GroundItems::_on_added",
|
||
"GroundItems::_on_removed",
|
||
"GroundItems::try_pickup",
|
||
"MobView::build",
|
||
"MobView::set_anim_state"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [
|
||
"CNetworkActorManager/CPythonCharacterManager 映射为 EntityStore + M2Client + NetWorld 两层数据/场景生命周期",
|
||
"CItemData drop model 与 CPythonTextTail 映射为 ground_items 的 Godot 模型、Label3D 和 owner_tag",
|
||
"DirectX actor blend-out 映射为 Godot Tween/queue_free,必须保持数据行与可见节点分离"
|
||
],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.npc-drop-actor-spawn.md",
|
||
"tests": [
|
||
"project/net_world_vis_test.gd",
|
||
"project/entity_rules_test.gd",
|
||
"project/forest_mob_render_test.gd",
|
||
"project/mob_view_test.gd",
|
||
"project/net_world_push_test.gd",
|
||
"project/test_ground_pickup_parity.gd",
|
||
"project/mob_death_drop_test.gd",
|
||
"project/test_mob_drop_scatter_parity.gd",
|
||
"project/mob_winding_test.gd",
|
||
"project/test_metin_spawn_event_parity.gd",
|
||
"build/extension/net_entity_test",
|
||
"build/extension/net_classic_session_test",
|
||
"build/extension/net_classic_wire_test"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"修复并回归 net_world_vis_test 的 4 项可见性/裁剪失败,确认节点只建一次、离开范围只删一次",
|
||
"补齐不可见 race、未知 race/vnum、模型工厂未就绪、真实 drop model/effect/sound 和建筑碰撞的失败矩阵",
|
||
"验证 ground add/owner/del 与 CG_ITEM_PICKUP→GC_DEL/ITEM_GET/ITEM_UPDATE 的 live 包序、失败、乱序和重连清理",
|
||
"验证 GC_SHOP_SIGN 空/非空、未知 VID、重复包、私人商店 UI 和换图/断线清理",
|
||
"把本地掉落散落、活动石头和 owner 保护与 40250 client presentation 分层,不把 custom authority 当 parity"
|
||
],
|
||
"notes": "已完成 40250 actor/ground/shop-sign 调用链和当前端 parser/store/view 对照;合同保持 PARTIAL。net_world_vis_test 复现 4 项可见性生命周期失败,本轮仅登记审计结果,未修改实现。",
|
||
"fingerprints": {
|
||
"reference": "3bf9532d6c9f258fbbc3dba6864171160594f94c7cd708e056c250f88be126ff",
|
||
"implementation": "3a7d87cad0ba405495ba51e13feab294af9ebddbde2fb18b5df9c606bbe3af08",
|
||
"tests": "9783566cc5ee2f17b89509082c9fd8d9334154f9b03fb499c1616613bba3a2ab"
|
||
}
|
||
},
|
||
{
|
||
"id": "world.reset_cleanup",
|
||
"title": "换图、死亡、退出和 UI/实体/特效清理",
|
||
"subsystem": "world",
|
||
"priority": "P0",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonNetworkStream.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseLoading.cpp",
|
||
"UserInterface/PythonPlayer.cpp",
|
||
"UserInterface/PythonApplicationProcedure.cpp",
|
||
"UserInterface/PythonEventManager.cpp",
|
||
"GameLib/MapManager.cpp",
|
||
"UserInterface/NetworkActorManager.cpp",
|
||
"UserInterface/FlyingObjectManager.cpp",
|
||
"EterLib/EffectManager.cpp"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::SetLoadingPhase",
|
||
"CPythonNetworkStream::RecvWarpPacket",
|
||
"CPythonNetworkStream::__DirectEnterMode_Initialize",
|
||
"CPythonPlayer::Clear",
|
||
"CFlyingManager::DeleteAllInstances",
|
||
"CEffectManager::DeleteAllInstances",
|
||
"CNetworkActorManager::Destroy",
|
||
"CNetworkActorManager::__RemoveAllActors",
|
||
"CNetworkActorManager::__RemoveAllGroundItems",
|
||
"CMapManager::UnloadMap",
|
||
"CPythonApplication::Destroy"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/app_flow.gd",
|
||
"project/game_scene.gd",
|
||
"project/audio.gd",
|
||
"project/net_play.gd",
|
||
"extension/src/net/wire.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/m2_client.cpp",
|
||
"project/net_world.gd",
|
||
"project/fly_object.gd",
|
||
"project/ground_items.gd",
|
||
"project/bgm_director.gd"
|
||
],
|
||
"symbols": [
|
||
"M2Client::pump_classic",
|
||
"M2Client::disconnect_from_server",
|
||
"EntityStore::reset_for_map_change",
|
||
"GameScene::_on_world_reset",
|
||
"GameScene::_reload_map_for_warp",
|
||
"GameScene::_exit_tree",
|
||
"AppFlow::_exit_tree",
|
||
"Audio::shutdown",
|
||
"NetPlay::clear_for_map_change",
|
||
"NetWorld::clear_for_map_change",
|
||
"FlyObject::clear_for_map_change",
|
||
"Metin2World::unload_map",
|
||
"uninitialize_mtgodot_module"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "MAPPED",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "MAPPED",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "MAPPED",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [
|
||
"CPythonPlayer/Actor/Flying/Effect manager 映射为 EntityStore + NetPlay + NetWorld + FlyObject + Godot effect registry",
|
||
"同步删除映射为信号同步清理与 queue_free 延迟释放",
|
||
"CPythonApplication Destroy 映射为 AppFlow/GameScene/RAII 生命周期"
|
||
],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.reset-cleanup.md",
|
||
"tests": [
|
||
"extension/tests/net_entity_test.cpp",
|
||
"project/fly_reset_test.gd",
|
||
"project/screen_hp_bar_test.gd",
|
||
"project/net_world_reset_test.gd",
|
||
"project/app_flow_lifecycle_test.gd",
|
||
"project/gamescene_test.gd",
|
||
"project/rendering_scenario_test.gd",
|
||
"project/disconnect_world_reset_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"GC_WARP/重连/close/dead 的完整顺序矩阵",
|
||
"旧地图先清理还是新地图先构造的资源与引用安全证明",
|
||
"统一 generation 防止 queue_free/模型升级/旧网络回调回灌",
|
||
"真实断线重连保留 GameScene 时的重复 world_reset/旧数据清理包序",
|
||
"通用特效 manager、窗口关闭和显式 Destroy 等价测试",
|
||
"清理 test_exp_fly_parity 旧式单函数 fixture 的 3 个 ObjectDB 泄漏与 SceneTree path warning"
|
||
],
|
||
"notes": "本轮按 SetLoadingPhase、CMapManager::UnloadMap、CSoundManager::Destroy 和 CPythonApplication::Destroy 的清理边界,新增 net_world_reset_test,修复 _upgrade_queue、_chat_tails、target/hover VID、target effect 残留,并将 GameScene 换图改为旧 world 先 unload 再创建新 world;AppFlow、GameScene、Audio 与 GDExtension 退出路径清空模型/动作/NPC/UI/音频缓存,rendering_scenario 的 139/54 泄漏和 Debug 下 2 个 AudioStream 泄漏已修复。FlyInstance handler 自捕获环也已解除,fly_reset_test 的 5 个 ObjectDB 泄漏已清零;显式 disconnect 现在先 reset 活动 EntityStore、发 world_reset 再释放 transport,test_exp_fly_parity 仍有 3 个旧 fixture 警告。真实重连包序、统一 generation 和通用 EffectManager 仍使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "30cb04eed7ba1b82165b30583723a2aaf89f98d1a7a2e7e17b327bb16be10605",
|
||
"implementation": "260e6182daa7f44706bebb081eea849b0d4d58c57e5cea129c2920d715c47743",
|
||
"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": "15850029efadc3a50e52511fb45f51da5ad77e6ce0841a41a53d66fd6306d03d",
|
||
"implementation": "d2e8fc11d36e5f898a4aaea356ce4bee03d1d43b7dea7101c5c610a74adefd76",
|
||
"tests": "cdee05a904f96b260c201553915af522bd3623eed78ffb5b23d16ae8e0b2a88a"
|
||
}
|
||
},
|
||
{
|
||
"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": "2dc90fd41d26f4a61085fdd57d961ffd8c83f5b1ef4d359559c3104dcbabe801",
|
||
"implementation": "99d49c732d9d141818db12ef3b2f98f081980fac372d34692fdb50ff775d9dca",
|
||
"tests": "f3fdfa1a6d0ebed1c8fb78588e44d92fb79ff6b1059d77453d9e7cd5f962e15c"
|
||
}
|
||
},
|
||
{
|
||
"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": "f59276d75e5cdf0fc54c95fdce5ca85c794080b94ba7fdee540870273004a471",
|
||
"implementation": "01ca0b13b2db5489c1fa37dc48d555d68da096dc2d2370d9d64b2cf78c8f9fc7",
|
||
"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",
|
||
"project/socket_glow_fx_system.gd"
|
||
],
|
||
"symbols": []
|
||
},
|
||
"equivalence": {},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/combat.attack-combo-damage.md",
|
||
"tests": [
|
||
"project/combat_fx_test.gd",
|
||
"project/combat_parity_test.gd",
|
||
"project/gamescene_test.gd",
|
||
"project/weapon_trace_test.gd",
|
||
"project/weapon_trace_multi_part_parity_test.gd",
|
||
"project/weapon_trace_endpoint_parity_test.gd",
|
||
"project/weapon_trace_reach_affect_parity_test.gd",
|
||
"project/weapon_reach_scale_attack_parity_test.gd",
|
||
"project/damage_local_main_parity_test.gd",
|
||
"extension/tests/net_state_queue_test.cpp"
|
||
],
|
||
"last_test_result": "PARTIAL; local damage, multi-part WeaponTrace, bound-box/local-Z/reach-scale and attack-sweep regressions pass"
|
||
},
|
||
"remaining": [
|
||
"鼠标 press/release、空格自由攻击、弓 FLY 帧和自动攻击完整状态矩阵",
|
||
"服务端伤害/死亡/重复命中与客户端命中窗的真实包级 fixture",
|
||
"换图、断线、缺失动作资源和攻击队列清理测试",
|
||
"WeaponTrace 已按 bound-box 最大长度、局部 Z 和 reach scale 接入 PlayerView;当前仍未证明 native bone matrix 的同一 composite/rotation 坐标语义、远端/怪物覆盖、真实刀光纹理视觉和服务端动作资源",
|
||
"socket_glow_fx_system.has_trail 目前只有配置字段,没有进入武器刀光渲染链"
|
||
],
|
||
"notes": "已按 40250 isValidAttacking/HitDataContainer 删除缺失动作攻击数据、空命中窗和缺少 .msm 防御球时的本地合成命中;状态包、命中窗、去重、受击、弓箭 FLY、本地主角伤害飘字和 WeaponTrace 多部件生命周期有回归证据。WeaponTrace 目前通过 weapon_trace.gd 适配层接入,包含武器类型过滤、每个 Weapon/Shield 部件独立 trace、双轨迹时间队列、cubic spline triangle strip、alpha/texture 材质、攻击动作开关、纹理控制入口、bound-box 最大长度/局部 Z/reach scale 和换装清理;AFFECT_GEOMGYEONG 的 1.5/1.0 reach scale 已同时接入刀光与普通攻击扫掠。native 骨骼矩阵坐标语义、远端/怪物覆盖、真实视觉资源及服务端伤害闭环仍不等价。强化配置中的 has_trail 仍是独立未消费字段。",
|
||
"fingerprints": {
|
||
"reference": "3b79deef2e50cb16c6b7226e8900e4d96c82f5dbe4e409f40609b6a656586608",
|
||
"implementation": "fa633ddfb631df0d1b472ad674d558869399079d9789b54b6c6775e45b8310ac",
|
||
"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",
|
||
"project/alignment_pk_system.gd"
|
||
],
|
||
"symbols": [
|
||
"EntityRules.is_attackable_instance",
|
||
"EntityRules.can_pick_instance",
|
||
"NetPlay._is_attackable",
|
||
"NetPlay._attack_ctx",
|
||
"NetPlay._on_pick",
|
||
"NetPlay._change_target_to_picked_instance",
|
||
"NetPlay._motion_blocks_target_change",
|
||
"NetPlay._can_change_target",
|
||
"NetPlay._is_targetable_instance",
|
||
"NetPlay._reserve_process_click_actor",
|
||
"NetPlay._on_target_info",
|
||
"TargetBoard.classify",
|
||
"TargetBoard.can_view_target_hp",
|
||
"PlayerController._on_click",
|
||
"NetWorld._apply_field_updates",
|
||
"NetWorld.set_target_vid",
|
||
"NetWorld._on_despawn"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/combat.target-selection-rules.md",
|
||
"tests": [
|
||
"project/entity_rules_test.gd",
|
||
"project/target_board_test.gd",
|
||
"project/test_alignment_pk_parity.gd",
|
||
"project/test_click_target_effect_parity.gd",
|
||
"project/test_auto_combat_bot_parity.gd",
|
||
"project/playable_combat_test.gd",
|
||
"project/netplay_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"完整 .msa 事件源下的 CanChangeTarget 动作时序和缺失动作资源回退验证",
|
||
"隐身、复活隐身、视锥、门死亡、裁剪/淡出和迟到实体更新的目标生命周期矩阵",
|
||
"地图 ATTRIBUTE_BANPK 与实体 in_safe 的实时数据权威和移动/换图更新链",
|
||
"SetTarget 切换时间窗、OnTargeted/OnUntargeted、飞行目标和 CG_TARGET 顺序",
|
||
"真实服务端目标包、死亡/删除/乱序/断线重连和目标框清理"
|
||
],
|
||
"notes": "已完成目标攻击资格、目标可选中、目标框和安全区的逐分支静态审计;目标入口已统一接入 CanChangeTarget/CanPickInstance、重复目标窗口和 CG_TARGET(0) 清理,合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "818e201fffe6335de4b2c79b505d8749b7ef00d61267865861a8fefc21c5bdf5",
|
||
"implementation": "07fcaf36da0b7322ac63c5d2b3ec1001b6fff2c1810dd637c54b3fecfba430fb",
|
||
"tests": "9ad10ac749fbc12f4537fe489f592ac8e2159446c6bb1ab01855076c3b6c39ec"
|
||
}
|
||
},
|
||
{
|
||
"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": "78aa9c45552beea0f552c072bdf663049fb14d1e7ca8bd7c916bdfda7f6d8339",
|
||
"implementation": "c9ef1ee8219d239c2b1abe821e8fac5a4d562ba407cabfb4054135a8c768ab7c",
|
||
"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/combat_status_effect_system.gd",
|
||
"project/ui/hit_reaction.gd",
|
||
"project/ui/player_view.gd",
|
||
"project/ui/mob_view.gd",
|
||
"project/net_play.gd",
|
||
"project/net_world.gd",
|
||
"project/game_scene.gd",
|
||
"project/hud.gd",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/m2_client.cpp",
|
||
"project/ui/char_status_ui.gd"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::mut_affect_flags",
|
||
"EntityStore::mut_stun",
|
||
"EntityStore::mut_affect_add",
|
||
"EntityStore::mut_affect_remove",
|
||
"NetWorld::_apply_field_updates",
|
||
"NetWorld::_on_damage",
|
||
"NetPlay::_can_process_network_state",
|
||
"NetPlay::_process_attack_success",
|
||
"HitReaction.good",
|
||
"HitReaction.greate",
|
||
"PlayerView.set_affect_flags",
|
||
"MobView.set_affect_flags"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/combat.affect-status.md",
|
||
"tests": [
|
||
"project/test_combat_status_effect_parity.gd",
|
||
"project/damage_effect_test.gd",
|
||
"project/combat_fx_test.gd",
|
||
"project/test_mob_combat_reaction_parity.gd",
|
||
"project/test_combat_death_pickup_entry_parity.gd",
|
||
"project/hit_view_test.gd",
|
||
"build/extension/net_entity_test",
|
||
"build/extension/net_classic_session_test"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"隔离 combat_status_effect_system 的本地权威结算,改为服务端 affect/damage 包驱动",
|
||
"补齐 affect bit 到附着特效、图标、骨骼、stone smoke 和建筑 no-op 的逐位资源矩阵",
|
||
"统一远端 stunned/knockdown/skill-cancel 的动作和网络状态门控",
|
||
"修复 hit_view_test 前向 damage 资源/动作锁时序失败",
|
||
"补齐重复、乱序、duration、死亡、换图、重连和 VID 重用清理测试"
|
||
],
|
||
"notes": "已完成 40250 AffectFlagContainer、Actor affect/受击动作、damage/affect 协议和当前 native/GDScript 链的逐分支审计;确认本地状态结算器的数据权威错误,且 hit_view_test 有 1 项失败。合同保持 PARTIAL,本轮仅登记审计结果,未修改实现。",
|
||
"fingerprints": {
|
||
"reference": "a17de5fbf004c1c6b799300ee026c44250c7be01839e79f4e2df927c65748301",
|
||
"implementation": "c0d2d09d88eddafb7e3586b6a5894e95d3d143182f486f7572c790b9c031bd4f",
|
||
"tests": "c8c6a05be637f15e1ca812a4a59cd5f3be900104ce325e729de3690beb594d3b"
|
||
}
|
||
},
|
||
{
|
||
"id": "skill.player_ui_cooldown",
|
||
"title": "技能栏、快捷键、等级、冷却和禁止条件",
|
||
"subsystem": "skill",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonSkill.cpp",
|
||
"UserInterface/PythonPlayerSkill.cpp",
|
||
"UserInterface/PythonPlayer.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/InstanceBase.cpp",
|
||
"UserInterface/Packet.h"
|
||
],
|
||
"symbols": [
|
||
"CPythonSkill::RegisterSkillTable",
|
||
"CPythonSkill::SSkillData::GetSkillCoolTime",
|
||
"CPythonSkill::SSkillData::GetTargetCount",
|
||
"CPythonSkill::SSkillData::IsCanUseSkill",
|
||
"CPythonPlayer::ClickSkillSlot",
|
||
"CPythonPlayer::__CanUseSkill",
|
||
"CPythonPlayer::__CheckSkillUsable",
|
||
"CPythonPlayer::__UseSkill",
|
||
"CPythonPlayer::__RunCoolTime",
|
||
"CPythonPlayer::SetSkillLevel_",
|
||
"CPythonPlayer::SetSkillCoolTime",
|
||
"CPythonPlayer::EndSkillCoolTime",
|
||
"CPythonNetworkStream::RecvSkillLevelNew",
|
||
"CPythonNetworkStream::RecvSkillCoolTimeEnd",
|
||
"CInstanceBase::CanUseSkill"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/player_skill.gd",
|
||
"project/ui/skill_table.gd",
|
||
"project/ui/skill_ui.gd",
|
||
"project/ui/quickbar.gd",
|
||
"project/net_play.gd",
|
||
"project/net_client_fake.gd",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/wire.h"
|
||
],
|
||
"symbols": [
|
||
"SkillTable.load_file",
|
||
"SkillTable.cooldown_of",
|
||
"SkillTable.can_level_up_skill",
|
||
"PlayerSkill.click_skill_slot",
|
||
"PlayerSkill.can_use_skill",
|
||
"PlayerSkill.check_skill_usable",
|
||
"PlayerSkill.use_skill",
|
||
"PlayerSkill.use_guild_skill",
|
||
"Quickbar.restore_from_server",
|
||
"Quickbar._activate_slot",
|
||
"Quickbar._activate_guild_skill",
|
||
"Quickbar._skill_cooldown",
|
||
"Quickbar._on_cd_end",
|
||
"NetPlay.skill_context",
|
||
"EntityStore::mut_skill_levels",
|
||
"EntityStore::mut_skill_group",
|
||
"EntityStore::mut_quickslot_set",
|
||
"EntityStore::mut_skill_cooldown_end"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/skill.player-ui-cooldown.md",
|
||
"tests": [
|
||
"project/player_skill_test.gd",
|
||
"project/skill_test.gd",
|
||
"project/netplay_test.gd",
|
||
"project/test_skill_inventory_parity.gd",
|
||
"project/test_taskbar_quickslot_parity.gd",
|
||
"project/test_status_skill_reset_parity.gd",
|
||
"project/mobile_gesture_test.gd",
|
||
"project/playable_combat_test.gd",
|
||
"project/guild_war_skill_test.gd",
|
||
"project/test_full_skill_flow.gd",
|
||
"build/extension/net_entity_test",
|
||
"build/extension/net_classic_session_test"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"CanUseSkill 五类动作门已补齐;仍需把真实 shape/图形动作字段和拒绝副作用接入端到端回归",
|
||
"POINT_CASTING_SPEED 已暴露并按 40250 __RunCoolTime 修正;仍需真实点变更/冷却回包时序 fixture",
|
||
"补齐 skill/group/quickslot/cooldown-end 乱序、重复、断线、重连和换图测试",
|
||
"建立全部技能 ID/grade/.msk 公式/动作/目标追加和服务端 damage 顺序快照"
|
||
],
|
||
"notes": "已完成 40250 技能数据、三层施放校验、CanUseSkill、casting-speed 冷却公式、快捷栏、普通/公会技能发送入口、skill/group/cooldown 协议和 FUNC_SKILL→CG_USE_SKILL 动作/意图包序的逐分支审计;CanUseSkill 五类动作门、POINT_CASTING_SPEED 桥接和公会技独立路径已实现,但真实拒绝/乱序/跨生命周期 fixture、完整动作资源包序和服务端 damage 顺序仍使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "41123a16a7c43e570f57854c4165bc04bb2dfb875efb0bf34d9f5e6afd08e383",
|
||
"implementation": "bad2f21e5c0ea4de37508d94ed1724931696d9409db5f487a708da0bdfbd3cc7",
|
||
"tests": "0746ef57d85bea05245ba44d178e24790d28623c40559520508db7acbd7970f0"
|
||
}
|
||
},
|
||
{
|
||
"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": "1278f3e04f31a69efd71c1d5542e3d697e279575675031bdf96d30aaa0d1ff75",
|
||
"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/item_split_system.gd",
|
||
"project/ui/quickbar.gd",
|
||
"project/ui/inventory_ui.gd",
|
||
"project/ui/shop_ui.gd",
|
||
"project/net_play.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/tests/net_classic_session_test.cpp",
|
||
"project/test_consumables_parity.gd",
|
||
"project/test_item_split_parity.gd",
|
||
"project/test_taskbar_quickslot_parity.gd",
|
||
"project/test_skill_inventory_parity.gd",
|
||
"project/skill_test.gd",
|
||
"project/mobile_gesture_test.gd",
|
||
"project/complex_item_drop_test.gd",
|
||
"project/netplay_test.gd"
|
||
],
|
||
"symbols": [
|
||
"ConsumableSystem::toggle_elixir",
|
||
"ConsumableSystem::process_elixir_recovery",
|
||
"ConsumableSystem::use_dragon_god_potion",
|
||
"ConsumableSystem::use_warp_scroll",
|
||
"ItemSplitSystem::split_item",
|
||
"ItemSplitSystem::quick_sell_item",
|
||
"Quickbar::assign",
|
||
"Quickbar::_activate_slot",
|
||
"Quickbar::_clear",
|
||
"Quickbar::_swap",
|
||
"InventoryUI::move_to",
|
||
"InventoryUI::use",
|
||
"ShopUI::_send_sell",
|
||
"M2Client::quickslot_add",
|
||
"M2Client::quickslot_del",
|
||
"M2Client::quickslot_swap",
|
||
"M2Client::use_item",
|
||
"M2Client::use_item_to_item",
|
||
"M2Client::move_item",
|
||
"M2Client::shop_sell",
|
||
"ClassicSession::send_item_use",
|
||
"ClassicSession::send_item_use_to_item",
|
||
"ClassicSession::send_item_move",
|
||
"ClassicSession::send_shop_sell",
|
||
"EntityStore::mut_quickslot_set",
|
||
"EntityStore::mut_quickslot_del",
|
||
"EntityStore::mut_quickslot_swap"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/item.consumable-action.md",
|
||
"tests": [
|
||
"project/test_consumables_parity.gd",
|
||
"project/test_item_split_parity.gd",
|
||
"project/test_taskbar_quickslot_parity.gd",
|
||
"project/complex_item_drop_test.gd",
|
||
"project/netplay_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"快捷栏请求现在等待 GC_QUICKSLOT_* 后重建本地状态;仍需真实拒绝、重复、乱序和断线/换图回包 fixture",
|
||
"消耗品 ITEM_USE 后 ITEM_UPDATE/AFFECT/POINT/SPECIAL_EFFECT 的真实包序和 UI 反馈",
|
||
"堆叠拆分已统一为 CG_ITEM_MOVE 请求并等待服务器确认;快捷出售已统一为 CG_SHOP SELL/SELL2,价格、金币和物品变化仍待真实回包 fixture",
|
||
"消耗品 native action gate 与装备类型门已补齐;物品音效和 SendSequence 的资源/包序证据仍缺",
|
||
"断线、换图、重复回包、请求失败和延迟期间的状态清理/回滚",
|
||
"快捷栏乐观更新和页面取模差异已修复;quickbar 资源泄漏、完整生命周期清理和真实服务器确认 fixture 仍待补齐;complex_item_drop_test 的购买模式卖出目标判定已修复"
|
||
],
|
||
"notes": "已将拆分、快捷出售和快捷栏增删交换统一到 request→server/GC 确认路径,并修复购买模式误发出售请求;快捷栏使用、消耗品真实反馈包序、音效/sequence 和断线清理仍使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "46e51b7a21012ae531c21f8a752fde5592659081feec6d67edfa86c19f7c2082",
|
||
"implementation": "0139c7d3d92f8d812eaef51d3d8dfb70f0143f0b4660fa21cbca4c2f16284bfd",
|
||
"tests": "bc8250918b7945d30b8174a2206ad3f95f9f9fe6523217e312e9090317a03f0c"
|
||
}
|
||
},
|
||
{
|
||
"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": "18e94abecd2cbd63407ec94fe4f2b692505b0ea156e3149fcfa426920a10856b",
|
||
"implementation": "664112e5403d65b499075c104656efec9e2920da2f5dca0edec66339e9005b66",
|
||
"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/refine_effect_system.gd",
|
||
"project/item_attr_system.gd",
|
||
"project/item_enchant_system.gd",
|
||
"project/jewelry_socket_system.gd",
|
||
"project/costume_system.gd",
|
||
"project/costume_attr_transfer_system.gd",
|
||
"project/wardrobe_transmog_system.gd",
|
||
"project/ui/equip_model.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/wire_classic.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/net_bounds.h"
|
||
],
|
||
"symbols": [
|
||
"RefineUI::_on_ask",
|
||
"RefineUI::_accept",
|
||
"RefineUI::cancel_refine",
|
||
"M2Client::refine",
|
||
"ClassicSession::send_refine",
|
||
"EntityStore::mut_refine",
|
||
"ItemAttrSystem::apply_add_attribute",
|
||
"ItemAttrSystem::apply_change_attribute",
|
||
"ItemAttrSystem::apply_blessing_marble",
|
||
"ItemEnchantSystem::apply_scroll",
|
||
"JewelrySocketSystem::drill_socket",
|
||
"JewelrySocketSystem::insert_gem",
|
||
"CostumeSystem::equip_costume",
|
||
"CostumeSystem::unequip_costume",
|
||
"WardrobeTransmogSystem::apply_transmutation"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "GAP",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/item.refine-socket-costume.md",
|
||
"tests": [
|
||
"project/test_refine_parity.gd",
|
||
"project/test_refine_effect_parity.gd",
|
||
"project/test_item_attr_parity.gd",
|
||
"project/test_item_enchant_manager_parity.gd",
|
||
"project/test_jewelry_socket_parity.gd",
|
||
"project/test_costume_system_parity.gd",
|
||
"project/test_costume_attr_transfer_parity.gd",
|
||
"project/test_wardrobe_transmog_parity.gd",
|
||
"extension/tests/net_classic_session_test.cpp",
|
||
"extension/tests/net_entity_test.cpp",
|
||
"extension/tests/net_bounds_test.cpp"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"覆盖 M2Client::refine(255,255) 通过后的真实取消包和服务器回包",
|
||
"按 GC_REFINE_INFORMATION(95) 与 GC_REFINE_INFORMATION_NEW(119) 分开解析并覆盖两种包",
|
||
"将属性/镶嵌操作改为 ItemManager/use subtype + CG_ITEM_USE/ITEM_USE_TO_ITEM 请求和 GC_ITEM_UPDATE 权威回写",
|
||
"把时装穿戴、属性、过期和 bonus 接入服务端 item/affect/point 与 actor armor/weapon/hair 更新链",
|
||
"让强化等级/socket/type/subtype 驱动实际装备模型、强化特效 attach/clear,并覆盖资源缺失清理",
|
||
"补充拒绝、断线、重复/乱序回包、换图、过期和服务端回滚测试",
|
||
"将 wardrobe/transmog 自定义扩展与 40250 parity 分开标注,除非补齐对应服务端协议和角色部件同步"
|
||
],
|
||
"notes": "本轮已修复 M2Client::refine 对 40250 取消 sentinel(255,255) 的错误拦截,并补充边界/EntityStore 回归;属性、socket、时装和幻化仍绕过服务器权威回包,合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "e76ca9bb01f7b7838943820f3fe5633f68a32364c3f3ece48cbc66bf74a6a247",
|
||
"implementation": "176ad923087d84b8f11dee97d2bd198c495d91014d8b720d3bf58acd0bd26fb8",
|
||
"tests": "6bb13ce9e81271dfa30eaaab03c5cf5e801ef22965c32d4b4d38af95f6874bfb"
|
||
}
|
||
},
|
||
{
|
||
"id": "npc.dialog_shop",
|
||
"title": "NPC 交互、普通商店、个人商店和购买流程",
|
||
"subsystem": "npc",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonNonPlayer.cpp",
|
||
"UserInterface/PythonShop.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGameItem.cpp",
|
||
"UserInterface/PythonNetworkStreamModule.cpp",
|
||
"UserInterface/PythonPlayerInput.cpp",
|
||
"UserInterface/PythonPlayerInputMouse.cpp",
|
||
"UserInterface/Packet.h",
|
||
"../../Client/Eternexus/root/uishop.py",
|
||
"../../Client/Eternexus/root/uiprivateshopbuilder.py",
|
||
"../../Client/Eternexus/root/uiinventory.py",
|
||
"../../Client/Eternexus/root/playersettingmodule.py"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvShopPacket",
|
||
"CPythonNetworkStream::SendShopEndPacket",
|
||
"CPythonNetworkStream::SendShopBuyPacket",
|
||
"CPythonNetworkStream::SendShopSellPacket",
|
||
"CPythonNetworkStream::SendShopSellPacketNew",
|
||
"CPythonNetworkStream::SendOnClickPacket",
|
||
"CPythonNetworkStream::RecvShopSignPacket",
|
||
"CPythonShop::SetItemData",
|
||
"CPythonShop::BuildPrivateShop",
|
||
"PrivateShopBuilder::OnSelectEmptySlot",
|
||
"ShopDialog::Open",
|
||
"ShopDialog::SelectItemSlot",
|
||
"ShopDialog::UnselectItemSlot"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/npc_shop_system.gd",
|
||
"project/private_shop_system.gd",
|
||
"project/ui/shop_ui.gd",
|
||
"project/ui/private_shop_ui.gd",
|
||
"project/net_play.gd",
|
||
"project/net_world.gd",
|
||
"project/ui/quest_dialog.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/wire_classic.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h"
|
||
],
|
||
"symbols": [
|
||
"NpcShopSystem::buy_item",
|
||
"NpcShopSystem::sell_item",
|
||
"ShopUI::_drop_to_sell",
|
||
"ShopUI::_buy_slot",
|
||
"ShopUI::_request_sell",
|
||
"PrivateShopUI::_ok",
|
||
"NetPlay::_on_pick",
|
||
"NetWorld::_refresh_shop_sign",
|
||
"M2Client::click_npc",
|
||
"M2Client::shop_buy",
|
||
"M2Client::shop_sell",
|
||
"M2Client::open_private_shop",
|
||
"EntityStore::mut_shop_update_item",
|
||
"EntityStore::mut_shop_sign"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "GAP",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "GAP",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/npc.dialog-shop.md",
|
||
"tests": [
|
||
"project/test_npc_shop_parity.gd",
|
||
"project/test_npc_shop_flow.gd",
|
||
"project/test_npc_dialog_shop_ui_parity.gd",
|
||
"project/private_shop_ui_test.gd",
|
||
"project/test_private_shop_bundle_parity.gd",
|
||
"project/shop_rules_test.gd",
|
||
"project/p8_test.gd",
|
||
"project/complex_item_drop_test.gd",
|
||
"extension/tests/net_classic_session_test.cpp",
|
||
"extension/tests/net_entity_test.cpp"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"修复 EntityStore::mut_shop_update_item 对 40..119 全局位置的 tab 映射,并补充多货架增量更新测试",
|
||
"禁止 ShopUI 在购买模式接收拖放出售请求,并补充输入路径回归",
|
||
"按 shop VID 区分 NPC、他人个人商店和自己的个人商店,复现 40250 的按钮/关闭分支",
|
||
"把带 shop_sign 的 PC 点击路由到 SendOnClickPacket 并覆盖招牌创建、点击和 GC_END 清理",
|
||
"为买卖请求补齐动作门控、实际库存/anti flag/商店状态校验和服务器结果回写",
|
||
"移除 live 路径对硬编码 NPC 库存与本地金币/背包 helper 的权威依赖",
|
||
"补充 START/START_EX/UPDATE/ERROR/END、拒绝、售罄、断线、重连和乱序包的端到端测试",
|
||
"补充个人商店死亡/骑乘/战斗/bundle/重复源格校验和服务端拒绝清理测试"
|
||
],
|
||
"notes": "已完成 40250 与当前端的 NPC 点击、脚本对话、普通/多货架商店、买卖、个人商店招牌和摆摊提交逐分支/协议/状态审计。协议骨架和本地 UI 测试存在,但多货架更新、个人商店点击/状态、动作门控及本地直改旁路使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "75f3901cc3c0f8cdb60bb5d5c5c04e695776ce365d9988ce83e4ea64f24e4dee",
|
||
"implementation": "3396b617791fddb530e44180457248d2222fd3e971a1bd2b08503486de73213d",
|
||
"tests": "3829ff07408c019b3a4a65fa8d4f283fe723342bcc2d1ed51ec4993d7f9c1874"
|
||
}
|
||
},
|
||
{
|
||
"id": "social.chat_text_tail",
|
||
"title": "公聊、私聊、聊天气泡、名字尾标和文本布局",
|
||
"subsystem": "social",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonChat.cpp",
|
||
"UserInterface/PythonChat.h",
|
||
"UserInterface/PythonTextTail.cpp",
|
||
"UserInterface/PythonTextTail.h",
|
||
"UserInterface/InstanceBaseEffect.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.h",
|
||
"UserInterface/Packet.h",
|
||
"../../Client/Eternexus/root/game.py",
|
||
"../../Client/Eternexus/root/uiwhisper.py",
|
||
"../../Client/Eternexus/root/localeInfo.py",
|
||
"../../Client/Eternexus/root/colorInfo.py"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvChatPacket",
|
||
"CPythonNetworkStream::RecvWhisperPacket",
|
||
"CPythonChat::AppendChat",
|
||
"CPythonChat::AppendChatWithDelay",
|
||
"CPythonChat::AppendWhisper",
|
||
"CPythonChat::IgnoreCharacter",
|
||
"CPythonChat::IsIgnoreCharacter",
|
||
"CPythonTextTail::RegisterChatTail",
|
||
"CPythonTextTail::RegisterInfoTail",
|
||
"CPythonTextTail::UpdateAllTextTail",
|
||
"CInstanceBase::RefreshTextTail",
|
||
"CInstanceBase::AttachTextTail",
|
||
"GameWindow::OnRecvWhisper"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/chat_tail.gd",
|
||
"project/text_tail.gd",
|
||
"project/text_tail_arrange.gd",
|
||
"project/name_tail_layout.gd",
|
||
"project/ui/chat_ui.gd",
|
||
"project/whisper_chat_system.gd",
|
||
"project/net_world.gd",
|
||
"project/game_scene.gd",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"extension/src/net/classic/wire_classic.h"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::mut_chat",
|
||
"EntityStore::mut_whisper",
|
||
"EntityStore::drain_chat",
|
||
"M2Client::say",
|
||
"M2Client::whisper",
|
||
"ChatUI::_on_submit",
|
||
"ChatUI::_on_chat",
|
||
"ChatUI::_on_whisper",
|
||
"WhisperChatSystem::receive_whisper",
|
||
"NetWorld::_bubble",
|
||
"NetWorld::register_info_tail",
|
||
"NetWorld::_expire_chat_tails",
|
||
"NetWorld::_reposition_name_tag",
|
||
"ChatTail::register_chat",
|
||
"ChatTail::register_info",
|
||
"TextTail::refresh_title",
|
||
"TextTail::refresh_level"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "GAP",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "GAP",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "GAP",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/social.chat-text-tail.md",
|
||
"tests": [
|
||
"project/chat_test.gd",
|
||
"project/chat_tail_test.gd",
|
||
"project/text_tail_test.gd",
|
||
"project/text_tail_arrange_test.gd",
|
||
"project/name_tail_layout_test.gd",
|
||
"project/guild_mark_tail_test.gd",
|
||
"project/test_whisper_chat_parity.gd",
|
||
"project/chat_mobile_ui_test.gd",
|
||
"project/test_chat_hyperlink_dice_parity.gd",
|
||
"build/extension/net_entity_test",
|
||
"build/extension/net_classic_session_test"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"补齐 whisper mode 0..5/0xFF 的独立会话、系统/GM/错误颜色、通知和服务端正文语义",
|
||
"补齐 live WhisperChatSystem 的窗口历史、任务栏按钮、未读闪烁、ignore 命令和发送结果状态",
|
||
"补齐无 VID INFO/NOTICE/BIG_NOTICE 专用提示、COMMAND 副作用和未知 VID 忽略分支",
|
||
"复现入站表情解析、跨帝国文字转换、辱骂过滤和 40250 延迟聊天队列",
|
||
"统一公共聊天颜色和文本前缀处理,发送失败不能生成成功回显",
|
||
"让聊天/信息尾标使用同一权威实体生命周期,补齐真实字体度量、屏幕投影和死亡/换图清理",
|
||
"补充重复/乱序/断线/重连、未知类型、超长文本和 whisper 错误端到端测试"
|
||
],
|
||
"notes": "已完成 40250 与当前端的公共聊天、私聊、忽略、聊天气泡、名字/称号/等级/公会名尾标及文本布局逐分支/协议/状态审计。已修正 whisper mode 映射、截断 GC_WHISPER 拒绝逻辑,并将 live ChatUI 的普通/GM whisper 接入独立会话和 ignore 判断;基础网络与静态尾标测试存在,但窗口通知、公共提示/过滤/表情/颜色和 Godot 字体布局差异使合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "82802154cb44736cfa53a3dc2de5879c1fb9ccb4f47037b70827a2ba8262c9f8",
|
||
"implementation": "cd02d44596b377534a4200e0ef7002b3c6de8e7c328fbdd96afc7e9ed8a6cb8f",
|
||
"tests": "c86122ea9b1448a74f4af2a90bc25382ae7b477d6a2cc96b27c5ac3b48d97e39"
|
||
}
|
||
},
|
||
{
|
||
"id": "social.party_guild_messenger",
|
||
"title": "组队、公会、好友、Messenger 和徽章",
|
||
"subsystem": "social",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGameActor.cpp",
|
||
"UserInterface/PythonGuild.cpp",
|
||
"UserInterface/PythonMessenger.cpp",
|
||
"UserInterface/PythonCharacterManager.cpp",
|
||
"UserInterface/GuildMarkDownloader.cpp",
|
||
"UserInterface/GuildMarkUploader.cpp",
|
||
"UserInterface/MarkManager.cpp",
|
||
"UserInterface/Packet.h",
|
||
"../../Client/Eternexus/root/uiparty.py",
|
||
"../../Client/Eternexus/root/uiguild.py",
|
||
"../../Client/Eternexus/root/uimessenger.py",
|
||
"../../Client/Eternexus/root/uiuploadmark.py",
|
||
"../../Client/Eternexus/root/game.py",
|
||
"../../Client/Eternexus/root/interfacemodule.py",
|
||
"../../Client/Eternexus/root/uitaskbar.py"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvPartyPacket",
|
||
"CPythonNetworkStream::RecvGuildPacket",
|
||
"CPythonNetworkStream::RecvMessengerPacket",
|
||
"CPythonNetworkStream::SendPartyInvitePacket",
|
||
"CPythonNetworkStream::SendPartyParameterPacket",
|
||
"CPythonNetworkStream::SendGuildPacket",
|
||
"CPythonMessenger::OnFriendLogin",
|
||
"CPythonMessenger::OnFriendLogout",
|
||
"CPythonMessenger::SetMobile",
|
||
"CPythonCharacterManager::RefreshAllGuildMark",
|
||
"CGuildMarkDownloader::__LoginState_RecvMarkIndex",
|
||
"CGuildMarkDownloader::__LoginState_RecvMarkBlock",
|
||
"CGuildMarkDownloader::__SendMarkCRCList",
|
||
"CPythonGuild::UploadMark"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/party_management_system.gd",
|
||
"project/guild_skill_system.gd",
|
||
"project/guild_war_system.gd",
|
||
"project/guild_war_observer_arena_system.gd",
|
||
"project/messenger_system.gd",
|
||
"project/guild_mark_uploader_system.gd",
|
||
"project/ui/party_ui.gd",
|
||
"project/ui/guild_ui.gd",
|
||
"project/ui/friend_ui.gd",
|
||
"project/game_scene.gd",
|
||
"project/net_world.gd",
|
||
"project/app_flow.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/classic_mark_client.cpp",
|
||
"extension/src/net/classic/classic_mark_client.h",
|
||
"extension/src/net/classic/wire_classic.h"
|
||
],
|
||
"symbols": [
|
||
"PartyManagementSystem::distribute_exp",
|
||
"PartyManagementSystem::set_member_role",
|
||
"GuildSkillSystem::donate_exp",
|
||
"GuildSkillSystem::use_skill",
|
||
"GuildWarSystem::record_score",
|
||
"GuildWarSystem::end_war",
|
||
"MessengerSystem::add_friend",
|
||
"MessengerSystem::set_friend_state",
|
||
"GuildMarkUploaderSystem::upload_guild_mark",
|
||
"PartyUI::_on_invite",
|
||
"GuildUI::_fill_members",
|
||
"GuildUI::_fill_war",
|
||
"FriendUI::_on_add",
|
||
"M2Client::party_invite",
|
||
"M2Client::party_answer",
|
||
"M2Client::party_set_state",
|
||
"M2Client::add_friend",
|
||
"M2Client::friend_answer",
|
||
"M2Client::guild_post_comment",
|
||
"M2Client::use_guild_skill",
|
||
"M2Client::download_guild_marks",
|
||
"M2Client::upload_guild_mark",
|
||
"EntityStore::mut_party_update",
|
||
"EntityStore::mut_friend_set",
|
||
"EntityStore::mut_guild_member",
|
||
"EntityStore::mut_guild_war"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "GAP",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/social.party-guild-messenger.md",
|
||
"tests": [
|
||
"project/test_party_management_parity.gd",
|
||
"project/test_messenger_parity.gd",
|
||
"project/test_guild_mark_uploader_parity.gd",
|
||
"project/guild_mark_test.gd",
|
||
"project/guild_mark_tail_test.gd",
|
||
"project/test_guild_war_parity.gd",
|
||
"project/test_guild_war_observer_arena_parity.gd",
|
||
"project/test_guild_skill_parity.gd",
|
||
"project/guild_creation_ui_test.gd",
|
||
"project/test_privilege_empire_guild_parity.gd",
|
||
"project/p8_test.gd",
|
||
"extension/tests/net_entity_test.cpp",
|
||
"extension/tests/net_classic_session_test.cpp"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"为 GC_PARTY/GC_MESSENGER/GC_GUILD 全分支补充真实回包、拒绝、重复、乱序和重连测试",
|
||
"禁止本地 party/guild skill/war helper 作为 live 权威,改为请求后等待服务端 state/affect/point 回写",
|
||
"统一 live FriendUI 与 MessengerSystem 的会话/忽略/在线/移动状态来源",
|
||
"补充公会权限拒绝、离会/踢人、公告/技能/金钱回包和邀请清理",
|
||
"补充公会战声明/开始/结束/list/point 的连续事件与服务端奖励验证",
|
||
"补充独立 mark client 的握手、index、CRC、压缩 block、损坏、断线、重试和角色刷新测试",
|
||
"上传会徽/战旗必须验证真实用户资源、权限和服务端完成回包,避免默认占位图成为成功状态"
|
||
],
|
||
"notes": "已完成 40250 与当前端的组队、公会、好友/Messenger、会徽/战旗及相关 UI/native 回包链审计。live native 主路径较完整,但本地队伍/技能/公会战/Messenger/会徽 helper 仍存在直接状态旁路,真实权限拒绝、mark 独立连接和重连清理证据不足,合同保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "6223e48f6be2d1471133c76658c264f2eaf5fcef7e2870ee21f538ff81e0d0ec",
|
||
"implementation": "df7b3599aa8255d8134668158fda22e2914854e30b763adc1deceb9c345a6873",
|
||
"tests": "89f4d155ee7d3b4f3457ef2114aed642ef6030a7a7c226d5731fcc20cd2ba2dd"
|
||
}
|
||
},
|
||
{
|
||
"id": "social.exchange_safebox",
|
||
"title": "玩家交易、仓库、安全箱和金币确认",
|
||
"subsystem": "social",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonExchange.cpp",
|
||
"UserInterface/PythonSafeBox.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGameItem.cpp",
|
||
"Client/Eternexus/root/uiexchange.py",
|
||
"Client/Eternexus/root/uisafebox.py",
|
||
"Client/Eternexus/root/game.py"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvExchangePacket",
|
||
"CPythonNetworkStream::SendExchangeStartPacket",
|
||
"CPythonNetworkStream::SendExchangeElkAddPacket",
|
||
"CPythonNetworkStream::SendExchangeItemAddPacket",
|
||
"CPythonNetworkStream::SendExchangeAcceptPacket",
|
||
"CPythonNetworkStream::SendExchangeExitPacket",
|
||
"CPythonNetworkStream::SendSafeBoxCheckinPacket",
|
||
"CPythonNetworkStream::SendSafeBoxCheckoutPacket",
|
||
"CPythonNetworkStream::SendSafeBoxItemMovePacket",
|
||
"CPythonNetworkStream::RecvSafeBoxSetPacket",
|
||
"CPythonNetworkStream::RecvSafeBoxDelPacket",
|
||
"CPythonNetworkStream::RecvSafeBoxSizePacket",
|
||
"CPythonNetworkStream::RecvMallOpenPacket",
|
||
"CPythonNetworkStream::RecvMallItemSetPacket",
|
||
"CPythonNetworkStream::RecvMallItemDelPacket",
|
||
"CPythonExchange",
|
||
"CPythonSafeBox"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/player_trade_exchange_system.gd",
|
||
"project/safebox_storage_keeper_system.gd",
|
||
"project/ui/exchange_ui.gd",
|
||
"project/ui/safebox_ui.gd",
|
||
"project/ui/mall_ui.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h"
|
||
],
|
||
"symbols": [
|
||
"M2Client::exchange_start",
|
||
"M2Client::exchange_add_item",
|
||
"M2Client::exchange_add_gold",
|
||
"M2Client::exchange_accept",
|
||
"M2Client::exchange_cancel",
|
||
"M2Client::safebox_checkin",
|
||
"M2Client::safebox_checkout",
|
||
"M2Client::safebox_move",
|
||
"M2Client::mall_checkout",
|
||
"EntityStore::apply",
|
||
"ExchangeUI",
|
||
"SafeboxUI",
|
||
"MallUI",
|
||
"PlayerTradeExchangeSystem",
|
||
"SafeboxStorageKeeperSystem"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "GAP",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/social.exchange-safebox.md",
|
||
"tests": [],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"补齐 GC_EXCHANGE/GC_SAFEBOX/GC_MALL 的拒绝、乱序、断线和回包回滚测试",
|
||
"将交易和仓库 UI 的 optimistic 状态改为由 native 回包重建并验证源位置",
|
||
"区分并隔离本地 helper,避免将交易结算、密码、库存和金条规则当作客户端 authority",
|
||
"补齐 exchange 动作门控、真实服务端物品/金币校验和商城 checkout 失败清理"
|
||
],
|
||
"notes": "已完成交易、仓库、安全箱、商城和金币确认的逐分支/协议/状态审计;native 主链存在但与 40250 仍是 PARTIAL,本轮未修改实现。",
|
||
"fingerprints": {
|
||
"reference": "32d03a88d33723e087656bdfcbe806507b2e88e5ef20e36211d9f2cbab26a745",
|
||
"implementation": "d2cad2b8bdf7fc421478157ea0085bef07a4979d3d3799ff81823e1dd16216ab",
|
||
"tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||
}
|
||
},
|
||
{
|
||
"id": "quest.quest_event_navigation",
|
||
"title": "任务状态、事件、对话、HUD 和导航提示",
|
||
"subsystem": "quest",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonQuest.cpp",
|
||
"UserInterface/PythonEventManager.cpp",
|
||
"UserInterface/PythonEventManager.h",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonNetworkStreamEvent.cpp",
|
||
"UserInterface/PythonMiniMap.cpp",
|
||
"Client/Eternexus/root/uiquest.py",
|
||
"Client/Eternexus/root/interfacemodule.py",
|
||
"Client/Eternexus/root/uicharacter.py",
|
||
"Client/Eternexus/root/uiminimap.py",
|
||
"Client/Eternexus/root/atlasinfo.txt"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvQuestInfoPacket",
|
||
"CPythonNetworkStream::RecvQuestConfirmPacket",
|
||
"CPythonNetworkStream::RecvScriptPacket",
|
||
"CPythonNetworkStream::SendScriptAnswerPacket",
|
||
"CPythonNetworkStream::SendScriptButtonPacket",
|
||
"CPythonQuest::MakeQuest",
|
||
"CPythonQuest::RegisterQuestInstance",
|
||
"CPythonQuest::SetQuestClockValue",
|
||
"CPythonQuest::GetQuestLastTime",
|
||
"CPythonEventManager::RegisterEventSetFromString",
|
||
"CPythonEventManager::ProcessEventSet",
|
||
"CPythonEventManager::Skip",
|
||
"CPythonEventManager::EndEventProcess",
|
||
"CPythonMiniMap::AddSignalPoint",
|
||
"CPythonMiniMap::ClearAllSignalPoint"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/ui/quest_log.gd",
|
||
"project/ui/quest_dialog.gd",
|
||
"project/ui/quest_curtain.gd",
|
||
"project/ui/char_status_ui.gd",
|
||
"project/ui/atlas_ui.gd",
|
||
"project/ui/minimap.gd",
|
||
"project/atlas_navigation_system.gd",
|
||
"project/game_scene.gd",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/m2_client.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_session.h",
|
||
"project/biologist_quest_system.gd",
|
||
"project/dynamic_bounty_event_system.gd",
|
||
"project/event_manager_system.gd",
|
||
"project/metin_spawn_event_system.gd"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::apply",
|
||
"M2Client::get_quests",
|
||
"M2Client::script_button",
|
||
"M2Client::script_answer",
|
||
"M2Client::quest_input",
|
||
"M2Client::quest_confirm",
|
||
"M2Client::quest_cancel",
|
||
"QuestLog",
|
||
"QuestDialog",
|
||
"QuestCurtain",
|
||
"AtlasUI",
|
||
"Minimap",
|
||
"GameScene::_on_quest_fade_event",
|
||
"GameScene::_on_quest_camera_event"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "GAP",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/quest.quest-event-navigation.md",
|
||
"tests": [
|
||
"extension/tests/net_entity_test.cpp",
|
||
"extension/tests/net_classic_session_test.cpp",
|
||
"extension/tests/net_bounds_test.cpp",
|
||
"project/quest_test.gd",
|
||
"project/quest_event_test.gd",
|
||
"project/test_quest_hud_parity.gd",
|
||
"project/test_quest_dialog_parity.gd"
|
||
],
|
||
"last_test_result": "PASS"
|
||
},
|
||
"remaining": [
|
||
"验证 clock_start_ms 与 40250 CTimer::GetCurrentSecond() 的时间基准、后台恢复和过期任务清理,并增加真实任务日志到 UI 行号映射测试",
|
||
"补齐真实 server 脚本序列与中断回归;script-button、DONE、ESC/CANCEL answer 语义已闭合",
|
||
"验证 RUN_CINEMA 的有效 .msc 资源序列、过场消费者和中断清理;验证地图信号在 Minimap/Atlas 的切图与多点生命周期",
|
||
"补齐对话确认超时、fade/camera/cinema 中断、断线和重连清理测试",
|
||
"隔离生物学家/悬赏/活动 helper,避免其本地规则冒充 40250 客户端实现"
|
||
],
|
||
"notes": "已完成任务状态、事件脚本、对话、HUD、地图导航和过场链的逐分支/协议/时序审计;GC_QUEST_INFO END、script-button 任务索引 sentinel、DONE close-only/DoneEvent、ESC/CANCEL answer 分支和 RUN_CINEMA 缺失资源分支已按 40250 修复,其他明确差异继续保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "ba46c9195d60a257062b207f81d4fbc730b71d8098d6b2a2a531640f05debe38",
|
||
"implementation": "5aa1707764f5a74db510210bfd8803a9e90e0a4d989202795fe506d1f29a8395",
|
||
"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.h",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/PythonPlayer.cpp",
|
||
"UserInterface/PythonPlayer.h",
|
||
"UserInterface/PythonEventManager.cpp",
|
||
"UserInterface/PythonEventManager.h"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvDungeon",
|
||
"CPythonPlayer::SetDungeonDestinationPosition",
|
||
"CPythonPlayer::AlarmHaveToGo",
|
||
"CPythonPlayer::Update",
|
||
"CPythonEventManager::ProcessEventSet",
|
||
"CGameEventManager::ProcessEventScreenWaving"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/dungeon_state.gd",
|
||
"project/ui/dungeon_result_ui.gd",
|
||
"project/game_scene.gd",
|
||
"project/world_boss_system.gd",
|
||
"project/dungeon_ranking_system.gd",
|
||
"project/spider_dungeon_system.gd",
|
||
"project/spider_dungeon_key_system.gd",
|
||
"project/flame_dungeon_razador_system.gd",
|
||
"project/endless_spire_dungeon_system.gd",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/classic/classic_session.cpp"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::apply",
|
||
"M2Client::poll",
|
||
"M2Client::request_dungeon",
|
||
"ClassicSession::send_dungeon",
|
||
"DungeonState::_on_dungeon_event",
|
||
"DungeonState::_process",
|
||
"DungeonResultUI::show_result",
|
||
"GameScene::_on_dungeon_result"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.dungeon-activity.md",
|
||
"tests": [],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"接通 alarm_requested 到等价的 compass/mse 资源效果,并验证方向、初次提示和 20 秒重复提示",
|
||
"补齐 GC_DUNGEON/DUNGEON_RESULT 的 live 回包、重复/乱序、拒绝、断线、切图和重连测试",
|
||
"确认 server cm 到 Godot 世界坐标的轴向、地图基准和到达阈值实机一致",
|
||
"把 Boss、排行和具体副本 helper 与 40250 client parity 分开,禁止本地 authority 冒充客户端等价实现"
|
||
],
|
||
"notes": "已完成 GC_DUNGEON 目标点、结果窗口、地图事件及本地 Boss/排行/副本 helper 的逐分支、协议、资源和时序审计;目标状态算法接近参考,但 compass 副作用未接通,本地 authority 不能视为 40250 客户端实现。",
|
||
"fingerprints": {
|
||
"reference": "45e83143617b16eda5b4ad7cc38e701fa9590b02a4c36d5adb2dd8a3bae77b7f",
|
||
"implementation": "c4885c44a33a107d3095a20b988f26a5a62d50814514e34ffb03c86bfa7a745d",
|
||
"tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||
}
|
||
},
|
||
{
|
||
"id": "world.fishing_mining",
|
||
"title": "钓鱼、采矿、资源采集和水域条件",
|
||
"subsystem": "world",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonPlayerInput.cpp",
|
||
"UserInterface/InstanceBase.cpp",
|
||
"UserInterface/InstanceBaseMotion.cpp",
|
||
"UserInterface/Packet.h",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"GameLib/MapOutdoorWater.cpp",
|
||
"GameLib/AreaTerrain.cpp"
|
||
],
|
||
"symbols": [
|
||
"CPythonPlayer::NEW_Fishing",
|
||
"CPythonPlayer::NEW_CancelFishing",
|
||
"CInstanceBase::GetFishingRot",
|
||
"CInstanceBase::CanFishing",
|
||
"CInstanceBase::StartFishing",
|
||
"CInstanceBase::StopFishing",
|
||
"CInstanceBase::ReactFishing",
|
||
"CInstanceBase::CatchSuccess",
|
||
"CInstanceBase::CatchFail",
|
||
"CPythonNetworkStream::SendFishingPacket",
|
||
"CPythonNetworkStream::RecvFishing",
|
||
"CPythonNetworkStream::RecvDigMotionPacket",
|
||
"CTerrain::GetWaterHeight",
|
||
"CMapOutdoor::RenderWater"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/fishing_mining_system.gd",
|
||
"project/fishing_rod_reel_system.gd",
|
||
"project/fishing_cooking_camp_system.gd",
|
||
"project/mining_refinery_smelt_system.gd",
|
||
"project/net_play.gd",
|
||
"project/net_world.gd",
|
||
"extension/src/metin2_world.cpp",
|
||
"extension/src/metin2_world.h",
|
||
"extension/src/water_builder.cpp",
|
||
"extension/src/water_motion.h",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/classic/classic_session.cpp",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/wire_classic.h"
|
||
],
|
||
"symbols": [
|
||
"M2Client::fishing",
|
||
"ClassicSession::send_fishing",
|
||
"EntityStore::apply",
|
||
"NetPlay::_new_fishing",
|
||
"NetPlay::cancel_fishing",
|
||
"NetWorld::_on_fishing_event",
|
||
"NetWorld::_on_dig_motion",
|
||
"Metin2World::get_fishing_rotation",
|
||
"Metin2World::can_fishing_position"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.fishing-mining.md",
|
||
"tests": [],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"补齐 CanFishing/持竿/持镐动作门控、服务端拒绝和角色消失/重连测试",
|
||
"验证 GetFishingRot 的轴向、MapCoord heading 和全地图 water attribute/水高单位",
|
||
"让 GC_DIG_MOTION 按 count 复现多个 dig motion,并验证目标缺失、动作完成和中断清理",
|
||
"把钓鱼收益、烹饪、营火、采矿、熔炼和宝石脚本标记为 custom/server authority,禁止冒充 40250 client parity"
|
||
],
|
||
"notes": "已完成钓鱼输入/水面扫描、GC_FISHING、GC_DIG_MOTION、地图水面和当前端生活技能 helper 的逐分支、协议、状态与资源审计;协议骨架和部分算法接近参考,但 live 动作/门控/清理证据不足,本地收益脚本不是 40250 客户端 authority。",
|
||
"fingerprints": {
|
||
"reference": "bbc26abb3d3a2cdca5abae515be1dbd0a83188cdf44c5e55738e2a3b664a0ee4",
|
||
"implementation": "c72913ee472ed61dfa156b67ba7468f8098512972e49c4a1e307aa8740152fca",
|
||
"tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||
}
|
||
},
|
||
{
|
||
"id": "world.mount_pet_polymorph",
|
||
"title": "马匹、宠物、变身和骑乘战斗",
|
||
"subsystem": "world",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"UserInterface/PythonPlayer.cpp",
|
||
"UserInterface/PythonPlayerSkill.cpp",
|
||
"UserInterface/InstanceBase.cpp",
|
||
"UserInterface/InstanceBaseMotion.cpp",
|
||
"UserInterface/InstanceBase.h",
|
||
"GameLib/ActorInstance.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGameActor.cpp",
|
||
"UserInterface/PythonNetworkStreamPhaseGame.cpp",
|
||
"UserInterface/Packet.h"
|
||
],
|
||
"symbols": [
|
||
"CPythonNetworkStream::RecvMountPacket",
|
||
"CPythonNetworkStream::__RecvCharacterAppendPacket",
|
||
"CPythonNetworkStream::RecvCharacterUpdatePacket",
|
||
"CInstanceBase::MountHorse",
|
||
"CInstanceBase::DismountHorse",
|
||
"CInstanceBase::CanAttack",
|
||
"CInstanceBase::CanUseSkill",
|
||
"CInstanceBase::CanAttackHorseLevel",
|
||
"CInstanceBase::IsHoldingPickAxe",
|
||
"CInstanceBase::IsPoly"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/horse_system.gd",
|
||
"project/horse_combat_system.gd",
|
||
"project/horse_level_growth_system.gd",
|
||
"project/pet_companion_system.gd",
|
||
"project/classic_pet_system.gd",
|
||
"project/polymorph_system.gd",
|
||
"project/polymorph_marble_system.gd",
|
||
"project/mounted_combat_skill_system.gd",
|
||
"project/net_play.gd",
|
||
"project/net_world.gd",
|
||
"project/ui/equip_model.gd",
|
||
"extension/src/net/entity_store.cpp",
|
||
"extension/src/net/entity_store.h",
|
||
"extension/src/net/m2_client.cpp",
|
||
"extension/src/net/classic/classic_parser.cpp",
|
||
"extension/src/net/classic/wire_classic.h"
|
||
],
|
||
"symbols": [
|
||
"EntityStore::mut_mount",
|
||
"EntityStore::mut_char_update",
|
||
"M2Client::poll",
|
||
"NetPlay::_refresh_motion_mode",
|
||
"NetPlay::_can_attack_horse_level",
|
||
"NetWorld::_apply_field_updates",
|
||
"EquipModel::_is_mounting",
|
||
"EquipModel::_is_poly"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "PARTIAL",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "PARTIAL",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "GAP",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "GAP"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.mount-pet-polymorph.md",
|
||
"tests": [],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"补齐 mount_vnum/GC_MOUNT 到实际 horse actor/model attach/detach 的 live 链和位置清理",
|
||
"验证骑乘 motion root、300/1200 度旋转速度、马等级攻击门和 weapon mode 的 live 回包时序",
|
||
"把变身 affect/shape/装备遮挡接入 native entity 或明确标为缺失,不用本地 helper 代替",
|
||
"将宠物、马匹成长/耐力/马上技能和伤害脚本与 40250 client parity 分离,禁止 custom authority 冒充等价实现"
|
||
],
|
||
"notes": "已完成 mount_vnum/GC_MOUNT、骑乘动作/攻击门、变身和当前端宠物/马匹 helper 的逐分支、协议、资源和时序审计;网络字段与部分门控存在,但实际坐骑模型挂接和变身 native 闭环缺失,本地宠物/成长/技能规则不是 40250 客户端 authority。",
|
||
"fingerprints": {
|
||
"reference": "deefe3ad38347b0ab207653391cd54b5f06811f6892ee15a8650cc42e8637e98",
|
||
"implementation": "333ea5a5c89e7628044ea4b548354e73d27e602b6348eb46cedd24f8a85d7568",
|
||
"tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||
}
|
||
},
|
||
{
|
||
"id": "render.model_attachment",
|
||
"title": "Granny 模型、LOD、骨骼、武器、盾牌和发型挂点",
|
||
"subsystem": "render",
|
||
"priority": "P1",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"EterGrnLib/ThingInstance.cpp",
|
||
"EterGrnLib/ThingInstance.h",
|
||
"EterGrnLib/LODController.cpp",
|
||
"EterGrnLib/LODController.h",
|
||
"EterGrnLib/ModelInstanceUpdate.cpp",
|
||
"EterGrnLib/ModelInstance.cpp",
|
||
"EterGrnLib/ModelInstanceMotion.cpp",
|
||
"EterGrnLib/ModelInstanceModel.cpp",
|
||
"EterGrnLib/ModelInstanceRender.cpp",
|
||
"EterGrnLib/ModelInstanceCollisionDetection.cpp",
|
||
"GameLib/ActorInstanceAttach.cpp",
|
||
"GameLib/RaceData.cpp",
|
||
"GameLib/RaceData.h",
|
||
"GameLib/ItemData.cpp",
|
||
"UserInterface/InstanceBase.cpp",
|
||
"UserInterface/InstanceBaseRender.cpp"
|
||
],
|
||
"symbols": []
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"extension/src/metin2_model.h",
|
||
"extension/src/metin2_model.cpp",
|
||
"extension/src/metin2_anim.cpp",
|
||
"extension/src/gr2_bridge.cpp",
|
||
"project/ui/player_view.gd",
|
||
"project/ui/equip_model.gd",
|
||
"project/ui/remote_player_view.gd",
|
||
"project/ui/mob_view.gd",
|
||
"project/ui/race_spec.gd"
|
||
],
|
||
"symbols": []
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "GAP",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "GAP",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "STATIC_VERIFIED",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/render.model-attachment.md",
|
||
"tests": [
|
||
"project/weapon_attach_test.gd",
|
||
"project/gpu_lod_attachment_test.gd",
|
||
"project/gpu_pose_bounds_test.gd",
|
||
"project/race_motion_assembly_test.gd",
|
||
"project/equip_model_test.gd",
|
||
"project/remote_player_test.gd",
|
||
"project/mob_view_test.gd",
|
||
"project/rendering_scenario_test.gd",
|
||
"project/fly_target_bounds_test.gd",
|
||
"project/mob_winding_test.gd",
|
||
"project/animation_cache_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"统一 40250 Actor LOD 的 500/1500/2500cm 分档、模型队列和缺失资源回退",
|
||
"验证 LOD 切换时动作复制、武器/盾牌/发型/盔甲挂点、材质、碰撞和 bounds 同步",
|
||
"修复 03150 双手武器 run 动作左手挂点偏移,并建立逐动作挂点矩阵",
|
||
"覆盖非连续 LOD、缺骨骼、重复换装、死亡、换图和资源释放;修复 ObjectDB 泄漏与 exit 139",
|
||
"补齐变身装备遮挡及 ItemData/RaceData 多模型资源列表等价实现"
|
||
],
|
||
"notes": "已完成 40250 ThingInstance/LODController/ModelInstance/ActorInstanceAttach/RaceData/ItemData 与当前 GR2/Godot 模型链的首轮逐分支审计;确认默认 LOD 常量、LOD 子模型生命周期和 03150 双手武器姿态存在真实差异,合同保持 PARTIAL,未修改实现。",
|
||
"fingerprints": {
|
||
"reference": "553e2ce8e6b51e4acf8192cc1f7983865f52a2d68ae2d7c2f1d8aee9fa63f349",
|
||
"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": "0f027842dc3a7991870c155a9a9453babb4c0f8fc55ac2c4c7f0bb75aaa445c8",
|
||
"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": "46da799d71b3ad4ec459ebaa5364dac82a1cd503a30c55692ccadaf183ade1cc",
|
||
"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/weather_daynight_system.gd",
|
||
"project/fx/weather.gd"
|
||
],
|
||
"symbols": [
|
||
"mtgodot::apply_environment",
|
||
"Metin2World::load_map",
|
||
"EntityStore::apply_server_command",
|
||
"M2Client::weather_changed",
|
||
"GameScene::_build_world",
|
||
"GameScene::_on_weather_changed",
|
||
"GameScene::_apply_server_weather",
|
||
"Weather",
|
||
"WeatherDayNightSystem"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"environment_parse_load_cleanup": "PARTIAL",
|
||
"background_character_lighting": "PARTIAL",
|
||
"fog_near_far_color_density": "PARTIAL",
|
||
"sky_gradient_faces_cloud_transition": "PARTIAL",
|
||
"screen_filter": "GAP",
|
||
"lens_flare": "GAP",
|
||
"wind_to_forest": "GAP",
|
||
"snow_particle_pool_render_order": "PARTIAL",
|
||
"server_xmas_snow_command": "PARTIAL",
|
||
"day_night_weather_scene_integration": "PARTIAL",
|
||
"reload_pause_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/world.environment-weather.md",
|
||
"tests": [
|
||
"project/environment_test.gd",
|
||
"project/skybox_test.gd",
|
||
"project/p9_test.gd",
|
||
"project/test_weather_daynight_parity.gd",
|
||
"extension/tests/net_entity_test.cpp",
|
||
"project/gamescene_test.gd",
|
||
"project/netbridge_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL: msenv/skybox/weather tests, xmas_snow native command, GameScene map filtering and M2Client signal pass; SnowEnvironment pool, DayMode, filter/lens flare/wind and linear fog remain open"
|
||
},
|
||
"remaining": [
|
||
"实现或明确 screen filter、lens flare 和 SpeedTree/树木风更新,不以 metadata 代替运行时效果",
|
||
"修正/验证 40250 线性 fog near/far/color 与 HTP/STP/terrain 的参数和时序",
|
||
"补齐 DayMode/PRESERVE_DayMode 正式环境切换;WeatherDayNightSystem 的自动地图预设继续隔离,不能替代服务器 xmas_snow 命令",
|
||
"对天空 transition、云 plane、SnowEnvironment 池和真实渲染顺序增加参数/screenshot oracle",
|
||
"增加重复加载、快速切图、暂停恢复、资源缺失和环境/粒子清理测试"
|
||
],
|
||
"notes": "已完成 .msenv、天空/云、双方向光、fog、天气粒子、服务器 xmas_snow 命令和正式场景过滤的逐分支审计;命令链与四张排除地图已有回归,但 SnowEnvironment 池、DayMode、filter/lens flare/wind、线性 fog 仍与 40250 不一致或缺失,故保持 PARTIAL。",
|
||
"fingerprints": {
|
||
"reference": "0a076b8a9779c989184c20ef617cddf5538720e8ac7e2e0773d63af3d72e6eb0",
|
||
"implementation": "dfc0fe4b13645b06daeea26f517d813a8c8ebb4cfcb2db19ebe2ee7e8f573c7c",
|
||
"tests": "05fd8d3202d9774f7043311e8615e1210a09cc579f1346eb2fb9ebbfec339b5b"
|
||
}
|
||
},
|
||
{
|
||
"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": "11181965574e3b0c3e235b1403e17494f8fc1b8777a2b18b955f53a84e0bae54",
|
||
"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",
|
||
"UserInterface/uitaskbar.py",
|
||
"UserInterface/uicharacter.py",
|
||
"UserInterface/uitarget.py"
|
||
],
|
||
"symbols": [
|
||
"CPythonTextTail::UpdateAllTextTail",
|
||
"CPythonTextTail::UpdateShowingTextTail",
|
||
"CPythonTextTail::ArrangeTextTail",
|
||
"CPythonMiniMap::SetScale",
|
||
"CPythonMiniMap::ScaleUp",
|
||
"CPythonMiniMap::ScaleDown",
|
||
"CPythonPlayer::SetStatus",
|
||
"CPythonPlayer::RequestUseLocalQuickSlot",
|
||
"CPythonNetworkStream::RecvTargetPacket"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/hud.gd",
|
||
"project/net_play.gd",
|
||
"project/net_world.gd",
|
||
"project/ui/char_status_ui.gd",
|
||
"project/ui/target_ui.gd",
|
||
"project/target_board.gd",
|
||
"project/ui/minimap.gd",
|
||
"project/text_tail.gd",
|
||
"project/text_tail_arrange.gd",
|
||
"project/chat_tail.gd",
|
||
"project/ui/ground_items.gd",
|
||
"project/ui/quickbar.gd"
|
||
],
|
||
"symbols": [
|
||
"NetPlay::_on_points",
|
||
"NetPlay::_on_target_info",
|
||
"NetWorld::_apply_text_tail_to",
|
||
"NetWorld::_update_hp_bars",
|
||
"Minimap::_draw_map",
|
||
"Quickbar::_on_points_changed",
|
||
"CharStatusUI::_refresh_status"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"preconditions": "PARTIAL",
|
||
"branch_structure": "PARTIAL",
|
||
"algorithms_formulas": "GAP",
|
||
"state_transition_order": "PARTIAL",
|
||
"constants_units": "GAP",
|
||
"timing_event_sources": "PARTIAL",
|
||
"resource_data_sources": "PARTIAL",
|
||
"protocol_side_effects": "PARTIAL",
|
||
"interruption_failure_cleanup": "PARTIAL"
|
||
},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/ui.hud-target-minimap-status.md",
|
||
"tests": [
|
||
"project/test_target_board_parity.gd",
|
||
"project/target_board_test.gd",
|
||
"project/test_minimap_radar_parity.gd",
|
||
"project/p9_test.gd",
|
||
"project/text_tail_test.gd",
|
||
"project/text_tail_arrange_test.gd",
|
||
"project/chat_tail_test.gd",
|
||
"project/chat_test.gd",
|
||
"project/name_show_test.gd",
|
||
"project/char_status_ui_test.gd",
|
||
"project/test_taskbar_quickslot_parity.gd",
|
||
"project/taskbar_dock_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL"
|
||
},
|
||
"remaining": [
|
||
"在正式 GameScene 创建并注入唯一 TargetUI,移除/隐藏简化目标条并补生产链回归",
|
||
"只保留一套生产小地图,统一 40250 的 scale、terrain-cell 单位、按钮倍率和切换行为",
|
||
"集中 points/quickslot/target/party/delete/world-reset 的 UI owner 并覆盖重复创建、死亡、换图、重连、退出清理",
|
||
"逐帧对照 TextTail UpdateAll/Show/UpdateShowing/Arrange,消除 CanPickInstance、2Hz 和字体度量近似",
|
||
"补真实资源/网络包驱动的 HUD/marker 测试并修复 Quickbar ObjectDB/RID/resource cleanup"
|
||
],
|
||
"notes": "已完成 PythonTextTail/PythonMiniMap/PythonPlayer/TargetBoard/TaskBar 与当前 GameScene/UI 链的首轮逐分支审计;确认 TargetUI 未在正式 GameScene 注入、HUD 与新 Minimap 重复创建、Minimap scale 常量与 40250 不同,合同保持 PARTIAL,未修改实现。",
|
||
"fingerprints": {
|
||
"reference": "a948059544c578dfb6f67d4e64ed41322095fb5734835aaa60aa4dd53567d152",
|
||
"implementation": "6b31f5832b14c75d5210ed1bf312c88f550c8d03eadbcf5b9f57b1983c4ec2c4",
|
||
"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": "772c0563544d7e07bc38ae2c2a3684b5f1a248d22200dfaa108b2967e26e3ca8",
|
||
"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/test_bgm_jukebox_parity.gd",
|
||
"project/app_flow_lifecycle_test.gd",
|
||
"project/system_option_ui_test.gd",
|
||
"project/rendering_scenario_test.gd"
|
||
],
|
||
"last_test_result": "PARTIAL: focused BGM/SFX background mute-restore and Debug exit cleanup pass; coverage does not prove Miles instance pool or multi-instance music state"
|
||
},
|
||
"remaining": [
|
||
"验证真实 3D 播放实例不会绕过 master SFX,并补设备/实例路径的后台静音回归",
|
||
"验证 BGM 多实例淡入/限幅淡出/全量淡出/加载失败回滚状态机",
|
||
"验证 3D attenuation、实例池耗尽、远端动作音效和完整销毁顺序",
|
||
"验证流式地图快速切换、环境声来源删除和退出时无悬挂播放器"
|
||
],
|
||
"notes": "已完成首轮逐分支审计;基础音量/监听器/环境声/动作音效路径存在,后台 BGM/SFX 同时静音恢复与退出 Audio 缓存清理已有回归,但 Miles 实例池和 BGM 逐帧状态机仍不等价。",
|
||
"fingerprints": {
|
||
"reference": "2d3182776cf583a5e474701489a417e87ff2f8d7952a4ce03a4523dd570528cd",
|
||
"implementation": "f4c3fed1f7cbf08c1032c536927a689a9a1b71b1c431de2afb3c0041ee577315",
|
||
"tests": "a7ca9e4555b71f92776b56669a0b74e443dc2aaab78a2f208c86f9fc4bc75bfc"
|
||
}
|
||
},
|
||
{
|
||
"id": "lifecycle.platform_release",
|
||
"title": "Windows/桌面窗口、导出、资源自包含、崩溃和发布门禁",
|
||
"subsystem": "lifecycle",
|
||
"priority": "P2",
|
||
"status": "PARTIAL",
|
||
"reference": {
|
||
"files": [
|
||
"EterLib/MSApplication.cpp",
|
||
"EterLib/MSWindow.cpp",
|
||
"UserInterface/PythonApplication.cpp",
|
||
"UserInterface/PythonApplicationProcedure.cpp",
|
||
"UserInterface/CheckLatestFiles.cpp",
|
||
"EterPack/EterPackManager.cpp"
|
||
],
|
||
"symbols": [
|
||
"CMSApplication::MessageLoop",
|
||
"CMSApplication::WindowProcedure",
|
||
"CMSWindow::Create",
|
||
"CMSWindow::OnSize",
|
||
"CPythonApplication::Process",
|
||
"CPythonApplication::WindowProcedure",
|
||
"CheckLatestFilesEntry"
|
||
]
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"build.sh",
|
||
"build-macos-client.sh",
|
||
"build-android.sh",
|
||
"export-android.sh",
|
||
"project/export_presets.cfg",
|
||
"project/client_main.gd",
|
||
"project/app_flow.gd",
|
||
"project/app_lifecycle.gd",
|
||
"project/asset_root.gd",
|
||
"project/asset_pack.gd"
|
||
],
|
||
"symbols": [
|
||
"AppFlow.start",
|
||
"AppFlow._goto_game",
|
||
"AppLifecycle._notification",
|
||
"AssetRoot.path",
|
||
"AssetPack.ensure"
|
||
]
|
||
},
|
||
"equivalence": {
|
||
"windows_window_and_message_loop": "GAP",
|
||
"application_update_render_exit_order": "PARTIAL",
|
||
"focus_fullscreen_and_close": "PARTIAL",
|
||
"resource_self_containment": "PARTIAL",
|
||
"latest_file_crc_update_check": "GAP",
|
||
"exception_and_crash_handling": "GAP",
|
||
"macos_android_export": "PASS_WITH_ADAPTATION",
|
||
"windows_export": "GAP"
|
||
},
|
||
"platform_adaptations": [
|
||
"Godot MainLoop/Window and platform export replace the reference Win32 CMSApplication/CMSWindow; this is acceptable only as an explicit platform adaptation, not as Windows 1:1 parity."
|
||
],
|
||
"evidence": {
|
||
"contract": "audit/contracts/lifecycle.platform-release.md",
|
||
"tests": [
|
||
"project/app_flow_lifecycle_test.gd",
|
||
"project/bgm_test.gd",
|
||
"project/dds_lifecycle_test.gd",
|
||
"godot --headless --path project --editor --quit",
|
||
"godot --headless --path project --export-release macOS /tmp/mtgodot-platform-audit.app"
|
||
],
|
||
"last_test_result": "PARTIAL: focus activation/audio save-restore, AppFlow, DDS, editor validation and macOS export passed; Windows export/window/CRC/crash paths absent"
|
||
},
|
||
"remaining": [
|
||
"增加 Windows 导出预设、GDExtension 构建和整包启动/退出验证",
|
||
"补窗口 resize/全屏/关闭/IME/native input 的平台测试;焦点激活音量路径已有回归",
|
||
"补资源版本/CRC/缺包/坏包/路径大小写门禁",
|
||
"补异常处理、crash report、正常退出和 native 资源释放证据"
|
||
],
|
||
"notes": "已完成首轮逐分支审计;AppFlow、AssetPack、焦点激活音量保存恢复和 macOS/Android 适配存在,但 Windows 原生窗口/导出、全屏与 resize、CRC 更新检查、Eter 异常处理和完整发布门禁仍缺失。",
|
||
"fingerprints": {
|
||
"reference": "ddccccedfb929112d89c6c14b3746569a64493245a0e8b70145df3979dfd611d",
|
||
"implementation": "84417dac55504e5f626731fd2d3c78decec8808187fa70b427fdf83f66adf040",
|
||
"tests": "10987358f838e043daaed30ccb4639f738a6bbd19522783091a9916fbb5e2bc1"
|
||
}
|
||
},
|
||
{
|
||
"id": "extension.current_only_features",
|
||
"title": "没有 40250 参考实现的新增玩法和平台扩展",
|
||
"subsystem": "extension",
|
||
"priority": "P3",
|
||
"status": "EXCLUDED",
|
||
"reference": {
|
||
"files": [],
|
||
"symbols": []
|
||
},
|
||
"implementation": {
|
||
"files": [
|
||
"project/offline_bazaar_market_system.gd",
|
||
"project/mail_system.gd",
|
||
"project/dynamic_bounty_event_system.gd",
|
||
"project/dragon_soul_system.gd",
|
||
"project/solo_guild_domain_system.gd"
|
||
],
|
||
"symbols": []
|
||
},
|
||
"equivalence": {},
|
||
"platform_adaptations": [],
|
||
"evidence": {
|
||
"contract": "audit/contracts/function-inventory.md",
|
||
"tests": [],
|
||
"last_test_result": "NOT_RUN"
|
||
},
|
||
"remaining": [],
|
||
"notes": "完整功能清单首批合同;尚未完成 40250 逐分支等价审计。",
|
||
"exclusion_reason": "在指定 40250 ClientVS22 source 中没有可达对应行为;记录该当前端扩展以防止漏审,但不计入 40250 等价率。",
|
||
"fingerprints": {
|
||
"reference": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||
"implementation": "fdaf946a6bffa5e914dfe7f40c2bbd42dc00e64d43e60231e9290021f14cbba2",
|
||
"tests": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||
}
|
||
}
|
||
]
|
||
}
|