# CLAUDE.md

本文件为 Claude Code 在本工程中工作时的约定与规范。

## 项目简介

Spellforge（万法熔炉）—— Godot 4.7.1-stable (Mono/.NET) 弹幕射击 Roguelite，SoA / ECS-Lite 架构，法术管道系统。工程概览见 [`README.md`](README.md)。

## 编程语言规范（GDScript / C# / C++）

本工程为 Mono 工程，为追求性能允许 **GDScript / C# / C++** 三语共存，但**各有明确定位、按需逐级下探，绝非平均混用**。核心原则：**用对语言，而非用多语言**；发挥各自长处，以「先测量后优化」驱动性能，不做无凭据的过早优化。性能优化路线是一条**逐级升级的逃生梯**：GDScript（默认）→ C#（热路径逃生舱）→ C++（极限内核），**仅当上一级被 profile 证明不够时才下探下一级**。

**现状（务必知悉）**：当前**运行时 100% 为 GDScript** —— 全部 42 个 autoload、法术 VM、子弹/敌人/空间网格等所有热循环均是 GDScript，实测 500 敌 + 1500 弹 `_physics_process` ≈ 0.6ms（约帧预算 3.6%），性能充裕。C#/C++ **构建环境已搭好并冒烟验证通过**（`Rogue.csproj` + `gdextension/` godot-cpp 子模块 + 示例扩展，详见 [`gdextension/README.md`](gdextension/README.md)），但属**「就绪但休眠」的逃生舱**：`csharp/` 下的 3 个 `.cs` 虽可编译，仍是**未激活的死骨架**（从未实例化、`_cs_node` 恒为 `null` 走 GD 回退），C++ 侧仅有冒烟示例类 —— **不要假设它们在运行**。热路径迁移是文档记录但**尚未触发**的方案（风险 `R-08` / `ADR-L1`），环境就绪 ≠ 已迁移。

- **GDScript 为默认与主力语言**：游戏逻辑、系统管线、UI、`@tool` 编辑器插件、数据驱动胶水（JSON 加载/注册表/事件）、场景树与信号交互 —— 一律用 GDScript。它的长处是迭代快、与引擎/场景树/信号无缝、动态灵活。**新功能默认写 GDScript**，除非有实测数据证明是瓶颈。
  - GDScript 内的性能纪律：热循环用 SoA（`PackedFloat32Array` / `PackedInt32Array` + 整数实体 ID），避免每帧 `new`/字典/lambda；分支用 `match`/`for` 而非闭包；变量与函数签名标注静态类型（`: float` / `-> void`）以启用引擎优化；缓存跨节点引用，勿在内循环反复 `get_node`。
- **C# 仅为「热路径逃生舱」**：**唯一**动机是 GDScript 已被 profile 证明扛不住的密集数值内循环（大规模弹幕积分、Boid 分离、空间哈希）。C# 长处是原生吞吐、真值类型 `struct`、`Span<T>` 零拷贝、纯 C# 循环内无每次调用的封送开销。**除此之外不要引入 C#。**
  - 引入门槛（缺一不可）：① 有 profile 数据佐证瓶颈；② 环境已就绪（`Rogue.csproj` + `csharp/` 已可编译，见 [`gdextension/README.md`](gdextension/README.md)），启用即在此之上落地 C# 内核；③ 遵循 `docs/technical/architecture_design.md` §6.1 / `ADR-L1` 热路径三铁律 —— **(a)** 内循环**禁止** `GodotObject.Call()` / `.Set()`（跨语言封送即性能杀手）；**(b)** 经 `PackedFloat32Array.AsSpan()` 零拷贝直读 SoA 数组；**(c)** 事件经 `EventBus.emit_batch` 批量通知，勿逐个跨界回调。
  - 跨语言边界成本真实（`R-08`）：**热数据与热循环须落在边界同一侧**，仅在边界处批量进出。切忌把一个循环拆成 GD↔C# 反复横跳。
- **C++（GDExtension）为「极限内核」最深一级**：**唯一**动机是连 C# 都被 profile 证明不够、且属计算密集到值得动用原生手段的核心内核（如 SIMD 向量化的弹幕/碰撞积分、需手动内存布局与缓存对齐的空间结构）。C++ 长处是无托管开销、可控内存布局、SIMD/内联汇编、直贴 `PackedArray` 底层指针。代价也最高：需 `godot-cpp` 子模块 + SCons 工具链、按平台产出 `.dll`/`.so`/`.dylib`、调试与移植成本远高于前两级 —— **绝不为「感觉会更快」而引入，必须有前一级的实测瓶颈数据背书。** GDExtension 环境已搭好（`gdextension/` 内 godot-cpp 子模块 + `SConstruct` + 冒烟示例 `SpellforgeNative`，构建/激活见 [`gdextension/README.md`](gdextension/README.md)）；在此之上加类即可，但仍须先在文档立项（对齐 `ADR-L1` 同源精神）。
  - C++ 侧同样遵循边界纪律：接口经 `GDExtension` 绑定暴露给 GDScript 壳，热循环全程在 C++ 内完成，仅在边界批量传 `PackedArray`（零拷贝取原生指针），不在内循环回调 GDScript/C#。
