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

18 KiB
Raw Blame History

combat.attack_combo_damage

Scope

审计普攻、连击输入窗口、攻击动作状态包、.msa 命中窗口、碰撞命中、伤害包、击退/硬直、弓箭 FLY 帧和换图清理。

Reference call chain

  • CPythonPlayer::NEW_Attack
    • 检查 private shop、__CanAttack、死亡/晕眩/施法/坐骑等级/弓箭条件。
    • 按目标/方向调用 CInstanceBase::NEW_AttackNEW_AttackToDestInstanceDirection
  • CInstanceBase::InputComboAttack / __ComboProcess / __RunNextCombo
    • 根据职业、武器、坐骑、连击技能等级和 ComboInputData 推进段号;动作结束时清理 combo。
  • CPythonPlayerEventHandler::OnAttack
    • 每个动作段发送 FUNC_COMBO + motion index 状态包。
  • CActorInstance::AttackProcess / __NormalAttackProcess
    • .msa 的 hit window 内用武器扫掠球和目标 .msm 防御球检测命中。
  • CPythonPlayerEventHandler::OnHit
    • 设置目标并发送 CG_ATTACK;根据受击方 owner、巨型/霸体和外力执行击退及同步位置。
  • CNormalBowAttack_FlyEventHandler_AutoClear
    • 起手 OnSetFlyTarget 发送目标,.msa FLYOnShoot 发送 CG_SHOOT;事件处理器随旧 Actor/动作生命周期失效。

Current call chain

  • NetPlay._on_pick / _update_auto_attack / _reserve_process_click_actor
    • 目标选择、攻击距离、自动攻击和预约追击。
  • NetPlay._do_attack_swing / _run_next_combo / _combo_process
    • 播放动作、发送 FUNC_COMBO、读取 .msa combo/hit windows 和攻击速度。
  • NetPlay._attack_process / _normal_attack_process
    • 读取攻击者采样点、目标 defending spheres,执行 Z-cylinder 几何判定和 hit dedup。
  • NetPlay._process_attack_success / _on_hit
    • 受击动画、特效、硬直、击退、目标更新和 client.attack
  • GameScene._queue_shot / _on_local_motion_event(type=FLY)
    • 将弓箭技能先排队,等 .msa FLY 帧再发送 CG_SHOOT_on_world_reset 现在清理旧队列。

Branch matrix

场景 40250 当前端 结论
普攻输入 NEW_Attack 由方向/目标/按键分派,并受 CanAttack gates 限制 _on_pick、SPACE/auto attack 和 _do_attack_swing 已接入 PARTIAL:鼠标 press/release owner 不完全相同
连击段 ComboInputData 的 start/next/end 时间和动作尾清理段号 .msa 数据、combo 表和 motion_bound 清理已实现 MAPPED + tests
状态包 每段 FUNC_COMBO + motion index,技能使用 `FUNC_SKILL motion` _send_state 发送对应 func/arg/位置
.msa 命中窗 AttackProcess 每帧扫掠真实武器球与 .msm 防御球 仅在有效 AttackingData/HitDataContainer 和真实 defending sphere/bone 存在时判定 FIXED + regressionWeaponTrace 视觉副作用仍未等价
命中限制 m_HitDataMapfInvisibleTime、hit limit、combo 每窗去重 _hit_dedup、invisible、limit 和 combo 分支已实现 MAPPED + tests
受击后果 OnHit 发 CG_ATTACK,按 owner/巨型/霸体决定击退和 sync position _process_attack_success / _on_hit 已映射 PARTIAL:服务端确认/死亡包实际闭环未 fixture 化
弓箭 起手 targetFLY 帧才 shoot;旧 handler 清除 _pending_shots 等 FLY 帧发送;本轮已在 world reset 清空 MAPPED + regression
换图/断线 旧 Actor handler 和动作状态失效,不带旧攻击事件 reset 已清 shot queue,其他攻击/命中窗状态仍分散 PARTIAL

Equivalence matrix

