Merge remote-tracking branch 'refs/remotes/origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m17s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m25s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m34s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m37s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m3s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m23s
Project CI / Frontend tests (pull_request) Failing after 14m1s
Project CI / Repository checks (pull_request) Successful in 12m35s
Project CI / Native shell tests (pull_request) Successful in 17m2s
Project CI / Backend tests (pull_request) Successful in 17m18s
Project CI / AI game creator shell web tests (pull_request) Failing after 14m48s

This commit is contained in:
2026-09-19 15:39:41 +08:00
228 changed files with 15831 additions and 3162 deletions
@@ -31,21 +31,23 @@
## 4. 请求载荷映射
| 入口 | IPC | 载荷要点 |
| --------------------- | --------------------------------- | ------------------------------------------------------------------------------------------- |
| 生成图片 | `start_local_project_asset_generation` | `kind: 'image'`,比例 / 尺寸可调,默认 `1:1 · 1K` |
| 生成角色形象 | 同上 | `kind: 'character'`,默认 `1:1 · 1K` |
| 生成规范 → 图标规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;缺权威规范图时 `outputPath: 'assets/art-spec.png'` |
| 生成规范 → 角色规范 | 同上 | `kind: 'spec'`,固定 `1:1 · 1K` |
| 生成规范 → 自定义规范 | 同上 | `kind: 'spec'`,固定 `1:1 · 1K` |
| 生成图标素材 | 同上 | `kind: 'art-spritesheet'`,默认 `1:1 · 1K`;前置:已登记 `assets/art-spec.png` |
| 生成 UI 设计图 | 同上 | `kind: 'ui-prototype'`,默认 `16:9 · 1K`;前置同上 |
| 生成背景音乐 | `derive_local_project_resource` | `editKind: 'background-music'``generationMode: 'create'``sourceMediaType: 'audio/mpeg'` |
| 生成音效 | `derive_local_project_resource` | `editKind: 'sound-effect'`、其余同上 |
| 上传 | `upload_local_asset` | `{ projectPath, fileName, mediaType, bytes }` |
| 入口 | IPC | 载荷要点 |
| --------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
| 生成图片 | `start_local_project_asset_generation` | `kind: 'image'`,比例 / 尺寸可调,默认 `1:1 · 1K` |
| 生成角色形象 | 同上 | `kind: 'character'`,默认 `1:1 · 1K` |
| 生成规范 → 图标规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;缺权威规范图时 `outputPath: 'assets/art-spec.png'` |
| 生成规范 → 角色规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K`;与图标规范 / 自定义规范共用同一条规范通道 |
| 生成规范 → 自定义规范 | 同上 | `kind: 'icon-spec'`,固定 `1:1 · 1K` |
| 生成图标素材 | 同上 | `kind: 'icon-spritesheet'`,默认 `1:1 · 1K`;前置:已登记 `assets/art-spec.png` |
| 生成 UI 设计图 | 同上 | `kind: 'ui-design'`,默认 `16:9 · 1K`;前置同上 |
| 生成背景音乐 | `derive_local_project_resource` | `editKind: 'background-music'``generationMode: 'create'``sourceMediaType: 'audio/mpeg'` |
| 生成音效 | `derive_local_project_resource` | `editKind: 'sound-effect'`、其余同上 |
| 上传 | `upload_local_asset` | `{ projectPath, fileName, mediaType, bytes }` |
`start_local_project_asset_generation` 的完整参数是 `{ projectPath, projectId, taskId, kind, prompt, aspectRatio, imageSize, assetName, outputPath }`Rust `src-tauri/src/asset_generation_tasks.rs`),**提交即返回**一条任务记录(`taskId / status / phaseDetail / assetId / error` 等);生成在 `tauri::async_runtime::spawn` 出来的后台任务里跑,账本落在项目内 `.agent/runtime/asset-generation-tasks/tasks.json`,进度由 `list_local_project_asset_generations` 读回。截图面板不再「等生成结束」,所以**点「生成」即同步关闭面板**(不等 IPC),面板内不出现阶段文案;只有「点击瞬间就失败」才带草稿重开(关闭 ≠ 取消)。
入参 `kind` 一律是共享 `GameCreationAppAssetKind` 的 canonical 成员(`image` / `character` / `icon-spec` / `icon-spritesheet` / `ui-design` / `publication-material`);`spec` / `art-spritesheet` / `ui-prototype` 这类平台请求词汇只由 Rust 侧派生到 `source.generationKind`,不再出现在 IPC 载荷或 manifest kind 里。
## 4a. 本地排队与「生成任务」侧栏
- **本地排队**AGC 本地 durable 输出槽按**精确动作指纹**分槽(`run_id = slot-<sha256(动作材料)>`,材料为 prompt / outputPath / 比例 / 尺寸 / assetKind / assetLabel / replaceExisting / requireSlices;不同 prompt 或素材名各自独立成槽,不再按 `outputPath` 共用;旧 `{outputPath,requireSlices}` 槽账本由同一精确动作懒迁移)。因此后端已具备并行能力,但**本批前端仍按「同一时刻只派发一条」排队**——真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批。所以第二条提交停在**前端本地队列**里(不调用提交 IPC,阶段显示本地排队的「排队中。」),第一条终态后由同一条循环自动补发;判据是「存在 `dispatched && 未终态` 的任务时不派发下一条」。
@@ -66,7 +68,7 @@
- 判据与 Rust `canonical_art_spec_reference_at` 逐字对齐(`projectHasIconSpecReference`):manifest 里必须存在 `localPath === 'assets/art-spec.png'``kind === 'icon-spec'``mediaType``image/` 开头、`source.kind === 'canvas'` 的资产。
- 缺前置时入口**保持可点击**`aria-disabled="true"`,不是原生 `disabled`),点击后在工具栏上沿给 `role="alert"` 的原因说明:`需要先完成并登记图标规范(assets/art-spec.png);请先用「生成规范 → 图标规范」生成`;此时**不发任何生成请求**。
- 「图标规范」入口在缺前置时把 `outputPath` 指向 `assets/art-spec.png`,让前置条件能由工具栏自身满足;已有权威规范图时 `outputPath``null`。这条策略对应 Rust 侧硬约束:`prepare_local_project_asset_generation``replace_existing` 固定为 `false`,指向已存在文件会被拒绝(`图片生成 outputPath 已存在,禁止静默覆盖`)。
- 已知能力缺口(本轮不扩接口,记账备查):本地 IPC 没有 ① `model` 入参(面板因此不给模型选择)、② `specType` 入参(角色规范与自定义规范共用 `spec` 通道,靠素材名与提示词区分)、③ `replaceExisting` 入参(因此不能重写已存在的权威规范图)。
- 已知能力缺口(本轮不扩接口,记账备查):本地 IPC 没有 ① `model` 入参(面板因此不给模型选择)、② `specType` 入参(角色规范与自定义规范共用 `icon-spec` 通道,靠素材名与提示词区分)、③ `replaceExisting` 入参(因此不能重写已存在的权威规范图)。
## 7. 音频入口收敛
@@ -0,0 +1,93 @@
# AGC Unity 编辑器插件接入
> 文档状态:`current`
> 规范关系:承接 AGC 通用插件宿主与编辑器适配主规范
更新时间:`2026-09-18`
## 目标与非目标
将 DotCraft.Unity 0.4.3 对应的 Attach 执行核心接入 AGC 现有插件系统,使当前 Unity 项目能够探测编辑器、建立连接、执行 C# 并获得真实结果。复用既有扩展列表、内置插件开关、权限、审计、EditorAdapter 和 Agent 工具通路。
首期只支持 Windows x64 的 Unity Mono Editor。连接不修改项目文件、不安装 UPM 包、不启动或关闭用户编辑器。不引入 DotCraft.Harness、另一套 Agent Runtime、MCP 服务或聊天界面;截图、热重载专用工具、macOS、Linux 和 Unity CoreCLR 不属于本次交付。
## 来源与分发
源码固定为 `DotHarness/dotcraft-unity``a65f091c162ebcfde5ec9b399d188fcc57261947`(发布插件 0.4.3 的 provenance)。只吸收 Attach 与必要 Shared 源码,保留 Apache-2.0 许可、第三方声明、来源及本地修改记录;构建产物不提交。Roslyn 版本固定为 5.3.0。Windows helper 自包含分发,用户不需要预装 .NET;构建端需要 .NET 10 SDK 和 x64 C++ 工具链。
## 入口与行为合同
- Unity 项目以当前受控根目录中的普通 `ProjectSettings/ProjectVersion.txt``Assets/``Packages/` 识别;复用现有打开项目入口,不创建平行工作台。
- 只有当前项目为 Unity、内置插件启用且平台适配器可用时才显示插件并向 Agent 暴露 Unity 工具。禁用或离开 Unity 项目后停止插件实例,执行入口再次检查开关和项目身份。
- 探测只读取进程和项目身份。连接必须匹配规范化项目路径、PID、进程启动身份与实际握手;多个候选时失败,不选择任意实例。助手只接受受控项目、操作和代码,不接受任意可执行文件或 payload 路径。
- 执行接收 UTF-8 C# 代码,最多 128 KiB,拒绝空值和 NUL。连接及执行有总期限,消息最多 2 MiB,并发执行直接拒绝,不积压写请求。
- 成功仅由 Unity 真实执行回执决定;编译或运行错误返回结构化失败与脱敏诊断。主线程同步代码不承诺可硬中止。
- 执行发送后超时、连接丢失、回执损坏或上游 unknown/lost 一律返回 `needs-reconciliation``retryAllowed=false`,阻断后续执行。停止/重启 JS 插件、断开连接和切换项目不能清除该阻断;用户核对后重启宿主才恢复。发送前参数、项目、平台或缺少 helper 的失败不进入执行不确定状态。
- 探测不等于连接;连接不等于执行成功。Domain Reload 后重新验证身份与 generation,不能重放上一条写操作。
- 宿主禁止 RPC 覆盖 manifest 声明的 adapter,并校验显式项目路径等于当前受控项目;缺省路径由宿主填充。编辑器路由不允许在全局锁后无界等待。切换项目使旧连接失效,旧请求回执只能归属原请求,不更新新项目连接状态。连接尝试先撤销旧连接,失败保持 disconnected。
- GUI 项目切换采用线性化处理:已有插件执行先在受控期限内结束,再切换宿主项目和失效旧连接;切换在后台线程等待,不阻塞 UI 线程。前端项目设置与 cleanup 按顺序送达宿主,旧 cleanup 不能覆盖新项目。
- 插件不继承 Provider 凭据。helper 使用受控可执行路径、最小环境、受限消息和超时;任意 C# 在 Unity 权限下执行,宿主 RPC 权限不构成 OS 沙箱。
## 模块与契约
插件包放在 `plugins/agc-unity-editor`,使用现有 Agent Plugins manifest 和 `agc.plugin.v1`。JS 入口通过 `host.rpc` 使用编译期注册的 `unity-editor` 适配器。AGC Runner 是 Unity 执行服务的唯一 owner:GUI 适配器复用现有经过鉴权的 Runner RPC 转发,Runtime 和 DirectProject 在同一个长寿命 Runner 中调用原生服务;不创建另一条跨进程 IPC。执行服务调用独立 Attach helper,再由上游本地协议连接 Unity。GUI 项目切换通过同一路径使连接失效;JS 或连接池重建不得重置 owner 内的不确定门闩,恢复要求核对后重启该执行 owner。
插件命令 `unity.editor.execute`,连接能力 `unity.editor.connection`Agent Runtime 同名工具与 DirectProject `agc_unity_execute` 共享同一原生服务、项目检查与不确定结果阻断。面向模型的执行参数只有 `code`,项目路径由宿主注入。
Runner 在执行前保存请求身份,不保存代码或用户路径;GUI 完整验证确定性终态回执后才确认交付。回执丢失、损坏及插件最后一跳失败均保留持久阻断;正常并发或等待确定回执确认返回未派发失败,不误判执行不确定。Runner 自动重启和新增窗口都不清除阻断;用户核对后退出全部 AGC/Runner,再次启动客户端时,只有同时独占既有 GUI 参与锁和 Runner 实例锁才能恢复。启动、派发、读取和确认共享 80 秒客户端总期限,传给 helper 的预算扣除已用启动时间,过期请求不得派发。
helper 使用一行一条 JSON 请求/响应,请求包含 `id``method``params`,响应回显 `id``result` 或结构化 `error`。方法为 `detect``connect``status``execute``disconnect``projectPath` 始终来自受控宿主,`processId` 为可选探测约束,`timeoutMs` 不超过 60000。执行响应至少有 `ok``status``retryAllowed``result`/`error`,状态为 `completed``failed``needs-reconciliation`。detect/connect/status 结果包含 `adapter``connected``pid``projectPath``version`
协议名为 `agc.unity.attach.v1`,每条请求均带 `jsonrpc: "2.0"``protocol`,id 为正整数。helper 是由原生服务持有的单个常驻子进程;Rust 服务在插件与 Runtime 之间共享,执行不确定门闩独立于 helper 生命周期。请求的 `params` 每次包含当前 `projectPath`,无单独 initialize/wait 公开方法;execute 内部完成连接、执行与有界 wait,总 deadline 包含发现、连接与等待,内部不重发 execute。detect 不进行注入。非执行方法的协议错误为 `{code, message}`;execute 的已知未发送失败同样放在结构化 result,只有带完整 `status: failed, ok: false, retryAllowed: false, dispatched: false` 的回执可判为未执行,未知错误不得据此解锁。执行成功含 `dispatched: true`;不确定结果含 `dispatched: true`。连接成功另含 `startedUtc``generation`,以实际目标身份/握手为准。
明确收到可信 `failed, ok: false, dispatched: true` 回执表示 Unity 已确定执行失败,不进入不确定门闩;可以由 LLM 修复代码后再执行。`retryAllowed: false` 禁止自动原样重放,不阻止确定性编译或运行错误后的修复。只有完整且匹配请求的终态回执可以支持这一判断。
首次通过现有打开项目入口导入 Unity 时允许沿用 AGC 的 `.agent` 元数据初始化;不修改 `Assets``Packages``ProjectSettings` 或 Unity 工程文件。此动作与只读 detect、不改文件 connect 分开。
不修改服务端 API、SpacetimeDB schema 或现有持久项目数据。新增插件开关沿用既有内置开关存储;不兼容或模拟 DotCraft.Harness 插件 ABI。
## 验收标准
| 条款 | 必须取得的证据 |
| --- | --- |
| 来源与构建 | 固定源码版本、许可、helper 构建和自包含发布检查 |
| 插件接入 | manifest/协议测试,发现、启停、开关和项目级工具过滤测试 |
| 执行闭环 | helper 与原生适配器定向测试,Agent 参数及结果映射测试 |
| 失败边界 | 跨项目、并发、超时、损坏回执、不确定阻断、重启插件不解除阻断测试 |
| 分发 | Windows 构建脚本准备 helperstaging 仅包含目标平台运行文件及许可 |
| 运行时 | 若有可用 Unity,使用临时测试项目验证连接和无副作用 C# 回执;没有可用编辑器时明确标为未验证,不以 mock 代替实机 |
| 仓库门禁 | 定向测试、类型检查、文档索引、编码检查及 git diff --check |
真实 Unity、安装包及全版本兼容验收与单元测试分别报告。
## 当前验证范围与复验入口
- .NET helper 的 27 项定向测试与 Windows x64 自包含发布通过;发布程序在最小环境中完成协议 smoke。
- 插件 JS 的参数/消息/并发/项目切换测试通过;Unity/Cocos 打开入口、插件启停投影及开发构建参数的前端定向验证通过。
- 原生 helper 进程测试覆盖写入阻塞、EOF、错误 id、超长帧、期限、环境隔离及不确定结果阻断。
- 宿主 Unity 定向 8 项、PluginHost 13 项、原生工具目录 16 项和引擎识别 5 项通过;Cocos 10 项、MCP 25 项通过,两组中同一个默认忽略的 Cocos 实机用例未在本任务运行。双 feature 编译检查和调试构建通过。
- Unity `6000.3.7f1` 的独立临时工程已完成真实连接、C# 执行、编译错误回传及修复、确定运行错误、临时对象创建/销毁、断开重连验证。项目没有安装 DotCraft UPM 包。
- `tests/live_unity.rs` 已显式执行通过,覆盖共享原生服务、编译修复以及 Domain Reload 后重新握手和 generation 更新;它默认 ignored,避免普通测试连接开发者项目。
- 新构建的 AGC Runner 使用独立临时配置,完成真实 Unity 执行、ACK、错误 ACK 拒绝、未确认回执的并发拒绝,以及不确定状态跨 Runner 重启保留的验证。
- 安装包 UI smoke、其它 Unity 版本及 CoreCLR 不属于上述已验证范围;不能从单一版本实机通过推断全版本兼容。
- Gitea 的现有 AGC web 与 Rust crates 门禁分别执行 Cocos/Unity 插件 JS 和原生 crate 测试;Linux CI 不执行 Windows Attach helper 或真实 Unity。Windows Jenkins 构建要求 PATH 可解析 .NET 10 SDK,并由 helper 构建脚本执行 .NET 测试及自包含发布;实机与安装包验收仍单独报告。
复验命令:
```powershell
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File plugins/agc-unity-editor/dotnet/build.ps1
node --test plugins/agc-unity-editor/src/entry.test.mjs
cargo test --locked --manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml
```
实机复验先自行创建、打开隔离测试工程,核对其 Unity PID,并设置
`AGC_UNITY_SMOKE_HELPER``AGC_UNITY_SMOKE_PROJECT``AGC_UNITY_SMOKE_PID` 后执行:
```powershell
cargo test --locked --manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml --test live_unity -- --ignored --nocapture
```
该实机用例会触发 Domain Reload,不能指向有未保存工作的用户工程。
独立评审已核对项目归属、并发拒绝、总期限、ACK 原子归属、最后一跳回执丢失、
进程重启阻断及发布许可边界;定向证据与上述真实运行时验证共同覆盖本次接入合同。
@@ -4,7 +4,7 @@
> 规范关系:AGC 插件与编辑器适配主规范
> 验收范围:插件 manifest、宿主生命周期、RPC、Capability/权限审计、UI 挂载和编辑器适配器边界
更新时间:`2026-09-09`
更新时间:`2026-09-18`
## 目标与边界
@@ -19,6 +19,7 @@ apps/ai-game-creator-shell/src-tauri/src/editor_adapters.rs
packages/agc-plugin-sdk/src/index.ts
server-rs/crates/editor-adapter-api/src/lib.rs
plugins/agc-cocos-editor/ (第一个编辑器插件包)
plugins/agc-unity-editor/ Unity Mono 编辑器插件包)
```
现有 DirectProject 的 Skill/MCP 导入仍保留。它们是 Codex 扩展注入链路,不等同于本宿主管理的可运行 AGC Plugin。
@@ -115,6 +116,13 @@ host.rpc(method, params)
当前 native 适配器仍由宿主在编译期链接(Cargo path 依赖);动态加载插件 native 模块不在本次范围,插件包格式与宿主协议不受此限制。
Unity 插件复用此扩展点,GUI 适配器通过已有 Runner RPC 转发到唯一的 Unity 执行
服务,避免 GUI、DirectProject 与 Runtime 分别持有连接或执行不确定门闩。
宿主 RPC 将目标 adapter 绑定到 manifest,并校验显式项目路径等于当前项目;切换
项目使旧连接失效,禁止互斥锁后积压的请求在调用方超时后继续派发。
Windows x64 的 Attach helper 来源、构建工具链和执行回执合同见
[Unity 插件接入](./【技术方案】AGC Unity编辑器插件接入-2026-09-18.md)。
## Tauri 命令
`list_agc_extensions` 返回统一的 Plugin/Skill/MCP catalog`list_agc_plugins``refresh_agc_plugins``start_agc_plugin``stop_agc_plugin``reload_agc_plugin``call_agc_plugin``read_agc_plugin_panel` 提供 Runtime Plugin 管理入口;`set_agc_plugin_project_path` 设置当前项目的受控上下文。编辑器适配器通过宿主 registry 和 Plugin RPC 使用,不增加编辑器专属 Tauri 命令。
@@ -1,5 +1,34 @@
# AI 游戏创作智能体 App 实施计划
## 2026-09-17 GameCreationApp 资源 kind:唯一词汇表、严格解析与 `app_log!` 留痕
本节覆盖 2026-09-15 节里关于「canonical 字符串列表 / legacy 别名表 / `tracing` 留痕 / ts-rs 生成路径」的表述;枚举成员集合、「不迁移、不静默转换」的总体口径不变。
- **唯一词汇表**kind 的变体、线上值(kebab-case)、`as_str()``ALL`、严格解析与 ts-rs 绑定全部由 `server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs` 的声明表派生。`GAME_CREATION_APP_CANONICAL_ASSET_KINDS``canonical_game_creation_app_asset_kind()`(含 `font → document` 特例与 legacy 别名表)已删除;TS 侧同名的 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS``GameCreationAppCanonicalAssetKind``GAME_CREATION_APP_LEGACY_ASSET_KINDS``canonicalGameCreationAppAssetKind()` 一并删除,仓库里不再有第二份 kind 列表。
- **TS 生成路径**`GameCreationAppAssetKind` 由 ts-rs 生成到 `packages/shared/src/contracts/generated/GameCreationAppAssetKind.ts`(不再是 `apps/ai-game-creator-shell/src/contracts/generated/`);`packages/shared/src/contracts/gameCreationApp.ts` 直接 re-export 该 union,运行期 kind 列表只有一份 `GAME_CREATION_APP_ASSET_KINDS`(穷举 `Record<GameCreationAppAssetKind, true>` 保证不会与生成 union 分叉)。重新生成:`cargo test --locked -p shared-contracts --features ts-bindings export_bindings --manifest-path server-rs/Cargo.toml`,之后 `git diff` 必须为空。两张生成目录的忽略规则同步登记在 `.prettierignore``.eslintrc.cjs`
- **严格解析只有一个入口**`GameCreationAppAssetKind::parse_with_context(value, context)` 是唯一公开解析入口(`FromStr`、serde、所有外部边界都走它),内部严格匹配是私有 `match_canonical()`;先前那批易混名字(`from_str_lossy()`、公开的 `from_str_or_unknown()`)都已删除,认不出 canonical 值只有这一处收口 + 留痕。口径是等值匹配——不 trim、不 lowercase、不查别名、不迁移;`"UI"``"ui"``" image "``"art-spritesheet-slice"` 一律收口成 `unknown`(分类落 `unclassified`)。**接受 unknown 是显式决定**:留痕日志给出原始串与上下文,供回查仍在写 legacy kind 的代码;不写兼容、不做迁移。
- **登记边界同口径**:外部登记 kindTauri 命令 `commands::register_local_asset``commands::import_canvas_asset`、平台导入)统一走 `assets::registration_asset_kind()`——空白入参按「没有信息」落中性 `image`(→「待归类」),非空但认不出的值走严格解析收口成 `Unknown` 并留痕。三条边界不再各写一份 trim/兜底分支;解析只发生在命令边界,`import_canvas_asset_at` 等内部函数直接接收 `GameCreationAppAssetKind`,不再接受字符串 kind。
- **登记内容的 kind 必须与内容相符**`normalize_local_project_raster_resource_at` 的源 subtype 只接受图片族成员(`image / scene / character / character-animation / icon / icon-spritesheet / icon-spec / ui-design`)。未提供或空白时按没有类型信息落 `image`;音频、视频、文档、字体、代码等非图片族值,以及未知字符串,均明确报错,不再静默改成 `image`,以暴露调用方错误。派生资源写回 manifest 时不得主动写 `Unknown`Agent 回执派生物是 markdown 文本,按 `Text => Document` 同口径落 `document`
- **切片残留判据只看路径**`register_existing_platform_art_slices_at` 判定「已有半成品切片登记」时只看 `assets/art-spritesheet-slices/` 前缀,不再附带 `kind == Icon`——历史切片刻写的是非 canonical kind,严格解析后是 `unknown`,再带 kind 判据会漏掉这些登记并静默跳过回填。
- **sprite 身份不含展示串**:UI 工作流比较 sprite 资源身份时忽略 `metadata.asset_type`(它随 canonical kind 派生),只看真正影响渲染的字段;否则对既有 State 重跑工作流会误报「与已有 State 资源冲突」。
- **TS 侧判据也只留一份**`isGameCreationAppUiDesignDocAsset(asset)` 是「`kind == ui-design-doc``mediaType == application/json`」的唯一定义,资源画布入口、UI 编辑器桥接、资源引用缩略图三处统一调用它。
- **严格口径的已知代价**:既有项目 manifest 里若存的是 legacy kind`"UI"``"ui-prototype"``"art-spritesheet"``"game-background"`…),读入后就是 `unknown`,依赖 kind 等值比较的运行门禁会按「缺少该资源」处理。这是「不迁移、不 fallback」的必然结果,由项目决策接受;若将来要迁就存量数据,必须单独立项(一次性迁移或读侧白名单),不得把别名表加回解析路径。
- **留痕走 `app_log!`**`shared-contracts` 不再依赖 `tracing``kind-observability` feature 删除),改为暴露可注册回调 `set_non_canonical_asset_kind_reporter()`AGC 壳在 `main()` 里注册成 `app_log!`,日志同时含**原始输入串**与调用上下文,用于回查还有谁在写 legacy kind。TS 侧对应 `parseGameCreationAppAssetKind()``console.warn`
- **分类映射**`GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 改为按 `GameCreationAppAssetKind` 变体穷举(含 `unknown → unclassified`),与 `GameCreationAppAssetKind::ALL` 的对齐由单测 `asset_category_mapping_covers_every_kind` 守住;TS 侧同表按生成 union 穷举。
- **画布生成与图片快速编辑共用枚举**`canvas.asset_generate``assetKind` 白名单直接复用 `GameCreationAppAssetKind::CANVAS_ASSET_KINDS`(当前为 `image / icon-spec / ui-design / icon-spritesheet`),工具 schema、prompt 和执行前校验均从该枚举派生,不再维护 `&[&str]` 字符串目录。图片快速编辑来源同样严格解析 `GameCreationAppAssetKind`,只允许 `is_static_image()` 的 canonical 成员并要求 `mediaType=image`;原有 `EDITOR_IMAGE_EDIT_STATIC_IMAGE_ASSET_KINDS` 兼容白名单已删除,退役值不再放行。平台适配层若仍需要其他领域的外部请求字面值,只能在发送边界显式映射;manifest 始终写入枚举的 canonical 值。
- **版本素材替换**`subtypeEqual` 改为枚举相等(不再经别名字符串归一),`categoryEqual` 仍用 `game_creation_app_asset_effective_category` 的读时口径。
## 2026-09-15 GameCreationApp 资源 kind 枚举化(当前权威口径)
本节覆盖并替代下方 2026-09-09 资源 kind 别名、canonical fallback 与读时自愈相关口径。当前仍在开发期,不为旧本地 manifest 提供 migration、alias 兼容或静默转换;旧值只会在解析时落入 `Unknown` 并记录结构化日志,后续按扫描清单清理产生旧值的代码。
本次重构范围限定在 `apps/ai-game-creator-shell` 内表达 **GameCreationApp 资源 kind** 的字段、参数、投影、筛选和 manifest 读写边界。`mediaType`、MIME、文件扩展名、`source.kind``source.generationKind`、任务/工作流 kind、路径名,以及 server-side 其他 `asset_kind` 不在本次重构范围内,继续使用各自现有类型。
Rust 侧在 `server-rs/crates/shared-contracts` 维护唯一权威 `GameCreationAppAssetKind` enum,并使用 `serde` kebab-case 序列化;`Unknown` 是正式解析结果,序列化值为 `unknown`。TypeScript 绑定由 `ts-rs` 从 Rust 生成到 `apps/ai-game-creator-shell` 内,删除 `packages/shared/src/contracts/gameCreationApp.ts` 中重复的手写 kind 契约。正常写入路径不得主动构造 `Unknown`;未知外部值进入 `Unknown` 时必须记录原始值、来源和调用上下文的 `tracing`/日志。
当前候选正式成员以 api-server 仍有效的资源 kind 命名为基线,并保留 shell 已实际使用的字体与通用音频语义:`image``scene``character``character-animation``icon``icon-spritesheet``icon-spec``ui-design``ui-design-doc``publication-material``spec``video``audio``sound-effect``background-music``font``document``code``unknown``audio` 表示通用上传音频,`sound-effect``background-music` 表示更具体的生成/资源语义,不因同属 `audio` category 而合并。`asset``ui``animation``game-background``art-spritesheet` 等旧值必须逐处审计:进入 GameCreationAppAssetKind 的生产写入改为正式成员;仅作为 workflow、generation、路径或协议标识的值保持原字段,留待后续独立 PR。
实施要求:先完成 shell 内 raw kind / typed kind 扫描清单与生产写入审计,再分小提交实现 Rust enum、生成绑定、内部字段收紧、写入方修正、Unknown 可观测性和旧 alias 删除。每个提交只形成一个可验证的深模块,不把不同领域的字符串类型强行合并;完成后补充 `ts-rs` 生成/校验门禁、serde round-trip、未知值日志和所有 manifest writer 的 canonical/Unknown 边界测试。
## 游戏画布居中指引
开发 Agent 的系统工程提示与 `agc-web-game-development` Skill 明确约束同一 canvas 的居中只能由一方负责:Phaser `FIT + CENTER_BOTH` 配合尺寸明确的普通块级父容器,不叠加同一父容器的 Grid/Flex 居中、自动外边距或居中 transform;如由 CSS 居中则设置 Phaser `NO_CENTER`,外围页面的 Grid/Flex 不受此限制。布局修改后构建实际 dist,并在桌面、移动和 resize 下核对 canvas 对游戏父容器中心偏差不超过 1 CSS px、无溢出与意外滚动条。出现偏移先检查游戏项目的 CSS/scale,不修改 AGC 预览固定偏移掩盖问题;这些要求通过 Agent 指引执行,不新增运行时门禁或平行校验系统。
@@ -156,9 +185,9 @@ App 界面测试中,关闭 Agent 弹窗后的迟到读取用例先等待「刷
## 2026-09-09 manifest 资源功能分类与自定义标签(数据层)
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类`GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 按 canonical kind 穷举映射`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction``character / character-animation → character``scene → scene``audio / sound-effect / background-music → audio``document / spec → document``image / video / code / publication-material → unclassified`alias 表**大小写不敏感**(UI 设计资产的现役写入侧写的是大写 `"UI"``font` 是字体上传登记的 kind),`"UI" → ui-design → ui-interaction``font → document → document`;其余映射不到 canonical kind 的原始 kind(例如 `test`)才经 `canonicalGameCreationAppAssetKind``image`、归 `unclassified`。读显示口径在 TS`gameCreationAppAssetCategory`)与 Rust`game_creation_app_asset_effective_category`)同构,写回 manifest 必须用落盘原值 `gameCreationAppAssetPersistedCategory`
本地 manifest 资产条目末尾新增 `category`(单值,6 类 `ui-interaction / character / scene / audio / document / unclassified`,默认 `unclassified`)与 `tags`(字符串数组,默认空数组);字段始终序列化,不进 api-server、外部 OpenAPI 或 SpacetimeDB schema。默认分类继续由资源 kind 到 category 的显式映射决定`icon / icon-spritesheet / icon-spec / ui-design → ui-interaction``character / character-animation → character``scene → scene``audio / sound-effect / background-music → audio``document / spec / font → document``image / video / code / publication-material / unknown → unclassified`。kind 枚举、未知值与写入边界以本文件顶部 2026-09-15 口径为准;本节不再定义 alias、canonical fallback 或旧 manifest 自愈
历史 manifest 缺少字段时读取按 `kind` 派生分类、`tags` 取空数组;显式写入的合法分类原样保留,未知分类字符串按前向兼容退回 `kind` 派生。新建资源条目写 `kind` 派生默认值,更新既有条目保留已有 `category` / `tags`,不覆盖用户值。`tags` 归一化(trim、去空、去重)只以纯函数交付,本轮不接入写入路径。本轮不做任何 UI(画布功能画布与筛选、@ 面板标签筛选、标签管理面板)、数据迁移脚本、标签库聚合派生与 Agent 检索工具
开发期 manifest 只按当前枚举解析;旧字段兼容、迁移脚本与 alias 不属于当前目标。新建资源条目必须由已验证的 `GameCreationAppAssetKind` 写入;未知输入只能产生 `Unknown` 解析结果并记录日志,不能由 writer 静默改成其他 kind。`tags` 归一化(trim、去空、去重)保持现有规则。本节原有 UI、标签库和 Agent 检索范围不因本次 kind 枚举化扩大
## 2026-09-08 Web 游戏 npm 与 Phaser 4 产物合同
@@ -1354,7 +1383,7 @@ game-project/
## 2026-08-17 UI Editor 项目资源 State 持久化
- UI 编辑器复用现有 manifest `kind: "UI"``mediaType: "application/json"` 资源,不增加平行 asset kind。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope`projectId``assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets``Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。
- UI 编辑器使用唯一的 manifest `kind: "ui-design-doc"``mediaType: "application/json"` 资源,不接受旧 `UI` / `ui` / `ui-design`,也不增加 fallback 或迁移。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope`projectId``assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets``Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。
- Tauri 专用 load/save command 只接受项目路径、期望项目 ID、manifest asset ID 和(保存时)资源 revisionRust 按 manifest 解析受控本地路径并在项目写锁内做 CAS。相同 `State` 返回 unchanged 且不推进 project revision;不同内容安装并回读一致后才推进 revision,后续推进失败返回 `reconciliation-required`,不伪装为完整保存。
- UI 编辑器代码导出仅写入用户项目目录下的 `ui/generated-*.js` 派生文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不得把生成文件写入冒充为项目 mutation。
- UI State 原子安装保留最近一个可解析、canonical 的 `.previous` 恢复候选,作为最佳努力恢复来源;写入主文件前不把完整 State 语义校验重复执行一遍。主文件损坏时,恢复候选仍必须通过同一严格 schema、project/asset identity、revision、引用和 State 校验后才能安装;恢复安装与保存共用项目写锁,并在持锁后重新读取主文件,已有并发保存的有效新版本时直接返回而不安装旧副本。任一候选均不可信则停在加载错误,前端禁编辑和保存。新建 UI 资源先登记并安装合法 envelope,任一步失败补偿 manifest/文件,避免把空 JSON 留给资源卡。
@@ -1497,7 +1526,7 @@ DirectProject 使用 `approvalPolicy=never`,避免每次原生调用再经过
- **命令**`read_local_project_version_resource_replacement_candidates`(只读,`asset.list`)与 `replace_local_project_version_resource`(写,`asset.register`),实现在 `src-tauri/src/project/version_resource_replacement.rs`。入参都是单个 `input` 对象、`deny_unknown_fields`:读 `{projectPath, sourceVersionId, sourceResourceId}`,写额外要求 `expectedProjectId + expectedProjectRevision + replacementResourceId`;出参 `{versionId, committedProjectRevision, replacement:{versionId, sourceResourceId, replacementResourceId, compatibility, warning}}`**没有** `parentVersionId`,因为没有新版本)。这两个 DTO 是 Tauri 本地 DTO**不进跨端契约**。
- **写入语义**:持项目写锁(与删除 / 重命名 / 标签同一把)→ 锁外与锁内各复核一次 `projectId`,锁内复核 durable revisionCAS 失败报 `project-identity-conflict` / `project-revision-conflict` 且零写入)→ 走上面的绑定改写通道,放行集合固定为 `[sourceVersionId]` → 成功后推进一次项目 revision(改绑定属于 `versions` 变化,跨面快照门禁要求 revision 前进)→ 追加一条 `asset.version_binding.replace` 审计(复用既有 `append_agent_db_record`)。审计写失败会报错但不回滚,与 `asset.register` 同口径。
- **绑定改写语义**(恒等绑定口径的硬约束):`resourceBindings` 是"该版本使用的素材集合"而不是槽位表,因此改写 = **源素材从集合里消失 + 保证替换素材在集合里**;替换素材是源版本创建之后才登记时按源素材原来的位置插回(顺序稳定),早已登记时只做摘除 —— 不能把源素材那条槽位改写成替换素材,那会撞「资源槽位重复」。
- **准入与提示(后端权威,前端只呈现)****硬门禁**只有 `categoryEqual`(用 PRD §5.3 的**读时自愈**口径,Rust 侧 `game_creation_app_asset_category_with_read_time_healing``packages/shared``gameCreationAppAssetCategory` 逐分支一致并配用例锁定)与 `subtypeEqual`canonical `kind`);`sizeSpecEqual` **降级为提示**(「格式与源素材不同」)不再拒绝 —— 它的完整判据今天不存在(manifest 没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,实际只等于"媒体格式相等"),硬拦会误拒 `png ↔ webp` 这类最常见需求;要变成硬判据须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。
- **准入与提示(后端权威,前端只呈现)****硬门禁**只有 `categoryEqual`(用 PRD §5.3 的**读时自愈**口径,Rust 侧 `game_creation_app_asset_effective_category``packages/shared``gameCreationAppAssetCategory` 逐分支一致并配用例锁定)与 `subtypeEqual`canonical `kind`);`sizeSpecEqual` **降级为提示**(「格式与源素材不同」)不再拒绝 —— 它的完整判据今天不存在(manifest 没有 `width / height / durationMs`,现役写入侧几乎全写 `imageSequenceFrames: None`,实际只等于"媒体格式相等"),硬拦会误拒 `png ↔ webp` 这类最常见需求;要变成硬判据须先给 manifest asset 增尺寸字段并在写入侧回填(跨端契约变更)。
- **入口**:资源卡选中工具条的宿主 `extraActions` 新增「替换素材」,判据复用现役 `isResourceUsedByCurrentVersion`manifest 身份 + 被当前版本绑定),未绑定素材不渲染入口;不动 `ImageCanvasSelectedLayerToolbarAction` 共享 union、不改 `resourceCanvasToolbarModel``supportedActions`。候选弹窗复用 `ImageCanvasProjectAssetPickerDialog`(该弹窗在 AGC 侧首次使用),以可选 prop 扩展:`singleSelect` / `assetBlockedReasons` / `assetHints` / `renderAssetMedia` / `selectionNoun` / `errorMessage`,**默认值保持网页端美术画布行为逐字不变**。候选行渲染类型占位而不挂 `<img>`(AGC 的预览要经带 scope 的原生调度器拿 Blob URL,弹窗内没有同步 `src`)。
- **成功后行为**:重读 manifest,**不切换版本**(没有新版本可切),**不自动重载 / 重启运行中的预览**(PRD §3.2 末条),不做运行时资源重映射。可见变化只有资源卡「当前使用」高亮移到替换素材、`@` 面板「当前版本素材」更新。
- **已知代价(用户已确认接受)**:**替换历史不可回溯**——替换前身份只剩那条审计与 manifest 的 `.previous` 副本;需要"某版本历史上换过什么"时要另立切片(PRD §3.2 / §5.3 保留为未来合同正是为此)。