Files
mtgodot-poc/audit/contracts/combat.target-selection-rules.md
T
shenleiandClaude Opus 5 a989b8f19b chore: delete 106 unreachable *_system.gd and their parity tests
These files were loaded only by their own test_*_parity.gd and never by
the runtime (transitively checked, incl. client_phase_integration_system).
Removes 76 manifest implementation/evidence references across 18
contracts; contract docs get a note that conclusions based on them are
void. Live systems kept: chest, consumable, item_attr, metin_socket,
whisper_chat.

Full headless suite: no new failures (7 failing + 5 headless-hanging
render tests are identical before the deletion).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:51:52 +09:00

9.9 KiB
Raw Blame History

combat.target_selection_rules —— 目标选择、可攻击判定、目标框和安全区

状态:PARTIAL

本合同对照 40250 Windows 客户端的三套不能合并的判据:

  1. CInstanceBase::IsAttackableInstance:是否允许战斗逻辑攻击目标。
  2. CInstanceBase::CanPickInstance / IsTargetableInstance:是否允许鼠标/脚本设置目标。
  3. CPythonNetworkStream::RecvTargetPacket + CanViewTargetHP:服务端目标包到达后,目标框是否关闭、显示血量或保持。

40250 参考调用链

战斗判定

  • UserInterface/InstanceBase.cpp:2147 CInstanceBase::IsAttackableInstance
    • 主角观战模式先拒绝;自身 VID 先拒绝。
    • 石头只能被 PC 攻击。
    • PC 目标按决斗、同公会、killer、PK mode、队伍、同/异帝国、PVP/GVG、善恶冲突逐分支判断。
    • PC 可攻击敌怪、木门;敌怪可攻击 PC/建筑;变身实例按 IsPoly 分支判断。
  • UserInterface/PythonPlayer.cpp:207 CPythonPlayer::__Update_AutoAttack
    • 自动攻击目标必须存在、未死亡、骑乘等级允许,并通过 IsAttackableInstance
  • UserInterface/PythonPlayerInput.cpp:324-370 __OnClickActor
    • 点击实体后先清预约;攻击目标再检查 __CanAttack、距离和 NPC 交互分支。
  • UserInterface/PythonPlayerInput.cpp:336-380 __OnPressActor
    • 目标切换、自动攻击、弓箭射程、预约靠近和最终 IsAttackableInstance 的顺序固定。

目标可选中判定

  • UserInterface/PythonPlayerInput.cpp:112 CPythonPlayer::SetTarget
    • 先检查主角 CanChangeTarget()
    • 已选中目标有强制/非强制切换时间窗。
    • 新目标必须通过主角 IsTargetableInstance()
    • 目标成功时设置 actor targeted 状态、飞行目标和网络 SendTargetPacket;失败时清空本地目标。
  • UserInterface/InstanceBase.cpp:2251 IsTargetableInstance
    • 直接委托目标的 CanPickInstance()
  • UserInterface/InstanceBase.cpp:2263 CanPickInstance
    • 视锥外拒绝。
    • 死亡门;死亡门对门对象单独处理。
    • PC 的隐身、复活隐身、普通隐身按 affect 和主角可见性拒绝。

目标框和安全区

  • UserInterface/PythonNetworkStreamPhaseGame.cpp:2424 RecvTargetPacket
    • 目标或主角实例缺失:关闭目标框。
    • 目标已死亡:保持当前目标框状态,不写入新的实例目标。
    • PC/建筑:CloseTargetBoardIfDifferent
    • 石头/木门/敌怪:SetHPTargetBoard
    • 其它实体:关闭目标框。
  • UserInterface/InstanceBase.cpp:2293 CanViewTargetHP
    • 只有石头、木门和敌怪能显示 HP 目标框。
  • UserInterface/InstanceBase.cpp:560 IsInSafe
    • 通过地图 ATTRIBUTE_BANPK 判定安全区;它与“能否选目标”和“目标框能否显示”是独立判据。
  • UserInterface/PythonPlayer.cpp:496-520 NotifyCharacterDead/Update
    • 当前目标死亡时清目标;目标更新后不再 CanPickInstance 时清目标并关闭目标框。

当前端实现映射

  • project/entity_rules.gd
    • is_attackable_instance 已覆盖观战、自身、PC/怪/石头/木门、PK mode、party、killer、duel、PVP/GVG 和 alignment 分支;can_pick_instance 统一镜像视锥、死亡、门死亡和三类隐身 affect 门。
  • project/net_play.gd
    • _is_attackable 拼装会话上下文并调用 EntityRules
    • _on_pick 负责 NPC/warp 交互、战斗目标选择、预约靠近和自动攻击。
    • _reserve_process_click_actor 处理距离、攻击资格、死亡、安全区、弓箭和攻击顺序。
    • _on_target_info 镜像 RecvTargetPacket 的目标框分派。
    • _change_target_to_picked_instance 是当前统一脚本目标入口,先走 _can_change_target,再走重复目标时间窗和 _is_targetable_instance
    • _motion_blocks_target_change 读取当前 .msa 的 WARP/FLY/EFFECT_TO_TARGET 事件,缺少动作数据时按 40250 的保守门处理。
  • project/target_board.gd
    • classifycan_view_target_hp、异阵营过滤、Ctrl 悄悄话和 HP 百分比边界已独立镜像。
  • project/player_controller.gd
    • 使用射线和可点选节点进行实体命中;射线与悬停两条路径均消费 NetWorld 写入的 can_pick 门。
  • project/net_world.gd
    • 负责目标光圈、目标/悬停名字强显和实体节点生命周期,并在实体创建/更新时同步 can_pick
  • project/alignment_pk_system.gd
    • 提供善恶值、PK 头衔和死亡掉落辅助逻辑;这些不是 40250 客户端 SetTarget 的替代实现。