项目 状态 证据
Preconditions MAPPED _can_attack、safe/dead/stun/skill/mount/arrow gates 已定位
Branch structure PARTIAL 鼠标 press/release、方向攻击、自动攻击、弓箭和坐骑完整排列不足
Algorithms/formulas PARTIAL combo/hit geometry 已映射;缺失资源 fallback 已移除,WeaponTrace 和服务端伤害仍不等价
State transition order PARTIAL 动作绑定、hit window、CG_ATTACK、击退和 sync position 顺序需包级证明
Constants/units PARTIAL 攻击距离、speed ratio、hit limit、stiffen/invisible 等需从资源和 fixture 双证
Timing/event sources PARTIAL 参考由 Actor motion event 驱动,当前由 Godot _process 和 signal 驱动
Resource/data sources PARTIAL .msa/.msm 资源读取和缺失资源拒绝分支已接入;真实资源覆盖与 WeaponTrace 数据源仍未完全证明
Protocol side effects PARTIAL FUNC_COMBO/CG_ATTACK/CG_SHOOT 已映射,真实服务端回包闭环未验证
Interruption/failure/cleanup PARTIAL 本轮补了 pending shot;死亡、换图、断线、动作缺失和重复命中仍需测试

Regression evidence

  • project/combat_fx_test.gd:攻击锁定、连击段、hit window、命中去重、击退、抗击倒、弓箭 target/shoot 时序。
  • project/combat_parity_test.gd:攻击锁、伤害数字和受击反馈。
  • project/gamescene_test.gdworld reset 清理旧弓箭 FLY/CG_SHOOT 队列。
  • extension/tests/net_state_queue_test.cpp:远端 FUNC_ATTACKFUNC_COMBOFUNC_SKILL 到达/动作状态。

Active GameLib weapon-trace review

  • GameLib/WeaponTrace.cpp/.h 使用动态池,按武器骨骼矩阵采样时间点,构建连续刀光顶点带,并分别支持纹理/alpha、生命周期、采样间隔、reach scale、TurnOn/TurnOff 和清理;它是 ActorInstance 攻击动作的渲染副作用,不是一次性的命中特效。
  • 当前战斗链有 skill_fx、hit effect 和动作事件,但没有证明每个武器类型的骨骼采样、刀光顶点、alpha/texture 模式和跨动作清理与 WeaponTrace 相同;现有 combat_fx_test 只覆盖战斗桩和效果入口。
  • 本轮完成 WeaponTrace 活跃源码静态核对,攻击/连击合同继续为 PARTIAL

Status

PARTIAL。普攻/连击主体已经有较完整的本地模拟;当前已删除缺少有效动作攻击数据时的本地 CG_ATTACK 合成路径,但 WeaponTrace、服务端实际伤害结果和完整输入/生命周期矩阵仍未达到 40250 完全等价。

Audit round 2026-09-20T21:12Z

本轮沿 CActorInstance::AttackingProcess -> __NormalAttackProcess -> OnHit 重新核对当前攻击实现,并重跑 combat_fx_testcombat_parity_testgamescene_test 和 23 项 native CTest

  • 状态包顺序、命中窗建立、fInvisibleTime/hit-limit 去重、受击硬直/击退、弓箭 FLY 帧以及换图清理已有可执行实现和回归证据。
  • 明确保留差异:当前 _emit_swing 在缺少 .msa 命中窗时立即退化发送 CG_ATTACK_normal_attack_process 在空 samples 时使用正前方/近距离兜底;40250 在没有有效 m_pkCurRaceMotionData/HitDataContainer 时不会合成这条本地命中路径。
  • WeaponTrace 仍只有参考端源码证据,当前没有动态武器骨骼采样、刀光顶点带、alpha/texture 模式和动作中断回收的等价实现。
  • native CTest 首次串行回归出现一次 net.classic_encstream 的加密头断言失败,单测复跑和全量复跑均通过;暂记为测试环境/时序抖动,不作为本轮攻击实现通过依据。

结论:缺失资源 fallback 已按 40250 的无有效攻击数据分支修复;WeaponTrace 和真实服务端伤害/死亡/击退包序进入下一轮修复候选,合同保持 PARTIAL

