feat: deepen battle atmosphere and portrait eras
This commit is contained in:
168
scripts/verify-battle-environment-profiles.mjs
Normal file
168
scripts/verify-battle-environment-profiles.mjs
Normal file
@@ -0,0 +1,168 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user