Extend square-hole creation flow with visual asset timeout guard

This commit is contained in:
kdletters
2026-05-05 15:27:09 +08:00
parent 2252afb080
commit 60b667a9d1
30 changed files with 2838 additions and 215 deletions

View File

@@ -7,9 +7,10 @@ import type {
CustomWorldGenerationProgress,
CustomWorldGenerationStep,
} from '../../packages/shared/src/contracts/runtime';
import type { SquareHoleSessionSnapshot } from '../../packages/shared/src/contracts/squareHoleAgent';
import type { CustomWorldStructuredAnchorEntry } from './customWorldAgentGenerationProgress';
export type MiniGameDraftGenerationKind = 'puzzle' | 'big-fish';
export type MiniGameDraftGenerationKind = 'puzzle' | 'big-fish' | 'square-hole';
export type MiniGameDraftGenerationPhase =
| 'idle'
@@ -17,6 +18,10 @@ export type MiniGameDraftGenerationPhase =
| 'big-fish-draft'
| 'big-fish-levels'
| 'big-fish-runtime'
| 'square-hole-draft'
| 'square-hole-cover'
| 'square-hole-shapes'
| 'square-hole-ready'
| 'puzzle-images'
| 'puzzle-select-image'
| 'ready'
@@ -86,12 +91,39 @@ const BIG_FISH_STEPS = [
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const SQUARE_HOLE_STEPS = [
{
id: 'square-hole-draft',
label: '整理玩法草稿',
detail: '收拢题材、形状、洞口与加分选项。',
weight: 28,
},
{
id: 'square-hole-cover',
label: '生成封面与背景',
detail: '生成作品封面和运行背景。',
weight: 32,
},
{
id: 'square-hole-shapes',
label: '生成形状贴图',
detail: '为每个可投放形状生成贴图。',
weight: 40,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
function clampProgress(value: number) {
return Math.max(0, Math.min(100, Math.round(value)));
}
function getStepDefinitions(kind: MiniGameDraftGenerationKind) {
return kind === 'puzzle' ? PUZZLE_STEPS : BIG_FISH_STEPS;
if (kind === 'puzzle') {
return PUZZLE_STEPS;
}
if (kind === 'square-hole') {
return SQUARE_HOLE_STEPS;
}
return BIG_FISH_STEPS;
}
function getActiveStepIndex(
@@ -132,7 +164,12 @@ export function createMiniGameDraftGenerationState(
): MiniGameDraftGenerationState {
return {
kind,
phase: kind === 'big-fish' ? 'big-fish-draft' : 'compile',
phase:
kind === 'big-fish'
? 'big-fish-draft'
: kind === 'square-hole'
? 'square-hole-draft'
: 'compile',
startedAtMs: Date.now(),
completedAssetCount: 0,
totalAssetCount: 0,
@@ -152,6 +189,18 @@ function resolveBigFishPhaseByElapsedMs(
return 'big-fish-draft';
}
function resolveSquareHolePhaseByElapsedMs(
elapsedMs: number,
): MiniGameDraftGenerationPhase {
if (elapsedMs >= 6_500) {
return 'square-hole-shapes';
}
if (elapsedMs >= 2_400) {
return 'square-hole-cover';
}
return 'square-hole-draft';
}
export function buildMiniGameDraftGenerationProgress(
state: MiniGameDraftGenerationState | null,
nowMs = Date.now(),
@@ -169,6 +218,13 @@ export function buildMiniGameDraftGenerationProgress(
...state,
phase: resolveBigFishPhaseByElapsedMs(elapsedMs),
}
: state.kind === 'square-hole' &&
state.phase !== 'failed' &&
state.phase !== 'ready'
? {
...state,
phase: resolveSquareHolePhaseByElapsedMs(elapsedMs),
}
: state;
const steps = getStepDefinitions(normalizedState.kind);
@@ -190,6 +246,8 @@ export function buildMiniGameDraftGenerationProgress(
? 1
: normalizedState.kind === 'big-fish'
? 0.55
: normalizedState.kind === 'square-hole'
? 0.42
: 0;
const overallProgress =
normalizedState.phase === 'failed'
@@ -223,6 +281,8 @@ export function buildMiniGameDraftGenerationProgress(
? 0
: normalizedState.kind === 'big-fish'
? Math.max(0, 7_000 - elapsedMs)
: normalizedState.kind === 'square-hole'
? Math.max(0, 12_000 - elapsedMs)
: null,
activeStepIndex,
steps: buildMiniGameProgressSteps(steps, activeStepIndex, normalizedState),
@@ -306,3 +366,46 @@ export function buildBigFishGenerationAnchorEntries(
}))
.filter((entry) => entry.value.trim());
}
export function buildSquareHoleGenerationAnchorEntries(
session: SquareHoleSessionSnapshot | null | undefined,
): CustomWorldStructuredAnchorEntry[] {
if (!session) {
return [];
}
const draft = session.draft;
const shapeCount =
draft?.shapeOptions.filter((option) => option.imageSrc?.trim()).length ??
session.config.shapeOptions.filter((option) => option.imageSrc?.trim())
.length;
const totalShapeCount =
draft?.shapeOptions.length || session.config.shapeOptions.length;
const entries: Array<MiniGameAnchorSource | null> = [
{
key: 'square-hole-title',
label: '作品名称',
value: draft?.gameName || `${session.config.themeText}方洞挑战`,
},
{
key: 'square-hole-theme',
label: '题材与规则',
value: `${session.config.themeText}${session.config.twistRule}`,
},
{
key: 'square-hole-options',
label: '选项资产',
value: totalShapeCount > 0 ? `形状贴图 ${shapeCount}/${totalShapeCount}` : '',
},
];
return entries
.filter((entry): entry is MiniGameAnchorSource => Boolean(entry))
.map((entry) => ({
id: entry.key,
label: entry.label,
value: entry.value,
}))
.filter((entry) => entry.value.trim());
}