These files were loaded only by their own test_*_parity.gd and never by the runtime (transitively checked, incl. client_phase_integration_system). Removes 76 manifest implementation/evidence references across 18 contracts; contract docs get a note that conclusions based on them are void. Live systems kept: chest, consumable, item_attr, metin_socket, whisper_chat. Full headless suite: no new failures (7 failing + 5 headless-hanging render tests are identical before the deletion). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16 KiB
item.consumable_action
Scope
审计使用物品、消耗品、物品堆叠/拆分、快捷栏使用、快捷栏服务器同步、物品使用反馈和 NPC 商店出售请求。判断标准不是“本地函数能算出同样的结果”,而是从输入到 CG_* 请求、服务器 GC_* 确认、物品状态更新、点数/效果/UI 刷新的完整链路是否与 40250 一致。
Reference call chain
PythonPlayer.cpp/PythonPlayerModule.cppSetQuickPage将页面索引按QUICKSLOT_MAX_LINE做取模并刷新背包窗口。RequestUseLocalQuickSlot只接受本地 8 格范围;道具槽转发SendItemUsePacket(TItemPos(INVENTORY, pos)),技能槽调用ClickSkillSlot,表情槽调用BINARY_ActEmotion。RequestAddLocalQuickSlot、RequestAddToEmptyLocalQuickSlot、RequestDeleteGlobalQuickSlot、RequestMoveGlobalQuickSlotToLocalQuickSlot只发请求;内存快捷栏由后续GC_QUICKSLOT_*回包修改。MoveItemData是给 UI/脚本使用的本地槽位交换工具,不等同于服务端物品移动成功。
PythonNetworkStreamPhaseGameItem.cppSendItemUsePacket、SendItemUseToItemPacket、SendItemMovePacket、SendItemDropPacket(New)先执行__CanActMainInstance;装备相关路径还执行交易/商店/攻击中门控,必要时播放物品音效,写包后调用SendSequence。SendShopSellPacket/SendShopSellPacketNew发送 NPC 商店出售请求;出售数量和价格由服务器确认,不由客户端直接增钱或清槽。RecvItemUsePacket只触发物品窗口刷新;RecvItemUpdatePacket按 count→socket→attribute 的顺序写入后刷新。RecvQuickSlotAddPacket/RecvQuickSlotDelPacket/RecvQuickSlotMovePacket先修改本地快捷栏,再刷新物品窗口。
Packet.h/PythonNetworkStreamModule.cpp- 快捷栏使用、物品使用、物品对物品、物品移动、物品更新和商店出售分别有独立的请求/回包结构,不应由本地数组操作替代。
- 消耗品具体效果(药水、日月神水、龙神药水、卷轴)主要由 40250 服务端
char_item.cpp执行;40250 Windows 客户端本身负责发送ITEM_USE并消费GC_ITEM_USE、GC_ITEM_UPDATE、GC_SPECIAL_EFFECT、GC_AFFECT_*、GC_POINT_*等反馈。客户端源码中没有可证明本地独立执行这些服务端效果的等价实现。
Current call chain
project/ui/quickbar.gdassign、_clear、_swap调用M2Client::quickslot_add/del/swap;但请求返回后立即修改_state,真实客户端只有GC_QUICKSLOT_*到达EntityStore后才会产生权威状态。activate对 item 槽直接调用M2Client::use_item(1, cell),对 skill/emote 走各自本地链路;没有统一复现 40250RequestUseLocalQuickSlot的本地 8 格检查、服务器回包失败回滚和RecvItemUsePacket刷新时序。_page是 Godot UI 私有状态,set_page不调用与 40250 对应的SetQuickPage/统一刷新入口;36 个服务端槽位只显示前 32 个,与参考端可显示页/保留槽的关系尚未由端到端测试证明。
extension/src/net/m2_client.cpp/extension/src/net/classic/classic_session.cppquickslot_add/del/swap、use_item、use_item_to_item、move_item和drop_item已能构造经典协议包,但 native 层只有游戏阶段、槽位/整数范围检查;没有 40250 的__CanActMainInstance、装备类型、交易/商店/攻击中门控、物品音效和SendSequence语义。shop_sell已存在并发送服务端出售请求,但ItemSplitSystem::quick_sell_item没有使用它。
project/consumable_system.gd- 本地实现了日月神水 socket 容量、龙神药水 buff 描述和回城/地标卷轴结果字典,但没有被
M2Client、EntityStore、GC_ITEM_USE、GC_ITEM_UPDATE、GC_AFFECT_*或GC_POINT_*链路调用;它不能证明消耗品已经在游戏中生效。 - 脚本把服务端效果算法复制到了客户端,却没有处理服务端拒绝、重复包、延迟、断线、跨图和回滚。
- 本地实现了日月神水 socket 容量、龙神药水 buff 描述和回城/地标卷轴结果字典,但没有被
project/item_split_system.gdsplit_item直接修改传入 Array 的 source/destination count 并发本地 signal;没有发送 40250 对应的SendItemMovePacket,也没有等待两个槽位的GC_ITEM_*确认。quick_sell_item直接增加本地 gold、清空槽位并发 signal;没有调用M2Client::shop_sell,也没有等待服务器金币/物品回包。因此它不是 40250 NPC 商店出售链路的实现。
project/ui/inventory_ui.gd/project/ui/shop_ui.gd- 普通拖动、物品对物品和正式商店出售路径已经调用
move_item、use_item_to_item、shop_sell;这是可复用的正确方向,但与ItemSplitSystem形成两条不同的物品权威路径。
- 普通拖动、物品对物品和正式商店出售路径已经调用
Branch matrix
| 场景 | 40250 | 当前端 | 结论 |
|---|---|---|---|
| 快捷栏添加/删除/交换 | 只发 CG 请求,GC 回包后更新本地槽 | native 请求正确;quickbar.gd 请求后乐观修改 _state |
PARTIAL:拒绝/乱序时可能显示未确认状态 |
| 快捷栏本地使用 | 本地槽 <8;道具发 ITEM_USE,技能走 ClickSkillSlot,表情走脚本回调 |
UI 槽 <8;道具/技能/表情分支存在,但不是统一参考入口,死亡/忙碌/服务器拒绝回滚未形成同一链路 |
PARTIAL |
| 快捷栏页面 | 负数/超范围按 4 页取模,并刷新背包窗口 | UI 只接受 0..3,自己维护 _page,不复用统一 SetQuickPage 事件源 |
PARTIAL |
| 普通药水/消耗品 | 客户端发 CG_ITEM_USE,效果、扣数、点数和特效由服务端回包驱动 |
可以发 M2Client::use_item,但 ConsumableSystem 未接入反馈链 |
PARTIAL |
| 日月神水/龙神药水/卷轴 | 服务端判断 item proto/socket/状态,客户端只显示回包结果 | 本地静态脚本可计算结果,但没有 item proto、服务器确认、effect/point/warp 回包闭环 | PARTIAL |
| 堆叠拆分 | 通过 CG_ITEM_MOVE 的源/目标/数量请求,服务端决定能否拆分并回包更新 |
ItemSplitSystem::split_item 直接改 Array,不发网络请求 |
GAP |
| 快捷出售 | 通过 CG_SHOP SELL/SELL2 请求,服务器计算价格并回包金币/物品变化 |
ShopUI 正式路径已调用 shop_sell;ItemSplitSystem::quick_sell_item 直接加金币清槽 |
GAP:存在错误的旁路实现 |
| 物品对物品使用 | 发送 ITEM_USE_TO_ITEM,反馈由服务端更新 |
InventoryUI 已发送 use_item_to_item |
PARTIAL:native action gates/反馈时序尚未对齐 |
| 物品使用反馈 | GC_ITEM_USE/GC_ITEM_UPDATE/special effect/affect/point 触发对应刷新 |
item update store 信号存在;未证明所有消耗品反馈和 UI 声音/特效/状态消费 | PARTIAL |
| 中断/拒绝/乱序 | 不成功的服务器请求不应改变权威客户端状态 | 拆分/快捷出售本地 helper 无拒绝回滚;快捷栏和 UI 部分乐观更新 | GAP |
Implementation-equivalence matrix
| 项目 | 状态 | 证据/限制 |
|---|---|---|
| Preconditions | PARTIAL | 基础 stage/cell/数量检查存在;行动状态、装备类型、交易/商店/攻击中门未在 native 统一执行 |
| Branch structure | PARTIAL | 快捷栏三类入口和正式商店路径存在;本地消耗品、拆分、快捷出售旁路与参考协议链不一致 |
| Algorithms/formulas | PARTIAL | 本地消耗品/价格/拆分算法可独立通过单元场景,但 40250 权威算法在服务端,当前本地复制没有被回包约束 |
| State transition order | PARTIAL | 参考是 request→server→GC update→refresh;当前快捷栏/拆分/快捷出售有 request 后本地立即改变或直接本地改变的分支 |
| Constants/units | PARTIAL | 快捷栏 36/4×8、item cell 和数量宽度有基础覆盖;消耗品 vnum/affect/socket 只在脚本测试出现,未证明与运行时 proto/服务端一致 |
| Timing/event sources | PARTIAL | 当前大量依赖 Godot signal/本地函数;没有真实 ITEM_USE→ITEM_UPDATE/AFFECT/POINT/SPECIAL_EFFECT 的顺序 fixture |
| Resource/data sources | PARTIAL | 正式 UI 可从 item_proto/item_list 获取数据;ConsumableSystem 使用硬编码 vnum/容量,ItemSplitSystem 使用默认价格,均未从 40250 权威资源和服务器结果取值 |
| Protocol side effects | PARTIAL | native 物品、快捷栏、shop 包结构已定位;action gate、音效、sequence、失败通知和对应 GC 反馈未完整闭合 |
| Interruption/failure/cleanup | GAP | 本地拆分/快捷出售没有服务端失败路径;快捷栏乐观状态、断线、换图和延迟 GC 期间的回滚/清理尚未证明 |
Regression evidence
project/test_consumables_parity.gd:36/36 通过,覆盖本地识别、socket 容量、恢复计算、buff 描述和卷轴结果字典;它没有启动M2Client、服务器或 GC feedback。project/test_item_split_parity.gd:19/19 通过,覆盖本地拆分、直接加金币的快捷出售和丢弃提示;由于测试正是调用本地 Array/Dictionary,它不能作为网络等价证据。project/test_taskbar_quickslot_parity.gd:34/34 通过,覆盖 UI 页面、经验球和按钮状态;退出时仍有 91 个 CanvasItem RID、221 个 ObjectDB 对象和资源泄漏警告。project/complex_item_drop_test.gd:退出失败,购买模式下“卖出目标应拒绝拖放”的检查失败;这说明物品窗口/商店目标的交互合同仍有回归问题。project/netplay_test.gd:通过,但只验证 NetPlay 主循环,不覆盖消耗品或快捷出售服务器确认。- 需要新增的回归覆盖:快捷栏 GC add/del/swap 拒绝回滚;
ITEM_USE后无效/成功的 item update、affect、point、special effect 顺序;拆分请求失败不改本地状态;SELL2 服务器价格/金币/物品回包;断线/换图/重复回包和请求乱序。
Deep audit round 2026-09-20
Fresh regression results
godot --headless --path project --script test_consumables_parity.gd:退出码 0,36/36 通过;仍然只是本地识别、socket、恢复计算、buff 描述和卷轴结果字典测试,没有ITEM_USE/GC_ITEM_USE/GC_ITEM_UPDATE服务器回路。godot --headless --path project --script test_item_split_parity.gd:退出码 0,19/19 通过;测试直接检查Array/Dictionary的本地拆分、加金币和清槽,不能证明CG_ITEM_MOVE/CG_SHOP SELL2的服务器确认语义。godot --headless --path project --script test_taskbar_quickslot_parity.gd:退出码 0,34/34 通过;退出时报告 91 个 CanvasItem RID、221 个 ObjectDB 实例、3 个资源和文本 RID 泄漏,因此只计局部 UI PASS,不计干净生命周期 PASS。godot --headless --path project --script complex_item_drop_test.gd:退出码 1;购买模式下“卖出目标应拒绝拖放”的检查失败,说明商店目标的模式门仍有回归。godot --headless --path project --script netplay_test.gd:退出码 0;只覆盖 NetPlay 主循环,未覆盖消耗品、快捷栏或商店服务器确认。
Static parity findings
- 40250 的快捷栏添加/删除/交换只发送请求,
GC_QUICKSLOT_*到达后才修改权威槽位;当前Quickbar::assign/_clear/_swap在 native 请求返回后立即写_state,拒绝、重复或乱序回包可能留下未确认 UI 状态。 - 40250 的
SetQuickPage对页码按QUICKSLOT_MAX_LINE取模并触发统一刷新;当前Quickbar只接受 0..3、维护私有_page,没有统一 page 状态源和参考端刷新入口。 - 40250 的物品使用、移动、物品对物品和出售发送前都经过
__CanActMainInstance,装备路径还检查交易/商店/攻击状态、播放物品音效并发送 sequence;当前 native 仅检查游戏阶段、槽位和整数范围。 ConsumableSystem的本地效果函数没有接入EntityStore的 item/affect/point/special-effect 消费链;它不能替代服务端效果,也没有拒绝、重复、断线、跨图和回滚状态机。ItemSplitSystem::split_item直接修改源/目标数组,没有发CG_ITEM_MOVE并等待两个槽位的GC_ITEM_*;quick_sell_item直接加本地金币并清槽,没有统一走M2Client::shop_sell和服务器价格回包。InventoryUI/ShopUI已存在走 native 请求的正式路径,但与上述本地旁路并存,导致同一功能存在两套权威来源。
Round conclusion
本轮仍为 PARTIAL:协议包的基础构造存在,局部 UI 和本地算法测试通过,但快捷栏确认时序、消耗品反馈闭环、堆叠拆分、快捷出售和失败清理没有与 40250 统一。未修改实现代码。
Implementation round 2026-09-21T13:45Z
本轮修复了合同中两个会绕过 40250 服务端权威状态的实际旁路:
ItemSplitSystem::split_item现在只做源/目标/数量的前置验证,然后调用M2Client::move_item(source, target, count);请求成功返回pending,不再提前改 source/destination 的本地 count,也不再提前发“已拆分”完成信号。ItemSplitSystem::quick_sell_item现在只调用shop_sell(cell, count),不再读取 本地sale_price、增加金币或清空物品;服务端后续GC_ITEM_UPDATE/GC_POINT_GOLD才是唯一完成条件,新增 request 信号区分“已请求”和“已出售”。ShopUI::_drop_to_sell在购买模式下现在直接拒绝拖入出售目标,不发送CG_SHOP SELL/SELL2;只有切换到出售模式后才进入原有 anti-sell、确认框和发包路径。- 回归测试改为记录
CG_ITEM_MOVE/CG_SHOP SELL2请求并断言本地数组、金币在 服务器确认前保持不变;test_item_split_parity20/20、complex_item_drop_test和shop_rules_test均通过。
仍保持 PARTIAL:快捷栏 GC add/del/swap 的拒绝回滚、消耗品真实
ITEM_USE→ITEM_UPDATE/AFFECT/POINT/SPECIAL_EFFECT 包序、断线/换图/重复回包清理,
以及物品音效和完整 sequence 证据仍需后续轮次。
Implementation round 2026-09-21T14:15Z
本轮按 40250 CPythonPlayer 的快捷栏状态时序修复了另一条乐观更新旁路:
Quickbar::assign、_clear、_swap在线且 native 请求成功后不再直接写_state; 它们只发送CG_QUICKSLOT_ADD/DEL/SWAP,等待EntityStore消费对应的GC_QUICKSLOT_*并发出quickslots_changed后,由restore_from_server()重建界面。 因此服务器拒绝、延迟或乱序回包不会被一次“发送成功”伪装成已确认状态。Quickbar::set_page改为按QUICKSLOT_MAX_LINE=4取模,-1和4分别回到第 4 页和第 1 页, 与 40250SetQuickPage的页面语义一致;本地仍保留 36 个服务端槽、显示 4×8 槽。- 现有测试夹具改为显式模拟
GC_QUICKSLOT_*后才更新服务器快照,并新增延迟确认断言;test_skill_inventory_parity45 项、skill_test、mobile_gesture_test和任务栏分页回归通过。
仍保持 PARTIAL:快捷栏使用本身仍需补统一 RequestUseLocalQuickSlot 的失败反馈/动作门,
消耗品 ITEM_USE→ITEM_UPDATE/AFFECT/POINT/SPECIAL_EFFECT 的真实包序、断线/换图清理,
以及物品音效和完整 SendSequence 证据仍未闭合。
Implementation round 2026-09-21T04:10Z — shared item sound source
本轮把消耗品/快捷使用路径依赖的物品音效分支统一到 native M2Client:
item_action_rules.h复刻 40250__GetUseSoundType/__GetDropSoundType的 type/subtype 分支。M2Client::use_item与move_item在行动门通过后、构造 transport 包前发出item_action_sound;GameScene 统一交给 Audio 播放。普通USE_POTION保留参考端的无 use sound 行为。item_action_sound_parity_test.gd和net_bounds_test覆盖弓/箭/防具/首饰/能力提升药/普通药水/符咒/未知 vnum。
快捷栏的实际服务端确认、完整 SendSequence 包序、服务器拒绝反馈和断线重建仍保持 PARTIAL。
Status
PARTIAL。快捷栏增删/交换现在遵循请求→GC_QUICKSLOT_*→重建显示的权威时序,分页也已按 4 页取模;
但快捷栏使用的统一入口、消耗品反馈闭环、真实回包 fixture、音效/sequence 和跨生命周期清理仍未闭合,
因此不能把整个合同标记为 40250 等价。
2026-09-22 死代码删除
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:item_split_system.gd, test_item_split_parity.gd。