- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
281 lines
25 KiB
Markdown
281 lines
25 KiB
Markdown
# combat.death-exp-drop
|
||
|
||
## Scope
|
||
|
||
比较普通攻击/技能命中后的动作事件、`GC_DAMAGE_INFO`、`GC_DEAD`、主角死亡、`POINT_EXP` 增量、`GC_CREATE_FLY(FLY_EXP)`、地面掉落、归属校验、重复事件、复活和地图切换清理。
|
||
|
||
## Reference call chain
|
||
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::RecvDamageInfoPacket`
|
||
- 读取目标 VID、伤害值和 `EDamageFlag`,交给角色管理器显示伤害/受击表现;DAMAGE_FLYING 等特殊 flag 进入击飞状态。
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::RecvDeadPacket`
|
||
- 找到 `CInstanceBase` 后调用 `Die`;若 VID 是主角,非决斗时通知 `OnGameOver`,并调用 `CPythonPlayer::NotifyDeadMainCharacter` 清除自动攻击目标。
|
||
- `UserInterface/InstanceBaseBattle.cpp::CInstanceBase::Die`
|
||
- 卸马鞍、清 AFFECT、取消选中/目标,再进入 `CActorInstance::Die`;底层 `m_isRealDead` 门保证死亡动作只启动一次。
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::RecvPointChange`
|
||
- 对所有点数先 `ShowPointEffect(Type, VID)`;主角更新 `CPythonPlayer::SetStatus` 和 HUD,等级/属性/技能/金币走专门刷新;`POINT_EXP` 使用 `TPacketGCPointChange.amount` 作为本次权威增量。
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp::RecvCreateFlyPacket`
|
||
- 只按 `bType/startVID/endVID` 创建客户端飞行表现;经验数值仍来自 `POINT_EXP`,经验球不能替代点数包。
|
||
- `UserInterface/PythonNetworkStreamPhaseGameItem.cpp::RecvItemGroundAddPacket` / `RecvItemGroundDelPacket`
|
||
- 创建/删除地面物品、记录 vnum/坐标/owner;拾取请求由客户端发送,归属和结果由服务端裁决。
|
||
- `UserInterface/PythonPlayer.cpp::NotifyCharacterDead`
|
||
- 目标死亡时清除当前 target,避免死亡实体继续被攻击/显示目标框。
|
||
- `GameLib/FlyingData.cpp` / `PythonEffectModule.cpp`
|
||
- `FLY_EXP` 只负责经验球视觉的起点、终点和资源类型,不本地计算经验。
|
||
|
||
## Current call chain
|
||
|
||
- `extension/src/net/classic/classic_parser.cpp`
|
||
- 解析 `GC_DAMAGE_INFO`、`GC_DEAD`、`GC_POINT_CHANGE`、`GC_CREATE_FLY`、`GC_ITEM_GROUND_ADD/DEL`,写入 `EntityStore`。
|
||
- `extension/src/net/entity_store.cpp::mut_set_point` / `mut_dead` / `mut_ground_add`
|
||
- 保存主角绝对点数和 `POINT_EXP.amount`,设置实体 HP/dead,维护地面掉落队列和飞行 cue;`take_exp_gain()` 只消费一次累计增量。
|
||
- `extension/src/net/m2_client.cpp::_process` / classic mirror
|
||
- 将 `points_changed`、`vitals_changed`、去重后的 `entity_dead`、`ground_item_added/removed`、`fly_cue` 和 `damage` 发给 GDScript。
|
||
- `project/net_world.gd::_on_dead` / `_on_damage` / `_on_fly`
|
||
- 隐藏 HP/名字/目标效果,标记死亡并延迟渐隐尸体;显示受击表现;对 `FLY_EXP` 生成三枚追踪经验球并指向本地主角。
|
||
- `project/net_play.gd::_on_points` / `_trigger_exp_gain` / `_clear_target`
|
||
- 优先使用 `exp_gain`,其次从绝对经验差计算反馈;HUD 更新经验/等级;目标死亡时清目标并记录经验球起点。
|
||
- `project/ui/ground_items.gd`
|
||
- 贴地生成掉落物、显示 owner,并按 40250 `GetCloseItem` / `SendClickItemPacket`
|
||
实现 `DISTANCE_APPROX` 300cm、VID tie-break、party/anti-flag 归属检查和
|
||
`pickup_item` 请求。
|
||
- `project/ui/death_ui.gd` / `project/ui/quickbar.gd`
|
||
- 主角死亡显示复活面板并禁用快捷栏;复活后的正 HP/vitals 解除面板。
|
||
|
||
## Audit findings (2026-09-20)
|
||
|
||
### 已对齐的部分
|
||
|
||
- `GC_POINT_CHANGE` 的 `POINT_EXP.amount` 已在 classic 和 m2dev 两条解析路径保留并累计;表现层不再只比较绝对 EXP,因此跨等级 EXP 回零仍能显示增量。
|
||
- `GC_DEAD`、HP<=0、`GC_DAMAGE_INFO`、目标清除、死亡实体不可选/不可阻挡、地面物品添加/删除和 owner 拒绝路径已有实现。
|
||
- `GC_CREATE_FLY` 的经验球只负责视觉;当前 `_trigger_exp_gain` 不再额外合成第二批经验球,避免一个服务端飞行包造成双倍表现。
|
||
- 掉落物坐标转换、地面贴合、owner 独立文本、普通/骑马拾取距离以及服务端拾取请求已有回归证据。
|
||
|
||
### 材料差异
|
||
|
||
- 40250 主角收到 `GC_DEAD` 时会执行 `OnGameOver`、清除自动攻击并进入死亡 Actor 状态;当前 `entity_dead` 主要被 DeathUI/Quickbar 消费,`NetPlay` 没有统一把主角死亡传给 `PlayerController.stop`/动作锁和自动攻击清理,主角节点若正在点地/WASD 可能继续响应移动。这是和用户此前“死亡后仍有动作/控制”风险直接相关的 P0 差异。
|
||
- 40250 `CInstanceBase::Die`/`CActorInstance::Die` 有 `m_isRealDead` 一次性门、清 affect、卸马鞍、OnUnselected/OnUntargeted;当前 `_on_dead` 依赖 `dead_seen` 和 `meta` 的外围去重,死亡视觉、NetWorld、PlayerController、技能/钓鱼/自动攻击状态没有一个共同的 death transition,重复 `GC_DEAD`/HP<=0/重建时序仍缺端到端矩阵。
|
||
- 40250 的 `RecvPointChange` 对每个点类型先显示 point effect,并按 POINT_LEVEL/属性/技能/金币走分支刷新;当前 `_on_points` 只完整处理经验、等级、HUD 数值和少量音效,普通 POINT_* 的上浮数字/point effect、金币 amount 专门反馈和属性/技能窗口刷新没有完全等价。
|
||
- 40250 `RecvCreateFlyPacket` 要求起点和终点 actor 均存在,否则只丢弃视觉;当前为支持主角未在 `_by_vid` 的结构做了 `_local_node` 特判,这属于合理平台适配,但远端起点/终点缺失、目标坐标 fallback、地图切换时飞行队列清理仍需要协议级对照。
|
||
- 40250 `CInstanceBase::Die` 并不负责本地计算经验;经验只来自服务端 `POINT_EXP.amount`。当前离线/事件路径仍存在 `spawn_exp_fly` 和 `_trigger_exp_gain` 两套可调用入口,需要继续审计是否有任何非权威路径把本地掉落/经验反馈当成服务端结果。
|
||
- 当前掉落归属检查使用名称匹配,参考端由服务端 owner/name 裁决且 `CPythonItem` 的创建、删除和拾取响应受网络包序约束;名字为空、重名账号、先拾取后 DEL、重连 catch-up 和 owner 更新的结果还没有包级记录。
|
||
- 当前死亡尸体使用固定 2.5 秒后 fade,参考端由 `CInstanceBase`/CharacterManager 的删除与 fade 生命周期驱动;服务端提前 `GC_CHARACTER_DEL`、重新 `GC_CHARACTER_ADD`、死亡后地图切换时,旧 tween、目标效果、经验球和动作状态的统一清理还未完全证明。
|
||
|
||
## Equivalence matrix
|
||
|
||
| 项目 | 状态 | 备注 |
|
||
|---|---|---|
|
||
| Preconditions | PARTIAL | VID、目标、owner、点数索引和服务端权威增量已有检查;主角死亡门、起止 actor 和重连包序不完整 |
|
||
| Branch structure | PARTIAL | DAMAGE/DEAD/POINT_EXP/FLY_EXP/ground add-del 主分支已映射;POINT_* 专门刷新、主角 GameOver、重复死亡和归属边界未闭合 |
|
||
| Algorithms/formulas | PARTIAL | EXP amount 优先、绝对值 fallback、经验球追踪和拾取距离已对齐;死亡门、point effect 和 owner 裁决仍不同 |
|
||
| State transition order | PARTIAL | 网络包→EntityStore→signals→NetWorld/HUD 主顺序已对齐;主角死亡→停止输入/清自动攻击→复活恢复未统一 |
|
||
| Constants/units | PARTIAL | `POINT_EXP.amount`、经验球数量/速度和拾取范围已有实现;死亡 fade、动画持续时间和完整 point effect 常量未闭合 |
|
||
| Timing/event sources | PARTIAL | `GC_CREATE_FLY` 与 `POINT_EXP` 分离、points dirty 单次消费已测;真实攻击命中窗→死亡→经验→掉落包序仍缺记录 |
|
||
| Resource/data sources | PARTIAL | 伤害/死亡/经验球/掉落基础资源可用;完整死亡动作、point effect、掉落模型和 item/mob 失败回退未全等价 |
|
||
| Protocol side effects | PARTIAL | dead/point/fly/ground packets 已解析;主角 GameOver、自动攻击清除、point effect 和拾取结果副作用未全闭合 |
|
||
| Interruption/failure/cleanup | PARTIAL | 基本尸体 fade、删除和地图 reset 已有;重复死亡、MISS/无效目标、重连、复活、跨图和待飞行物清理仍需矩阵 |
|
||
|
||
## Implementation fix round 2026-09-21
|
||
|
||
- `NetPlay::_on_entity_dead` 现在区分目标死亡和主角死亡;主角死亡会清理自动攻击、预约动作、技能/钓鱼状态,锁住 `PlayerController` 的点地与 WASD 输入,并阻止网络状态处理。
|
||
- `PlayerController.set_dead` 增加死亡/复活状态门;正 HP `vitals_changed` 会恢复主角控制。`EntityStore` 与 `M2Client.dead_seen` 同时修复正 HP 清除死亡边沿,避免复活后下一次死亡信号丢失。
|
||
- `test_combat_death_pickup_entry_parity.gd`、`netplay_test.gd`、`net_entity_test.cpp` 覆盖死亡、移动清理、复活恢复和再次死亡所需的状态边沿;本轮未宣称 POINT_* 特效、真实服务端包序和跨图清理全部等价。
|
||
|
||
本合同继续为 `PARTIAL`:经验/掉落权威链、完整死亡动画/复活 UI、真实服务器包序和异常清理仍需后续审计。
|
||
|
||
## Regression evidence
|
||
|
||
- `extension/tests/net_state_queue_test`:`POINT_EXP.amount` 累计和一次性消费、死亡状态和网络状态队列。
|
||
- `project/mob_death_drop_test.gd`:怪物死亡目标/HP/名字/影子清理、掉落坐标贴地、owner/拾取/删除。
|
||
- `project/combat_parity_test.gd`:防御球、击退阻挡、攻击锁、伤害表现和死亡抗击退相关分支。
|
||
- `project/playable_combat_test.gd`:无目标、超距、死亡目标/自身、冷却和断线时不发送错误战斗请求。
|
||
- `project/gamescene_test.gd`:地图 reset 时待发攻击/飞行和 NetPlay 状态清理。
|
||
|
||
这些测试证明当前端的若干主链可工作,但没有真实 40250 服务器的同一场战斗包序、主角死亡后输入门、POINT_* effect 和重复/乱序事件记录,因此合同保持 `PARTIAL`。
|
||
|
||
## Status
|
||
|
||
`PARTIAL`。经验增量丢失、掉落归属 fixture 和双重经验球等已确认问题已有修复;死亡/经验/掉落主链可运行,但主角死亡状态门、完整 point effect、服务端包序、重复/乱序/复活清理仍未达到 40250 的统一实现。
|
||
|
||
## Audit round 2026-09-21T03:23Z — ground-pickup ownership and distance
|
||
|
||
本轮继续沿 40250 `CPythonPlayer::PickCloseItem` / `SendClickItemPacket` /
|
||
`__OnPressItem`、`CPythonItem::GetCloseItem/SetOwnership` 与当前
|
||
`GroundItems::try_pickup/try_pickup_vid`、`NetPlay::pick_ground_item`、
|
||
`EntityStore::mut_ground_owner` 对照,确认已有经验/死亡主链没有回归,但掉落拾取仍有三处实现差异:
|
||
|
||
- 40250 的所有权预检不是“只允许同名”。`SendClickItemPacket` 对非本人 owner
|
||
还会检查 `IsPartyMemberByName` 以及物品 `ITEM_ANTIFLAG_DROP | ITEM_ANTIFLAG_GIVE`;
|
||
当前 `GroundItems` 只比较 owner 字符串和本地角色名,队伍成员掉落与 anti-flag
|
||
分支没有实现,因此会把 40250 允许请求的队伍物品提前拦截在客户端。
|
||
- 40250 `GetCloseItem` 使用 `DISTANCE_APPROX` 的厘米距离,并在该版本函数体内固定以
|
||
300cm 判定;当前 `try_pickup` 使用 Godot 欧氏米制距离,且骑马时放宽到 5m。
|
||
两者在对角线边界、骑马拾取和同距离物品选择上可能选出不同 VID。
|
||
- 40250 `__OnPressItem` 在点击路径先用 `GetGroundItemPosition` 和
|
||
`NEW_IsClickableDistanceDestPixelPosition` 判断,超距才建立预约;当前
|
||
`NetPlay::pick_ground_item` 有同类主路径,但公开的 `try_pickup_vid` 自身不再检查
|
||
距离,后续若被其它输入入口直接调用会绕过这个边界。
|
||
|
||
对照结果:`CPythonItem::SetOwnership` 和当前 `mut_ground_owner` 对未知 VID 都只忽略,
|
||
`GC_ITEM_GROUND_ADD/DEL` 的创建/删除主顺序一致;本轮 `test_combat_death_pickup_entry_parity.gd`、
|
||
`mob_death_drop_test.gd`、`combat_parity_test.gd`、`playable_combat_test.gd`、
|
||
`gamescene_test.gd` 与 `net_state_queue_test` 全部通过,但现有 fixture 只覆盖本人掉落,
|
||
没有覆盖队伍 owner、anti-flag、厘米近似距离、同距 tie-break、先拾取后 DEL 和真实服务端
|
||
攻击→死亡→经验→掉落包序。因此合同继续为 `PARTIAL`,下一轮修复候选是补齐 party/anti-flag
|
||
上下文并把拾取距离/选择规则收敛到 40250 的实际函数语义。
|
||
|
||
## Implementation fix round 2026-09-21T04:10Z — ground-pickup parity
|
||
|
||
本轮先在 `project/test_ground_pickup_parity.gd` 中加入回归断言,修复前实际复现
|
||
6 项失败:队伍成员物品被错误拒绝、对角线 `DISTANCE_APPROX` 边界被欧氏距离拒绝、
|
||
同距物品没有按最高 VID 选择,以及 `try_pickup_vid` 可绕过点击距离门。随后按 40250
|
||
实际调用链完成修复:
|
||
|
||
- `GroundItems::_on_added` 保存掉落包或 item proto 的 `anti_flags`;
|
||
`_can_pick_owned_item` 与 `CPythonPlayer::SendClickItemPacket` 一致,自己的 owner
|
||
直接允许,队伍成员仅在没有 `ITEM_ANTIFLAG_DROP | ITEM_ANTIFLAG_GIVE` 时允许,
|
||
其他 owner 仍触发 `cannot_pick_item`。
|
||
- `GroundItems::try_pickup` 改为厘米整数 `DISTANCE_APPROX`,固定使用
|
||
`CPythonItem::GetCloseItem` 实际函数体的 300cm 边界;按升序 VID 遍历并用 `<=`
|
||
复刻同距时后者(最高 VID)覆盖前者。骑马传入的 500cm 参数不再错误放宽实际
|
||
`GetCloseItem` 的 300cm 硬边界。
|
||
- `GroundItems::try_pickup_vid` 增加 `NEW_IsClickableDistanceDestPixelPosition`
|
||
对应的 150cm 自身距离门;`NetPlay::CLICK_ITEM_PICKUP_CM` 和预约到达边界同步为
|
||
150cm,并在边界使用 `<=`。
|
||
|
||
修复后通过:`test_ground_pickup_parity.gd`、`netplay_test.gd`、
|
||
`mob_death_drop_test.gd`、`test_combat_death_pickup_entry_parity.gd`。本轮仍未宣称
|
||
真实服务端攻击→死亡→经验→掉落包序、空 owner/重名、拾取竞争和重连 catch-up 已完全
|
||
等价,合同继续为 `PARTIAL`。
|
||
|
||
## 深审轮次(2026-09-20)
|
||
|
||
本轮重新沿 `GC_DEAD -> entity_dead -> NetWorld/NetPlay/PlayerController` 和
|
||
`GC_PLAYER_POINT_CHANGE -> POINT_EXP.amount -> _trigger_exp_gain` 跟踪正式连接:
|
||
|
||
- `EntityStore` 已把 `POINT_EXP.amount` 累加为一次性 `exp_gain`,`NetPlay._on_points`
|
||
优先消费该增量;因此“服务端确实下发 POINT_EXP 但跨等级回零后没有经验反馈”的旧问题已有防回归证据。
|
||
- `GC_CREATE_FLY(FLY_EXP)` 仍只触发 `NetWorld._on_fly` 的视觉球,`_trigger_exp_gain` 不再生成第二批球,权威经验和表现来源没有混用。
|
||
- 当前 `client.entity_dead` 在 `NetWorld` 中只消费 `_by_vid` 的实体;本地主角由
|
||
`GameScene` 以 `_local_node` 单独持有,`NetPlay` 的 `entity_dead` 回调只在死亡目标等于当前目标时清目标,未统一调用
|
||
`PlayerController.stop`、清自动攻击/预约动作、死亡 UI/GameOver 和复活恢复。该缺口仍是正式链路差异,不是测试 fixture 问题。
|
||
- `RecvPointChange` 的参考端对所有 `POINT_*` 先执行 `ShowPointEffect`,再按等级、属性、技能、能量和金币分支刷新;当前 `_on_points` 主要更新 HUD/经验/等级,完整 point effect 与金币 amount 的专门副作用仍未闭合。
|
||
|
||
本轮执行 `test_combat_death_pickup_entry_parity.gd`、`mob_death_drop_test.gd`、
|
||
`combat_parity_test.gd`、`playable_combat_test.gd`、`gamescene_test.gd` 和
|
||
`build/extension/net_state_queue_test` 均通过;这些测试证明已有局部链路可工作,但没有把主角 `GC_DEAD` 门禁、
|
||
真实服务端包序和完整 `POINT_*` 分支纳入断言,因此合同继续保持 `PARTIAL`。
|
||
|
||
## Deep audit round 2026-09-20
|
||
|
||
本轮重新从 40250 `RecvDeadPacket`、`CInstanceBase::Die`、`RecvPointChange` 和 `RecvCreateFlyPacket` 对到当前 parser/store/UI 消费链:
|
||
|
||
- 40250 主角 `GC_DEAD` 会先触发非决斗 `OnGameOver`、`NotifyDeadMainCharacter` 清自动攻击,再进入一次性 `Die`;当前 `entity_dead` 主要由 `NetWorld` 处理远端节点,主角由 `GameScene.player` 单独持有,尚未有统一入口把死亡同时传给 `PlayerController.stop`、NetPlay 自动攻击/预约清理、死亡 UI 和复活恢复。
|
||
- 当前 `POINT_EXP.amount` 的累计与一次性消费仍然正确,`GC_CREATE_FLY(FLY_EXP)` 只做视觉,不会再造第二份经验;但 40250 对所有 `POINT_*` 先 `ShowPointEffect`,并对等级/属性/技能/能量/金币有专门刷新,当前 `_on_points` 对普通 point effect、金币 amount 和属性/技能窗口刷新仍不完整。
|
||
- 当前地面掉落和 owner/拾取主路径通过了本轮回归,但名字匹配、重名/空 owner、先拾取后 DEL、重连 catch-up、重复死亡/重新 Add、复活和跨图时序仍没有真实包级证据。
|
||
- 本轮 `test_combat_death_pickup_entry_parity.gd`、`mob_death_drop_test.gd`、`combat_parity_test.gd`、`playable_combat_test.gd`、`gamescene_test.gd` 和 `net_state_queue_test` 均退出码 0;通过项覆盖局部战斗门、死亡怪物、掉落、经验增量和 map reset,不覆盖主角 `GC_DEAD` 门禁、完整 `POINT_*` 副作用及服务端攻击→死亡→经验→掉落包序。
|
||
|
||
结论:经验增量和基础掉落/死亡表现已可回归,但主角死亡状态门、完整 point effect、重复/乱序/复活清理和真实服务器包序仍与 40250 不同,合同保持 `PARTIAL`。
|
||
|
||
## Audit round 2026-09-20T19:03Z
|
||
|
||
本轮重新核对 `RecvDeadPacket`、`CInstanceBase::Die`、`RecvPointChange`、
|
||
`RecvCreateFlyPacket` 与当前 parser/store/bridge 的完整边界,并重跑死亡、经验和掉落回归:
|
||
|
||
- 上一轮补的主角死亡门现在已形成可验证闭环:`NetPlay::_on_entity_dead` 清除自动攻击、
|
||
预约动作、技能/钓鱼状态并调用 `PlayerController.set_dead(true)`;正 HP 的 `vitals_changed`
|
||
和 `GC_MAIN_CHARACTER` 清除死亡状态。相关测试全部通过,原先“死亡后仍继续 WASD/点地”
|
||
的差异不再列为当前未修复项。
|
||
- `POINT_EXP.amount` 仍是唯一经验增量来源,`GC_CREATE_FLY(FLY_EXP)` 只创建视觉飞行物;
|
||
`_trigger_exp_gain` 不再重复生成经验球,经验增量消费测试通过。
|
||
- 当前端仍没有 40250 `ShowPointEffect(type, vid)` 的 typed event。所有点数最终合并进
|
||
`points_changed`,没有按 `POINT_STAT_RESET_COUNT`、四维属性、技能/坐骑技能、能量、金币
|
||
等分支保留“先显示点效果、再刷新对应窗口”的事件来源;`POINT_GOLD.amount` 也没有
|
||
等价的 `OnPickMoney(amount)` 信号。
|
||
- `fly_cue` 的主角 end VID 适配和缺失 actor 丢弃已有明确分支,但当前 `NetWorld` 仍把
|
||
服务端 FLY_EXP 组合成固定三颗 Godot 光球,真实 `.fly`/索引飞行资源、地图切换中的飞行
|
||
队列清理以及 start/end actor 包序没有 live 服务端证据。
|
||
- 地面物品、owner 和拾取距离测试通过,但空 owner/重名、先 PICKUP 后 DEL、重复 DEAD/ADD、
|
||
重连 catch-up 和跨图清理仍未由同一套包序 fixture 证明;这些仍属于服务端权威边界,
|
||
当前本地 helper 不能替代协议证据。
|
||
|
||
本轮执行 `test_combat_death_pickup_entry_parity.gd`、`mob_death_drop_test.gd`、
|
||
`combat_parity_test.gd`、`playable_combat_test.gd`、`gamescene_test.gd`、
|
||
`net_state_queue_test` 和 `net_entity_test`,均退出码 0;合同继续为 `PARTIAL`。
|
||
|
||
## Implementation fix round 2026-09-20T19:17Z
|
||
|
||
本轮按 40250 `RecvPointChange` → `CPythonCharacterManager::ShowPointEffect` →
|
||
`CPythonPlayer::SetStatus` / 窗口刷新分支修复点数事件链:
|
||
|
||
- `EntityStore::mut_set_point` 统一 classic 与 m2dev 的单点变更入口,先保留
|
||
`{vid, type, value, amount}` typed event,再更新主角点表、实体生命/等级和经验增量;
|
||
`POINT_ENERGY == 0` 同步清除 `POINT_ENERGY_END_TIME`。
|
||
- `M2Client` 新增 `point_changed`,两种协议镜像都在 `vitals_changed` /
|
||
`points_changed` 前发出,保留 40250 的“先 point effect、后状态刷新”顺序;新增
|
||
`POINT_LEVEL_STEP`、`POINT_STAT_RESET_COUNT` 及四维属性常量。
|
||
- `NetWorld` 对 `POINT_LEVEL` / `POINT_LEVEL_STEP` 按 40250 注册的
|
||
`level_up.mse` / `skillup_1.mse` 资源在对应 VID 上播放,并在远端等级变更后刷新
|
||
`LevelTag`;`NetPlay` 对金币正 amount 发出 `money_picked` / HUD `on_pick_money`,
|
||
并发出属性、技能窗口刷新请求;`SkillUI` 监听点数事件。
|
||
- `net_entity_test.cpp`、`netplay_test.gd` 覆盖 typed event 顺序、EXP/GOLD amount、
|
||
零能量清理、金币反馈和属性/技能刷新;同时修复 NetWorld 在未显式分离本地节点的
|
||
headless/adaptor 场景错误跳过主实体的问题。
|
||
|
||
本轮仍不宣称完全等价:`LevelUp`/`SkillUp` 的真实 C++ effect manager 生命周期、
|
||
金币浮字的完整 UI 资源、状态窗口所有旁路调用,以及真实服务器攻击→死亡→经验→掉落
|
||
包序和跨图异常清理仍需继续审计。
|
||
|
||
本合同继续为 `PARTIAL`。
|
||
|
||
## Audit round 2026-09-20T21:20Z
|
||
|
||
本轮对上一轮修复后的死亡/经验/掉落链做回归复核,并重新对照
|
||
`RecvDeadPacket -> CInstanceBase::Die -> NotifyCharacterDead/NotifyDeadMainCharacter`、
|
||
`RecvPointChange -> POINT_EXP.amount`、`RecvCreateFlyPacket` 和地面物品包:
|
||
|
||
- 主角死亡的 `PlayerController` 输入门、自动攻击清理、复活正 HP 边沿、远端死亡目标清理和经验增量一次性消费均有测试证据;本轮未发现上一轮已修复分支回归。
|
||
- `POINT_EXP.amount` 仍是经验的权威增量,`FLY_EXP` 只负责视觉;死亡、掉落坐标/贴地、owner 文本/拒绝、拾取距离和删除主路径回归通过。
|
||
- 仍不等价的分支保持明确:真实服务端攻击→伤害→死亡→`POINT_EXP`→`CREATE_FLY`→地面掉落包序未 fixture 化;重复/乱序死亡、无效 VID、重连 catch-up、重名/空 owner 和拾取竞争未闭合。
|
||
- 40250 的 `CInstanceBase::Die` 会清 affect、卸马鞍、取消选中/目标并由底层一次性死亡门驱动动作;当前这些动作分散在 NetWorld/NetPlay/UI,完整死亡动画、特效管理器生命周期和跨图旧队列清理仍没有同一入口证明等价。
|
||
|
||
结论:本轮是回归审计,没有新增实现修改;已有死亡和经验主链保持可用,但合同继续为 `PARTIAL`,下一轮优先补协议级死亡包序和重复/乱序/复活清理 fixture。
|
||
|
||
## Implementation fix round 2026-09-21T04:00Z — local main fly target fallback
|
||
|
||
沿 40250 `RecvCreateFlyPacket` 的起点/终点实例解析继续核对发现:参考端从
|
||
`CPythonCharacterManager` 取得 `CInstanceBase`,本地主角虽然不在远端 actor 容器中,
|
||
仍由 `GetMainInstancePtr()` 提供位置。当前端 `GameScene` 将本地主角单独放在
|
||
`NetWorld._local_node`,但 `_entity_pos()` 原先只查 EntityStore 与 `_by_vid`;在阶段切换
|
||
的短窗口内若本地主角 EntityStore 行尚未镜像,`_on_fly(FLY_EXP, mob_vid, main_vid)` 会因
|
||
终点为 `Vector3.INF` 直接丢弃经验球。
|
||
|
||
修复前先让 `test_exp_fly_parity.gd` 删除本地 VID 的 EntityStore fixture,专项实际复现
|
||
“经验球数量为 0”的失败。现在 `_entity_pos()` 对本地 VID 优先回退到 `_local_node`,
|
||
`_on_fly()` 对本地主角作为起点也使用同一实例来源;因此 EntityStore 行暂缺时仍可按
|
||
40250 的主角实例语义创建和追踪飞行物。
|
||
|
||
修复后通过:`test_exp_fly_parity.gd`、`fly_reset_test.gd`、`netbridge_test.gd`、
|
||
`test_combat_death_pickup_entry_parity.gd`、`mob_death_drop_test.gd`、
|
||
`gamescene_test.gd`。测试中 `test_exp_fly_parity.gd` 仍有 3 个 ObjectDB leak warning,
|
||
不影响退出码和本轮断言结果,需单独做生命周期清理审计。
|
||
|
||
合同继续为 `PARTIAL`:真实服务器攻击→死亡→经验→掉落包序、重复/乱序/重连/拾取竞争、
|
||
完整死亡资源生命周期和跨图清理仍未由 live packet fixture 证明。
|
||
|
||
## Implementation fix round 2026-09-21T04:02Z — indexed fly actor-instance gate
|
||
|
||
继续核对 40250 `RecvCreateFlyPacket`:参考端在 `CreateIndexedFly` 前必须同时取得
|
||
`GetInstancePtr(startVID)` 和 `GetInstancePtr(endVID)`,任一实例不存在就直接返回,
|
||
不会用网络层残留坐标制造一个脱离角色实例的飞行物。
|
||
|
||
修复前回归新增两项失败:远端终点节点不存在但 EntityStore 有坐标时错误生成飞行物;
|
||
远端起点节点不存在但 EntityStore 有坐标时同样错误生成飞行物。当前
|
||
`NetWorld._on_fly()` 对起点和非零终点增加实例存在门;本地主角保留上一轮的
|
||
`_local_node` 适配,因此只在本地主角实例真实存在时允许创建。
|
||
|
||
修复后 `test_exp_fly_parity.gd`、`fly_reset_test.gd`、`netbridge_test.gd`、
|
||
`test_combat_death_pickup_entry_parity.gd`、`mob_death_drop_test.gd`、
|
||
`gamescene_test.gd` 和 `combat_fx_test.gd` 全部通过。合同仍为 `PARTIAL`,真实服务端
|
||
攻击→死亡→经验→掉落包序、重复/乱序/重连/拾取竞争和跨图清理仍未闭合。
|