Files
Genarrative/docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md
2026-05-11 16:15:48 +08:00

10 KiB
Raw Blame History

抓大鹅草稿素材生成流水线 2026-05-10

1. 范围

本方案用于改造 生成抓大鹅草稿 的首版生成链路:点击按钮后先进入独立生成过程页,生成结束后自动进入抓大鹅草稿页,并在草稿页 3D素材 Tab 预览本次生成的 3D 模型。

本次只把任意难度都收敛为 3 件物品。后续难度曲线恢复时,再把物品数、网格数和手动 3D 任务数量从配置中放开。

2. 前端流程

入口仍复用 Match3DAgentWorkspace 表单。点击 生成抓大鹅草稿 后:

  1. 创建 Match3D session。
  2. 进入 match3d-generating 生成过程页。
  3. 过程页复用拼图生成页的 CustomWorldGenerationView 结构。
  4. 生成成功后自动进入 match3d-result
  5. 生成失败时停留在生成过程页,允许重新生成或返回创作中心。

生成页步骤固定为:

生成游戏名称 -> 生成物品名称 -> 生成素材图 -> 切割独立图片 -> 上传图片资产 -> 生成3D模型 -> 写入草稿页

生成页只展示题材和物品数量,不展示玩法规则说明。

3. 后端编排边界

外部模型和 OSS 上传全部由 api-server 编排,不进入 SpacetimeDB reducer。SpacetimeDB 继续只负责 Match3D 会话、草稿和作品 profile 的确定性写入。

match3d_compile_draft action 的后端顺序为:

  1. 读取 session config。
  2. 将本次 MVP 的 clearCount 固定为 3,并同步用于草稿编译。
  3. 基于入口页题材设定文本调用文本模型生成作品元信息。模型固定请求 gpt-4o,只返回 JSON其中 gameName 为 4 到 12 个中文字符的游戏名称,tags 为 3 到 6 个中文短标签;summary 首版必须保持空字符串,结果页 作品描述 默认留给用户填写。
  4. 调用文本模型生成 3 个题材下的短物品名称。
  5. 调用项目当前图片链路 VectorEngine gpt-image-2-all 生成一张 1:1 素材图,提示词必须合入入口页选择的 assetStylePrompt。历史 nanobanana2 图片选项当前按项目统一决策回落到 VectorEngine不重新接入 APIMart 图片网关。
  6. 将素材图按 n*n 网格切割成独立图片。当前 3 件物品使用 2*2 网格,取前 3 格。
  7. 将素材图和每张独立图片上传到 OSS其中独立图片作为草稿页素材预览和 Rodin 图生模型参考图。
  8. 使用每张独立图片作为参考图,并行调用 Hyper3D Rodin 图生模型;所有 3D 模型任务必须在同一阶段同时提交、同时轮询状态、同时下载并转存 OSS禁止逐个物品串行等待模型完成。每个任务按官方 minimal example 轮询状态;只有 jobs 全部进入 Done 才能视为任务完成,任一 job Failed 则失败。完成后选择 .glb 下载文件,并把 GLB 转存到 OSS。Rodin 的 subscriptionKey 是上游 opaque token不做 256 字符这类短文本长度限制。Rodin 任务状态进入完成态后,下载列表仍可能延迟发布;后端必须对下载列表继续轮询,并兼容 urldownloadUrlfileUrlsignedUrl 等下载字段别名,只有预览图而没有模型文件时不能伪装成 GLB 成功。
  9. 调用现有 SpacetimeDB compile procedure 写入草稿,并把本次生成的独立物品图片和模型列表序列化写入 match3d_work_profile.generated_item_assets_json。这一步对标拼图的 save_puzzle_generated_images:生成资产不能只挂在本次 HTTP response 上,否则退出结果页后从草稿架读取 getMatch3DWorkDetail 会丢失素材列表。
  10. 在 HTTP 返回的 draft/profile DTO 中附带本次生成的素材资产预览信息,模型生成成功的素材状态为 model_ready;后续重进草稿页时从 work profile 的持久化 generatedItemAssets 恢复同一批素材。

若文本模型不可用或返回无法解析,后端必须降级为 {themeText}抓大鹅 与本地标签兜底,不阻断素材生成;但描述仍保持空字符串。

草稿生成阶段会调用 Hyper3D Rodin 并等待 GLB 下载完成;前端 match3d_compile_draft action 请求超时必须覆盖该长耗时链路,当前 Match3D client 使用 20 分钟超时。Rodin 单模型状态轮询预算为 10 分钟,下载列表发布轮询预算为 5 分钟;由于 3 个模型并行生成,总耗时按最慢模型计算,不能按模型数量线性叠加。结果页 3D素材 Tab 直接加载已生成模型;用户点击 重新生成 时再复用 Rodin 安全代理,首版重新生成只更新当前页面内预览状态,后续正式资产绑定以独立技术方案为准。

4. 图片提示词

素材图提示词必须显式包含:

生成一张1:1图片
生成2*2网格素材图
整体画风遵循:...
只绘制这些物品:...
不要出现文字、水印、UI、边框

包含若干个物品名称 在落地中解释为“按生成出的物品名称绘制对应主体”,不要求图片上写出物品名称。这样可以避免文字渲染污染切图和后续手动 3D 模型参考。

