feat: add playable Zhuo prologue village
This commit is contained in:
160
scripts/verify-prologue-village-data.mjs
Normal file
160
scripts/verify-prologue-village-data.mjs
Normal file
@@ -0,0 +1,160 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user