import { createServer } from 'vite'; const expectedArcs = [ { skinId: 'yellow-turban', firstBattleOrdinal: 1, lastBattleOrdinal: 4 }, { skinId: 'anti-dong', firstBattleOrdinal: 5, lastBattleOrdinal: 6 }, { skinId: 'xuzhou', firstBattleOrdinal: 7, lastBattleOrdinal: 9 }, { skinId: 'wandering', firstBattleOrdinal: 10, lastBattleOrdinal: 15 }, { skinId: 'wolong', firstBattleOrdinal: 16, lastBattleOrdinal: 18 }, { skinId: 'red-cliffs', firstBattleOrdinal: 19, lastBattleOrdinal: 22 }, { skinId: 'jing-yi', firstBattleOrdinal: 23, lastBattleOrdinal: 34 }, { skinId: 'hanzhong-shuhan', firstBattleOrdinal: 35, lastBattleOrdinal: 37 }, { skinId: 'jingzhou-crisis', firstBattleOrdinal: 38, lastBattleOrdinal: 44 }, { skinId: 'yiling-baidi', firstBattleOrdinal: 45, lastBattleOrdinal: 46 }, { skinId: 'nanzhong', firstBattleOrdinal: 47, lastBattleOrdinal: 54 }, { skinId: 'northern', firstBattleOrdinal: 55, lastBattleOrdinal: 66 } ]; const expectedTextureKeys = { 'yellow-turban': 'story-militia-training-yard', 'anti-dong': 'story-anti-dong-coalition-oath-camp', xuzhou: 'story-xuzhou-gate-council', wandering: 'story-wandering-night-camp', wolong: 'story-wolong-autumn-study', 'red-cliffs': 'story-red-cliffs-wind-altar', 'jing-yi': 'story-yizhou-council-at-river', 'hanzhong-shuhan': 'story-yizhou-mountain-pass-command-post', 'jingzhou-crisis': 'story-jingzhou-crisis-river-watchtowers', 'yiling-baidi': 'story-yiling-baidi-lantern-vigil', nanzhong: 'story-nanzhong-captures-river-night-camp', northern: 'story-northern-supply-depot' }; const expectedSpecialSteps = { 'hanzhong-king-camp': 'hanzhong-shuhan', 'shu-han-foundation-camp': 'hanzhong-shuhan', 'baidi-entrustment-camp': 'yiling-baidi', 'northern-campaign-prep-camp': 'northern', 'ending-complete': 'northern' }; const errors = []; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true }, appType: 'custom' }); try { const { campSkinDefinitions, campSkinBattleArcs, campSkinBattleOrdinal, selectCampSkin } = await server.ssrLoadModule('/src/game/data/campSkins.ts'); const { storyBackgroundAssets } = await server.ssrLoadModule('/src/game/data/storyAssets.ts'); const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts'); const expectedSkinIds = expectedArcs.map(({ skinId }) => skinId); const definitionEntries = Object.entries(campSkinDefinitions); const battleIds = Object.keys(battleScenarios); assertEqual(definitionEntries.length, expectedSkinIds.length, 'camp skin definition count'); assertEqual(campSkinBattleArcs.length, expectedArcs.length, 'camp skin arc count'); assertEqual(battleIds.length, 66, 'battle count used by camp skin ordinals'); expectedSkinIds.forEach((skinId) => { const definition = campSkinDefinitions[skinId]; assert(Boolean(definition), `missing camp skin definition "${skinId}"`); if (!definition) { return; } assertEqual(definition.id, skinId, `${skinId}: id`); assertEqual(definition.textureKey, expectedTextureKeys[skinId], `${skinId}: texture key`); ['textureKey', 'assetUrl', 'chapterLabel', 'locationLabel', 'seal'].forEach((fieldName) => { assertNonEmptyString(definition[fieldName], `${skinId}: ${fieldName}`); }); ['accentColor', 'headerColor', 'washColor'].forEach((fieldName) => { assertColor(definition[fieldName], `${skinId}: ${fieldName}`); }); assertAlpha(definition.backdropAlpha, `${skinId}: backdropAlpha`); assertAlpha(definition.washAlpha, `${skinId}: washAlpha`); assert( Object.hasOwn(storyBackgroundAssets, definition.textureKey), `${skinId}: missing story background "${definition.textureKey}"` ); assertEqual( definition.assetUrl, storyBackgroundAssets[definition.textureKey], `${skinId}: asset URL must match its story background` ); }); definitionEntries.forEach(([skinId]) => { assert(expectedSkinIds.includes(skinId), `unexpected camp skin definition "${skinId}"`); }); assertUnique( definitionEntries.map(([, definition]) => definition.textureKey), 'camp skin texture keys' ); assertUnique( definitionEntries.map( ([, definition]) => `${definition.accentColor}:${definition.headerColor}:${definition.washColor}` ), 'camp skin visual color signatures' ); let nextExpectedOrdinal = 1; expectedArcs.forEach((expectedArc, index) => { const actualArc = campSkinBattleArcs[index]; assert(Boolean(actualArc), `missing camp skin arc at index ${index}`); if (!actualArc) { return; } assertEqual(actualArc.skinId, expectedArc.skinId, `arc ${index}: skin id`); assertEqual(actualArc.firstBattleOrdinal, expectedArc.firstBattleOrdinal, `arc ${index}: first battle ordinal`); assertEqual(actualArc.lastBattleOrdinal, expectedArc.lastBattleOrdinal, `arc ${index}: last battle ordinal`); assertEqual(actualArc.firstBattleOrdinal, nextExpectedOrdinal, `arc ${index}: contiguous first battle ordinal`); assert( actualArc.lastBattleOrdinal >= actualArc.firstBattleOrdinal, `arc ${index}: last battle ordinal must not precede its first` ); nextExpectedOrdinal = actualArc.lastBattleOrdinal + 1; }); assertEqual(nextExpectedOrdinal, battleIds.length + 1, 'camp skin arcs must cover every battle ordinal'); battleIds.forEach((battleId, index) => { const battleOrdinal = index + 1; const expectedSkinId = expectedSkinForOrdinal(battleOrdinal); const nextBattleSelection = selectCampSkin({ nextBattleId: battleId }); const currentBattleSelection = selectCampSkin({ currentBattleId: battleId }); assertEqual(campSkinBattleOrdinal(battleId), battleOrdinal, `${battleId}: battle ordinal lookup`); assertSelection(nextBattleSelection, { skinId: expectedSkinId, source: 'next-battle', requestedBattleId: battleId, battleOrdinal }); assertSelection(currentBattleSelection, { skinId: expectedSkinId, source: 'current-battle', requestedBattleId: battleId, battleOrdinal }); }); Object.entries(expectedSpecialSteps).forEach(([campaignStep, skinId]) => { const selection = selectCampSkin({ campaignStep, nextBattleId: battleIds[0], currentBattleId: battleIds.at(-1) }); assertSelection(selection, { skinId, source: 'special-step', requestedBattleId: battleIds[0], battleOrdinal: 1 }); }); const currentBattleRecovery = selectCampSkin({ nextBattleId: 'unknown-battle', currentBattleId: battleIds.at(-1) }); assertSelection(currentBattleRecovery, { skinId: 'northern', source: 'current-battle', requestedBattleId: battleIds.at(-1), battleOrdinal: battleIds.length }); assertSelection(selectCampSkin({ nextBattleId: 'unknown-battle' }), { skinId: 'yellow-turban', source: 'fallback' }); assertEqual(campSkinBattleOrdinal('unknown-battle'), undefined, 'unknown battle ordinal lookup'); if (errors.length > 0) { throw new Error(`Camp skin data verification failed:\n${errors.map((error) => `- ${error}`).join('\n')}`); } console.log( `Verified ${definitionEntries.length} camp skins, ${campSkinBattleArcs.length} contiguous era arcs, ` + `${battleIds.length} next/current battle selections, and ${Object.keys(expectedSpecialSteps).length} special-step overrides.` ); } finally { await server.close(); } function expectedSkinForOrdinal(battleOrdinal) { return expectedArcs.find( ({ firstBattleOrdinal, lastBattleOrdinal }) => battleOrdinal >= firstBattleOrdinal && battleOrdinal <= lastBattleOrdinal )?.skinId; } function assertSelection(selection, expected) { const context = `${expected.source}/${expected.requestedBattleId ?? expected.skinId}`; assert(Boolean(selection), `${context}: selection is missing`); if (!selection) { return; } assertEqual(selection.skin?.id, expected.skinId, `${context}: selected skin`); assertEqual(selection.source, expected.source, `${context}: selection source`); assertEqual(selection.requestedBattleId, expected.requestedBattleId, `${context}: requested battle id`); assertEqual(selection.battleOrdinal, expected.battleOrdinal, `${context}: battle ordinal`); } function assertNonEmptyString(value, context) { assert(typeof value === 'string' && value.trim().length > 0, `${context} must be a non-empty string`); } function assertColor(value, context) { assert(Number.isInteger(value) && value >= 0 && value <= 0xffffff, `${context} must be a 24-bit integer color`); } function assertAlpha(value, context) { assert(Number.isFinite(value) && value > 0 && value <= 1, `${context} must be greater than 0 and at most 1`); } function assertUnique(values, context) { assertEqual(new Set(values).size, values.length, `${context} must be unique`); } function assertEqual(actual, expected, context) { assert(Object.is(actual, expected), `${context}: expected ${format(expected)}, received ${format(actual)}`); } function assert(condition, message) { if (!condition) { errors.push(message); } } function format(value) { return value === undefined ? 'undefined' : JSON.stringify(value); }