From d8f02b568b0d0b34e0492b8c7be2ccaed8aef8c9 Mon Sep 17 00:00:00 2001 From: panFD Date: Sat, 20 Jun 2026 22:59:20 +0800 Subject: [PATCH] docs(config-editor): add design spec for hero/skill visual config editor extension Schema-driven Cocos Creator 3.8.6 extension that round-trips the existing Record .ts configs via the TypeScript compiler API (preserves symbolic AtkSpeedSet expressions and hand-written comments). Non-invasive: zero changes to game runtime code. --- ...6-06-20-hero-skill-config-editor-design.md | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-20-hero-skill-config-editor-design.md diff --git a/docs/superpowers/specs/2026-06-20-hero-skill-config-editor-design.md b/docs/superpowers/specs/2026-06-20-hero-skill-config-editor-design.md new file mode 100644 index 00000000..8bc327ed --- /dev/null +++ b/docs/superpowers/specs/2026-06-20-hero-skill-config-editor-design.md @@ -0,0 +1,351 @@ +# 通用英雄/技能配置编辑器(Cocos Creator 扩展)— 设计规格 + +- 日期:2026-06-20 +- 作者:brainstorm 协作产出 +- 目标引擎:Cocos Creator **3.8.6** +- 部署位置:`extensions/pixelhero-config-editor/` +- 关联配置源:`assets/script/game/common/config/{heroSet,SkillSet,HeroAttrs,HeroSkillDesc}.ts` + +--- + +## 1. 概述(Overview) + +构建一个 Cocos Creator 编辑器扩展,提供**可视化**的英雄与技能配置编辑能力。扩展以 **schema 驱动 + TypeScript AST 往返**为核心:直接读写现有 `Record` 形态的 `.ts` 配置文件,**不改动任何游戏运行时代码**。通过声明式 schema 描述每张表的字段、枚举、引用关系,UI 由 schema 自动生成,从而实现"通用"——未来新增驻场技能表、卡牌表等只需追加 schema,无需编写新 UI。 + +## 2. 目标与非目标 + +### 目标 +- 可视化编辑 `HeroInfo`(英雄 50xx + 怪物 60xx/61xx)、`SkillSet`(技能 60xx/63xx/64xx/65xx)、`FieldSkillSet`(驻场 70xx/72xx/74xx)三张表。 +- 原地回写对应 `.ts` 文件,**保留符号表达式**(如 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`)与手工注释/分节标题。 +- 异构触发槽、技能引用覆盖(`SkillOverrides`)、进化配置的可视化编辑。 +- 实时校验 + 实时描述预览(与游戏内 `buildSkillDesc` 一致)。 +- 新建/复制/删除/保存/还原,并保持 `HeroList` 与 `HeroInfo` 一致。 +- 纯逻辑层(schema/IO/校验)有自动化单元测试,作为 BLOCKING 证据。 + +### 非目标(v1) +- 不重构游戏代码、不把数据迁移到 JSON。 +- 不做运行时热重载游戏逻辑(仅通过 asset-db 刷新让编辑器与编辑器内预览生效)。 +- 撤销/重做(Undo/Redo)列为 v1.1。 +- 不编辑 `CardSet / HighlightSet / GameSet / ScoreSet`(架构允许后续以新增 schema 方式扩展,但不在本次范围)。 +- 不做多人协作/版本对比。 + +## 3. 背景事实(已核实) + +| 事实 | 影响 | +|---|---| +| 引擎 Cocos Creator 3.8.6 | 使用 `package_version:2` 扩展格式;面板经 `Editor.Panel.define({...})`;消息经 `contributions.messages` | +| 配置运行时只读、仅按 key 访问、无动态加载 | 回写安全;输出只需是合法 TS 且 `HeroList` 一致 | +| `skills[n].cd` 为符号表达式 | IO 必须基于 TS Compiler API,识别并保号往返 | +| 现有 `oops-plugin-framework` 仅运行时框架 | 无面板示例可抄;需自建打包 | +| `HeroList` 被运行时迭代(`CardSet.ts`) | 写英雄表后必须同步 `HeroList = 排序后的英雄(HERO) uuid` | +| `HeroSkillDesc.buildSkillDesc` 生成游戏内描述 | 移植为 JS 用于面板实时预览 | + +## 4. 架构(五层) + +``` +┌────────────────────────────────────────────────────────────┐ +│ UI 层 Vue3 面板 (dist/panels/default.js) │ +│ master-detail + 嵌套编辑器 + 校验面板 + 描述预览 │ +└───────────────────────────────┬────────────────────────────┘ + Editor.Message.request │ broadcast 'record-changed' +┌───────────────────────────────┴────────────────────────────┐ +│ 主进程 dist/main.js 内存真理源 + 消息处理 + 广播 │ +└──────┬──────────────────────┬──────────────────────┬───────┘ + │ │ │ +┌──────▼───────┐ ┌───────────▼──────────┐ ┌─────────▼────────┐ +│ 校验层 │ │ Schema 注册表 │ │ IO 层 (TS 往返) │ +│ validate() │ │ tables/fields/enums │ │ TsConfigFile │ +│ → Issue[] │ │ → 驱动 UI 生成 │ │ load/patch/save │ +└──────────────┘ └──────────────────────┘ └──────────┬────────┘ + │ 写回 .ts + Editor.Message.request('asset-db','refresh-asset') +``` + +层次依赖单向:UI → 主进程 → {校验, schema, IO}。校验与 schema 为纯逻辑(可独立测试)。IO 依赖 `typescript` 包。 + +## 5. 组件详设 + +### 5.1 Schema 注册表(`src/shared/schema/`) + +"通用"的核心。每个数据表用一份 `TableSchema` 描述: + +```ts +interface TableSchema { + id: 'hero' | 'skill' | 'field'; // 表标识 + label: string; // "英雄/怪物" + sourceFile: string; // 相对 assets 配置目录,如 'heroSet.ts' + exportName: string; // 'HeroInfo' | 'SkillSet' | 'FieldSkillSet' + keyType: 'number'; + idSegments: { label: string; min: number; max: number; note?: string }[]; + listExportName?: string; // 仅 hero:'HeroList'(需同步) + fields: FieldSchema[]; // 记录字段(顺序即 UI 顺序) +} + +interface FieldSchema { + key: string; // 字段名(对应 .ts 对象键) + label: string; // 中文标签 + type: FieldType; // 见下 + required?: boolean; + default?: unknown; + group?: string; // UI 分组("基础"/"触发技能"/...) + help?: string; + // type 相关的可选元数据: + enumRef?: string; // type=enum → enums.ts 中的枚举键 + refTable?: TableId; // type=ref → 引用哪张表 + overlayKeys?: string[]; // type=overrides → 可覆盖键集合 + showIf?: { field: string; in: unknown[] };// 条件显示 +} + +type FieldType = + | 'number' | 'string' | 'boolean' + | 'enum' // 下拉,选项来自 enumRef + | 'ref' // 引用另一表的 uuid(下拉,显示 目标.name) + | 'speedExpr' // 攻速符号表达式,下拉=AtkSpeedLv 档位 + | 'skillMap' // Record(英雄专用) + | 'triggerSlots' // 6 种数组触发槽(英雄专用) + | 'fieldList' // number[] 驻场技能列表(英雄专用) + | 'reviveSlot' // 单对象 {s_uuid,r_num,upr}(英雄专用) + | 'overrides'; // SkillOverrides 覆盖层(出现在 triggerSlots 内部) +``` + +**枚举源 `src/shared/schema/enums.ts`**:镜像游戏枚举为 `{label,value}[]` 字典——`HType, FacSet, TGroup, DTType, SkillKind, DType, IType, RType, EType, FieldSkillType, Attrs(buff_type), AtkSpeedLv`。此文件为编辑器侧单一事实源;并提供 `assertEnumsMatchGame()` 调试期检查(读取游戏 `.ts` 枚举定义比对,不一致则告警),避免漂移。 + +**三张表的字段清单(v1)**: + +- **hero (`HeroInfo`)** — 分组: + - 基础:`uuid`(number,必填), `name`(string,必填), `path`(string,必填), `icon`?(string), `fac`(enum FacSet,必填), `pool_lv`?(number), `lv`(number,必填,默认1), `type`(enum HType,必填), `hp`(number,必填), `ap`(number,必填), `dis`?(number), `speed`?(number), `info`(string,必填) + - 技能:`skills`(skillMap,必填), 触发槽组 `call/dead/fstart/fend/atking/atked`(triggerSlots), `field`(fieldList), `revive`(reviveSlot), `evolve`(evolveMap — v1 只读展示,标注"v1.1 编辑") + - ID 段:英雄 [5000,5999];怪物 [6000,6999](6101-6106 为 Boss) +- **skill (`SkillSet` → `SkillConfig`)**: + - 基础:`uuid`(number,必填), `name`(string,必填), `sp_name`(string,必填), `icon`(string,必填), `act`(string,必填), `info`(string,必填) + - 目标/类型:`TGroup`(enum,必填), `DTType`(enum,必填), `IType`(enum,必填), `RType`(enum,必填), `EType`(enum,必填), `kind`?(enum SkillKind), `DType`?(enum,默认 ATK) + - 数值:`ap`(number,必填), `gold`?(number), `hit_count`(number,必填), `hitcd`(number,必填), `speed`(number,必填), `ready`(number,必填), `with`(number,必填,默认0) + - 动画/特效:`readyAnm`,`endAnm`,`DAnm`(string), `EAnm`(number) + - 高级:`crt?`,`stun?`,`frz?`,`bck?`(number), `buff_type`?(enum Attrs), `call_hero`?(ref hero), `time`?, `bezier_start_y?`,`bezier_mid_y?`,`bezier_arc?`(number) + - ID 段:6001-6999 +- **field (`FieldSkillSet` → `FieldSkillConfig`)**: + - `uuid`(number,必填), `name`(string,必填), `icon`(string,必填), `type`(enum FieldSkillType,必填), `value`(number,必填), `info`(string,必填) + - ID 段:7001-7999 + +> 字段清单来源 = 直接对照 `heroSet.ts`/`SkillSet.ts` 的 interface 定义,已逐字段核对类型与必填性。 + +### 5.2 IO 层(`src/main/io/TsConfigFile.ts`) + +基于 `typescript` 包(npm,在扩展主进程 Node 上下文中 `require`)。 + +```ts +class TsConfigFile { + load(file, exportName): void; // 解析并缓存 SourceFile + 目标 VariableDeclaration + getKeys(): number[]; // 该 const 的所有键 + read(key): RecordValue; // AST → 结构化值 + patch(key, value: RecordValue): void; // AST 区间替换该条目 + add(key, value): void; // 在 const 末尾插入新条目 + delete(key): void; // 删除条目区间 + serialize(value): string; // 单条目确定性序列化 + save(): { ok: boolean; error?: string }; // 写 .bak → 校验可解析 → 落盘 → 触发 asset-db refresh + reload(): void; +} +``` + +**值模型 `RecordValue`**: +- 标量:`{kind:'num',value}` / `{kind:'str',value}` / `{kind:'bool',value}` +- 符号表达式:`{kind:'speed', level:'Slow3'}` ⇄ 源 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`(AST 形态:`PropertyAccessExpression(ElementAccessExpression(Ident,ElementAccessExpression(Ident,Ident)),Ident)`,固定匹配该模式;不匹配则降级为 `{kind:'num',value:<节点文本>}` 并标记需人工确认) +- 对象:`{kind:'obj', props: {key: RecordValue}}` +- 数组:`{kind:'arr', items: RecordValue[]}` + +**回写策略 = 条目级 AST 区间替换**: +1. 解析文件 → 定位目标 `exportName` 的 `ObjectLiteralExpression`。 +2. 在其中按 key 定位单个 `PropertyAssignment` 的完整文本区间(含其尾随逗号)。 +3. 对该条目调用 `serialize()` 生成新文本。**确定性格式(固定,无歧义)**:受控多行对象字面量,每字段一行、4 空格缩进、字段顺序按 schema `fields` 顺序、尾随逗号;`cd` 用符号形式 `AtkSpeedSet[AtkSpeedLv.X].cd`;`info` 非空时在条目上方输出一行 `// {info}` 注释。示例: + ```ts + // 每受击3次为自身添加4层护盾 + 5011:{uuid:5011,name:"小铁卫",path:"hk1",fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:400,ap:20, + skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}}, + atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}], + info:"每受击3次为自身添加4层护盾"}, + ``` +4. 用新文本替换该区间;其余条目、分节注释、其他 const 原样不动 → **diff 最小**。 +5. 新增:在 `}` 前插入;删除:移除区间。 +6. 落盘前:`createProgram` 对改动文件做语法检查;失败则回滚 `.bak` 并报错。 +7. 落盘后:`await Editor.Message.request('asset-db','refresh-asset', url)`,使编辑器重新导入该脚本。 + +**降级与安全**: +- 任一步骤异常 → 不写文件,返回结构化错误,面板展示。 +- 始终先写 `*.bak`;校验通过后再覆盖原文件。 +- 若条目内出现未识别的初始化表达式(非上述 RecordValue 形态),该条目标记为"只读(含未支持表达式)",可编辑其他条目但不改动它,避免破坏。 + +### 5.3 校验层(`src/shared/validation/`) + +纯函数 `validate(tableId, allRecords): Issue[]`,每次改动后对受影响表运行。规则: + +| 规则 | 级别 | +|---|---| +| uuid 在表内唯一 | error | +| uuid 落在该实体声明的 idSegments 内 | error | +| 必填字段非空 | error | +| enum 字段值 ∈ 枚举集合 | error | +| ref 字段目标在引用表中存在(英雄触发槽/技能图引用 → SkillSet;field 列表 → FieldSkillSet;call_hero → HeroInfo) | error | +| overrides 仅含 `SkillOverrides` 允许键(TGroup,ap,gold,hit_count,hitcd,crt,frz,stun,bck,buff_type,call_hero) | error | +| 英雄表:`HeroList` 与 `HeroInfo` 一致(每 HeroList 项存在且 fac=HERO;每 fac=HERO 条目都在 HeroList) | error | + +**`HeroList` 同步策略(最小 diff)**:保留现有数组顺序与分节注释,仅在新增英雄时把新 uuid 追加到数组末尾、删除英雄时移除其 uuid。**不重排、不重生成**,以避免破坏手工分节注释。同步后再次运行一致性校验。 +| 怪物无 `pool_lv`/`evolve`(语义警告) | warn | +| `info` 文案长度 >0 | warn | + +`Issue = {tableId, key, fieldPath, severity:'error'|'warn', code, message}`。面板据此在列表行显红点、字段内联报错;存在 error 时禁用"保存"。 + +### 5.4 主进程(`src/main/index.ts`) + +扩展入口。在内存持有三张表的 `TsConfigFile` 实例与 schema 注册表,作为唯一真理源。处理消息(见 §7)。任何写入先校验:error 则拒绝并返回 Issue 列表;成功后广播 `record-changed {tableId, key}`,所有打开面板据此刷新。 + +### 5.5 UI 层(`src/panels/default/`,Vue 3) + +- **打包**:`esbuild` 将 `src/panels/default/index.ts`(含 Vue 3 runtime+compiler)打成单文件 `dist/panels/default.js`。Vue 用**字符串模板**(`compiler` 在线编译),避免 SFC 工具链。 +- **面板入口**(`Editor.Panel.define`):`template`=`
`,`ready()` 中 `createApp({…}).mount(this.$.app)`,`close()` 卸载。 +- **布局**:左 master(表切换 + 搜索 + 列表),右 detail(schema 驱动表单 + 嵌套编辑器 + 预览),底部校验条。 +- **控件映射**:number→``;string→``;boolean→``;enum→``;ref→``(选项=目标表 `{uuid:name}`,空选项=清除);speedExpr→``(选项=AtkSpeedLv 档位,含"自定义数值"兜底)。 +- **嵌套编辑器**: + - `TriggerSlotsEditor`:对 6 种触发类型,每组可增删行 `{s_uuid(技能 ref 选择器), t_num(number), overrides(可折叠)}`;行内显示该技能基础信息(name/kind)。 + - `SkillMapEditor`:英雄 `skills`;每项 `uuid + lv + cd(speedExpr)`;至少 1 项(index 0=普攻)。 + - `FieldListEditor`:`field:number[]` 多选驻场技能。 + - `ReviveEditor`:`revive` 单对象 `{s_uuid,r_num,upr}` 或空。 + - `OverrideEditor`:依据所选基础技能 `kind`,仅渲染相关覆盖键(如 Damage→ap/hit_count/crt/stun…;Support→TGroup/ap/buff_type…)。 +- **实时预览**:`PreviewPane` 调主进程 `query-preview-desc`(移植 `buildSkillDesc`),随编辑即时刷新。 +- **图标/特效预览**:`icon`、`sp_name` 变更时 `query-asset` 取贴图,旁置缩略图。 +- **操作栏**:新建(自动取 idSegment 内下一个可用 uuid)、复制(uuid+2 或手动指定)、删除、还原(reload)、保存并刷新(触发 §5.2 save)。未保存改动用 `*` 标记;切换记录前若有未保存改动,弹确认。 +- **原生观感**:尽量用 Cocos `` 元素,Vue 仅做状态与组合。 + +## 6. 内存与持久化 + +- 真理源 = 磁盘 `.ts` 文件。主进程首次 `query-*` 时懒加载并缓存 `TsConfigFile`。 +- 编辑改动先作用于内存 AST;"保存"才落盘。还原=丢弃内存改动重载。 +- 多面板实例:主进程单例,广播保证一致。 + +## 7. 消息协议(`contributions.messages`) + +| 消息 | 方向 | 载荷 | 返回 | +|---|---|---|---| +| `query-schema` | panel→main | `tableId?` | schema(全部或单表) | +| `query-enums` | panel→main | — | 枚举字典 | +| `query-keys` | panel→main | `tableId` | `number[]` | +| `query-record` | panel→main | `tableId,key` | `RecordValue` | +| `query-preview-desc` | panel→main | `hero RecordValue` | `string`(描述) | +| `query-asset` | panel→main | `name, type` | 贴图 url 或 null | +| `validate` | panel→main | `tableId` | `Issue[]` | +| `save-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` | +| `add-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` | +| `delete-record` | panel→main | `tableId,key` | `{ok, issues?}` | +| `revert-record` | panel→main | `tableId,key` | `RecordValue`(重载后值) | +| `record-changed` | main→broadcast | `tableId,key` | — | + +`save/add/delete` 成功后主进程自动广播 `record-changed`。 + +## 8. 数据流(保存一条英雄) + +``` +面板改字段 → save-record(hero,5011,value) + → 主进程 validate(hero):error? 返回 {ok:false,issues} + → TsConfigFile.patch(5011, value) // AST 区间替换 + → TsConfigFile.save():写 .bak → createProgram 语法校验 → 覆盖原 .ts → asset-db refresh + → 广播 record-changed(hero,5011) + → 面板重查 → UI 刷新(含 HeroList 若变动) +``` + +## 9. 模块/文件布局 + +``` +extensions/pixelhero-config-editor/ +├── package.json # package_version:2, panels, contributions.messages/menu, deps: typescript, vue, esbuild +├── tsconfig.json +├── esbuild.config.mjs # 打包 main + 面板 +├── i18n/{en,zh}.js +├── static/ +│ ├── template/default/index.html +│ └── style/default/index.css +├── src/ +│ ├── main/ +│ │ ├── index.ts # 扩展入口 + onMessage 注册 +│ │ └── store.ts # 三张表 TsConfigFile 单例 + 广播 +│ ├── io/ +│ │ ├── TsConfigFile.ts # 解析/序列化/patch/save +│ │ ├── recordValue.ts # RecordValue 类型与归一化(含 speedExpr) +│ │ └── serializer.ts # serialize(entry) 确定性文本生成 +│ ├── shared/ # 纯逻辑(无 Cocos/Editor 依赖,可独立测试) +│ │ ├── schema/ +│ │ │ ├── types.ts # TableSchema/FieldSchema 定义 +│ │ │ ├── registry.ts # 三张表 schema 注册 +│ │ │ ├── hero.ts +│ │ │ ├── skill.ts +│ │ │ ├── field.ts +│ │ │ └── enums.ts # 枚举镜像 + assertEnumsMatchGame +│ │ ├── validation/ +│ │ │ └── index.ts # validate() 规则 +│ │ └── desc/ +│ │ └── buildSkillDesc.ts # HeroSkillDesc 的 JS 移植(预览用) +│ └── panels/default/ +│ ├── index.ts # Editor.Panel.define + Vue mount +│ └── app/ # Vue 组件(App, MasterList, DetailForm, TriggerSlots, SkillMap, FieldList, Revive, Override, PreviewPane, ValidationBar) +├── dist/ # 打包产物(main.js, panels/default.js) +└── __tests__/ # node:test 纯逻辑测试 + ├── tsConfigFile.roundtrip.test.ts + ├── serializer.test.ts + ├── validation.test.ts + ├── speedExpr.test.ts + ├── schema.test.ts + └── fixtures/ # 真实配置副本 +``` + +## 10. 构建与开发 + +- `npm run build`:`esbuild` 同时打包 `main`(platform=node, format=cjs)与 `panels/default`(platform=browser, format=iife, bundle vue),输出到 `dist/`。 +- 开发:改完 `build` → 在 Cocos"扩展管理器"重载扩展。 +- 依赖:`typescript`(IO 用)、`vue`(面板用)、`esbuild`(打包)、`fs-extra`(可选,读模板)。`typescript` 体积大但必要;打 main 时 bundle 进 dist。 + +## 11. 测试策略 + +遵循项目 `coding-standards.md`: + +- **逻辑层(BLOCKING,自动化,`node:test`)**: + - `tsConfigFile.roundtrip`:用真实 `heroSet.ts`/`SkillSet.ts` 副本作 fixture → load → 读全部 → 不改动 → save → 与原文件逐字节相等(验证保号保注释)。 + - 改动往返:patch 一条英雄 → save → 重新 load → 读回值 == 改入值;且产物经 `createProgram` 语法合法。 + - `serializer`:给定结构化值 → 序列化文本 → 再解析 → 等价。 + - `speedExpr`:`AtkSpeedSet[AtkSpeedLv.Slow3].cd` ⇄ `{kind:'speed',level:'Slow3'}` 双向。 + - `validation`:构造各类非法数据 → 断言 Issue 正确。 + - `schema`:所有表 schema 字段 key 与对应 interface 一致;枚举镜像与游戏 `.ts` 枚举一致(`assertEnumsMatchGame`)。 + - 命名/隔离/无外部依赖,遵循测试规范;fixture 为常量文件副本,不内联魔法数。 +- **UI(ADVISORY)**:`production/qa/evidence/` 下手动走查文档(覆盖三表增删改查、校验阻断、预览一致性)+ 编辑器内截图。 + +## 12. 分阶段实施(供 writing-plans 细化) + +| 阶段 | 产出 | 验证 | +|---|---|---| +| P0 脚手架 | package.json/panels/menu/build;面板可从菜单打开显示 "hello" | 截图 | +| P1 IO 层 | TsConfigFile + RecordValue + serializer + roundtrip 测试 | 测试通过 | +| P2 schema+枚举+校验 | 三表 schema、enums、validate + 测试 | 测试通过 | +| P3 主进程 | store + 全部消息处理(先只读类) | 手动 query 验证 | +| P4 面板基础 | master 列表 + 标量/枚举/ref 字段编辑 + 保存往返 | 端到端改一个英雄保存 | +| P5 嵌套与预览 | 触发槽/技能图/驻场/复活/覆盖 + 描述预览 + 缩略图 | 编辑复杂英雄保存往返一致 | +| P6 收尾 | 新建/复制/删除/HeroList 同步/还原/打磨/QA 文档 | QA 走查通过 | + +## 13. 风险与缓解 + +| 风险 | 缓解 | +|---|---| +| TS AST 往返破坏文件 | 条目级区间替换 + `.bak` + `createProgram` 校验 + 未识别表达式标记只读 | +| 符号表达式形态多样 | 固定匹配 `AtkSpeedSet[AtkSpeedLv.X].cd` 模式;其余降级为只读数值并提示 | +| 枚举镜像与游戏漂移 | `assertEnumsMatchGame` 调试期比对告警 | +| `typescript` 打包体积 | 仅 bundle 进 main(node),面板不包含;可接受 | +| esbuild/Vue 在扩展环境运行 | P0 先打通最小面板(Vue mount 成功)再扩展 | +| 多面板状态不一致 | 主进程单例 + 广播 record-changed | +| `HeroList` 失同步 | 英雄表写入后由 store 强制同步并校验 | + +## 14. 验收标准 + +1. 扩展可从"面板"菜单打开,三张表可切换浏览、搜索。 +2. 对三张表任一记录:可视化查看/编辑所有字段(含触发槽、技能图、覆盖、驻场、复活),保存后磁盘 `.ts` 被更新且为合法 TS,asset-db 已刷新。 +3. **保号往返**:仅查看后保存,文件零字节变化(测试断言)。 +4. 实时校验:违反任一 error 规则时"保存"被阻断并列出问题;行/字段级可见。 +5. 描述预览与游戏内 `buildSkillDesc` 输出一致。 +6. 新建/复制/删除可用;`HeroList` 与 `HeroInfo` 始终一致。 +7. 逻辑层单元测试全部通过(BLOCKING)。 +8. 未改动 `assets/script/game/**` 任何游戏运行时代码(git diff 可证)。