feat: add narrative page turn audio cue
This commit is contained in:
@@ -102,6 +102,7 @@ try {
|
||||
'operation-alert': '/audio/operation-alert.ogg',
|
||||
'objective-success': '/audio/objective-success.ogg',
|
||||
'objective-failure': '/audio/objective-failure.ogg',
|
||||
'story-page-turn': '/audio/story-page-turn.ogg',
|
||||
'ui-select': '/audio/ui-select.ogg'
|
||||
});
|
||||
director.registerEffectPools({
|
||||
@@ -387,7 +388,7 @@ try {
|
||||
debug = director.getDebugState();
|
||||
assert.deepEqual(
|
||||
debug.semanticCues.cooldownMs,
|
||||
{ turn: 900, operation: 650, objective: 1200 },
|
||||
{ turn: 900, operation: 650, objective: 1200, narrative: 220 },
|
||||
'semantic cue cooldowns should be visible in debug state'
|
||||
);
|
||||
assert.deepEqual(
|
||||
@@ -417,6 +418,35 @@ try {
|
||||
assert.equal(debug.activeEffectCount, 0, 'tactical cues should retire after their playback windows');
|
||||
assert.equal(audioHarness.activeInstances().length, 1, 'tactical cue cleanup should leave only music active');
|
||||
|
||||
const narrativeCueStart = audioHarness.instances.length;
|
||||
const musicDuckRevisionBeforeNarrativeCue = debug.musicDuck.revision;
|
||||
assert.equal(director.playStoryAdvanceCue(), true, 'the first story advance cue should play');
|
||||
assertClose(audioHarness.byOriginalSrc('/audio/story-page-turn.ogg').volume, 0.15, 'story advance cue volume');
|
||||
debug = director.getDebugState();
|
||||
assert.equal(
|
||||
debug.musicDuck.revision,
|
||||
musicDuckRevisionBeforeNarrativeCue,
|
||||
'story advance cues must not duck music'
|
||||
);
|
||||
assert.deepEqual(
|
||||
debug.semanticCues.lastPlayed,
|
||||
{ group: 'narrative', key: 'story-page-turn', at: clock.now },
|
||||
'debug state should expose a played story advance cue'
|
||||
);
|
||||
assert.equal(director.playStoryAdvanceCue(), false, 'rapid story advance cues should be coalesced');
|
||||
debug = director.getDebugState();
|
||||
assert.deepEqual(
|
||||
debug.semanticCues.lastSuppressed,
|
||||
{ group: 'narrative', key: 'story-page-turn', at: clock.now, remainingMs: 220 },
|
||||
'debug state should expose a coalesced story advance cue'
|
||||
);
|
||||
assert.equal(audioHarness.instances.length, narrativeCueStart + 1, 'coalesced story cues must not construct Audio');
|
||||
clock.advance(220);
|
||||
assert.equal(director.playStoryAdvanceCue(), true, 'story advance cues should replay after the narrative cooldown');
|
||||
assert.equal(audioHarness.instances.length, narrativeCueStart + 2, 'expired narrative cooldown should permit new Audio');
|
||||
clock.advance(1100);
|
||||
assert.equal(director.getDebugState().activeEffectCount, 0, 'story advance cues should retire after playback');
|
||||
|
||||
const semanticEffectStart = audioHarness.instances.length;
|
||||
director.playMeleeSwing(true, false);
|
||||
assert.equal(director.getDebugState().lastEffect?.key, 'melee-swing', 'melee API should use the swing pool');
|
||||
@@ -455,7 +485,7 @@ try {
|
||||
console.log(
|
||||
`Verified dual SoundDirector channels across ${debug.music.elementRevision} music and ` +
|
||||
`${debug.ambience.elementRevision} ambience element revisions, no-repeat effect pools, ` +
|
||||
'coalesced tactical cues, semantic combat effects, category multipliers, and deterministic music ducking.'
|
||||
'coalesced tactical and narrative cues, semantic combat effects, category multipliers, and deterministic music ducking.'
|
||||
);
|
||||
} finally {
|
||||
restoreGlobals();
|
||||
|
||||
Reference in New Issue
Block a user