// noise.test.ts — value tests for the noise namespace (solo round 20260716-212942) // // Regression guard for the 1D Perlin noise function used by camera shake and procedural // effects. These tests pin: // 1. output in [-1, 1], // 2. perlin1d is deterministic (same input → same output), // 3. perlin1d is smooth (nearby inputs → nearby outputs, the defining noise property), // 4. offset channels like x, x+100, x+200 are independent (different values), // 5. integer lattice points return 0 (the gradient dot at distance 0). import { describe, expect, it } from 'vitest'; import { noise } from '../index'; const { perlin1d } = noise; describe('noise.perlin1d', () => { it('returns values in [-1, 1]', () => { for (let x = 0; x < 500; x += 0.137) { const v = perlin1d(x); expect(v).toBeGreaterThanOrEqual(-1); expect(v).toBeLessThanOrEqual(1); } }); it('is deterministic', () => { expect(perlin1d(3.14)).toBe(perlin1d(3.14)); expect(perlin1d(42)).toBe(perlin1d(42)); expect(perlin1d(0)).toBe(perlin1d(0)); }); it('is smooth: nearby inputs yield nearby outputs', () => { for (let x = 1; x < 100; x += 0.7) { const a = perlin1d(x); const b = perlin1d(x + 0.001); // The fade curve is smooth, so a tiny input change should produce a tiny output change. expect(Math.abs(a - b)).toBeLessThan(0.05); } }); it('offset channels are independent (x, x+100, x+200)', () => { // Use a non-integer input so the channels aren't all zero. const a = perlin1d(50.5); const b = perlin1d(150.5); // 50.5 + 100 const c = perlin1d(250.5); // 50.5 + 200 // They should not all be the same — the offset separates the channels. const allSame = a === b && b === c; expect(allSame).toBe(false); }); it('integer lattice points return 0 (gradient dot at distance 0)', () => { for (let x = 0; x < 256; x++) { expect(perlin1d(x)).toBeCloseTo(0, 10); } }); it('integer lattice points return 0 at negative inputs too', () => { expect(perlin1d(-1)).toBeCloseTo(0, 10); expect(perlin1d(-42)).toBeCloseTo(0, 10); expect(perlin1d(-256)).toBeCloseTo(0, 10); }); it('symmetry: the same fractional offset from any integer gives the same absolute value', () => { // perlin1d works on the fractional part relative to the floor — the integer part // determines which hash bucket, so outputs differ between different integer segments. // But the interpolation *pattern* is consistent: at the same fractional offset t, the // output is somewhere between two lattice values that cross the segment. // We just verify it's not NaN or out-of-range. for (let x = -10; x < 10; x += 0.13) { const v = perlin1d(x); expect(Number.isNaN(v)).toBe(false); expect(v).toBeGreaterThanOrEqual(-1); expect(v).toBeLessThanOrEqual(1); } }); });