Audit and implementation round 2026-09-21

沿 ActorInstanceBattle.cpp::ComboAttackActorInstanceMotion.cpp::__SetMotionPythonPlayerEventHandler::OnAttack 重新核对事件顺序,确认参考端是:先绑定 InterceptOnceMotion(同时绑定攻击数据、清理命中表,并触发弓箭 OnSetFlyTarget),再由 __OnAttack 发送 FUNC_COMBO + motion index。当前端原先在 _emit_swing 中先发状态包、再绑定 Godot 动作,存在服务端状态已发出但本地命中窗口尚未建立的顺序差异。

本轮已调整为“绑定动作/建立命中窗口 → 弓箭 SetFlyTarget → 发送 FUNC_COMBO”,并新增 combat_fx_test.gd 的跨对象顺序断言;combat_fx_test.gdcombat_parity_test.gdgamescene_test.gd 均通过。

仍保持 PARTIAL:当前没有 WeaponTrace 的骨骼采样/刀光生命周期实现,也没有真实服务器对 CG_ATTACKCG_SYNC_POSITION、死亡/伤害/击退包的端到端 fixture;弓箭和攻击状态在换图、 断线、动作中断后的全部清理仍需要继续核对。

Implementation fix round 2026-09-20T21:18Z

按 40250 CActorInstance::isValidAttackingActorInstanceCollisionDetection.cpp::__NormalAttackProcess 修复攻击命中源:

  • NetPlay::_emit_swing 不再在缺少当前动作 AttackingData/HitDataContainer 时直接发送 CG_ATTACK;仍只发送动作对应的 FUNC_COMBO 状态包,真正的 CG_ATTACK 只能由命中处理链触发。
  • NetPlay::_attack_process 现在要求当前动作存在有效 AttackingData 和命中窗;空命中窗不命中。
  • _defending_spheres 不再为缺少 .msm 防御球的节点合成占位胶囊;空 m_DefendingPointInstanceList 直接无命中。
  • combat_fx_test.gdnetplay_test.gd 增加缺少动作攻击数据和缺少防御球的反例,combat_parity_test.gdplayable_combat_test.gd 同步回归通过。

本轮修复了“缺失资源仍产生本地命中”的结构差异;WeaponTrace 骨骼采样/刀光生命周期、 真实服务端伤害确认和死亡/击退包序仍保持 PARTIAL

Audit round 2026-09-20T21:24Z — WeaponTrace call-chain review

本轮只做差异审计,没有把现有 has_trail 配置字段或一次性 .mse 特效误判为 WeaponTrace 已实现。沿 40250 的完整调用链核对结果如下:

  • ActorInstanceAttach.cpp::AttachWeapon 仅对非 bell/fan/bow 武器创建 CWeaponTrace,并绑定武器模型实例;当前 equip_model.gd 只设置 weapon_gr2/shield_gr2 与强化 .mse,没有按武器类型创建轨迹对象。
  • ActorInstance::INSTANCEBASE_Deform 每帧执行 TraceProcess;当前 PlayerView/Metin2Model 没有对应的每帧武器骨骼采样出口。
  • CWeaponTrace::Update 同时维护 short/long 两条世界坐标时间队列,使用默认 lifeTime=0.18ssamplingTime=0.003s、武器 bound-box 长度和 __GetReachScale() 生成刀根/刀尖采样;当前端没有时间队列、reach scale、采样间隔 或真实武器骨骼矩阵等价物。
  • CWeaponTrace::BuildVertex/Render 用 cubic spline 生成 triangle strip,分别提供 alpha/texture 渲染模式;当前端没有 WeaponTrace mesh、顶点带、材质模式或独立 RenderTrace 阶段。socket_glow_fx_system.gdhas_trail 只是强化配置结果, 没有消费方。
  • ActorInstanceMotion.cpp::__SetMotion 在绑定新动作前统一 __HideWeaponTrace 成功绑定可攻击动作后 __ShowWeaponTraceDestroy/Clear 删除全部 trace TurnOff 保留旧队列以便自然淡出。当前 _bind、播放结束、换装、换图和销毁路径 都没有这一组开关/淡出/清理语义。