入口页内置风格参考图通过同一 VectorEngine gpt-image-2-all 能力生成,保存路径固定为:

public/match3d-style-references/clay-toy.png
public/match3d-style-references/low-poly.png
public/match3d-style-references/toy-plastic.png
public/match3d-style-references/wood-carved.png
public/match3d-style-references/voxel-block.png
public/match3d-style-references/metal-mecha.png

这些图片只作为入口页风格选择的视觉参考,不进入用户草稿资产,不替代生成时的物品素材图。

5. OSS 路径

新增 generated legacy prefix

generated-match3d-assets

建议对象分组:

generated-match3d-assets/{sessionId}/{profileId}/material-sheet/{taskId}/sheet.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/image/image.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/model/{taskUuid}/model.glb

itemSlug 必须带 itemId 前缀,例如 match3d-item-1-item。中文物品名清洗后可能都退回 item,不能只用物品名做路径,否则多张切割图会写到同一个 object key导致草稿页预览图全部一致。

HTTP DTO 同时返回 imageSrcimageObjectKeymodelSrcmodelObjectKeymodelFileNametaskUuidsubscriptionKeystatus。模型生成成功后 status = model_ready;若后续允许部分模型失败降级,失败素材必须带 error,且不能伪装成可预览模型。前端模型预览必须通过 /api/assets/read-bytes 读取私有 GLB 字节并转成 Blob URL 后交给 Three.js不直接请求裸 /generated-match3d-assets/... 路径。

5.1 运行态模型消费

生成模型不仅用于结果页预览,也必须进入游戏运行态。运行态入口的传递链路为:

Match3DWorkProfile / PlatformMatch3DGalleryCard
-> Match3DRuntimeShell(generatedItemAssets)
-> Match3DPhysicsBoard / Match3DTrayPreviewBoard

Match3DPhysicsBoardMatch3DTrayPreviewBoard 按运行快照中的 itemTypeId 稳定排序后,把生成出的模型顺序映射到对应类型。当前 MVP 固定 clearCount = 3,因此 match3d-type-01/02/03 分别对应生成列表的第 1/2/3 个模型;后续恢复更多物品生成时,后端必须继续保证 generatedItemAssets 顺序与类型编号一致。

前端加载规则:

  1. 优先读取 modelSrc;为空时使用 modelObjectKey
  2. 通过 readAssetBytes 调用 /api/assets/read-bytes,由同源后端读取 OSS 私有对象字节。
  3. 使用 Three.js GLTFLoader.parseAsync 解析 GLB 字节,并按物品类型缓存模板。
  4. 场内每个物品和备选栏预览都从模板 clone 独立对象,点击命中继续写入 itemInstanceId
  5. 物理碰撞和边界仍沿用现有 visualKey 的程序化几何,生成 GLB 只替换视觉模型,不承接规则真相。
  6. 模型缺失、读取失败或 WebGL 回退时,继续使用默认积木素材,不能阻断开局、点击、入槽或结算。

6. 自动保存与草稿恢复

抓大鹅结果页的基础信息自动保存继续调用 PUT /api/runtime/match3d/works/{profileId} 更新名称、题材、描述、标签、封面、消除数和难度;该保存不得清空 generated_item_assets_json。SpacetimeDB update_match3d_work / publish_match3d_work 必须保留当前行的生成素材 JSON。

草稿架重进路径为:

草稿 Tab -> getMatch3DWorkDetail(profileId) -> Match3DResultView(profile.generatedItemAssets)

因此 map_match3d_work_summary_response / map_match3d_work_profile_response 需要从 work profile snapshot 反序列化 generated_item_assets_json 并输出 generatedItemAssets。前端 Match3DResultView 仍保持现有优先级:本次生成流程内有 draft.generatedItemAssets 时用 draft从草稿架重进没有 draft 时,用 profile.generatedItemAssets;两者都没有才回退到默认 3D 素材占位。

结果页 作品信息 Tab 字段命名对齐拼图草稿:

  1. 作品名称 对应 Match3D gameName
  2. 作品描述 对应 Match3D summary,草稿生成默认空。
  3. 作品标签 对应 Match3D tags,可由 AI 首次生成并允许用户继续编辑。
  4. 封面图与作品名称不再拆成左右两个大模块;封面只作为同一 Tab 内的可选上传入口,避免和作品基础信息割裂。

3D素材 详情页只保留:

  1. 模型预览区:优先加载 modelSrc 对应 GLB支持拖动旋转没有模型时展示空预览。
  2. 素材名称输入。
  3. 重新生成 按钮。

详情页不再展示参考图、用途、提示词、文生/图生切换、状态查询、下载列表、taskUuid 或 subscriptionKey。

7. 验收

建议执行:

npm run check:encoding
npm run test -- src\services\miniGameDraftGenerationProgress.test.ts
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
npm run typecheck
cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml
cargo test -p spacetime-client match3d --manifest-path server-rs\Cargo.toml
cargo test -p platform-oss --manifest-path server-rs\Cargo.toml
cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml
cargo check -p api-server --manifest-path server-rs\Cargo.toml
cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml
cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml

真实草稿生成需要本地私密环境配置 VECTOR_ENGINE_API_KEYHYPER3D_API_KEY 和 OSS 访问变量。后端改动后使用 npm run api-server 启动,并检查 /healthz