# 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、affect;Add2 还完整保存 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 后完整 Spawn;generic 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 | PARTIAL:dead-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 已有对应测试 | MAPPED(frame/淡出时序为平台适配) | ## 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 已变为单次完整 Spawn,Update 已合并为一次 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 槽,并覆盖交错 Add;Delete/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/affect;PC/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,并在发生变化时只发一次 Info;mount 变化仍发 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`,后一个 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`。