feat: refine exploration presentation and audio flow

This commit is contained in:
2026-07-28 05:00:24 +09:00
parent 1773bffbf2
commit 495f98372c
20 changed files with 2295 additions and 354 deletions

View File

@@ -63,11 +63,22 @@ const server = await createServer({
});
try {
const { campMusicGroups, campAmbienceDefinitions, campSoundscapeMapping, getCampSoundscape } =
const {
campMusicGroups,
campAmbienceDefinitions,
campSoundscapeMapping,
getCampSoundscape,
campMusicVolumeForTrackKey,
musicVolumeMatchedToCampTrack
} =
await server.ssrLoadModule('/src/game/data/campSoundscapes.ts');
const { selectCampSkin } = await server.ssrLoadModule('/src/game/data/campSkins.ts');
const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts');
const { musicTracks, ambienceTracks } = await server.ssrLoadModule('/src/game/audio/audioAssets.ts');
const {
musicTracks,
ambienceTracks,
musicTrackIntegratedLoudnessLufs
} = await server.ssrLoadModule('/src/game/audio/audioAssets.ts');
const expectedSkinIds = Object.keys(expectedMapping);
const musicEntries = Object.entries(campMusicGroups);
@@ -83,6 +94,38 @@ try {
validateDefinitions(musicEntries, expectedMusicGroups, musicTracks, 'music group', 0.05, 0.4);
validateDefinitions(ambienceEntries, expectedAmbiences, ambienceTracks, 'ambience', 0.03, 0.3);
musicEntries.forEach(([, definition]) => {
assertEqual(
campMusicVolumeForTrackKey(definition.trackKey),
definition.volume,
`${definition.trackKey}: shared exploration music volume`
);
});
assertEqual(
campMusicVolumeForTrackKey('unknown-track', 0.19),
0.19,
'unknown exploration music fallback volume'
);
assertEqual(
musicVolumeMatchedToCampTrack('militia-theme'),
0.124,
'militia theme volume matched to the camp-rally perceptual level'
);
const militiaEffectiveLoudness =
musicTrackIntegratedLoudnessLufs['militia-theme'] +
20 * Math.log10(musicVolumeMatchedToCampTrack('militia-theme'));
const campRallyEffectiveLoudness =
musicTrackIntegratedLoudnessLufs['camp-rally'] +
20 * Math.log10(campMusicVolumeForTrackKey('camp-rally'));
assert(
Math.abs(
militiaEffectiveLoudness - campRallyEffectiveLoudness
) <= 0.1,
`militia and camp-rally effective loudness must match within 0.1 LU; received ${(
militiaEffectiveLoudness - campRallyEffectiveLoudness
).toFixed(2)} LU`
);
validateExplorationSceneMixes();
validateCampAudioAssets(
musicEntries.map(([, definition]) => ({ key: definition.trackKey, url: musicTracks[definition.trackKey], kind: 'music' })),
ambienceEntries.map(([, definition]) => ({ key: definition.trackKey, url: ambienceTracks[definition.trackKey], kind: 'ambience' }))
@@ -143,6 +186,34 @@ try {
await server.close();
}
function validateExplorationSceneMixes() {
const citySource = readFileSync('src/game/scenes/CityStayScene.ts', 'utf8');
const villageSource = readFileSync('src/game/scenes/PrologueVillageScene.ts', 'utf8');
const campVisitSource = readFileSync(
'src/game/scenes/CampVisitExplorationScene.ts',
'utf8'
);
assert(
citySource.includes('musicVolume: campMusicVolumeForTrackKey(this.profile.musicKey)'),
'city stays must reuse the calibrated camp music level for their selected track'
);
assert(
villageSource.includes(
"musicVolume: musicVolumeMatchedToCampTrack('militia-theme')"
) &&
!villageSource.includes('musicVolume: 0.72') &&
!villageSource.includes('musicVolume: 0.22'),
'the prologue village must loudness-match its hotter source track to the calibrated camp mix'
);
assertEqual(
campVisitSource.match(
/musicVolume:\s*campMusicVolumeForTrackKey\('camp-rally'\)/g
)?.length ?? 0,
2,
'camp visits must reuse the calibrated camp-rally level in both exploration variants'
);
}
function validateDefinitions(entries, expectedDefinitions, registeredTracks, context, minimumVolume, maximumVolume) {
const expectedIds = Object.keys(expectedDefinitions);
const registeredKeys = new Set(Object.keys(registeredTracks));