feat: wire bark battle platform loop
Some checks are pending
CI / verify (pull_request) Waiting to run
Some checks are pending
CI / verify (pull_request) Waiting to run
This commit is contained in:
130
packages/shared/src/contracts/barkBattle.test.ts
Normal file
130
packages/shared/src/contracts/barkBattle.test.ts
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
218
packages/shared/src/contracts/barkBattle.ts
Normal file
218
packages/shared/src/contracts/barkBattle.ts
Normal 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;
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export type * from './creationAudio';
|
||||
export type * from './hyper3d';
|
||||
export type * from './puzzleCreativeTemplate';
|
||||
export type * from './visualNovel';
|
||||
export type * from './barkBattle';
|
||||
|
||||
Reference in New Issue
Block a user