Files
mtgodot-poc/audit/contracts/world.npc-drop-actor-spawn.md
T
shenleiandClaude Opus 5 a989b8f19b chore: delete 106 unreachable *_system.gd and their parity tests
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>
2026-09-22 17:51:52 +09:00

92 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# world.npc_drop_actor_spawn
## Scope
审计 40250 Windows 客户端的 NPC、怪物、石头、建筑/墙体、玩家实体、地面掉落、所有权、私人商店招牌和实体清理链。判断标准是实体是否按相同的包字段、VID 生命周期、分类、可见性、模型/动作资源、名字尾标、掉落所有权和删除时序进入客户端;服务端掉落概率、散落规则或本地活动规则不能因为在当前端有脚本就当作 40250 客户端等价实现。
## Reference call chain
- `UserInterface/PythonNetworkStreamPhaseGameActor.cpp`
- `RecvCharacterAppendPacket` / `RecvCharacterAdditionalInfo` / `RecvCharacterUpdatePacket` 解包 race、character type、parts、affect、位置、速度、名字、公会、等级、mount 等字段;不可见 race 不创建实例;角色新增最终进入 `CNetworkActorManager` / `CInstanceBase`,非零 mount vnum 由角色实例处理。
- `RecvCharacterDeletePacket` 通过 actor manager 删除 VID,并同步通知私人商店消失。
- `RecvOwnerShipPacket` 把 owner VID 与 victim VID 写入网络 actor manager。
- `UserInterface/NetworkActorManager.cpp` / `UserInterface/PythonCharacterManager.cpp` / `UserInterface/InstanceBase.cpp`
- actor manager 负责 add/update/move/delete、VID 查找和实例状态;character manager 负责碰撞、拾取、排序、距离可见性和渲染列表;`CInstanceBase` 按 race/type 选择模型、动作、名字和状态。
- 40250 的可见性由 `CHAR_STAGE_VIEW_BOUND``AFFECT_SHOW_ALWAYS` 和 wall 判定共同决定;离开范围时走 delete/blend-out,数据与渲染实例不是同一层。
- `UserInterface/PythonNetworkStreamPhaseGameItem.cpp` / `UserInterface/PythonItem.cpp` / `UserInterface/PythonTextTail.cpp` / `Packet.h`
- `RecvItemGroundAddPacket` 将全局坐标转本地坐标后调用 `CPythonItem::CreateItem(vid, vnum, x, y, z)`;未知 item data 不创建地面物体。
- `RecvItemOwnership` 调用 `SetOwnership(vid, name)`,更新独立 owner 尾标;`RecvItemGroundDelPacket` 删除地面实例并删除对应 item text tail。
- `CreateItem``CItemData::GetDropModelThing()` 建立掉落模型,注册 item text tail`GetCloseMoney` / `GetCloseItem`、模型拾取和网络 `CG_ITEM_PICKUP` 负责交互,服务器回包才完成真正拾取。
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
- `RecvShopSignPacket` 对空 sign 触发 `BINARY_PrivateShop_Disappear`,非空 sign 触发 `BINARY_PrivateShop_Appear(vid, sign)`;该包不是普通实体 spawn,必须依赖已存在角色 VID。
## Current call chain
- `extension/src/net/classic/classic_parser.cpp` 已将 character add/additional/update/delete、`GC_OWNERSHIP``GC_ITEM_GROUND_ADD/DEL/OWNERSHIP``GC_SHOP_SIGN` 分别写入 `EntityStore`/ground store`M2Client::poll` 将实体和地面变化转为 Godot signal。
- `extension/src/net/entity_store.cpp/.h` 以 VID 保存实体,spawn 是整行替换;地面掉落独立保存 `vid/vnum/pos/owner`,shop sign 是已存在实体的可变字段;未知 VID 的 shop sign/owner 不凭空创建实体。
- `project/net_world.gd` 在数据层保存全部实体,在场景层按主角距离、`AFFECT_SHOW_ALWAYS` 和 wall race 创建/淡出节点;`_on_spawn` 写入分类、名字、parts、affect、速度、mount 和 shop sign`_on_info` 刷新可变字段;`project/ui/mob_view.gd` 负责 NPC/怪物模型、动作、受击和名字补点。
- `project/ui/ground_items.gd` 监听 ground add/del,按 item proto 解析名字/颜色,加载 item bag/money 模型或 fallback BoxMesh,创建名字与 owner 尾标、稀有掉落光柱,按距离和 500ms 节流发送拾取;服务端 ground del 负责最终消失。
- `project/entity_rules.gd``project/metin_spawn_event_system.gd``project/mob_drop_scatter.gd` 等本地 helper 还承担木门/建筑分类、石头活动、掉落散落和所有权保护等自定义规则;这些不等于 40250 Windows 客户端本身的 authority。
## Branch matrix
| 场景 | 40250 | 当前端 | 结论 |
|---|---|---|---|
| 角色/NPC/怪物新增 | add packet→actor manager→`CInstanceBase`→模型/尾标;不可见 race 跳过 | parser/store→`entity_spawned``NetWorld`,可延迟 placeholder 后升级真模型 | PARTIAL:字段链存在,但不可见 race、未知 race、真资源缺失时的失败分支未完整证明 |
| character additional/update | 按包刷新 parts、名字、公会、affect、速度、状态和 shop 相关数据 | `_on_info`/`_apply_field_updates` 按顺序刷新部分字段 | PARTIAL:顺序入口有,角色类型、异常字段、重复/乱序包和实际 model attachment 缺 live 证据 |
| VID 重用 | 删除旧实例后重新 addactor manager 查找不应残留旧状态 | EntityStore whole-row spawnNetWorld 销毁并重建节点 | PARTIAL:设计上接近,但旧节点延迟释放与同 VID 新 spawn 的边界没有完整回归 |
| 远端删除/淡出 | actor manager 删除;尾标、商店和交互引用同步清理 | `entity_despawned``_fade_and_free`,数据层/场景层分离 | PARTIAL:删除链存在,blend-out 期间重复 spawn、目标/尾标/交互引用的完整时序仍需验证 |
| 距离可见性 | `CHAR_STAGE_VIEW_BOUND`、always-show、wall 分支控制实例和渲染列表 | `_update_visibility` 每帧以 20000cm/20010cm、affect bit、wall race 建/淡出 | PARTIAL:基础分支已映射;距离常量、真实 actor manager 淡出和复杂 wall/always-show 组合仍未完全证明 |
| 建筑/墙体/门 | 由 race 与 InstanceBase/碰撞数据决定,墙体不因距离错误消失 | `entity_rules`/cursor shape 识别 door/buildingwall race 强制可见 | PARTIAL:分类与 cursor 入口有;模型、碰撞、阻挡和真实 wall 生命周期未统一证明 |
| 地面掉落新增 | 坐标转换→CItemData drop model→item text tail;未知 vnum 不创建 | ground store→`ground_items`item bag/money 或 BoxMesh fallback→Label3D | PARTIAL:协议、坐标、名字尾标和 fallback 有;真实 drop model/effect/sound/未知 vnum 失败语义不等价 |
| 地面掉落所有权 | `RecvItemOwnership` 更新独立 owner text tail;服务器仍是拾取 authority | owner 写入 ground row/owner_tag,并在本地 pickup 前做名字判断 | PARTIAL:回包链存在;本地 owner precheck 是额外 optimistic gateparty/anti-flag/native action gate 未完整接入 |
| 地面掉落删除/拾取 | `GC_ITEM_GROUND_DEL` 同步删除模型和尾标;拾取成功依赖服务器包序 | ground del signal 删除节点,pickup 只发送请求 | PARTIAL:删除基础链有;`CG_ITEM_PICKUP→GC_DEL→ITEM_GET/ITEM_SET/UPDATE` 的 live 成功、失败、乱序和回滚未证明 |
| 私人商店招牌 | `GC_SHOP_SIGN` 空/非空走 disappear/appear,依赖已有角色 VID | `shop_sign` 进入实体 info`_refresh_shop_sign` 创建/释放 Label3D | PARTIAL:空/非空状态可映射;BINARY 私人商店 UI、重复/未知 VID、重连清理和视觉资源不等价 |
| 怪物死亡与掉落 | 死亡/删除/地面掉落由服务端包和 actor/item manager 驱动 | `mob_view` 死亡表现,活动 helper 可生成本地掉落 | GAPcustom authority):本地散落/活动掉落不能作为 40250 client paritylive server death→drop 时序仍缺证据 |
## Implementation-equivalence matrix
| 项目 | 状态 | 证据/限制 |
|---|---|---|
| Preconditions | PARTIAL | stage、VID、实体/ground store 和部分分类存在;未知 race/vnum、主 VID、模型工厂未就绪和 owner/anti-flag 门控未完整证明 |
| Branch structure | PARTIAL | actor add/info/update/delete、ground add/del/owner、shop sign 主要分支存在;visibility、不可见 race、未知数据和 server rejection 分支不等价 |
| Algorithms/formulas | PARTIAL | 坐标转换、20000cm 可见范围、owner 标签和拾取距离有实现;CItemData 模型选择、碰撞/拾取几何、drop effect 和参考资源算法未统一 |
| State transition order | PARTIAL | parser→store→signal→view 顺序明确;延迟模型升级、淡出、同 VID 重用、ground del 与 owner/sign 乱序的可观察顺序未闭合 |
| Constants/units | PARTIAL | `CHAR_STAGE_VIEW_BOUND`、+10 cull、ground height/pickup 距离、packet fields 有证据;Godot world unit 与 40250 pixel/cm、文本像素度量、模型锚点仍是近似 |
| Timing/event sources | PARTIAL | poll、ground signal、500ms pickup throttle、visibility interval 有;真实 frame cull、blend-out、drop animation/effect/sound、server packet delay 未覆盖 |
| Resource/data sources | PARTIAL | mob/NPC 资源扫描和部分真模型测试通过,但有 4 个 fallbackground 使用通用 bag/BoxMesh,未证明所有 CItemData drop model、特效、音效和牌匾资源 |
| Protocol side effects | PARTIAL | classic parser/session 与 ground/ownership/shop sign 基础路径存在;完整拾取回包、商店 UI、不可见实体过滤和未知 packet 语义缺 live 证据 |
| Interruption/failure/cleanup | PARTIAL | `net_world_vis_test.gd` 的基本可见性/重显/删除回归已通过;地图切换基础清理已有,但实体淡出、地面掉落、尾标、商店、同 VID 重用、断线/重连和模型 fallback 的组合清理未完整证明 |
## Regression evidence
- `project/net_world_vis_test.gd`:通过;覆盖主角进入范围后的远端节点创建、单次创建、离开范围裁剪和删除信号去重。仍不证明真实 actor manager 的 wall/always-show/模型失败组合。
- `project/entity_rules_test.gd`:通过;验证实体分类/可攻击规则,但不证明 40250 的模型、碰撞和 visibility chain。
- `project/forest_mob_render_test.gd`24 个结果、0 failures、4 fallback;有 Godot ObjectDB leak warning,且仍需人工资源 sign-off,因此不能标记 PASS。
- `project/mob_view_test.gd``project/mob_winding_test.gd``project/net_world_push_test.gd`:通过;覆盖单体模型/绕序/受击推开,不能证明完整 actor manager 生命周期。
- `project/test_ground_pickup_parity.gd``project/mob_death_drop_test.gd``project/test_mob_drop_scatter_parity.gd``project/test_metin_spawn_event_parity.gd`:通过;主要覆盖当前端 helper、掉落散落和活动石头规则,不等于 40250 live drop/model/pickup parity。
- `build/extension/net_entity_test``build/extension/net_classic_session_test``build/extension/net_classic_wire_test`:覆盖部分实体、地面包和协议边界;未覆盖上述 visibility 失败、真实资源失败、完整拾取回包、商店牌匾和重连组合清理。
### Active GameLib spawn/property review
- `GameLib/MonsterAreaInfo.cpp` 保存刷怪区域 origin/size、左右上下边界、数量、随机初始位置、方向、group/leader/VID/name 和清理默认值;`Property.cpp`/`PropertyLoader.cpp`/`PropertyManager.cpp` 为地图 object/property 注册提供 CRC 和 token 数据。
- 当前实体生成主要来自服务端 spawn packet 和 `NetWorld`,地图静态刷怪区域/property 并没有一条等价的客户端本地 `MonsterAreaInfo` 消费链;因此本轮只确认参考数据模型和清理边界,不能把网络实体测试当作静态刷怪 parity。
### Active UserInterface NPC data review
- `PythonNonPlayer.h`/`PythonNonPlayerModule.cpp` 以 MMPT mob table 为权威,提供 `LoadNonPlayerData`、vnum/name/type/event/color、VID event lookup 和按等级区间匹配的 mob list;`PythonCharacterModule.cpp` 再用 event type 决定角色 render/behavior。
- 当前 `Metin2Proto`/`EntityStore` 能解析 mob proto 并生成网络实体,但 event type、mob color/name、等级筛选和 unknown vnum/缺记录失败分支没有完整接入 `NetWorld`/NPC view;本轮静态证据确认“能生成一个怪物”不等于 NPC data chain 一致。
- 本轮完成 UserInterface NPC 数据入口静态核对,合同保持 `PARTIAL`
## Deep audit round 2026-09-20
本轮复跑 `project/net_world_vis_test.gd` 时发现固定 100ms 可见性节流会让快速连续帧的结果不稳定;40250 `CPythonCharacterManager::Update` 每帧执行距离检查,因此已改为每帧调用 `_update_visibility()`。连续两次测试均 PASS。该结果只证明当前 Godot 可见性状态机的基础边沿,不足以把 40250 的 actor manager 淡出、wall 判定和真实资源失败分支标为等价。
## Status
`PARTIAL`。当前端已经具备 40250 实体/掉落协议的主要 native parser、VID 数据层、NPC/怪物场景层、地面物体和 shop sign 状态入口;可见性基础回归和每帧更新边界已修复,但掉落模型/特效/音效、建筑碰撞、完整 actor/ground 生命周期和 live 拾取/商店回包证据仍不足。
### 2026-09-22 死代码删除
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`metin_spawn_event_system.gd`, `test_metin_spawn_event_parity.gd`