Enforce Genarrative play-type SOP and update docs

Rewrite Genarrative play-type integration guidance across .codex and .hermes to define a platform-level SOP: default to form/image workbench, unify single-image asset slots (CreativeImageInputPanel), standardize series-material sheet->cut->transparent->OSS pipeline, and forbid copying legacy chat/agent workflows as the default. Add decision-log entry freezing the SOP and a pitfalls note warning against direct reuse of old play tools. Update CONTEXT.md and docs/README.md, add a new PRD file, and apply related small server-side changes (module-auth, spacetime-client mappers and runtime) to align back-end code with the new contracts and flows.
This commit is contained in:
2026-05-20 12:12:00 +08:00
parent f370539a6f
commit 3931442249
123 changed files with 15514 additions and 3419 deletions

View File

@@ -17,13 +17,18 @@ import type {
} from '../../packages/shared/src/contracts/runtime';
import type { SquareHoleSessionSnapshot } from '../../packages/shared/src/contracts/squareHoleAgent';
import type { CustomWorldStructuredAnchorEntry } from './customWorldAgentGenerationProgress';
import type {
CreateJumpHopSessionRequest,
JumpHopSessionSnapshot,
} from './jump-hop/jumpHopClient';
export type MiniGameDraftGenerationKind =
| 'puzzle'
| 'big-fish'
| 'square-hole'
| 'match3d'
| 'baby-object-match';
| 'baby-object-match'
| 'jump-hop';
export type MiniGameDraftGenerationPhase =
| 'idle'
@@ -49,6 +54,11 @@ export type MiniGameDraftGenerationPhase =
| 'baby-object-draft'
| 'baby-object-images'
| 'baby-object-ready'
| 'jump-hop-draft'
| 'jump-hop-character'
| 'jump-hop-tile-atlas'
| 'jump-hop-slice-tiles'
| 'jump-hop-write-draft'
| 'puzzle-images'
| 'puzzle-ui-background'
| 'puzzle-select-image'
@@ -268,6 +278,41 @@ const BABY_OBJECT_MATCH_STEPS = [
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const JUMP_HOP_STEPS = [
{
id: 'jump-hop-draft',
label: '整理玩法草稿',
detail: '建立主题、难度和路径基础数据。',
weight: 10,
},
{
id: 'jump-hop-character',
label: '生成角色形象',
detail: '生成可进入运行态的俯视角角色图。',
weight: 34,
},
{
id: 'jump-hop-tile-atlas',
label: '生成地块图集',
detail: '生成起点、普通、目标和终点地块图集。',
weight: 34,
},
{
id: 'jump-hop-slice-tiles',
label: '切分地块素材',
detail: '切分透明地块 PNG 并校验落点半径。',
weight: 14,
},
{
id: 'jump-hop-write-draft',
label: '写入正式草稿',
detail: '保存角色、地块、路径和封面合成结果。',
weight: 8,
},
] as const satisfies ReadonlyArray<MiniGameStepDefinition>;
const JUMP_HOP_ESTIMATED_WAIT_MS = 5 * 60_000;
function clampProgress(value: number) {
return Math.max(0, Math.min(100, Math.round(value)));
}
@@ -285,6 +330,9 @@ function getStepDefinitions(kind: MiniGameDraftGenerationKind) {
if (kind === 'baby-object-match') {
return BABY_OBJECT_MATCH_STEPS;
}
if (kind === 'jump-hop') {
return JUMP_HOP_STEPS;
}
return BIG_FISH_STEPS;
}
@@ -340,8 +388,10 @@ export function createMiniGameDraftGenerationState(
? 'square-hole-draft'
: kind === 'match3d'
? 'match3d-work-title'
: kind === 'baby-object-match'
? 'baby-object-draft'
: kind === 'baby-object-match'
? 'baby-object-draft'
: kind === 'jump-hop'
? 'jump-hop-draft'
: 'compile',
startedAtMs: Date.now(),
completedAssetCount: 0,
@@ -413,6 +463,24 @@ function resolveBabyObjectMatchPhaseByElapsedMs(
return 'baby-object-draft';
}
function resolveJumpHopPhaseByElapsedMs(
elapsedMs: number,
): MiniGameDraftGenerationPhase {
if (elapsedMs >= 270_000) {
return 'jump-hop-write-draft';
}
if (elapsedMs >= 220_000) {
return 'jump-hop-slice-tiles';
}
if (elapsedMs >= 115_000) {
return 'jump-hop-tile-atlas';
}
if (elapsedMs >= 12_000) {
return 'jump-hop-character';
}
return 'jump-hop-draft';
}
function resolvePuzzleTimelineByElapsedMs(elapsedMs: number) {
let elapsedBeforePhase = 0;
@@ -491,7 +559,14 @@ export function buildMiniGameDraftGenerationProgress(
...state,
phase: resolveBabyObjectMatchPhaseByElapsedMs(elapsedMs),
}
: state;
: state.kind === 'jump-hop' &&
state.phase !== 'failed' &&
state.phase !== 'ready'
? {
...state,
phase: resolveJumpHopPhaseByElapsedMs(elapsedMs),
}
: state;
const steps = getStepDefinitions(normalizedState.kind);
const activeStepIndex = getActiveStepIndex(steps, normalizedState.phase);
@@ -518,9 +593,11 @@ export function buildMiniGameDraftGenerationProgress(
? 0.42
: normalizedState.kind === 'match3d'
? 0.5
: normalizedState.kind === 'baby-object-match'
? 0.52
: 0;
: normalizedState.kind === 'baby-object-match'
? 0.52
: normalizedState.kind === 'jump-hop'
? 0.5
: 0;
const overallProgress =
normalizedState.phase === 'failed'
? Math.max(1, completedWeight)
@@ -551,6 +628,8 @@ export function buildMiniGameDraftGenerationProgress(
? '抓大鹅素材与草稿已准备完成,可进入结果页继续编辑。'
: normalizedState.kind === 'baby-object-match'
? '宝贝识物草稿已准备完成,可进入结果页继续发布。'
: normalizedState.kind === 'jump-hop'
? '跳一跳草稿已准备完成,可进入结果页试玩或发布。'
: '首关草稿与正式图已准备完成,可进入结果页补作品信息。'
: activeStep.detail),
batchLabel: activeStep.label,
@@ -574,6 +653,8 @@ export function buildMiniGameDraftGenerationProgress(
0,
BABY_OBJECT_MATCH_ESTIMATED_WAIT_MS - elapsedMs,
)
: normalizedState.kind === 'jump-hop'
? Math.max(0, JUMP_HOP_ESTIMATED_WAIT_MS - elapsedMs)
: null,
activeStepIndex,
steps: buildMiniGameProgressSteps(
@@ -585,6 +666,65 @@ export function buildMiniGameDraftGenerationProgress(
};
}
export function buildJumpHopGenerationAnchorEntries(
session: JumpHopSessionSnapshot | null | undefined,
formPayload: CreateJumpHopSessionRequest | null | undefined = null,
): CustomWorldStructuredAnchorEntry[] {
const sessionRecord = session as
| {
config?: Partial<CreateJumpHopSessionRequest>;
draft?: {
workTitle?: string;
themeText?: string;
characterPrompt?: string;
stylePreset?: string;
} | null;
}
| null
| undefined;
const config = sessionRecord?.config;
const draft = sessionRecord?.draft;
const entries: Array<MiniGameAnchorSource | null> = [
{
key: 'jump-hop-theme',
label: '主题',
value:
formPayload?.themeText?.trim() ||
config?.themeText?.trim() ||
draft?.themeText?.trim() ||
draft?.workTitle?.trim() ||
'',
},
{
key: 'jump-hop-character',
label: '角色',
value:
formPayload?.characterDescription?.trim() ||
config?.characterDescription?.trim() ||
draft?.characterPrompt?.trim() ||
'',
},
{
key: 'jump-hop-tile-style',
label: '地块',
value:
formPayload?.tileStyle?.trim() ||
config?.tileStyle?.trim() ||
draft?.stylePreset?.trim() ||
'',
},
];
return entries
.filter((entry): entry is MiniGameAnchorSource => Boolean(entry))
.map((entry) => ({
id: entry.key,
label: entry.label,
value: entry.value,
}))
.filter((entry) => entry.value.trim());
}
export function buildPuzzleGenerationAnchorEntries(
session: PuzzleAgentSessionSnapshot | null | undefined,
formPayload: CreatePuzzleAgentSessionRequest | null | undefined = null,