← Natura·Docs/Modules/Source

caustics-test.js

67 lines · in natura-src-1.0.0.zip as natura/caustics-test.js · a test

// caustics-test.js -- assertions for OPTICS / CAUSTICS (light refracted through a surface onto a floor).
// Run: node caustics-test.js
const N = require('./natura.js');
const { makeCaustics } = require('./caustics.js');
const { World, UNITS } = N;
const DAY = UNITS.DAY;
let pass = 0, fail = 0;
const ok = (c, m) => { if (c) pass++; else { fail++; console.log('  FAIL: ' + m); } };
const GRID = 64;
const build = (cfg) => World({ seed: 8, systems: [makeCaustics(Object.assign({ N: GRID }, cfg))] });

console.log('caustics assertions:');

// 1. DETERMINISM
{
  const a = build({}), b = build({}); a.step(DAY); b.step(DAY);
  const fa = a.fields().caustics.caustic, fb = b.fields().caustics.caustic; let same = true;
  for (let k = 0; k < GRID * GRID; k++) if (fa[k] !== fb[k]) { same = false; break; }
  ok(same, 'same config -> identical caustic (reproducible)');
}

// 2. LIGHT IS CONSERVED -- every ray lands; total floor brightness == the number of rays, for any surface
{
  const w = build({ amp: 1.2 }); w.step(DAY); const f = w.fields().caustics;
  ok(Math.abs(f.totalLight - GRID * GRID) < 1, 'light is CONSERVED: total floor brightness (' + f.totalLight + ') == rays cast (' + GRID * GRID + ')');
}

// 3. A FLAT SURFACE -> EVEN FLOOR (no caustics: peak ~ mean)
{
  const w = build({ surface: () => 0.0 }); w.step(DAY); const f = w.fields().caustics;
  ok(f.peakToMean < 1.5, 'a flat surface lights the floor EVENLY (peak/mean ' + f.peakToMean + ')');
}

// 4. THE EMERGENCE -- a WAVY surface GATHERS the light into bright fold-lines (caustics)
{
  const w = build({ amp: 0.9 }); w.step(DAY); const f = w.fields().caustics;
  ok(f.peakToMean > 4, 'a wavy surface REFRACTS the light into bright caustic fold-lines (peak/mean ' + f.peakToMean + 'x)');
}

// 5. STRONGER WAVES -> SHARPER CAUSTICS (more surface tilt gathers the light harder)
{
  const calm = build({ amp: 0.3 }); calm.step(DAY);
  const rough = build({ amp: 1.1 }); rough.step(DAY);
  ok(rough.fields().caustics.peakToMean > calm.fields().caustics.peakToMean,
    'stronger waves gather the light harder (calm ' + calm.fields().caustics.peakToMean + ' -> rough ' + rough.fields().caustics.peakToMean + ')');
}

// 6. IT SHIFTS -- the caustic net MOVES as the surface waves travel (not a frozen pattern)
{
  const w = build({ amp: 0.9 }); w.step(DAY); const a = Float32Array.from(w.fields().caustics.caustic);
  for (let i = 0; i < 8; i++) w.step(DAY); const b = w.fields().caustics.caustic;
  let diff = 0; for (let k = 0; k < GRID * GRID; k++) diff += Math.abs(a[k] - b[k]);
  ok(diff > GRID, 'the caustic net SHIFTS as the waves travel (it is not frozen)');
}

// 7. CROSS-SYSTEM COUPLING -- caustics driven by another system's live water SURFACE field (ocean/hydro/watershed)
{
  const wavy = new Float32Array(GRID * GRID);
  for (let j = 0; j < GRID; j++) for (let i = 0; i < GRID; i++) wavy[j * GRID + i] = 1.3 * (Math.sin(0.55 * i) + Math.cos(0.48 * j) + 0.7 * Math.sin(0.33 * (i + j)));
  const ocean = { name: 'ocean', domain: 'physical', scale: 'landscape', params: {}, init() { return {}; }, step(s) { return s; }, fields() { return { N: GRID, surface: wavy }; } };
  const w = World({ seed: 8, systems: [ocean, makeCaustics({ N: GRID, surfaceFrom: 'ocean', surfaceKey: 'surface', amp: 1 })] });
  w.step(DAY); const f = w.fields().caustics;
  ok(f.peakToMean > 4, 'caustics REFRACTS a coupled surface field (from another system) into a real caustic net (peak/mean ' + f.peakToMean + 'x)');
}

console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);