169 lines
6.6 KiB
JavaScript
169 lines
6.6 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { createServer } from 'vite';
|
|
|
|
const expectedProfiles = {
|
|
'ninth-battle-xuzhou-gate-night-raid': {
|
|
profileId: 'xuzhou-night-mist',
|
|
effect: 'night-mist',
|
|
ambienceKey: 'river-night-ambience',
|
|
particleKind: null,
|
|
hazeKind: 'mist'
|
|
},
|
|
'twenty-second-battle-red-cliffs-fire': {
|
|
profileId: 'red-cliffs-firestorm',
|
|
effect: 'embers-smoke',
|
|
ambienceKey: 'battle-fire',
|
|
particleKind: 'ember',
|
|
hazeKind: 'smoke'
|
|
},
|
|
'fortieth-battle-han-river-flood': {
|
|
profileId: 'han-river-flood-rain',
|
|
effect: 'rain-mist',
|
|
ambienceKey: 'battle-rain',
|
|
particleKind: 'rain',
|
|
hazeKind: 'mist'
|
|
},
|
|
'forty-sixth-battle-yiling-fire': {
|
|
profileId: 'yiling-burning-forest',
|
|
effect: 'embers-smoke',
|
|
ambienceKey: 'battle-fire',
|
|
particleKind: 'ember',
|
|
hazeKind: 'smoke'
|
|
},
|
|
'sixty-first-battle-hanzhong-rain-defense': {
|
|
profileId: 'hanzhong-defense-rain',
|
|
effect: 'steady-rain',
|
|
ambienceKey: 'battle-rain',
|
|
particleKind: 'rain',
|
|
hazeKind: 'mist'
|
|
},
|
|
'sixty-sixth-battle-wuzhang-final': {
|
|
profileId: 'wuzhang-snow-wind',
|
|
effect: 'snow-wind',
|
|
ambienceKey: 'mountain-wind-ambience',
|
|
particleKind: 'snow',
|
|
hazeKind: 'mist'
|
|
}
|
|
};
|
|
|
|
const server = await createServer({
|
|
logLevel: 'error',
|
|
server: { middlewareMode: true, hmr: false },
|
|
appType: 'custom'
|
|
});
|
|
|
|
try {
|
|
const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts');
|
|
const { ambienceTracks } = await server.ssrLoadModule('/src/game/audio/audioAssets.ts');
|
|
const environmentModule = await server.ssrLoadModule('/src/game/data/battleEnvironmentProfiles.ts');
|
|
const {
|
|
battleEnvironmentProfiles,
|
|
battleEnvironmentProfileBattleIds,
|
|
battleEnvironmentProfileFor,
|
|
battleEnvironmentProfileVerification,
|
|
validateBattleEnvironmentProfiles
|
|
} = environmentModule;
|
|
const errors = [...validateBattleEnvironmentProfiles()];
|
|
const expectedBattleIds = Object.keys(expectedProfiles).sort();
|
|
const actualBattleIds = Object.keys(battleEnvironmentProfiles).sort();
|
|
const declaredBattleIds = [...battleEnvironmentProfileBattleIds].sort();
|
|
|
|
if (JSON.stringify(actualBattleIds) !== JSON.stringify(expectedBattleIds)) {
|
|
errors.push(`expected exactly six representative profile keys: ${JSON.stringify(actualBattleIds)}`);
|
|
}
|
|
if (JSON.stringify(declaredBattleIds) !== JSON.stringify(expectedBattleIds)) {
|
|
errors.push(`representative battle id declaration is out of sync: ${JSON.stringify(declaredBattleIds)}`);
|
|
}
|
|
|
|
const profileIds = new Set();
|
|
Object.entries(expectedProfiles).forEach(([battleId, expected]) => {
|
|
const profile = battleEnvironmentProfiles[battleId];
|
|
if (!battleScenarios[battleId]) {
|
|
errors.push(`${battleId}: battle scenario does not exist`);
|
|
}
|
|
if (!profile) {
|
|
errors.push(`${battleId}: missing environment profile`);
|
|
return;
|
|
}
|
|
if (battleEnvironmentProfileFor(battleId) !== profile) {
|
|
errors.push(`${battleId}: lookup API must return the registered profile object`);
|
|
}
|
|
if (profile.id !== expected.profileId || profile.effect !== expected.effect) {
|
|
errors.push(`${battleId}: expected ${expected.profileId}/${expected.effect}, got ${profile.id}/${profile.effect}`);
|
|
}
|
|
if (profile.soundscape?.ambienceKey !== expected.ambienceKey) {
|
|
errors.push(
|
|
`${battleId}: expected ambience ${expected.ambienceKey}, got ${profile.soundscape?.ambienceKey ?? null}`
|
|
);
|
|
}
|
|
if (!ambienceTracks[profile.soundscape?.ambienceKey]) {
|
|
errors.push(`${battleId}: ambience track is not registered: ${profile.soundscape?.ambienceKey ?? null}`);
|
|
}
|
|
if (
|
|
!Number.isFinite(profile.soundscape?.ambienceVolume) ||
|
|
profile.soundscape.ambienceVolume <= 0 ||
|
|
profile.soundscape.ambienceVolume > 0.16
|
|
) {
|
|
errors.push(`${battleId}: ambience volume must stay within 0..0.16`);
|
|
}
|
|
if ((profile.particles?.kind ?? null) !== expected.particleKind) {
|
|
errors.push(`${battleId}: expected particle kind ${expected.particleKind}, got ${profile.particles?.kind ?? null}`);
|
|
}
|
|
if ((profile.haze?.kind ?? null) !== expected.hazeKind) {
|
|
errors.push(`${battleId}: expected haze kind ${expected.hazeKind}, got ${profile.haze?.kind ?? null}`);
|
|
}
|
|
if (profileIds.has(profile.id)) {
|
|
errors.push(`${battleId}: duplicate profile id ${profile.id}`);
|
|
}
|
|
profileIds.add(profile.id);
|
|
if (!Number.isInteger(profile.particles?.count ?? 0) || (profile.particles?.count ?? 0) > 40) {
|
|
errors.push(`${battleId}: particle fixed-pool budget must be an integer at or below 40`);
|
|
}
|
|
if (!Number.isInteger(profile.haze?.count ?? 0) || (profile.haze?.count ?? 0) > 4) {
|
|
errors.push(`${battleId}: haze fixed-pool budget must be an integer at or below 4`);
|
|
}
|
|
if ((profile.particles?.count ?? 0) + (profile.haze?.count ?? 0) + 1 > 45) {
|
|
errors.push(`${battleId}: total environment object budget exceeds 45`);
|
|
}
|
|
if (profile.particles && (!Array.isArray(profile.particles.colors) || profile.particles.colors.length === 0)) {
|
|
errors.push(`${battleId}: particle colors are required`);
|
|
}
|
|
});
|
|
|
|
if (
|
|
battleEnvironmentProfileVerification?.valid !== true ||
|
|
battleEnvironmentProfileVerification?.profileCount !== expectedBattleIds.length ||
|
|
battleEnvironmentProfileVerification?.representativeBattleCount !== expectedBattleIds.length ||
|
|
battleEnvironmentProfileVerification?.maximumParticleCount > 40 ||
|
|
battleEnvironmentProfileVerification?.maximumHazeCount > 4
|
|
) {
|
|
errors.push(`invalid profile verification summary: ${JSON.stringify(battleEnvironmentProfileVerification)}`);
|
|
}
|
|
|
|
const battleSceneSource = readFileSync('src/game/scenes/BattleScene.ts', 'utf8');
|
|
[
|
|
'battleEnvironmentProfileFor(battleScenario.id)',
|
|
'this.drawBattleEnvironment();',
|
|
'this.updateBattleEnvironment(delta);',
|
|
'this.clearBattleEnvironment();',
|
|
'ambienceKey: profile?.soundscape.ambienceKey',
|
|
'environment: this.battleEnvironmentDebugState()'
|
|
].forEach((requiredSource) => {
|
|
if (!battleSceneSource.includes(requiredSource)) {
|
|
errors.push(`BattleScene environment integration is missing: ${requiredSource}`);
|
|
}
|
|
});
|
|
|
|
if (errors.length > 0) {
|
|
console.error(`Battle environment profile verification failed with ${errors.length} issue(s):`);
|
|
errors.forEach((error) => console.error(`- ${error}`));
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log(
|
|
`Verified ${expectedBattleIds.length} battle environment profiles, fixed-pool budgets, lookup API, and BattleScene lifecycle/debug integration.`
|
|
);
|
|
}
|
|
} finally {
|
|
await server.close();
|
|
}
|