feat: wire bark battle platform loop
Some checks are pending
CI / verify (pull_request) Waiting to run

This commit is contained in:
2026-05-14 18:20:46 +08:00
parent 8c6ec9e6e4
commit 1d7ef7e4b6
73 changed files with 7933 additions and 107 deletions

View File

@@ -0,0 +1,130 @@
import { describe, expect, test } from 'vitest';
import {
BARK_BATTLE_DIFFICULTY_PRESETS,
type BarkBattleDraftConfig,
type BarkBattleFinishResponse,
type BarkBattlePersonalBestSummary,
type BarkBattleWorkStats,
} from './barkBattle';
describe('Bark Battle shared contracts', () => {
test('default draft config fixture uses normal difficulty and camelCase fields', () => {
const draft: BarkBattleDraftConfig = {
draftId: 'draft-bark-1',
title: '汪汪声浪挑战',
description: '轻配置草稿',
themePreset: 'city-park',
playerDogSkinPreset: 'corgi',
opponentDogSkinPreset: 'husky',
difficultyPreset: 'normal',
leaderboardEnabled: true,
updatedAt: '2026-05-13T03:00:00.000Z',
};
expect(BARK_BATTLE_DIFFICULTY_PRESETS).toEqual(['easy', 'normal', 'hard']);
expect(draft.difficultyPreset).toBe('normal');
expect(Object.keys(draft)).toEqual([
'draftId',
'title',
'description',
'themePreset',
'playerDogSkinPreset',
'opponentDogSkinPreset',
'difficultyPreset',
'leaderboardEnabled',
'updatedAt',
]);
});
test('finish accepted player_win fixture exposes backend adjudication result', () => {
const response: BarkBattleFinishResponse = {
status: 'accepted',
runId: 'run-bark-1',
workId: 'work-bark-1',
configVersion: 3,
rulesetVersion: 'bark-battle-ruleset-v1',
difficultyPreset: 'hard',
serverResult: 'player_win',
scoreSummary: {
finalEnergy: 87,
triggerCount: 42,
maxVolume: 0.96,
averageVolume: 0.61,
comboMax: 9,
durationMs: 30000,
},
leaderboardScore: 870429630,
antiCheatFlags: [],
updatedAt: '2026-05-13T03:00:30.000Z',
};
expect(response.status).toBe('accepted');
expect(response.serverResult).toBe('player_win');
expect(response.scoreSummary.finalEnergy).toBe(87);
expect(response.antiCheatFlags).toEqual([]);
});
test('work stats fixture tracks starts, finishes, result counts, flags and energy summary', () => {
const stats: BarkBattleWorkStats = {
workId: 'work-bark-1',
configVersion: 3,
rulesetVersion: 'bark-battle-ruleset-v1',
difficultyPreset: 'normal',
playStartCount: 18,
finishCount: 15,
winCount: 8,
drawCount: 2,
lossCount: 5,
flaggedCount: 1,
leaderboardEntryCount: 7,
bestLeaderboardScore: 930389410,
bestFinalEnergy: 93,
averageFinalEnergy: 41.25,
updatedAt: '2026-05-13T04:00:00.000Z',
};
expect(stats.playStartCount).toBe(18);
expect(stats.finishCount).toBe(15);
expect(stats.winCount + stats.drawCount + stats.lossCount).toBe(15);
expect(stats.flaggedCount).toBe(1);
expect(stats.bestFinalEnergy).toBeGreaterThan(stats.averageFinalEnergy);
});
test('optional score fields may be omitted instead of serialized as null', () => {
const finishWithoutLeaderboard: BarkBattleFinishResponse = {
status: 'accepted',
runId: 'run-bark-no-rank',
workId: 'work-bark-1',
configVersion: 3,
rulesetVersion: 'bark-battle-ruleset-v1',
difficultyPreset: 'normal',
serverResult: 'draw',
scoreSummary: {
finalEnergy: 50,
triggerCount: 12,
maxVolume: 0.7,
averageVolume: 0.5,
comboMax: 3,
durationMs: 30000,
},
antiCheatFlags: [],
updatedAt: '2026-05-13T03:00:30.000Z',
};
const personalBestWithoutWin: BarkBattlePersonalBestSummary = {
workId: 'work-bark-1',
rulesetVersion: 'bark-battle-ruleset-v1',
difficultyPreset: 'normal',
winCount: 0,
drawCount: 1,
lossCount: 2,
finishCount: 3,
updatedAt: '2026-05-13T04:00:00.000Z',
};
expect('leaderboardScore' in finishWithoutLeaderboard).toBe(false);
expect('bestLeaderboardScore' in personalBestWithoutWin).toBe(false);
expect('bestFinalEnergy' in personalBestWithoutWin).toBe(false);
});
});

View File

