feat: 完善敲木鱼结果页元信息补录

This commit is contained in:
2026-05-24 20:34:36 +08:00
parent 8638397faa
commit 838c74d8fe
14 changed files with 757 additions and 215 deletions

View File

@@ -407,7 +407,7 @@ describe('miniGameDraftGenerationProgress', () => {
]);
});
test('wooden fish draft generation exposes hit object, background and sound pipeline', () => {
test('wooden fish draft generation exposes hit object, background and back button pipeline', () => {
const state = createMiniGameDraftGenerationState('wooden-fish');
const progress = buildMiniGameDraftGenerationProgress(
@@ -419,12 +419,40 @@ describe('miniGameDraftGenerationProgress', () => {
'wooden-fish-draft',
'wooden-fish-hit-object',
'wooden-fish-background',
'wooden-fish-hit-sound',
'wooden-fish-back-button',
'wooden-fish-write-draft',
]);
expect(progress?.phaseId).toBe('wooden-fish-hit-object');
expect(progress?.phaseLabel).toBe('生成敲击物图案');
expect(progress?.estimatedRemainingMs).toBe(272_000);
expect(progress?.estimatedRemainingMs).toBe(530_000);
});
test('wooden fish draft generation follows hit object, background, back button and writeback', () => {
const state = createMiniGameDraftGenerationState('wooden-fish');
const hitObjectProgress = buildMiniGameDraftGenerationProgress(
state,
state.startedAtMs + 20_000,
);
const backgroundProgress = buildMiniGameDraftGenerationProgress(
state,
state.startedAtMs + 200_000,
);
const backButtonProgress = buildMiniGameDraftGenerationProgress(
state,
state.startedAtMs + 390_000,
);
const writeBackProgress = buildMiniGameDraftGenerationProgress(
state,
state.startedAtMs + 575_000,
);
expect(hitObjectProgress?.phaseId).toBe('wooden-fish-hit-object');
expect(backgroundProgress?.phaseId).toBe('wooden-fish-background');
expect(backButtonProgress?.phaseId).toBe('wooden-fish-back-button');
expect(writeBackProgress?.phaseId).toBe('wooden-fish-write-draft');
expect(writeBackProgress?.estimatedRemainingMs).toBe(0);
expect(writeBackProgress?.steps[4]?.status).toBe('completed');
});
test('wooden fish generation anchors expose hit object, sound and words', () => {

View File

@@ -70,7 +70,7 @@ export type MiniGameDraftGenerationPhase =
| 'wooden-fish-draft'
| 'wooden-fish-hit-object'
| 'wooden-fish-background'
| 'wooden-fish-hit-sound'
| 'wooden-fish-back-button'
| 'wooden-fish-write-draft'
| 'puzzle-cover-image'
| 'puzzle-level-scene'
@@ -415,30 +415,36 @@ const WOODEN_FISH_STEPS = [
{
id: 'wooden-fish-hit-object',
label: '生成敲击物图案',
detail: '使用 image2 生成最终运行态敲击物图案。',
weight: 34,
detail: '用 image2 生成绿幕敲击物并去绿透明化,预计约 3 分钟。',
weight: 32,
},
{
id: 'wooden-fish-background',
label: '生成背景环境图',
detail: '使用 image2 生成敲击背景环境图。',
weight: 34,
detail: '使用透明敲击物作参考生成 9:16 背景环境图,预计约 3 分钟。',
weight: 32,
},
{
id: 'wooden-fish-hit-sound',
label: '准备敲击音效',
detail: '写回上传、录音或默认短促敲击音效资产。',
weight: 16,
id: 'wooden-fish-back-button',
label: '生成返回按钮图',
detail: '使用敲击物和背景作参考生成主题圆形返回按钮,预计约 3 分钟。',
weight: 20,
},
{
id: 'wooden-fish-write-draft',
label: '写入正式草稿',
detail: '保存图案、背景、音效、飘字和封面摘要。',
detail: '保存图案、背景、返回按钮、音效、飘字和封面摘要。',
weight: 8,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const WOODEN_FISH_ESTIMATED_WAIT_MS = 5 * 60_000;
const WOODEN_FISH_COMPILE_EXPECTED_MS = 8_000;
const WOODEN_FISH_IMAGE_GENERATION_EXPECTED_MS = 180_000;
const WOODEN_FISH_WRITE_DRAFT_EXPECTED_MS = 10_000;
const WOODEN_FISH_ESTIMATED_WAIT_MS =
WOODEN_FISH_COMPILE_EXPECTED_MS +
WOODEN_FISH_IMAGE_GENERATION_EXPECTED_MS * 3 +
WOODEN_FISH_WRITE_DRAFT_EXPECTED_MS;
function clampProgress(value: number) {
return Math.max(0, Math.min(100, Math.round(value)));
@@ -486,15 +492,16 @@ function buildMiniGameProgressSteps(
return steps.map((step, index) => {
// 中文注释:拼图草稿编译的 action 回包才代表可进入结果页;
// 但预计写入时长已耗尽时,最后一步自身应呈现已完成,避免出现“进行中 100%”。
const isPuzzleWriteStepCompleted =
state.kind === 'puzzle' &&
const isTimedWriteStepCompleted =
(state.kind === 'puzzle' || state.kind === 'wooden-fish') &&
state.phase !== 'failed' &&
step.id === 'puzzle-select-image' &&
(step.id === 'puzzle-select-image' ||
step.id === 'wooden-fish-write-draft') &&
clampProgress(activeStepProgressRatio * 100) >= 100;
const isCompleted =
state.phase === 'ready' ||
index < activeStepIndex ||
isPuzzleWriteStepCompleted;
isTimedWriteStepCompleted;
const isActive =
state.phase !== 'failed' && !isCompleted && index === activeStepIndex;
const isAssetStep = step.id === state.phase && state.totalAssetCount > 0;
@@ -618,22 +625,64 @@ function resolveJumpHopPhaseByElapsedMs(
return 'jump-hop-draft';
}
function resolveWoodenFishPhaseByElapsedMs(
elapsedMs: number,
): MiniGameDraftGenerationPhase {
if (elapsedMs >= 270_000) {
return 'wooden-fish-write-draft';
function buildWoodenFishPhaseTimeline(): Array<{
phase: Extract<
MiniGameDraftGenerationPhase,
| 'wooden-fish-draft'
| 'wooden-fish-hit-object'
| 'wooden-fish-background'
| 'wooden-fish-back-button'
| 'wooden-fish-write-draft'
>;
durationMs: number;
}> {
return [
{
phase: 'wooden-fish-draft',
durationMs: WOODEN_FISH_COMPILE_EXPECTED_MS,
},
{
phase: 'wooden-fish-hit-object',
durationMs: WOODEN_FISH_IMAGE_GENERATION_EXPECTED_MS,
},
{
phase: 'wooden-fish-background',
durationMs: WOODEN_FISH_IMAGE_GENERATION_EXPECTED_MS,
},
{
phase: 'wooden-fish-back-button',
durationMs: WOODEN_FISH_IMAGE_GENERATION_EXPECTED_MS,
},
{
phase: 'wooden-fish-write-draft',
durationMs: WOODEN_FISH_WRITE_DRAFT_EXPECTED_MS,
},
];
}
function resolveWoodenFishTimelineByElapsedMs(elapsedMs: number) {
let elapsedBeforePhase = 0;
for (const item of buildWoodenFishPhaseTimeline()) {
const elapsedInPhase = elapsedMs - elapsedBeforePhase;
if (elapsedInPhase < item.durationMs) {
return {
phase: item.phase,
activeStepProgressRatio: Math.max(
0,
Math.min(1, elapsedInPhase / item.durationMs),
),
};
}
elapsedBeforePhase += item.durationMs;
}
if (elapsedMs >= 240_000) {
return 'wooden-fish-hit-sound';
}
if (elapsedMs >= 120_000) {
return 'wooden-fish-background';
}
if (elapsedMs >= 12_000) {
return 'wooden-fish-hit-object';
}
return 'wooden-fish-draft';
return {
phase: 'wooden-fish-write-draft' as const,
activeStepProgressRatio: 1,
};
}
function resolvePuzzleTimelineByElapsedMs(
@@ -683,12 +732,23 @@ export function buildMiniGameDraftGenerationProgress(
state.phase !== 'ready'
? resolvePuzzleTimelineByElapsedMs(elapsedMs, state)
: null;
const woodenFishTimeline =
state.kind === 'wooden-fish' &&
state.phase !== 'failed' &&
state.phase !== 'ready'
? resolveWoodenFishTimelineByElapsedMs(elapsedMs)
: null;
const normalizedState =
puzzleTimeline != null
? {
...state,
phase: puzzleTimeline.phase,
}
: woodenFishTimeline != null
? {
...state,
phase: woodenFishTimeline.phase,
}
: state.kind === 'big-fish' &&
state.phase !== 'failed' &&
state.phase !== 'ready'
@@ -724,13 +784,6 @@ export function buildMiniGameDraftGenerationProgress(
...state,
phase: resolveJumpHopPhaseByElapsedMs(elapsedMs),
}
: state.kind === 'wooden-fish' &&
state.phase !== 'failed' &&
state.phase !== 'ready'
? {
...state,
phase: resolveWoodenFishPhaseByElapsedMs(elapsedMs),
}
: state;
const steps =
@@ -766,7 +819,7 @@ export function buildMiniGameDraftGenerationProgress(
: normalizedState.kind === 'jump-hop'
? 0.5
: normalizedState.kind === 'wooden-fish'
? 0.5
? (woodenFishTimeline?.activeStepProgressRatio ?? 0)
: 0;
const overallProgress =
normalizedState.phase === 'failed'
@@ -779,6 +832,8 @@ export function buildMiniGameDraftGenerationProgress(
? overallProgress
: normalizedState.kind === 'puzzle'
? Math.min(PUZZLE_NON_READY_MAX_PROGRESS, overallProgress)
: normalizedState.kind === 'wooden-fish'
? Math.min(PUZZLE_NON_READY_MAX_PROGRESS, overallProgress)
: overallProgress;
return {