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

205 lines
16 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.
# skill.cast-effect-timing
## Scope
比较技能等级、目标选择、施法前置、动作事件、伤害/状态结果以及粒子和命中特效的时序。
## Reference call chain
- `UserInterface/PythonSkill.cpp`
- `UserInterface/PythonPlayerSkill.cpp`
- `UserInterface/PythonNetworkStreamPhaseGame.cpp`
- `UserInterface/InstanceBaseEffect.cpp`
- `EffectLib/EffectInstance.cpp`
- `EffectLib/ParticleInstance.cpp`
- `GameLib/ActorInstanceMotionEvent.cpp`
- `UserInterface/InstanceBase.cpp`
## Current call chain
- `project/player_skill.gd`
- `project/skill_table.gd`
- `project/net_play.gd`
- `project/net_world.gd`
- `project/fx/skill_fx.gd`
- `project/fx/effect_player.gd`
- `extension/src/net/entity_store.cpp`
- `extension/tests/net_state_queue_test.cpp`
- `project/combat_fx_test.gd`
- `project/skill_test.gd`
## Initial finding
当前存在技能表、技能上下文和特效测试,但这些测试主要证明局部数据/材质行为,尚未证明完整 40250 施法链路。技能动作事件、挂点、目标范围、冷却、服务端结果和特效停止/重播仍需要逐分支对照。
## Current finding
`combat_fx_test.gd` 的失败是测试选择器使用本地化文本造成的,并非技能业务失败;现已改为按 40250 的控制节点名定位并通过。更重要的是,实体状态机此前没有复刻 40250 的 `eFunc & FUNC_SKILL` 位判断,移动中的技能会在到达目标时回到 WAIT;现已补齐远近两种分支并加入 C++ 回归测试。
本轮继续追到视觉动作层后,发现还有一个具体的参数丢失:`project/net_world.gd::_apply_anim` 只从 `FUNC_SKILL``motion_idx` 并调用 `PlayerView.play_skill_motion(motion_name)`,没有把 40250 `NEW_UseSkill`/到达逻辑中的 `uArg & 0x0f` 动作循环次数和 `(uArg >> 4)` 移动技能标志传入视图;`PlayerView.play_skill_motion``grade` 也没有来自网络状态的真实来源。这样只能证明“技能状态会进入”,不能证明每个技能的动作循环、移动技能动作和结束时机与 40250 一致。
同时,当前 `SkillFx`/`NetPlay` 仍有固定施法锁定和特效时长路径;40250 的关键帧、挂点、颜色/混合和动作事件来自 MSA/MSE 与 `ActorInstanceMotionEvent`。这些资源驱动事件尚未建立逐技能时间线,因此本合同保持 `PARTIAL`
## Deep audit round 2026-09-20
本轮沿 `CInstanceBase::NEW_UseSkill``CActorInstance::ProcessMotionEvent*`、本地施法入口和远端 `FUNC_SKILL` 路径复核,并重新执行技能/战斗/特效回归:
- 40250 `NEW_UseSkill` 明确接收 `uMotLoopCount``isMovingSkill`,调用 `InterceptOnceMotion``__OnUseSkill`,再设置动作循环次数;当前 `NetPlay.on_use_skill()` 只把 `motion_skill``arg` 上行,`NetWorld._apply_anim()` 只取 `FUNC_SKILL` 的 motion index,调用远端 `play_skill_motion(mname)` 时没有把 `uArg & 0x0f` 循环次数、`uArg >> 4` 移动技能标志或技能 grade 传入。
- 当前 `PlayerView.play_skill_motion()` 支持按 grade 选择 `.msa` 后缀,但正式远端网络动作路径没有 grade 来源;本地 `GameScene._on_skill_cast_started()` 能传 grade 并从 MSA 读取 duration/cancel_enable,两个路径的输入并不统一。
- 当前 `SkillFx.spawn_skill()` 只在 `skill_fx_test.gd` 中被直接调用,正式 `GameScene`/`NetPlay` 没有生产调用;正式本地技能特效主要依赖 MSA motion event 和 `fx.spawn_motion`,因此技能表中的主特效/子特效映射没有被证明与 40250 的技能触发链一致。
- 40250 的 `MOTION_EVENT_TYPE_FLY``EFFECT_TO_TARGET``SPECIAL_ATTACKING``SOUND` 都由动作事件驱动,带有攻击窗口、挂点、跟随目标、碰撞球和一次性/循环生命周期;当前 `effect_playback_test` 只验证通用 MSE 播放/停止/重播,`combat_fx_test` 只验证战斗门和桩,不是逐技能 MSA→MSE→命中结果时间线。
- 本轮 `skill_test.gd``combat_fx_test.gd``effect_playback_test.gd``net_state_queue_test` 均退出码 0。它们证明现有表格、技能 UI、战斗状态机、通用特效播放和 `FUNC_SKILL` 队列不回归,但不能把动作循环、移动技能、grade、挂点/颜色/混合、飞行/命中事件和服务端结果判定视为 40250 等价。
本轮没有修改实现;技能基础校验和部分本地动作链可用,但正式技能特效接线、远端动作参数和资源驱动事件仍未闭合,合同保持 `PARTIAL`
## Active MSA motion ownership review
本轮继续对照 `CInstanceBase::NEW_UseSkill``CActorInstance::SetMotionLoopCount`
`CActorInstance::MotionEventProcess` 核对施法锁定的结束源。此前本地入口虽然已经绑定真实
MSA 并支持 LoopData,但 `NetPlay` 仍会无条件按 clip duration 清除 `_using_skill`,因此
当循环片段或动作尾部晚于名义 duration 时会提前解锁,和 40250 的动作所有权不一致。
现已修复:`GameScene._on_skill_cast_started()` 将“技能 MSA 是否成功绑定”传入
`NetPlay.start_skill_cast()`;有效动作设置为 motion-driven,施法门只在
`PlayerView` 的动作尾部发出 `motion_bound("wait")` 时清除;缺失 MSA 或 headless 测试仍使用
duration 作为兼容兜底。`is_lock()``_process()` 两条计时路径均遵守该门,死亡/重置/网络
状态失效也会清除 motion-driven 状态,避免下一次施法继承旧门。
新增回归覆盖“名义时长已过但 MSA 动作未结束仍保持锁定”和“动作尾事件后解锁”。
`target_effect_test``test_no_auto_move_regression``netplay_test``skill_test`
`combat_fx_test``effect_playback_test``test_skill_state_arg_parity`
`net_world_remote_action_test``player_motion_test` 及既有战斗/技能循环测试均通过。
### Active GameLib skill-event review
- `GameLib/ActorInstanceEvent.cpp` 明确把 `OnAttack``OnUseSkill``OnHit` 和 affect 清理交给 actor event handler`OnUseSkill``uLoopCount` 与 moving-skill bit 组合后才传给上层,不能用固定施法延迟完全替代。
- `GameLib/ActorInstance.h` 的 motion queue、`IsUsingMovingSkill``CanCancelSkill``MotionEventProcess``ProcessMotionEventEffectToTargetEvent``ProcessMotionEventSpecialAttacking``ProcessMotionEventFly` 构成技能时序的消费者;当前 `GameScene` 主要处理本地主角 signal,远端/怪物的同一事件分支没有完整接线。
- 本轮完成 GameLib actor skill/event 核对;现有 `skill_test`/`effect_playback_test` 仍只是局部资源与 helper 证据,技能合同继续为 `PARTIAL`
## Equivalence matrix
| 项目 | 状态 | 备注 |
|---|---|---|
| Preconditions | PARTIAL | 等级、职业、目标、冷却已有本地校验;真实服务端权限仍待验证 |
| Branch structure | PARTIAL | 单体/范围/辅助及失败分支已有代码映射;中断仍需端到端记录 |
| Algorithms/formulas | PARTIAL | 范围/目标选择已映射,技能公式仍由服务端权威 |
| State transition order | VERIFIED | `FUNC_SKILL|motion` 远近分支与到达后动作已按 40250 修复并测试 |
| Constants/units | UNASSESSED | 范围、持续时间、速度和坐标 |
| Timing/event sources | PARTIAL | 有效本地 MSA 的施法门现在由 `motion_bound("wait")` 动作尾释放,缺失动作才使用兼容计时器;逐技能 MSA 关键帧和特效事件仍未完整接线 |
| Resource/data sources | UNASSESSED | skilldesc、skilltable、MSA、MSE 和挂点 |
| Protocol side effects | PARTIAL | 技能状态上行、动作状态接收已覆盖;实际结果包仍需服务端验证 |
| Interruption/failure/cleanup | PARTIAL | 远端不可取消技能/表情会阻止后续 TCP 状态,动作尾、节点剔除和换图会清门;可取消技能、死亡中断、重复施法和特效回收仍未全闭合 |
## Remaining
- 逐技能类型建立 40250 → Godot 分支映射。
- 用至少一个真实攻击技能和一个辅助技能完成时序测试。
- 远端 `FUNC_SKILL` 的动作循环次数、移动技能标志和等级动作索引已完成传递;本地有效 MSA
的施法锁定已改为动作尾驱动;逐技能
MSA 事件到 MSE 的挂点、颜色/混合和服务端结果仍需继续验证。
- 继续补齐未实现的颜色、混合、挂点和动作事件语义。
## Active UserInterface skill-data review
- `PythonSkill.h` 还负责从 skill table/desc 注册技能、计算 cooldown/SP/continuation/loop/target/duration 和 motion index;这些值是 `PythonPlayerSkill`、quickslot 与 `ActorInstanceEvent` 的输入,不能由固定 UI 延迟代替。
- 本轮确认技能数据桥已被 active Visual Studio 工程引用,但当前端动作事件链仍没有证明使用同一套资源字段驱动循环次数、移动技能标志、grade、飞行/命中/特殊攻击挂点和 cleanup;合同保持 `PARTIAL`
## Implementation fix round 2026-09-21T12:30Z
本轮按 40250 `CActorInstance::__OnUseSkill` 的确切打包规则闭合技能动作参数丢失:
`uArg = uLoopCount | (isMovingSkill ? 1 << 4 : 0)``SkillTable` 现在从 `.msk`
`MotionLoopCountFormula``SkillPoint` 计算低 4 位,并把 `MOVING_SKILL` 属性编码到
第 4 位;快捷栏和本地 `GameScene` 都使用同一份值,`M2Client.cast_skill` 不再固定发送
零参数。
远端链路新增 `Entity.func_arg`TCP state queue 在“走到目标后施法”与“近距离立即施法”
两条分支都保留该参数;`NetWorld._apply_anim` 再解出循环次数和移动技能标志传给
`PlayerView.play_skill_motion`。新增 `test_skill_state_arg_parity.gd` 与原生
`net_state_queue_test` 断言了位编码、近距离分支和远距离到达分支;技能表/快捷栏、场景装配、
战斗特效、通用播放及全量 23 项 CTest 均通过。
这一轮修复的是动作参数和状态链,不宣称已经实现 `SetMotionLoopCount` 的真实循环片段、
远端 grade 的权威来源,或完整 MSA→MSE→命中结果时间线;挂点、颜色/混合、飞行/特殊攻击
事件、服务端结果和中断清理继续保持 `PARTIAL`
## Implementation fix round 2026-09-21 — remote motion gates
按 40250 `__CanProcessNetworkStatePacket` / `__IsEnableTCPProcess` 补齐远端动作期间的
网络状态门:不可取消技能、活动表情和 TCP 状态禁用会保持后续 `GC_MOVE` 在队列中;技能或
表情的真实一次性动作通过 `motion_bound("wait")` 尾事件释放,而不是使用固定计时器。`FUNC_EMOTION`
`uArg` 也会尝试进入实际 `CRaceMotionData` action ID,出生时携带动作状态的实体会立即绑定
初始动作。
`net_state_queue_test``net_world_remote_action_test``net_world_vis_test``netplay_test`
`player_motion_test``test_skill_state_arg_parity` 和完整 `ctest`23/23)通过。技能可取消
性的权威 MSA 数据、grade、逐技能关键帧/特效事件、双人表情目标和完整死亡/断线清理仍为
`PARTIAL`
## Implementation fix round 2026-09-22 — MSA LoopData / SetMotionLoopCount
按 40250 `CInstanceBase::NEW_UseSkill``CActorInstance::SetMotionLoopCount`
`CurrentMotionProcess` 的实际语义补齐技能动作循环:`Metin2AnimPlayer` 现在读取当前
`.msa``LoopData`,保留动作的 lead-in/tail,只在 `LoopStartTime..LoopEndTime` 片段
内回绕;正数 `uMotLoopCount` 在动作绑定后覆盖资源默认次数,`0` 使用资源中的
`MotionLoopCount``-1` 保持无限片段循环。循环边界会重复派发该段动作事件,最终尾部才发出
`playback_finished`,因此 `motion_bound("wait")`/技能网络门不会提前释放。
`PlayerView.play_skill_motion()` 已把本地与远端统一传入的 loop count 交给原生播放器。
新增 `skill_motion_loop_test.gd` 覆盖 PlayerView 参数传递、真实 `yeonsa.msa` 的资源默认值、
覆盖为 2 时的一次片段回绕、计数递减、尾部结束和切换普通动作后的清理;技能表、状态参数、
远端动作、特效播放和 PlayerView 回归均通过。
该轮闭合了 `SetMotionLoopCount`/MSA 片段时序差异,但技能 grade 的权威来源、逐技能 MSA
事件到 MSE 的挂点/颜色/混合、飞行/特殊攻击、服务端结果和完整中断清理仍保持 `PARTIAL`
## Implementation fix round 2026-09-21T03:46Z — skill grade motion index
本轮先在 `project/skill_test.gd` 加入等级动作回归,修复前复现 1 项失败:技能表没有
等级动作索引入口。对照 40250 `PythonSkill.cpp::GetSkillMotionIndex`
`SKILL_EFFECT_COUNT=4``SKILL_GRADEGAP=25` 以及当前资源
`assets/root/constinfo.py``USE_SKILL_EFFECT_UPGRADE_ENABLE=1` 后完成修复:
- `SkillTable::motion_idx_for_grade``base + grade * 25` 计算 M/G/P 动作索引,
超出该技能 `grade_count` 回退基础索引;新增 `motion_name_by_idx` / `motion_grade_by_idx`
让远端收到 26/51/76 等等级动作索引时能恢复同一技能和对应 `_2/_3/_4.msa`
- `Quickbar` 普通技能快捷栏和技能窗口直施路径在 `cast_skill` 前读取当前 master grade
不再只发送基础 motion index;公会技能仍按 40250 `UseGuildSkill` 使用基础索引。
- `NetWorld::_apply_anim` 从远端 FUNC_SKILL 的等级动作索引反解 grade,再传给
`PlayerView.play_skill_motion`;循环次数和 moving-skill bit 保持原有编码。
修复后通过:`skill_test.gd``net_world_remote_action_test.gd`
`test_skill_state_arg_parity.gd``skill_motion_loop_test.gd``skill_fx_test.gd`
`combat_fx_test.gd`。本轮只闭合了等级动作索引及其本地/远端映射,不宣称逐技能 MSA
事件、MSE 挂点/颜色/混合、飞行/特殊攻击、服务端结果和完整中断清理已完全等价,合同继续为
`PARTIAL`
## Implementation fix round 2026-09-21 — remote SPECIAL_ATTACKING consumer
本轮按 40250 `ActorInstanceMotionEvent.cpp::ProcessMotionEventSpecialAttacking`
`InstanceBaseBattle.cpp::AttackProcess`
`ActorInstanceCollisionDetection.cpp::__SplashAttackProcess` 继续核对技能事件消费者。
参考端的 `SPECIAL_ATTACKING` 不是只有本地主角才能创建的事件:每个可见
`CActorInstance` 都会建立自己的 `m_kSplashArea`,随后由 `AttackProcess` 遍历目标,执行
1000cm broad-phase、`ATTACK_TYPE_SNIPE` 目标限制、碰撞球检测、`HittedObjectSet` 去重和
`iHitLimitCount` 上限,最后调用 `__ProcessDataAttackSuccess`;只有本地事件处理器才把
`isEnableHitProcess` 转成 `CG_ATTACK`
修复前 `GameScene._on_remote_motion_event()` 只处理远端特效/飞行/隐藏事件,远端 type 4
事件没有消费者;`NetPlay` 只有本地主角的 `start_motion_splash()`
`_splash_attack_process()`。现在:
- `NetWorld._apply_anim()` 保存当前远端 `FUNC_SKILL` 的 motion index,供事件恢复 `uSkill`
- `GameScene._on_remote_motion_event()` 将 type 4 送入 `NetPlay.start_remote_motion_splash()`
- `NetPlay._remote_splash_attack_process()` 使用与本地相同的 `MotionSplash` 几何、持续时间、
去重、命中上限和攻击可达性判定;
- 远端命中复用 `__ProcessDataAttackSuccess` 对应的硬直、无敌时间、受击动作、抖动和击退
分支,但把 `InsertDelay` 施加在远端攻击者,且禁止远端命中伪造本地 `CG_ATTACK`
新增 `remote_motion_splash_test.gd` 覆盖创建、命中反应、攻击者硬直、无上行攻击包、同 VID
去重和 `DuringTime` 到期清理。该回归与 `net_world_remote_action_test.gd`
`net_world_motion_event_test.gd``motion_splash_test.gd``combat_fx_test.gd`
`skill_test.gd` 均通过。
本轮只闭合了远端 `SPECIAL_ATTACKING` 的本地消费者;真实服务端伤害/经验结果、完整技能
MSA→MSE 挂点/颜色/混合、远端资源矩阵、所有中断/断线清理仍保持 `PARTIAL`