- **接口壳 + 原生内核模式**（如某系统确需 C# 或 C++ 内核）：GDScript 侧为接口壳（对外 API、注册、事件），原生内核（C# 子节点自注册 `_cs_node`，或 C++ GDExtension 类）承担热循环；GDScript **必须保留可用的纯 GD 回退路径**（现有 `bullet_manager.gd` / `spatial_grid.gd` / `enemy_manager.gd` 的 `if not _cs_node:` 即此模式），确保内核未编译/未加载时游戏仍可运行。
- **改动后验证**：GDScript 用 godot-mcp-pro `validate_script`；若真激活 C#/C++，须确保对应工程能编译（`.csproj` / GDExtension 构建）、过 `ADR-L1` review，并补基准对比（迁移前后 `_physics_process` 耗时）证明确有净收益，否则不合入 —— **性能优化以数据论成败，不以语言层级论高低**。

## Git 提交规范

- **及时提交**：完成一个逻辑完整的改动（一个功能点、一处修复、一批相关文档更新）后应立即提交，不要将多个无关改动堆积在一个大提交里。
- 提交信息使用中文，简明说明「做了什么、为什么」。
- 仅在用户要求时才 push；如当前在主分支（`main` / `master`）上进行功能开发，先切分支。
- 提交前确认改动范围符合预期（`git status` / `git diff`），不要顺手提交无关文件。

## 文档存放规范

工程文档分两处存放，新增或修改文档时按用途归位：

- **`docs/`** —— **项目指导 / 架构设计等权威文档**。面向产品的设计规范、技术架构、机制细节、开发手册。这是跨切片实现约束的权威来源（含关键规范速查表 `P6-N*` / `ADR-*`）。索引见 [`docs/README.md`](docs/README.md)。
- **`docs_dev/`** —— **开发过程中产生的文档**。开发计划、进度追踪、审计报告、平台认证清单、历史归档草案等过程性文档。索引见 [`docs_dev/README.md`](docs_dev/README.md)。

> 判断依据：若文档是「项目应当如何设计/实现」的权威规范 → `docs/`；若是「开发过程中的计划、记录、追踪、归档」→ `docs_dev/`。

## 编辑器插件 / 游戏设计器开发规范

面向 `addons/game_designer/` 等 `@tool` 编辑器插件（可视化配置工具）。用户手册见 [`docs/handbook/07_game_designer.md`](docs/handbook/07_game_designer.md)。

- **字段化，禁止手写 JSON**：凡是可结构化的数据都用输入控件（`SpinBox` / `OptionButton` / `CheckBox` / 行编辑器），**不要**用 `TextEdit` 让用户手写 JSON —— 易配置错、可读性差。
  - 可变长的小整数元组列表（如 Core `edges`、balance `aether_thresholds`）用共享组件 [`addons/game_designer/tuple_list_editor.gd`](addons/game_designer/tuple_list_editor.gd)（`setup`/`set_values`/`get_values`，行 + ➕加/🗑删）。
  - **唯一例外**：专门收纳「schema 之外的未知键」的逃生舱（如法术 meta 的「其它(JSON)」），因键不可预知才保留文本框，且正常应为空。
- **复用 `designer_ui.gd` 辅助**：控件一律经 `UI.spin/line/opt/btn/cell_label/header/status_label/set_status/load_json/save_json` 创建；元素标签用共享常量 `UI.ELEM_LABELS`/`UI.ELEM_TAGS`。不要自建裸控件绕过。
- **输入框须完整显示内容**：输入/下拉控件设 `size_flags_horizontal = Control.SIZE_EXPAND_FILL`（`designer_ui.gd` 的 `line/opt/spin` 工厂已内置），撑满面板宽度，长文本/长选项不截断。
- **标签中英双语，中文在前**：所有枚举/选项显示用「中文 English」序（如 `动作 ACTION`、`火 Fire`、`矩阵 MATRIX`、`每 N 次 every_n_shots`）。
- **显示与存储解耦**：`OptionButton` 存整数 index 或 key，**绝不**把双语显示串写进 JSON。显示标签数组与存储值数组分离（如 `LOGIC_OPS` 存 key、`LOGIC_OP_LABELS` 仅供下拉显示，二者 index 对齐）。
- **数据往返一致 / 防丢键**：`_on_select` 回读 → `_on_apply` 应用应语义等价；未知键用逃生舱保留不丢；可选键为默认值时可省略以保持 JSON 简洁。
- **改动后验证**：用 godot-mcp-pro `validate_script` 校验语法；对真实 `data/*.json` 跑回读往返（`execute_editor_script` 断言 `FAILS=0`）。⚠️ dock 里驻留的旧 GDScript 类需 `EditorInterface.restart_editor(true)` 清缓存后才测得到新代码（`reload_project`/`reload_plugin` **不**重编译已加载类）。
- **纯数据驱动**：内容只存在于 `res://data/*.json`，代码不含硬编码副本或回退；文件缺失/格式错误应 `push_error` 明确报错，不静默用旧值。
- **新增 `.gd` 记得提交同名 `.gd.uid`**（与既有组件一致）。
- **新增分页 3 步**：`@tool extends VBoxContainer` 标签脚本（复用 `designer_ui.gd`）→ 在 `designer_panel.gd` 的 `_init()` 里 `_add_tab(...)` 注册 → 对应 Manager 加 `_load_json_xxx()` 于 `_ready()` 调用。
