refactor: 收口 Match3D 生成资产进度

This commit is contained in:
2026-06-04 04:56:21 +08:00
parent 05713e1d3b
commit df5e20d550
6 changed files with 122 additions and 35 deletions

View File

@@ -49,7 +49,6 @@ import type {
} from '../../../packages/shared/src/contracts/match3dAgent';
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
import type {
Match3DGeneratedItemAsset,
Match3DWorkProfile,
Match3DWorkSummary,
} from '../../../packages/shared/src/contracts/match3dWorks';
@@ -516,6 +515,7 @@ import {
createPuzzleDraftGenerationStateFromPayload,
isMiniGameDraftGenerating,
isMiniGameDraftReady,
mergeMatch3DGeneratedAssetsIntoGenerationState,
mergePuzzleSessionProgressIntoGenerationState,
rebaseMiniGameDraftBackgroundCompileTaskForDisplay,
rebaseMiniGameDraftGenerationStateForDisplay,
@@ -751,35 +751,6 @@ function mapVisualNovelWorkDetailToSession(
};
}
function resolveMatch3DGenerationStateFromAssets(
current: MiniGameDraftGenerationState | null,
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
): MiniGameDraftGenerationState | null {
if (!current || current.phase === 'ready' || current.phase === 'failed') {
return current;
}
const assetList = assets ?? [];
const imageReadyCount = assetList.filter(
(asset) =>
asset.imageViews?.some(
(view) => view.imageObjectKey?.trim() || view.imageSrc?.trim(),
) ||
asset.imageObjectKey?.trim() ||
asset.imageSrc?.trim(),
).length;
const totalAssetCount = Math.max(5, assetList.length);
const failedAsset = assetList.find((asset) => asset.error?.trim());
return {
...current,
phase: imageReadyCount > 0 ? 'match3d-generate-views' : current.phase,
completedAssetCount: imageReadyCount,
totalAssetCount,
error: failedAsset?.error?.trim() || current.error,
};
}
function buildSquareHoleProfileFromSession(
session: SquareHoleSessionSnapshot | null,
): SquareHoleWorkProfile | null {
@@ -4807,7 +4778,7 @@ export function PlatformEntryFlowShellImpl({
const normalizedItem = normalizeMatch3DWorkForRuntimeUi(item);
setMatch3DProfile(normalizedItem);
setMatch3DGenerationState((current) =>
resolveMatch3DGenerationStateFromAssets(
mergeMatch3DGeneratedAssetsIntoGenerationState(
current,
normalizedItem.generatedItemAssets,
),

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import type { Match3DGeneratedItemAsset } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleAnchorPack } from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type {
CreatePuzzleAgentSessionRequest,
@@ -12,6 +13,7 @@ import {
createPuzzleDraftGenerationStateFromPayload,
isMiniGameDraftGenerating,
isMiniGameDraftReady,
mergeMatch3DGeneratedAssetsIntoGenerationState,
mergePuzzleSessionProgressIntoGenerationState,
rebaseMiniGameDraftBackgroundCompileTaskForDisplay,
rebaseMiniGameDraftGenerationStateForDisplay,
@@ -96,6 +98,17 @@ function buildState(
};
}
function buildMatch3DAsset(
overrides: Partial<Match3DGeneratedItemAsset> = {},
): Match3DGeneratedItemAsset {
return {
itemId: 'item-1',
itemName: '红宝石',
status: 'pending',
...overrides,
};
}
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
@@ -277,6 +290,74 @@ describe('platformMiniGameDraftGenerationStateModel', () => {
});
});
test('merges match3d generated assets into active generation state', () => {
const state = buildState({
kind: 'match3d',
phase: 'match3d-material-sheet',
completedAssetCount: 0,
totalAssetCount: 0,
error: '旧错误',
});
expect(
mergeMatch3DGeneratedAssetsIntoGenerationState(state, [
buildMatch3DAsset({
itemId: 'item-with-view',
imageViews: [
{
viewId: 'front',
viewIndex: 0,
imageObjectKey: 'objects/front.png',
},
],
}),
buildMatch3DAsset({
itemId: 'item-with-src',
imageSrc: '/generated/item.png',
}),
buildMatch3DAsset({
itemId: 'item-with-error',
error: '切图失败',
}),
]),
).toMatchObject({
phase: 'match3d-generate-views',
completedAssetCount: 2,
totalAssetCount: 5,
error: '切图失败',
});
});
test('keeps match3d generated asset merge away from finished states', () => {
const readyState = buildState({
kind: 'match3d',
phase: 'ready',
completedAssetCount: 5,
totalAssetCount: 5,
});
const failedState = buildState({
kind: 'match3d',
phase: 'failed',
error: '已失败',
});
expect(
mergeMatch3DGeneratedAssetsIntoGenerationState(readyState, [
buildMatch3DAsset({ imageSrc: '/generated/new.png' }),
]),
).toBe(readyState);
expect(
mergeMatch3DGeneratedAssetsIntoGenerationState(failedState, [
buildMatch3DAsset({ imageSrc: '/generated/new.png' }),
]),
).toBe(failedState);
expect(
mergeMatch3DGeneratedAssetsIntoGenerationState(null, [
buildMatch3DAsset({ imageSrc: '/generated/new.png' }),
]),
).toBeNull();
});
test('finishes generation state and resolves ready/generating flags', () => {
const failedState = resolveFinishedMiniGameDraftGenerationState(
buildState({ error: '旧错误' }),

View File

@@ -1,3 +1,4 @@
import type { Match3DGeneratedItemAsset } from '../../../packages/shared/src/contracts/match3dWorks';
import type {
CreatePuzzleAgentSessionRequest,
PuzzleAgentSessionSnapshot,
@@ -134,6 +135,35 @@ export function mergePuzzleSessionProgressIntoGenerationState(
};
}
export function mergeMatch3DGeneratedAssetsIntoGenerationState(
current: MiniGameDraftGenerationState | null,
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
): MiniGameDraftGenerationState | null {
if (!current || current.phase === 'ready' || current.phase === 'failed') {
return current;
}
const assetList = assets ?? [];
const imageReadyCount = assetList.filter(
(asset) =>
asset.imageViews?.some(
(view) => view.imageObjectKey?.trim() || view.imageSrc?.trim(),
) ||
asset.imageObjectKey?.trim() ||
asset.imageSrc?.trim(),
).length;
const totalAssetCount = Math.max(5, assetList.length);
const failedAsset = assetList.find((asset) => asset.error?.trim());
return {
...current,
phase: imageReadyCount > 0 ? 'match3d-generate-views' : current.phase,
completedAssetCount: imageReadyCount,
totalAssetCount,
error: failedAsset?.error?.trim() || current.error,
};
}
export function resolveFinishedMiniGameDraftGenerationState(
state: MiniGameDraftGenerationState,
phase: 'ready' | 'failed',