Files
feuerwerk/tests/core/project-files.test.ts
T
adminandClaude Opus 5.5 ed2b58b3c0 Load the sounds of a project ahead of their first play
A sound was fetched and decoded on its first Play sound. That play
started late, its beginning was not heard, and a graph that tuned the
voice in the meantime had moved on by the time it started. Images and
fonts were already loaded when a project opens. Sounds now are too.

- projectFiles lists the sounds of a project, and preloadProjectFiles
  hands them to the new soundLibrary.preload. That covers the editor
  and the published game.
- Preloading opens no audio device. It decodes with an
  OfflineAudioContext at 48 kHz. The real AudioContext is still made
  on the first play, and a source resamples where the device runs at
  another rate.
- A run started right after opening a project can still find a sound
  loading. It then starts late as before.

Tests:
- sound-library: a preload makes only the offline context, and a
  play after it starts at once, opening the device only then.
- sound-library + project-files: 18 tests, green.
- e2e 34 on melektaus-atx: ALL PASSED, 30 checks.
- Counter-check: decoding with the real AudioContext again fails the
  new test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:47:00 +02:00

81 lines
2.5 KiB
TypeScript

/**
* What the editor loads ahead of the first draw: every stored file a project's assets name, so an
* animation or an animated tile does not blink out the first time it reaches a frame of its own,
* and the first play of a sound is not late.
*/
import { describe, expect, it } from 'vitest';
import type { Asset } from '../../src/core/model/Asset';
import { createEmptyProject } from '../../src/core/model/Project';
import { projectFiles } from '../../src/core/model/projectFiles';
const PNG_A = `${'a'.repeat(64)}.png`;
const PNG_B = `${'b'.repeat(64)}.webp`;
const FONT = `${'c'.repeat(64)}.woff2`;
const CEL_A = `${'d'.repeat(64)}.cel`;
const CEL_B = `${'e'.repeat(64)}.cel`;
const SOUND = `${'f'.repeat(64)}.ogg`;
function projectWith(assets: unknown[]) {
const project = createEmptyProject('p', 'P');
project.assets.push(...(assets as Asset[]));
return project;
}
describe('the files a project names', () => {
it('lists every texture, font, cel and sound, used somewhere or not', () => {
const project = projectWith([
{
id: 't1',
type: 'texture',
name: 'Water 1',
props: {},
file: PNG_A,
width: 16,
height: 16,
frames: [],
},
{
id: 't2',
type: 'texture',
name: 'Water 2',
props: {},
file: PNG_B,
width: 16,
height: 16,
frames: [],
},
{ id: 'f', type: 'font', name: 'Font', props: {}, file: FONT },
{
id: 'px',
type: 'pixelart',
name: 'Px',
props: {},
width: 8,
height: 8,
layers: [{ id: 'l', name: 'L', opacity: 1 }],
frames: [
{ id: 'f1', cels: { l: CEL_A } },
{ id: 'f2', cels: { l: CEL_B } },
{ id: 'f3', cels: { l: CEL_A } },
],
},
{ id: 's', type: 'sound', name: 'Blip', props: {}, file: SOUND },
]);
expect(projectFiles(project)).toEqual({
textures: [PNG_A, PNG_B],
fonts: [FONT],
cels: [CEL_A, CEL_B],
sounds: [SOUND],
});
});
it('leaves out a name that is no stored file', () => {
const project = projectWith([
{ id: 't', type: 'texture', name: 'Failed', props: {}, file: '', width: 0, height: 0, frames: [] },
{ id: 'f', type: 'font', name: 'Failed', props: {}, file: 'x.woff2' },
{ id: 's', type: 'sound', name: 'Failed', props: {}, file: '' },
]);
expect(projectFiles(project)).toEqual({ textures: [], fonts: [], cels: [], sounds: [] });
});
});