feat: create a large randomized library of playful game sounds and audio combos
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const { createSoundDirector, COUNTS, CATALOG } = require('../../data/game_sounds.js');
|
||||
|
||||
function recorder() { const played = []; return { played, playPreset: preset => { played.push(preset); return true; } }; }
|
||||
|
||||
test('every required event family has its planned minimum of original synthesis presets', () => {
|
||||
Object.entries(COUNTS).forEach(([family, count]) => {
|
||||
assert.equal(CATALOG[family].length, count);
|
||||
assert.equal(new Set(CATALOG[family].map(preset => preset.id)).size, count);
|
||||
});
|
||||
});
|
||||
|
||||
test('selection is randomized without immediate repeats and keeps bounded history', () => {
|
||||
const output = recorder(); let sample = 0;
|
||||
const director = createSoundDirector({ engine: output, random: () => (sample++ % 11) / 11, historyLimit: 3 });
|
||||
const selected = [];
|
||||
for (let index = 0; index < 30; index += 1) selected.push(director.play('shot').preset.id);
|
||||
selected.slice(1).forEach((id, index) => assert.notEqual(id, selected[index]));
|
||||
assert.ok(director.getHistory('shot').length <= 3);
|
||||
});
|
||||
|
||||
test('preset parameter ranges remain safe through many randomized selections', () => {
|
||||
const output = recorder(); const director = createSoundDirector({ engine: output, random: Math.random });
|
||||
for (let index = 0; index < 3000; index += 1) director.play(Object.keys(COUNTS)[index % Object.keys(COUNTS).length]);
|
||||
output.played.forEach(preset => {
|
||||
assert.ok(preset.frequency >= 100 && preset.frequency <= 1000);
|
||||
assert.ok(preset.endFrequency >= 100 && preset.endFrequency <= 1000);
|
||||
assert.ok(preset.durationMs >= 100 && preset.durationMs <= 900);
|
||||
assert.ok(preset.filterHz >= 900 && preset.filterHz <= 3500);
|
||||
assert.ok(Math.abs(preset.pan) <= 0.4 && preset.gain <= 0.75);
|
||||
});
|
||||
});
|
||||
|
||||
test('state versions, perspectives, priority, waiting rate limit, and reconnect summaries are safe', () => {
|
||||
const output = recorder(); let clock = 0;
|
||||
const director = createSoundDirector({ engine: output, random: () => 0, now: () => clock, waitingIntervalMs: 12000 });
|
||||
assert.ok(director.getPriority('victory') > director.getPriority('sunk'));
|
||||
assert.ok(director.getPriority('sunk') > director.getPriority('hit'));
|
||||
assert.ok(director.getPriority('hit') > director.getPriority('miss'));
|
||||
assert.equal(director.play('watch-hit', { version: 4 }).preset.family, 'hit');
|
||||
assert.equal(director.play('damage', { version: 4 }), false);
|
||||
assert.equal(director.play('watch-sunk', { version: 5 }).preset.family, 'sunk');
|
||||
assert.equal(director.play('waiting').preset.family, 'waiting');
|
||||
assert.equal(director.play('waiting'), false);
|
||||
clock = 12001;
|
||||
assert.equal(director.play('waiting').preset.family, 'waiting');
|
||||
assert.equal(director.play('summary', { version: 6, summary: true }).preset.family, 'victory');
|
||||
});
|
||||
|
||||
test('combos escalate, reduced intensity suppresses extras, and recovery remains calm', () => {
|
||||
const output = recorder(); const director = createSoundDirector({ engine: output, random: () => 0.3 });
|
||||
assert.equal(director.play('hit', { combo: 2 }).combo, 'combo');
|
||||
assert.equal(director.play('hit', { combo: 3 }).combo, 'mega');
|
||||
assert.equal(director.play('sunk', { combo: 4 }).combo, 'ultra');
|
||||
const count = output.played.length;
|
||||
assert.equal(director.play('hit', { combo: 4, reduced: true }).combo, 'ultra');
|
||||
assert.equal(output.played.length, count + 1);
|
||||
assert.equal(director.play('waiting', { reduced: true }), false);
|
||||
const recovery = ['recovery-open', 'recovery-cancel', 'recovery-leave', 'recovery-profile', 'recovery-game'].map(event => director.play(event).preset);
|
||||
assert.equal(new Set(recovery.map(preset => preset.id)).size, 5);
|
||||
recovery.forEach(preset => assert.ok(preset.family === 'recovery' && preset.gain <= 0.16 && preset.durationMs <= 300));
|
||||
});
|
||||
@@ -111,11 +111,14 @@ test('the application exposes an explicit Russian sound control and cleans audio
|
||||
assert.match(page, /id="sound-reduced"/);
|
||||
assert.match(page, /id="recovery-sound-mute"/);
|
||||
assert.match(page, /src="\/web_audio\.js"/);
|
||||
assert.match(page, /src="\/game_sounds\.js"/);
|
||||
assert.match(firmware, /strcmp\(request->uri, "\/web_audio\.js"\)/);
|
||||
assert.match(firmware, /strcmp\(request->uri, "\/game_sounds\.js"\)/);
|
||||
assert.match(app, /audio\.cleanup\(\)/);
|
||||
assert.match(app, /globalThis\.BattleshipAudio\?\.createAudioEngine\?\.\(\) \|\| silentAudio/);
|
||||
assert.match(app, /visibilitychange/);
|
||||
assert.match(app, /soundKeys = new Set/);
|
||||
assert.match(app, /ui\.recoverySoundMute\.hidden = !preferences\.enabled/);
|
||||
assert.match(app, /\/api\/game\/shot/);
|
||||
assert.match(app, /sounds\.play\('shot'/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user