feat: daybreak finale turns the contact arrival into a sunrise with a dawn chord
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/audio/soundtrack-transport';
|
||||
import type { AdaptiveMixer } from '@/audio/adaptive-mixer';
|
||||
import type { DriveTexture } from '@/audio/drive-texture';
|
||||
import type { DawnResolve } from '@/audio/dawn-resolve';
|
||||
import type {
|
||||
AudioInitialState,
|
||||
SoundtrackSources,
|
||||
@@ -41,6 +42,7 @@ interface EngineHarnessOptions {
|
||||
readonly rejectDriveLift?: boolean;
|
||||
readonly rejectDriveTexture?: boolean;
|
||||
readonly deferDriveLift?: boolean;
|
||||
readonly rejectDawn?: boolean;
|
||||
}
|
||||
|
||||
interface Deferred<T> {
|
||||
@@ -71,10 +73,12 @@ function createEngineHarness(
|
||||
const bedInput = harness.context.createGain();
|
||||
const driveLiftInput = harness.context.createGain();
|
||||
const driveTextureInput = harness.context.createGain();
|
||||
const dawnInput = harness.context.createGain();
|
||||
const mixer = {
|
||||
bedInput,
|
||||
driveLiftInput,
|
||||
driveTextureInput,
|
||||
dawnInput,
|
||||
setChapterIntent: vi.fn(),
|
||||
setDriveEnergy: vi.fn(),
|
||||
setTransportAnchor: vi.fn(),
|
||||
@@ -85,6 +89,14 @@ function createEngineHarness(
|
||||
setEnergy: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
} satisfies DriveTexture;
|
||||
const dawn = {
|
||||
setDawn: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
} satisfies DawnResolve;
|
||||
const createDawnResolve = vi.fn((_context: AudioContext, _destination: AudioNode) => {
|
||||
if (options.rejectDawn) throw new Error('dawn chord unavailable');
|
||||
return dawn;
|
||||
});
|
||||
const createMixer = vi.fn(() => mixer);
|
||||
const createDriveTexture = vi.fn(() => {
|
||||
if (options.rejectDriveTexture) throw new Error('drive texture unavailable');
|
||||
@@ -116,6 +128,7 @@ function createEngineHarness(
|
||||
loadBuffer,
|
||||
createMixer,
|
||||
createDriveTexture,
|
||||
createDawnResolve,
|
||||
});
|
||||
|
||||
const sourcesFor = (url: string): FakeSourceRecord[] => {
|
||||
@@ -144,6 +157,8 @@ function createEngineHarness(
|
||||
harness,
|
||||
mixer,
|
||||
texture,
|
||||
dawn,
|
||||
createDawnResolve,
|
||||
loadCalls,
|
||||
sourcesFor,
|
||||
sourceFor,
|
||||
@@ -348,7 +363,35 @@ describe('soundtrack engine', () => {
|
||||
expect(source.disconnectCount).toBe(1);
|
||||
}
|
||||
expect(test.texture.dispose).toHaveBeenCalledOnce();
|
||||
expect(test.dawn.dispose).toHaveBeenCalledOnce();
|
||||
expect(test.mixer.dispose).toHaveBeenCalledOnce();
|
||||
expect(test.harness.closeCount).toBe(1);
|
||||
});
|
||||
|
||||
it('routes the dawn chord into the mixer and resolves it only at the destination', () => {
|
||||
const test = createEngineHarness();
|
||||
expect(test.createDawnResolve).toHaveBeenCalledWith(test.harness.context, test.mixer.dawnInput);
|
||||
expect(test.dawn.setDawn).not.toHaveBeenCalled();
|
||||
test.graph.setChapterIntent(resolveChapterIntent('toolkit', 1));
|
||||
expect(test.dawn.setDawn).toHaveBeenLastCalledWith(false);
|
||||
test.graph.setChapterIntent(resolveChapterIntent('contact', 2));
|
||||
expect(test.dawn.setDawn).toHaveBeenLastCalledWith(true);
|
||||
// A repeated intent for the same chapter does not re-trigger the chord.
|
||||
const calls = test.dawn.setDawn.mock.calls.length;
|
||||
test.graph.setChapterIntent(resolveChapterIntent('contact', 3));
|
||||
expect(test.dawn.setDawn).toHaveBeenCalledTimes(calls);
|
||||
test.graph.setChapterIntent(resolveChapterIntent('toolkit', 4));
|
||||
expect(test.dawn.setDawn).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
|
||||
it('keeps playing when the dawn chord cannot be built, and ignores it after close', async () => {
|
||||
const test = createEngineHarness(initialState, { rejectDawn: true });
|
||||
expect(() => test.graph.setChapterIntent(resolveChapterIntent('contact', 1))).not.toThrow();
|
||||
expect(test.dawn.setDawn).not.toHaveBeenCalled();
|
||||
const closed = createEngineHarness();
|
||||
await closed.graph.close();
|
||||
closed.graph.setChapterIntent(resolveChapterIntent('contact', 1));
|
||||
expect(closed.dawn.setDawn).not.toHaveBeenCalled();
|
||||
expect(closed.dawn.dispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user