合并最新 master 并解决画布共享冲突

同步 origin/master 的场景、音效、运行时与后端原子提交改动

保留共享画布包导出并补齐场景、生成配方和历史动作类型

融合清单刷新测试、幂等请求、生成合同校验与项目记忆文档
This commit is contained in:
2026-08-10 11:34:38 +08:00
358 changed files with 51496 additions and 9219 deletions
@@ -28,6 +28,10 @@
- 锁定、分组、翻转等状态不影响图片文件导出,但写入元数据。
- 空画布时按钮置灰,或点击后显示轻提示。
左侧素材库选择模式同时支持导出当前可见范围内的选中素材:只选中一个普通素材时直接下载原文件;选中多个素材时沿用画布素材 ZIP 的读取、去重、媒体分目录、序列帧、元数据、部分失败和浏览器下载能力,生成 `项目名-选中素材-YYYYMMDD-HHmmss.zip`,包内根目录为 `项目名-选中素材/`。单素材、单序列帧、选中素材 ZIP 和画布素材 ZIP 的下载文件名都必须包含到秒的本地时间戳,避免同一天重复导出时重名。序列帧层只要至少一帧具有可读 `imageSrc / objectKey` 即可进入导出计划,不要求层级 `src / objectKey`;单层、选中素材和画布集合导出必须共用同一互斥状态,避免并发下载覆盖进度与结果提示。该入口不改变中央画布和图层列表的选择 / 下载语义。
持久素材只要 `src``objectKey` 任一有效即可进入点击、Shift、框选和全选范围。`src` 为空但保留私有 `objectKey` 的素材,导出时复用统一资源读取链路换签;浏览器无法直读签名 URL 时继续回退同源 `/api/assets/read-bytes` 字节代理,不得因缺少临时展示地址而过滤。
暂不实现:
- 画布整体截图 PNG。
@@ -40,7 +44,7 @@
导出文件名:
```text
项目名-画布素材-YYYYMMDD.zip
项目名-画布素材-YYYYMMDD-HHmmss.zip
```
包内结构:
@@ -72,7 +76,7 @@
普通序列帧 ZIP 结构:
```text
角色动作-Sequence.zip
角色动作-Sequence-YYYYMMDD-HHmmss.zip
├─ frames/
│ ├─ frame-01.png
│ └─ frame-02.png
@@ -84,7 +88,7 @@
Spine JSON ZIP 结构:
```text
角色动作-SpineJSON.zip
角色动作-SpineJSON-YYYYMMDD-HHmmss.zip
├─ frames/
│ ├─ frame-01.png
│ └─ frame-02.png
@@ -164,6 +168,7 @@ assetObjectId > objectKey > sourceAssetId > src
2. 过滤无效图层,保留隐藏图层。
3. 按去重 key 合并图片源或序列帧源。
4. 对每个素材源读取 Blob
- 集合导出先按图层顺序完成去重和文件名规划,再用同一个四路并发读取器读取 / 转换普通素材与序列帧,最后按规划和原帧顺序写入 ZIP;不得让响应完成顺序改变导出目录或帧顺序。
- `data:image/...` 直接转换为 Blob。
- 同源或可访问 URL 使用 `fetch` 拉取 Blob。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;必须在同一保护边界内完整消费响应体,换签、请求、状态码或响应体读取任一阶段失败时,才 fallback 到同源 `/api/assets/read-bytes`,不得在只拿到 `2xx/206` 响应头后提前视为读取成功。
@@ -198,6 +203,10 @@ assetObjectId > objectKey > sourceAssetId > src
- 动作图层右键菜单把 `导出为` 作为一级入口,二级菜单提供 `序列帧导出(zip``Spine 导出(zip`
- 单图层普通序列帧 ZIP 包含 `frames/``preview.gif``metadata.json``manifest.txt`,且不包含 `skeleton.json`
- 序列帧导出的 `skeleton.json` 可被独立验证器解析并预览。
- 左侧素材库只选一个素材时直接下载该素材,不创建集合 ZIP。
- 左侧素材库选择多个素材时只把选中素材写入 `项目名-选中素材-YYYYMMDD-HHmmss.zip`,不混入未选素材,并继续覆盖部分失败和全部失败边界。
- 同一事件循环内重复触发集合导出时只启动一次读取和下载;单个选中素材下载成功后显示 `选中素材已导出`
- 多素材与长序列帧集合导出同时读取不超过四项,外层素材无需等待前一个序列全部完成,异步读取逆序完成时 ZIP 文件与帧顺序仍保持稳定。
## Spine JSON 验证器
@@ -226,7 +235,6 @@ npm run spine-export-validator:build
## 后续扩展
- 导出当前选中素材。
- 导出画布快照 PNG。
- 导出可恢复工程包。
- 导入工程包恢复画布。
File diff suppressed because one or more lines are too long
@@ -101,9 +101,12 @@
## 第十阶段模块
- `useImageCanvasAssetLibrary.ts`
- 承载账号级素材库状态模型:素材文件夹、素材列表、文件夹折叠 / 新建 / 重命名 / 删除、素材重命名 / 删除、素材选择模式、框选、多选删除、素材拖到文件夹和鉴权失败登录弹窗。
- 承载账号级素材库状态模型:素材文件夹、素材列表、文件夹折叠 / 新建 / 重命名 / 删除、素材重命名 / 删除、批量删除资源副作用、素材拖到文件夹和鉴权失败登录弹窗;不再持有选择集合、范围锚点或框选交互状态
- 主视图继续保留上传文件读取、上传占位卡片进度、拖到画布坐标、创建画布图层、工程资源持久化和画布图层清理;素材删除通过 `onDeleteAssets` 回调通知主视图清理关联图层。
- 该 hook 有独立单测覆盖素材库加载归一化、401 登录、新建文件夹临时 id 替换、素材移动、删除回调和多选删除,避免后续整理侧栏 JSX 时丢失素材库能力。
- `useImageCanvasAssetSelection.ts`
- 作为素材选择的唯一状态边界,统一持有选择模式、完整选中集合、范围锚点、可选素材有效性 reconcile、单项 / Shift / 当前可见全选增量、框选几何与框选生命周期;框选除 `pointerup / pointercancel` 外必须在匹配的 `lostpointercapture` 到达时只清理框选状态,不得再次释放已经丢失的 capture。该 hook 向批量下载 / 删除只暴露已经按素材顺序解析的 `selectedAssets`;删除入口根据当前 `visibleAssetIds` 识别未显示选择并在执行完整集合删除前弹出危险确认。
- 搜索和文件夹折叠只在侧栏产生 `visibleAssetIds` 并传入增量动作,不得直接修改或 reconcile 选择集合;选择行为测试集中在该 hook,素材库 model 不再维护第二套选择状态机。
## 第十一阶段模块
@@ -1,6 +1,6 @@
# React 组件测试准则
更新时间:`2026-06-26`
更新时间:`2026-08-07`
## 背景
@@ -36,6 +36,10 @@
- `data-testid` 名称必须描述用户或稳定渲染边界,例如 `image-canvas-editor-snap-guide-vertical`;不要描述 React 私有 state 名称。
- 测试用 fixture 只包含本行为需要的字段。演化中的 payload 使用 `expect.objectContaining(...)` 或 helper 生成默认对象,避免一处契约加字段导致大量无关用例碎裂。
- 当测试是为防止历史回归,应在测试名或邻近注释中说明防的是什么行为,而不是记录实现步骤。
- 同一用例既要验证定时器调度参数,又要断言确定的中间帧或中间状态时,必须 mock 定时器回调或使用可控假时钟;不得让真实墙上时间在异步交互期间推进被断言的状态,否则本地通过的用例会在较慢 CI 中偶发失败。
- 测试 React effect 中注册的事件监听时,触发事件前先用可观测的 listener 调用确认注册已完成;异步请求已开始应用有界 `waitFor` 断言确认,不要用无界手工 Promise 等待一次性信号。解除挂起请求时将 Promise 收尾纳入异步 `act`,确保后续 React 更新在断言前已冲刷。
- 公共 `AutoGrowTextArea` 使用 Lexical `contenteditable`,业务测试通过 `setPlainTextEditorValue` 写入文本、通过 `getPlainTextEditorHost` 断言外层样式;占位文案是独立渲染节点,不读取原生 `placeholder` 属性,也不对编辑根触发 textarea 专属的 `change` 事件。
- 异步请求失败时,错误状态与调用方的草稿 / 附件恢复可能分属连续两次 React 更新;用例必须对最终恢复结果使用有界 `waitFor`,不能把错误文案刚出现的中间帧当成恢复已经完成。
## 试点调整
@@ -29,7 +29,8 @@ VectorEngine `gpt-image-2`、音频、LLM 等外部生成不能由面向外部
- `claim_external_generation_jobs_and_return`worker 按 `worker_id``limit` 和 lease 时长抢占 `pending` 或 lease 过期的 `running` 任务,返回本次 claim 的 `lease_token`
- `renew_external_generation_job_lease_and_return`worker 长任务执行期间按 `worker_id + lease_token` 续租,防止外部生成超过单次 lease 后被重复领取。
- `update_external_generation_job_phase_and_return`worker 按 `job_id + worker_id + lease_token` 把当前执行阶段更新为 `generating``processing`,并同步现有摘要投影;不新增阶段任务或阶段表。procedure 用结构化结果区分 `LeaseFencingRejected``OtherRejected`,调用方不解析错误文案;`LeaseFencingRejected` 立即终止,`OtherRejected` 以及 SDK 的 `Procedure` / `Runtime` 错误不重试,只有 `Build` / `ConnectDropped` / `Timeout` 在同一个 job attempt 内重试 `1` 次。该重试只重新上报 phase,不把任务写回 `pending`,也不重新调用 provider;编辑器 job 入队固定 `max_attempts=1`,第二次传输失败后任务进入 `failed`,不会回到 `pending` 或从 provider 生成起点重跑。
- `complete_external_generation_job_and_return`worker 成功后按 `worker_id + lease_token` 写入 `result_payload_json`,任务进入 `completed`
- `complete_external_generation_job_and_return`只保留给不携带编辑器正式 object/resource/asset/canvas 业务写回的兼容路径。现役编辑器生成成功时不得单独调用它
- `persist_editor_generation_result_and_return`:编辑器 queue / inline 共用的结果提交口。单一事务写入可选 asset object、全部 project resource / account asset / asset binding、可选 canvas V2 CAS、queue job 完成与 durable receipt;返回 `Applied / AlreadyApplied` 和权威快照。
- `fail_external_generation_job_and_return`worker 失败后按 `worker_id + lease_token` 回写错误,并按 `max_attempts` 决定回到 `pending` 重试或进入 `failed`
- `list_external_generation_job_summaries_and_return`:按当前账号从轻量摘要投影读取正式生成任务列表,返回 pending / running / 未确认终态数量、任务价格、执行阶段和完成提示确认状态。
- `acknowledge_external_generation_job_summaries_and_return`:按当前账号确认已终态任务的完成 / 失败提示,写入摘要投影的 `notification_acknowledged_at` 并追加审计事件。
@@ -87,6 +88,8 @@ BFF 只做鉴权、授权裁剪、字段脱敏和契约映射;worker 调度、
新增私有审计表 `external_generation_job_event`,记录 `enqueued/claimed/lease_renewed/completed/failed/acknowledged` 等事件。事件表只追加状态转换事实,不作为当前状态源;排障时先看 `external_generation_job` 当前状态,再按 `job_id``external_generation_job_event` 时间线。
另新增私有 `editor_generation_operation` durable commit receipt。它不与 `external_generation_job` 争抢任务状态:job 仍负责队列、lease、计费和通知,receipt 只固化某个 owner/kind/operation 的 request fingerprint、整笔 commit SHA-256、可选 project 以及 queue 的 job/worker/lease/result 绑定。inline 虽没有 job,也必须写 receipt;否则 API 进程重启后无法安全区分“完整提交”与“稳定 ID 巧合/历史部分记录”。
索引:
- `by_external_generation_job_status_available(status, available_at)`
@@ -205,9 +208,11 @@ controller 配置:
- `editor_video_generation`:画布视频生成和视频素材快速编辑。
- `editor_sound_effect_generation` / `editor_background_music_generation`:画布音效与背景音乐。
画板结果的业务真相仍是 `editor_project_resource`、账号级 `editor_asset``editor_canvas.layers_json`。请求携带 `projectId + canvasCompletion` 时,worker 成功后读取当前项目 layout,用最新 generation dialog placeholder 或无 dialog 完成占位写入结果图层,并保存项目快照;前端轮询单 job 到 completed 后重新读取项目快照,不从队列 payload 或本地临时响应重建正式图层。生成器已被删除时,worker 只保留生成出的资源 / 素材记录,不把结果重新塞回画布。
画板结果的业务真相仍是 `asset_object``editor_project_resource`、账号级 `editor_asset`、可选 `asset_entity_binding` 和对应的 legacy / structured canvas 表;`editor_generation_operation` 只是提交回执,不替代这些 read model。worker 在 Provider 与 OSS 完成后只做 prepare:使用 owner + operation kind + job ID + stable slot 派生 resource/asset ID,构造可选 object/binding、候选 layout 和 compact job result,然后一次调用 `persist_editor_generation_result_and_return`。该 procedure 必须在当前事务快照校验 owner、job kind、由 `request_payload_json` 重算的 fingerprint 与未过期 lease,最后与业务记录一起完成 job 和 receipt。任一验证、binding 或 canvas CAS 失败都回滚全部数据库事实;worker 不得随后再调用 `complete_external_generation_job_and_return`前端轮询单 job 到 completed 后重新读取项目快照,不从队列 payload、receipt 或本地临时响应重建正式图层。
角色形象、图标 spritesheet 和 UI 素材提取在 provider 原图已经持久化后,如果透明背景处理最终失败,只用原图完成 `canvasCompletion`,不创建或回填透明处理图,图标和 UI 也不继续拆分,任务保持 `completed`。这个 source-only 降级只包住透明背景处理的最终失败;phase 上报、provider 原图持久化、透明处理图持久化或画布写回失败仍按任务错误传播
结果重放必须保持同一 operation fingerprint 和同一 prepared commitreceipt 存在时核对 commit SHA-256、project/job/worker/lease/result 绑定与逐 slot 权威记录,完全一致才返回 `AlreadyApplied`,不重复 job/binding 事件或 canvas revision。receipt 缺失但任一稳定 resource/asset/binding 已存在、同 operation 异指纹/异内容、已过期 lease 都失败关闭;事务前已确认 object 只在候选全字段精确一致时复用。canvas CAS 冲突时只刷新 project 重算 layout,不再次调用 Provider 或上传 OSS。`completed_at_micros` 必须为正数,候选原时间字段与它一起绑定到 commit SHA-256,重放不得重新取时;job 终态与事件使用 SpacetimeDB `ctx.timestamp`。OSS `PUT / HEAD` 仍位于事务外,可留下无引用 object,不声称跨 OSS exactly-once
角色形象、图标 spritesheet 和 UI 素材提取在 provider 原图已可用且 OSS 上传已验证后,如果透明背景处理最终失败,最终 prepared commit 只保留原图并用它完成 `canvasCompletion`,不创建或回填透明处理图,图标和 UI 也不继续拆分,任务保持 `completed`。这个 source-only 降级只包住透明背景处理的最终失败;phase 上报、原图候选构造、透明处理图候选构造或统一原子提交失败仍按任务错误传播。
透明背景处理正常成功时,角色形象、图标 spritesheet 和 UI 素材提取的画布都同时放透明主结果与 provider 原图:透明主结果保持生成器 `generatedLayerId` 主锚点,provider 原图作为第二个图层放在其右侧;图标和 UI 实际拆分出的业务素材从 provider 原图右侧继续排列。
@@ -0,0 +1,116 @@
# 编辑器生成结果原子提交与幂等重放方案
日期:`2026-08-06`
## 目标
修复 Issue #134:现役图片、图片修改、背景移除、图标图集、UI 素材提取、角色动作、视频、音效和背景音乐生成,在 OSS 结果已经可用后,必须把正式 `asset_object``editor_project_resource``editor_asset`、可选画布完成和队列终态作为同一个可重放提交处理,禁止继续按多个独立 SpacetimeDB procedure 分段写入。
本方案只承诺数据库内原子性。OSS `PUT / HEAD` 仍位于 SpacetimeDB 事务外;事务失败可能留下尚未登记或尚未引用的对象,后续按 operation 前缀做异步清理,不把它描述成跨 OSS 的 exactly-once。
## 权威操作身份
- 默认 queue 模式:`external_generation_job.job_id` 是唯一 operation ID。External v1 的 `Idempotency-Key`、主站稳定 `x-request-id` 和 Editor Agent 确定性任务 ID 都先收敛为该 job ID。
- inline 兼容模式:使用 `RequestContext.request_id` 作为 operation ID,并对规范请求计算 SHA-256 fingerprint;同 ID 异 fingerprint 必须返回幂等冲突。inline 也必须写 durable receipt,不能只靠进程内 prepared result 或稳定记录 ID 猜测是否已提交。
- Provider `taskId` 只保留为生成审计字段,不参与正式记录唯一性。
- 每个 operation 的产物以稳定 `slot` 区分,例如 `provider-source``primary``processed``slice-0000``animation-preview``animation-final`。记录 ID 按 `owner + operation kind + operation ID + slot + record kind` 做 domain-separated SHA-256 派生,产物顺序变化不能改变既有 slot 的 ID。
## 统一 procedure
`spacetime-module` 增加 `persist_editor_generation_result_and_return`。procedure 只允许 editor generation runtime service identity 调用,并在一个 `try_with_tx` 内完成全部动作。
输入的编码级形状:
```rust
EditorGenerationResultPersistItemInput {
slot: String,
asset_object: Option<AssetObjectUpsertInput>,
project_resource: Option<EditorProjectResourceCreateInput>,
asset: Option<EditorAssetCreateInput>,
binding: Option<AssetEntityBindingInput>,
}
EditorGenerationResultPersistInput {
owner_user_id: String,
operation_kind: String,
operation_id: String,
operation_fingerprint: String,
items: Vec<EditorGenerationResultPersistItemInput>,
canvas_layout: Option<EditorProjectLayoutSaveV2Input>,
job_completion: Option<ExternalGenerationJobCompleteInput>,
completed_at_micros: i64,
}
```
输出返回 `Applied / AlreadyApplied`、逐 slot 的 object/resource/asset/binding 快照、可选 project 快照和可选 job 快照。
### Durable receipt
新增私有表 `editor_generation_operation`,它是 queue 和 inline 共用的 durable commit receipt,不是第二套业务状态或任务队列。主键 `operation_key` 由 owner 和 operation ID 做 domain-separated SHA-256 派生,因此同 owner 不得跨 operation kind 复用同一 operation ID;表内固化 `owner_user_id / operation_kind / operation_id / operation_fingerprint / commit_sha256 / project_id / job_id / job_worker_id / job_lease_token / job_result_payload_sha256 / completed_at`
- `operation_fingerprint` 绑定用户请求;`commit_sha256` 对完整 `EditorGenerationResultPersistInput` 的稳定 BSATN 编码做 domain-separated SHA-256,另外绑定本次准备提交的 slot、object/resource/asset/binding、画布候选与 job completion。不得使用 Rust `Debug` 文本充当持久协议,两类指纹也不得混为一个。
- queue 路径必须把 receipt 与原 `job_id + worker_id + lease_token + result_payload_json` 全量绑定;receipt 只保存 payload SHA-256,不复制正文。首次提交仍必须验证当前有效 lease,完成后重放以 receipt 为提交凭证,并回读已完成 job 核对业务身份、权威 compact result 及其 SHA-256。
- receipt 只保存幂等校验所需的有界元数据与摘要,不复制 project/canvas 大快照,不代替 resource、asset、binding 和 job 的权威表。
### 首次提交顺序
1. 校验调用身份、operation 字段、fingerprint、item 数量上限和 slot 唯一性。统一提交最多接受 66 个 item,用于容纳最多 64 个图集切片以及 provider 原图和透明整图。
2. queue 输入必须完整携带 `job_id + worker_id + lease_token + result_payload_json`;inline 输入必须全部省略,禁止半套 guard。
3. queue 路径在同一事务快照内校验 job owner、kind、request fingerprint、running 状态和有效 lease;过期 worker 不得写业务结果。`source_entity_id` 必须精确等于本次唯一结果 `project_id`,不得用同 owner 的 job 向其他项目提交。
4. 对每个 item 校验稳定 object/resource/asset ID、owner、project、folder、object key、source resource、task 审计字段和媒体字段的交叉一致性。project resource 和 account asset 的 `source_resource_id` 均必须单独验证:来源资源必须是本次同事务候选或已登记资源,属于同 owner,且在结果具有项目上下文时属于同 project;不接受 asset-only 分支绕过血缘校验。
5. `asset_object` 存在于输入时在同一事务内做精确 upsert;省略时,resource/asset/binding 引用的 object 必须已登记且属于同 owner。事务前 OSS `HEAD` 成功不等于 object 已正式登记。
6. 创建全部 project resource、account asset 和可选 `asset_entity_binding`。binding 必须指向同 slot 的 object 与对应 resource/asset 实体,且 owner、asset kind、entity kind/id 和稳定 binding ID 完全一致。不得接受普通 media reuse 返回另一个随机 resource ID;稳定 ID 已被占用且内容不一致时失败关闭。
7.`canvas_layout` 时调用既有 V2 layout 持久化函数,以 `expected_revision` 做 CAS,并继续执行 legacy / structured 大小、资源引用和媒体族门禁。
8. queue 路径最后调用事务内 job complete,写入调用方预先按现有规则构造的 compact result payload;再写入 durable receipt。任一步失败时 object/resource/asset/binding/canvas/job/receipt 全部回滚。
`completed_at_micros` 必须为正数,首次提交把它固化为 receipt `completed_at`。object/resource/asset/binding/canvas 候选各自现有的时间字段连同 `completed_at_micros` 一起进入 commit SHA-256;同一 prepared commit 的未知结果重放必须复用原时间,不得重新取时。明确的 canvas CAS 表示该事务已回滚,刷新 project 后形成新的 layout candidate,使用刷新时的 `updated_at_micros`,避免把并发用户刚写入的项目时间回拨。job `completed_at/updated_at` 与 job event 时间仍由 SpacetimeDB 事务时间 `ctx.timestamp` 产生,不信任调用方时钟。成功重放返回原快照,不刷新 receipt、业务记录、事件或 canvas revision。
### 重放
- receipt 存在时,只允许相同 owner/kind/ID、operation fingerprint、commit SHA-256 与原 project/job 绑定的完整重放;queue 额外核对原 worker/lease/result payload。逐 slot 权威 object/resource/asset/binding 和可选 project/job 仍必须可读且与候选一致,不得只看 receipt 就伪造快照。
- receipt 存在且全部事实一致时返回 `AlreadyApplied`,不得新增记录、重复 binding changed / job completed 事件、刷新时间或推进 canvas revision。
- receipt 缺失但任一稳定 asset object/resource/asset/binding、画布结果或已完成 job 已存在,属于可疑的部分写入,必须失败关闭;不得临时补 receipt 后声称幂等。现役生成 prepare 阶段只做 OSS PUT/HEAD,不得在统一 procedure 前单独登记稳定 asset object。
- procedure 调用结果未知时,调用方最多自动重放同一 prepared commit 两次,不重新调用 Provider 或重新上传 OSS;明确的业务错误和 CAS 冲突不进入传输重放。
- `operation_id``external_generation_job` 中已存在时,首次提交和 receipt 重放都必须携带与它一致的完整 job completion guard;只有事务内确认不存在同 ID job 时才允许 inline。
- 同一 item 的 resource/asset 尺寸、媒体引用、task、asset kind 与生成元数据必须一致;binding 必须匹配 operation 明确允许的 entity/slot/kind/profile tuple。音频使用 `sound-effect -> editor_sound_effect``background-music -> editor_background_music` 显式映射,不使用粗暴的全字段硬等。
- item 省略 `asset_object` candidate 而复用已登记对象时,首次提交与 `AlreadyApplied` 重放都要回读 canonical object,重新验证存在性、owner、object key、task、kind 和音频媒体类型。
## api-server 接入
- 通用持久化改为 `prepare -> build canvas candidate -> atomic commit`。prepare 阶段只生成稳定 ID、上传/验证对象和构造候选 DTO,不创建 resource/asset。
- api-server 继续复用现有画布 completion / replacement 逻辑计算候选 `layers_json``expected_revision`;统一 procedure 在最终事务内重新执行既有 layout 校验和 CAS。
- CAS 冲突只刷新当前 project、重新计算 layout 并重试 prepared commit;相同 operation、slot、对象和记录候选保持不变,禁止重跑 Provider。
- 重新计算 layout 时只允许 revision、layers 与 layout `updated_at_micros` 随最新 project 变化;业务 items、job result payload 和 `completed_at_micros` 保持不变。首次 CAS 事务已明确回滚,因此刷新后的 layout 是新的 prepared commit;该 commit 若结果未知,只能原样重放自身。调用方最多自动刷新一次,第二次冲突直接返回。
- queue completion 不持久化 inline handler 的完整响应:普通画布任务只保留 source/warning 元数据,Editor Agent 只写入裁剪后的 `editor-agent-tool-call-result`External API 只写入裁剪后的 `result`。图集/UI 切片不得在 queue payload 中重复携带完整 resource/asset/prompt/generationInputs,最终 JSON 必须在 512 KiB 持久化上限内。
- compact result 必须先满足原消费 DTO 的必填字段:角色动作/视频保留 `ok`,图标/UI 正常与 source-only fallback 保留 `ok / prompt / actualPrompt`,音效/BGM 保留 `prompt`Editor Agent 与 External v1 的二次 allowlist 裁剪都不得再删除 `prompt / actualPrompt`,最终持久 payload 必须能反序列化为对应 response contract。Editor Agent 图片生成/修改 DTO 的 `provider` 为可选审计字段,compact payload 可删除它而不影响终态回填。External API 的角色动作与视频结果还必须保留本次已创建账号素材的稳定 `assetId`;裁剪可移除大 payload,但不得让 completed 结果无法定位正式素材。
- queue 消费者身份在 worker 从完整 claimed job 构造调用上下文时固化;原子提交不得再从 summary 兼容快照反推,因为该快照会清空 dedupe key 并裁剪 request payload。
- queue 的 compact result 当前不保存 `project`,因此可在事务前由稳定候选 resource/asset 和生成响应元数据构造;HTTP 成功响应中的 project 使用 procedure 返回的权威快照。
- worker 在统一 procedure 已完成 job 后不得再次调用 `complete_external_generation_job`。只有 `Applied / AlreadyApplied` 才能作为成功终态。
- Provider 已成功且计费 attempt 已扣款后,若原子提交确定失败并要把 job 置为终态 `failed`,必须由同一 SpacetimeDB 事务先验证当前 worker/lease,再结算当前 attempt 退款并写失败终态。不得在 api-server 先独立退款,否则过期 worker 或已成功但回包丢失的提交可能同时得到正式结果与退款。
- inline 模式没有 job 失败事务补退,计费成功边界必须延迟到 durable result commit 完成。Provider/上传成功后的明确持久化失败退还已扣泥点;`Build / PoolAcquire / ConnectBuild / ConnectHandshake` 等 procedure 未发出阶段的失败或取消仍通过 deferred guard 退款。procedure dispatch 后到明确回包前必须标记结果未知;连续传输不确定或此窗口内 HTTP future 被取消时保留扣款,避免远端已成功时变成「正式结果 + 退款」。`Procedure` 回包是确定结果,成功或明确失败后必须清除未知标记。
## 多产物与现有特例
- 图片的 provider source、透明/规整结果和 source-only fallback 必须在最终选择明确后一次提交;fallback 只提交实际保留的结果集合。fallback compact result 的尺寸、`prompt / actualPrompt`、model 和图标/UI `priceMudPoints` 必须来自本次已冻结生成上下文,不得从可选 project resource 反推;不带 `projectId` 时仍必须产生完整消费契约。
- 图标图集和 UI 提取使用稳定 slot 提交 provider source、透明整图和成功切片;切片失败时按既有 warning 语义只提交可信整图集合。
- 角色动作一次提交预览视频与最终序列素材;逐帧 `asset_object` 可作为 item upsert 或已登记对象被最终序列引用,正式 project resource / account asset 与 canvas 不得分段提交。
- 视频、音效和背景音乐使用单个 primary item。
- 完美像素保留现有专用 operation/fingerprint/procedure;手动图集拆分不调用 Provider,不属于本次九类生成 job 的原子提交范围,继续使用现有批量 procedure 与画布完成链路。
## Schema 与兼容性
- 新增私有 `editor_generation_operation` durable receipt 表;它与 `external_generation_job` 分工,前者证明一笔业务结果原子提交,后者仍是 queue 执行、lease、计费和通知真相。新表必须纳入 `migration.rs` 导入/导出、schema 检查和本文档表目录。
- 新增 Spacetime procedure/type ABI 后必须重新生成 `spacetime-client` bindings,并同步 facade mapper。
- HTTP 路由、请求/响应 DTO、header、状态码和 External v1 异步语义保持不变,因此不修改 OpenAPI;必须复跑 External v1 契约测试证明没有漂移。
- inline 兼容模式没有 durable job,但必须具有同样的 durable receipt、稳定 ID、fingerprint 和单事务重放;这仍不授权浏览器自动重试已可能发出的生成 POST,调用方应先走结果对账。
## 验收
- 资源创建后资产或 binding 校验失败:事务结束后 object/resource/asset/binding/canvas/job/receipt 均无部分写入。
- 资源/资产创建后 canvas revision 冲突:全部业务记录回滚;使用同 operation 和 prepared result 刷新布局后可成功。
- 成功后相同 operation 重放:返回原 object/resource/asset/binding/project/jobreceipt 只有一行,记录数、时间、完成事件数和 canvas revision 不变。
- 同 operation 异 request fingerprint、异 commit SHA-256、异 project/job 绑定、除精确可复用 object 外的部分既有记录、缺失 receipt 和过期 lease:失败关闭且零新增写入。
- queue job 的 `source_entity_id` 与结果项目不同、或 `source_resource_id` 不属于同 owner / project:失败关闭且零新增写入。
- Provider 成功后原子持久化确定失败:有效 lease、当前计费 attempt 退款与 job `failed` 在同一事务内成功或回滚;已 completed 或过期 lease 失败关闭且不退款。External 角色动作/视频成功结果保留稳定素材引用。
- legacy 与 structured canvas、dialog 已删除、无 project/asset folder、单产物、多产物、64 切片和角色动作序列均覆盖。
- 图片、修改、背景移除、图集、UI 提取、角色动作、视频、音效、背景音乐的生产路径不得再出现 `create resource -> create asset -> save canvas -> complete job` 分段组合。
@@ -31,17 +31,17 @@
- Windows AppData 安全迁移:首次创建客户端 AppData 时必须以进程 `TokenUser` SID 显式设置 owner,并写入当前用户私有 DACL,不能把可能为 Administrators 的 `TokenOwner` 当作用户身份。发现历史目录 owner 不属于当前 `TokenUser` 时,不在原目录上放宽权限,而是拒绝 reparse point / junction / symlink 后,将旧目录原子重命名到同级唯一 `.owner-mismatch-backup-*` 备份,再新建并验证当前用户 owner 与私有 DACL;迁移或备份失败必须失败关闭,不覆盖旧配置。
- Windows Runner 私有文件初始化:父 AppData 已归当前 `TokenUser` 后,新建 `agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。固定 stale lock 只有在父目录已验证为当前用户 protected 私有 DACL、Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许修复;其它三类文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
- 启动诊断:独立 release 的 `startup.log``agent-runner.log` 只记录有界、脱敏的阶段与 stdout / stderr 摘要,凭据、AppData 路径和其它绝对路径不得原样落盘;单文件达到 256 KiB 后只轮转保留一份 `.previous.log``startup.log` 优先写独立 AppData,目录不可写时回退到系统 TEMP 下的 `Genarrative-Game-Chat-Diagnostics`Tauri context、窗口 URL、AppData、Runner 或 `.setup()` / `.build()` 初始化失败时,Windows 必须显示可见错误对话框并给出诊断日志位置,不能只在无控制台 release 中静默退出。
- 对话与事件:窗口固定使用 `project-supervisor + autonomous-game-build`,继续复用 active Session、External Runner、持久 conversation、流式回复、same-run steer、工具确认与用户追问。以 `/` 开头的输入必须继续走现有内置命令解析,例如 `/preview` 只能生成 `preview.start` 确认卡,不得作为自主构建任务投递给 Supervisor。界面聚合当前 Supervisor 父 run 及其直接委派专业 Agent 的最新原始事件,按时间倒序稳定去重并标注 Agent;默认显示 4 条,可展开至最新 20 条。原始 `summary / detail` 仍只作 Runtime 状态投影,不直接写入 conversation。需要进入聊天的事件必须由 Rust 同步生成唯一 `eventId` 与安全 `publicText`;前端只按这两个字段形成独立 assistant 消息,无 `eventId`、空 `publicText`、legacy 事件和内部 tool / Provider / Runner 协议一律忽略。
- 对话与事件:窗口固定使用 `project-supervisor + autonomous-game-build`,继续复用 active Session、External Runner、持久 conversation、流式回复、same-run steer、工具确认与用户追问。以 `/` 开头的输入必须继续走现有内置命令解析,例如 `/preview` 只能生成 `preview.start` 确认卡,不得作为自主构建任务投递给 Supervisor。game-chat 的自主链路中,Supervisor 持久化意图后只有 `code-prototype` 是主 Agent;它可能临时委派一个受限美术 child,后者只写 `assets/**`,回执返回同一主 Run 后由主 Agent 接入与验收。界面聚合当前 Supervisor 父 run、单主 Agent 及其直接美术 child 的最新原始事件,按时间倒序稳定去重并标注 Agent;默认显示 4 条,可展开至最新 20 条。原始 `summary / detail` 仍只作 Runtime 状态投影,不直接写入 conversation。需要进入聊天的事件必须由 Rust 同步生成唯一 `eventId` 与安全 `publicText`;前端只按这两个字段形成独立 assistant 消息,无 `eventId`、空 `publicText`、legacy 事件和内部 tool / Provider / Runner 协议一律忽略。
- 公开消息硬门:模型仍负责 Supervisor / 专业 Agent 回复的业务语义,Runtime 不根据 tool 或 Provider 事件自行补写业务结论;但用户直接投递的 Project Supervisor 根后台任务必须先落为不可执行的 `preparing / public-status-pending`,再以 `runtime-public-status-*` 稳定 message ID 把“任务已接收,正在启动处理”写入项目 conversation,成功后才转为 `pending / queued`;恢复预检只读,只能在验证到同 run accepted 消息后把该任务临时分类为可恢复,真实 resume 持有 Agent 锁后才可持久提升为 `pending / queued`;写入失败则落为 `failed / public-status-write-failed`,不得继续执行。这些 Runtime 公开状态只供 UI 展示,prompt 构建器必须按稳定前缀排除。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,必须在 task、event、state 等其它终态投影之前先幂等写入一条脱敏、用户可理解的失败消息;专业 Agent 命中该全局硬期限时,也必须通过权威 Run Profile 和根 task 将同一根终态写入项目 conversation,同时保留 child 私有 Session 状态;状态文件本身写坏也不能导致零公开结果。当前 Runtime 自称根 agent/run 时,其 session 和两个 parent 字段必须与权威根 task 一致;任一身份冲突必须失败关闭,不得以另一 session 派生第二条项目终态。前端把该前缀识别为 Runtime-owned,同秒时排在触发它的 Supervisor 用户消息之后,不二次持久化;仅根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted` 只保留在 Runtime 详情和进度投影中,不能再生成第二条聊天消息,专业 Agent 的公开启动事件仍可见。该硬门不改变 final-reply 的唯一性;非 Supervisor 专业 Agent 的失败消息继续留在对应 Agent Session,不把私有诊断写进项目 conversation。
- 启动恢复和续跑边界:本条取代上一条中“只有 accepted 才可恢复”的窄口径。若进程在 Supervisor 用户消息已持久、accepted 未持久之间崩溃,只读 preflight 可以把该 `preparing` 识别为可恢复,但不改写 task/conversation;真实 resume 持有 Agent 锁后必须先幂等补写 accepted,再提升为 `pending / queued`。用户消息或 accepted conversation 已落盘而辅助审计失败时,以 conversation 为公开真相继续入队,不留下“已接收但永不执行”的任务;根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。receipt / isolated-join 等带 parent 的 Supervisor continuation 不再另写 Session 终态,只保留单一后端公开事件;`runtime-task-*``runtime-public-status-*` 共享同 run 的不透明关联摘要,秒级时间戳下多个连续任务必须按实际 run 对应的 `user -> accepted -> terminal` 顺序交错展示。
- Supervisor 进度播报:聊天消息流内保留且只保留一条当前 run 的 Runtime-owned 播报卡,由客户端从 manifest 任务图、Supervisor 结构化计划、`loopIteration`、当前动作、直接委派专业 Agent 及其持久事件确定性整理;显示当前轮次、任务 / 计划进度、活跃 Agent、最近试玩与静态检查、返工决定、代码修改和截图检查证据。同一 run 原位更新,切换 run 时替换,不调用额外模型、不追加持久 conversation,也不改变最终 assistant 回复的唯一性;任意详情必须有界且不展示绝对路径、Provider 元数据或内部指纹。
- Supervisor 进度播报:聊天消息流内保留且只保留一条当前 run 的 Runtime-owned 播报卡,由客户端从 manifest 任务图、Supervisor 结构化计划、`loopIteration`、当前动作、直接委派专业 Agent 及其持久事件确定性整理;显示当前轮次、任务 / 计划进度、活跃 Agent、最近试玩与静态检查、返工决定、代码修改和截图检查证据。同一 run 原位更新,切换 run 时替换,不调用额外模型、不追加持久 conversation,也不改变最终 assistant 回复的唯一性;任意详情必须有界且不展示绝对路径、Provider 元数据或内部指纹。运行详情弹窗在项目或 run 身份切换的同次提交中同步关闭,不能由延迟 effect 关闭用户在新 run 状态可见后刚打开的弹窗。
- ready-task 启动活性:`background_task.queued``autonomous_ready_task.scheduled`、Runner heartbeat 或执行锁已移交都不等于 child 已启动。实际持有执行权的 Runner 必须在释放项目写锁后同步写入 child 的 running task、`turn.started` 与 started journal,再把已启动 state 和 per-Agent 执行锁交给已确认开始轮询的独立 execution worker;同步启动或 worker 接管失败时,要在仍持有执行锁期间依次把 child 和 manifest Graph 节点明确落为 failed,再释放锁并让 parent 收到调度错误。`autonomous_ready_task.scheduled` 只作诊断审计,其写入失败不能阻断 durable child 启动;external client 只 wake Runner,不在客户端抢占执行。Supervisor 进度卡通过 durable `startedAt`(旧 Run 从完整 task journal 恢复,最新 task-record fallback 保持 0)显示真实持续时间,并以父 Run 与当前关联专业 Agent 的最大事件时间计算运行态活跃度:运行超过 5 分钟无新事件时显示“运行中 · 疑似停滞”和静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 与 paused 不误报。父 Run terminal 后,持续时间冻结在父 Run 自身最后活动,不随 child 晚到收口事件增长。消息时间统一校验为 JavaScript 可表示的 Date;越界值显示“时间未知”且不写无效 `datetime`。实时回复只显示 response stream 自己的 `updatedAt`,缺失时同样显示“时间未知”,不能借用其它 Runtime 活动时间或随前端时钟漂移。该提示只提供可观测性,不改变 Runtime/manifest 正式状态。
- ready-task manifest 漂移:父 Supervisor 必须分别判断“能否调度新节点”和“是否存在必须等待的工作”。派生视觉需要父规划修复时不再调度新 child,但当前最新且活跃的根 Run 下,只要存在确定性 runId、scheduler source、正确父绑定且 durable journal 为 queued/running 的 ready child,父 Run 就保持 `waiting-for-manifest-tasks`,不能因旧 hydration 快照把 manifest running 覆盖成 pending 而提前 fixed-graph-stalled。game-chat child 可在相同严格身份下容忍 pending 漂移;正式产物、Canvas、revision、`game.static_smoke``preview.validate` 门禁不放宽。GUI/CLI、旧父 Run、终态、确认/用户输入/reconciliation、伪造绑定或非确定性 runId 全部失败关闭;更新根 Run 后旧 child 不得继续维持新 DAG 或投影完成。
- Supervisor 主导的条件美术路由:game-chat 的关键词、用户是否报告“美术未接入”、占位状态和当前资产探测只形成 `advisoryOnly=true` 的补充上下文,不得直接重置 Graph、预完成美术节点、选择复用/生成分支或继承历史试玩类型。当前根 Run 没有持久化 Supervisor 决策时,scheduler 不启动任何 manifest childSupervisor Provider 必须先通过 auto-safe 的 `agent.route_manifest` 选择 `audit-existing-first` 或用户明确要求整体重做时的 `regenerate-art`。决策后首波只开放 `design-director + code-director``code-director` 必须先成功调用 `asset.list`,再以同一工具提交 `use-existing-art / generate-missing-art / regenerate-art` 与精确 `missingAssetSlots`。Runtime 只负责校验 root/child 身份、当前 revision、Canvas 登记、私有图集合同、四张语义切片art manifest、覆盖 fingerprint 和路由 fingerprint,并据此把 `art-director / art-asset-plan` 投影为 completed 或 pending;缺少 art spec 若同时使图集引用合同失效,两个槽位都属于真实缺口,不能为了少调一个 Agent 伪称图集可复用。只有持久路由明确复用 `art-asset-plan` 时,根完成门才忽略 `assets/manifest.art.json(unchanged-from-run-baseline)`;其余产物、可见代码使用与试玩门禁不放宽。明确 `regenerate-art` 时两个正式图片 owner 使用严格绑定当前 root Run 的原位替换授权。纯“继续”仍走既有正式 continuation 合同普通美术措辞不得借用更老项目的具体试玩场景。不得以增加 loop 预算、伪造 revision、机械改写 manifest 或重放历史图片 action 代替 Supervisor 决策和程序侧审计。
- Supervisor 持久决策与单主条件美术:game-chat 的关键词、用户是否报告“美术未接入”、占位状态和当前资产探测只形成 `advisoryOnly=true` 的补充上下文,不得直接重置 Graph、预完成美术节点、选择复用/生成分支或继承历史试玩类型。当前根 Run 没有持久化 Supervisor 决策时,scheduler 不启动任何 childSupervisor Provider 通过 auto-safe 的 `agent.route_manifest` 提交 `game-chat-workflow-decision.v2``intentSummary` 是 Supervisor 自行理解并持久化的用户意图,`strategy=audit-existing-first` 只是固定安全执行策略,两者不得混用。此动作不能审计、生成、委派或替代后续判断,也不能把整体视觉重做解释成整套美术的强制重生成;成功后 Runtime 只启动唯一 `code-prototype` 主 Agent。升级恢复时严格校验 v1 sidecar 的旧 fingerprint,并从完成合同绑定的有效任务恢复 `intentSummary`;旧 `code-director` coverage/route 只作为迁移输入,不作为当前完成证据,必须由同一根 Run 的 `code-prototype` 重新 `asset.list` 后原位替换为单主合同。确定性 `code-prototype` Run 仅兼容已知 canonical task 文本版本,其余 task/binding/root 身份继续失败关闭;升级前已运行的 fixed-graph 美术 child 不再具备任何 mutation 或生图权限。主 Agent 必须以当前正式资产、Canvas 登记、私有图集合同、四张语义切片art manifest 判断真实缺口;完整覆盖时直接接入,不得生成或扣费。只有可证实缺失 `art-spec` 或核心 spritesheet 时,主 Agent 才可对相应 `art-director``art-asset-plan` 建立一条 durable 委派;每次最多一个活跃美术 child,child 仅可写 `assets/**`,不得修改 `game/**` 或接入/验收游戏。若两个槽位都缺失,必须先完成 `art-director`,由同一主 Run 认领其 `EvidenceReady` delivery 后,才能委派依赖规范图的 `art-asset-plan`;失败或未就绪 delivery 不得消耗不可重试的图集委派槽位。主 Agent 认领必要回执后继续同一 Run 完成素材接入、原玩法语义校验、`game.static_smoke` 与桌面/移动 `preview.validate`。绝对硬截止对嵌套美术 child 继续核验 `root -> code-prototype -> agent-delegate` 完整身份并保留未知外部生成的 reconciliation 证据。Runtime 只负责校验根/父子身份、当前 revision、路径、Canvas 登记、缺口/路由 fingerprint、写入范围及完成证据;纯“继续”仍走既有正式 continuation 合同普通美术措辞不得借用更老项目的具体试玩场景。不得以增加 loop 预算、伪造 revision、机械改写 manifest 或重放历史图片 action 代替 Supervisor 决策和程序侧审计。
- ready-task 对账取消续跑:未知工具结果仍停在 `needs-reconciliation` 且禁止自动重放;人工核对后显式取消原 child,保留 cancel tombstone,旧 child 和旧父 Run 按真实终态收口。若随后创建同 Session、同 Supervisor source、同有效任务语义的 continuation,新完成合同只对同时具有历史 `failed / needs-reconciliation`、最终 `cancelled` 和 durable tombstone 的 ready-task,把当前 manifest 对应 failed 节点恢复为 pending,并由 scheduler 创建全新 child Run。manifest 的读取、failed 筛选、每任务一次的 child journal 索引、证据重验和写回必须位于同一项目写锁域;较新的无 child 根 Run 只有在 durable journal 精确表明为旧 failed Graph 在进入调度前即失败时才能跨过,scheduler 自身失败必须阻断借用更老 tombstone。普通失败、无 tombstone、不同 source/Session/任务语义或证据冲突均保持失败关闭;不得复活旧 pending action、补造 observation 或把取消任务标成 completed。
- 完成门静态分析预算:Canvas 视觉门必须先做只会提前拒绝的词法预检。经典或模块脚本同时不含大小写精确的 `import``export` 字节序列时,不运行模块依赖语义分析;纯 `export ... from` / `export * from` 仍须进入正式模块图分析。当前脚本不含目标文件名或任一已绑定 DOM 图片元素 ID 时,先低成本解码 `\\xNN``\\uNNNN``\\u{...}`、简单转义和续行;解码后仍无候选才不运行完整 Canvas alias / 函数可达性分析,解码不确定则保守进入 Oxc。存在任一候选时仍执行原 parser、semantic binding、解码后的 computed 属性/StringLiteral 路径、可达 `drawImage`、可见 Canvas、路径大小写和动态 namespace 写入门禁;HTML 中存在某个绑定元素不得使所有无关 JavaScript 单元进入重分析,禁止把词法命中当作通过条件。
- Provider 故障展示:Provider retry 的“是否可重试”继续使用 `upstream-5xx` 等稳定类别判断,但 durable retry record 保留安全的精确 `upstream-<HTTP status>` 身份。等待态必须从真实 record 显示 HTTP 状态、`nextAttempt/maxRetries` 与当前持久退避剩余秒数,例如“Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 8 秒后重试”;不得以动画或前端自增计时伪造 attempt。重试耗尽的 Runtime 私有错误只保存 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState`,前端和持久 conversation 仅在字段顺序、范围、状态一致且无尾随正文时派生“上游服务返回 HTTP 503;自动重试已耗尽(3/3)”;其它错误使用固定安全摘要。Provider 响应正文、URL/query、凭据、本地绝对路径、fingerprint、字符数和 `[redacted ...]` 占位符均不得进入用户可见消息。
- 跨轮阶段记录:game-chat 父 run 进入真实 completed / failed / cancelled 终态后,客户端等待 `design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 七项首版任务也全部投影到 completed / failed 终态,再把本轮、任务 / 计划完成度、最新试玩 / 静态检查、最近返工决定和已登记成果图片路径整理成一条 `【Supervisor 阶段记录】` 项目 assistant 消息。父 run 先终态而 manifest 仍在 hydration 时不得陈旧 `0/7` 提前归档,要暂存终态 Runtime 并在 manifest 刷新后重试。页面初始 hydration 若直接读到缺少阶段记录的真实终态 run,也必须补写,但 `idle` 不是可归档终态。每个“项目 + 父 run”最多追加一次,进入现有 `conversation.write` 权限与项目 conversation 持久化链路,下一轮及重载后继续保留。阶段记录不是 Supervisor Runtime 正式回复,不写入 Agent Session、不增加 final assistant 数量,也不逐条复制原始事件或内部正文。
- 跨轮阶段记录:game-chat 父 run 进入真实 completed / failed / cancelled 终态后,客户端等待唯一 `code-prototype` 主 Run 及其所有必要美术委派都已形成真实终态,再把本轮、主 Agent 进度、是否复用/补齐素材、最新试玩 / 静态检查、最近返工决定和已登记成果图片路径整理成一条 `【Supervisor 阶段记录】` 项目 assistant 消息。父 run 先终态而 child 或 manifest 仍在 hydration 时不得陈旧快照提前归档,要暂存终态 Runtime 并在状态刷新后重试。页面初始 hydration 若直接读到缺少阶段记录的真实终态 run,也必须补写,但 `idle` 不是可归档终态。每个“项目 + 父 run”最多追加一次,进入现有 `conversation.write` 权限与项目 conversation 持久化链路,下一轮及重载后继续保留。阶段记录不是 Supervisor Runtime 正式回复,不写入 Agent Session、不增加 final assistant 数量,也不逐条复制原始事件或内部正文。
- 图片成果:当前 manifest 新增或恢复已登记的 PNG / JPEG / WebP 资源时,聊天消息流同步显示 Runtime-owned “Supervisor 成果图片”卡,最多展示最新 4 张并随 manifest 原位更新。图片必须通过现有 `read_local_project_image_preview` 读取,只允许当前授权项目中 `assets/` 下的已登记资源,继续执行 `file.read` auto 权限、真实格式、大小、尺寸、普通文件、祖先目录和项目根边界校验;前端只接受返回路径、媒体类型和 `data:` 前缀与请求完全一致的结果。缩略图点击后使用独立模态查看器,支持按钮与滚轮缩放、指针拖拽、双击 / 按钮复位、Esc / 按钮 / 遮罩关闭,移动端占满视口;不得在聊天卡下方追加展开区。图片卡不写入 conversation,不解析 assistant 文本中的任意 Markdown / 绝对路径,也不开放 `.agent` 验收截图读取。
- Run 接管:External Runner 模式下首次提交可能返回“旧 canonical state + 新 `acceptedRunId`”;页面必须以 `acceptedRunId` 作为本轮权威身份,在 state 尚未切换时显示“已投递,正在同步 Agent Runner”,并允许该 run 的 Tauri event 或轮询结果接管。不得把旧 idle state 当作本轮结果、过滤新 run 事件,自动预览授权也必须绑定 `acceptedRunId`
- 运行容器:当前项目没有由 Tauri 客户端 `PreviewRegistry` 返回的有效 `running` 预览时,页面只渲染聊天,不显示游戏区域或占位文案,顶部运行状态必须明确显示“预览未启动”,不得再使用含义不明的“未启动”;预览运行后自动显示 iframe,桌面端按“游戏 2 / 聊天 1”分栏,移动端改为上下布局。预览停止、失败或切换项目后立即移除 iframe。运行容器继续只接受当前授权项目的 `http://127.0.0.1:*`,复用现有 CSP、iframe sandbox、autoplay、fullscreen 和 gamepad 约束;远程 URL、`file://`、手填地址或陈旧 manifest 状态均不得显示。
@@ -59,27 +59,27 @@
## 2026-07-31 game-chat 输出、单轮预览与平台美术资源
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;七任务目录中本轮实际启动的专业 Agent,其 `requestKind=final-reply``status=ready|committed` 的非空安全回复分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。条件路由跳过的美术节点只要求 manifest 正确投影为 completed不伪造 Agent 回复。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;本轮唯一主 `code-prototype` 与实际按缺口启动的美术 child,其 `requestKind=final-reply``status=ready|committed` 的非空安全回复分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。现有素材完整时不伪造美术 Agent 回复。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出中的 `eventId + publicText` 只指需要独立进入聊天的进度事件;`turn.started` 和根 Run 终态失败事件由上一条 `runtime-public-status-*` 硬门覆盖,不得同时转成事件消息。专业 Agent child 的失败消息继续留在其 Agent Session,根项目聊天只接收 Supervisor 终态失败、明确公开进度和安全 final-reply,避免一项失败被 Runtime event 与 conversation 各播报一次。
- 单轮收束:game-chat source 只生成至 `preview-playtest` 的 manifest seed task,试玩完成后父 Run 直接进入完成门,不调度 `publish-strategy` / `publish-package``agent.schedule_ready` 必须按当前 Supervisor Run 的持久 source/profile 选择同一 source-aware scheduler,不能绕过该边界。`task.list` 对同一 root source 必须从任务行、readyTaskIds 和统计中排除两个发布节点,`agent.delegate` 也必须按 root binding 拒绝直接委派这两个节点,不能让 Provider 用“读取完整 DAG 后手工委派”恢复已裁掉的发布阶段。完成门满足且 collaboration、Provider batch、进程会话、视觉资源等非验证屏障全部清零后,Runtime 必须用确定性回复直接收束结构化计划并结束父 Run,不再请求下一次 Provider 工具计划。普通 GUI / CLI 仍执行完整发布 DAG。
- 轮次展示:`loopIteration` 只是同一父 Run 内的 Provider / 工具规划循环,用于委派、回执、返工和验收,不是用户发起的游戏生成轮次。game-chat 的进度卡、当前工作和“最新状态”事件统一显示“本轮”,整个页面不向用户显示“第 N 轮”;完整 GUI / CLI pre-publish 任务图仍可显示 `x/14`但首版只按七项任务显示 `x/7`(详见 2026-08-03 小节),不得把两个发布节点计入任一分母。父 Run 终态后移除运行中进度卡,只保留终态阶段记录与预览。
- 平台美术资源:game-chat 保留正式视觉 DAG,但是否进入生成节点由 2026-08-06 的持久条件路由决定。现有正式资源覆盖完整时直接复用;存在真实缺口或 Supervisor 明确选择整体重做时,`art-director` 通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png``art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json``code-prototype` 必须等待复用或补齐后的图集与切片清单,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 单轮收束:game-chat 只在 `code-prototype` 完成当前 revision 的接入、静态 smoke 与 desktop/mobile 试玩后进入完成门,不调度 `publish-strategy` / `publish-package` 或旧固定验证节点`agent.schedule_ready` 必须按当前 Supervisor Run 的持久 source/profile 选择同一 source-aware scheduler,不能绕过该边界。`task.list` 对同一 root source 必须从任务行、readyTaskIds 和统计中排除发布节点以及不属于单主 route 的固定 DAG 节点,`agent.delegate` 也必须按 root binding 拒绝直接恢复这些节点。完成门满足且美术 delivery、Provider batch、进程会话等非验证屏障全部清零后,Runtime 必须用确定性回复直接收束结构化计划并结束父 Run,不再请求下一次 Provider 工具计划。普通 GUI / CLI 仍执行完整发布 DAG。
- 轮次展示:`loopIteration` 只是同一父 Run 内的 Provider / 工具规划循环,用于委派、回执、返工和验收,不是用户发起的游戏生成轮次。game-chat 的进度卡、当前工作和“最新状态”事件统一显示“本轮”,整个页面不向用户显示“第 N 轮”;完整 GUI / CLI pre-publish 任务图仍可显示 `x/14`game-chat 只显示单主及必要美术 child,不显示固定七项分母。父 Run 终态后移除运行中进度卡,只保留终态阶段记录与预览。
- 平台美术资源:game-chat 不再执行固定视觉 DAG。现有正式资源覆盖完整时,单主 `code-prototype` 直接复用;只有它的 `asset.list` 审计证实真实缺口时,才临时委派相应美术 owner。`art-director` 通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png``art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json`;两者只可写 `assets/**``code-prototype` 必须认领复用或补齐后的结果,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 验证:前端运行时模型定向测试、Rust completion/source/asset 合同测试、`cargo fmt --check``npm run check:encoding``git diff --check` 必须全部执行;Windows 文件锁竞态只可作为既有测试失败单独记录,不得将其改写为本次改动的通过证据。
## 2026-08-01 game-chat 首版七任务素材完整快车道与美术硬门
## 2026-08-07 game-chat 单主素材审计、按需美术委派与自验收
- 首版任务边界:game-chat 首版只展示 `design-director``art-director``art-asset-plan``code-director``code-prototype``preview-readiness``preview-playtest` 七项任务,进度统一显示为 `x/7`;当前根 Run 在 Supervisor 持久路由前零 child`audit-existing-first` 决策后的首波只激活 `design-director + code-director`。code-director 审计后,完整资源把两个美术节点投影为 completed;真实缺口或整体重做才按依赖开放对应美术 owner,随后 `code-prototype` 与验证节点串行推进。不把完整 GUI / CLI 任务图的其它节点投影到该页面,也不显示内部 Provider / child loop 轮次。
- 单主任务边界:game-chat 的根 Run 在 Supervisor 持久路由前零 child;决策后只启动 `code-prototype`。它先 `asset.list` 审计,再视真实缺口临时委派一个受限美术 child;现有资源完整时零美术委派,用户明确整体重做也不能绕过审计或把生成变成固定规则。美术 child 只写 `assets/**`,回执由主 Agent 认领并恢复同一 Run;主 Agent 随后接入素材、执行静态检查和 desktop/mobile 试玩。game-chat 不投影或调度 `design-director``code-director``preview-readiness``preview-playtest` 等固定节点,也不显示内部 Provider / child loop 轮次。完整 GUI / CLI 的 16 节点 DAG 不受影响。
- 时间预算:从 game-chat 父 Run 接受用户请求开始,素材完整首版使用 `4200` 秒软预算;父 Run 与其全部 child Run、等待和回收阶段共享从 root `bound_at` 计算的 `4500` 秒绝对硬上限。该值来自现有两次串行生成各自最长 35 分钟的客户端等待合同,并为代码兜底、静态检查和双视口试玩保留 5 分钟。整个 Runtime pass 受同一 `timeout_at` 约束;硬上限内未通过完成门必须失败关闭,不得为了守时跳过图集、换普通生图或回退纯代码核心画面。即使完成证据恰好在上限后到齐,单轮确定性收束也必须再次检查累计预算并拒绝写入 `single_round_converged`
- Provider 次数:Supervisor 先用一个 Provider turn 理解目标并持久化条件路由;`audit-existing-first` 的首波仅请求 `design-director / code-director`code-director 成功 `asset.list` 并提交覆盖合同后,Runtime 才确定性派发真实缺口对应的 `art-director / art-asset-plan`。完整复用不得调用图片生成接口;图集复用或登记完成后 `code-prototype` 最多执行一次 Provider 首版写入请求。软预算耗尽时只允许使用已登记图集和当前 resourceId 切片清单的确定性本地兜底,Provider 成功返回后由 Runtime 依次执行确定性的 `game.static_smoke``preview.validate`
- Provider 次数:Supervisor 先用一个 Provider turn 理解目标并持久化条件路由;随后唯一 `code-prototype` 主 Agent 先 `asset.list`,只有审计的精确缺口才建立相应的受限美术委派。完整复用不得调用图片生成接口;图集复用或主 Agent 认领生成回执后,仍由该主 Agent 接入、执行 `game.static_smoke` 和 desktop/mobile `preview.validate`。软预算耗尽时只允许使用已登记图集和当前 resourceId 切片清单的确定性本地兜底,不得由 Runtime 或关键词强制生成图片
- 可玩兜底:软预算或首版 Provider 无法及时完成时,只能为已显式实现真实语义的玩法生成完整、自包含、无远程运行依赖的中文 HTML 模板;未知玩法失败关闭,不能只替换标题后套用固定收集游戏。俄罗斯方块模板必须包含 10×20 棋盘、下落、移动、旋转、锁定、消行和触顶失败;收集模板只匹配明确收集类目标。模板必须从 `ready` 开始,包含真实 Canvas 绘制、`requestAnimationFrame`、键盘 / 触控主要操作、唯一可见且启用的 start / primary-action / restart 控件,状态 JSON 只随真实输入、状态迁移或模拟状态变化推进,并能在 primary-action 后保持 `playing`、在 restart 后稳定回到 `ready | playing`;不得在开始前固定进入 `lost`,不得通过固定失败冒充试玩通过,也不得由纯渲染帧空转 `sequence`。兜底只允许写入缺失或精确初始化占位的 `game/index.html`;存在非占位入口时,当前 `code-prototype` 必须读取并实际 patch,取得本人 `mutationRevision` 后再静态检查和试玩,不得反复用只读 smoke 冒充续作。
- 平台图片:`art-spec.png` 只用于约束色板、材质、形状与后续派生,不是运行时背景、角色、目标或图集。`art-asset-plan` 必须以其稳定资源 ID 走专用 icon-spritesheet 路由,透明像素、generation route/kind、同画布归属和 reference resource ID 继续由 Runtime 验证。完整透明图集在编辑器合同中即使产生 `sliceWarning` 仍可登记,但 game-chat 不能据此猜测 atlas 坐标;图集 resourceId 缺失或空白时必须在任何本地写入前失败,四个切片均须保留与整图完全一致的 `sourceResourceId`,切片数量也必须在正式图集登记前严格等于四,多或少都失败关闭。四类唯一性按尺寸与解码后的规范 RGBA 像素摘要判断,不能用不同 PNG 压缩或 ancillary chunk 冒充不同素材。主图、四张 canonical 切片和切片清单必须共同提交,并把主图/切片摘要及 Canvas 身份冻结到 `.agent/runtime/art-spritesheet-contract.json` 私有回执;后续主图安装、回执或登记失败时恢复旧合同,避免出现旧图集绑定新切片。进程在固定主图落盘后崩溃时,保留的 generation 账本只允许按远端预期摘要幂等补齐同一组合同,路径内容冲突继续失败关闭。全部切片下载累计最多 `32 MiB`。完成门重新读取本地素材时仍执行相同文件大小、解码内存、内容摘要、规范像素摘要、可见 alpha 与四类唯一性校验,并要求公开切片清单、当前 Canvas 登记与私有回执完全一致,后续代码 Agent 不能通过同时改写素材和公开清单重定义合同。`postprocess-failed-source-preserved`、无真实 alpha、缺文件或缺登记同样失败关闭。首版只在 `preview-playtest` 后单轮收束,不进入发布节点。
- 关联验收:快车道必须分别验证 Supervisor 决策前零 child、审计首波只有 design/code、完整覆盖零图片生成、精确缺口只启动对应 owner、显式重做两个正式 owner、`art-asset-plan` 在代码前完成、`x/7` 投影、实际启动的专业 Agent 安全 final-reply、跳过节点正确终态投影、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过action-driven `sequence`,以及当前 revision 的静态 smoke 与浏览器试玩
- 平台图片:`art-spec.png` 只用于约束色板、材质、形状与后续派生,不是运行时背景、角色、目标或图集。`art-asset-plan` 必须以其稳定资源 ID 走专用 icon-spritesheet 路由,透明像素、generation route/kind、同画布归属和 reference resource ID 继续由 Runtime 验证。完整透明图集在编辑器合同中即使产生 `sliceWarning` 仍可登记,但 game-chat 不能据此猜测 atlas 坐标;图集 resourceId 缺失或空白时必须在任何本地写入前失败,四个切片均须保留与整图完全一致的 `sourceResourceId`,切片数量也必须在正式图集登记前严格等于四,多或少都失败关闭。四类唯一性按尺寸与解码后的规范 RGBA 像素摘要判断,不能用不同 PNG 压缩或 ancillary chunk 冒充不同素材。主图、四张 canonical 切片和切片清单必须共同提交,并把主图/切片摘要及 Canvas 身份冻结到 `.agent/runtime/art-spritesheet-contract.json` 私有回执;后续主图安装、回执或登记失败时恢复旧合同,避免出现旧图集绑定新切片。进程在固定主图落盘后崩溃时,保留的 generation 账本只允许按远端预期摘要幂等补齐同一组合同,路径内容冲突继续失败关闭。全部切片下载累计最多 `32 MiB`。完成门重新读取本地素材时仍执行相同文件大小、解码内存、内容摘要、规范像素摘要、可见 alpha 与四类唯一性校验,并要求公开切片清单、当前 Canvas 登记与私有回执完全一致,后续代码 Agent 不能通过同时改写素材和公开清单重定义合同。`postprocess-failed-source-preserved`、无真实 alpha、缺文件或缺登记同样失败关闭。game-chat 首版由 `code-prototype` 在同一主 Run 完成静态 smoke 和试玩后单轮收束,不进入发布节点。
- 关联验收:快车道必须分别验证 Supervisor 决策前零 child、持久路由后只启动 `code-prototype`、主 Agent 成功 `asset.list` 后才可判断缺口、完整覆盖零图片生成/零委派、精确缺口只委派对应 owner、整体重做仍先审计且不产生无关委派、美术 child 对 `game/**` 写入拒绝而 `assets/**` 允许、回执恢复同一主 Run、主 Agent 自行完成接入/静态 smoke/desktop-mobile 试玩、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过action-driven `sequence`。完整 GUI / CLI 的固定 16 节点 DAG 另行保持原有回归
## 2026-08-03 game-chat 开发态同源与持久输出修复
- 开发态启动必须在 Tauri CLI 之前预检固定 `3080`。现有 marker 只包含 API target,不能证明监听器属于当前 worktree;因此只有端口空闲时才允许继续,任何已存在的 AGC Vite、非 HTTP 监听器或其它服务都必须在原生窗口创建前失败关闭。启动器不擅自终止无法证明归属的旧服务,也不得把当前 Rust 壳 / Runner 与其它 worktree 的旧 Vite 前端混用。Tauri CLI 任意退出后,外层启动器必须有界收束已启动的客户端进程树,避免 `beforeDevCommand` 失败后留下假在线窗口。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能进入 Supervisor 决策前零 child 的条件 lane`audit-existing-first` 路由后先运行 `design-director + code-director`,再按覆盖合同选择复用或补齐美术,最后推进 `code-prototype → preview-readiness → preview-playtest`、单轮确定性收束和自动预览。若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的 ready child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source、持久工作流决策和资产路由解析本轮已开放节点,不得硬编码旧三 Director 首波;当前审计首波是 `design-director / code-director`,后续美术、code prototype 与 preview child 仍严格拒绝未授权或不满足依赖的 `Pending` 收束
- 开发态启动必须在 Tauri CLI 之前解析并预检 AGC Vite 最终地址。Linux 使用系统级用户端口段的 `start + 5` 槽位并只在本段内漂移,Windows / macOS 以 `3080` 为兼容首选;启动器通过 `GENARRATIVE_AGC_VITE_PORT` 绑定 `beforeDevCommand` 和配套后端预留,通过 Tauri CLI `--config` 绑定 `build.devUrl`,并通过 Vite CLI `--port` 绑定 `strictPort` 监听。任何竞态中已存在的 AGC Vite、非 HTTP 监听器或其它服务都必须在原生窗口创建前失败关闭。启动器不擅自终止无法证明归属的旧服务,也不得把当前 Rust 壳 / Runner 与其它 worktree 的旧 Vite 前端混用。Tauri CLI 任意退出后,外层启动器必须有界收束已启动的客户端进程树,避免 `beforeDevCommand` 失败后留下假在线窗口。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能进入 Supervisor 决策前零 child 的单主 laneSupervisor 持久 intent 后先运行 `code-prototype`,由它按权威审计结果选择复用或暂时委派受限美术,再在同一主 Run 完成接入、静态 smoke、desktop/mobile 试玩、单轮确定性收束和自动预览。若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的主 Run 或其经授权美术 child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source、持久工作流决策、单主 route 与 child delegation 解析本轮已开放工作,不得从旧七节点图硬编码重启 Director、验证或试玩节点;未授权 child、第二个活跃美术 child,或缺少成功 `asset.list` 审计的美术委派仍严格失败关闭
- 专业 Agent 的非流式 final reply 继续由既有 finalization journal 重建并提交 `responseStream``streaming / ready` 投影仍必须匹配当前项目 revision;已经 finalization 提交的 `committed` 回复以 Agent / Session / run / request slot / response revision 稳定身份为准,不得因后续阶段推进项目 revision 而从 game-chat 查询中消失。
## Runtime 边界
@@ -601,7 +601,7 @@ game-project/
## 当前最小落地
- `apps/ai-game-creator-shell` 是独立 Tauri App,不复用 `apps/desktop-shell`
- 独立客户端启动时先进入平台登录检查;未登录页默认展示手机号验证码登录,并保留密码登录切换。验证码登录调用平台后端 `/api/auth/phone/send-code``/api/auth/phone/login`,密码登录继续调用 `/api/auth/entry`Tauri dev 下 `/api`固定 3080 Vite 代理,发布版静态窗口下登录请求默认直连本机配套 `http://127.0.0.1:8082` API,网络层失败时展示登录服务不可达提示,不裸露 WebView 的 `Load failed`
- 独立客户端启动时先进入平台登录检查;未登录页默认展示手机号验证码登录,并保留密码登录切换。验证码登录调用平台后端 `/api/auth/phone/send-code``/api/auth/phone/login`,密码登录继续调用 `/api/auth/entry`Tauri dev 下 `/api`本轮动态 AGC Vite 代理,发布版静态窗口下登录请求默认直连本机配套 `http://127.0.0.1:8082` API,网络层失败时展示登录服务不可达提示,不裸露 WebView 的 `Load failed`
- `npm run agc` 的本地 SpacetimeDB owner identity 以独立 `spacetimeDataDir` 为作用域,不绑定可能漂移的监听端口;旧端口作用域记录仅在同一 data dir 下身份唯一时自动迁移,出现多个不同旧身份时失败关闭。`.app/dev-stack.json` 必须记录规范化 `spacetimeDataDir`,独立壳只复用数据库名和该目录同时匹配且健康的后端,旧 schema 状态或共享目录状态缺少此字段时不得复用。POSIX 子进程在 `spawn` 返回时立即登记 `error / exit` 生命周期、保存 detached leader 的 PGID 并把句柄交给外层;即使 direct leader 已先退出,也必须继续向负 PGID 发信号清理同组后代。后端 ready 前的 SIGINT、SIGTERM、超时或 ENOENT 都必须走同一进程组清理链路,不能遗留 npm、Cargo 或 SpacetimeDB。非 Linux Runtime 执行 `project.verify` 时,`npm run` 参数校验必须允许受控的 `--silent``--ignore-scripts` 位于脚本名前,并继续拒绝缺少真实脚本名的调用。
- Tauri Rust 入口保持薄壳:`src-tauri/src/main.rs` 只保留共享类型 / 常量、模块声明、CLI preflight、`tauri::Builder`、运行时配置初始化和 `invoke_handler` 清单;命令行入口放在 `cli.rs`Tauri command 包装放在 `commands.rs`,运行时配置与 LLM 配置检查放在 `config.rs`Agent loop 与生成编排放在 `agent.rs`,上传 / 画板 / 平台美术生成接入放在 `assets.rs`,本地项目文件、记忆、对话、权限、checkpoint、manifest 和通用路径工具放在 `project.rs`,本地 HTTP 预览与 preview 命令放在 `preview.rs`,旧窗口兼容命令放在 `windows.rs`Rust 单测放在 `tests.rs`。后续继续拆分时保持 Tauri command 名、JSON 字段、`.agent/*` 路径和错误语义不变。
- 本地项目初始化会创建 `game/``assets/``memory/``memory/agents/``exports/``.agent/logs/`,写入 `.agent/manifest.json`,生成 append-only JSONL 本地项目索引 `.agent/agent.db`,并生成默认 `game/index.html`
@@ -627,7 +627,7 @@ game-project/
- 终端可用 `npm run ai-game-creator-shell:check` 跑 v1 开发验收:壳 typecheck、`platform-llm` 网关测试、共享契约测试、Tauri Rust 测试和无密钥本地 provider 端到端 smoke;已退役的 `platform-agent` 不再进入 workspace 或该门禁。
- 终端可用 `npm run ai-game-creator-shell:agent-run -- /绝对项目路径 "游戏创作需求"` 跑一次真实 LLM 生成、落盘、`game.static_smoke` 和本地 HTTP 预览;发布 App 读取 Tauri 应用配置目录中的 `game-creator.config.json`,开发 CLI 无 AppHandle 时才读取仓库旁边的配置模板和 gitignored 本机覆盖文件,不把 API Key 写入仓库或项目文件。自动验证可加 `--no-wait`,例如 `npm run ai-game-creator-shell:agent-run -- --no-wait /tmp/genarrative-ai-game-test "像素风反弹弹幕厨房"`,生成预览 trace 后立即停止本地预览,避免终端卡在回车等待。默认 API kind 为 `openai_responses`;旧 Chat Completions 兼容网关设置 `llm.apiKind``openai_chat`Anthropic Messages 网关设置 `llm.apiKind``anthropic`。真实 OpenAI-compatible 网关建议设置 `llm.stream``true` 跑 Planner 和 Generator,避免长请求非流式空闲断连。
- 终端可用 `npm run ai-game-creator-shell:agent-run:smoke` 跑一次无密钥本地端到端 smoke:脚本启动本机 OpenAI-compatible SSE 流式测试 provider,预置一个本地上传图片和一个本地上传音频,复用真实 `--agent-run`、Planner / Orchestrator / 角色 agent / Generator / Evaluator loop、本地落盘、`game.static_smoke` 和本地 HTTP 预览,并断言每次 provider 请求都使用 `stream: true`、Planner 与 Generator 分别命中自己的 `agentLlm` provider 配置、provider prompt 收到图片与音频资产上下文以及最近对话上下文、生成 HTML 引用这些资产、预览服务能用 `GET` 读取 `/assets/...`、用 `HEAD` 返回真实资源长度和对应 MIME、headless Chrome 打开预览后至少执行一帧游戏 JS,且通过确定性亮色探针采样证明 canvas 不是空白画布、`.agent/run.latest.json` 的 step group 覆盖 design / balance / art / audio / code / publishing 六组、第二轮会重跑 Evaluator 命中任务及其下游影响任务,未受影响角色 carry-over;随后脚本自动给 CLI 发送回车停止预览。该脚本只用于开发验证,不进入产品生成路径。
- `npm run ai-game-creator-shell:dev` Tauri `devUrl` 固定为 `http://127.0.0.1:3080/`Vite 必须 `strictPort` 对齐;`beforeDevCommand` 先复用已经跑在 3080 且页面标题为 `AI 游戏创作` 的本 app Vite server,否则才启动新的 Vite端口被其它服务占用直接失败并提示释放端口
- `npm run ai-game-creator-shell:dev` 必须经受控启动器解析 AGC Vite 端口;Linux 默认使用用户端口段的 `start + 5`,非 Linux 保留 `3080` 兼容首选。启动器用动态 Tauri `build.devUrl``GENARRATIVE_AGC_VITE_PORT` 和 Vite CLI `--port` 保证 WebView、`beforeDevCommand`、配套后端预留和 Vite `strictPort` 对齐;不得复用无法证明 worktree 归属的现有 Vite最终端口被竞态占用直接失败。
- AI 游戏创作 App 的本地后端使用 gitignored 的 `server-rs/.spacetimedb/ai-game-creator/data`,不复用主站旧 standalone 数据目录。启动器从本地 `/v1/identity` 获取并持久化 API identity,再通过数据目录内 `0600` 的独立 `dev-cli/cli.toml` 发布模块;不得读取或覆盖开发者全局 SpacetimeDB 登录,也不得回退到每次变化的 `--anonymous` 身份。发布失败时 API 和 Vite 不得继续启动旧 schema,避免 `external_generation_job` 等缺表订阅进入持续重试。
- `start-dev-stack.mjs` 在 POSIX 下以独立进程组托管后端和 Vite,关闭 Tauri 或任一子进程失败时必须收束整组;macOS 不注册仅支持 Windows/Linux 的 api-server 进程指标 observable callback,避免每轮指标采集重复输出平台不支持告警。
- Unix 下 Agent DB、External Runner owner 和 tool-plan handoff 的相对句柄复核必须同时比较设备号、inode 和文件类型;`libc::stat``st_dev / st_ino` 先按 Rust `MetadataExt` 的 Unix 口径规范为 `u64` 再比较,保持 Linux 和 macOS 的同一安全语义,不得为了通过 macOS 编译而删除路径替换检测。
@@ -867,7 +867,7 @@ game-project/
- 自动验收现在严格要求 manifest 恰好包含固定 16 个不重复 task ID 且全部为 `completed`,并逐任务核对当前父 Run 下唯一 logical run、一次 started、一次 completed、零 failed / cancelled 和一次 manifest projection;七份基础正式产物存在并满足文件 / JSON / 非占位入口检查,配置画布 API Key 时再增加 `art-spec / ui-prototype / art-spritesheet` 三张图片。PNG 验收不止检查 magic / IHDR / 比例,还会校验 chunk CRC、zlib 解压、scanline 长度、索引色 PLTE 和未知 critical chunk。Runtime 根 Supervisor 的完成合同已升级为 `game-creator-autonomous-completion-contract.v2``baselineArtifacts` 必填并纳入指纹,旧 v1 或缺基线合同失败关闭;最终门禁要求最后一次验证工具是 `game.static_smoke`、状态通过且 `verifiedRevision == currentRevision``preview.validate` 回执必须绑定同一 Agent、run、current revision、当前 `game/index.html` 摘要、固定试玩场景、持久浏览器报告以及 desktop / mobile 两张截图的路径、摘要和 PNG 身份,任一证据缺失、变化、过期或来自其它 run / revision 都阻止最终回复。旧两图合同的确定性证据不替代新三图 DAG 验收;新合同实现后必须新起独立单轮。
- `design-foundation` 已增加专属职责边界:项目文件只允许写 `memory/project.md``game/game_design.md`;配置 External Editor API Key 且合同要求界面原型时,只额外允许固定 `assets/ui-prototype.png`。它不得创建、修改、删除或补丁 `game/index.html`,不得改动其它程序实现、发布、音频或美术素材,也不得调用 `preview.start``preview.validate``game.static_smoke`,或借 `command.exec / command.start / command.run_limited` 启动预览服务、浏览器、Playwright 和桌面 / 移动试玩。程序和质量 Agent 的共享 Runtime 工具合同不因此缩减;有 / 无画布配置和其它 Agent 不受影响的聚焦回归为 `3/3` 通过。
- `canvas.asset_generate.replaceExisting` 默认并必须保持 `false`;只有静态专业 Agent 的 `delegated-*` 唯一 repair run 才能申请 `true`。Runtime 要求当前 delivery 带 `repairOfDelegationId`,原 delivery 已被同一父 Agent / 父 run 认领,原始与返工合同的目标 Agent 和精确 `expectedArtifacts` 路径一致;普通 run、未声明路径、错误 Agent、未认领原交付或缺失原图都失败关闭。图片生成仍服从 `art-director` / `design-foundation` / `art-asset-plan` 的固定输出路径、比例、尺寸、kind 和 label,禁止先删除正式图片;请求前记录旧文件 SHA-256,外部生成返回后在项目写锁内复核,旧图在网络请求期间变化即拒绝覆盖。授权替换先写私有临时文件,再以备份 / rename 切换;落盘或 manifest 登记失败时恢复旧图,不把新旧文件并存状态当作成功。
- 2026-07-28 起,在既有 16-task manifest 内固定正式视觉 DAG,不新增平行任务系统:`art-director` 先通过 `/api/external/v1/editor/images/generations``kind=spec` 生成 `assets/art-spec.png`,并登记为 `assetKind=icon-spec``design-foundation` 使用该规范图的 External Editor 稳定资源 ID 作为 `referenceImageSrcs` 中的视觉规范参考,再通过同一 images 接口的 `kind=ui-design` 生成完整 `assets/ui-prototype.png``art-asset-plan` 以同一 `assets/art-spec.png` 资源 ID 作为必填 `referenceImageSrc` 调用 `/api/external/v1/editor/icon-spritesheets/generations`,传入具体 `iconDescriptions``screenColor=auto`、同名画布 / 素材库与 `canvasCompletion`,生成透明 `assets/art-spritesheet.png``generationInputs.artSpec` 只是辅助结构化上下文,不能代替真实 `art-spec.png`;严禁把 `assets/ui-prototype.png` 当作图集规范图。规范图缺失、未登记为当前画布的 `icon-spec` 或缺少稳定资源 ID 时,两个下游任务均等待 `art-director`,不得退回普通生图。UI extraction 只适用于已有且带红框标注的 UI 设计图,不用于生成完整 UI,也不进入本次 canonical DAG。单波最多 `3` 个静态职责的资源上限保持不变,只调整现有任务的依赖边与就绪顺序。图集返回 `warning` 时以 `postprocess-failed-source-preserved` 源图保留语义失败关闭,不把不透明源图登记为正式 spritesheet,也不自动重试;仅有 `sliceWarning` 时完整透明图集仍可登记,但必须原样保留切片失败原因。客户端下载后还要解码 PNG 并确认至少存在一个 alpha 小于 255 的像素,未形成真实透明像素时拒绝落盘和 manifest 登记。
- 2026-07-28 起,在既有 16-task manifest 内固定正式视觉 DAG,不新增平行任务系统:`art-director` 先通过 `/api/external/v1/editor/images/generations``kind=spec` 生成 `assets/art-spec.png`,并登记为 `assetKind=icon-spec``design-foundation` 使用该规范图的 External Editor 稳定资源 ID 作为 `referenceImageSrcs` 中的视觉规范参考,再通过同一 images 接口的 `kind=ui-design` 生成完整 `assets/ui-prototype.png``art-asset-plan` 以同一 `assets/art-spec.png` 资源 ID 作为必填 `referenceId` 调用 `/api/external/v1/editor/icon-spritesheets/generations`,传入具体 `iconDescriptions``screenColor=auto`、同名画布 / 素材库与 `canvasCompletion`,生成透明 `assets/art-spritesheet.png``generationInputs.artSpec` 只是辅助结构化上下文,不能代替真实 `art-spec.png`;严禁把 `assets/ui-prototype.png` 当作图集规范图。规范图缺失、未登记为当前画布的 `icon-spec` 或缺少稳定资源 ID 时,两个下游任务均等待 `art-director`,不得退回普通生图。UI extraction 只适用于已有且带红框标注的 UI 设计图,不用于生成完整 UI,也不进入本次 canonical DAG。单波最多 `3` 个静态职责的资源上限保持不变,只调整现有任务的依赖边与就绪顺序。图集返回 `warning` 时以 `postprocess-failed-source-preserved` 源图保留语义失败关闭,不把不透明源图登记为正式 spritesheet,也不自动重试;仅有 `sliceWarning` 时完整透明图集仍可登记,但必须原样保留切片失败原因。客户端下载后还要解码 PNG 并确认至少存在一个 alpha 小于 255 的像素,未形成真实透明像素时拒绝落盘和 manifest 登记。
- 旧项目已有同路径派生图但缺少上述 provenance 时,一律标记为 legacy,不得只因文件、kind 或通用视觉检查存在就完成。原位替换仍走显式 repair:`design-foundation``art-asset-plan` 先在同一 Supervisor 批次分别建立 owner 精确原合同并交付 `needs-repair`,父 run 认领后再在同一批次分别发起各自唯一 repair;两个 repair 合称一个显式视觉返工阶段。`art-director` 不得跨 owner 声明或替换 UI / spritesheetRuntime 在委派落盘前就拒绝这类合同,不再等到生图阶段才失败。
- 2026-07-27 新起的“16 任务正式产物 + 两张真实画布图片 + current revision 静态 / 双视口浏览器 / PNG 证据 + 受限 repair 替换”独立外部 Provider 验收,使用 `npm run agc:test:chat -- --timeout-minutes 75`,约 `59m50s` 后以退出码 `0` 完整 **PASS**。同一轮真实生成并登记 `assets/ui-prototype.png``2829418` bytes)与 `assets/art-spritesheet.png``1361906` bytes),固定 `16` 个 manifest task 均为当前父 Run 下唯一 logical run、一次 started、一次 completed、零 failed / cancelled 和一次 manifest projection;七份基础正式产物、两张 PNG、当前 revision 的 `game.static_smoke`、desktop / mobile `lane-defense-v1` playtest、浏览器报告与截图全部通过。`turn.report=settled` 且唯一 assistantbusy / pending / running / confirmation / user-input / reconciliation 均为 `0`;隔离 Runner、一次性项目和隔离 AppData 已自动清理。此前失败轮继续独立保留,不与本轮拼接;未来合同变化仍须新起完整轮次复验。
- 2026-07-27 补充 tool-plan 成功响应交接的内容边界:Provider 的自然语言计划叙述,以及结构化 arguments 中 `body / code / content / css / html / newText / oldText / patch / script / text` 等源码内容字段,只检查真实密钥 token 形状、凭据头标记和不安全控制字符;仅仅提及 `.env``game-creator.config` 不能阻断已经计费的安全响应。结构化输入中的敏感 JSON key、非内容字段中的配置痕迹或绝对路径、真实 token、容量、thinking、身份、顺序和账本完整性门禁仍失败关闭。成功 handoff 失败进入 reconciliation 时,Runtime 额外只持久化受控 `failureKind`、脱敏错误 SHA-256 和字符数,不保存 Provider 正文、function arguments、密钥或绝对路径。定向回归覆盖叙述/源码字段放行、`.env.local` 路径和真实 token 拒绝、全部 tool-plan handoff 回归及诊断零正文。
@@ -905,7 +905,7 @@ game-project/
## 2026-07-31 长耗时与恢复收口
- `autonomous-game-build` 的固定 manifest DAG 是唯一缺省首轮专业执行链。缺省 collaboration policy 不再额外强制 `code-prototype / quality-review / art-*` 静态首波,Runtime 也不再按 Editor Key 或已有图片偷偷追加 Agent。2026-08-03 起,显式项目 policy 或旧 batch 恢复若进入首批 `agent.delegate` 兜底,同样只能激活 `design-director / art-director / code-director`,不得提前激活底层 Agent;这条兜底不能在默认 manifest 前复制同职责委派。
- 除持久 `project-supervisor-game-chat` 单主 route 外,`autonomous-game-build` 的固定 manifest DAG 是唯一缺省首轮专业执行链。缺省 collaboration policy 不再额外强制 `code-prototype / quality-review / art-*` 静态首波,Runtime 也不再按 Editor Key 或已有图片偷偷追加 Agent。2026-08-03 起,显式项目 policy 或旧 batch 恢复若进入首批 `agent.delegate` 兜底,同样只能激活 `design-director / art-director / code-director`,不得提前激活底层 Agent;这条兜底不能在默认 manifest 前复制同职责委派。game-chat 例外只允许持久路由后的 `code-prototype`,以及该主 Run 经审计后临时建立的受限美术 delivery。
- `preview-readiness` 只有在自己的 child run 持有当前 project revision 的 `game.static_smoke=passed` 凭证后才能完成;`preview-playtest` 作为根 Supervisor 的直接 manifest child,必须解析并写入根完成合同的当前 revision browser receipt,报告、desktop/mobile 截图及摘要复核通过后才能投影 manifest completed。
- 浏览器未发现、临时环境不可建、启动超时或在 WebSocket URL 解析前退出统一分类为 `preview-infrastructure-unavailable`。首个持久 observation 后收束当前 action batch并失败结束 child/root run,禁止继续用 Provider 逐轮规划同一 revision 的重复启动;普通页面/玩法验收失败仍保留为业务失败,不混入基础设施分类。
- game-chat release 在 `CloseRequested / ExitRequested` 前复用 Runner durable idle probe;只要存在 process session、pending/finalization/provider/tool-plan handoff 或非终态 Agent queue/phase,就阻止关闭并提示先完成、暂停或取消。不可撤销的最终 `Exit` 不再作为唯一保护点,Windows Job Object 的 child-owned 安全边界保持不变。
@@ -924,7 +924,7 @@ game-project/
- `.agent/manifest.json` 的存储写边界使用同目录持久文件锁跨线程、跨进程串行化;锁必须覆盖旧 manifest 读取、不可变版本前缀校验、临时文件安装和安装后回读一致性校验。锁文件拒绝符号链接、非普通文件和异常所有权 / 硬链接;Windows 使用不共享写句柄,Unix 使用 `O_NOFOLLOW + flock`。旧快照在新版本安装后只能被拒绝,不能覆盖已追加版本。
- 后台 Agent 的 manifest 变化以共用 Runtime 状态投影 / 终态 emitter 作为失效因果点:`game-creator-agent-runtime-update` 的 Rust / TypeScript DTO 固定携带 `manifestInvalidated`,且 App 必须在 Supervisor、selected agent、session 和 run 身份的任何 early return 之前处理失效。GUI 进程内 Runtime 直接发该事件;External Runner 是独立进程、没有 GUI `AppHandle`,因此 Runner 协议 v5 的 `runner.attach_gui_owner` 必须登记 GUI 创建的随机 loopback 端口和 64 位随机令牌,Runner 的同一 emitter 通过受令牌保护的短连接转发 `game-creator-manifest-invalidated`。两条路径都只传项目路径与 Agent 身份,不复制 manifest,也不靠轮询补偿。
- App 收到当前项目的 Runtime / relay 失效后重新调用 `get_local_game_manifest`。重读按项目 single-flight 合并事件风暴;读取中再到达失效只追加一轮串行重读,不并发提交同项目响应。应用结果同时校验组件仍挂载、当前项目路径和项目 scope version;项目切换、组件卸载或旧 scope 的迟到响应不得覆盖新项目。Project Supervisor 对外发布前以“revision 前读 -> manifest -> revision 后读”取得一致快照,再通过 `onManifestChange(projectPath, manifest, metadata)` 携带 `projectId + revision + source`;启动器按 `projectPath + projectId` 只接受更高 revision,同 revision 只接受内容一致的重复,旧轮询和同 revision 分叉都不得覆盖。资源列表、依赖图输入、任务状态、运行入口和正式版本卡必须在当前页面实时重投影,不要求关闭或重开项目。
- App 收到当前项目的 Runtime / relay 失效后重新调用 `get_local_game_manifest`。重读按项目 single-flight 合并事件风暴;读取中再到达失效只追加一轮串行重读,不并发提交同项目响应。应用结果同时校验组件仍挂载、当前项目路径和项目 scope version;项目切换、组件卸载或旧 scope 的迟到响应不得覆盖新项目。Project Supervisor 对外发布前以“revision 前读 -> manifest -> revision 后读”取得一致快照,再通过 `onManifestChange(projectPath, manifest, metadata)` 携带 `projectId + revision + source`;启动器按 `projectPath + projectId` 只接受更高 revision,同 revision 只接受内容一致的重复,旧轮询和同 revision 分叉都不得覆盖。资源列表、依赖图输入、任务状态、运行入口和正式版本卡必须在当前页面实时重投影,不要求关闭或重开项目。集成测试记录“事件未重新打开项目”的调用基线前,必须先等待项目写入最近列表后触发的只读目录状态刷新完成,不能把这项合法后台检查误算成失效事件副作用。
- `.agent/agent.db` 有界尾部读取报告截断时,审计 producer 映射失败关闭,不生成基于不完整审计的 producer、task flow 或对应任务环。前端收到截断 DTO 时只剔除 `producerAssignments``taskFlows` 与对应 `cyclicTaskIds`Rust 根据当前 manifest、精确资源引用和仍可信任务深度下限返回的 `dependencyDepths` 继续保留,前端只校验资源仍存在且深度为非负安全整数,不得自行重算或压平权威深度。精确引用边、reference connection index、`cyclicResourceIds` 与 unresolved references 同样继续保留。
- 资源依赖 SVG 继续作为不可交互装饰层隐藏,但 dependency 画布通过 `aria-describedby` 提供当前可见精确引用和任务流的文本等价列表。中央资源聚焦按稳定 `resourceId` 驱动焦点状态:仅 `null -> id``idA -> idB` 聚焦详情 region,同一 ID 的 manifest 重投影不得抢走音频、视频、链接或关闭按钮焦点;显式收起和 Escape 恢复画布滚动并优先聚焦原触发卡片。聚焦资源被删除时清理 stale focused / selected ID,关闭详情并把焦点落到资源搜索框;项目切换或运行视图切换清除旧恢复意图,不得恢复旧项目卡片。橙色引用线及箭头使用对 `#fffdfa` 画布达到至少 `3:1` 的颜色。
@@ -0,0 +1,181 @@
# 图片画布游戏场景生成链路
更新时间:`2026-08-08`
## 1. 目标
在现役图片画布中补齐单张、静态、非分层游戏环境背景的专用生成链路:
```text
首页游戏场景 / 画布底部游戏场景
-> 场景专用生成表单
-> 后端确定性组装场景 Prompt
-> 现有 editor_image_generation 队列与 Worker
-> 现有计费、失败、资源持久化和 canvasCompletion
```
本期不是 Agent 工作流,不新增场景 Agent、场景 Worker、场景任务表或场景专属计费系统。
## 2. 输入和默认值
用户输入:
- 必填画面内容。
- 视觉风格:日系动画、清透水彩、平面几何、定格模型、自定义。
- 自定义风格下必填自定义画风。
- 可选用户参考图,沿用普通图片生成的参考图处理能力。
- 图片比例、清晰度和图片模型。
默认值:
- 图片比例:`16:9`
- 清晰度:`1K`
- 模型:`gemini-3.1-flash-image-preview`,产品展示名沿用现有画布。
- 视觉风格:日系动画。
- 参考图:无。
`model``aspectRatio``imageSize` 继续使用现有编辑器的字符串契约、模型选项、尺寸选项和后端标准化函数,不维护场景专属枚举列表。
## 3. 前端边界
前端只保存和提交结构化场景意图,不保存或拼接完整场景 Prompt。
正式场景状态使用 `GenerateDialogState.mode = "scene"`,不沿用前端壳中的 `generatorVariant = "game-scene"` Demo 标识。
交接壳复用范围:
- 画风选择控件、交互和局部样式。
- 自定义画风条件输入框。
- 底部游戏场景按钮。
- 四张固定画风预览图。
不复用:
- Demo `App.tsx`
- `setTimeout` 模拟生成。
- FileReader Data URL 正式请求。
- 复制出的整套画布和缩减版类型。
四张固定图片只用于前端预览,不进入 `generationReferences`,不随生成请求提交。保留原分辨率,只做轻量无损压缩;画风菜单打开时不同时挂载全部预览图,只在用户悬停、键盘聚焦或点击对应预览入口时按需挂载当前图片。点击同一入口可关闭预览,保证无 hover 的触屏设备可用。
## 4. API 契约
主站新增:
```text
POST /api/editor/scenes/generations
```
请求字段:
```text
sceneContent
stylePreset
customStyle?
model?
aspectRatio?
imageSize?
referenceImageSrcs?
projectId?
generationInputs?
assetFolderId?
assetLabel?
canvasCompletion?
```
请求不接受前端组装后的完整 `prompt`。通用 `/api/editor/images/generations``/api/external/v1/editor/images/generations` 共用同一边界校验,均拒绝 `kind = scene``assetKind = scene`,防止调用方绕过结构化字段校验和后端 Prompt 组装。External v1 当前没有场景专用路由,因此不能通过通用图片接口提交结构化场景;主站 `/api/editor/scenes/generations` 构造规范请求后直接复用队列,不经过通用入口校验。
完整 Provider Prompt 仍只能由后端生成。
场景参考图沿用普通图片生成的客户端前置门禁,最多 5 张;超限时不得发送 HTTP 请求。场景生成 POST 使用生成专用零重试策略,避免 inline 响应丢失后重复调用 Provider。
后端对模型、比例和清晰度先沿用 `normalize_editor_generation_options` 标准化,再使用标准化比例决定画幅描述和入队价格。
## 5. Prompt
场景 Prompt 在 `server-rs/crates/api-server/src/prompt/editor_scene.rs` 中维护,经 `api-server::prompt` 现役模块出口编译,沿用现有后端 Prompt 常量和 builder 模式,不拆分为运行时文本模板文件,不调用 LLM 润色、扩写或改写。旧 `prompt/scene_background.rs` 属于已退役的自定义世界 / RPG 场景链路,不作为本功能复用入口。
提示词来源为产品提供的 `scene_prompt.md`
- 删除文档章节编号和 `[图片]` 占位。
- 保留场景结构约束和四套完整预设风格正文。
- 自定义模式只使用用户自定义画风,不附加预设风格正文。
- `视觉风格:` 标题只由场景结构模板输出,预设正文不重复携带标题。
- 先替换受控的比例和画幅方向,再切分静态用户占位符并一次性拼入画面内容与画风正文;用户输入中的模板占位符按原文保留,不参与后续替换。
画幅方向按标准化比例映射:
- `16:9``4:3``3:2`:横幅。
- `1:1`:方形。
- `9:16``2:3`:竖幅。
## 6. 队列和计费
场景请求在后端组装完整 Prompt 后,转换成现有 `EditorImageGenerationRequest`
```text
kind = scene
assetKind = scene
prompt = 后端完整 Prompt
```
随后复用 `enqueue_editor_image_generation_for_owner`,队列类型继续是 `editor_image_generation`Worker 继续执行 `generate_editor_image_for_owner`
队列标题固定为“图片画布生成游戏场景”。任务摘要只展示 `generationInputs.fields` 中的“画面内容”,不得把队列 payload 里的后端完整 Prompt 暴露到任务侧栏;历史错误摘要在投影刷新时按同一规则重新派生。`assetLabel` 省略、空字符串或纯空白时统一使用“游戏场景”,不能退回完整 Prompt 作为素材名称。
计费规则:
- 前端展示价继续读取 `/api/editor/generation-pricing`
- 入队时按标准化后的模型和清晰度由后端计算并固化 `external_generation_job.price_mud_points`
- Worker 按入队价格扣费,不能在场景接口增加第二层扣费。
- `scene` 明确按普通图片模型档位计价。
- Provider 失败、执行取消和 lease 耗尽沿用现有扣退费语义。
- Provider 成功后的持久化或画布写回失败语义不在本期调整。
- inline 响应中的通用生成告警必须在队列终态处理前转发;有项目画布占位但响应缺少权威 `project` 快照时,不得追加本地图层或把占位标记为已完成。
- 队列任务进入 `completed` 后,首次项目快照仍可能短暂保留同一 `dialogId``generating` 占位。场景提交必须把 completion 的 `dialogId` 传给统一项目回读逻辑;首个权威快照仍未收口时做一次有界延迟重读,不能把该快照当作最终状态永久套用。
## 7. 数据落点
不修改 SpacetimeDB schema,继续复用:
- `external_generation_job`
- `editor_canvas_generation_dialog`
- `editor_project_resource`
- `editor_asset`
- `editor_canvas_layer`
- `asset_object` / `asset_entity_binding`
- 现有钱包流水和 `asset_operation_wallet_settlement`
字段语义:
- `prompt`:后端最终提交给图片 Provider 的完整场景 Prompt。
- `actual_prompt`Provider 返回的 actual/revised Prompt。
- `asset_kind``scene`
- `generation_inputs_json`:可恢复场景生成器的 V2 配方;`assetKind = scene` 只表示素材类别,不直接授予“改造”能力。
场景新产物统一保存以下稳定执行契约:
```text
version = 2
action = scene.generate
fields[].id = prompt | stylePreset | customStyle | model | aspectRatio | imageSize
references[].id = reference
```
`title` / `label` 仅用于中文展示,不参与路由或恢复。前端提交快照和再次提交使用同一组规范化后的模型、比例与清晰度。请求兼容字段 `generationInputs.fields[]` 保留中文展示 `title`,同时把规范值字符串化,例如 `{ title: "视觉风格", value: "anime" }`;它不携带 V2 的 `id / action / version`。后端忽略客户端提供的执行字段和引用 provenance,按请求顶层 `sceneContent / stylePreset / customStyle / model / aspectRatio / imageSize` 重建 V2 `fields`,并按本次真实 `referenceImageSrcs` 只保留安全的 `references[id="reference"]` 槽位,再由现有 owner-scoped 引用重建链补齐 `refType/refId`
`scene.generate` 必须同时进入已知 action 与可改造 action allowlist,并由 action 级 decoder 和场景 composer 恢复路径读取。`assetKind = scene` 不能替代上述 capability 判断。`72f268e0` 之后、V2 上线之前产生的场景数据只在 `assetKind === "scene"` 且 legacy `fields` 含展示标题“画面内容”时走既有 legacy 恢复和告警;“画面内容”不得加入全局 legacy 可复用标题列表,其他素材即使带同名字段也不能获得改造入口。
## 8. 验收
- 首页和底部按钮进入同一个场景生成器。
- 默认值为 `16:9 / 1K / Banana / 日系动画 / 无参考图`
- 预设顺序和自定义条件输入正确。
- 预览图不进入请求参考图。
- 前端请求中不存在完整场景 Prompt。
- 后端正确组装四种预设、一个自定义风格和三类画幅。
- 展示价、入队价、实际扣费和资源成本一致。
- 任务复用现有等待、失败、轮询、资源入库和画布添加逻辑。
- 场景产物以 `assetKind = scene` 持久化,画布图层右上角显示“场景”标签,不降级为“未知”。
- 新场景产物保存 `scene.generate` V2 配方,点击“改造”后恢复画面内容、风格、自定义风格、模型、比例、清晰度和当前画布中仍存在的参考图,再次提交仍保存规范 V2。
- V2 场景、窄化匹配的历史场景显示“改造”;非场景素材即使 legacy 字段标题为“画面内容”也不得显示“改造”。