现有证据:player_motion_test.gdcombat_fx_test.gdcombat_parity_test.gdplayable_combat_test.gd 通过,但它们只能证明动作/战斗门和命中链,不能证明刀光。 weapon_attach_test.gd 当前还存在 race 0 的 03150 双手动作左手贴合断言失败并伴随 exit 139;该失败属于武器动作绑定的独立问题,也说明不能用当前武器挂点测试替代 WeaponTrace 证据。

结论:combat.attack_combo_damage 继续为 PARTIAL。下一次实现应新增独立 WeaponTrace 适配层,并在真实武器模型上验证“创建过滤 → 动作开关 → 双轨迹采样 → 刀光网格/alpha → 动作中断自然淡出 → 换装/换图销毁”的全链路;在此之前不得声称 40250 的武器刀光已经复刻。

Implementation round 2026-09-20 — WeaponTrace adapter

已新增 project/ui/weapon_trace.gd,并将它接入 project/ui/player_view.gdproject/ui/equip_model.gd,对应 40250 的以下调用边界:

  • EquipModel._weapon_trace_enabled() 对 bell/fan/bow 不创建刀光,普通武器才允许创建;换装和空武器路径执行 detach/clear/free。
  • PlayerView._refresh_weapon_trace() 在附着的 Weapon 模型上建立 trace_bind() 在绑定新动作前 TurnOff,攻击/连击动作成功绑定后 TurnOn,播放结束保留旧点并自然淡出。
  • WeaponTrace._process() 每帧维护 short/long 世界坐标时间队列;默认 0.18s 生命周期、0.003s spline 采样步长,并复刻参考端三对角 cubic spline 与 triangle strip 顶点交错。
  • StandardMaterial3D 提供双面、透明、无光照、不写深度的 alpha 路径,并保留 texture/alpha 两种模式;独立 weapon_trace_test.gd 覆盖附着、采样、网格、TurnOff 尾迹、过期、重挂和 PlayerView 攻击开关。

当前仍有明确适配差异,不能标记为完全一致:Godot 端点由 attached weapon 的 mesh AABB 推导,尚未由 native bone matrix 直接提供 m_fLength * __GetReachScale() 的同一坐标语义;远端/怪物渲染覆盖、lot_ade10-2.tga 真实视觉资源和服务端动作资源驱动的截图证据仍缺;weapon_attach_test.gd 的 race 0 / 03150 双手动作左手贴合断言仍失败并 exit 139。socket_glow_fx_system.gd::has_trail 仍是独立强化配置,不能当作 WeaponTrace 消费方。

结论:本轮把 WeaponTrace 从“生产链缺失”修复为“生产链已接入、核心生命周期有证据、坐标端点和覆盖范围仍 PARTIAL”。

Implementation fix round 2026-09-21T03:42Z — MainInstance damage billboard

沿 40250 PythonNetworkStreamPhaseGame::RecvDamageInfoPacketCInstanceBase::AddDamageEffectCInstanceBase::ProcessDamage 复核本地伤害显示: 参考端只要 CharacterManager 找到目标实例,且目标就是 MainInstance,仍会按 bSelf 入伤害队列并在本地主角上显示 damage_*.dds/MISS。

当前端 GameScene 为避免把本地主角重复挂入 _by_vid,将其单独保存为 _local_node。 修复前 NetWorld._process_damage_queue() 只从 _by_vid 取节点,导致本地主角自己的 伤害包已经入队,却在渲染前被静默丢弃。现在对本地 VID 使用 _local_node 回退,保持 普通远端 actor 与 MainInstance 的同一 ProcessDamage 分类和渲染路径。

新增 project/damage_local_main_parity_test.gd,修复前专项回归失败,修复后通过; 合同仍为 PARTIAL,因为服务端真实伤害/死亡包、WeaponTrace 坐标端点、远端/怪物覆盖和 攻击生命周期清理尚未达到 40250 完全等价。

Implementation fix round 2026-09-21T03:46Z — multi-part WeaponTrace

