← Natura·Docs/Modules/Source

laws/interference-colour-test.js

60 lines · in natura-src-1.0.0.zip as natura/laws/interference-colour-test.js · a test

// interference-colour-test.js -- prove the founding LAW against the known Newton / Michel-Levy colour series.
// The law is verified by RUNNING it and asserting on the NUMBERS (the framework's test discipline), never by
// reasoning about what the colour "should" be.
const L = require('./interference-colour.js');

let pass = 0, fail = 0;
const ok = (c, m) => { if (c) pass++; else { fail++; console.log('  FAIL: ' + m); } };
const maxc = (c) => Math.max(c[0], c[1], c[2]);
const spread = (c) => maxc(c) - Math.min(c[0], c[1], c[2]);

// --- the colour pipeline is sane: a FLAT reflectance spectrum is neutral WHITE ---
const white = L.spectrumColour(() => 1);
ok(white[0] > 235 && white[1] > 235 && white[2] > 235, 'flat spectrum -> bright (' + white + ')');
ok(spread(white) < 12, 'flat spectrum -> neutral, tiny channel spread (' + spread(white) + ')');

// --- a monochromatic red spectrum reads RED; a green one reads GREEN (CMF wired correctly) ---
const red = L.spectrumColour((l) => Math.exp(-Math.pow((l - 620) / 12, 2)));
ok(red[0] > red[1] && red[0] > red[2], 'peak-620 spectrum -> red channel highest (' + red + ')');
const grn = L.spectrumColour((l) => Math.exp(-Math.pow((l - 540) / 12, 2)));
ok(grn[1] > grn[0] && grn[1] > grn[2], 'peak-540 spectrum -> green channel highest (' + grn + ')');

// --- THIN FILM: the black film (d=0) is BLACK; the reflected series then brightens and turns chromatic ---
const d0 = L.thinFilm({ d: 0 });
ok(maxc(d0) < 8, 'd=0 -> black film (' + d0 + ')');
ok(d0.opd === 0, 'd=0 -> OPD 0');
const thin = L.thinFilm({ d: 90, n: 1.33 });     // OPD ~239nm: still near-neutral grey/white, low chroma
const firstOrder = L.thinFilm({ d: 210, n: 1.33 }); // OPD ~559nm: deep in the CHROMATIC first order
ok(maxc(thin) > maxc(d0), 'thicker than 0 is brighter than black');
ok(spread(firstOrder) > 45, 'first-order thickness is strongly CHROMATIC (spread ' + spread(firstOrder) + ')');
ok(spread(firstOrder) > spread(thin), 'first order is more saturated than the near-black thin film');

// OPD increases monotonically with thickness (the geometry) ...
ok(L.thinFilm({ d: 300 }).opd > L.thinFilm({ d: 150 }).opd, 'OPD grows with thickness');
// ... and tilting the film off-normal SHORTENS the OPD (cos theta_t < 1) -> the colour shifts (why a bubble
// changes hue as it curves away from you).
ok(L.thinFilm({ d: 300, angleDeg: 60 }).opd < L.thinFilm({ d: 300, angleDeg: 0 }).opd, 'off-normal shortens the OPD');

// --- BIREFRINGENCE: the Michel-Levy "sensitive tint" at gamma ~= 551nm is MAGENTA (green extinguished) ---
const tint = L.birefringence({ retardation: 551 });
ok(tint[1] < tint[0] && tint[1] < tint[2], 'gamma=551 first-order tint -> green is the LOWEST channel (magenta) (' + tint + ')');
ok(tint.retardation === 551, 'retardation echoed');
const g0 = L.birefringence({ retardation: 0 });
ok(maxc(g0) < 8, 'gamma=0 -> extinction (black) between crossed polars (' + g0 + ')');
// retardation via birefringence * thickness resolves the same way
const viaProduct = L.birefringence({ birefringence: 0.009, thickness: 61222 }); // ~551nm
ok(Math.abs(viaProduct.retardation - 551) < 1, 'gamma = birefringence*thickness (' + viaProduct.retardation + ')');

// --- THE UNIFICATION: the two manifestations are ONE law -- same OPD, same colour ---
const filmC = L.thinFilm({ d: 550 / (2 * 1.5), n: 1.5 });   // choose d so OPD = 550 exactly (cos0=1)
const opdEq = L.colourFromOPD(filmC.opd);
const birefEq = L.birefringence({ retardation: filmC.opd });
ok(opdEq[0] === birefEq[0] && opdEq[1] === birefEq[1] && opdEq[2] === birefEq[2],
   'thin-film OPD and birefringence retardation at the SAME OPD give the SAME colour (one law)');

// --- determinism: same input, same output, twice ---
const a = L.thinFilm({ d: 333, n: 1.33 }), b = L.thinFilm({ d: 333, n: 1.33 });
ok(a[0] === b[0] && a[1] === b[1] && a[2] === b[2], 'deterministic');

console.log('\ninterference-colour: ' + pass + ' passed, ' + fail + ' failed');
if (fail) process.exit(1);