Files
mtgodot-poc/audit/contracts/world.entity-spawn-state-sync.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

211 lines
20 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.entity_spawn_state_sync
## Scope
审计 Actor 的 Add、AdditionalInfo、Add2、Update、Move、Delete、主角色切换、VID 重用、可见性和实体节点/网络数据同步。目标不是比较函数名,而是确认 40250 的字段、前置条件、分支、调用顺序、资源副作用和清理行为在当前客户端中保持相同;Godot 的节点/信号只是平台适配,不能掩盖网络实体状态的缺失。
## Reference implementation and evidence
- `../40250/Server Client TMP4/ClientVS22/source/UserInterface/NetworkActorManager.cpp`
- `AppendActor`, `UpdateActor`, `MoveActor`, `RemoveActor`, `SetMainActorVID`, `__AppendCharacterManagerActor`, `__RemoveCharacterManagerActor`, `__RemoveDynamicActors`
- Add 时保存完整 `SNetworkActorData`;创建失败时删除数据行;主角色使用立即删除,远端 Delete 使用 `DeleteInstanceByFade`
- `UpdateActor` 的副作用顺序为 armor、weapon、hair、guild、affect、move speed、attack speed、alignment、PK、state flags。
- 同 VID 重新创建时,只有 mount 状态发生切换才保留旧实例位置;否则使用新 Actor 的当前坐标。
- `../40250/Server Client TMP4/ClientVS22/source/UserInterface/PythonNetworkStreamPhaseGameActor.cpp`
- `RecvCharacterAppendPacket`:旧 Add 的两包链;PC/NPC 暂存,其他种族直接完成 Add。
- `RecvCharacterAdditionalInfo`:合并暂存 Actor;VID 不匹配时记录错误,不新建 Actor。
- `RecvCharacterAppendPacketNew`Add2 完整填充字段,并在 `IsInvisibleRace` 时丢弃。
- `RecvCharacterUpdatePacket`:传递 state、affect、装备、速度、guild、alignment、PK、mount。
- `RecvCharacterDeletePacket`:删除 Actor 并清理相关附属对象。
- `../40250/Server Client TMP4/ClientVS22/source/UserInterface/PythonCharacterManager.cpp`
- `DeleteInstance` 立即移除;`DeleteInstanceByFade` 进入 dead instance list 后延迟销毁。
- `../40250/Server Client TMP4/ClientVS22/source/UserInterface/InstanceBase.cpp`
- `SCreateData` 承接 race、parts、移动/攻击速度、guild、state、affect、mount 等创建字段。
## Current implementation and evidence
- `extension/src/net/classic/classic_parser.cpp`
- Add 的 PC/NPC 使用 `m_pending_actor` 等待 AdditionalInfo;非 PC/NPC 直接 `mut_spawn`
- Add2 现在先执行与 40250 相同的隐身种族过滤,再一次性构造完整 `Entity` 并调用 `mut_spawn_full`;旧 Add 的非 PC/NPC 也保留 `state_flag``affect_flag`
- Update 现在把 state、affect、装备、速度、guild、alignment、PK、mount 一次传入 `mut_char_update`,不再拆出独立 affect mutation。
- pending 现在与 40250 一样只有一个静态暂存记录;后一个旧 Add 覆盖前一个,AdditionalInfo 只能完成最后暂存 VID。Delete 后迟到 AdditionalInfo、map reset 后迟到包仍需继续核对。
- `extension/src/net/entity_store.cpp`
- `apply(GC_CHARACTER_ADD/ADD2/UPDATE)` 现在保留 state、affectAdd2 还完整保存 empire、guild、alignment、PK、mount,并过滤三种隐身 race。
- `apply(GC_CHAR_ADD_INFO)` 现在必须命中已有 Entity,未知 VID 只记录忽略,不再通过 `touch` 创建部分 Entity。
- `mut_spawn_main` / generic `GC_MAIN_CHARACTER` 现在统一产生一次 `MainSet` 边沿;首次主角为 `Spawn -> MainSet`,重复主角包仅为一次 `MainSet`,不再依赖重复事件或场景轮询。
- `project/net_world.gd`
- `_on_spawn` 会替换旧节点;已按 40250 改为仅在 mount 状态切换时保留旧 global position,普通重建使用新包坐标,并始终使用新包朝向。
- `_fading` 只跟踪旧节点的 blend-out;收到同 VID 新 Spawn 时会立即解除门控并重建 live 节点,旧 tween 独立完成。
- `_on_info` 只更新已有节点,不会修复 generic AdditionalInfo 误创建的部分节点。
- `_on_main_set` / `set_local_vid` 没有完全复刻 `SetMainActorVID` 对远端 Actor 字典和动态实例的清理范围。
- `extension/src/net/m2_client.cpp`
- classic Add2 现在由 `mut_spawn_full` 产生一条完整 Spawn,再由 `pump_classic` 统一向 Godot 发出;不会再拆成 Add2 Spawn + Info + affect 三条事件。
## Branch matrix
| 场景 | 40250 行为 | 当前端行为 | 结论 |
|---|---|---|---|
| 旧 Add 新 VID | 保存 Add 字段;PC/NPC 等待 AdditionalInfo,其他种族直接 AppendActor | PC/NPC 使用单个 static-equivalent pending;非 PC/NPC 现在完整保存 state/affect | PARTIAL |
| AdditionalInfo 命中 | 合并暂存记录后只创建完整 Actor | classic parser 命中 pending 后完整 Spawngeneric apply 命中已有 Entity 后发一次 Info | PARTIAL |
| AdditionalInfo 未命中 | 记录错误,不创建 Actor | classic parser 与 generic apply 均不创建 Entity | MAPPED |
| Add2 新 VID | 隐身种族直接丢弃;其他分支一次性填满字段后 AppendActor | 两条 parser/apply 路径均过滤隐身 race,并一次性填满字段后 `mut_spawn_full` | PARTIAL |
| Update 已有 VID | 按固定顺序更新装备、guild、affect、速度、alignment、PK、state、mount | shared mutator 一次更新完整字段并发一次 Info;平台渲染副作用仍需核对 | PARTIAL |
| Update 未知 VID | 记录错误,不创建 | mutator 丢弃;generic update 丢弃 | MAPPED(字段完整性仍需补测) |
| Delete 远端 | `DeleteInstanceByFade`,并清理附属引用 | Godot 远端 fade + queue_free;同 VID 新 Spawn 可立即建 live replacement | PARTIALdead-list 与 Godot 双节点短时并存仍是平台适配 |
| Delete 主角色 | 立即删除 | main/local 由 game scene 和 net world 分层清理 | PARTIAL |
| 同 VID 重用 | 非 mount 切换使用新坐标;mount 切换可保留旧位置,朝向使用新包 | `_on_spawn` 已按 mount 状态决定是否保留位置,朝向统一取新包;淡出期间允许立即替换 live 节点 | PARTIAL:旧 dead-list/tween 的跨帧可见性仍是平台适配 |
| Main VID 切换 | 清空 Actor 字典、重置位置,并清理动态实例 | 只设置 main/local VID 并移除本地节点;通常依赖 map reset | PARTIAL |
| 可见性 | distance、wall、AFFECT_SHOW_ALWAYS、动态实例规则 | data/node 两层可见性和 fade 已有对应测试 | MAPPEDframe/淡出时序为平台适配) |
## Equivalence matrix
| 项目 | 状态 | 证据/缺口 |
|---|---|---|
| Preconditions | MAPPED | unknown AdditionalInfo/update 不创建;Add/Add2 隐身 race 在 parser/apply 两条入口均提前丢弃 |
| Branch structure | PARTIAL | Add/AdditionalInfo/Add2/Update 的隐身、PC/NPC、主角色、未知 VID 和交错旧 Add 已覆盖主要分支;Delete/map reset 后迟到包仍缺 fixture |
| Algorithms/formulas | PARTIAL | 可见距离和 EntityStore tick 有对应实现;同 VID 的 mount 位置保留和淡出期间立即重建条件已对齐,旧 dead-list/tween 的跨帧并存仍是平台适配 |
| State transition order | PARTIAL | Add2 已变为单次完整 SpawnUpdate 已合并为一次 shared mutator;渲染桥接的逐字段副作用顺序仍需验证 |
| Data completeness | MAPPED | Add/Add2/Update/AdditionalInfo 的协议字段已落入 EntityStore,包含 state/affect/empire/guild/alignment/PK/mount |
| Constants/units | PARTIAL | VID、位置、yaw、移动速度和 mount 标志要在 Add/Update/Move 组合包中逐字段验证 |
| Timing/event sources | PARTIAL | 40250 manager update/dead list 与 Godot frame、signal、queue_free 的交错未完成证明 |
| Resource/data sources | PARTIAL | 40250 `SCreateData`/`CInstanceBase` 与当前 model factory 的失败、替换、升级 ownership 未完全覆盖 |
| Protocol side effects | PARTIAL | Add/Info/Update/Delete 入口已映射;Add2/unknown AdditionalInfo 已对齐,渲染层删除/更新副作用仍需证明 |
| Interruption/failure/cleanup | PARTIAL | 模型创建失败、升级中 Delete、pending stale、Delete/map reset 后迟到包和主 VID 清理仍需自动测试 |
## Required tests
- 旧 Add -> AdditionalInfo 的命中、未知 VID、交错两个 Add、Delete 后迟到 AdditionalInfo、map reset 后迟到 AdditionalInfo。
- Add/Add2 的完整字段快照:race/type/position/yaw/speeds/state/affect/empire/guild/alignment/PK/parts/mount/name。
- 隐身种族在旧 Add 和 Add2 中均不产生 Entity/node。
- Update 前后 state、affect、装备、guild、速度、alignment、PK、mount 的字段和事件顺序。
- Add -> Info -> Update -> Delete -> 同 VID Add,分别覆盖普通远端、mount 切换、非 mount 切换、淡出未完成和模型升级中;淡出未完成时新 Add 必须立即建 live replacement。
- 主 VID 切换时旧 Actor、HP、尾标、target、hover、模型和动态实例的清理顺序。
- 模型工厂返回失败、map reset 中断、重复 Delete、重复 Add 和跨帧 signal 的一致性。
## Deep audit round 2026-09-20
本轮补做了参考实现的创建/重建/可见性时序核对,并运行实体层与 Godot 节点层回归:
1. 40250 `CNetworkActorManager::Update()``CPythonNetworkStream::OnProcess()` 中每次网络处理调用 `__UpdateMainActor()`,再遍历 Actor 字典;当前 `NetWorld._process()` 已按每个 Godot frame 调用 `_update_visibility()`,不再使用 100ms 节流。`net_world_vis_test.gd` 已覆盖远端进入范围、单次建节点、离开范围淡出、数据层保留和重新进入,当前通过;旧版本的 4 项可见性失败已不再复现。
2. 参考 `__AppendCharacterManagerActor()` 对已有 VID 重建时读取旧坐标,但只有从骑乘到非骑乘或反向切换才覆盖新创建数据的坐标;当前 `_on_spawn()` 已改为只在 mounted 布尔状态切换时保留旧位置,普通同 VID Add/换装使用新坐标,朝向始终使用新包。
3. 参考显式 `RemoveActor()` 对普通远端 actor 走 `DeleteInstanceByFade`,主 actor 走立即 `DeleteInstance()``_on_despawn()` 现已按 `_main_vid` 区分这两个分支。`_on_spawn()` 仍在同 VID 新 Spawn 时解除 `_fading` 并立即创建 live replacement,旧 tween 只负责旧节点。重复 Delete 和跨帧双节点生命周期仍需继续核对。
4. 本轮已修复 Add/Add2/Update 的字段和分支差异:classic parser 的旧 Add 非 PC/NPC、Add2、Update 与 generic `EntityStore::apply` 均通过完整字段结构或 shared mutator 保留 state/affect 等字段;Add2 过滤 20025/20038/20039;未知 generic AdditionalInfo 不再 `touch()` 创建半实体。随后又将 classic pending 从 keyed map 收敛为 40250 的单个 static-equivalent 槽,并覆盖交错 AddDelete/map reset 后迟到 AdditionalInfo 和渲染层副作用顺序仍需验证。
5. `SetMainActorVID` 参考实现会清空 NetworkActor 字典、重置主坐标;当前 `NetWorld::_on_main_set()` 只改 `_main_vid` 并重刷名字色,旧远端节点、动态实例和 UI 引用的清理依赖外层 map reset,主 VID 切换的独立语义未证明。
## Evidence run
- `./build/extension/net_entity_test`PASS,证明 EntityStore 的部分 Add/Info/Update/Delete、字段和状态队列夹具仍可运行。
- `godot --headless --path project --script net_world_vis_test.gd`:PASS,覆盖主角移动后可见实体创建、远端节点一次性构建、离开范围剔除、`entity_removed` 一次性事件、强制可见实体和重新进入。
- `project/gamescene_test.gd`:PASS,但该测试验证的是场景装配/实体镜像/初始定位,不覆盖上述可见性节流和同 VID 淡出竞态。
- 因此本合同继续 `PARTIAL`,可见性和字段回归已通过,但 Delete/map reset 后迟到包、主角色清理和渲染层副作用顺序仍未完全证明。
### Active UserInterface actor-sync review
- `NetworkActorManager.h` 保存每个 actor 的服务器时间、目标位置/持续时间、当前/目标坐标、owner/victim、affect flags 和 `UpdateActor/MoveActor/Update``PythonCharacterManager` 负责将其映射到可渲染实例和删除列表。
- 当前 `EntityStore`/`NetWorld` 以 packet 快照和 Godot interpolation 驱动 remote view,基础移动同步可用,但没有参考端 actor manager 的 server-time movement、owner/victim 和统一删除/fade/update transform 入口;这也是远端动作事件、VID 复用和跨图旧包清理的剩余风险。
- 本轮完成 NetworkActorManager 与角色 manager 接口静态核对,实体同步合同继续为 `PARTIAL`
## Implementation fix round 2026-09-22T04:30Z
沿 40250 `NetworkActorManager::__AppendCharacterManagerActor()`
`CPythonNetworkStream::RecvMainCharacter()` 复核同 VID 重建及主实例注册边沿,补齐了两处已证实差异:
- `NetWorld._on_spawn()` 现在只在旧/新实例的 mounted 布尔状态发生变化时保留旧 global position;普通重生、换装或 VID 重用使用新 Add 数据的坐标。无论是否上下马,旋转都使用新包的 `angle_deg`
- `EntityStore::mut_spawn_main()``EntityStore::apply(GC_MAIN_CHARACTER)` 现在在首次主角建立时按 `Spawn -> MainSet` 发出一次注册边沿,重复主角包只发一次 `MainSet`,消除了旧实现的缺失/重复通知。
新增回归:
- `project/net_world_respawn_test.gd`:普通同 VID 重建坐标/朝向、上下马重建保留旧位置但使用新朝向。
- `extension/tests/net_entity_test.cpp`:首次主角注册和重复主角包的 `MainSet` 次数。
修复前上述测试分别能够复现普通重建位置/朝向错误、上下马朝向错误以及 `MainSet` 缺失/重复;修复后
`build/extension/net_entity_test``net_world_respawn_test.gd` 通过。Add2 隐身种族、AdditionalInfo 未命中、旧 Add 字段完整性已在后续修复轮补齐;淡出期间同 VID 重用和主 VID 切换清理当时仍保持 `PARTIAL`
## Implementation fix round 2026-09-22T05:10Z
按 40250 `PythonNetworkStreamPhaseGameActor::RecvCharacterAppendPacket*`
`RecvCharacterAdditionalInfo``RecvCharacterUpdatePacket`
`NetworkActorManager::AppendActor/UpdateActor` 的完整字段链复核并修复:
- classic 旧 Add 的即时实体现在以完整 `Entity` 写入 state/affectPC/NPC pending 合并继续一次完整 Spawn。
- classic Add2 和 generic Add2 都执行 `IsInvisibleRace` 等价过滤,并一次性保存 name、parts、state、affect、empire、guild、alignment、PK、mount 等全部字段。
- classic/generic Update 统一经 `mut_char_update` 更新 state、affect、装备、速度、guild、alignment、PK、mount,并在发生变化时只发一次 Infomount 变化仍发 mount cue。
- generic AdditionalInfo 仅接受已存在的 actor;未知 VID 被忽略,不创建半实体,并补齐 empire 和字段变更检测。
新增/扩展回归:
- `extension/tests/net_entity_test.cpp`Add/Add2/Update 全字段、隐身 Add2、未知 AdditionalInfo、单次 Spawn/Info。
- `extension/tests/net_classic_session_test.cpp`:40250 Add2 全字段、三种隐身 race、未知 AdditionalInfo 和清理。
修复前新增回归可复现 state/affect 丢失、Add2 元数据丢失、隐身实体错误出现、unknown AdditionalInfo 半实体和事件计数差异;修复后两个 native 测试均通过。淡出期间同 VID 重用、pending 交错清理、主 VID 切换清理和 Godot 可见性时序仍保持 `PARTIAL`
## Implementation fix round 2026-09-22T05:35Z
按 40250 `RemoveActor -> DeleteInstanceByFade` 与后续 `AppendActor ->
__AppendCharacterManagerActor` 的实例生命周期复核并修复:
- `_on_despawn()` 的旧节点继续渐隐;如果渐隐未结束就收到同 VID 的新 Spawn,`_on_spawn()` 解除旧 `_fading` 门控并立即创建新的 live 节点,符合参考端 dead-list 不占用 `GetInstancePtr()` 的行为。
- 旧 fade tween 只释放旧节点,不会删除新节点;新节点使用新的权威位置和旋转。
扩展 `project/net_world_respawn_test.gd`,修复前可复现淡出期间同 VID Spawn 被直接丢弃;修复后覆盖立即重建、位置以及旧 tween 完成后的新节点存活。
## Implementation fix round 2026-09-22T05:55Z
按 40250 `PythonNetworkStreamPhaseGameActor::RecvCharacterAppendPacket`
`static SNetworkActorData s_kNetActorData`
`RecvCharacterAdditionalInfo` VID 匹配分支复核并修复:
- `ClassicParser::m_pending_actor` 从 keyed map 改为单个 `std::optional<Entity>`,后一个 PC/NPC 旧 Add 覆盖前一个,避免当前端比 40250 多出可交错完成的 pending actor。
- AdditionalInfo 只有在 VID 命中最后暂存记录时才调用一次 `mut_spawn_full`;被覆盖 VID 的迟到 AdditionalInfo 只记录未处理 header,不创建实体。
新增 `net_classic_session_test.cpp` 的交错旧 Add 回归,修复前可复现两个 pending 和第一个 VID 错误生成;修复后 `net_classic_session_test``net_entity_test` 通过。Delete 后迟到 AdditionalInfo、map reset 后迟到包、主 VID 清理和渲染副作用顺序仍保持 `PARTIAL`
## Evidence reconciliation 2026-09-22T06:15Z
重新以当前工作树运行可见性回归:`net_world_vis_test.gd` 通过。当前 `_process()` 已逐帧执行 `_update_visibility()`,与 40250 `CNetworkActorManager::Update()` 的检查频率一致;因此旧审计中关于 100ms 节流和 4 项可见性失败的描述已更新为历史发现,不再作为当前缺陷。Add2 的当前 pump 也已改为单次完整 Spawn,旧的“Spawn + Info + affect 拆分”描述同步更正。
## Implementation fix round 2026-09-21T07:08Z
`CNetworkActorManager::__RemoveCharacterManagerActor` 的主/远端分支修正
`NetWorld._on_despawn()``_main_vid` 现在走立即删除,普通远端 actor 继续走
`DeleteInstanceByFade` 等价的渐隐路径。新增 `net_world_respawn_test.gd` 断言主角
不会进入 `_fading`,同时保留普通远端 actor 的同 VID 重建和旧 tween 隔离验证。
`net_world_respawn_test``net_world_reset_test``net_world_vis_test`
`fly_target_lifecycle_test``netbridge_test` 均通过。主 VID 切换、Delete/map reset
后的迟到包和完整渲染副作用顺序仍保持 `PARTIAL`
## Implementation fix round 2026-09-21T07:15Z
继续核对 `RecvCharacterAdditionalInfo` 的单槽暂存生命周期时发现:旧 Add
等待 AdditionalInfo 期间如果先收到 Delete,或随后进入 Loading/warp,参考端的
静态 `SNetworkActorData` 已失效;当前端原先仍保留 `m_pending_actor`,迟到的
AdditionalInfo 可以错误生成旧角色。现已在匹配 VID 的 Delete 中清槽,并新增
`ClassicSession::reset_for_map_change()`,将 parser 暂存和 EntityStore 一起清理。
新增回归覆盖 Delete 后 AdditionalInfo、map reset 后 AdditionalInfo
`net_classic_session_test``net_entity_test``mtgodot` 构建通过。主 VID
切换、跨连接迟到 Update/Move 和渲染副作用顺序仍保持 `PARTIAL`
## Status
`PARTIAL`。本轮已完成 Add/Add2/Update/AdditionalInfo 的主要协议字段、前置条件、事件粒度、单槽 pending、逐帧可见性和淡出期间同 VID 立即重建;仍必须补齐 Delete/map reset 后迟到包、主角色清理和 Godot 渲染副作用顺序的独立证明,不能宣称与 40250 完全等价。
## Implementation fix round 2026-09-21 — SetMainActorVID lifecycle boundary
本轮继续核对 40250 `CNetworkActorManager::SetMainActorVID`。参考实现收到新的主角色
VID 时会重置主坐标并清空 `m_kNetActorDict`,旧角色的 live instance、目标和临时 actor
状态不会带入新的主角色生命周期。当前端此前的 `NetWorld._on_main_set()` 只更新
`_main_vid` 并重算名字颜色;如果没有先经过完整 `world_reset`,旧远端节点、target/
hover、HP/击退同步、伤害队列和聊天尾标仍可能残留。
现在主 VID 发生实际变化时,`_on_main_set()` 会先解除目标特效,立即删除所有旧 live
actor,清空模型升级队列、sync-push/push、伤害队列、聊天尾标和渐隐索引,再注册新的
主 VID;相同 VID 的重复通知不触发清理。新增 `net_world_main_switch_test.gd` 覆盖旧
远端节点、target/hover 和临时表现状态在切换边沿全部清除,并与
`net_world_reset_test.gd``net_world_respawn_test.gd``net_world_vis_test.gd` 一起通过。
本轮只闭合了 Godot presentation 层对应 `SetMainActorVID` 的清理边沿;EntityStore 中
已有快照在切换后重新可见的生成时序、旧渐隐节点的最终销毁、跨连接迟到 Move/Update
以及完整渲染副作用顺序仍保持 `PARTIAL`