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

173 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.
# combat.flying_projectile —— 飞行物、目标、地形碰撞、命中和回收
状态:`PARTIAL`
本合同覆盖 40250 客户端的两条飞行物来源:
- 普攻/弓箭的 `.msa FLY` 事件 → `OnSetFlyTarget` / `OnShoot``CG_FLY_TARGETING``CG_SHOOT`
- 服务端 `GC_CREATE_FLY``GC_FLY_TARGETING``GC_ADD_FLY_TARGETING` → 客户端 `CFlyingManager` 弹道、目标追踪、背景/Actor 碰撞和爆炸回调。
## 40250 参考调用链
### 客户端发射事件
- `GameLib/ActorInstanceFly.cpp` `OnGetFlyTargetPosition`
- 从目标 Actor 的飞行目标包围球读取位置。
- `UserInterface/PythonPlayerEventHandler.cpp:232` `CNormalBowAttack_FlyEventHandler_AutoClear::OnSetFlyTarget`
- 读取主角位置/朝向并发送 `SendFlyTargetingPacket(target VID, target position)`
- `UserInterface/PythonPlayerEventHandler.cpp:241` `OnShoot`
- 在动作资源的 FLY 事件帧发送 `SendShootPacket(skill)`
- `UserInterface/PythonPlayerEventHandler.cpp:259` `OnExplodingAtAnotherTarget`
- 若启用对应处理,向另一 Actor 发送攻击包并调用目标 `OnShootDamage`;当前参考实现该函数主体被直接 `return`,必须保留这个可达性事实,不能凭注释臆造伤害链。
### 飞行数据和弹道
- `GameLib/FlyingData.cpp` `CFlyingData::LoadScriptFile`
- `.fly` 资源读取 initial velocity、cone/roll、angular velocity、gravity、range、acceleration、homing、bomb effect、hit-on-background、hit-on-another-monster、pierce count、bomb range 和 attach data。
- `GameLib/FlyingInstance.cpp` `CFlyingInstance::Create`
- 绑定 `CFlyTarget``canAttack`、起点、旋转方向、局部初速/加速度和剩余射程。
- `GameLib/FlyingInstance.cpp:347` `CFlyingInstance::Update`
- homing → 速度/加速度推进 → 维护 attach effect → 超程 → 目标线段命中 → 穿透/爆炸 → 其它 Actor 动态碰撞 → 地形高度/静态背景碰撞。
- `GameLib/FlyTarget.cpp` `CFlyTarget`
- 目标可以是对象或坐标;对象被删除时转为最后目标位置,避免悬空指针。
- `GameLib/FlyingObjectManager.cpp`
- Update 返回 false 后从实例表删除;地图/场景销毁时删除全部实例和挂接特效。
### 网络收包
- `UserInterface/PythonNetworkStreamPhaseGame.cpp:2559` `RecvAddFlyTargetingPacket`
- 找不到射手时只记录并返回;存在目标 VID 时绑定对象,否则通过地图高度构造坐标目标。
- `:2599` `RecvFlyTargetingPacket`
- 与上面相同,但替换当前目标而不是追加。
- `:2697` `RecvCreateFlyPacket`
- 起点或终点实例不存在时不创建;否则 `CreateIndexedFly(type, start, end)`
- `:2637-2695` `SendShootPacket``SendAddFlyTargetingPacket``SendFlyTargetingPacket`
- 每个发送操作后都发送 sequence。
## 当前端实现映射
- `project/fly_object.gd`
- `FlyData``FlyInstance``FlyManager` 对应 `CFlyingData/CFlyingInstance/CFlyingManager`
- 已实现 `.msf``CFlyingData::LoadScriptFile` 字段解析和 cm→m 单位适配,并保留目标对象/坐标、homing、线段到点命中、超程、地形高度/静态 Physics layer 2 碰撞、穿透计数、爆点和地图清理。
- `project/net_world.gd`
- `_on_fly``playersettingmodule.py` 的索引表加载 `.msf` 并消费服务端飞行 cue`_on_fly_targeting` 暂存 shooter 的目标队列;`clear_for_map_change` 清理飞行物和爆点。
- 服务端索引经验球通过 `FLY_EXP` 加载 `ga_piece_yellow_small2.msf`;离线 `spawn_exp_fly` 仍是独立的多球测试/表现辅助入口。
- `project/net_play.gd`
- `_emit_swing` 在弓箭起手时调用 `_send_fly_target`,并由 `bow_shot_fired` 进入发射链。
- `send_fly_targeting` 处理扇形/圆形目标追加。
- `project/world_shot.gd``project/fx/skill_fx.gd`
- 提供世界投射物/技能表现层。
- `extension/src/net/*`
- 当前 classic parser/store/client 提供飞行事件和目标坐标的网络入口。
## 分支等价矩阵
| 参考判据/副作用 | 当前实现 | 结论 |
|---|---|---|
| `.fly`/`.msf` 资源读取飞行参数和 attach data | `FlyData.load_msf` 解析参考字段、相对/绝对资源路径和 cm→m 单位;`FlyInstance` 已创建 AttachFile/FlyTrace`NetWorld._indexed_fly_data` 绑定 40250 注册资源;远端 Actor 的 MSA FLY 也进入同一实例入口 | 部分等价;精确远端 skill 上下文、相机尾迹几何和所有资源矩阵仍缺 |
| 起点/终点实例缺失则不创建飞行物 | `_on_fly``Vector3.INF`/节点存在门控后返回 | 基本等价;完整 packet handler/sequence 仍未 live 验证 |
| 对象目标随 Actor 移动,目标删除后转位置 | `FlyTargetAnchor.fly_target_position` 使用包围球中心;`notify_target_despawn` 在渐隐/替换前立即转为位置目标 | 基本等价;迟到目标包和 VID 重用的包级矩阵仍未完成 |
| homing、速度/加速度、射程和线段到点命中 | `FlyInstance._adjust_homing`/`update` | 基本等价;坐标轴和单位转换需真实资源验证 |
| `m_fCollisionSphereRadius = max(move distance*2, configured radius)` | `FlyInstance.update` 在同一顺序计算膨胀半径,并通过 `actor_provider` 查询当前可见 Actor | 基本等价;Actor 碰撞球半径的真实模型标定仍未完全证明 |
| `hitonanothermonster` + 已命中对象集合 + `OnExplodingAtAnotherTarget` | `FlyInstance._find_another_actor``hitted_objects``at_another` 回调和穿透分支已接入;射手被排除 | 部分等价;EffectManager/真实动态 culling 仍未完全复现 |
| hit-on-background 先地形高度后静态背景碰撞 | 当前动态 Actor 分支后仍是 `sample_height` 后 Physics layer 2 ray | 基本等价的 Godot 平台适配;真实 culling 半径/查询范围未证明相同 |
| pierce count:命中后 bomb,减计数,耗尽才 explode | `FlyInstance.pierce`/`target_hitted` 与动态 `at_another` 分支均执行相同的 bomb/继续/爆炸顺序 | 基本等价;多种真实资源与同帧背景竞争仍未完整验证 |
| `OnShootDamage` 仅在 `canAttack` 对象目标命中时触发 | 当前 `shoot_damage` signal 有入口,但服务端 GC 弹道使用 `can_attack=false` | 基本等价;真实本地弓箭事件到服务端回包链未完整证明 |
| 超程 `OnExplodingOutOfRange`、背景/目标回调和删除 | `exploded` signal + manager 删除;BombEffect 优先走 EffectRegistry,资源不可用才 fallback flash | 基本等价;EffectManager 真实生命周期和全部回调包序仍未完整验证 |
| Add/Set FlyTargeting 追加与替换 | 入站通过 shooter meta `fly_target_queue``append`;出站普通弓箭走独立 `fly_targeting`/`CG_FLY_TARGETING`,附加目标走 `add_fly_targeting`/`CG_ADD_FLY_TARGETING` | 基本等价;shooter 不可见/本地主角/VID 重用分支待验证 |
| `GC_CREATE_FLY` indexed normal/firecracker/auto-fire 类型 | `INDEXED_FLY_TYPES` 与 40250 注册表一致;`_on_fly` 已分别复刻普通、随机位置烟花和 +100cm AUTO_FIRE 起点,AttachFile 优先作为实际视觉 | 基本等价;资源缺失 fallback、真实模型半径和包序仍未完全证明 |
| 每个 outbound 飞行目标/射击包后发送 sequence | ClassicSession 字节级验证 `CG_FLY_TARGETING``CG_ADD_FLY_TARGETING``CG_SHOOT` 均追加正确 sequence;完整动作队列与真实 socket 包序仍未 live 验证 | 部分等价 |
## 已执行测试
- `project/fly_test.gd`PASS;覆盖 `CFlyingManager/CFlyingInstance` 基础弹道、命中、超程和 `__CanShot`
- `project/fly_reset_test.gd`:PASS;覆盖地图切换时飞行物和爆点清理。
- `project/test_exp_fly_parity.gd`:6/6 PASS;覆盖经验光球飞行和吸收。
- `project/fly_target_bounds_test.gd`:0 failures;覆盖模型目标包围球中心和 CPU/GPU 一致性,但有 ObjectDB leak warning。
- `project/test_click_target_effect_parity.gd`:0 failures;覆盖静态障碍碰撞和目标效果清理,但有 ObjectDB/RID 清理警告。
- `project/combat_fx_test.gd`:PASS;覆盖命中/连击/效果主链。
- `project/fly_data_msf_test.gd`PASS;覆盖 `geompung.msf`、经验球和 `arrow_01.msf` 的字段、相对资源路径、attach data、枚举及 cm→m 转换。
- `project/indexed_fly_type_test.gd`PASS;覆盖 40250 NORMAL/FIRE_CRACKER/AUTO_FIRE 的索引类型、起点高度、对象/位置目标和随机距离范围。
- `project/fly_dynamic_collision_test.gd`:PASS;覆盖动态 Actor 双目标穿透和同一 Actor 的 `HittedObjectSet` 去重。
- `project/fly_target_lifecycle_test.gd`:PASS;覆盖包围球目标中心和 Actor 销毁时立即转位置目标。
- `project/fly_attachment_effect_test.gd`PASS;覆盖绝对 AttachFile 解析、EffectRegistry 实例、FlyTrace 轨迹、BombEffect 和资源视觉不重复绘制。
- `project/remote_motion_fly_test.gd`PASS;覆盖远端 Actor `GC_FLY_TARGETING` 队列、MSA FLY 事件、owner VID/skill 和资源飞行实例创建。
## Implementation fix round 2026-09-21T06:34Z
`FlyingObjectManager::CreateIndexedFly` 接入 `INDEXED_FLY_TYPES`:NORMAL 保持射手根位置和对象目标,FIRE_CRACKER 使用 +200cm 起点、-90° 基准方向与 20..50m/10..25m 随机位置目标,AUTO_FIRE 使用 +100cm 起点并保留对象目标。新增 `indexed_fly_type_test.gd`,修复前 FIRE_CRACKER/AUTO_FIRE 断言失败,修复后通过。
`FlyingInstance::Update` 接入动态 Actor 碰撞:每帧使用 `max(move_distance * 2, collision_sphere_radius)`,排除 owner,按节点实例身份维护 `hitted_objects`,触发 `at_another` 并复刻 pierce/bomb/explode 分支。按 `FlyTarget`/`ActorInstanceFly` 接入 `FlyTargetAnchor` 包围球中心,并在 NetWorld 的 despawn、同 VID 替换前调用 `notify_target_despawn`,保证渐隐节点不再继续移动飞行目标。新增 `fly_dynamic_collision_test.gd``fly_target_lifecycle_test.gd`,均通过。
修复后 `fly_test``fly_reset_test``fly_data_msf_test``motion_event_fly_test``test_exp_fly_parity``netbridge_test``combat_fx_test``netplay_test` 和静态建筑碰撞回归通过。真实 `EffectManager`/`FlyTrace` 资源表现、真实模型动态半径、包序和不可见 Actor 生命周期仍使合同保持 `PARTIAL`
## Implementation fix round 2026-09-22T07:55Z
复核 `CFlyingManager::DeleteAllInstances` 的销毁边界时发现,当前 `FlyInstance.handler` 闭包捕获实例自身,而实例又持有该 Callable;地图清理只清数组会留下 `RefCounted` 环。现新增 `_release_instance()`,在地图清理和自然过期两个删除路径中同步解除 handler、target、world、data 引用,再移除实例;地图清理的 visual/flash 节点也按 Loading 的同步删除边界直接释放。
修复前 `fly_reset_test.gd` 虽 PASS 但退出报告 5 个 ObjectDB 泄漏;修复后退出无 ObjectDB 泄漏。`test_exp_fly_parity.gd` 行为断言仍为 6/6 PASS,但其旧式单函数 fixture 仍报告 3 个 ObjectDB/不在 SceneTree 的路径警告,属于测试 harness 生命周期待清理,不将其伪装成完全干净证据。动作事件 FLY 的 owner/skill/包序、真实模型半径、远端不可见 Actor 和 EffectManager 全量生命周期仍保持 `PARTIAL`
## Implementation fix round 2026-09-21T06:46Z
继续按 40250 `FlyingData::LoadScriptFile``FlyTrace``FlyingObjectManager` 的资源链修复飞行特效:`TailLength` 按参考保持秒单位,`TailSize` 只做 cm→m 转换;`AttachData` 现在会实例化 `AttachFile`,并维护 line/multi-line/sine/exp 四类尾迹历史和按时间衰减的轨迹几何。`BombEffect` 优先通过 `EffectRegistry` 资源实例化,只有资源不存在时才使用兼容性 flash,避免资源特效和通用球体重复绘制。
同时修复 `EffectRegistry``.msf` 内绝对资源路径的解析:存在的绝对路径不再被重复拼接到资源根目录。新增 `fly_attachment_effect_test.gd` 覆盖 AttachFile 实例、FlyTrace 历史、BombEffect、资源视觉去重和地图清理;`fly_data_msf_test.gd` 增加 `TailLength` 单位回归。资源特效链已进入正式运行路径,但精确相机朝向、所有远端 FLY/SHOOT owner-skill 组合、EffectManager 的每种资源生命周期和真实包序仍未完成,因此合同继续保持 `PARTIAL`
## Implementation fix round 2026-09-21T06:54Z
按 40250 `CActorInstance::ProcessMotionEventFly` 补齐远端 Actor 的动作事件飞行链:`NetWorld` 现在从 `GC_FLY_TARGETING/GC_ADD_FLY_TARGETING` 保存的当前目标队列取目标,在远端 MSA 的 type 6 事件帧创建同一个 `.msf` 飞行实例,使用 Actor 动作位置/挂骨骼位置作为起点,并传递 owner VID 与 skill index;对象目标、坐标目标和目标队列消费均保持分支语义。`GC_CREATE_FLY` 的服务端索引飞行仍与动作事件路径分离。
新增 `remote_motion_fly_test.gd` 覆盖远端目标队列、资源实例、owner/skill 和单次消费,修复后的测试无 ObjectDB 泄漏。远端动作事件没有原生 `m_kCurMotNode.uSkill` 的完整网络字段时仍只能使用事件字段或当前动作参数回退,精确 skill 上下文、包序、不可见 Actor 和全量资源矩阵继续保持 `PARTIAL`
## Implementation fix round 2026-09-21T06:59Z
按 40250 `CNormalBowAttack_FlyEventHandler_AutoClear::OnSetFlyTarget``CPythonNetworkStream::SendFlyTargetingPacket` 修正出站主目标链:新增 `M2Client.fly_targeting``ClassicSession::send_fly_targeting` 和 Godot 绑定,普通弓箭不再误发 `CG_ADD_FLY_TARGETING`;扇形/圆形技能的附加目标继续使用独立的 `add_fly_targeting`。ClassicSession 回归逐字节验证两种 header 和 sequence,战斗回归验证弓起手优先调用主目标 API。
该修复只闭合了主/附加目标包类型和局部 sequence 证据;不可见 shooter、VID 重用、真实 socket 中乱序/重连和完整 MSA→CG_SHOOT 时序仍保持 `PARTIAL`
## Implementation fix round 2026-09-21T07:03Z
继续核对 `FlyingInstance::UpdateAttachInstance` 后修正尾迹几何:40250 的
`MULTI_LINE``SINE``EXP` 分支使用 `(-sin(roll) * offset, 0,
-cos(roll) * offset)`,当前端原先把局部 Z 号写反,导致资源飞行物的尾迹/AttachFile
偏到相反一侧;现已改为同号。`LINE` 的 effect 朝向按参考只使用基础飞行旋转,其他三种
偏移类型才叠加 `m_qAttachRotation`。新增直接检查 `MULTI_LINE` 局部 Z 偏移的回归断言。
`fly_attachment_effect_test``fly_data_msf_test``fly_test``fly_dynamic_collision_test`
`fly_target_lifecycle_test``fly_reset_test` 均通过。`coneangle/rollangle` 初始旋转、
相机相关 FlyTrace 顶点生成、完整资源矩阵和远端原生 skill 上下文仍未闭合,合同保持 `PARTIAL`
### Active GameLib flying-core review
- `GameLib/FlyingData.h` 定义 `.fly` 的初速、cone/roll、角速度、重力、加速度、homing、range、bomb effect、background/another-monster 命中、pierce、collision sphere 和 attach tail`FlyingInstance.h` 保存速度/旋转/剩余射程、目标、已命中集合和 attach effect 生命周期。
- `FlyingObjectManager.h` 按 index/type 注册飞行资源,创建/更新/渲染/删除实例;`FlyTrace.cpp` 则用动态池记录位置时间 deque,按 tail length 衰减并生成相机相关的飞行尾迹。当前 `fly_object.gd` 已按索引读取 `.msf`,接入动态 Actor 碰撞/已命中集合、AttachFile/FlyTrace、BombEffect 资源清理和远端 MSA FLY 创建;精确相机朝向、模型半径标定、全部资源组合和远端原生 skill 上下文仍缺证据。
- 本轮完成 `FlyTarget`/`FlyingData`/`FlyingInstance`/`FlyingObjectManager`/`FlyTrace` 的活跃源码静态核对;测试通过只证明基础 Godot 弹道,不提升合同状态。
## 已确认差异和未验证项
1. 普通 GC 飞行物已从 40250 索引注册表加载 `.msf` 建立 `FlyData`,本地主角和远端 Actor 的动作事件 type 6 都进入同一实例入口;远端原生 `m_kCurMotNode.uSkill` 上下文、动作/网络包序仍未闭合。
2. `hitonanothermonster` 已接入动态 Actor 碰撞、按节点身份的 `HittedObjectSet` 去重、穿透后继续飞行和另一目标回调;真实客户端的 culling 范围、模型动态半径和背景/Actor 同帧竞争仍未证明一致。
3. 正式目标命中已经使用 `FlyTargetAnchor` 包围球中心,目标销毁也会转为最后坐标;`.fly` 的 bomb range/collision sphere 与真实模型资源组合仍没有逐类标定矩阵。
4. `_on_fly_targeting` 只把目标队列挂在可见 shooter 节点;40250 的 `CPythonCharacterManager` 实例表与当前 `NetWorld` 可见节点表不是同一生命周期,远端暂不可见、主角本地节点、VID 重用和迟到 `GC_CREATE_FLY` 尚未证明一致。
5. `bombeffect` 已优先走 `EffectRegistry`AttachFile/FlyTrace 也已进入飞行实例生命周期;精确相机尾迹、颜色/材质、所有资源组合和 EffectManager 的逐资源回收仍未证明一致。
6. 已有测试覆盖 `.msf` 字段、索引类型、动态 Actor 双目标穿透/去重、目标删除、AttachFile/FlyTrace/BombEffect、主/附加目标 header+sequence 和地图清理;仍缺 Add/Set 乱序、真实服务端包序、完整资源矩阵和背景/Actor 同帧竞争。
## Audit round 2026-09-21T06:24Z
本轮沿 `FlyingObjectManager::CreateIndexedFly``FlyingInstance::Update``FlyTarget::NotifyTargetClear``CActorInstance::OnGetFlyTargetPosition` 做了源码级闭环核对,并验证了当前资源目录包含 40250 注册的 7 个 FIRE_CRACKER `.msf`、1 个 AUTO_FIRE `.msf` 以及普通索引 `.msf`
- `playersettingmodule.py` 的 16 个索引资源路径已在当前 `INDEXED_FLY_FILES` 中出现,但类型元数据没有进入当前表;`NetWorld._on_fly` 所有索引都统一创建普通对象目标,未复刻 FIRE_CRACKER 的 `+200cm` 起点/随机位置目标和 AUTO_FIRE 的 `+100cm` 起点。
- 40250 每帧按 `max(move_distance * 2, collision_sphere_radius)` 扫描动态 Actor,并用 `HittedObjectSet` 去重;当前已按同一顺序实现 provider 查询、owner 排除、去重和 `at_another` 分支,但 provider 的可见性边界与真实模型半径仍是适配层差异。
- 40250 目标 Actor 销毁时立即 `ClearFlyTargeter()`,飞行物切换为最后目标坐标;当前 `_on_despawn` 先走渐隐/延迟释放,`FlyInstance` 仍持有旧 Node3D,目标删除、同 VID 重建和迟到 `GC_CREATE_FLY` 尚未达到同一时序。
- 40250 对象目标位置来自 `CActorInstance::OnGetFlyTargetPosition` 的包围球中心;当前 `FlyInstance.target_position()` 读取 Node3D 根位置,`fly_target_bounds_test` 的包围球 helper 尚未接入正式飞行命中链。
- 40250 的 `bombeffect`、attach/tail 和 FIRE_CRACKER/AUTO_FIRE 的资源表现由 `EffectManager`/`FlyTrace` 管理;当前只生成通用 SphereMesh/flash,资源路径虽能解析但尚未真正消费。
因此本轮没有提升合同状态;当前结论仍为 `PARTIAL`。资源消费、动态碰撞和目标生命周期已从“缺失”推进到“有运行证据的部分等价”,后续应优先补远端动作事件 FLY、包序/乱序、不可见 Actor 与完整资源生命周期矩阵。
## 下一轮验证入口
- 为动作事件 FLY 增加远端 Actor 的原生 skill 上下文和 `CG_FLY_TARGETING → CG_SHOOT → sequence` 真实状态机/包序测试。
- 增加背景与 Actor 同帧竞争、三 Actor/多种 collision sphere 的真实可见性与半径矩阵。
- 增加 `GC_FLY_TARGETING → GC_ADD_FLY_TARGETING → GC_CREATE_FLY` 乱序、不可见 shooter、目标删除/VID 重用和 map reset 测试。
- 对照每个 `INDEX_FLY_TYPE` 的 AttachFile、BombEffect、尾迹材质、颜色和回收时序,补齐资源矩阵后再决定是否提升合同状态。