docs(plan): 记录 CACHE_MODE_IGNORE 为验证 class_name 脚本的首选手段 + 加回填步骤
Task 1 实测发现:改完源码后经全局类名直调会拿到**过时结果**(返回修复前的 数值),是假通过陷阱——编辑器持有的已编译类是旧的。正确手段是 ResourceLoader.load(path, "GDScript", CACHE_MODE_IGNORE),它保留 class_name 原样且走引擎自己的编译器,故顺带证明静态类型标注真能被引擎编译。 另记 var x := <Variant 方法调用> 在 4.7.1 是无行号的编译错误而非警告。 新增 Task5 Step8:回填计划为实际执行版并勾复选框——归航那期踩过 「计划里留着过时代码和失效断言」的坑。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,16 @@
|
|||||||
> **规范**:提交中文;纯数据驱动,文件缺失 `push_error` 不静默回退。spec:`docs_dev/specs/2026-07-31-player-attributes-design.md`。
|
> **规范**:提交中文;纯数据驱动,文件缺失 `push_error` 不静默回退。spec:`docs_dev/specs/2026-07-31-player-attributes-design.md`。
|
||||||
> **已知工具坑**:
|
> **已知工具坑**:
|
||||||
> ① `validate_script` 对任何带 `class_name` 的脚本必然假阴性(报 `hides a global script class`)—— `attribute_formula.gd` 属此类;`player_stats.gd` / `player_manager.gd` / `spell_evaluator.gd` / `combat_manager.gd` / `attribute_tab.gd` / `designer_panel.gd` 无 `class_name`,可正常 `validate_script`。
|
> ① `validate_script` 对任何带 `class_name` 的脚本必然假阴性(报 `hides a global script class`)—— `attribute_formula.gd` 属此类;`player_stats.gd` / `player_manager.gd` / `spell_evaluator.gd` / `combat_manager.gd` / `attribute_tab.gd` / `designer_panel.gd` 无 `class_name`,可正常 `validate_script`。
|
||||||
> ② **`GDScript.new()` + `source_code` + `reload()` 这个绕法,在该类被引擎注册为全局类之后同样会失败**(同一条 `hides a global script class`)。Task 1 首次运行时因尚未注册而侥幸通过,**后续任务不能照抄**。正确做法二选一:
|
> ② **验证 `class_name` 脚本的首选手段(Task 1 实测得出,优于下面两种绕法)**:
|
||||||
> - **剥离 `class_name` 行再 `reload()`**:`S.source_code = src.replace("class_name AttributeFormula\n", "")`(`@tool` 编辑器类同理剥自己的 `class_name`);
|
> ```gdscript
|
||||||
> - 或**直接调用已注册的全局类**:`AttributeFormula.compute(...)`(只验证已落盘版本时更省事)。
|
> var S = ResourceLoader.load(path, "GDScript", ResourceLoader.CACHE_MODE_IGNORE)
|
||||||
> ③ `@tool` 编辑器类在 dock 里被缓存,`load(...).new()` 拿到旧类,绕法同 ②。
|
> ```
|
||||||
> ④ `execute_editor_script` 静默阻止运行时 `FileAccess.WRITE`。
|
> 强制绕过引擎缓存、从磁盘重新编译,**保留 `class_name` 原样**且走引擎自己的编译器 —— 因此它顺带证明了静态类型标注(如 `Dictionary[String, Combine]`、`for m: Dictionary`)真能被引擎编译,而不只是能被动态副本解析。无需 `restart_editor`,也不必改源码。
|
||||||
> ⑤ 新增 `.gd` 后 `.gd.uid` 不会自动出现,需 `EditorInterface.get_resource_filesystem().scan()` 触发扫描才生成;项目约定二者一并提交。
|
> ③ **⚠️ 直接调用已注册的全局类名会拿到过时结果,是假通过陷阱。** Task 1 修完文件后经 `AttributeFormula.compute(...)` 直调,返回的仍是**修复前**的数值 —— 编辑器持有的已编译类是旧的。**改完源码后不要用全局类名验证**,用 ② 的 `CACHE_MODE_IGNORE`。
|
||||||
|
> ④ `GDScript.new()` + `source_code` + `reload()` 这个绕法,在该类被注册为全局类之后会失败(同一条 `hides a global script class`);若要用必须先 `src.replace("class_name AttributeFormula\n", "")` 剥掉那行。`@tool` 编辑器类在 dock 里被缓存,同理。**优先用 ②。**
|
||||||
|
> ⑤ **`var x := <Variant 方法调用>` 在 4.7.1 是编译错误而非警告**,且报错无行号(`Script compilation failed`),极难定位。断言夹具里若持有动态 `GDScript` 引用 `F`,必须写 `var r: float = F.compute(...)` 而非 `var r := F.compute(...)`。
|
||||||
|
> ⑥ `execute_editor_script` 静默阻止运行时 `FileAccess.WRITE`。
|
||||||
|
> ⑦ 新增 `.gd` 后 `.gd.uid` 不会自动出现,需 `EditorInterface.get_resource_filesystem().scan()` 触发扫描才生成;项目约定二者一并提交。
|
||||||
> **关键既有事实**:`spell_evaluator.gd:332` `var max_ops: int = MAX_OPS_PER_CPU * 5`(`MAX_OPS_PER_CPU = 40`,`:8`)。`player_manager.gd:7` `const MOVE_SPEED: float = 200.0`,`:45` 消费,`:88` `_cast_interval = core.cast_interval if core else 0.5`。`combat_manager.gd:257` `_rebuild_wand()`,`:267-271` 为 `mana_leech` 重算段。`player_stats.gd`:`hp_max` :10、`cpu_limit` :21、`armor` :22、`resistance` :23、`_ready()` :33、`reset_for_run()` :125、`get_save_data()` :135、`load_save_data()` :138。`CompiledDeck` **没有** `cpu_limit` 字段(只有 `feature_tags`),故 `spell_evaluator` 侧不需要新增字段。
|
> **关键既有事实**:`spell_evaluator.gd:332` `var max_ops: int = MAX_OPS_PER_CPU * 5`(`MAX_OPS_PER_CPU = 40`,`:8`)。`player_manager.gd:7` `const MOVE_SPEED: float = 200.0`,`:45` 消费,`:88` `_cast_interval = core.cast_interval if core else 0.5`。`combat_manager.gd:257` `_rebuild_wand()`,`:267-271` 为 `mana_leech` 重算段。`player_stats.gd`:`hp_max` :10、`cpu_limit` :21、`armor` :22、`resistance` :23、`_ready()` :33、`reset_for_run()` :125、`get_save_data()` :135、`load_save_data()` :138。`CompiledDeck` **没有** `cpu_limit` 字段(只有 `feature_tags`),故 `spell_evaluator` 侧不需要新增字段。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -703,7 +707,17 @@ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 8: 合并决策**
|
- [ ] **Step 8: 回填本计划为实际执行版**
|
||||||
|
|
||||||
|
实现过程中若因评审而改动了代码或断言,**本计划里对应的代码块与脚本必须回填为实际落地的版本**,并勾上各任务的复选框。否则任何人按这份计划复跑,得到的是过时代码和可能已失效的断言 —— 归航那期就踩过这个坑。
|
||||||
|
|
||||||
|
已知必须回填的(Task 1 评审后代码有变):
|
||||||
|
- Task 1 Step 2 的实现代码块 → 加上 `pct` 越界守卫、`_COMBINE_NAMES`→`_COMBINE_BY_NAME`、`ri`→`floored`、`Dictionary[String, Combine]` 与 `for m: Dictionary` 的静态类型、以及 `hard` 语义差异的注释。
|
||||||
|
- Task 1 Step 3 的夹具 → 改用工具坑 ② 的 `ResourceLoader.load(..., CACHE_MODE_IGNORE)`;`cases` 补上 ⑦/⑦b 两条越界断言(共 12 条);注意工具坑 ⑤(`var r: float =` 而非 `var r :=`)。
|
||||||
|
|
||||||
|
其余任务同理:**以实际跑通的为准**,并在每处回填旁注明「原写法为何不可用」。
|
||||||
|
|
||||||
|
- [ ] **Step 9: 合并决策**
|
||||||
|
|
||||||
用 `superpowers:finishing-a-development-branch` 决定分支去向。前四个已完成子项(i-frames / 抗性 / 弹跳 / 归航)均合并进 `master` 并删分支。
|
用 `superpowers:finishing-a-development-branch` 决定分支去向。前四个已完成子项(i-frames / 抗性 / 弹跳 / 归航)均合并进 `master` 并删分支。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user