Decides whether two renderers give the same picture, and whether a test scene returns the expected brightness, by comparing block means against the pictures' own noise.
How the precision is kept
Means and variances are accumulated in Float64Array from the pictures' samples. The pictures themselves are 32-bit floats and are Monte Carlo estimates, so the comparison is a statistical bound, not an equality: a block fails when its means differ by more than 4.5 standard errors.
Calculations under this policy
5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
The mean picture and the variance of that mean from two or more independent batch pictures.
mean = Σ b ÷ k; var(mean) = Σ (b − mean)² ÷ (k − 1) ÷ k
Each batch value is divided by k and added in 64-bit; the variance is computed from the deviations from that mean, which avoids subtracting two large sums.
compute/parity.js
lines 24 to 32
BimCraft 0.8.1
export function fromBatches(batches, w, h) {
const k = batches.length, n = w * h * 3;
if (k < 2) throw new Error('parity: at least two batches');
const mean = new Float64Array(n), varMean = new Float64Array(n);
for (const b of batches) { if (b.length < n) throw new Error('parity: a batch is smaller than w·h·3'); for (let i = 0; i < n; i++) mean[i] += b[i] / k; }
for (const b of batches) for (let i = 0; i < n; i++) { const d = b[i] - mean[i]; varMean[i] += (d * d) / (k - 1); }
for (let i = 0; i < n; i++) varMean[i] /= k;
return { w, h, mean, varMean };
}
Mean and variance from sums
64-bit
The mean and the variance of the mean from per-pixel sums of the samples and of their squares.
m = Σx ÷ n; s² = max(0, (Σx² − n m²) ÷ (n − 1)); var(mean) = s² ÷ n
Σx² − n m² subtracts two nearly equal numbers when the spread is small against the mean, so the variance loses digits there; a negative result from that rounding is set to 0.
compute/parity.js
lines 34 to 44
BimCraft 0.8.1
export function fromSums(sum, sumSq, nSamples, w, h) {
const n = w * h * 3;
if (!(nSamples >= 2)) throw new Error('parity: at least two samples');
const mean = new Float64Array(n), varMean = new Float64Array(n);
for (let i = 0; i < n; i++) {
const m = sum[i] / nSamples;
const v = Math.max(0, (sumSq[i] - nSamples * m * m) / (nSamples - 1));
mean[i] = m; varMean[i] = v / nSamples;
}
return { w, h, mean, varMean };
}
Block means
64-bit
The mean of each block of pixels and the variance of that mean.
block mean = Σ means ÷ count; block variance = Σ variances ÷ count²
Sums over the block in 64-bit, divided once. Blocks at the right and bottom edges hold fewer pixels and use their own count.
compute/parity.js
lines 46 to 61
BimCraft 0.8.1
function blocksOf(st, block) {
const { w, h, mean, varMean } = st;
const bw = Math.ceil(w / block), bh = Math.ceil(h / block);
const bm = new Float64Array(bw * bh * 3), bv = new Float64Array(bw * bh * 3);
for (let by = 0; by < bh; by++) for (let bx = 0; bx < bw; bx++) {
let cnt = 0;
const o = (by * bw + bx) * 3;
for (let y = by * block; y < Math.min(h, (by + 1) * block); y++) for (let x = bx * block; x < Math.min(w, (bx + 1) * block); x++) {
const p = (y * w + x) * 3;
for (let c = 0; c < 3; c++) { bm[o + c] += mean[p + c]; bv[o + c] += varMean[p + c]; }
cnt++;
}
for (let c = 0; c < 3; c++) { bm[o + c] /= cnt; bv[o + c] /= cnt * cnt; }
}
return { bw, bh, bm, bv };
}
Two pictures compared
64-bit
Whether a test picture agrees with a reference picture.
z = |μ_t − μ_r| ÷ √(σ²_r + σ²_t + (10⁻⁴ max(1, |μ_r|))²); ok if failed blocks ≤ 1 % and the whole-image z ≤ 4.5
The floor term keeps the denominator above zero when both variances are zero. The whole-image means are sums of every pixel's mean divided by the pixel count, added in order.
compute/parity.js
lines 74 to 95
BimCraft 0.8.1
export function compareImages(ref, test, { block = 8, k = 4.5, maxFail = 0.01, floor = 1e-4 } = {}) {
if (ref.w !== test.w || ref.h !== test.h) throw new Error('parity: pictures of different sizes');
const A = blocksOf(ref, block), B = blocksOf(test, block);
let failed = 0;
const worst = { z: 0, bx: -1, by: -1, channel: -1 };
const blocks = A.bw * A.bh;
for (let i = 0; i < blocks; i++) {
let bad = false;
for (let c = 0; c < 3; c++) {
const mr = A.bm[i * 3 + c], mt = B.bm[i * 3 + c];
const e = floor * Math.max(1, Math.abs(mr));
const z = Math.abs(mt - mr) / Math.sqrt(A.bv[i * 3 + c] + B.bv[i * 3 + c] + e * e);
if (z > worst.z) { worst.z = z; worst.bx = i % A.bw; worst.by = Math.floor(i / A.bw); worst.channel = c; }
if (z > k) bad = true;
}
if (bad) failed++;
}
const mr = meanOf(ref), mt = meanOf(test), vr = varOfMean(ref), vt = varOfMean(test);
let iz = 0;
for (let c = 0; c < 3; c++) { const e = floor * Math.max(1, Math.abs(mr[c])); iz = Math.max(iz, Math.abs(mt[c] - mr[c]) / Math.sqrt(vr[c] + vt[c] + e * e)); }
return { ok: failed <= Math.floor(maxFail * blocks) && iz <= k, blocks, failed, worst, image: { ref: mr, test: mt, z: iz } };
}
Furnace test
64-bit
Whether every block of a test picture has the expected mean within its noise.
z = |block mean − expected| ÷ √(block variance + (10⁻⁴ max(1, |expected|))²); ok if no block has z > 4.5
The same z as the comparison, against a fixed expected value. A block counts once however many of its three channels exceed the limit.
compute/parity.js
lines 97 to 108
BimCraft 0.8.1
export function furnace(st, expected = 1, { block = 8, k = 4.5, floor = 1e-4 } = {}) {
const B = blocksOf(st, block);
let failed = 0, worstZ = 0;
const blocks = B.bw * B.bh;
for (let i = 0; i < blocks; i++) for (let c = 0; c < 3; c++) {
const e = floor * Math.max(1, Math.abs(expected));
const z = Math.abs(B.bm[i * 3 + c] - expected) / Math.sqrt(B.bv[i * 3 + c] + e * e);
if (z > worstZ) worstZ = z;
if (z > k) { failed++; break; }
}
return { ok: failed === 0, mean: meanOf(st), worstZ, failed, blocks };
}
In detail
Method
Per pixel and channel: mean and variance of the mean from batches or from Σx and Σx²; block means over 8 × 8 pixels; z = |μ_t − μ_r| ÷ √(σ²_r + σ²_t + floor²).
Number types
Float64Array for means and variances; the input pictures are 32-bit.
Tolerances
z limit 4.5; at most 1 % of blocks may exceed it; a floor of 1e-4 × max(1, |mean|) is added to the standard error.
Limits
The result is a statistical judgment at the given number of samples. Pixels within a block are treated as independent.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.