fix: polish bark battle creation flow

This commit is contained in:
kdletters
2026-05-22 05:00:07 +08:00
parent 01da85a577
commit bf82f04b64
73 changed files with 9362 additions and 2663 deletions

View File

@@ -1,15 +1,19 @@
import { describe, expect, test } from 'vitest';
import {
BARK_BATTLE_ASSET_SLOTS,
BARK_BATTLE_DIFFICULTY_PRESETS,
type BarkBattleDraftConfig,
type BarkBattleDraftConfigUpdateRequest,
type BarkBattleFinishResponse,
type BarkBattleGeneratedImageAsset,
type BarkBattleImageAssetGenerateRequest,
type BarkBattlePersonalBestSummary,
type BarkBattleWorkStats,
} from './barkBattle';
describe('Bark Battle shared contracts', () => {
test('default draft config fixture uses normal difficulty and camelCase fields', () => {
test('default draft config fixture uses normal difficulty and v1 description fields', () => {
const draft: BarkBattleDraftConfig = {
draftId: 'draft-bark-1',
workId: 'work-bark-1',
@@ -17,15 +21,14 @@ describe('Bark Battle shared contracts', () => {
rulesetVersion: 'bark-battle-ruleset-v1',
title: '汪汪声浪挑战',
description: '轻配置草稿',
themePreset: 'city-park',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
themeDescription: '傍晚城市公园里的声浪擂台',
playerImageDescription: '戴红围巾的柯基主角',
opponentImageDescription: '蓝色运动头带的哈士奇对手',
onomatopoeia: ['轰汪!', '嗷呜!', '咚咚!'],
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
opponentCharacterImageSrc: 'https://example.test/opponent.png',
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
barkSoundSrc: '/generated-bark-battle/audio/bark.mp3',
difficultyPreset: 'normal',
leaderboardEnabled: true,
updatedAt: '2026-05-13T03:00:00.000Z',
};
@@ -38,18 +41,100 @@ describe('Bark Battle shared contracts', () => {
'rulesetVersion',
'title',
'description',
'themePreset',
'playerDogSkinPreset',
'opponentDogSkinPreset',
'themeDescription',
'playerImageDescription',
'opponentImageDescription',
'onomatopoeia',
'playerCharacterImageSrc',
'opponentCharacterImageSrc',
'uiBackgroundImageSrc',
'barkSoundSrc',
'difficultyPreset',
'leaderboardEnabled',
'updatedAt',
]);
expect(draft.playerCharacterImageSrc).toContain('/generated-bark-battle/');
expect('barkSoundSrc' in draft).toBe(false);
expect('leaderboardEnabled' in draft).toBe(false);
});
test('draft config update contract persists generated image slots only', () => {
const update: BarkBattleDraftConfigUpdateRequest = {
draftId: 'draft-bark-1',
workId: 'BB-12345678',
configVersion: 2,
rulesetVersion: 'bark-battle-ruleset-v1',
title: '汪汪声浪挑战',
description: '轻配置草稿',
themeDescription: '傍晚城市公园里的声浪擂台',
playerImageDescription: '戴红围巾的柯基主角',
opponentImageDescription: '蓝色运动头带的哈士奇对手',
onomatopoeia: ['轰!', '燃起来!', '破阵!'],
playerCharacterImageSrc: '/generated-bark-battle/player/image.png',
opponentCharacterImageSrc: '/generated-bark-battle/opponent/image.png',
uiBackgroundImageSrc: '/generated-bark-battle/ui/background.png',
difficultyPreset: 'normal',
};
expect(Object.keys(update)).toEqual([
'draftId',
'workId',
'configVersion',
'rulesetVersion',
'title',
'description',
'themeDescription',
'playerImageDescription',
'opponentImageDescription',
'onomatopoeia',
'playerCharacterImageSrc',
'opponentCharacterImageSrc',
'uiBackgroundImageSrc',
'difficultyPreset',
]);
expect('barkSoundSrc' in update).toBe(false);
expect('leaderboardEnabled' in update).toBe(false);
});
test('image generation contract uses dedicated Bark Battle slots and backend prompt result', () => {
expect(BARK_BATTLE_ASSET_SLOTS).toEqual([
'player-character',
'opponent-character',
'ui-background',
]);
const request: BarkBattleImageAssetGenerateRequest = {
slot: 'opponent-character',
draftId: 'bark-battle-draft-1',
config: {
title: '汪汪冠军杯',
description: '',
themeDescription: '霓虹公园擂台',
playerImageDescription: '红围巾柴犬',
opponentImageDescription: '蓝头带哈士奇',
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
difficultyPreset: 'normal',
},
};
const response: BarkBattleGeneratedImageAsset = {
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
assetId: 'asset-1',
sourceType: 'generated',
model: 'gpt-image-2',
size: '1024*1024',
taskId: 'task-1',
prompt: '后端拼装后的对手形象 prompt',
};
expect(JSON.parse(JSON.stringify(request))).toMatchObject({
slot: 'opponent-character',
config: {
opponentImageDescription: '蓝头带哈士奇',
onomatopoeia: ['轰汪!', '炸场!', '冲啊!'],
},
});
expect(JSON.parse(JSON.stringify(response))).toMatchObject({
imageSrc: '/generated-bark-battle-assets/draft/opponent/image.webp',
prompt: '后端拼装后的对手形象 prompt',
});
});
test('finish accepted player_win fixture exposes backend adjudication result', () => {

View File

@@ -16,31 +16,65 @@ export type BarkBattleFinishStatus =
export type BarkBattlePlayTypeId = 'bark-battle';
export const BARK_BATTLE_ASSET_SLOTS = [
'player-character',
'opponent-character',
'ui-background',
] as const;
export type BarkBattleAssetSlot = (typeof BARK_BATTLE_ASSET_SLOTS)[number];
export interface BarkBattleReplacementConfig {
playerCharacterImageSrc?: string;
opponentCharacterImageSrc?: string;
uiBackgroundImageSrc?: string;
barkSoundSrc?: string;
}
export type BarkBattleOnomatopoeia = string[];
export interface BarkBattleConfigEditorPayload extends BarkBattleReplacementConfig {
title: string;
description?: string;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
themeDescription: string;
playerImageDescription: string;
opponentImageDescription: string;
onomatopoeia?: BarkBattleOnomatopoeia;
difficultyPreset: BarkBattleDifficultyPreset;
leaderboardEnabled: boolean;
}
export interface BarkBattleDraftCreateRequest extends BarkBattleConfigEditorPayload {}
export interface BarkBattleDraftConfigUpdateRequest
extends BarkBattleConfigEditorPayload {
draftId: string;
workId?: string | null;
configVersion?: number;
rulesetVersion?: string;
}
export interface BarkBattleWorkPublishRequest {
draftId: string;
workId: string;
publishedSnapshot?: BarkBattleConfigEditorPayload;
}
export interface BarkBattleImageAssetGenerateRequest {
slot: BarkBattleAssetSlot;
draftId?: string | null;
config: BarkBattleConfigEditorPayload;
}
export interface BarkBattleGeneratedImageAsset {
imageSrc: string;
assetId: string;
sourceType?: 'generated' | string;
model: string;
size: string;
taskId: string;
prompt: string;
actualPrompt?: string;
}
export interface BarkBattleDraftConfig extends BarkBattleConfigEditorPayload {
draftId: string;
workId?: string;
@@ -57,19 +91,62 @@ export interface BarkBattlePublishedConfig {
playTypeId: BarkBattlePlayTypeId;
title: string;
description?: string;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
themeDescription: string;
playerImageDescription: string;
opponentImageDescription: string;
onomatopoeia?: BarkBattleOnomatopoeia;
playerCharacterImageSrc?: string;
opponentCharacterImageSrc?: string;
uiBackgroundImageSrc?: string;
barkSoundSrc?: string;
difficultyPreset: BarkBattleDifficultyPreset;
leaderboardEnabled: boolean;
updatedAt: string;
publishedAt: string;
}
export type BarkBattleWorkStatus = 'draft' | 'published';
export type BarkBattleGenerationStatus =
| 'pending_assets'
| 'ready'
| 'partial_failed'
| string;
export interface BarkBattleWorkSummary {
workId: string;
draftId?: string | null;
ownerUserId: string;
authorDisplayName: string;
title: string;
summary: string;
themeDescription: string;
playerImageDescription: string;
opponentImageDescription: string;
onomatopoeia?: BarkBattleOnomatopoeia;
playerCharacterImageSrc?: string | null;
opponentCharacterImageSrc?: string | null;
uiBackgroundImageSrc?: string | null;
difficultyPreset: BarkBattleDifficultyPreset;
status: BarkBattleWorkStatus;
generationStatus?: BarkBattleGenerationStatus | null;
publishReady: boolean;
playCount: number;
finishCount?: number;
winCount?: number;
drawCount?: number;
lossCount?: number;
recentPlayCount7d?: number;
updatedAt: string;
publishedAt?: string | null;
}
export interface BarkBattleWorksResponse {
items: BarkBattleWorkSummary[];
}
export interface BarkBattleWorkDetailResponse {
item: BarkBattleWorkSummary;
}
export interface BarkBattleRuntimeConfig {
workId: string;
configVersion: number;
@@ -81,14 +158,13 @@ export interface BarkBattleRuntimeConfig {
drawThreshold: number;
minBarkGapMs: number;
difficultyPreset: BarkBattleDifficultyPreset;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
themeDescription: string;
playerImageDescription: string;
opponentImageDescription: string;
onomatopoeia?: BarkBattleOnomatopoeia;
playerCharacterImageSrc?: string;
opponentCharacterImageSrc?: string;
uiBackgroundImageSrc?: string;
barkSoundSrc?: string;
leaderboardEnabled: boolean;
updatedAt: string;
}