refactor: 收口 Bark Battle work cache 规则

This commit is contained in:
2026-06-04 04:30:36 +08:00
parent df17f51edf
commit f9f22e5663
7 changed files with 305 additions and 106 deletions

View File

@@ -16,6 +16,14 @@
---
## 2026-06-04 Bark Battle Work Cache 草稿状态收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 仍内联维护 Bark Battle 草稿三图完整性、生成状态归一,以及草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 字段映射,导致结果页试玩、作品架启动和公开详情启动都要理解同一份资产字段清单。
- 决策:扩展 `src/components/platform-entry/barkBattleWorkCache.ts`,以 `hasBarkBattleDraftRequiredImages``resolveBarkBattleDraftGenerationStatus``buildBarkBattlePublishedConfigFromDraft``buildBarkBattlePublishedConfigFromWork``buildBarkBattlePublishSnapshot``mergeBarkBattlePublishedConfigAssets` 收口 Bark Battle 纯规则。平台壳只保留 API、缓存刷新、React state、URL 和 stage 副作用。
- 影响范围Bark Battle 草稿生成完成、结果页保存、草稿试玩、作品架 / 公开详情启动正式 runtime以及后续 Bark Battle 资产字段或 ruleset 默认值调整。
- 验证方式:`npm run test -- src/components/platform-entry/barkBattleWorkCache.test.ts`、针对 Bark Battle Work Cache Module 与平台壳执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md`
## 2026-06-04 Platform Creation Launch Model 收口
- 背景:平台创作入口点击回调曾在 `PlatformEntryFlowShellImpl.tsx` 内联判断 `airp` 占位、隐藏的 `baby-object-match`、未知入口和各玩法工作台启动目标,壳层同时承接入口 ID 规则、启动前准备顺序和副作用。

View File

@@ -67,6 +67,8 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
平台入口个人钱包本地 delta、dashboard 乐观更新与服务端快照对账规则收口到 `src/components/platform-entry/platformProfileWalletDeltaModel.ts`,平台壳只保留 API、ref 与 state 副作用,规则见 [【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md)。
Bark Battle 草稿三图完整性、生成状态归一、发布快照 / 发布回包资产兜底和草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射收口到 `src/components/platform-entry/barkBattleWorkCache.ts`,规则见 [【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md](./technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md)。
RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。
平台入口创作生成通知、pending 作品架占位、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。

View File

@@ -0,0 +1,44 @@
# 【前端架构】Bark Battle Work Cache 草稿状态收口计划
## 背景
`PlatformEntryFlowShellImpl.tsx` 仍内联维护 Bark Battle 草稿三图完整性、生成状态归一,以及草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射。壳层因此需要同时理解三图资产字段、`partial_failed``pending_assets` 的差异、`publishedAt` 兜底和草稿试玩配置默认值。
这些规则属于 Bark Battle 作品摘要与草稿缓存的纯模型。若留在平台壳层,后续发布、作品架刷新、公开详情启动或草稿试玩都容易重复一份字段清单。
## 决策
扩展 `src/components/platform-entry/barkBattleWorkCache.ts`,作为 Bark Battle Work Cache **Module** 继续承接作品摘要缓存和草稿 runtime 配置规则。新增公开 **Interface**
- `hasBarkBattleDraftRequiredImages(draft)`:判断草稿是否已具备玩家形象、对手形象和竞技背景三图。
- `resolveBarkBattleDraftGenerationStatus(draft, partialFailed)`:三图齐备返回 `ready`,否则按是否部分失败返回 `partial_failed``pending_assets`
- `buildBarkBattlePublishedConfigFromDraft(draft)`:把草稿结果页试玩所需配置映射为 `BarkBattlePublishedConfig`
- `buildBarkBattlePublishedConfigFromWork(work)`:把作品架 / 公开详情启动正式 runtime 所需配置映射为 `BarkBattlePublishedConfig`
- `buildBarkBattlePublishSnapshot(draft)`:拼装发布接口所需的最终草稿快照。
- `mergeBarkBattlePublishedConfigAssets(published, draft)`:发布回包缺少三图字段时沿用结果页草稿图。
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它只负责 API 请求、React state、URL、运行态 stage 切换和错误提示,不再持有 Bark Battle 三图完整性与 runtime config 字段清单。
## Interface 约束
- 草稿三图必须同时具备 `playerCharacterImageSrc``opponentCharacterImageSrc``uiBackgroundImageSrc` 的非空值,才视为 `ready`
- 未齐三图且 `partialFailed=true` 时返回 `partial_failed`,否则返回 `pending_assets`
- 草稿试玩配置的 `workId` 优先使用草稿稳定 `workId`,缺失时回退 `draftId`
- 草稿试玩配置的 `configVersion``rulesetVersion` 使用草稿值,缺失时回退 `1``bark-battle-ruleset-v1`
- 已发布作品配置的 `publishedAt` 缺失时回退 `updatedAt`,保持旧 runtime 启动语义。
- 发布快照只携带草稿已有的三图字段,不凭空补空字符串。
- 发布接口回包缺少三图字段时,结果页草稿图继续作为 runtime 和作品摘要的兜底。
## Depth / Leverage / Locality
- **Depth**:壳层传入草稿或作品摘要,即可得到生成状态或 runtime 配置;字段归一、默认值和三图完整性藏入 Module Implementation。
- **Leverage**:结果页试玩、作品架启动、公开详情启动和缓存刷新可复用同一组 Bark Battle 规则。
- **Locality**Bark Battle 资产完整性与配置映射集中到纯测试面,后续变更三图字段或规则集默认值时无需搜索巨型平台壳。
## 验收
- `npm run test -- src/components/platform-entry/barkBattleWorkCache.test.ts`
- `npx eslint --max-warnings 0 src/components/platform-entry/barkBattleWorkCache.ts src/components/platform-entry/barkBattleWorkCache.test.ts`
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
- `npm run typecheck`
- `npm run check:encoding`

View File

@@ -306,8 +306,8 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
- 结果页:围绕三图槽位展示错误态与已生成结果,只保留单槽重试、重新生成和上传,不再提供一次生成按钮、音频配置入口或排名配置;生成回写 `partial_failed` 时作品架不再显示整卡“生成中”遮罩,由结果页槽位错误承接失败。
- 手动上传:结果页通过平台资产直传 `/api/assets/direct-upload-tickets``/api/assets/objects/confirm` 写入私有资产,再把返回的历史 generated 路径写回草稿配置。
- 发布:结果页确认后必须携带草稿返回的同一个 `workId` 和结果页最终 `publishedSnapshot` 调用 `POST /api/creation/bark-battle/works/publish`SpacetimeDB 发布态的 `config_json` 必须使用该最终快照works summary 若拿到 `publishedSnapshotJson` 也优先使用最终快照映射封面三图。发布成功后先进入统一作品详情页,再由详情页进入正式 runtime缺少 `workId` 的旧草稿状态需要重新生成草稿。
- 作品架Bark Battle 草稿 / 已发布列表优先读取后端 `/works`,但创建、生成完成、保存或发布后的本地摘要必须在后端 read model 尚未回读到同 `workId` 前继续保留;创作中心作品架同时接入 pending shelf 兜底,避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿 / 已发布”中消失。
- 试玩与正式 runtime草稿试玩使用 `runtimeMode=draft` 和 mock 输入,不写正式 run正式 runtime 使用 `runtimeMode=published`,进入运行态后直接申请真实麦克风权限,授权成功后立刻进入倒计时,启动对局时调用 `POST /api/runtime/bark-battle/works/{workId}/runs` 登记 start run并以返回的 `runtimeConfig` 作为本局前端规则参数;结算时调用 `POST /api/runtime/bark-battle/runs/{runId}/finish` 写入基础统计派生指标;对局会在能量条推到任一侧边界时提前结算并弹出独立结算弹窗,运行态内固定提供返回按钮。
- 作品架Bark Battle 草稿 / 已发布列表优先读取后端 `/works`,但创建、生成完成、保存或发布后的本地摘要必须在后端 read model 尚未回读到同 `workId` 前继续保留;创作中心作品架同时接入 pending shelf 兜底,避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿 / 已发布”中消失。草稿三图完整性、`pending_assets` / `partial_failed` / `ready` 生成状态归一和作品摘要合并规则统一由 `barkBattleWorkCache.ts` 承接,平台壳只执行读取、刷新与 React state 副作用。
- 试玩与正式 runtime草稿试玩使用 `runtimeMode=draft` 和 mock 输入,不写正式 run正式 runtime 使用 `runtimeMode=published`,进入运行态后直接申请真实麦克风权限,授权成功后立刻进入倒计时,启动对局时调用 `POST /api/runtime/bark-battle/works/{workId}/runs` 登记 start run并以返回的 `runtimeConfig` 作为本局前端规则参数;结算时调用 `POST /api/runtime/bark-battle/runs/{runId}/finish` 写入基础统计派生指标;对局会在能量条推到任一侧边界时提前结算并弹出独立结算弹窗,运行态内固定提供返回按钮。发布快照拼装、发布回包缺图时沿用草稿图,以及草稿 / 已发布作品进入前端 runtime 前的 `BarkBattlePublishedConfig` 映射也统一由 `barkBattleWorkCache.ts` 提供,缺失 `publishedAt` 时仍按 `updatedAt` 兜底。
支持的创作者可替换内容:

View File

@@ -381,10 +381,14 @@ import {
} from '../visual-novel-creation/visualNovelEntryGeneration';
import { createMockVisualNovelRunFromDraft } from '../visual-novel-runtime/visualNovelMockData';
import {
type BarkBattleGenerationStatus,
buildBarkBattlePublishedConfigFromDraft,
buildBarkBattlePublishedConfigFromWork,
buildBarkBattlePublishSnapshot,
buildBarkBattleWorkSummaryFromDraft,
mergeBarkBattlePublishedConfigAssets,
mergeBarkBattleWorksByWorkId,
mergeBarkBattleWorkSummary,
resolveBarkBattleDraftGenerationStatus,
shouldPreserveLocalBarkBattleWorkOnRefresh,
} from './barkBattleWorkCache';
import {
@@ -712,30 +716,6 @@ const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
function mapBarkBattleWorkToPublishedConfig(
work: BarkBattleWorkSummary,
): BarkBattlePublishedConfig {
return {
workId: work.workId,
draftId: work.draftId ?? null,
configVersion: 1,
rulesetVersion: 'bark-battle-ruleset-v1',
playTypeId: 'bark-battle',
title: work.title,
description: work.summary,
themeDescription: work.themeDescription,
playerImageDescription: work.playerImageDescription,
opponentImageDescription: work.opponentImageDescription,
onomatopoeia: work.onomatopoeia,
playerCharacterImageSrc: work.playerCharacterImageSrc ?? undefined,
opponentCharacterImageSrc: work.opponentCharacterImageSrc ?? undefined,
uiBackgroundImageSrc: work.uiBackgroundImageSrc ?? undefined,
difficultyPreset: work.difficultyPreset,
updatedAt: work.updatedAt,
publishedAt: work.publishedAt ?? work.updatedAt,
};
}
function mapVisualNovelWorkDetailToSession(
work: VisualNovelWorkDetail,
): VisualNovelAgentSessionSnapshot {
@@ -1004,24 +984,6 @@ function mergeBigFishWorkSummary(
: current;
}
function hasBarkBattleDraftRequiredImages(draft: BarkBattleDraftConfig) {
return Boolean(
draft.playerCharacterImageSrc?.trim() &&
draft.opponentCharacterImageSrc?.trim() &&
draft.uiBackgroundImageSrc?.trim(),
);
}
function resolveBarkBattleDraftGenerationStatus(
draft: BarkBattleDraftConfig,
partialFailed: boolean,
): BarkBattleGenerationStatus {
if (hasBarkBattleDraftRequiredImages(draft)) {
return 'ready';
}
return partialFailed ? 'partial_failed' : 'pending_assets';
}
async function resolvePublicWorkAuthorSummary(
entry: PlatformPublicGalleryCard,
): Promise<PublicUserSummary | null> {
@@ -6190,39 +6152,18 @@ export function PlatformEntryFlowShellImpl({
],
);
const buildBarkBattleDraftRuntimeConfig = useCallback(
(draft: BarkBattleDraftConfig): BarkBattlePublishedConfig => ({
workId: draft.workId ?? draft.draftId,
draftId: draft.draftId,
configVersion: draft.configVersion ?? 1,
rulesetVersion: draft.rulesetVersion ?? 'bark-battle-ruleset-v1',
playTypeId: 'bark-battle',
title: draft.title,
description: draft.description,
themeDescription: draft.themeDescription,
playerImageDescription: draft.playerImageDescription,
opponentImageDescription: draft.opponentImageDescription,
onomatopoeia: draft.onomatopoeia,
playerCharacterImageSrc: draft.playerCharacterImageSrc,
opponentCharacterImageSrc: draft.opponentCharacterImageSrc,
uiBackgroundImageSrc: draft.uiBackgroundImageSrc,
difficultyPreset: draft.difficultyPreset,
updatedAt: draft.updatedAt,
publishedAt: draft.updatedAt,
}),
[],
);
const testBarkBattleDraft = useCallback(
(draft: BarkBattleDraftConfig) => {
setBarkBattleError(null);
setBarkBattleRuntimeMode('draft');
setBarkBattleRuntimeReturnStage('bark-battle-result');
setBarkBattlePublishedConfig(buildBarkBattleDraftRuntimeConfig(draft));
setBarkBattlePublishedConfig(
buildBarkBattlePublishedConfigFromDraft(draft),
);
selectionStageRef.current = 'bark-battle-runtime';
setSelectionStage('bark-battle-runtime');
},
[buildBarkBattleDraftRuntimeConfig, setSelectionStage],
[setSelectionStage],
);
const publishBarkBattleDraft = useCallback(
@@ -6237,39 +6178,15 @@ export function PlatformEntryFlowShellImpl({
}
setIsBarkBattleBusy(true);
try {
const publishedSnapshot: BarkBattleConfigEditorPayload = {
title: draft.title,
description: draft.description,
themeDescription: draft.themeDescription,
playerImageDescription: draft.playerImageDescription,
opponentImageDescription: draft.opponentImageDescription,
onomatopoeia: draft.onomatopoeia,
...(draft.playerCharacterImageSrc
? { playerCharacterImageSrc: draft.playerCharacterImageSrc }
: {}),
...(draft.opponentCharacterImageSrc
? { opponentCharacterImageSrc: draft.opponentCharacterImageSrc }
: {}),
...(draft.uiBackgroundImageSrc
? { uiBackgroundImageSrc: draft.uiBackgroundImageSrc }
: {}),
difficultyPreset: draft.difficultyPreset,
};
const published = await publishBarkBattleWork({
draftId: draft.draftId,
workId,
publishedSnapshot,
publishedSnapshot: buildBarkBattlePublishSnapshot(draft),
});
const publishedWithAssets: BarkBattlePublishedConfig = {
...published,
playerCharacterImageSrc:
published.playerCharacterImageSrc ?? draft.playerCharacterImageSrc,
opponentCharacterImageSrc:
published.opponentCharacterImageSrc ??
draft.opponentCharacterImageSrc,
uiBackgroundImageSrc:
published.uiBackgroundImageSrc ?? draft.uiBackgroundImageSrc,
};
const publishedWithAssets = mergeBarkBattlePublishedConfigAssets(
published,
draft,
);
const publicWorkCode = buildBarkBattlePublicWorkCode(
publishedWithAssets.workId,
);
@@ -11597,7 +11514,9 @@ export function PlatformEntryFlowShellImpl({
setBarkBattleError(null);
setBarkBattleGenerationPartialFailed(false);
setBarkBattleRuntimeMode('published');
setBarkBattlePublishedConfig(mapBarkBattleWorkToPublishedConfig(item));
setBarkBattlePublishedConfig(
buildBarkBattlePublishedConfigFromWork(item),
);
setBarkBattleRuntimeReturnStage(returnStage);
try {
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(

View File

@@ -1,9 +1,18 @@
import { expect, test } from 'vitest';
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type {
BarkBattleDraftConfig,
BarkBattlePublishedConfig,
BarkBattleWorkSummary,
} from '../../../packages/shared/src/contracts/barkBattle';
import {
buildBarkBattlePublishedConfigFromDraft,
buildBarkBattlePublishedConfigFromWork,
buildBarkBattlePublishSnapshot,
mergeBarkBattlePublishedConfigAssets,
mergeBarkBattleWorksByWorkId,
mergeBarkBattleWorkSummary,
resolveBarkBattleDraftGenerationStatus,
shouldPreserveLocalBarkBattleWorkOnRefresh,
} from './barkBattleWorkCache';
@@ -34,6 +43,29 @@ function buildBarkBattleWork(
};
}
function buildBarkBattleDraft(
overrides: Partial<BarkBattleDraftConfig> = {},
): BarkBattleDraftConfig {
return {
draftId: 'bark-battle-draft-1',
workId: 'BB-cache-race-12345678',
configVersion: 2,
rulesetVersion: 'bark-battle-ruleset-v2',
title: '汪汪测试杯',
description: '测试声浪赛',
themeDescription: '阳光草坪声浪竞技场',
playerImageDescription: '戴红色围巾的柯基选手',
opponentImageDescription: '蓝色护目镜哈士奇对手',
onomatopoeia: ['汪', '破阵'],
playerCharacterImageSrc: '/generated-bark-battle/player.png',
opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
uiBackgroundImageSrc: '/generated-bark-battle/background.png',
difficultyPreset: 'normal',
updatedAt: '2026-05-21T10:00:00.000Z',
...overrides,
};
}
test('preserves local published bark battle when refresh only returns same work draft', () => {
const published = buildBarkBattleWork({
status: 'published',
@@ -106,3 +138,95 @@ test('preserves local ready bark battle draft when refresh has not returned it y
expect(merged[0]?.generationStatus).toBe('ready');
});
test('resolves bark battle draft generation status from required images', () => {
expect(
resolveBarkBattleDraftGenerationStatus(
buildBarkBattleDraft({ uiBackgroundImageSrc: undefined }),
false,
),
).toBe('pending_assets');
expect(
resolveBarkBattleDraftGenerationStatus(
buildBarkBattleDraft({ opponentCharacterImageSrc: '' }),
true,
),
).toBe('partial_failed');
expect(resolveBarkBattleDraftGenerationStatus(buildBarkBattleDraft(), true)).toBe(
'ready',
);
});
test('builds draft runtime config with stable defaults', () => {
const config = buildBarkBattlePublishedConfigFromDraft(
buildBarkBattleDraft({
workId: undefined,
configVersion: undefined,
rulesetVersion: undefined,
}),
);
expect(config.workId).toBe('bark-battle-draft-1');
expect(config.draftId).toBe('bark-battle-draft-1');
expect(config.configVersion).toBe(1);
expect(config.rulesetVersion).toBe('bark-battle-ruleset-v1');
expect(config.playTypeId).toBe('bark-battle');
expect(config.publishedAt).toBe('2026-05-21T10:00:00.000Z');
});
test('builds work runtime config with publishedAt fallback', () => {
const config = buildBarkBattlePublishedConfigFromWork(
buildBarkBattleWork({ publishedAt: null }),
);
expect(config.workId).toBe('BB-cache-race-12345678');
expect(config.description).toBe('测试声浪赛');
expect(config.publishedAt).toBe('2026-05-21T10:00:00.000Z');
expect(config.playerCharacterImageSrc).toBe('/generated-bark-battle/player.png');
});
test('builds publish snapshot without empty asset fields', () => {
const snapshot = buildBarkBattlePublishSnapshot(
buildBarkBattleDraft({
playerCharacterImageSrc: '',
opponentCharacterImageSrc: undefined,
}),
);
expect(snapshot).not.toHaveProperty('playerCharacterImageSrc');
expect(snapshot).not.toHaveProperty('opponentCharacterImageSrc');
expect(snapshot.uiBackgroundImageSrc).toBe(
'/generated-bark-battle/background.png',
);
});
test('merges draft assets into published config when publish response omits them', () => {
const draft = buildBarkBattleDraft();
const published: BarkBattlePublishedConfig = {
workId: 'BB-cache-race-12345678',
draftId: 'bark-battle-draft-1',
configVersion: 2,
rulesetVersion: 'bark-battle-ruleset-v2',
playTypeId: 'bark-battle',
title: '汪汪测试杯',
description: '测试声浪赛',
themeDescription: '阳光草坪声浪竞技场',
playerImageDescription: '戴红色围巾的柯基选手',
opponentImageDescription: '蓝色护目镜哈士奇对手',
onomatopoeia: ['汪', '破阵'],
difficultyPreset: 'normal',
updatedAt: '2026-05-21T10:01:00.000Z',
publishedAt: '2026-05-21T10:01:00.000Z',
};
const merged = mergeBarkBattlePublishedConfigAssets(published, draft);
expect(merged.playerCharacterImageSrc).toBe(
'/generated-bark-battle/player.png',
);
expect(merged.opponentCharacterImageSrc).toBe(
'/generated-bark-battle/opponent.png',
);
expect(merged.uiBackgroundImageSrc).toBe(
'/generated-bark-battle/background.png',
);
});

View File

@@ -1,6 +1,8 @@
import type {
BarkBattleConfigEditorPayload,
BarkBattleDraftConfig,
BarkBattleGenerationStatus as SharedBarkBattleGenerationStatus,
BarkBattlePublishedConfig,
BarkBattleWorkSummary,
} from '../../../packages/shared/src/contracts/barkBattle';
@@ -36,6 +38,110 @@ export function hasBarkBattleSummaryRequiredImages(item: BarkBattleWorkSummary)
);
}
export function hasBarkBattleDraftRequiredImages(draft: BarkBattleDraftConfig) {
return Boolean(
draft.playerCharacterImageSrc?.trim() &&
draft.opponentCharacterImageSrc?.trim() &&
draft.uiBackgroundImageSrc?.trim(),
);
}
export function resolveBarkBattleDraftGenerationStatus(
draft: BarkBattleDraftConfig,
partialFailed: boolean,
): BarkBattleGenerationStatus {
if (hasBarkBattleDraftRequiredImages(draft)) {
return 'ready';
}
return partialFailed ? 'partial_failed' : 'pending_assets';
}
export function buildBarkBattlePublishedConfigFromDraft(
draft: BarkBattleDraftConfig,
): BarkBattlePublishedConfig {
return {
workId: draft.workId ?? draft.draftId,
draftId: draft.draftId,
configVersion: draft.configVersion ?? 1,
rulesetVersion: draft.rulesetVersion ?? 'bark-battle-ruleset-v1',
playTypeId: 'bark-battle',
title: draft.title,
description: draft.description,
themeDescription: draft.themeDescription,
playerImageDescription: draft.playerImageDescription,
opponentImageDescription: draft.opponentImageDescription,
onomatopoeia: draft.onomatopoeia,
playerCharacterImageSrc: draft.playerCharacterImageSrc,
opponentCharacterImageSrc: draft.opponentCharacterImageSrc,
uiBackgroundImageSrc: draft.uiBackgroundImageSrc,
difficultyPreset: draft.difficultyPreset,
updatedAt: draft.updatedAt,
publishedAt: draft.updatedAt,
};
}
export function buildBarkBattlePublishSnapshot(
draft: BarkBattleDraftConfig,
): BarkBattleConfigEditorPayload {
return {
title: draft.title,
description: draft.description,
themeDescription: draft.themeDescription,
playerImageDescription: draft.playerImageDescription,
opponentImageDescription: draft.opponentImageDescription,
onomatopoeia: draft.onomatopoeia,
...(draft.playerCharacterImageSrc
? { playerCharacterImageSrc: draft.playerCharacterImageSrc }
: {}),
...(draft.opponentCharacterImageSrc
? { opponentCharacterImageSrc: draft.opponentCharacterImageSrc }
: {}),
...(draft.uiBackgroundImageSrc
? { uiBackgroundImageSrc: draft.uiBackgroundImageSrc }
: {}),
difficultyPreset: draft.difficultyPreset,
};
}
export function mergeBarkBattlePublishedConfigAssets(
published: BarkBattlePublishedConfig,
draft: BarkBattleDraftConfig,
): BarkBattlePublishedConfig {
return {
...published,
playerCharacterImageSrc:
published.playerCharacterImageSrc ?? draft.playerCharacterImageSrc,
opponentCharacterImageSrc:
published.opponentCharacterImageSrc ?? draft.opponentCharacterImageSrc,
uiBackgroundImageSrc:
published.uiBackgroundImageSrc ?? draft.uiBackgroundImageSrc,
};
}
export function buildBarkBattlePublishedConfigFromWork(
work: BarkBattleWorkSummary,
): BarkBattlePublishedConfig {
return {
workId: work.workId,
draftId: work.draftId ?? null,
configVersion: 1,
rulesetVersion: 'bark-battle-ruleset-v1',
playTypeId: 'bark-battle',
title: work.title,
description: work.summary,
themeDescription: work.themeDescription,
playerImageDescription: work.playerImageDescription,
opponentImageDescription: work.opponentImageDescription,
onomatopoeia: work.onomatopoeia,
playerCharacterImageSrc: work.playerCharacterImageSrc ?? undefined,
opponentCharacterImageSrc: work.opponentCharacterImageSrc ?? undefined,
uiBackgroundImageSrc: work.uiBackgroundImageSrc ?? undefined,
difficultyPreset: work.difficultyPreset,
updatedAt: work.updatedAt,
publishedAt: work.publishedAt ?? work.updatedAt,
};
}
export function shouldPreserveLocalBarkBattleWorkOnRefresh(
item: BarkBattleWorkSummary,
refreshed: readonly BarkBattleWorkSummary[],
@@ -85,11 +191,7 @@ export function buildBarkBattleWorkSummaryFromDraft(
difficultyPreset: draft.difficultyPreset,
status: 'draft',
generationStatus,
publishReady: Boolean(
draft.playerCharacterImageSrc?.trim() &&
draft.opponentCharacterImageSrc?.trim() &&
draft.uiBackgroundImageSrc?.trim(),
),
publishReady: hasBarkBattleDraftRequiredImages(draft),
playCount: 0,
updatedAt: draft.updatedAt,
publishedAt: null,