Files
heros_web/scripts/verify-battle-environment-profiles.mjs

170 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();',
'campaignPresentationProfileFor(battleScenario.id)',
'const tintColor = profile?.tint.color',
'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();
}