refactor: 收口抓大鹅运行资料模型
This commit is contained in:
@@ -1265,6 +1265,14 @@
|
|||||||
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
## 2026-06-03 Match3D Runtime Profile 收口
|
||||||
|
|
||||||
|
- 背景:`PlatformEntryFlowShellImpl.tsx` 内仍直接承载抓大鹅公开详情转 work、session draft 转 profile、生成背景资产提升、runtime active profile 选择和 run / profile / public detail 素材优先级,平台壳需要理解抓大鹅生成素材内部结构。
|
||||||
|
- 决策:新增 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts` 作为抓大鹅 runtime profile Module,Interface 收口 `mapPublicWorkDetailToMatch3DWork`、`buildMatch3DProfileFromSession`、`normalizeMatch3DWorkForRuntimeUi`、`mapMatch3DWorksForRuntimeUi`、`promoteMatch3DGeneratedBackgroundAsset`、`hasMatch3DRuntimeAsset`、`hasMatch3DRuntimeBackgroundAsset`、`resolveActiveMatch3DRuntimeProfile` 与 runtime item/background/backgroundImage 解析函数;平台壳只保留启动 run、预加载、路由、错误和 state 编排。
|
||||||
|
- 影响范围:抓大鹅作品架、公开详情试玩、推荐 runtime、正式 runtime 与草稿结果页试玩前素材规范化。
|
||||||
|
- 验证方式:`npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "match3d|抓大鹅"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
|
- 关联文档:`docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
## 2026-06-03 Public Work Presentation 收口
|
## 2026-06-03 Public Work Presentation 收口
|
||||||
|
|
||||||
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
|
抓大鹅 runtime profile 的公开详情转 work、session draft 转 profile、生成背景资产提升和 run/profile/public detail 素材优先级收口到 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91Match3DRuntimeProfile%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
公开作品的玩法类型 label、公开作者 lookup 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品的玩法类型 label、公开作者 lookup 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|||||||
34
docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md
Normal file
34
docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# 【前端架构】Match3D Runtime Profile 收口计划
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
`PlatformEntryFlowShellImpl.tsx` 同时编排抓大鹅创作、作品详情、推荐 runtime 和正式 runtime。运行态启动前的 profile 规范化、公开详情转 work、生成背景资产提升、run / profile / public detail 优先级和 runtime 素材选择原本都在平台壳 **Implementation** 内,导致平台壳必须理解抓大鹅生成素材的内部结构。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
新增 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,作为抓大鹅 runtime profile **Module**。该 **Module** 的 **Interface** 收口为:
|
||||||
|
|
||||||
|
- `mapPublicWorkDetailToMatch3DWork(entry)`:把公开作品详情映射为可启动 runtime 的 Match3D work,并补齐生成背景资产。
|
||||||
|
- `buildMatch3DProfileFromSession(session)`:从创作 session draft 生成 runtime profile。
|
||||||
|
- `normalizeMatch3DWorkForRuntimeUi(profile)` / `mapMatch3DWorksForRuntimeUi(profiles)`:统一作品列表进入 UI / runtime 前的素材规范化。
|
||||||
|
- `promoteMatch3DGeneratedBackgroundAsset(profile)`:从 `generatedBackgroundAsset` 或 `generatedItemAssets[].backgroundAsset` 提升背景图、对象 key 与 prompt。
|
||||||
|
- `hasMatch3DRuntimeAsset(profile.generatedItemAssets)` / `hasMatch3DRuntimeBackgroundAsset(profile)`:统一判断 runtime 是否具备物品与背景素材。
|
||||||
|
- `resolveActiveMatch3DRuntimeProfile(run, runtimeProfile, profile)`:按 run 的 `profileId` 选择当前 profile,避免切屏时误用旧草稿。
|
||||||
|
- `resolveMatch3DRuntimeGeneratedItemAssets(...)`、`resolveMatch3DRuntimeGeneratedBackgroundAsset(...)`、`resolveMatch3DRuntimeBackgroundImageSrc(...)`:统一 run / profile / public detail 的素材优先级。
|
||||||
|
|
||||||
|
`PlatformEntryFlowShellImpl.tsx` 只保留启动 run、预加载、路由、错误和 state 编排;抓大鹅素材规则集中到该 **Module**,提升 **Locality** 与测试 **Leverage**。
|
||||||
|
|
||||||
|
## 约定
|
||||||
|
|
||||||
|
- 公开详情补 runtime 素材时,只有 `profileId` 与 run 匹配才优先使用公开详情;错配时不得污染当前 run。
|
||||||
|
- 当前启动时拿到的 `runtimeProfile` 优先于旧草稿 profile;若 run 指向旧草稿 profile,才使用草稿 profile。
|
||||||
|
- 背景资产提升不得覆盖已有显式 `backgroundImageSrc` / `backgroundImageObjectKey` / `generatedBackgroundAsset`,只补缺。
|
||||||
|
- 本 **Module** 只放纯 profile / asset 规则,不引入启动 run、预加载、URL、状态机或 UI 副作用。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- `npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`
|
||||||
|
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "match3d|抓大鹅"`
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run check:encoding`
|
||||||
|
- 针对新 Module 与测试执行 ESLint;`PlatformEntryFlowShellImpl.tsx` 保留既有 hook dependency warnings,不在本切片扩大处理。
|
||||||
@@ -49,7 +49,6 @@ import type {
|
|||||||
} from '../../../packages/shared/src/contracts/match3dAgent';
|
} from '../../../packages/shared/src/contracts/match3dAgent';
|
||||||
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
|
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
|
||||||
import type {
|
import type {
|
||||||
Match3DGeneratedBackgroundAsset,
|
|
||||||
Match3DGeneratedItemAsset,
|
Match3DGeneratedItemAsset,
|
||||||
Match3DWorkProfile,
|
Match3DWorkProfile,
|
||||||
Match3DWorkSummary,
|
Match3DWorkSummary,
|
||||||
@@ -202,7 +201,6 @@ import {
|
|||||||
listMatch3DWorks,
|
listMatch3DWorks,
|
||||||
} from '../../services/match3d-works';
|
} from '../../services/match3d-works';
|
||||||
import {
|
import {
|
||||||
hasMatch3DGeneratedImageAsset,
|
|
||||||
mergeMatch3DGeneratedItemAssetsForRuntime,
|
mergeMatch3DGeneratedItemAssetsForRuntime,
|
||||||
normalizeMatch3DGeneratedItemAssetsForRuntime,
|
normalizeMatch3DGeneratedItemAssetsForRuntime,
|
||||||
preloadMatch3DGeneratedRuntimeAssets,
|
preloadMatch3DGeneratedRuntimeAssets,
|
||||||
@@ -442,6 +440,19 @@ import {
|
|||||||
type PlatformErrorDialogPayload,
|
type PlatformErrorDialogPayload,
|
||||||
} from './PlatformErrorDialog';
|
} from './PlatformErrorDialog';
|
||||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||||
|
import {
|
||||||
|
buildMatch3DProfileFromSession,
|
||||||
|
hasMatch3DRuntimeAsset,
|
||||||
|
hasMatch3DRuntimeBackgroundAsset,
|
||||||
|
mapMatch3DWorksForRuntimeUi,
|
||||||
|
mapPublicWorkDetailToMatch3DWork,
|
||||||
|
normalizeMatch3DWorkForRuntimeUi,
|
||||||
|
promoteMatch3DGeneratedBackgroundAsset,
|
||||||
|
resolveActiveMatch3DRuntimeProfile,
|
||||||
|
resolveMatch3DRuntimeBackgroundImageSrc,
|
||||||
|
resolveMatch3DRuntimeGeneratedBackgroundAsset,
|
||||||
|
resolveMatch3DRuntimeGeneratedItemAssets,
|
||||||
|
} from './platformMatch3DRuntimeProfile';
|
||||||
import {
|
import {
|
||||||
getPlatformPublicGalleryEntryKey,
|
getPlatformPublicGalleryEntryKey,
|
||||||
getPlatformRecommendRuntimeKind,
|
getPlatformRecommendRuntimeKind,
|
||||||
@@ -811,317 +822,6 @@ function mapVisualNovelWorkDetailToSession(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapPublicWorkDetailToMatch3DWork(
|
|
||||||
entry: PlatformPublicGalleryCard,
|
|
||||||
): Match3DWorkSummary | null {
|
|
||||||
if (!isMatch3DGalleryEntry(entry)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return promoteMatch3DGeneratedBackgroundAsset({
|
|
||||||
workId: entry.workId,
|
|
||||||
profileId: entry.profileId,
|
|
||||||
ownerUserId: entry.ownerUserId,
|
|
||||||
sourceSessionId:
|
|
||||||
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
|
||||||
? entry.sourceSessionId
|
|
||||||
: null,
|
|
||||||
gameName: entry.worldName,
|
|
||||||
themeText: entry.themeTags[0] ?? '经典消除',
|
|
||||||
summary: entry.summaryText,
|
|
||||||
tags: entry.themeTags,
|
|
||||||
coverImageSrc: entry.coverImageSrc,
|
|
||||||
referenceImageSrc: null,
|
|
||||||
clearCount: 12,
|
|
||||||
difficulty: 4,
|
|
||||||
publicationStatus: 'published',
|
|
||||||
playCount: entry.playCount ?? 0,
|
|
||||||
updatedAt: entry.updatedAt,
|
|
||||||
publishedAt: entry.publishedAt,
|
|
||||||
publishReady: true,
|
|
||||||
backgroundPrompt: entry.backgroundPrompt ?? null,
|
|
||||||
backgroundImageSrc: entry.backgroundImageSrc ?? null,
|
|
||||||
backgroundImageObjectKey: entry.backgroundImageObjectKey ?? null,
|
|
||||||
generatedBackgroundAsset:
|
|
||||||
entry.generatedBackgroundAsset ??
|
|
||||||
entry.generatedItemAssets
|
|
||||||
?.map((asset) => asset.backgroundAsset ?? null)
|
|
||||||
.find(Boolean) ??
|
|
||||||
null,
|
|
||||||
generatedItemAssets: normalizeMatch3DGeneratedItemAssetsForRuntime(
|
|
||||||
entry.generatedItemAssets ?? [],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function findMatch3DGeneratedBackgroundAsset(
|
|
||||||
generatedItemAssets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
|
||||||
): Match3DGeneratedBackgroundAsset | null {
|
|
||||||
return (
|
|
||||||
generatedItemAssets
|
|
||||||
?.map((asset) => asset.backgroundAsset ?? null)
|
|
||||||
.find(Boolean) ?? null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function promoteMatch3DGeneratedBackgroundAsset<
|
|
||||||
T extends Pick<
|
|
||||||
Match3DWorkSummary,
|
|
||||||
| 'backgroundPrompt'
|
|
||||||
| 'backgroundImageSrc'
|
|
||||||
| 'backgroundImageObjectKey'
|
|
||||||
| 'generatedBackgroundAsset'
|
|
||||||
| 'generatedItemAssets'
|
|
||||||
>,
|
|
||||||
>(profile: T): T {
|
|
||||||
const backgroundAsset =
|
|
||||||
profile.generatedBackgroundAsset ??
|
|
||||||
findMatch3DGeneratedBackgroundAsset(profile.generatedItemAssets);
|
|
||||||
if (!backgroundAsset) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...profile,
|
|
||||||
backgroundPrompt:
|
|
||||||
profile.backgroundPrompt ?? backgroundAsset.prompt ?? null,
|
|
||||||
backgroundImageSrc:
|
|
||||||
profile.backgroundImageSrc ??
|
|
||||||
backgroundAsset.imageSrc ??
|
|
||||||
backgroundAsset.imageObjectKey ??
|
|
||||||
null,
|
|
||||||
backgroundImageObjectKey:
|
|
||||||
profile.backgroundImageObjectKey ??
|
|
||||||
backgroundAsset.imageObjectKey ??
|
|
||||||
backgroundAsset.imageSrc ??
|
|
||||||
null,
|
|
||||||
generatedBackgroundAsset:
|
|
||||||
profile.generatedBackgroundAsset ?? backgroundAsset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeMatch3DWorkForRuntimeUi<T extends Match3DWorkSummary>(
|
|
||||||
profile: T,
|
|
||||||
): T {
|
|
||||||
return promoteMatch3DGeneratedBackgroundAsset({
|
|
||||||
...profile,
|
|
||||||
generatedItemAssets: normalizeMatch3DGeneratedItemAssetsForRuntime(
|
|
||||||
profile.generatedItemAssets,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapMatch3DWorksForRuntimeUi<T extends Match3DWorkSummary>(
|
|
||||||
profiles: readonly T[],
|
|
||||||
): T[] {
|
|
||||||
return profiles.map(normalizeMatch3DWorkForRuntimeUi);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMatch3DProfileFromSession(
|
|
||||||
session: Match3DAgentSessionSnapshot | null,
|
|
||||||
): Match3DWorkProfile | null {
|
|
||||||
const draft = session?.draft;
|
|
||||||
if (!session || !draft?.profileId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = session.updatedAt || new Date().toISOString();
|
|
||||||
const generatedItemAssets = normalizeMatch3DGeneratedItemAssetsForRuntime(
|
|
||||||
draft.generatedItemAssets,
|
|
||||||
);
|
|
||||||
return promoteMatch3DGeneratedBackgroundAsset({
|
|
||||||
workId: draft.profileId,
|
|
||||||
profileId: draft.profileId,
|
|
||||||
ownerUserId: 'current-user',
|
|
||||||
sourceSessionId: session.sessionId,
|
|
||||||
gameName: draft.gameName,
|
|
||||||
themeText: draft.themeText,
|
|
||||||
summary: draft.summary ?? draft.summaryText ?? '',
|
|
||||||
tags: draft.tags,
|
|
||||||
coverImageSrc: draft.coverImageSrc ?? draft.referenceImageSrc ?? null,
|
|
||||||
referenceImageSrc: draft.referenceImageSrc ?? null,
|
|
||||||
clearCount: draft.clearCount,
|
|
||||||
difficulty: draft.difficulty,
|
|
||||||
publicationStatus: 'draft',
|
|
||||||
playCount: 0,
|
|
||||||
updatedAt: now,
|
|
||||||
publishedAt: null,
|
|
||||||
publishReady: Boolean(draft.publishReady),
|
|
||||||
backgroundPrompt: draft.backgroundPrompt ?? null,
|
|
||||||
backgroundImageSrc: draft.backgroundImageSrc ?? null,
|
|
||||||
backgroundImageObjectKey: draft.backgroundImageObjectKey ?? null,
|
|
||||||
generatedBackgroundAsset: draft.generatedBackgroundAsset ?? null,
|
|
||||||
generatedItemAssets,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMatch3DRuntimeAsset(
|
|
||||||
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
|
||||||
) {
|
|
||||||
return hasMatch3DGeneratedImageAsset(assets);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMatch3DRuntimeBackgroundAsset(
|
|
||||||
profile: Pick<
|
|
||||||
Match3DWorkSummary,
|
|
||||||
| 'backgroundImageSrc'
|
|
||||||
| 'backgroundImageObjectKey'
|
|
||||||
| 'generatedBackgroundAsset'
|
|
||||||
| 'generatedItemAssets'
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
return Boolean(
|
|
||||||
profile.backgroundImageSrc?.trim() ||
|
|
||||||
profile.backgroundImageObjectKey?.trim() ||
|
|
||||||
profile.generatedBackgroundAsset?.imageSrc?.trim() ||
|
|
||||||
profile.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
|
||||||
profile.generatedBackgroundAsset?.containerImageSrc?.trim() ||
|
|
||||||
profile.generatedBackgroundAsset?.containerImageObjectKey?.trim() ||
|
|
||||||
profile.generatedItemAssets?.some(
|
|
||||||
(asset) =>
|
|
||||||
asset.backgroundAsset?.imageSrc?.trim() ||
|
|
||||||
asset.backgroundAsset?.imageObjectKey?.trim() ||
|
|
||||||
asset.backgroundAsset?.containerImageSrc?.trim() ||
|
|
||||||
asset.backgroundAsset?.containerImageObjectKey?.trim(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMatch3DRuntimeGeneratedItemAssets(
|
|
||||||
run: Match3DRunSnapshot | null,
|
|
||||||
profile: Match3DWorkProfile | null,
|
|
||||||
publicWorkDetail: PlatformPublicGalleryCard | null,
|
|
||||||
) {
|
|
||||||
const runProfileId = run?.profileId?.trim() ?? '';
|
|
||||||
const profileAssets = profile?.generatedItemAssets ?? [];
|
|
||||||
const publicDetailAssets =
|
|
||||||
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
|
||||||
? (publicWorkDetail.generatedItemAssets ?? [])
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (runProfileId && profile?.profileId === runProfileId) {
|
|
||||||
if (hasMatch3DRuntimeAsset(profileAssets)) {
|
|
||||||
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
publicWorkDetail &&
|
|
||||||
isMatch3DGalleryEntry(publicWorkDetail) &&
|
|
||||||
publicWorkDetail.profileId === runProfileId
|
|
||||||
) {
|
|
||||||
return hasMatch3DRuntimeAsset(publicDetailAssets)
|
|
||||||
? mergeMatch3DGeneratedItemAssetsForRuntime(
|
|
||||||
publicDetailAssets,
|
|
||||||
profileAssets,
|
|
||||||
)
|
|
||||||
: normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
runProfileId &&
|
|
||||||
publicWorkDetail &&
|
|
||||||
isMatch3DGalleryEntry(publicWorkDetail) &&
|
|
||||||
publicWorkDetail.profileId === runProfileId
|
|
||||||
) {
|
|
||||||
return normalizeMatch3DGeneratedItemAssetsForRuntime(publicDetailAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMatch3DRuntimeAsset(profileAssets)) {
|
|
||||||
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
|
||||||
}
|
|
||||||
return publicDetailAssets.length > 0
|
|
||||||
? normalizeMatch3DGeneratedItemAssetsForRuntime(publicDetailAssets)
|
|
||||||
: normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMatch3DRuntimeGeneratedBackgroundAsset(
|
|
||||||
run: Match3DRunSnapshot | null,
|
|
||||||
profile: Match3DWorkProfile | null,
|
|
||||||
publicWorkDetail: PlatformPublicGalleryCard | null,
|
|
||||||
) {
|
|
||||||
const runProfileId = run?.profileId?.trim() ?? '';
|
|
||||||
const profileBackground = profile
|
|
||||||
? (promoteMatch3DGeneratedBackgroundAsset(profile)
|
|
||||||
.generatedBackgroundAsset ?? null)
|
|
||||||
: null;
|
|
||||||
const publicBackground =
|
|
||||||
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
|
||||||
? (promoteMatch3DGeneratedBackgroundAsset(publicWorkDetail)
|
|
||||||
.generatedBackgroundAsset ?? null)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (runProfileId && profile?.profileId === runProfileId) {
|
|
||||||
return profileBackground ?? publicBackground;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
runProfileId &&
|
|
||||||
publicWorkDetail &&
|
|
||||||
isMatch3DGalleryEntry(publicWorkDetail) &&
|
|
||||||
publicWorkDetail.profileId === runProfileId
|
|
||||||
) {
|
|
||||||
return publicBackground ?? profileBackground;
|
|
||||||
}
|
|
||||||
return profileBackground ?? publicBackground;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveActiveMatch3DRuntimeProfile(
|
|
||||||
run: Match3DRunSnapshot | null,
|
|
||||||
runtimeProfile: Match3DWorkProfile | null,
|
|
||||||
profile: Match3DWorkProfile | null,
|
|
||||||
) {
|
|
||||||
const runProfileId = run?.profileId?.trim() ?? '';
|
|
||||||
if (runProfileId && runtimeProfile?.profileId === runProfileId) {
|
|
||||||
return runtimeProfile;
|
|
||||||
}
|
|
||||||
if (runProfileId && profile?.profileId === runProfileId) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
return runtimeProfile ?? profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMatch3DRuntimeBackgroundImageSrc(
|
|
||||||
run: Match3DRunSnapshot | null,
|
|
||||||
profile: Match3DWorkProfile | null,
|
|
||||||
publicWorkDetail: PlatformPublicGalleryCard | null,
|
|
||||||
) {
|
|
||||||
const runProfileId = run?.profileId?.trim() ?? '';
|
|
||||||
const resolvedProfile = profile
|
|
||||||
? promoteMatch3DGeneratedBackgroundAsset(profile)
|
|
||||||
: null;
|
|
||||||
const resolvedPublicWork =
|
|
||||||
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
|
||||||
? promoteMatch3DGeneratedBackgroundAsset(publicWorkDetail)
|
|
||||||
: null;
|
|
||||||
const profileBackground =
|
|
||||||
resolvedProfile?.backgroundImageSrc?.trim() ||
|
|
||||||
resolvedProfile?.generatedBackgroundAsset?.imageSrc?.trim() ||
|
|
||||||
resolvedProfile?.backgroundImageObjectKey?.trim() ||
|
|
||||||
resolvedProfile?.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
|
||||||
'';
|
|
||||||
const publicBackground =
|
|
||||||
resolvedPublicWork?.backgroundImageSrc?.trim() ||
|
|
||||||
resolvedPublicWork?.generatedBackgroundAsset?.imageSrc?.trim() ||
|
|
||||||
resolvedPublicWork?.backgroundImageObjectKey?.trim() ||
|
|
||||||
resolvedPublicWork?.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
|
||||||
'';
|
|
||||||
|
|
||||||
if (runProfileId && profile?.profileId === runProfileId) {
|
|
||||||
return profileBackground || publicBackground || null;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
runProfileId &&
|
|
||||||
publicWorkDetail &&
|
|
||||||
isMatch3DGalleryEntry(publicWorkDetail) &&
|
|
||||||
publicWorkDetail.profileId === runProfileId
|
|
||||||
) {
|
|
||||||
return publicBackground || profileBackground || null;
|
|
||||||
}
|
|
||||||
return profileBackground || publicBackground || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMatch3DGenerationStateFromAssets(
|
function resolveMatch3DGenerationStateFromAssets(
|
||||||
current: MiniGameDraftGenerationState | null,
|
current: MiniGameDraftGenerationState | null,
|
||||||
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent';
|
||||||
|
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
|
||||||
|
import type {
|
||||||
|
Match3DGeneratedBackgroundAsset,
|
||||||
|
Match3DGeneratedItemAsset,
|
||||||
|
Match3DWorkProfile,
|
||||||
|
} from '../../../packages/shared/src/contracts/match3dWorks';
|
||||||
|
import type { PlatformMatch3DGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
|
||||||
|
import {
|
||||||
|
buildMatch3DProfileFromSession,
|
||||||
|
mapPublicWorkDetailToMatch3DWork,
|
||||||
|
resolveActiveMatch3DRuntimeProfile,
|
||||||
|
resolveMatch3DRuntimeBackgroundImageSrc,
|
||||||
|
resolveMatch3DRuntimeGeneratedBackgroundAsset,
|
||||||
|
resolveMatch3DRuntimeGeneratedItemAssets,
|
||||||
|
} from './platformMatch3DRuntimeProfile';
|
||||||
|
|
||||||
|
function buildBackgroundAsset(
|
||||||
|
overrides: Partial<Match3DGeneratedBackgroundAsset> = {},
|
||||||
|
): Match3DGeneratedBackgroundAsset {
|
||||||
|
return {
|
||||||
|
prompt: '森林棋盘',
|
||||||
|
imageSrc: '/generated/match3d/background.png',
|
||||||
|
imageObjectKey: null,
|
||||||
|
status: 'ready',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildItemAsset(
|
||||||
|
overrides: Partial<Match3DGeneratedItemAsset> = {},
|
||||||
|
): Match3DGeneratedItemAsset {
|
||||||
|
return {
|
||||||
|
itemId: 'item-1',
|
||||||
|
itemName: '蘑菇',
|
||||||
|
imageSrc: '/generated/match3d/item.png',
|
||||||
|
imageObjectKey: null,
|
||||||
|
status: 'image_ready',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildProfile(
|
||||||
|
overrides: Partial<Match3DWorkProfile> = {},
|
||||||
|
): Match3DWorkProfile {
|
||||||
|
return {
|
||||||
|
workId: 'match3d-work-1',
|
||||||
|
profileId: 'match3d-profile-1',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
sourceSessionId: 'match3d-session-1',
|
||||||
|
gameName: '森林抓鹅',
|
||||||
|
themeText: '森林',
|
||||||
|
summary: '找出蘑菇。',
|
||||||
|
tags: ['森林', '蘑菇'],
|
||||||
|
coverImageSrc: '/cover.png',
|
||||||
|
referenceImageSrc: null,
|
||||||
|
clearCount: 12,
|
||||||
|
difficulty: 4,
|
||||||
|
publicationStatus: 'published',
|
||||||
|
playCount: 1,
|
||||||
|
updatedAt: '2026-05-20T00:00:00.000Z',
|
||||||
|
publishedAt: '2026-05-20T00:00:00.000Z',
|
||||||
|
publishReady: true,
|
||||||
|
backgroundPrompt: null,
|
||||||
|
backgroundImageSrc: null,
|
||||||
|
backgroundImageObjectKey: null,
|
||||||
|
generatedBackgroundAsset: null,
|
||||||
|
generatedItemAssets: [buildItemAsset()],
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRun(overrides: Partial<Match3DRunSnapshot> = {}): Match3DRunSnapshot {
|
||||||
|
return {
|
||||||
|
runId: 'match3d-run-1',
|
||||||
|
profileId: 'match3d-profile-1',
|
||||||
|
status: 'running',
|
||||||
|
snapshotVersion: 1,
|
||||||
|
startedAtMs: 1000,
|
||||||
|
durationLimitMs: 60000,
|
||||||
|
remainingMs: 55000,
|
||||||
|
clearCount: 12,
|
||||||
|
totalItemCount: 12,
|
||||||
|
clearedItemCount: 0,
|
||||||
|
items: [],
|
||||||
|
traySlots: [],
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPublicWork(
|
||||||
|
overrides: Partial<PlatformMatch3DGalleryCard> = {},
|
||||||
|
): PlatformMatch3DGalleryCard {
|
||||||
|
return {
|
||||||
|
sourceType: 'match3d',
|
||||||
|
workId: 'match3d-work-1',
|
||||||
|
profileId: 'match3d-profile-1',
|
||||||
|
sourceSessionId: 'match3d-session-1',
|
||||||
|
publicWorkCode: 'M3D-00000001',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '玩家',
|
||||||
|
worldName: '森林抓鹅',
|
||||||
|
subtitle: '抓大鹅',
|
||||||
|
summaryText: '找出蘑菇。',
|
||||||
|
coverImageSrc: '/cover.png',
|
||||||
|
backgroundPrompt: null,
|
||||||
|
backgroundImageSrc: null,
|
||||||
|
backgroundImageObjectKey: null,
|
||||||
|
generatedBackgroundAsset: null,
|
||||||
|
generatedItemAssets: [buildItemAsset()],
|
||||||
|
themeTags: ['森林', '蘑菇'],
|
||||||
|
visibility: 'published',
|
||||||
|
publishedAt: '2026-05-20T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-05-20T00:00:00.000Z',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Match3D runtime profile maps public detail and promotes item background asset', () => {
|
||||||
|
const backgroundAsset = buildBackgroundAsset({
|
||||||
|
imageSrc: '/generated/match3d/background-from-item.png',
|
||||||
|
imageObjectKey: 'oss/background-from-item.png',
|
||||||
|
});
|
||||||
|
const work = mapPublicWorkDetailToMatch3DWork(
|
||||||
|
buildPublicWork({
|
||||||
|
generatedBackgroundAsset: null,
|
||||||
|
backgroundImageSrc: null,
|
||||||
|
generatedItemAssets: [
|
||||||
|
buildItemAsset({
|
||||||
|
backgroundAsset,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(work?.generatedBackgroundAsset).toEqual(backgroundAsset);
|
||||||
|
expect(work?.backgroundImageSrc).toBe(
|
||||||
|
'/generated/match3d/background-from-item.png',
|
||||||
|
);
|
||||||
|
expect(work?.backgroundImageObjectKey).toBe('oss/background-from-item.png');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Match3D runtime profile builds draft profile from session snapshot', () => {
|
||||||
|
const backgroundAsset = buildBackgroundAsset({
|
||||||
|
imageSrc: '/generated/match3d/draft-background.png',
|
||||||
|
});
|
||||||
|
const session: Match3DAgentSessionSnapshot = {
|
||||||
|
sessionId: 'match3d-session-draft',
|
||||||
|
currentTurn: 2,
|
||||||
|
progressPercent: 100,
|
||||||
|
stage: 'draft_compiled',
|
||||||
|
anchorPack: {
|
||||||
|
theme: { key: 'theme', label: '主题', value: '森林', status: 'confirmed' },
|
||||||
|
clearCount: {
|
||||||
|
key: 'clearCount',
|
||||||
|
label: '消除数',
|
||||||
|
value: '12',
|
||||||
|
status: 'confirmed',
|
||||||
|
},
|
||||||
|
difficulty: {
|
||||||
|
key: 'difficulty',
|
||||||
|
label: '难度',
|
||||||
|
value: '4',
|
||||||
|
status: 'confirmed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messages: [],
|
||||||
|
lastAssistantReply: null,
|
||||||
|
updatedAt: '2026-05-21T00:00:00.000Z',
|
||||||
|
draft: {
|
||||||
|
profileId: 'match3d-draft-profile',
|
||||||
|
gameName: '草稿抓鹅',
|
||||||
|
themeText: '森林',
|
||||||
|
summaryText: '草稿摘要',
|
||||||
|
tags: ['森林'],
|
||||||
|
coverImageSrc: null,
|
||||||
|
referenceImageSrc: '/reference.png',
|
||||||
|
clearCount: 12,
|
||||||
|
difficulty: 4,
|
||||||
|
publishReady: true,
|
||||||
|
generatedItemAssets: [
|
||||||
|
buildItemAsset({
|
||||||
|
backgroundAsset,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const profile = buildMatch3DProfileFromSession(session);
|
||||||
|
|
||||||
|
expect(profile?.profileId).toBe('match3d-draft-profile');
|
||||||
|
expect(profile?.sourceSessionId).toBe('match3d-session-draft');
|
||||||
|
expect(profile?.publicationStatus).toBe('draft');
|
||||||
|
expect(profile?.coverImageSrc).toBe('/reference.png');
|
||||||
|
expect(profile?.generatedBackgroundAsset).toEqual(backgroundAsset);
|
||||||
|
expect(profile?.backgroundImageSrc).toBe(
|
||||||
|
'/generated/match3d/draft-background.png',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Match3D runtime profile selects active profile by run profile id', () => {
|
||||||
|
const runtimeProfile = buildProfile({
|
||||||
|
profileId: 'runtime-profile',
|
||||||
|
gameName: '运行态抓鹅',
|
||||||
|
});
|
||||||
|
const draftProfile = buildProfile({
|
||||||
|
profileId: 'draft-profile',
|
||||||
|
gameName: '旧草稿抓鹅',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveActiveMatch3DRuntimeProfile(
|
||||||
|
buildRun({ profileId: 'runtime-profile' }),
|
||||||
|
runtimeProfile,
|
||||||
|
draftProfile,
|
||||||
|
),
|
||||||
|
).toBe(runtimeProfile);
|
||||||
|
expect(
|
||||||
|
resolveActiveMatch3DRuntimeProfile(
|
||||||
|
buildRun({ profileId: 'draft-profile' }),
|
||||||
|
runtimeProfile,
|
||||||
|
draftProfile,
|
||||||
|
),
|
||||||
|
).toBe(draftProfile);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Match3D runtime profile resolves generated assets from matching public detail', () => {
|
||||||
|
const staleProfile = buildProfile({
|
||||||
|
profileId: 'stale-profile',
|
||||||
|
generatedBackgroundAsset: buildBackgroundAsset({
|
||||||
|
imageSrc: '/generated/match3d/stale-background.png',
|
||||||
|
}),
|
||||||
|
generatedItemAssets: [
|
||||||
|
buildItemAsset({
|
||||||
|
itemId: 'stale-item',
|
||||||
|
imageSrc: '/generated/match3d/stale-item.png',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const publicBackground = buildBackgroundAsset({
|
||||||
|
imageSrc: '/generated/match3d/public-background.png',
|
||||||
|
});
|
||||||
|
const publicWork = buildPublicWork({
|
||||||
|
profileId: 'public-profile',
|
||||||
|
generatedBackgroundAsset: publicBackground,
|
||||||
|
generatedItemAssets: [
|
||||||
|
buildItemAsset({
|
||||||
|
itemId: 'public-item',
|
||||||
|
imageSrc: '/generated/match3d/public-item.png',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const run = buildRun({ profileId: 'public-profile' });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveMatch3DRuntimeGeneratedItemAssets(run, staleProfile, publicWork).some(
|
||||||
|
(asset) => asset.imageSrc === '/generated/match3d/public-item.png',
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
resolveMatch3DRuntimeGeneratedBackgroundAsset(run, staleProfile, publicWork),
|
||||||
|
).toEqual(publicBackground);
|
||||||
|
expect(resolveMatch3DRuntimeBackgroundImageSrc(run, staleProfile, publicWork)).toBe(
|
||||||
|
'/generated/match3d/public-background.png',
|
||||||
|
);
|
||||||
|
});
|
||||||
326
src/components/platform-entry/platformMatch3DRuntimeProfile.ts
Normal file
326
src/components/platform-entry/platformMatch3DRuntimeProfile.ts
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
import type { Match3DAgentSessionSnapshot } from '../../../packages/shared/src/contracts/match3dAgent';
|
||||||
|
import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/match3dRuntime';
|
||||||
|
import type {
|
||||||
|
Match3DGeneratedBackgroundAsset,
|
||||||
|
Match3DGeneratedItemAsset,
|
||||||
|
Match3DWorkProfile,
|
||||||
|
Match3DWorkSummary,
|
||||||
|
} from '../../../packages/shared/src/contracts/match3dWorks';
|
||||||
|
import {
|
||||||
|
hasMatch3DGeneratedImageAsset,
|
||||||
|
mergeMatch3DGeneratedItemAssetsForRuntime,
|
||||||
|
normalizeMatch3DGeneratedItemAssetsForRuntime,
|
||||||
|
} from '../../services/match3dGeneratedModelCache';
|
||||||
|
import {
|
||||||
|
isMatch3DGalleryEntry,
|
||||||
|
type PlatformPublicGalleryCard,
|
||||||
|
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||||
|
|
||||||
|
export function mapPublicWorkDetailToMatch3DWork(
|
||||||
|
entry: PlatformPublicGalleryCard,
|
||||||
|
): Match3DWorkSummary | null {
|
||||||
|
if (!isMatch3DGalleryEntry(entry)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return promoteMatch3DGeneratedBackgroundAsset({
|
||||||
|
workId: entry.workId,
|
||||||
|
profileId: entry.profileId,
|
||||||
|
ownerUserId: entry.ownerUserId,
|
||||||
|
sourceSessionId:
|
||||||
|
'sourceSessionId' in entry && typeof entry.sourceSessionId === 'string'
|
||||||
|
? entry.sourceSessionId
|
||||||
|
: null,
|
||||||
|
gameName: entry.worldName,
|
||||||
|
themeText: entry.themeTags[0] ?? '经典消除',
|
||||||
|
summary: entry.summaryText,
|
||||||
|
tags: entry.themeTags,
|
||||||
|
coverImageSrc: entry.coverImageSrc,
|
||||||
|
referenceImageSrc: null,
|
||||||
|
clearCount: 12,
|
||||||
|
difficulty: 4,
|
||||||
|
publicationStatus: 'published',
|
||||||
|
playCount: entry.playCount ?? 0,
|
||||||
|
updatedAt: entry.updatedAt,
|
||||||
|
publishedAt: entry.publishedAt,
|
||||||
|
publishReady: true,
|
||||||
|
backgroundPrompt: entry.backgroundPrompt ?? null,
|
||||||
|
backgroundImageSrc: entry.backgroundImageSrc ?? null,
|
||||||
|
backgroundImageObjectKey: entry.backgroundImageObjectKey ?? null,
|
||||||
|
generatedBackgroundAsset:
|
||||||
|
entry.generatedBackgroundAsset ??
|
||||||
|
findMatch3DGeneratedBackgroundAsset(entry.generatedItemAssets) ??
|
||||||
|
null,
|
||||||
|
generatedItemAssets: normalizeMatch3DGeneratedItemAssetsForRuntime(
|
||||||
|
entry.generatedItemAssets ?? [],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findMatch3DGeneratedBackgroundAsset(
|
||||||
|
generatedItemAssets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
||||||
|
): Match3DGeneratedBackgroundAsset | null {
|
||||||
|
return (
|
||||||
|
generatedItemAssets
|
||||||
|
?.map((asset) => asset.backgroundAsset ?? null)
|
||||||
|
.find(Boolean) ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function promoteMatch3DGeneratedBackgroundAsset<
|
||||||
|
T extends Pick<
|
||||||
|
Match3DWorkSummary,
|
||||||
|
| 'backgroundPrompt'
|
||||||
|
| 'backgroundImageSrc'
|
||||||
|
| 'backgroundImageObjectKey'
|
||||||
|
| 'generatedBackgroundAsset'
|
||||||
|
| 'generatedItemAssets'
|
||||||
|
>,
|
||||||
|
>(profile: T): T {
|
||||||
|
const backgroundAsset =
|
||||||
|
profile.generatedBackgroundAsset ??
|
||||||
|
findMatch3DGeneratedBackgroundAsset(profile.generatedItemAssets);
|
||||||
|
if (!backgroundAsset) {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...profile,
|
||||||
|
backgroundPrompt:
|
||||||
|
profile.backgroundPrompt ?? backgroundAsset.prompt ?? null,
|
||||||
|
backgroundImageSrc:
|
||||||
|
profile.backgroundImageSrc ??
|
||||||
|
backgroundAsset.imageSrc ??
|
||||||
|
backgroundAsset.imageObjectKey ??
|
||||||
|
null,
|
||||||
|
backgroundImageObjectKey:
|
||||||
|
profile.backgroundImageObjectKey ??
|
||||||
|
backgroundAsset.imageObjectKey ??
|
||||||
|
backgroundAsset.imageSrc ??
|
||||||
|
null,
|
||||||
|
generatedBackgroundAsset:
|
||||||
|
profile.generatedBackgroundAsset ?? backgroundAsset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeMatch3DWorkForRuntimeUi<T extends Match3DWorkSummary>(
|
||||||
|
profile: T,
|
||||||
|
): T {
|
||||||
|
return promoteMatch3DGeneratedBackgroundAsset({
|
||||||
|
...profile,
|
||||||
|
generatedItemAssets: normalizeMatch3DGeneratedItemAssetsForRuntime(
|
||||||
|
profile.generatedItemAssets,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapMatch3DWorksForRuntimeUi<T extends Match3DWorkSummary>(
|
||||||
|
profiles: readonly T[],
|
||||||
|
): T[] {
|
||||||
|
return profiles.map(normalizeMatch3DWorkForRuntimeUi);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildMatch3DProfileFromSession(
|
||||||
|
session: Match3DAgentSessionSnapshot | null,
|
||||||
|
): Match3DWorkProfile | null {
|
||||||
|
const draft = session?.draft;
|
||||||
|
if (!session || !draft?.profileId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = session.updatedAt || new Date().toISOString();
|
||||||
|
const generatedItemAssets = normalizeMatch3DGeneratedItemAssetsForRuntime(
|
||||||
|
draft.generatedItemAssets,
|
||||||
|
);
|
||||||
|
return promoteMatch3DGeneratedBackgroundAsset({
|
||||||
|
workId: draft.profileId,
|
||||||
|
profileId: draft.profileId,
|
||||||
|
ownerUserId: 'current-user',
|
||||||
|
sourceSessionId: session.sessionId,
|
||||||
|
gameName: draft.gameName,
|
||||||
|
themeText: draft.themeText,
|
||||||
|
summary: draft.summary ?? draft.summaryText ?? '',
|
||||||
|
tags: draft.tags,
|
||||||
|
coverImageSrc: draft.coverImageSrc ?? draft.referenceImageSrc ?? null,
|
||||||
|
referenceImageSrc: draft.referenceImageSrc ?? null,
|
||||||
|
clearCount: draft.clearCount,
|
||||||
|
difficulty: draft.difficulty,
|
||||||
|
publicationStatus: 'draft',
|
||||||
|
playCount: 0,
|
||||||
|
updatedAt: now,
|
||||||
|
publishedAt: null,
|
||||||
|
publishReady: Boolean(draft.publishReady),
|
||||||
|
backgroundPrompt: draft.backgroundPrompt ?? null,
|
||||||
|
backgroundImageSrc: draft.backgroundImageSrc ?? null,
|
||||||
|
backgroundImageObjectKey: draft.backgroundImageObjectKey ?? null,
|
||||||
|
generatedBackgroundAsset: draft.generatedBackgroundAsset ?? null,
|
||||||
|
generatedItemAssets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasMatch3DRuntimeAsset(
|
||||||
|
assets: readonly Match3DGeneratedItemAsset[] | null | undefined,
|
||||||
|
) {
|
||||||
|
return hasMatch3DGeneratedImageAsset(assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasMatch3DRuntimeBackgroundAsset(
|
||||||
|
profile: Pick<
|
||||||
|
Match3DWorkSummary,
|
||||||
|
| 'backgroundImageSrc'
|
||||||
|
| 'backgroundImageObjectKey'
|
||||||
|
| 'generatedBackgroundAsset'
|
||||||
|
| 'generatedItemAssets'
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return Boolean(
|
||||||
|
profile.backgroundImageSrc?.trim() ||
|
||||||
|
profile.backgroundImageObjectKey?.trim() ||
|
||||||
|
profile.generatedBackgroundAsset?.imageSrc?.trim() ||
|
||||||
|
profile.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
||||||
|
profile.generatedBackgroundAsset?.containerImageSrc?.trim() ||
|
||||||
|
profile.generatedBackgroundAsset?.containerImageObjectKey?.trim() ||
|
||||||
|
profile.generatedItemAssets?.some(
|
||||||
|
(asset) =>
|
||||||
|
asset.backgroundAsset?.imageSrc?.trim() ||
|
||||||
|
asset.backgroundAsset?.imageObjectKey?.trim() ||
|
||||||
|
asset.backgroundAsset?.containerImageSrc?.trim() ||
|
||||||
|
asset.backgroundAsset?.containerImageObjectKey?.trim(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMatch3DRuntimeGeneratedItemAssets(
|
||||||
|
run: Match3DRunSnapshot | null,
|
||||||
|
profile: Match3DWorkProfile | null,
|
||||||
|
publicWorkDetail: PlatformPublicGalleryCard | null,
|
||||||
|
) {
|
||||||
|
const runProfileId = run?.profileId?.trim() ?? '';
|
||||||
|
const profileAssets = profile?.generatedItemAssets ?? [];
|
||||||
|
const publicDetailAssets =
|
||||||
|
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
||||||
|
? (publicWorkDetail.generatedItemAssets ?? [])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (runProfileId && profile?.profileId === runProfileId) {
|
||||||
|
if (hasMatch3DRuntimeAsset(profileAssets)) {
|
||||||
|
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
publicWorkDetail &&
|
||||||
|
isMatch3DGalleryEntry(publicWorkDetail) &&
|
||||||
|
publicWorkDetail.profileId === runProfileId
|
||||||
|
) {
|
||||||
|
return hasMatch3DRuntimeAsset(publicDetailAssets)
|
||||||
|
? mergeMatch3DGeneratedItemAssetsForRuntime(
|
||||||
|
publicDetailAssets,
|
||||||
|
profileAssets,
|
||||||
|
)
|
||||||
|
: normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
runProfileId &&
|
||||||
|
publicWorkDetail &&
|
||||||
|
isMatch3DGalleryEntry(publicWorkDetail) &&
|
||||||
|
publicWorkDetail.profileId === runProfileId
|
||||||
|
) {
|
||||||
|
return normalizeMatch3DGeneratedItemAssetsForRuntime(publicDetailAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMatch3DRuntimeAsset(profileAssets)) {
|
||||||
|
return normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
||||||
|
}
|
||||||
|
return publicDetailAssets.length > 0
|
||||||
|
? normalizeMatch3DGeneratedItemAssetsForRuntime(publicDetailAssets)
|
||||||
|
: normalizeMatch3DGeneratedItemAssetsForRuntime(profileAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMatch3DRuntimeGeneratedBackgroundAsset(
|
||||||
|
run: Match3DRunSnapshot | null,
|
||||||
|
profile: Match3DWorkProfile | null,
|
||||||
|
publicWorkDetail: PlatformPublicGalleryCard | null,
|
||||||
|
) {
|
||||||
|
const runProfileId = run?.profileId?.trim() ?? '';
|
||||||
|
const profileBackground = profile
|
||||||
|
? (promoteMatch3DGeneratedBackgroundAsset(profile)
|
||||||
|
.generatedBackgroundAsset ?? null)
|
||||||
|
: null;
|
||||||
|
const publicBackground =
|
||||||
|
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
||||||
|
? (promoteMatch3DGeneratedBackgroundAsset(publicWorkDetail)
|
||||||
|
.generatedBackgroundAsset ?? null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (runProfileId && profile?.profileId === runProfileId) {
|
||||||
|
return profileBackground ?? publicBackground;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
runProfileId &&
|
||||||
|
publicWorkDetail &&
|
||||||
|
isMatch3DGalleryEntry(publicWorkDetail) &&
|
||||||
|
publicWorkDetail.profileId === runProfileId
|
||||||
|
) {
|
||||||
|
return publicBackground ?? profileBackground;
|
||||||
|
}
|
||||||
|
return profileBackground ?? publicBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveActiveMatch3DRuntimeProfile(
|
||||||
|
run: Match3DRunSnapshot | null,
|
||||||
|
runtimeProfile: Match3DWorkProfile | null,
|
||||||
|
profile: Match3DWorkProfile | null,
|
||||||
|
) {
|
||||||
|
const runProfileId = run?.profileId?.trim() ?? '';
|
||||||
|
if (runProfileId && runtimeProfile?.profileId === runProfileId) {
|
||||||
|
return runtimeProfile;
|
||||||
|
}
|
||||||
|
if (runProfileId && profile?.profileId === runProfileId) {
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
return runtimeProfile ?? profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMatch3DRuntimeBackgroundImageSrc(
|
||||||
|
run: Match3DRunSnapshot | null,
|
||||||
|
profile: Match3DWorkProfile | null,
|
||||||
|
publicWorkDetail: PlatformPublicGalleryCard | null,
|
||||||
|
) {
|
||||||
|
const runProfileId = run?.profileId?.trim() ?? '';
|
||||||
|
const resolvedProfile = profile
|
||||||
|
? promoteMatch3DGeneratedBackgroundAsset(profile)
|
||||||
|
: null;
|
||||||
|
const resolvedPublicWork =
|
||||||
|
publicWorkDetail && isMatch3DGalleryEntry(publicWorkDetail)
|
||||||
|
? promoteMatch3DGeneratedBackgroundAsset(publicWorkDetail)
|
||||||
|
: null;
|
||||||
|
const profileBackground =
|
||||||
|
resolvedProfile?.backgroundImageSrc?.trim() ||
|
||||||
|
resolvedProfile?.generatedBackgroundAsset?.imageSrc?.trim() ||
|
||||||
|
resolvedProfile?.backgroundImageObjectKey?.trim() ||
|
||||||
|
resolvedProfile?.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
||||||
|
'';
|
||||||
|
const publicBackground =
|
||||||
|
resolvedPublicWork?.backgroundImageSrc?.trim() ||
|
||||||
|
resolvedPublicWork?.generatedBackgroundAsset?.imageSrc?.trim() ||
|
||||||
|
resolvedPublicWork?.backgroundImageObjectKey?.trim() ||
|
||||||
|
resolvedPublicWork?.generatedBackgroundAsset?.imageObjectKey?.trim() ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
if (runProfileId && profile?.profileId === runProfileId) {
|
||||||
|
return profileBackground || publicBackground || null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
runProfileId &&
|
||||||
|
publicWorkDetail &&
|
||||||
|
isMatch3DGalleryEntry(publicWorkDetail) &&
|
||||||
|
publicWorkDetail.profileId === runProfileId
|
||||||
|
) {
|
||||||
|
return publicBackground || profileBackground || null;
|
||||||
|
}
|
||||||
|
return profileBackground || publicBackground || null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user