Files
spellforge/CLAUDE.md
T
joywayerandClaude Opus 4.8 aa69c0e2d4 chore(build): 搭建并验证 C# / C++(GDExtension) 原生构建环境(4.7.1 Mono)
为「GDScript→C#→C++」性能逃生梯备好可随时启用的工具链,运行时仍 100% GDScript。

C#:新增根 Rogue.csproj(Godot.NET.Sdk/4.7.1,net8.0,程序集名 Rogue);
    csharp/ 下既有 3 个骨架 .cs 现可编译,dotnet build 与编辑器 --build-solutions 均 0 错通过。

C++:gdextension/ 下加 godot-cpp 子模块(master,因无 4.6/4.7 分支,配合本工程 dump 的
    4.7.1 extension_api.json + gdextension_interface.h 经 custom_api_file 精确匹配 ABI);
    SConstruct + 示例扩展 SpellforgeNative(含 PackedFloat32Array 零拷贝热路径写法);
    SCons 自动探测 MSVC 构建出 debug .dll,headless 冒烟验证类注册/调用通过
    (CLASS_EXISTS=true / multiply=42 / scale_inplace 正确)。

配套:.gitignore 忽略编译中间产物、保留 debug .dll 开箱即用;gdextension/README.md 记录构建/激活/升级流程;
    CLAUDE.md 修正引擎版本 4.6→4.7.1 并更新「环境已就绪但休眠」现状。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:53:39 +08:00

57 lines
9.4 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.
# CLAUDE.md
本文件为 Claude Code 在本工程中工作时的约定与规范。
## 项目简介
Spellforge(万法熔炉)—— Godot 4.7.1-stable (Mono/.NET) 弹幕射击 RogueliteSoA / 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()` 调用。