Update Match3D/image-generation docs & code

Adds/updates documentation, assets and implementation for Match3D and puzzle image generation workflows. Key changes: decision logs and pitfalls updated to prefer VectorEngine Gemini for Match3D material sheets and to require edits (multipart) for 1:1 container reference images; guidance added for when to use APIMart vs VectorEngine. .env.example clarified APIMart/Responses config. Many new public assets and PPT visuals added. Code changes across frontend and backend: updated shared contracts, server-rs match3d/puzzle/image-generation handlers, VectorEngine/OpenAI image generation clients, and multiple React components/tests to handle UI/background/container image signing, edits workflow, and puzzle UI background resolution. Added src/services/puzzle-runtime/puzzleUiBackgroundSource.ts and related test updates. Includes notes about multipart HTTP/1.1 requirement and test/verification commands in docs.
This commit is contained in:
2026-05-14 20:34:45 +08:00
parent d33c937ebc
commit 548db78ca7
103 changed files with 6687 additions and 3270 deletions

View File

@@ -43,7 +43,6 @@ export type MiniGameDraftGenerationPhase =
| 'match3d-slice-images'
| 'match3d-upload-images'
| 'match3d-generate-views'
| 'match3d-background-music'
| 'match3d-background-image'
| 'match3d-write-draft'
| 'match3d-ready'
@@ -51,7 +50,6 @@ export type MiniGameDraftGenerationPhase =
| 'baby-object-images'
| 'baby-object-ready'
| 'puzzle-images'
| 'puzzle-background-music'
| 'puzzle-ui-background'
| 'puzzle-select-image'
| 'ready'
@@ -98,27 +96,21 @@ const PUZZLE_STEPS = [
detail: '调用图片模型生成适合切块的正方形首图。',
weight: 42,
},
{
id: 'puzzle-background-music',
label: '生成背景音乐',
detail: '用作品题目生成纯音乐并转存音频资产。',
weight: 18,
},
{
id: 'puzzle-ui-background',
label: '生成UI背景',
detail: '生成不含槽位和控件的 9:16 纯背景。',
weight: 14,
weight: 32,
},
{
id: 'puzzle-select-image',
label: '写入正式草稿',
detail: '写入首图、音乐、UI背景和首关数据。',
detail: '写入首图、UI背景和首关数据。',
weight: 8,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const PUZZLE_ESTIMATED_WAIT_MS = 180_000;
const PUZZLE_ESTIMATED_WAIT_MS = 132_000;
const PUZZLE_NON_READY_MAX_PROGRESS = 98;
const PUZZLE_PHASE_TIMELINE: Array<{
phase: Extract<
@@ -126,7 +118,6 @@ const PUZZLE_PHASE_TIMELINE: Array<{
| 'compile'
| 'puzzle-level-name'
| 'puzzle-images'
| 'puzzle-background-music'
| 'puzzle-ui-background'
| 'puzzle-select-image'
>;
@@ -135,7 +126,6 @@ const PUZZLE_PHASE_TIMELINE: Array<{
{ phase: 'compile', durationMs: 12_000 },
{ phase: 'puzzle-level-name', durationMs: 8_000 },
{ phase: 'puzzle-images', durationMs: 70_000 },
{ phase: 'puzzle-background-music', durationMs: 48_000 },
{ phase: 'puzzle-ui-background', durationMs: 32_000 },
{ phase: 'puzzle-select-image', durationMs: 10_000 },
];
@@ -192,7 +182,7 @@ const MATCH3D_STEPS = [
{
id: 'match3d-item-names',
label: '生成作品计划',
detail: '生成游戏名称、物品名称、音乐名称与标签。',
detail: '生成游戏名称、物品名称与标签。',
weight: 10,
},
{
@@ -205,31 +195,25 @@ const MATCH3D_STEPS = [
id: 'match3d-material-sheet',
label: '分批生成素材图',
detail: '按 1K 参数分批生成 5x5 多视角素材图。',
weight: 22,
weight: 24,
},
{
id: 'match3d-slice-images',
label: '切割独立图片',
detail: '把素材图切成每个物品的五个视角。',
weight: 10,
weight: 12,
},
{
id: 'match3d-upload-images',
label: '上传图片资产',
detail: '上传每个物品的 2D 五视角素材。',
weight: 12,
weight: 14,
},
{
id: 'match3d-generate-views',
label: '校验素材结构',
detail: '确认物品顺序五视角图片和音效提示词。',
weight: 6,
},
{
id: 'match3d-background-music',
label: '生成背景音乐',
detail: '用音乐名称生成纯音乐并转存音频资产。',
weight: 14,
detail: '确认物品顺序五视角图片。',
weight: 8,
},
{
id: 'match3d-background-image',
@@ -240,11 +224,13 @@ const MATCH3D_STEPS = [
{
id: 'match3d-write-draft',
label: '写入草稿页',
detail: '保存素材、音乐、背景、容器和作品草稿。',
detail: '保存素材、背景、容器和作品草稿。',
weight: 2,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const MATCH3D_ESTIMATED_WAIT_MS = 510_000;
const MATCH3D_PHASE_ORDER: Partial<
Record<MiniGameDraftGenerationPhase, number>
> = {
@@ -255,9 +241,8 @@ const MATCH3D_PHASE_ORDER: Partial<
'match3d-slice-images': 4,
'match3d-upload-images': 5,
'match3d-generate-views': 6,
'match3d-background-music': 7,
'match3d-background-image': 8,
'match3d-write-draft': 9,
'match3d-background-image': 7,
'match3d-write-draft': 8,
};
const BABY_OBJECT_MATCH_STEPS = [
@@ -392,25 +377,23 @@ function resolveMatch3DPhaseByElapsedMs(
currentPhase: MiniGameDraftGenerationPhase,
): MiniGameDraftGenerationPhase {
const elapsedPhase =
elapsedMs >= 540_000
elapsedMs >= 492_000
? 'match3d-write-draft'
: elapsedMs >= 460_000
: elapsedMs >= 370_000
? 'match3d-background-image'
: elapsedMs >= 370_000
? 'match3d-background-music'
: elapsedMs >= 340_000
? 'match3d-generate-views'
: elapsedMs >= 260_000
? 'match3d-upload-images'
: elapsedMs >= 210_000
? 'match3d-slice-images'
: elapsedMs >= 28_000
? 'match3d-material-sheet'
: elapsedMs >= 12_000
? 'match3d-background-prompt'
: elapsedMs >= 4_000
? 'match3d-item-names'
: 'match3d-work-title';
: elapsedMs >= 340_000
? 'match3d-generate-views'
: elapsedMs >= 260_000
? 'match3d-upload-images'
: elapsedMs >= 210_000
? 'match3d-slice-images'
: elapsedMs >= 28_000
? 'match3d-material-sheet'
: elapsedMs >= 12_000
? 'match3d-background-prompt'
: elapsedMs >= 4_000
? 'match3d-item-names'
: 'match3d-work-title';
const elapsedOrder = MATCH3D_PHASE_ORDER[elapsedPhase] ?? 0;
const currentOrder = MATCH3D_PHASE_ORDER[currentPhase] ?? -1;
return currentOrder > elapsedOrder ? currentPhase : elapsedPhase;
@@ -579,7 +562,7 @@ export function buildMiniGameDraftGenerationProgress(
: normalizedState.kind === 'square-hole'
? Math.max(0, 12_000 - elapsedMs)
: normalizedState.kind === 'match3d'
? Math.max(0, 10 * 60_000 - elapsedMs)
? Math.max(0, MATCH3D_ESTIMATED_WAIT_MS - elapsedMs)
: normalizedState.kind === 'baby-object-match'
? Math.max(0, 60_000 - elapsedMs)
: null,