@@ -0,0 +1,218 @@
export const BARK_BATTLE_DIFFICULTY_PRESETS = [
'easy',
'normal',
'hard',
] as const;
export type BarkBattleDifficultyPreset =
(typeof BARK_BATTLE_DIFFICULTY_PRESETS)[number];
export type BarkBattleServerResult = 'player_win' | 'opponent_win' | 'draw';
export type BarkBattleFinishStatus =
| 'accepted'
| 'accepted_with_flags'
| 'rejected';
export type BarkBattlePlayTypeId = 'bark-battle';
export interface BarkBattleConfigEditorPayload {
title: string;
description?: string;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
difficultyPreset: BarkBattleDifficultyPreset;
leaderboardEnabled: boolean;
}
export interface BarkBattleDraftCreateRequest extends BarkBattleConfigEditorPayload {}
export interface BarkBattleWorkPublishRequest {
draftId: string;
workId?: string;
publishedSnapshot?: BarkBattleConfigEditorPayload;
}
export interface BarkBattleDraftConfig extends BarkBattleConfigEditorPayload {
draftId: string;
workId?: string;
configVersion?: number;
rulesetVersion?: string;
updatedAt: string;
}
export interface BarkBattlePublishedConfig {
workId: string;
draftId?: string | null;
configVersion: number;
rulesetVersion: string;
playTypeId: BarkBattlePlayTypeId;
title: string;
description?: string;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
difficultyPreset: BarkBattleDifficultyPreset;
leaderboardEnabled: boolean;
updatedAt: string;
publishedAt: string;
}
export interface BarkBattleRuntimeConfig {
workId: string;
configVersion: number;
rulesetVersion: string;
playTypeId: BarkBattlePlayTypeId;
durationMs: number;
energyMin: number;
energyMax: number;
drawThreshold: number;
minBarkGapMs: number;
difficultyPreset: BarkBattleDifficultyPreset;
themePreset: string;
playerDogSkinPreset: string;
opponentDogSkinPreset: string;
leaderboardEnabled: boolean;
updatedAt: string;
}
export interface BarkBattleRunStartRequest {
workId: string;
configVersion?: number;
sourceRoute?: string;
clientRuntimeVersion?: string;
}
export interface BarkBattleRunStartResponse {
runId: string;
runToken: string;
workId: string;
configVersion: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
runtimeConfig: BarkBattleRuntimeConfig;
serverStartedAt: string;
expiresAt: string;
}
export interface BarkBattleDerivedMetrics {
triggerCount: number;
maxVolume: number;
averageVolume: number;
finalEnergy: number;
comboMax: number;
}
export interface BarkBattleRunFinishRequest {
runId: string;
runToken: string;
workId: string;
configVersion: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
clientStartedAt: string;
clientFinishedAt: string;
durationMs: number;
derivedMetrics: BarkBattleDerivedMetrics;
clientResult?: BarkBattleServerResult;
sampleDigest?: string;
clientRuntimeVersion?: string;
}
export interface BarkBattleScoreSummary extends BarkBattleDerivedMetrics {
durationMs: number;
}
export interface BarkBattleFinishResponse {
status: BarkBattleFinishStatus;
runId: string;
workId: string;
configVersion: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
serverResult: BarkBattleServerResult;
scoreSummary: BarkBattleScoreSummary;
leaderboardScore?: number;
antiCheatFlags: string[];
updatedAt: string;
}
export interface BarkBattleLeaderboardEntry {
rank: number;
runId: string;
workId: string;
configVersion: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
displayName: string;
serverResult: BarkBattleServerResult;
scoreSummary: BarkBattleScoreSummary;
leaderboardScore: number;
updatedAt: string;
}
export interface BarkBattleLeaderboardResponse {
workId: string;
configVersion?: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
entries: BarkBattleLeaderboardEntry[];
viewerBest?: BarkBattleLeaderboardEntry | null;
updatedAt: string;
}
export interface BarkBattlePersonalHistoryItem {
runId: string;
workId: string;
configVersion: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
serverResult: BarkBattleServerResult;
scoreSummary: BarkBattleScoreSummary;
leaderboardScore?: number;
antiCheatFlags: string[];
updatedAt: string;
}
export interface BarkBattlePersonalBestSummary {
workId: string;
configVersion?: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
bestLeaderboardScore?: number;
bestFinalEnergy?: number;
bestTriggerCount?: number;
bestMaxVolume?: number;
winCount: number;
drawCount: number;
lossCount: number;
finishCount: number;
updatedAt: string;
}
export interface BarkBattlePersonalHistoryResponse {
workId?: string;
difficultyPreset?: BarkBattleDifficultyPreset;
items: BarkBattlePersonalHistoryItem[];
bestSummary?: BarkBattlePersonalBestSummary | null;
updatedAt: string;
}
export interface BarkBattleWorkStats {
workId: string;
configVersion?: number;
rulesetVersion: string;
difficultyPreset: BarkBattleDifficultyPreset;
playStartCount: number;
finishCount: number;
winCount: number;
drawCount: number;
lossCount: number;
flaggedCount: number;
leaderboardEntryCount: number;
bestLeaderboardScore?: number;
bestFinalEnergy?: number;
averageFinalEnergy?: number;
updatedAt: string;
}

View File

@@ -3,3 +3,4 @@ export type * from './creationAudio';
export type * from './hyper3d';
export type * from './puzzleCreativeTemplate';
export type * from './visualNovel';
export type * from './barkBattle';