1import { describe, expect, it } from 'vitest';
2import { decodeRecord, digest, encodeRecord, splitResult } from '../src/sim/cache';
3import type { SimResult } from '../src/sim/types';
5describe('result cache encoding', () => {
6 it('round-trips a result, optional arrays included', () => {
7 const r: SimResult = {
8 audio: new Float32Array([0.5, -1, 3]),
9 fs: 48000,
10 frames: new Float32Array(7).map((_, i) => i * 0.1),
11 frameTimes: new Float64Array([0, 0.5]),
12 energy: new Float64Array([1e-3, 2e-3]),
13 balance: new Float64Array([0, 1e-12]),
14 dt: 1e-6,
15 stepsPerSample: 21,
16 omegaMax: 12345.6,
17 wallSeconds: 3.2,
18 maxStretch: 0.01,
19 contactForce: new Float32Array([0, 4, 2]),
20 backend: 'CPU',
21 };
22 const bytes = encodeRecord(r);
23 expect(bytes.length % 8).toBe(0);
24 const back = decodeRecord<SimResult>(bytes.buffer);
25 expect(back).toEqual(r);
26 expect(back.frameTimes).toBeInstanceOf(Float64Array);
27 expect(back.malletPos).toBeUndefined();
29 // stored as a light entry and a frames entry
30 const { light, frames } = splitResult(r);
31 expect(light).not.toHaveProperty('frames');
32 const l = decodeRecord<Omit<SimResult, 'frames'>>(encodeRecord(light).buffer);
33 const f = decodeRecord<{ frames: Float32Array }>(encodeRecord({ frames }).buffer).frames;
34 expect({ ...l, frames: f }).toEqual(r);
35 });
37 it('hashes inputs canonically: key order does not matter, values and array types do', async () => {
38 const a = await digest({ x: 1, arr: new Float64Array([1, 2]), y: undefined });
39 expect(await digest({ arr: new Float64Array([1, 2]), x: 1 })).toBe(a);
40 expect(await digest({ x: 1, arr: new Float64Array([1, 2.0000001]) })).not.toBe(a);
41 expect(await digest({ x: 1, arr: new Float32Array([1, 2]) })).not.toBe(a);
42 expect(await digest({ x: 2, arr: new Float64Array([1, 2]) })).not.toBe(a);
43 });
44});