Files
mtgodot-poc/audit/contracts/skill.player-ui-cooldown.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

155 lines
13 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.player_ui_cooldown —— 技能栏、快捷键、等级、冷却和禁止条件
状态:`PARTIAL`
本合同覆盖 40250 Windows 客户端的技能数据解析、技能页加点、快捷栏持久化、技能施放前检查、目标/射程预约、冷却和服务端解锁事件。判断标准是调用顺序、禁止条件、数据来源、冷却公式和网络副作用一致,而不是只看当前端是否存在同名 `use_skill` 函数。
## 40250 参考调用链
### 技能数据和等级
- `UserInterface/PythonSkill.cpp`
- `RegisterSkillDesc`/`RegisterSkillTable` 读取技能属性、武器限制、动作、等级、目标范围、目标数量、SP/冷却公式和 `.msk` 数据。
- `SSkillData::IsCanUseSkill` 只允许非被动主动施放;`GetSkillCoolTime``GetTargetCount``GetNeedSP` 都以 `fcurEfficientPercentage` 代入公式。
- `UserInterface/PythonPlayer.cpp`
- `SetSkillLevel_` 将服务端等级/grade 转为当前 slot 的 grade、level 和 `LocaleService_GetSkillPower` 效率百分比。
- 快捷栏是服务端 `QUICKSLOT_MAX_NUM=36`,本地显示 4×8;分页、slot/ref 映射、add/del/move 请求均以全局槽位为权威。
- affect 的 toggle 技能会调用 `ActivateSkillSlot`/`DeactivateSkillSlot`,而不是由 UI 本地猜测。
### 施放前检查和副作用顺序
- `UserInterface/PythonPlayerSkill.cpp::ClickSkillSlot`
- 槽位/技能数据 → 公会技能路由 → 被动静默返回 → standing/toggle-off → `__UseSkill`
- `__CanUseSkill`
- 主角存在、非观战、坐骑术等级满足、`CInstanceBase::CanUseSkill` 通过。
- `CInstanceBase::CanUseSkill`
- 拒绝变身 `IsPoly`、穿礼服 `IsWearingDress`、持镐 `IsHoldingPickAxe`、坐骑不可用技能和当前图形动作不可施法。
- `__CheckSkillUsable`
- 骑乘/骑乘技、攻击技安全区、被动、空瓶/毒瓶、钓鱼、等级/未学习、武器类型、箭矢、冲刺 affect、冷却、HP/SP 按固定顺序检查。
- `__UseSkill`
- 私人商店静默返回 → `__CanUseSkill` → 特殊技能 → toggle-off → `__CheckSkillUsable` → 施法中检查 → 目标/尸体/结盟/安全区/可攻击/射程预约 → 改朝向 → 扇形/圆形追加目标 → 清预约 → `NEW_UseSkill` 动作 → `SendUseSkillPacket``__RunCoolTime`
- `__RunCoolTime`
- 以 skill `.msk` 冷却公式计算基础时间,再按 `POINT_CASTING_SPEED` 修正,最后触发 UI `RunUseSkillEvent`
### 网络回包
- `PythonNetworkStreamPhaseGame.cpp`
- `RecvSkillLevel`/`RecvSkillLevelNew` 刷新技能和效率百分比。
- `RecvSkillCoolTimeEnd` 调用 `EndSkillCoolTime`,按技能 ID 清除本地冷却。
- `RecvChangeSkillGroupPacket` 清空旧 skill data 并刷新角色窗口。
- `GC_QUICKSLOT_ADD/DEL/MOVE` 更新本地 36 槽数据。
## 当前端实现映射
- `project/ui/skill_table.gd`
- 已解析 `skilldesc.txt`、属性/武器 bit、`.msk` 的冷却/SP/目标数量/范围公式,并实现技能分类和等级加点谓词。
- `project/player_skill.gd`
- 已按 `ClickSkillSlot → can_use_skill → check_skill_usable → use_skill` 分层校验,包含
`CInstanceBase::CanUseSkill` 的变身、礼服、矿镐、坐骑动作和图形动作五类门,以及安全区、骑乘、被动、瓶子、钓鱼、等级、武器、箭矢、冷却、HP/SP、尸体、结盟、目标和射程预约。
- 公会技能按 `GUILD_ROUTED` 转入独立 `use_guild_skill`,补齐公会战状态门和独立冷却入口。
- `project/ui/quickbar.gd`
- 维护 36 个服务端槽位、4×8 可见页、server quickslot add/del/swap、技能图标/等级/冷却遮罩、移动端快捷栏和 `GC_SKILL_COOLTIME_END` 解锁。
- 通过 `M2Client.use_skill``use_guild_skill``cast_skill``NetPlay` 发起技能;冷却按 `.msk` 基础值和
`POINT_CASTING_SPEED` 的 40250 分段公式本地预测。
- `project/ui/skill_ui.gd`
- 有主动/辅助/坐骑页、技能等级/grade、加点按钮、技能拖入快捷栏和服务端 skill/group 刷新。
- `extension/src/net/entity_store.cpp``classic_parser.cpp``m2_client.cpp`
- 已存储/转发 skill level、master、group、quickslot 和 cooldown-end 包,并暴露相应信号。
## 分支等价矩阵
| 参考判据/副作用 | 当前实现 | 结论 |
|---|---|---|
| 技能属性、武器限制、`.msk` 公式和等级效率 | `SkillTable` 读取 skilldesc/`.msk``_skill_point` 有 40250 等级表 | 基本等价;全部语言、所有 `.msk` 和公式异常仍未建立快照 |
| `ClickSkillSlot` 分层顺序、被动/公会/特殊/开关技 | `PlayerSkill.click_skill_slot/use_skill/use_guild_skill` + `Quickbar._activate_guild_skill` | 基本等价;公会技已独立走 `CG_GUILD_USE_SKILL`toggle active 状态主要由调用方传入,未证明与 affect slot 状态始终一致 |
| `CInstanceBase::CanUseSkill` 变身、礼服、持镐、坐骑和图形动作门 | `can_use_skill` 按相同顺序拒绝五类动作状态,`NetPlay.skill_context` 从实体快照注入 poly/礼服/矿镐/坐骑/图形门 | 基本等价;礼服和图形层字段依赖服务器快照是否提供 |
| 安全区、瓶子、钓鱼、等级、武器、箭矢、HP/SP、尸体和结盟 | `check_skill_usable`/`_resolve_target` | 基本等价;in_safe、持有物、HP/SP 和目标字段均依赖当前端快照,实时/乱序未证实 |
| 冷却公式 `GetSkillCoolTime(fcurEfficientPercentage)` | `SkillTable.cooldown_of` | 公式主体基本等价 |
| `__RunCoolTime``POINT_CASTING_SPEED` 修正 | `M2Client.get_points()` 暴露点 21`Quickbar._skill_cooldown` 按 40250 分段公式换算 | 基本等价;真实服务端点变更与冷却回包乱序仍待 fixture |
| 冷却开始时机:动作成功后 `SendUseSkillPacket``__RunCoolTime` | 当前先成功发送 `cast_skill``FUNC_SKILL`),再发送 `use_skill``CG_USE_SKILL`),最后写本地 `cd_end` | 基本等价;没有以服务端/动作成功回执确认本地预测是否应回滚 |
| `GC_SKILL_COOLTIME_END` 按 skill ID 解锁 | native parser/store → `skill_cooldown_end` → Quickbar 清理 | 基本等价;重复、过期、断线和槽位重排时序未验证 |
| 36 槽 server quickslot 与 4×8 可见页 | Quickbar 36 状态 + 32 可见槽,native 36 槽 | 基本等价;最后 4 个不可见槽的恢复、键盘输入和 UI 可达性未验证 |
| 目标/射程预约、自动索敌、扇形/圆形追加目标 | `NetPlay`/`PlayerSkill`/`Quickbar` | 部分等价;`SetFlyTargetInstance`、动作朝向、资源目标数量和发送顺序尚无完整包序证据 |
| skill/group/quickslot 回包刷新 UI | native signals → Quickbar/SkillUI | 基本等价;已有单元测试,但没有真实乱序、拒绝和重连场景 |
## 已执行测试
- `project/player_skill_test.gd`PASS;覆盖三层技能合法性校验和 `CanUseSkill` 五类动作门。
- `project/skill_test.gd`:PASS;覆盖技能表、技能页和快捷栏基础链。
- `project/netplay_test.gd`PASS;覆盖 `GUILD_WAR_NONE/ON_WAR/WAIT_START` 到公会技状态门的映射。
- `project/test_skill_inventory_parity.gd`:45 项 PASS;覆盖技能门控、库存/装备、角色状态和 quickslot 同步。
- `project/test_taskbar_quickslot_parity.gd`34/34 PASS;覆盖任务栏、4×8 页面和鼠标技能模式;有 Godot ObjectDB/RID/resource cleanup 警告。
- `project/test_status_skill_reset_parity.gd`:32/32 PASS;覆盖技能重置相关状态变化。
- `project/mobile_gesture_test.gd`:PASS;覆盖移动端技能拖放、瞄准和快捷栏交换。
- `project/playable_combat_test.gd`:0 failures;覆盖离线可玩技能请求、冷却、目标、资源和断线。
- `project/guild_war_skill_test.gd`:PASS;覆盖公会技能页、公会战状态和 `use_guild_skill` UI 入口。
- `build/extension/net_entity_test``build/extension/net_classic_session_test`PASS;覆盖 native skill/group/quickslot/cooldown-end 协议存储和会话入口。
- `project/test_full_skill_flow.gd`PASS;共享 `net_client_fake.gd` fixture 可启动 GameScene,并断言 `FUNC_SKILL → CG_USE_SKILL` 动作/意图包序。
## 已确认差异和未验证项
1. `Quickbar` 在本地 `use_skill` 返回成功后立即预测冷却,缺少服务端拒绝/动作未起播/断线后的冷却回滚证据;40250 也做客户端预测,但其状态由 `CPythonPlayer` 的 slot 冷却对象统一管理。
2. 当前 skill/group/quickslot parser 虽已存在,尚无 `GC_SKILL_LEVEL_NEW → group → quickslot → cooldown-end` 乱序、重复、跨角色和重连生命周期测试。
3. 需要建立全部技能 ID/grade/语言/`.msk` 公式和动作资源索引,验证动作事件、目标追加、`SendUseSkillPacket`、冷却和服务端 damage 的顺序。
## 下一轮验证入口
- 验证 `POINT_CASTING_SPEED` 在真实 points 包、重复点变更和冷却结束回包中的时序,并补 native/GDScript 端到端证据。
-`CanUseSkill` 五类禁止状态接到实体真实 shape/动作字段,并验证拒绝时不发 `CG_USE_SKILL``CG_MOVE(FUNC_SKILL)` 或 fly-target 包。
- 用真实 `GUILD_WAR_ON_WAR` 回包验证公会技 `ONLY_FOR_GUILD_WAR` 门、`CG_GUILD_USE_SKILL` 和冷却结束包序。
- 增加 skill level/group/quickslot/cooldown-end 的乱序、重复、断线、重连和换图测试,再决定是否可提升合同状态。
## Active UserInterface skill-data bridge review
- `PythonSkill.h``TSkillData`/技能描述字段包括 target range、attribute、need weapon、状态上下限、affect description、cooldown、SP、continuation SP、motion loop count、target count、duration、motion index、grade icon 和职业/等级限制;`RegisterSkill/Table/Desc` 是资源权威入口。
- 当前端有 `SkillTable`/quickbar/cooldown 基础,已补 casting speed、动作门和
`FUNC_SKILL → CG_USE_SKILL` 包序;loop count、grade/motion、技能描述公式和服务端拒绝后的冷却回滚尚未由同一数据链驱动,合同继续 `PARTIAL`
## Implementation round 2026-09-21T14:45Z
本轮按 40250 `CInstanceBase::CanUseSkill()``CPythonPlayer::__RunCoolTime()` 修复两处实现差异:
- `PlayerSkill.can_use_skill()` 新增并按参考顺序执行 `POLYMORPHED``WEARING_DRESS`
`HOLDING_PICKAXE``HORSE_CANNOT_USE``GRAPHIC_CANNOT_USE` 五类动作门;
`NetPlay.skill_context()` 从实体 `poly/shape/wedding_dress/parts[weapon]` 及服务器动作字段注入这些状态。
- native `POINT_CASTING_SPEED=21` 加入协议点位定义,`M2Client.get_points()` 暴露 `casting_speed`
`Quickbar._skill_cooldown()``iSpd = 100 - casting_speed` 的两段公式计算本地冷却遮罩和阻止时长,
默认中性值为 100,与 40250 `char.cpp` 的初始化一致。
- `player_skill_test` 新增五类 gate 断言,`skill_test` 新增 `casting_speed=20 → 180%` 数值断言;
`skill_test``player_skill_test``test_skill_inventory_parity``playable_combat_test`
`netplay_test` 和 native 目标构建通过。
仍保持 `PARTIAL`:服务端拒绝/动作未起播后的冷却回滚、skill/group/quickslot/cooldown-end 乱序与跨生命周期、
完整动作资源和 damage 包序尚未闭合。
## Implementation round 2026-09-20T20:45Z
本轮按 40250 `CPythonPlayer::__UseSkill` 的真实副作用顺序修复技能发送链:
- `Quickbar._activate_slot()``activate_skill_direct()` 现在先调用 `cast_skill()` 上行
`FUNC_SKILL|motion + arg`,动作成功后再调用 `use_skill()` 上行 `CG_USE_SKILL(skill, target)`
本地 `skill_cast_started` 也在动作状态成功后触发,扇形/圆形追加目标仍在主技能意图之后发送。
- 补充共享 `project/net_client_fake.gd`,替换 `test_full_skill_flow.gd` 的缺失夹具;该测试现在实际装配
`GameScene`、执行一次技能,并断言动作包先于技能意图包。
- `playable_combat_test``skill_test``mobile_gesture_test``player_skill_test`
`test_full_skill_flow` 均通过。
仍保持 `PARTIAL`:服务端拒绝/动作未起播后的冷却回滚、skill/group/quickslot/cooldown-end
乱序与跨生命周期、完整动作资源和服务端 damage 包序仍需真实网络证据。
## Implementation round 2026-09-20T20:52Z
本轮按 40250 `ClickSkillSlot → UseGuildSkill` 分支修复公会技能:
- `PlayerSkill` 保留 `GUILD_ROUTED` 的入口分流,并新增 `use_guild_skill()`,执行主角动作门、技能数据、冷却和 `ONLY_FOR_GUILD_WAR` 检查。
- `NetPlay.skill_context()` 将 native `GUILD_WAR_ON_WAR=6` 映射为 `guild_war_active`;其它公会战通知状态不会误放行公会战限定技能。
- `Quickbar` 新增 `_activate_guild_skill()`:先上行 `FUNC_SKILL`loop=1),再调用
`M2Client.use_guild_skill(skill, 0)`,并复用技能冷却/动作信号;普通技能仍走 `CG_USE_SKILL`
- `player_skill_test``skill_test``guild_war_skill_test` 均通过,覆盖非公会战拒绝、公会战独立包和 UI 公会技能页。
仍保持 `PARTIAL`:真实 `GUILD_WAR_ON_WAR` 回包、服务端拒绝/冷却回滚、跨生命周期乱序和完整动作/damage 证据尚未闭合。
### 2026-09-22 死代码删除
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`test_status_skill_reset_parity.gd`