161 lines
4.8 KiB
JavaScript
161 lines
4.8 KiB
JavaScript
import { createServer } from 'vite';
|
|
|
|
const expectedOpeningPageIds = ['yellow-turban-chaos', 'liu-bei-resolve'];
|
|
const expectedDeparturePageIds = [
|
|
'first-sortie',
|
|
'yellow-turban-nearby',
|
|
'first-battle-plan-talk',
|
|
'battle-briefing'
|
|
];
|
|
const expectedRequiredObjectiveIds = [
|
|
'meet-zhang-fei',
|
|
'meet-guan-yu',
|
|
'check-supplies'
|
|
];
|
|
const expectedCampaignMarkerIds = [
|
|
'prologue-village-entered',
|
|
'prologue-village-meet-zhang-fei',
|
|
'prologue-village-meet-guan-yu',
|
|
'prologue-village-check-supplies',
|
|
'prologue-village-complete'
|
|
];
|
|
|
|
const server = await createServer({
|
|
logLevel: 'error',
|
|
server: { middlewareMode: true },
|
|
appType: 'custom'
|
|
});
|
|
|
|
try {
|
|
const village = await server.ssrLoadModule('/src/game/data/prologueVillage.ts');
|
|
const campaign = await server.ssrLoadModule('/src/game/state/campaignState.ts');
|
|
const unitAssets = await server.ssrLoadModule('/src/game/data/unitAssets.ts');
|
|
const failures = [];
|
|
|
|
const openingPages = village.prologueOpeningPages();
|
|
const departurePages = village.prologueDeparturePages();
|
|
assertArrayEquals(
|
|
openingPages.map((page) => page.id),
|
|
expectedOpeningPageIds,
|
|
'opening story page order',
|
|
failures
|
|
);
|
|
assertArrayEquals(
|
|
departurePages.map((page) => page.id),
|
|
expectedDeparturePageIds,
|
|
'departure story page order',
|
|
failures
|
|
);
|
|
assertArrayEquals(
|
|
[...village.prologueVillageRequiredObjectiveIds],
|
|
expectedRequiredObjectiveIds,
|
|
'required objective order',
|
|
failures
|
|
);
|
|
|
|
const objectives = village.prologueVillageObjectiveDefinitions;
|
|
assert(objectives.length === 4, `expected 4 objective rows, received ${objectives.length}`, failures);
|
|
assert(
|
|
new Set(objectives.map((objective) => objective.id)).size === objectives.length,
|
|
'objective ids must be unique',
|
|
failures
|
|
);
|
|
assert(
|
|
objectives.at(-1)?.id === 'make-oath',
|
|
'the final objective must be make-oath',
|
|
failures
|
|
);
|
|
|
|
const npcs = village.prologueVillageNpcDefinitions;
|
|
const objectiveNpcIds = npcs
|
|
.filter((npc) => npc.objectiveId)
|
|
.map((npc) => npc.objectiveId);
|
|
assertArrayEquals(
|
|
[...objectiveNpcIds].sort(),
|
|
[...expectedRequiredObjectiveIds].sort(),
|
|
'required NPC objective coverage',
|
|
failures
|
|
);
|
|
assert(
|
|
new Set(npcs.map((npc) => npc.id)).size === npcs.length,
|
|
'NPC ids must be unique',
|
|
failures
|
|
);
|
|
assert(npcs.some((npc) => !npc.objectiveId), 'at least one optional villager should be available', failures);
|
|
|
|
for (const npc of npcs) {
|
|
assert(
|
|
Number.isFinite(npc.x) && npc.x >= 70 && npc.x <= 1435 &&
|
|
Number.isFinite(npc.y) && npc.y >= 130 && npc.y <= 900,
|
|
`${npc.id}: position must remain inside the FHD village play area`,
|
|
failures
|
|
);
|
|
assert(unitAssets.hasUnitSheetAsset(npc.textureKey), `${npc.id}: missing ${npc.textureKey}`, failures);
|
|
assert(Array.isArray(npc.dialogue) && npc.dialogue.length > 0, `${npc.id}: dialogue is required`, failures);
|
|
for (const [lineIndex, line] of npc.dialogue.entries()) {
|
|
assert(
|
|
typeof line.speaker === 'string' && line.speaker.trim().length > 0,
|
|
`${npc.id}[${lineIndex}]: speaker is required`,
|
|
failures
|
|
);
|
|
assert(
|
|
typeof line.text === 'string' && line.text.trim().length >= 8 && line.text.length <= 180,
|
|
`${npc.id}[${lineIndex}]: dialogue must be 8-180 characters`,
|
|
failures
|
|
);
|
|
if (line.textureKey) {
|
|
assert(
|
|
unitAssets.hasUnitSheetAsset(line.textureKey),
|
|
`${npc.id}[${lineIndex}]: missing dialogue sprite ${line.textureKey}`,
|
|
failures
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(
|
|
unitAssets.hasUnitSheetAsset('unit-liu-bei'),
|
|
'the controllable Liu Bei base sheet is required',
|
|
failures
|
|
);
|
|
assertArrayEquals(
|
|
Object.values(campaign.prologueVillageCampaignTutorialIds),
|
|
expectedCampaignMarkerIds,
|
|
'campaign progress marker ids',
|
|
failures
|
|
);
|
|
assert(
|
|
expectedCampaignMarkerIds.every((id) => campaign.campaignTutorialIds.includes(id)),
|
|
'every village marker must survive campaign save normalization',
|
|
failures
|
|
);
|
|
|
|
if (failures.length > 0) {
|
|
throw new Error(
|
|
`Prologue village data verification failed with ${failures.length} issue(s):\n` +
|
|
failures.map((failure) => `- ${failure}`).join('\n')
|
|
);
|
|
}
|
|
|
|
console.log(
|
|
`Verified ${npcs.length} village NPCs, ${expectedRequiredObjectiveIds.length} required interactions, ` +
|
|
`${openingPages.length} opening pages, ${departurePages.length} departure pages, and save markers.`
|
|
);
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
|
|
function assert(condition, message, failures) {
|
|
if (!condition) {
|
|
failures.push(message);
|
|
}
|
|
}
|
|
|
|
function assertArrayEquals(actual, expected, label, failures) {
|
|
assert(
|
|
JSON.stringify(actual) === JSON.stringify(expected),
|
|
`${label}: expected [${expected.join(', ')}], received [${actual.join(', ')}]`,
|
|
failures
|
|
);
|
|
}
|