import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; const shader = (await readFile(new URL('../src/shaders/mosaic.frag', import.meta.url), 'utf8')).replace(/\r\n/g, '\n'); const baseline = (await readFile(new URL('../docs/qa-surface-reflection/baseline-mosaic.frag', import.meta.url), 'utf8')).replace(/\r\n/g, '\n'); function statement(source, type, name) { const match = source.match(new RegExp(`\\b${type} ${name}=([^;]+);`)); assert.ok(match, `complete ${type} ${name} statement exists`); return match[1]; } function effectiveRegionAtlasSource(source) { let active = true; const stack = []; const effective = []; for (const line of source.split('\n')) { const directive = line.trim(); if (directive === '#ifdef REGION_ATLAS') { stack.push({ parentActive: active, ifdef: true }); continue; } if (directive === '#ifndef REGION_ATLAS') { stack.push({ parentActive: active, ifdef: false }); active = false; continue; } if (directive === '#else') { const frame = stack.at(-1); assert.ok(frame, 'REGION_ATLAS preprocessor else has a matching branch'); active = frame.parentActive && !frame.ifdef; continue; } if (directive === '#endif') { const frame = stack.pop(); assert.ok(frame, 'REGION_ATLAS preprocessor endif has a matching branch'); active = frame.parentActive; continue; } if (active) effective.push(line); } assert.equal(stack.length, 0, 'REGION_ATLAS preprocessor branches are balanced'); return effective.join('\n'); } function finalOutputBranches(source) { const reflectionStart = source.indexOf('#ifndef REGION_ATLAS\n color.a=1.;'); const outputEnd = source.indexOf('\n#endif\n}', reflectionStart); assert.ok(reflectionStart >= 0 && outputEnd > reflectionStart, 'surface reflections must be in the final non-atlas display pass'); const output = source.slice(reflectionStart, outputEnd); const atlasMarker = '\n#else\n'; const atlasIndex = output.indexOf(atlasMarker); assert.ok(atlasIndex >= 0, 'the final output must expose an effective atlas branch'); return { reflectionStart, output, nonAtlas: output.slice(0, atlasIndex), atlas: output.slice(atlasIndex + atlasMarker.length), }; } function finalWeightExpression(nonAtlas) { const match = nonAtlas.match(/color\.rgb=mix\(color\.rgb,vec3\(1\.\),([^;]+)\);/); assert.ok(match, 'final surface composition must mix the color with white'); return match[1]; } function expressions(source) { const { reflectionStart, output, nonAtlas, atlas } = finalOutputBranches(source); // Atlas now shares the depth-aware sampling correction. It must still contain // no surface reflection, rather than remain byte-identical to the old optics. assert.doesNotMatch(effectiveRegionAtlasSource(source), /\breflect\(|color\.rgb=mix\(color\.rgb,vec3\(1\.\)/, 'complete effective REGION_ATLAS source must exclude surface reflection'); const finalWeight = finalWeightExpression(nonAtlas); assert.equal(finalWeight, 'specGloss+broadReflection', 'final composition must use the complete existing and broad amplitudes'); assert.equal(statement(source, 'float', 'surfaceReflectionWeight'), 'mix(.35,1.,edgeWeight)', 'surface reflection must use a separate bounded edge weight'); assert.equal(statement(source, 'float', 'glossStrength'), 'clamp(uGlossStrength,0.,1.)', 'both surface reflection terms must share the actual clamped gloss gate'); assert.equal(statement(source, 'float', 'specGloss'), 'spec*glossStrength*.12*surfaceReflectionWeight*clamp(uStrength,0.,1.)', 'the existing rectangular reflection must use its complete amplitude expression'); assert.equal(statement(source, 'vec2', 'broadWindowMask'), '1.-smoothstep(vec2(.15),vec2(1.75),windowCoord)', 'the broad reflection must use its complete soft spatial mask'); assert.equal(statement(source, 'float', 'broadFacing'), 'pow(max(dot(nCurved,v),0.),2.)', 'the broad reflection factor must use the curved normal and view direction'); assert.equal(statement(source, 'float', 'broadReflection'), 'broadWindowMask.x*broadWindowMask.y*broadFacing*glossStrength*.035*surfaceReflectionWeight*clamp(uStrength,0.,1.)', 'the broad reflection must use its complete spatial/view-dependent amplitude expression'); for (const name of ['edgeWeight', 'strength', 'sceneHit']) { assert.equal(statement(source, name === 'sceneHit' ? 'vec3' : 'float', name), statement(baseline, name === 'sceneHit' ? 'vec3' : 'float', name), `${name} refraction expression remains unchanged from the preserved baseline`); } assert.ok(!nonAtlas.slice('#ifndef REGION_ATLAS\n'.length).includes('REGION_ATLAS'), 'reflection composition must stay outside the atlas branch'); assert.match(atlas, /color\.a=atlasEvidence;\s+gl_FragColor=color;/, 'the effective atlas branch must preserve atlas evidence output'); assert.doesNotMatch(atlas, /specGloss|broadReflection|surfaceReflectionWeight|color\.rgb=mix/, 'the effective atlas branch must not receive final surface reflection'); const simplification = source.indexOf('color.rgb=mix(color.rgb,candidateColor,'); assert.ok(simplification >= 0 && simplification < reflectionStart, 'color-plane simplification must precede final reflection composition'); const finalMix = source.indexOf('color.rgb=mix', reflectionStart); const colorSpace = source.indexOf('#include ', finalMix); assert.ok(finalMix >= 0 && colorSpace > finalMix, 'reflection composition must happen before color-space conversion'); return { surfaceWeight: statement(source, 'float', 'surfaceReflectionWeight'), gloss: statement(source, 'float', 'glossStrength'), spec: statement(source, 'float', 'specGloss'), broadMask: statement(source, 'vec2', 'broadWindowMask'), broadFacing: statement(source, 'float', 'broadFacing'), broad: statement(source, 'float', 'broadReflection'), finalWeight, output, }; } function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function mix(left, right, factor) { return left * (1 - factor) + right * factor; } function evaluate(expression, variables) { const names = Object.keys(variables); return Function(...names, 'clamp', 'max', 'pow', 'mix', `return ${expression};`)( ...names.map(name => variables[name]), clamp, Math.max, Math.pow, mix); } function evaluateOutput(source, sample) { const expr = expressions(source); const surfaceReflectionWeight = evaluate(expr.surfaceWeight, { edgeWeight: sample.edgeWeight }); const glossStrength = evaluate(expr.gloss, { uGlossStrength: sample.uGlossStrength }); const variables = { ...sample, surfaceReflectionWeight, glossStrength, }; const specGloss = evaluate(expr.spec, variables); const broadReflection = evaluate(expr.broad, variables); const weight = evaluate(expr.finalWeight, { ...variables, specGloss, broadReflection }); const output = sample.baseColor * (1 - weight) + weight; return { specGloss, broadReflection, weight, output }; } function assertBehavior(source) { const center = { spec: .82, broadWindowMask: { x: .95, y: .88 }, broadFacing: .92, edgeWeight: .9, uGlossStrength: .3, uStrength: 1, baseColor: .22, }; const viewTilt = { ...center, broadFacing: .25 }; const spatialEdge = { ...center, spec: .18, broadWindowMask: { x: .40, y: .55 }, edgeWeight: .1, baseColor: .31, }; const glossZero = evaluateOutput(source, { ...center, uGlossStrength: 0 }); const centerOutput = evaluateOutput(source, center); const viewTiltOutput = evaluateOutput(source, viewTilt); const spatialEdgeOutput = evaluateOutput(source, spatialEdge); assert.equal(glossZero.specGloss, 0, 'gloss 0 removes the existing reflection amplitude'); assert.equal(glossZero.broadReflection, 0, 'gloss 0 removes the broad reflection amplitude'); assert.equal(glossZero.weight, 0, 'gloss 0 removes the actual final reflection mix weight'); assert.equal(glossZero.output, center.baseColor, 'gloss 0 preserves the pre-reflection color'); assert.ok(centerOutput.specGloss > 0 && centerOutput.broadReflection > 0, 'representative positive-gloss input produces both actual reflection amplitudes'); assert.notEqual(centerOutput.broadReflection, viewTiltOutput.broadReflection, 'the broad amplitude varies with representative view/normal input'); assert.notEqual(centerOutput.broadReflection, spatialEdgeOutput.broadReflection, 'the broad amplitude varies with representative spatial input'); assert.notEqual(centerOutput.output, viewTiltOutput.output, 'the actual final output varies with representative view input'); assert.notEqual(centerOutput.output, spatialEdgeOutput.output, 'the actual final output varies with representative spatial input'); } expressions(shader); assertBehavior(shader); const constantOutputMutation = shader.replace( 'color.rgb=mix(color.rgb,vec3(1.),specGloss+broadReflection);', 'color.rgb=mix(color.rgb,vec3(1.),.5);'); assert.notEqual(constantOutputMutation, shader, 'constant-output mutation must change the shader'); assert.throws(() => { expressions(constantOutputMutation); assertBehavior(constantOutputMutation); }, /complete existing and broad amplitudes|actual final output varies/, 'constant 50% white output mutation must fail the covering assertions'); const atlasReflectionMutation = shader.replace( ' vec3 atlasCenterColor=sampleScene(finalSceneUv).rgb;', ' vec3 atlasCenterColor=sampleScene(finalSceneUv).rgb; float atlasGloss=clamp(uGlossStrength,0.,1.)*.5; color.rgb=mix(color.rgb,vec3(1.),atlasGloss);'); assert.notEqual(atlasReflectionMutation, shader, 'atlas-reflection mutation must change the shader'); assert.throws(() => expressions(atlasReflectionMutation), /complete effective REGION_ATLAS source/, 'an earlier REGION_ATLAS reflection mutation must fail the covering assertions'); console.log('surface reflection source/math regression passed; constant-output and atlas-reflection mutations rejected');