// The RAW develop hands its band plane to Skia as half (src/engine/halfFloat.ts) // because the GPU backend quantises an F32 image to the 1/255 grid — which is // exactly the grid the shadows live under. This checks the conversion itself: // the named encodings, and that no plane value in the sensor's range moves more // than the 16-bit plane step it came from. // // node scripts/half-check.mjs import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import ts from 'typescript'; const src = readFileSync(new URL('../src/engine/halfFloat.ts', import.meta.url), 'utf8'); const js = ts.transpileModule(src, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }, }).outputText; const { f32ToF16 } = await import(`data:text/javascript,${encodeURIComponent(js)}`); function encode(...values) { const src32 = Float32Array.from(values); const out = new Uint16Array(values.length); f32ToF16(src32, out, values.length); return out; } // Independent decoder, from the IEEE 754 binary16 layout. function decode(h) { const sign = h & 0x8000 ? -1 : 1; const exp = (h >> 10) & 0x1f; const man = h & 0x3ff; if (exp === 0x1f) return sign * Infinity; if (exp === 0) return sign * man * 2 ** -24; return sign * (1 + man / 1024) * 2 ** (exp - 15); } const named = [ [0, 0x0000], [1, 0x3c00], [0.5, 0x3800], [2, 0x4000], [-1, 0xbc00], [2 ** -24, 0x0001], [1e-9, 0x0000], [70000, 0x7c00], [65504, 0x7bff], ]; for (const [v, bits] of named) { assert.equal(encode(v)[0], bits, `${v} -> expected ${bits.toString(16)}`); } // Every value the plane can carry — a 14-bit sensor from its black floor to its // white level — has to come back within half precision's 2^-11 relative error, // and the shadows within a far finer absolute one, since that is where the // develop's black level lives. 1e-3 relative is a tenth of an output level at // white; the absolute bound covers the values whose relative error says nothing. let worstRel = 0, worstAbs = 0; for (let n = 0; n <= 16312; n++) { const v = n / 65535; const back = decode(encode(v)[0]); worstAbs = Math.max(worstAbs, Math.abs(back - v)); if (v > 0) worstRel = Math.max(worstRel, Math.abs(back - v) / v); } assert.ok(worstRel < 1e-3, `worst relative error ${worstRel}`); assert.ok(worstAbs < 1e-4, `worst absolute error ${worstAbs} at raw ${Math.round(worstAbs * 65535)}`); console.log(`plane 0..16312: worst ${worstRel.toExponential(2)} relative, ${worstAbs.toExponential(2)} absolute`); console.log('half-check ok');