752505547d
新增正式测试切片合同、状态机与上一项/播放暂停/下一项控制 新增可信悬停资源解析与资源管理画布定位 扩展 Tauri 版本登记、运行 Bridge 和 Agent 生成合同 补齐 TypeScript、Rust 与 AppSurface 回归测试 同步工作台 PRD、技术方案与共享决策记录 保持 P2 资源替换、参数写入和下一版本能力未接线
227 lines
6.1 KiB
TypeScript
227 lines
6.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type {
|
|
GameRuntimeMessage,
|
|
RunnableGameVersion,
|
|
} from '../../../../packages/shared/src/contracts/gameCreationApp';
|
|
import {
|
|
beginGameTestSlice,
|
|
formalGameTestSlices,
|
|
projectGameTestSliceMessage,
|
|
projectGameTestSliceSessionStatus,
|
|
trustedInspectionFromRuntimeMessage,
|
|
} from '../src/features/project-workspace/gameTestSliceModel';
|
|
|
|
const version: RunnableGameVersion = {
|
|
schemaVersion: 'game-creator-runnable-version.v1',
|
|
versionId: 'runnable-r7',
|
|
projectId: 'project-1',
|
|
parentVersionId: null,
|
|
projectRevision: 7,
|
|
artifactPath: '.agent/runnable-versions/runnable-r7/artifact',
|
|
artifactSha256: 'a'.repeat(64),
|
|
entryPath: 'game/index.html',
|
|
resourceBindings: [{ slotId: 'hero', resourceId: 'hero-art' }],
|
|
testSlices: [
|
|
{
|
|
sliceId: 'boss',
|
|
versionId: 'runnable-r7',
|
|
title: '首领战',
|
|
order: 2,
|
|
startCondition: '进入首领关',
|
|
endCondition: '首领战结束',
|
|
status: 'idle',
|
|
},
|
|
{
|
|
sliceId: 'core',
|
|
versionId: 'runnable-r7',
|
|
title: '核心玩法',
|
|
order: 1,
|
|
startCondition: '游戏 ready',
|
|
endCondition: '核心循环结束',
|
|
status: 'idle',
|
|
},
|
|
],
|
|
resourceDescriptors: [
|
|
{
|
|
resourceId: 'hero-art',
|
|
name: 'hero.png',
|
|
category: 'art',
|
|
subtype: 'character',
|
|
width: 128,
|
|
height: 256,
|
|
format: 'image/png',
|
|
},
|
|
],
|
|
createdReason: 'initial',
|
|
validation: {
|
|
staticSmokePassed: true,
|
|
previewValidatePassed: true,
|
|
playtestPassed: true,
|
|
agentId: 'program-agent',
|
|
runId: 'run-7',
|
|
reportPath:
|
|
'.agent/runtime/browser-validations/program-agent/run-7/1/validation.json',
|
|
},
|
|
createdAt: 700,
|
|
};
|
|
|
|
function runtimeMessage(
|
|
type: GameRuntimeMessage['type'],
|
|
payload: GameRuntimeMessage['payload'],
|
|
): GameRuntimeMessage {
|
|
return {
|
|
schemaVersion: 'game-creator-runtime-message.v1',
|
|
protocolVersion: 'game-creator-run-bridge.v1',
|
|
requestId: `request-${type}`,
|
|
sessionId: 'session-1',
|
|
previewId: 'preview-1',
|
|
projectId: version.projectId,
|
|
sequence: 1,
|
|
type,
|
|
versionId: version.versionId,
|
|
projectRevision: version.projectRevision,
|
|
payload,
|
|
} as GameRuntimeMessage;
|
|
}
|
|
|
|
describe('P4/P5 game runtime models', () => {
|
|
it('uses only valid finite version-owned slices and enforces state transitions', () => {
|
|
const slices = formalGameTestSlices(version);
|
|
expect(slices.map((slice) => slice.sliceId)).toEqual(['core', 'boss']);
|
|
const starting = beginGameTestSlice(slices[0]);
|
|
const playing = projectGameTestSliceMessage(
|
|
starting,
|
|
runtimeMessage('runtime.slice', {
|
|
sliceId: 'core',
|
|
title: '不可信标题',
|
|
status: 'playing',
|
|
}),
|
|
);
|
|
expect(playing?.status).toBe('playing');
|
|
const completed = projectGameTestSliceMessage(
|
|
playing,
|
|
runtimeMessage('runtime.slice', {
|
|
sliceId: 'core',
|
|
title: '任意标题',
|
|
status: 'completed',
|
|
}),
|
|
);
|
|
expect(completed?.status).toBe('completed');
|
|
expect(
|
|
projectGameTestSliceMessage(
|
|
completed,
|
|
runtimeMessage('runtime.slice', {
|
|
sliceId: 'core',
|
|
title: '任意标题',
|
|
status: 'playing',
|
|
}),
|
|
)?.status,
|
|
).toBe('completed');
|
|
});
|
|
|
|
it('rejects invalid version slice identities instead of inventing a fallback', () => {
|
|
expect(
|
|
formalGameTestSlices({
|
|
...version,
|
|
testSlices: [{ ...version.testSlices![0], versionId: 'other' }],
|
|
}),
|
|
).toEqual([]);
|
|
expect(formalGameTestSlices({ ...version, testSlices: undefined })).toEqual(
|
|
[],
|
|
);
|
|
expect(
|
|
formalGameTestSlices({
|
|
...version,
|
|
testSlices: version.testSlices!.map((slice) => ({
|
|
...slice,
|
|
order: 1,
|
|
})),
|
|
}),
|
|
).toEqual([]);
|
|
expect(
|
|
formalGameTestSlices({
|
|
...version,
|
|
testSlices: [{ ...version.testSlices![0], status: 'playing' }],
|
|
}),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('projects only legal pause and resume transitions from the run session', () => {
|
|
const playing = projectGameTestSliceMessage(
|
|
beginGameTestSlice(formalGameTestSlices(version)[0]),
|
|
runtimeMessage('runtime.slice', {
|
|
sliceId: 'core',
|
|
title: '',
|
|
status: 'playing',
|
|
}),
|
|
);
|
|
const paused = projectGameTestSliceSessionStatus(
|
|
playing,
|
|
runtimeMessage('runtime.state', { status: 'paused' }),
|
|
);
|
|
expect(paused?.status).toBe('paused');
|
|
expect(
|
|
projectGameTestSliceSessionStatus(
|
|
paused,
|
|
runtimeMessage('runtime.state', { status: 'playing' }),
|
|
)?.status,
|
|
).toBe('playing');
|
|
});
|
|
|
|
it('resolves hover IDs only through the current version binding and descriptor', () => {
|
|
const resolved = trustedInspectionFromRuntimeMessage(
|
|
version,
|
|
runtimeMessage('runtime.inspection', {
|
|
inspectionId: 'hover-resource',
|
|
label: '伪造名称',
|
|
metrics: { name: '伪造资源' },
|
|
targetKind: 'resource',
|
|
targetId: 'hero-art',
|
|
}),
|
|
);
|
|
expect(resolved).toMatchObject({
|
|
name: 'hero.png',
|
|
resourceId: 'hero-art',
|
|
slotId: 'hero',
|
|
width: 128,
|
|
height: 256,
|
|
});
|
|
expect(
|
|
trustedInspectionFromRuntimeMessage(
|
|
version,
|
|
runtimeMessage('runtime.inspection', {
|
|
inspectionId: 'hover-entity',
|
|
label: '',
|
|
metrics: {},
|
|
targetKind: 'entity',
|
|
targetId: 'hero',
|
|
}),
|
|
),
|
|
).toMatchObject({ resourceId: 'hero-art', slotId: 'hero' });
|
|
expect(
|
|
trustedInspectionFromRuntimeMessage(
|
|
version,
|
|
runtimeMessage('runtime.inspection', {
|
|
inspectionId: 'hover-unbound',
|
|
label: '伪造名称',
|
|
metrics: {},
|
|
targetKind: 'resource',
|
|
targetId: 'unbound',
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
expect(
|
|
trustedInspectionFromRuntimeMessage(
|
|
version,
|
|
runtimeMessage('runtime.inspection', {
|
|
inspectionId: 'hover-clear',
|
|
label: '',
|
|
metrics: {},
|
|
targetKind: 'none',
|
|
}),
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|