← back to Quadrille Showroom
scripts/verify-scene-render.mjs
61 lines
// verify-scene-render.mjs — WebGL SCENE-CORRECTNESS assertion. The /3x six-way suite only
// proves the canvas drew *something* (a ~24KB screenshot); this proves the Three.js scene
// actually contains the wallcovering geometry: board meshes in the arc, real draw calls, the
// mirrored centre book, and a live (non-stalled) frame loop. Exit 1 on failure.
// FPS is REPORTED (headless GPU ≠ real hardware); only a stalled loop (<12fps) hard-fails.
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const CANDIDATES = ['playwright',
'/Users/macstudio3/.claude/skills/hero-readability-auditor/node_modules/playwright/index.js',
'/Users/macstudio3/.claude/skills/app-demo-video/node_modules/playwright/index.js'];
let chromium=null; for (const c of CANDIDATES){ try{({chromium}=require(c)); if(chromium)break;}catch{} }
if(!chromium){ console.error('playwright not found'); process.exit(2); }
const URL = process.env.URL || 'http://127.0.0.1:7690';
const b = await chromium.launch({ channel:'chrome' });
const ctx = await b.newContext({ httpCredentials:{ username:'admin', password:'DWSecure2024!' } });
const p = await ctx.newPage();
const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE:'+e.message));
await p.goto(URL,{ waitUntil:'load', timeout:20000 });
// wait for the boot to actually settle (scene built), not just DOM load
await p.waitForFunction(()=>window._qhBootSettled===true, { timeout:15000 }).catch(()=>{});
await p.waitForTimeout(2500);
const scene = await p.evaluate(()=>{
const qh=window._qh, sc=window._scene;
let meshes=0, withMap=0;
if(sc) sc.traverse(o=>{ if(o.isMesh){ meshes++; if(o.material && o.material.map) withMap++; } });
return {
hasQh:!!qh, hasScene:!!sc,
wingBoards: qh && qh.wingBoards ? qh.wingBoards.length : -1,
meshes, withMap,
bookPresent: qh ? qh.bookPresent : null,
bookState: qh && qh.bookState ? qh.bookState() : null,
perf: window._perf || null,
};
});
console.log('scene snapshot:', JSON.stringify({ ...scene, bookState: scene.bookState? {present:scene.bookState.present, leftMirrored:scene.bookState.leftMirrored, rightNormal:scene.bookState.rightNormal}:null }, null, 0));
// sample FPS over ~2s from the live _perf (loop must be running)
const fpsSamples=[];
for(let i=0;i<4;i++){ await p.waitForTimeout(500); const f=await p.evaluate(()=>window._perf?window._perf.fps:null); if(f!=null) fpsSamples.push(f); }
const avgFps = fpsSamples.length? Math.round(fpsSamples.reduce((a,b)=>a+b,0)/fpsSamples.length): null;
console.log('fps samples:', fpsSamples, '→ avg', avgFps, '(headless Chrome — real hardware is higher)');
let fail=0;
const need=(cond,msg)=>{ if(!cond){ console.log('❌ '+msg); fail++; } else console.log('✅ '+msg); };
need(scene.hasScene, 'window._scene exposed');
need(scene.wingBoards>0, `wing arc has boards (got ${scene.wingBoards})`);
need(scene.meshes>=10, `scene has real geometry (${scene.meshes} meshes, ${scene.withMap} textured)`);
need(scene.withMap>0, `at least one textured mesh (wallcovering maps loaded: ${scene.withMap})`);
need(scene.bookPresent===true, 'centre book present');
need(scene.bookState && scene.bookState.leftMirrored && scene.bookState.rightNormal, 'centre book book-matched (L mirrored, R normal)');
need(scene.perf && scene.perf.draws>0, `renderer issuing draw calls (${scene.perf?scene.perf.draws:'?'})`);
need(avgFps!=null && avgFps>=12, `frame loop alive, not stalled (avg ${avgFps}fps; ≥55 target is a real-hardware gate)`);
need(errs.length===0, `zero console errors (${errs.length})`);
if(errs.length) console.log(' errors:', errs.slice(0,5));
await b.close();
console.log(fail? `\n❌ ${fail} scene assertion(s) FAILED` : '\n✅ WebGL scene renders correctly (boards + textures + book-matched centre book + live loop)');
process.exit(fail?1:0);