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

9.4 KiB
Raw Blame History

CLAUDE.md

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

项目简介

Spellforge(万法熔炉)—— Godot 4.7.1-stable (Mono/.NET) 弹幕射击 RogueliteSoA / ECS-Lite 架构,法术管道系统。工程概览见 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),但属**「就绪但休眠」的逃生舱**: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# 长处是原生吞吐、真值类型 structSpan<T> 零拷贝、纯 C# 循环内无每次调用的封送开销。除此之外不要引入 C#。
    • 引入门槛(缺一不可):① 有 profile 数据佐证瓶颈;② 环境已就绪(Rogue.csproj + csharp/ 已可编译,见 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);在此之上加类即可,但仍须先在文档立项(对齐 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.gdif 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_dev/ —— 开发过程中产生的文档。开发计划、进度追踪、审计报告、平台认证清单、历史归档草案等过程性文档。索引见 docs_dev/README.md

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

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

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

  • 字段化,禁止手写 JSON:凡是可结构化的数据都用输入控件(SpinBox / OptionButton / CheckBox / 行编辑器),不要TextEdit 让用户手写 JSON —— 易配置错、可读性差。
    • 可变长的小整数元组列表(如 Core edges、balance aether_thresholds)用共享组件 addons/game_designer/tuple_list_editor.gdsetup/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_FILLdesigner_ui.gdline/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() 调用。