分支等价矩阵

参考判据/副作用 当前实现 结论
IsAttackableInstance 的 PC/怪/石头/木门/PK/决斗/PVP/GVG 分支 EntityRules.is_attackable_instance 基本等价;已有 entity_rules_test.gd 覆盖边界
自动攻击先检查存在、死亡、坐骑等级,再检查攻击资格 _on_pick_reserve_process_click_actor、自动攻击链 部分等价;多个调用点重复拼接门控,未由统一目标状态对象约束
SetTarget 先执行 CanChangeTarget _can_change_target_motion_blocks_target_change,按当前 .msa 的 WARP/FLY/EFFECT_TO_TARGET 事件门控 已补齐静态/可读取动作事件路径;真实完整动作资源仍待 live 验证
SetTarget 必须通过 IsTargetableInstance/CanPickInstance EntityRules.can_pick_instance + NetWorld can_pick 元数据,脚本目标、射线点选和悬停共用 已补齐视锥字段、死亡、门死亡、EUNHYEONG/复活隐身/普通隐身门;真实视锥仍待 live 验证
NPC/warp 不可攻击但可在距离内交互 _on_pick 分流到 _send_click_actor_packet 基本等价;真实包拒绝/冷却仍缺 live 证据
RecvTargetPacket 的 PC/building/HP/CLOSE/NONE 分支 TargetBoard.classify + _on_target_info 静态分支基本等价;缺少目标死亡/更新/删除乱序时序测试
NotifyCharacterDead/Update 清目标并关闭目标框 _on_entity_dead / _on_entity_gone 统一 CG_TARGET(0) 清理;NetWorld 更新同步 can_pick 死亡/删除已补齐;隐身、出视野和 stale 包时序仍待完整生命周期测试
IsInSafe 地图属性判定 使用实体快照 in_safe 数据来源不等价;当前端未证明由地图 ATTRIBUTE_BANPK 实时计算并在移动/换图时更新
PC 异阵营目标框开关和 Ctrl 悄悄话 TargetBoard + net_play helper 分支通过,真实 UI/输入时序仍需 live 验证

已执行测试

  • project/entity_rules_test.gdPASS,覆盖攻击资格判定矩阵。
  • project/target_board_test.gd:PASS,覆盖目标框分派、HP 可见性、异阵营过滤和 Ctrl 悄悄话。
  • project/test_alignment_pk_parity.gd35/35 PASS,覆盖善恶值和 PK 辅助逻辑。
  • project/test_click_target_effect_parity.gd:0 failures,覆盖点击、目标光圈和飞行物地形碰撞;存在 ObjectDB/RID 清理警告。
  • project/test_auto_combat_bot_parity.gd:22/22 PASS,覆盖自动选目标、移动、技能/普攻和拾取;主要为 fake client/helper 路径。
  • project/playable_combat_test.gd:0 failures,覆盖可玩战斗主链。

本轮实现修复(2026-09-20T21:05Z

  • CPythonPlayer::SetTarget 的顺序增加 _can_change_target、重复目标 1/2 秒时间窗和失败时的 CG_TARGET(0);自动命中改走 SetTarget(victim, FALSE) 语义。
  • CInstanceBase::CanPickInstance 增加统一 EntityRules.can_pick_instance:视锥字段、死亡/木门死亡、EUNHYEONG、复活隐身和普通隐身均在脚本目标入口检查。
  • NetWorld 在实体创建/更新时把同一结果写入 can_pickPlayerController 的点击和悬停射线均消费该门,避免 UI/脚本两条路径出现不同目标。
  • netplay_test.gd 新增技能动作锁、四类不可选目标、非强制命中换目标窗口测试;netplay_testentity_rules_testplayable_adapter_test、点击目标/NetWorld/自动战斗/可玩战斗回归均通过。

本合同仍为 PARTIAL:完整动作资源事件、隐身/裁剪/迟到更新生命周期、地图 ATTRIBUTE_BANPK 权威和真实服务端目标包顺序尚未闭合。

已确认差异和未验证项

  1. 真实完整 .msaget_events()、动作结束和缺失动作资源下的 CanChangeTarget live 时序仍需验证。
  2. 网络实体更新没有完全对应 40250 NotifyCharacterUpdate 的统一目标失效回调;隐身、出视野/淡出和迟到更新仍可能在不同入口留下目标框或光圈状态。
  3. 当前安全区来自实体字段 in_safe,尚未证明与地图 ATTRIBUTE_BANPK 在移动、跨图和服务端状态更新时保持同一数据权威。
  4. 当前端的 _is_attackable 在 helper 层提前检查 dead,而 40250 的 IsAttackableInstance 本身不检查死亡;虽然主要调用点另有死亡门,但该职责边界尚未统一。
  5. OnTargeted/OnUntargeted、飞行目标和 SendTargetPacket 的完整副作用顺序,以及真实服务端目标包拒绝/乱序,仍无 live fixture。

下一轮验证入口

  • 增加目标生命周期测试:存活 → 隐身 → 普通死亡 → 删除/裁剪 → 迟到更新,逐步检查目标 VID、目标框、光圈、名字强显和 CG_TARGET(0)
  • 增加完整 .msa 事件源的 CanChangeTarget/动作锁/重复点击时间窗测试,并对照 _change_target_to_picked_instance 的实际调用链。
  • 增加地图属性 ATTRIBUTE_BANPKin_safe 的移动、换图、目标切换矩阵。
  • 只有以上差异修复并通过 native/classic 与场景回归后,才能将本合同从 PARTIAL 提升。

2026-09-22 死代码删除

以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:alignment_pk_system.gd, test_alignment_pk_parity.gd, test_auto_combat_bot_parity.gd