feat: deepen audiovisual story immersion

This commit is contained in:
2026-07-23 10:57:07 +09:00
parent a9e401aedf
commit 8cf0886d7d
38 changed files with 2037 additions and 144 deletions

View File

@@ -65,6 +65,7 @@ try {
campaignBattlePresentationProfiles,
campaignPresentationStatePolicies,
campaignPresentationTransitionPolicy,
campaignBattleAmbienceFallbackVolume,
campaignPresentationArcForOrdinal,
campaignPresentationProfileFor,
campaignPresentationStateRuleFor,
@@ -76,6 +77,7 @@ try {
const arcEntries = Object.entries(campaignArcPresentationProfiles);
const arcIds = Object.keys(expectedArcRanges);
const battleMusicKeys = new Set();
let battleAmbienceFallbackCount = 0;
assertEqual(battleIds.length, 66, 'campaign battle count');
assertEqual(new Set(battleIds).size, 66, 'canonical campaign route uniqueness');
@@ -193,11 +195,28 @@ try {
const battlePlan = resolveCampaignPresentationSoundscape({ battleId, stage: 'battle' });
const environment = battleEnvironmentProfiles[battleId];
const expectedAmbienceKey = environment?.soundscape.ambienceKey ?? profile?.arc.audio.defaultAmbienceKey;
const expectedAmbienceVolume = environment?.soundscape.ambienceVolume ??
campaignBattleAmbienceFallbackVolume(profile?.arc.audio.defaultAmbienceVolume);
assertEqual(battlePlan?.musicKey, profile?.arc.audio.battleMusicKey, `${battleId}: battle music plan`);
assertEqual(battlePlan?.ambienceKey, environment?.soundscape.ambienceKey, `${battleId}: battle ambience plan`);
assertEqual(battlePlan?.ambienceVolume, environment?.soundscape.ambienceVolume, `${battleId}: battle ambience volume`);
assertEqual(battlePlan?.ambienceKey, expectedAmbienceKey, `${battleId}: battle ambience plan`);
assertEqual(battlePlan?.ambienceVolume, expectedAmbienceVolume, `${battleId}: battle ambience volume`);
assert(Boolean(battlePlan?.ambienceKey), `${battleId}: every battle must retain a quiet ambience bed`);
if (!environment) {
battleAmbienceFallbackCount += 1;
assert(
battlePlan.ambienceVolume >= campaignPresentationTransitionPolicy.battleAmbienceFallbackMinimum &&
battlePlan.ambienceVolume <= campaignPresentationTransitionPolicy.battleAmbienceFallbackMaximum,
`${battleId}: fallback ambience must stay inside the quiet battle mix`
);
}
assert(battlePlan?.transition === campaignPresentationTransitionPolicy, `${battleId}: transition policy identity`);
});
assertEqual(
battleAmbienceFallbackCount,
battleIds.length - Object.keys(battleEnvironmentProfiles).length,
'battle ambience fallback count'
);
const preservedStoryPlan = resolveCampaignPresentationSoundscape({
battleId: battleIds[0],
@@ -250,7 +269,14 @@ try {
assertEqual(campaignPresentationTransitionPolicy.sameTrackBehavior, 'keep-current-loop', 'same-track behavior');
assertEqual(campaignPresentationTransitionPolicy.musicCrossfadeMs, 900, 'music crossfade duration');
assertEqual(campaignPresentationTransitionPolicy.ambienceCrossfadeMs, 1200, 'ambience crossfade duration');
assertEqual(campaignPresentationTransitionPolicy.battleAmbienceBehavior, 'specific-profile-only', 'battle ambience behavior');
assertEqual(
campaignPresentationTransitionPolicy.battleAmbienceBehavior,
'specific-profile-then-arc-fallback',
'battle ambience behavior'
);
assertEqual(campaignPresentationTransitionPolicy.battleAmbienceFallbackScale, 0.58, 'battle ambience fallback scale');
assertEqual(campaignPresentationTransitionPolicy.battleAmbienceFallbackMinimum, 0.05, 'battle ambience fallback minimum');
assertEqual(campaignPresentationTransitionPolicy.battleAmbienceFallbackMaximum, 0.065, 'battle ambience fallback maximum');
assert(Object.isFrozen(campaignPresentationTransitionPolicy), 'transition policy must be frozen');
const soundDirectorSource = readFileSync('src/game/audio/SoundDirector.ts', 'utf8');
@@ -308,7 +334,8 @@ try {
console.log(
`Verified ${arcEntries.length} campaign presentation arcs across ${battleIds.length} battles, ` +
`${battleMusicKeys.size} battle music groups, all state policies, palettes, motifs, and crossfade constraints.`
`${battleMusicKeys.size} battle music groups, ${battleAmbienceFallbackCount} quiet ambience fallbacks, ` +
'all state policies, palettes, motifs, and crossfade constraints.'
);
} finally {
await server.close();