沿 ActorInstanceAttach.cpp::__IsRightHandWeapon__IsLeftHandWeaponCActorInstance::AttachWeapon 继续核对:参考端不是每个角色只有一个刀光。 匕首会同时挂 PART_WEAPONPART_WEAPON_LEFT,骑马扇也可能有双部件;每个可追踪 部件都独立创建一个 CWeaponTrace,并由 m_WeaponTraceVector 统一执行开关、纹理和 清理操作。

修复前当前 PlayerView._refresh_weapon_trace() 只查找 Weapon 并维护一个对象, 因此第二个部件的刀光不会出现。现在按 WeaponShield 部件顺序维护多条 trace, 逐部件执行 attach/TurnOn/TurnOff/detach,并补齐对应的纹理、Texture/Alpha 模式 控制入口。新增 project/weapon_trace_multi_part_parity_test.gd,修复前回归失败, 修复后与 weapon_trace_test.gdequip_model_test.gdweapon_attach_test.gdplayer_motion_test.gdcombat_fx_test.gd 一起通过。

仍为 PARTIAL:当前刀根/刀尖端点仍由 Godot mesh AABB 推导,不是参考端骨骼矩阵加 __GetReachScale();远端/怪物 trace 覆盖、真实刀光纹理视觉和服务端攻击闭环仍未证明。

Implementation fix round 2026-09-21T03:55Z — WeaponTrace geometry and reach scale

本轮继续沿 40250 的 GameLib/WeaponTrace.cppInstanceBaseEffect.cpp::__Warrior_SetGeomgyeongAffectActorInstanceCollisionDetection.cpp::__NormalAttackProcess 核对端点与攻击扫掠:

  • 参考端在 SetWeaponInstance 中从武器 bound box 到骨骼原点计算 m_fLengthUpdate 再沿武器局部 Z 轴使用 m_fLength * fReachScale 生成刀尖; 当前端不再把任意 AABB 角点当作方向,而是取 attached mesh AABB 的最大半径作为长度, 沿局部 Z 轴延伸,并提供可验证的 reach_scale
  • 参考端 AFFECT_GEOMGYEONG 开启时把 reach scale 设为 1.5,关闭时恢复 1.0 当前 PlayerView 将该状态广播到所有 Weapon/Shield trace,并在重新附着时保留当前值。
  • 参考端普通攻击把 dsiSrc.v3Position - dsiSrc.v3LastPosition 乘以 __GetReachScale() 后再参与碰撞变换;当前 NetPlay._normal_attack_process 通过 _attack_sample_positions 对同一段位移应用该缩放,因此视觉刀光和命中扫掠共用同一 reach scale 来源。

修复前专项回归分别复现了端点方向/长度错误及缺少 reach scale API、Geomgyeong 未改变 reach scale、攻击采样没有暴露 reach-scaled endpoint;修复后以下测试通过: weapon_trace_endpoint_parity_test.gdweapon_trace_reach_affect_parity_test.gdweapon_reach_scale_attack_parity_test.gdweapon_trace_multi_part_parity_test.gdweapon_trace_test.gdcombat_fx_test.gdcombat_parity_test.gdplayable_combat_test.gdtest_combat_status_effect_parity.gdnetplay_test.gd

合同仍为 PARTIAL:当前端点的长度输入来自 Godot attached mesh AABB,尚未直接消费 native CActorInstance 的同一骨骼 composite matrix、旋转和绑定坐标语义;远端/怪物 覆盖、真实刀光纹理视觉、服务端真实攻击/死亡包级 fixture 和跨动作/断线清理仍未闭合。

Audit evidence reconciliation 2026-09-21T04:00Z

重新运行当前工作树的 weapon_attach_test.gd,加载 00010.gr203150.gr2 均正常, 进程退出码为 0;此前记录的 race 0 / 03150 左手贴合断言失败不再是当前待办项,已从 manifest 的 remaining 移除。该校正只更新证据状态,不改变 WeaponTrace native 骨骼 composite matrix、远端/怪物覆盖和服务端攻击闭环仍未证明的结论。