# item.drop_pickup ## Scope 审计地面掉落物从 `GC_ITEM_GROUND_ADD`、所有权更新、模型/特效/名条创建,到鼠标点选、走近、500ms 拾取节流、权限拒绝、`CG_ITEM_PICKUP`、`GC_ITEM_GROUND_DEL` 和拾取后物品回包的完整链路。 ## Reference call chain - `PythonNetworkStreamPhaseGameItem.cpp` - `RecvItemGroundAddPacket` 读取坐标、VID、vnum,转换全局/本地坐标后调用 `CPythonItem::CreateItem`。 - `RecvItemOwnership` 调用 `CPythonItem::SetOwnership`;`RecvItemGroundDelPacket` 调用 `CPythonItem::DeleteItem`。 - `PythonItem.cpp` / `ItemData.cpp` / `ItemManager.cpp` - `CreateItem` 先通过 `CItemManager::GetItemDataPointer` 验证 vnum,使用 `CItemData::GetDropModelThing` 创建实际掉落模型,按地形高度/法线处理位置,并挂接 drop effect、掉落声音和掉落动画状态;名条使用 item data 名称。 - `SetOwnership` 只更新已有 VID 的 ground instance 和名条;未知 VID 不创建对象。 - `DeleteItem` 同时删除模型实例和 `CPythonTextTail` 名条;`DeleteAllItems` 用于清理整张地图。 - `PythonPlayer.cpp` / `PythonPlayerInputMouse.cpp` - `SendClickItemPacket` 在观战模式直接拒绝,500ms 节流;读取地面物品 owner、vnum 和 ItemManager anti-flag,非本人且不满足组队/可掉落可赠送条件时触发 `OnCannotPickItem`,否则发送 `SendItemPickUpPacket`。 - `__OnPressItem` 清理旧预约和自动攻击;距离足够时发送 WAIT 状态和拾取请求,否则建立 `MODE_CLICK_ITEM` 预约并持续走近。 - `__ProcessClickItem` 到达约 20 像素内时发送 WAIT、`SendClickItemPacket`、停止并清理预约;物品消失则清理预约。 - `PythonTextTail.cpp` / `CPythonItem::__Pick` - 鼠标拾取先测试实际掉落模型的相交,再回退到文本尾标拾取;owner 名条独立显示并参加尾标排布。 ## Current call chain - `extension/src/net/classic/classic_parser.cpp` / `EntityStore` - 解析 `GC_ITEM_GROUND_ADD`、`GC_ITEM_GROUND_DEL`、`GC_ITEM_OWNERSHIP`,写入 `GroundItem{vid,vnum,x,y,z,owner}` 并通过 `M2Client` 信号同步。 - 当前 parser/store 不查询 ItemManager 以拒绝未知 vnum;未知掉落仍可进入当前状态。 - `project/ui/ground_items.gd` - `_on_added` 生成通用 `item_bag.gr2` 或 BoxMesh,占位模型;按 MapCoord 和地形采样设置位置,创建名字/owner Label3D、稀有光柱和名条防重叠。 - `_on_removed`/`clear_for_map_change` 删除 Godot 节点;`_process` 旋转占位网格、按距离改名字颜色并重新排布尾标。 - `try_pickup`/`try_pickup_vid` 有 3m 步行、5m 骑马、最近优先、500ms 节流和 owner 名字拦截;然后直接调用 `client.pickup_item(vid)`。 - `pick_at` 用投影后的模型点、名字标签和 owner 标签的 64px 最近点选择,不是参考端的实际模型相交 + TextTail Pick。 - `project/net_play.gd` / `player_controller.gd` - `pick_ground_item` 在约 20m?(代码使用 `CLICK_ITEM_PICKUP_CM` 换算)内立即发 WAIT 并尝试拾取,否则建立走近预约;有观战拦截和自动攻击清理。 - `extension/src/net/m2_client.cpp` / `classic_session.cpp` - `pickup_item` 只检查 `is_in_game`、正 VID 和 u32 范围,再发送 `CG_ITEM_PICKUP`;没有参考端的 observer、item proto、owner/party、anti-flag 或 action gate。 - `ground_item_added`/`ground_item_removed`/`item_picked_up` 信号存在,但未证明 `GC_ITEM_GROUND_DEL` 后 inventory item set/update 的严格反馈顺序。 ## Branch matrix | 场景 | 40250 | 当前端 | 结论 | |---|---|---|---| | 掉落创建 | ItemManager 验证 vnum,按 item proto 的 drop model、地形/法线、drop effect、声音和动画创建 | parser/store 建立记录;UI 通用 bag/BoxMesh,无实际 item drop model、effect、sound、drop animation | GAP | | 坐标/地形 | 全局坐标转换后按 Background height/normal 设置模型位置 | MapCoord 转换并 `sample_height + 0.1`,没有法线、模型包围盒和掉落动画落点 | PARTIAL | | 所有权更新 | 只更新已存在 VID;owner 进入独立 TextTail owner 实例 | `mut_ground_owner` 更新并重新发 ground added;UI 重建/更新 owner tag | MAPPED + tests | | 名称与颜色 | 名称取 `CItemData::GetName`,颜色/尾标来自 item data 和 TextTail | 名称优先 proto/item_list;grade/color 有适配,但 `_process` 近距离颜色会覆盖原品阶色 | PARTIAL | | 名条点击 | 实际模型 `Intersect`,再 TextTail `Pick` | 64px 投影点近邻,可能选中不可见/非相交对象或错误标签 | PARTIAL | | 近距离拾取 | 距离约 20 像素内发送 WAIT+pickup;远距离建立 MODE_CLICK_ITEM 走近 | `pick_ground_item` 有走近预约和 WAIT;`GroundItems` 又有 3m/5m 阈值,两个距离层未统一证明 | PARTIAL | | 最近拾取/按键 | `GetCloseItem`/预留动作按参考距离和对象选择 | 最近优先、3m/5m 和 500ms 已有本地测试 | MAPPED + tests | | 所有权拒绝 | owner 不同且非 party/anti-flag 允许时本地拒绝并回调 | 只比较 owner 字符串与本地名字;没有 ItemManager anti-flag 和 party member 分支 | PARTIAL | | 观战/行动门 | `IsObserverMode` 和主角行动状态阻止拾取 | NetPlay 有 observer;native `pickup_item` 未有统一行动门 | PARTIAL | | 拾取协议 | `SendItemPickUpPacket` 写包并 `SendSequence` | ClassicSession 构造 VID 包,但没有序列/副作用等价保证 | PARTIAL | | 拾取成功/失败清理 | server GC ground del 删除模型和名条;后续 item get/set/update 刷新背包 | ground del 删除 Node,item event/物品信号存在;失败、超时、重复回包和预约清理缺少包级测试 | PARTIAL | | 换图/断线 | `DeleteAllItems` 和阶段清理释放全部 ground instance/text tail | `clear_for_map_change` queue_free;延迟释放/generation/断线顺序未完全证明 | PARTIAL | ## Implementation-equivalence matrix | 项目 | 状态 | 证据/限制 | |---|---|---| | Preconditions | PARTIAL | stage/VID、距离、owner 名和 observer 有部分检查;ItemManager vnum/anti-flag/party/action gate 缺失 | | Branch structure | PARTIAL | add/owner/del、最近拾取、走近预约和移动端入口存在;实际模型拾取、权限分支和 drop animation 分支不等价 | | Algorithms/formulas | PARTIAL | 坐标转换、距离、500ms、尾标布局已覆盖;模型包围盒/法线/近似拾取和颜色更新不同 | | State transition order | PARTIAL | reference 是 add→model/text tail→ownership→pickup→server del→inventory feedback;当前 signal→Node 与延迟 queue_free/双距离层顺序未端到端证明 | | Constants/units | PARTIAL | 3m/5m/500ms 有测试;参考 20 像素、真实 item model 尺寸和服务端 cm/m 换算还需统一 fixture | | Timing/event sources | PARTIAL | 当前依赖 Godot `_process`、signals、预约;参考由网络收包、输入状态机、Item/Actor/TextTail 事件驱动 | | Resource/data sources | GAP | 当前 ground model 使用通用 item bag/BoxMesh,没有使用 `CItemData::GetDropModelThing`、drop effect、drop sound、法线等资源链 | | Protocol side effects | PARTIAL | add/del/ownership/pickup packet 基础结构已解析;SendSequence、OnCannotPickItem 全分支和拾取后回包顺序未闭合 | | Interruption/failure/cleanup | PARTIAL | map change 清理有实现;server 拒绝、重复 ownership、延迟删除、断线和旧 VID generation 尚未完整覆盖 | ## Regression evidence - `project/test_ground_pickup_parity.gd`:拾取距离、骑马距离、最近优先、500ms、owner 名拦截、稀有光柱和基础名条通过。 - `project/mob_death_drop_test.gd`:掉落坐标/地形高度、Yang 名称、拾取调用和死亡清理通过。 - `project/p2b_test.gd`:掉落节点、proto 名称、多条名条去重、owner 独立名条和颜色通过,但退出仍有 2 个 ObjectDB 泄漏。 - `project/text_tail_arrange_test.gd`:尾标 AABB 和 owner 排布通过。 - `extension/tests/net_entity_test.cpp`、`extension/tests/net_classic_session_test.cpp`:ground add/del/owner 与经典物品包基础布局通过。 - 未覆盖:未知 vnum、真实 drop model/effect/sound、模型相交拾取、party + anti-flag 权限矩阵、observer/native action gate、CG_ITEM_PICKUP 后 GC_DEL→ITEM_GET/ITEM_SET 顺序、失败/重复/断线/换图竞态。 ## Deep audit round 2026-09-20 ### Fresh regression results - `godot --headless --path project --script test_ground_pickup_parity.gd`:退出码 0;距离、骑马距离、最近优先、500ms、owner 拦截和基础名条通过。 - `godot --headless --path project --script mob_death_drop_test.gd`:退出码 0;死亡脱离、地面坐标/高度、Yang 命名和拾取调用通过。 - `godot --headless --path project --script p2b_test.gd`:退出码 0;地面掉落、名称、名条去重、owner 名条和颜色通过,但退出仍有 2 个 ObjectDB 实例泄漏。 - `godot --headless --path project --script text_tail_arrange_test.gd`:退出码 0;尾标 AABB 与 owner 排布通过。 - `godot --headless --path project --script test_drop_and_cursor_parity.gd`:退出码 0;MapCoord heading、cursor 注册、占位模型创建和点选通过。 - `./build/extension/net_entity_test` 与 `./build/extension/net_classic_session_test`:均退出码 0;只证明 ground add/del/owner 和 pickup 包的基础 native 处理。 ### Static parity findings - 40250 `CPythonItem::CreateItem` 先用 `ItemManager` 校验 vnum,再加载 `GetDropModelThing`,创建 drop effect、drop sound、drop animation,并按地形法线调整实例;当前 parser/store 接受未知 vnum,`GroundItems` 使用通用 `item_bag.gr2` 或 BoxMesh,未接 item-specific model/effect/sound/animation。 - 40250 的 `SetOwnership` 只更新已存在 VID,`DeleteItem` 同时回收 ground instance 与 TextTail;当前 owner 更新通过 `ground_item_added` 信号重建/更新 Godot 节点,仍没有未知 vnum/未知 VID 的严格资源语义和 generation 保护。 - 40250 鼠标拾取优先测试真实掉落模型相交,再回退到 TextTail;当前 `pick_at` 只比较模型/名条/owner 标签的屏幕投影点距离 64px,可能选中不可见或非实际相交对象。 - 40250 `SendClickItemPacket` 有 observer、owner、party/anti-flag 和 action gate;当前 `GroundItems` 只比较 owner 字符串,`M2Client::pickup_item` 只检查游戏阶段、VID 和整数范围,缺少 ItemManager anti-flag、party 分支、native action gate 与 SendSequence 等价语义。 - 当前 `NetPlay` 的 20m 走近/等待路径与 `GroundItems` 的 3m/5m 拾取路径同时存在;它们都能发 `CG_ITEM_PICKUP`,但距离、预约清理、拒绝和重复/延迟回包尚未由同一状态机统一证明。 - 现有 `ground_item_removed` 和 `item_picked_up` 信号存在,但没有包级 fixture 证明 `CG_ITEM_PICKUP → GC_ITEM_GROUND_DEL → GC_ITEM_GET/SET/UPDATE` 的成功、失败、重复、断线和换图时序。 ### Round conclusion 本轮仍为 `PARTIAL`:基础 ground add/del/owner/pickup 协议和局部 UI 回归通过,但真实掉落资源链、模型相交点选、权限矩阵、native action gate 与拾取后的权威回包链仍未与 40250 统一;本轮未修改实现代码。 ## Status `PARTIAL`。当前端已具备掉落状态、坐标、owner 名条、最近拾取和基础协议骨架,但通用占位模型替代了 40250 的 item proto drop model/effect/sound/animation,拾取权限和模型点选分支也不完整;现有本地回归不能标记为完整 1:1,本轮未修改实现代码。