mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 04:34:30 +00:00
Toby r1 finding: three exports in src/geometry.js had no class-killer tests.
Named mutations that were undetected before this commit:
- camBaseDistU: Math.max(span,4)→span leaves camBaseDistU(0)=65 vs 77
- camLowFretPullbackU: drop Max(0,..) leaves (10)=-20 vs 0
- _makeGaussTex: default sigma=0 leaves centre-pixel alpha=0 vs 255
Each test is written to call without the value that makes the mutation
transparent (sigma passed without default; span=0 for the floor case;
fret=10 for the clamp case). Mutation verification run before commit:
all three mutations confirmed caught, all three pass on real code.
Suite (cmd): node --test tests/js/highway_3d*.test.js plugins/highway_3d/tests/*.test.js
Base (84d3376): 169/169
Tip: 172/172 (+3 discriminating tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uZ169yfoFYArXz962g7KW
118 lines
5.9 KiB
JavaScript
118 lines
5.9 KiB
JavaScript
// Class-killer for src/geometry.js — h3d-carve-1.
|
||
//
|
||
// Uses dynamic import() (not the vm source-scan pattern) so Node actually
|
||
// evaluates the ES module and its exports are the real runtime values.
|
||
// A refactor that renames geoFretX, changes the uniform/logarithmic
|
||
// decision, removes slideTrailEnd, or breaks computeBPM's BPM estimate
|
||
// would be caught here before any other test.
|
||
|
||
const { test } = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const path = require('node:path');
|
||
|
||
const GEOMETRY_JS = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'src', 'geometry.js');
|
||
|
||
test('geoFretX returns 0 for fret 0 in both modes', async () => {
|
||
const { geoFretX } = await import(GEOMETRY_JS);
|
||
assert.strictEqual(geoFretX(0, true), 0, 'uniform: fret 0 must be 0');
|
||
assert.strictEqual(geoFretX(0, false), 0, 'logarithmic: fret 0 must be 0');
|
||
});
|
||
|
||
test('geoFretX uniform spacing is linear — fret N is N × fret 1', async () => {
|
||
const { geoFretX } = await import(GEOMETRY_JS);
|
||
const step = geoFretX(1, true);
|
||
assert.ok(step > 0, 'uniform step must be positive');
|
||
assert.ok(Math.abs(geoFretX(5, true) - 5 * step) < 1e-9, 'fret 5 must be 5 × step');
|
||
assert.ok(Math.abs(geoFretX(12, true) - 12 * step) < 1e-9, 'fret 12 must be 12 × step');
|
||
});
|
||
|
||
test('geoFretX logarithmic spacing is non-linear — frets compress toward the bridge', async () => {
|
||
const { geoFretX } = await import(GEOMETRY_JS);
|
||
const d1 = geoFretX(1, false);
|
||
const d2 = geoFretX(2, false) - geoFretX(1, false);
|
||
const d3 = geoFretX(3, false) - geoFretX(2, false);
|
||
assert.ok(d1 > d2, 'fret 1 gap must be wider than fret 2 gap (compression toward bridge)');
|
||
assert.ok(d2 > d3, 'fret 2 gap must be wider than fret 3 gap');
|
||
});
|
||
|
||
test('geoFretX uniform and logarithmic agree at fret 24 (total board width)', async () => {
|
||
const { geoFretX } = await import(GEOMETRY_JS);
|
||
// By construction: _fretXUniStep = _fretXLog(24) / 24, so geoFretX(24, uniform)
|
||
// equals geoFretX(24, logarithmic). This is the board-width invariant.
|
||
const uniWidth = geoFretX(24, true);
|
||
const logWidth = geoFretX(24, false);
|
||
assert.ok(Math.abs(uniWidth - logWidth) < 1e-9, 'board width must be identical in both modes');
|
||
});
|
||
|
||
test('dZ converts positive dt to a negative Z delta', async () => {
|
||
const { dZ } = await import(GEOMETRY_JS);
|
||
assert.ok(dZ(1) < 0, 'positive time delta must produce negative Z (notes travel toward camera)');
|
||
assert.ok(dZ(0) === 0, 'zero dt must produce zero dZ');
|
||
assert.ok(Math.abs(dZ(2) / dZ(1) - 2) < 1e-9, 'dZ must be linear in dt');
|
||
});
|
||
|
||
test('slideTrailEnd returns null for notes with no slide fields', async () => {
|
||
const { slideTrailEnd } = await import(GEOMETRY_JS);
|
||
assert.strictEqual(slideTrailEnd({}), null);
|
||
assert.strictEqual(slideTrailEnd({ sl: -1 }), null, 'negative sl must be ignored');
|
||
});
|
||
|
||
test('slideTrailEnd prefers sl over slu and marks pitched/unpitched correctly', async () => {
|
||
const { slideTrailEnd } = await import(GEOMETRY_JS);
|
||
assert.deepStrictEqual(slideTrailEnd({ sl: 7 }), { endFret: 7, unpitched: false });
|
||
assert.deepStrictEqual(slideTrailEnd({ slu: 5 }), { endFret: 5, unpitched: true });
|
||
assert.deepStrictEqual(slideTrailEnd({ sl: 7, slu: 5 }), { endFret: 7, unpitched: false });
|
||
});
|
||
|
||
test('computeBPM returns 120 for degenerate inputs', async () => {
|
||
const { computeBPM } = await import(GEOMETRY_JS);
|
||
assert.strictEqual(computeBPM(null, 0), 120);
|
||
assert.strictEqual(computeBPM([], 0), 120);
|
||
assert.strictEqual(computeBPM([{ time: 0 }], 0), 120, 'single beat has no interval');
|
||
});
|
||
|
||
test('computeBPM estimates 120 BPM from evenly-spaced beats', async () => {
|
||
const { computeBPM } = await import(GEOMETRY_JS);
|
||
// 120 BPM = 0.5 s per beat
|
||
const beats = [0, 0.5, 1.0, 1.5, 2.0].map(time => ({ time }));
|
||
const bpm = computeBPM(beats, 1.0);
|
||
assert.ok(Math.abs(bpm - 120) < 0.01, `expected ~120 BPM, got ${bpm}`);
|
||
});
|
||
|
||
// Toby r1 findings: camBaseDistU, camLowFretPullbackU, _makeGaussTex had no
|
||
// class-killer tests. Each test below names the concrete mutation it catches.
|
||
|
||
test('camBaseDistU clamps span to minimum 4 — span=0 gives 77 not 65', async () => {
|
||
// Mutation: Math.max(span,4) → span
|
||
// camBaseDistU(0) mutant = 65+0*3 = 65 (wrong); original = 65+4*3 = 77
|
||
const { camBaseDistU } = await import(GEOMETRY_JS);
|
||
assert.strictEqual(camBaseDistU(0), 77, 'span=0: floor=4 so 65+4*3=77, not 65');
|
||
assert.strictEqual(camBaseDistU(10), 95, 'span=10: 65+10*3=95');
|
||
});
|
||
|
||
test('camLowFretPullbackU is clamped to zero — high fret gives 0 not negative', async () => {
|
||
// Mutation: drop Math.max(0,...) clamp
|
||
// camLowFretPullbackU(10) mutant = (5-10)*4 = -20 (wrong); original = 0
|
||
const { camLowFretPullbackU } = await import(GEOMETRY_JS);
|
||
assert.strictEqual(camLowFretPullbackU(0), 20, 'fret 0: (5-0)*4=20');
|
||
assert.strictEqual(camLowFretPullbackU(5), 0, 'fret 5: (5-5)*4=0');
|
||
assert.strictEqual(camLowFretPullbackU(10), 0, 'fret 10: clamped to 0, not -20');
|
||
});
|
||
|
||
test('_makeGaussTex peak alpha is 255 at the centre pixel', async () => {
|
||
// Mutation: default sigma changed to 0 → (u-0.5)/0 = NaN chain → all Uint8Array writes
|
||
// become 0 (TypedArray coerces NaN to 0). Test calls without explicit sigma so the
|
||
// default is exercised directly — changing the default is what is being guarded.
|
||
// Use odd width=3: i=1 gives u=0.5 exactly (d=(u-0.5)/sigma=0, peak=1, alpha=255).
|
||
const { _makeGaussTex } = await import(GEOMETRY_JS);
|
||
let capturedData;
|
||
const ThreeStub = {
|
||
DataTexture: class { constructor(d) { capturedData = d; } },
|
||
RGBAFormat: 1,
|
||
LinearFilter: 2,
|
||
};
|
||
_makeGaussTex(ThreeStub, 3); // no sigma arg — exercises the default (0.28)
|
||
// Pixel i=1: RGBA layout [4,5,6,7]; alpha is at index 7
|
||
assert.strictEqual(capturedData[7], 255, 'centre pixel (i=1 of w=3) alpha must be 255 at default sigma');
|
||
});
|