From f86d6cd20ee0d393541c2450cefdfb9b42644344 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 31 Jul 2026 11:04:44 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=E8=AE=B0=E5=BD=95=20CACHE=5FMODE?= =?UTF-8?q?=5FIGNORE=20=E4=B8=BA=E9=AA=8C=E8=AF=81=20class=5Fname=20?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E7=9A=84=E9=A6=96=E9=80=89=E6=89=8B=E6=AE=B5?= =?UTF-8?q?=20+=20=E5=8A=A0=E5=9B=9E=E5=A1=AB=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 1 实测发现:改完源码后经全局类名直调会拿到**过时结果**(返回修复前的 数值),是假通过陷阱——编辑器持有的已编译类是旧的。正确手段是 ResourceLoader.load(path, "GDScript", CACHE_MODE_IGNORE),它保留 class_name 原样且走引擎自己的编译器,故顺带证明静态类型标注真能被引擎编译。 另记 var x := 在 4.7.1 是无行号的编译错误而非警告。 新增 Task5 Step8:回填计划为实际执行版并勾复选框——归航那期踩过 「计划里留着过时代码和失效断言」的坑。 Co-Authored-By: Claude Opus 5 --- .../plans/2026-07-31-player-attributes.md | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs_dev/plans/2026-07-31-player-attributes.md b/docs_dev/plans/2026-07-31-player-attributes.md index 518f48e..5dea5e6 100644 --- a/docs_dev/plans/2026-07-31-player-attributes.md +++ b/docs_dev/plans/2026-07-31-player-attributes.md @@ -11,12 +11,16 @@ > **规范**:提交中文;纯数据驱动,文件缺失 `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`。 -> ② **`GDScript.new()` + `source_code` + `reload()` 这个绕法,在该类被引擎注册为全局类之后同样会失败**(同一条 `hides a global script class`)。Task 1 首次运行时因尚未注册而侥幸通过,**后续任务不能照抄**。正确做法二选一: -> - **剥离 `class_name` 行再 `reload()`**:`S.source_code = src.replace("class_name AttributeFormula\n", "")`(`@tool` 编辑器类同理剥自己的 `class_name`); -> - 或**直接调用已注册的全局类**:`AttributeFormula.compute(...)`(只验证已落盘版本时更省事)。 -> ③ `@tool` 编辑器类在 dock 里被缓存,`load(...).new()` 拿到旧类,绕法同 ②。 -> ④ `execute_editor_script` 静默阻止运行时 `FileAccess.WRITE`。 -> ⑤ 新增 `.gd` 后 `.gd.uid` 不会自动出现,需 `EditorInterface.get_resource_filesystem().scan()` 触发扫描才生成;项目约定二者一并提交。 +> ② **验证 `class_name` 脚本的首选手段(Task 1 实测得出,优于下面两种绕法)**: +> ```gdscript +> var S = ResourceLoader.load(path, "GDScript", ResourceLoader.CACHE_MODE_IGNORE) +> ``` +> 强制绕过引擎缓存、从磁盘重新编译,**保留 `class_name` 原样**且走引擎自己的编译器 —— 因此它顺带证明了静态类型标注(如 `Dictionary[String, Combine]`、`for m: Dictionary`)真能被引擎编译,而不只是能被动态副本解析。无需 `restart_editor`,也不必改源码。 +> ③ **⚠️ 直接调用已注册的全局类名会拿到过时结果,是假通过陷阱。** 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 := ` 在 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` 侧不需要新增字段。 --- @@ -703,7 +707,17 @@ Co-Authored-By: Claude Opus 5 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` 并删分支。