Feat/游戏场景需求v1.0 (#139)
Project CI / Repository checks (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled

实现[游戏场景需求v1.0](https://kcnz41bksl1c.feishu.cn/wiki/JSF3wdhduinpqhkrVGKcZFp4nng?psg_id=8477599259997387860&refer_index=1&refer_type=citation)。

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Co-authored-by: suzmii <suzmii@foxmail.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/139
Co-authored-by: 董羽秦 <suzmii@qq.com>
Co-committed-by: 董羽秦 <suzmii@qq.com>
This commit was merged in pull request #139.
This commit is contained in:
2026-08-08 10:25:42 +08:00
committed by 段舒康
parent fc0dcb0782
commit 72f268e088
64 changed files with 3441 additions and 120 deletions
+1
View File
@@ -19,6 +19,7 @@
- [图片画布编辑器 MVP 接入方案](./technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md)
- [图片画布编辑器前端拆分计划](./technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md)
- [图片画布游戏场景生成链路](./technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md)
- [画板音乐生成入口设计](./【编辑器】画板音乐生成入口设计-2026-06-18.md)
- [音频生成 Composer 恢复共享分流方案](./project-memory/plans/【前端重构】音频生成面板恢复共享分流方案-2026-08-06.md)
- [BGM 提示词优化 T6 测试与发布门禁](./【实施记录】BGM生成提示词优化T6测试与发布门禁-2026-08-05.md)
@@ -3299,7 +3299,7 @@
"ui-design",
"publication-material"
],
"description": "省略时生成普通图片;其它值选择对应的专用生成流程。"
"description": "省略时生成普通图片;其它值选择对应的专用生成流程。External v1 当前不开放结构化游戏场景生成,scene 不能通过该通用图片接口提交。"
},
"model": {
"type": "string",
@@ -3340,10 +3340,18 @@
]
},
"assetKind": {
"type": [
"string",
"null"
]
"anyOf": [
{
"type": "string",
"not": {
"pattern": "^\\s*scene\\s*$"
}
},
{
"type": "null"
}
],
"description": "生成产物分类。External v1 通用图片接口禁止使用 scene;结构化游戏场景必须使用主站场景专用契约。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
+16 -1
View File
@@ -369,6 +369,14 @@
- 验证:画板生成 workflow 测试覆盖 queueState 持续 `running` 到前端等待窗口结束时,不进入 failed、不显示该排队文案、不添加本地临时结果层。
- 关联:`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx`。
## 场景队列终态不保证首次项目快照已经收口生成占位
- 现象:游戏场景任务已经显示完成,但画布仍保留 `generating` 占位;场景链路又禁止用本地结果补层,因此当前会话可能一直停在生成中。
- 原因:外部生成任务终态与项目画布投影不是同一个原子观测点。队列轮询先看到 `completed` 后,紧接着的首次项目 GET 仍可能读到同一 `dialogId` 的未收口占位;若调用统一回读函数时没有传 completion dialog ID,函数无法识别该快照仍未完成,也不会执行已有的有界延迟重读。
- 处理:游戏场景队列调用要把本次占位 `dialogId` 传给 `applyQueuedEditorGenerationProject`。首个快照中该 ID 仍为 unresolved 时,只按既有间隔补读一次项目;不追加本地图层,也不把任务终态直接等同于画布投影终态。其他生成类型若要补同类保护,必须分别复现其权威回填时序后再改,不能用本条场景结论替代验证。
- 验证:场景 workflow 用两个连续快照复现时序:第一个保留 `scene / generating`,第二个包含场景结果并把同一占位置为 `idle`。修复前只读一次并超时,修复后依次应用两个权威快照。
- 关联:`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx`、`docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md`。
## 图片画布历史不能回退当前权威状态或复活后端已删素材
- 现象:生成占位框移动后开始生成,撤销移动会把仍在运行的生成对象恢复成待生成状态;切换到 2K 或改变比例后撤销位置,旧占位框还可能把当前尺寸回退。上传图层落库后,普通移动撤销可能被提示“可能会使图片消失”并永久卡在栈顶;即使安全检查已放行,直接恢复旧图层快照也会丢失刚回填的资源关联。素材库后端删除关联素材后,更早的移动快照还可能把已删图层重新加入并自动保存;修改素材类型虽然界面提示撤销成功,刷新后却可能从仍指向新类型的 resource 回弹。无稳定 ID 的“修改图片”草稿也可能被 target-null 快照直接关闭。
@@ -538,6 +546,14 @@
- 验证:`npm run test -- src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx`;`cargo test -p api-server inline_data_url --manifest-path server-rs/Cargo.toml`。
- 关联:`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/api-server/src/editor_generation_queue.rs`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 专用生成契约不能被通用生成接口和任务摘要绕过
- 现象:专用场景接口要求结构化 `sceneContent + stylePreset`,但调用方仍可向通用图片接口传 `kind = scene` 或 `assetKind = scene`,用任意完整 Prompt 生成并持久化正式场景;合法场景入队后,任务侧栏还可能显示后端完整规则文本和通用“生成图片”标题,空白素材名则可能回退成完整 Prompt。
- 原因:专用 handler 内部复用了通用图片 payload、队列和 Worker,但公开通用 HTTP handler 没有限制专用身份;任务摘要又无条件优先提取 payload 顶层 `prompt`,素材名默认值只处理了字段省略,没有处理空白字符串。
- 处理:公开通用 handler 拒绝专用 `kind / assetKind`,专用 handler 仍可直接调用内部共享执行函数;队列投影按 `kind = scene` 从权威 `generationInputs.fields[画面内容]` 派生标题和摘要,缺字段时失败关闭而不是回退内部 Prompt,并重新计算历史缓存;专用素材名统一把省略和空白收口为产品默认值。
- 验证:路由测试先证明旁路会越过 HTTP 边界,再断言两种旁路均返回 `400` 且指向专用端点;摘要测试覆盖新任务、历史错误缓存和缺少画面内容三种情况;标签测试覆盖省略、空白、自定义和 80 字上限。
- 关联:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/spacetime-module/src/external_generation.rs`、`docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md`。
## 图片编辑器角色动画必须提交稳定图片引用
- 现象:图片编辑器里对尚未上传的角色图点击 `生成动画` 后,前端或后端返回 `sourceImageSrc 必须先上传 OSS`。
@@ -4091,7 +4107,6 @@
- 现象:延迟重试携带比既有行更旧的调用方时间,回填图片序列字段时若无条件写入,会使 `updated_at` 倒退,导致基于时间戳的同步看不到更新或排序错误。
- 处理:同源图片序列字段只允许 `None → Some`,非空冲突失败关闭;发生回填时 `updated_at = max(existing.updated_at, request_timestamp)`。legacy 音频 repair 不派生资源级图片序列或通用时长,重放继续精确匹配。
## 历史钱包消费不能从最近流水或通用订单快照推算
- 现象:后台用户详情要展示累计花费时,直接复用只返回最近 50 条的 `list_profile_wallet_ledger`,或在充值订单每行使用的通用钱包快照里扫描该用户全部流水。
@@ -0,0 +1,166 @@
# 图片画布游戏场景生成链路
更新时间:`2026-08-04`
## 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`:画面内容、视觉风格、自定义画风和用户参考图引用。
## 8. 验收
- 首页和底部按钮进入同一个场景生成器。
- 默认值为 `16:9 / 1K / Banana / 日系动画 / 无参考图`。
- 预设顺序和自定义条件输入正确。
- 预览图不进入请求参考图。
- 前端请求中不存在完整场景 Prompt。
- 后端正确组装四种预设、一个自定义风格和三类画幅。
- 展示价、入队价、实际扣费和资源成本一致。
- 任务复用现有等待、失败、轮询、资源入库和画布添加逻辑。
- 场景产物以 `assetKind = scene` 持久化,画布图层右上角显示“场景”标签,不降级为“未知”。
@@ -32,7 +32,7 @@ v1 只开放以下能力:
- `POST /api/external/v1/editor/assets`:创建素材记录。
- `PATCH /api/external/v1/editor/assets/{assetId}`:更新素材名称或所在文件夹。
- `DELETE /api/external/v1/editor/assets/{assetId}`:删除素材记录。
- `POST /api/external/v1/editor/images/generations`:异步提交编辑器图片素材生成;通过 `kind` 支持普通图、规范图 `spec`、角色图 `character`、快速编辑参考图 `quick-edit`、UI 设计图 `ui-design` 和宣发素材 `publication-material`。
- `POST /api/external/v1/editor/images/generations`:异步提交编辑器图片素材生成;通过 `kind` 支持普通图、规范图 `spec`、角色图 `character`、快速编辑参考图 `quick-edit`、UI 设计图 `ui-design` 和宣发素材 `publication-material`。External v1 当前不开放结构化游戏场景生成,`kind = scene` 与 `assetKind = scene` 均在入队前返回 `400`。
- `POST /api/external/v1/editor/images/edits`:异步提交已有图片重绘 / 调整。
- `POST /api/external/v1/editor/icon-spritesheets/generations`:异步提交规范图驱动的图标 spritesheet 生成和拆分。
- `POST /api/external/v1/editor/ui-designs/assets/extractions`:异步提交 UI 设计图素材提取 / 拆分。
@@ -214,7 +214,7 @@ SpacetimeDB procedure:
外部生成接口复用站内编辑器已有 DTO、入队器和 worker executor,不维护第二套生成语义:
- 图片生成 / 重绘 / 规范图 / 宣发图 / UI 设计图复用 `/api/editor/images/generations` 与 `/api/editor/images/edits` 的校验、模型归一、计费和持久化规则,但 External handler 固定只入队。
- 图片生成 / 重绘 / 规范图 / 宣发图 / UI 设计图复用 `/api/editor/images/generations` 与 `/api/editor/images/edits` 的校验、模型归一、计费和持久化规则,但 External handler 固定只入队。主站和 External 的通用图片入口共用场景专用合同边界校验,禁止用 `kind = scene` 或 `assetKind = scene` 绕过后端场景 Prompt 组装;场景专用 handler 自己构造规范请求,不受该通用入口校验影响。
- 图标 spritesheet 和 UI 设计图素材提取复用站内拆分逻辑,生成图集后按连通域切片,并把图集与切片都按请求写入项目资源和素材库。
- 角色动画、视频、音效和背景音乐复用站内编辑器生成链路;请求携带 `assetFolderId` 时按站内规则写入素材库,音频类外部调用使用 API Key 所属账号作为 asset owner。
- API Key 管理接口仍只属于登录态个人中心,不进入外部 OpenAPI JSON。
@@ -254,6 +254,7 @@ docs/openapi/genarrative-external-v1.openapi.json
- API Key 创建只返回一次明文,列表不返回明文。
- 撤销后的 API Key 调用外部接口返回 `401`。
- 八类外部生成 POST 缺少或携带非法 `Idempotency-Key` 时返回 `400`;同一 owner、请求和 key 重试只得到同一 operation。
- External 通用图片生成携带 `kind = scene` 或 `assetKind = scene` 时均返回 `400`,且不得产生入队尝试。
- 八类外部生成 POST 固定返回 `202`,查询能从 `queued/running` 收敛到 `completed/failed`;调用方超时后使用原 operationId 继续查询。
- 外部图片生成、重绘、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐 completed 后,生成结果按请求同时出现在画布资源和账号级素材库。
- 角色图、图标 spritesheet 和 UI 素材提取的 completed result 允许携带 `EditorGenerationWarning`;provider 原图保留降级与自动拆分降级必须保持成功状态,并分别使用通用 `warning` 与兼容 `sliceWarning` 表达。