import assert from 'node:assert/strict'; import fs from 'node:fs'; import vm from 'node:vm'; const source = fs.readFileSync(new URL('../www/views/phase_wheel.js', import.meta.url), 'utf8'); const functionSource = source.match(/export function summarizeWeightedPhase[\s\S]*?\n\}/)?.[0]; assert.ok(functionSource, 'weighted phase summary must remain testable'); const context = vm.createContext({ Number, Math }); vm.runInContext(functionSource.replace('export function', 'function'), context); const dominant = context.summarizeWeightedPhase( new Float32Array([0, Math.PI / 2]), new Float32Array([100, 1]), 2, ); assert.ok(dominant, 'valid weighted phases must produce a summary'); assert.ok(Math.abs(dominant.angle) < 0.02, 'a quiet phase component must not pull a dominant component toward an unweighted mean'); const wrapped = context.summarizeWeightedPhase( new Float32Array([179 * Math.PI / 180, -179 * Math.PI / 180]), new Float32Array([1, 1]), 2, ); assert.ok(Math.abs(Math.abs(wrapped.angle) - Math.PI) < 0.02, 'circular averaging must preserve the wrap around at 180 degrees'); assert.ok(wrapped.coherence > 0.99, 'nearly aligned phase observations must report high coherence'); const opposed = context.summarizeWeightedPhase( new Float32Array([0, Math.PI]), new Float32Array([1, 1]), 2, ); assert.ok(opposed.coherence < 1e-6, 'opposing phase observations must report that their mean direction is ambiguous'); assert.equal(context.summarizeWeightedPhase( new Float32Array([0]), new Float32Array([0]), 1, ), null, 'samples without common L/R energy must not invent a phase angle'); console.log('phase wheel regression tests passed');