// Phase B verification of transparent-region flattening (finding #4): a scene // with backgroundVideo set makes render.html skip the background fill, leaving // transparent areas. With the opaque-white render window those areas must // capture as white (~255), matching the server's omitBackground:false, not // black. Builds the window exactly like renderWorker.createRenderWindow. // // Run: node_modules/.bin/electron scripts/verify-bg.cjs const { app, BrowserWindow } = require('electron') const path = require('node:path') const { pathToFileURL } = require('node:url') const RENDER_PARTITION = 'ruwio-render' // backgroundVideo present -> render.html does NOT fill canvas.background. const scene = { canvas: { width: 160, height: 120, background: '#202030' }, backgroundVideo: { src: 'data:video/mp4,', duration: 1 }, duration: 1, assets: [{ id: 'a1', kind: 'rect', width: 20, height: 20, fill: '#ff0000' }], layers: [ { id: 'l1', assetId: 'a1', visible: true, base: { x: 30, y: 30, scaleX: 1, scaleY: 1, rotation: 0, opacity: 1 }, keyframes: [], }, ], } function createRenderWindow() { const win = new BrowserWindow({ width: scene.canvas.width, height: scene.canvas.height, show: false, frame: false, transparent: false, backgroundColor: '#ffffff', webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true, partition: RENDER_PARTITION, }, }) win.webContents.session.webRequest.onBeforeRequest({ urls: ['*://*/*'] }, (_d, cb) => cb({ cancel: true }), ) return win } app.on('window-all-closed', () => {}) app.whenReady().then(async () => { const win = createRenderWindow() const renderHtml = path.join(process.cwd(), 'server', 'render.html') await win.loadURL(pathToFileURL(renderHtml).toString()) await win.webContents.executeJavaScript(`window.__animatrInit(${JSON.stringify(scene)})`, true) await win.webContents.executeJavaScript('window.__animatrSeek(0)', true) const img = await win.webContents.capturePage({ x: 0, y: 0, width: 160, height: 120 }) const bmp = img.getBitmap() // BGRA // Sample a corner pixel (far from the red rect at 30,30): index 0 == (0,0). const corner = { b: bmp[0], g: bmp[1], r: bmp[2], a: bmp[3] } let white = 0 let black = 0 for (let i = 0; i < bmp.length; i += 4) { if (bmp[i] > 240 && bmp[i + 1] > 240 && bmp[i + 2] > 240) white++ if (bmp[i] < 15 && bmp[i + 1] < 15 && bmp[i + 2] < 15) black++ } const notes = [] let pass = true if (!(corner.r > 240 && corner.g > 240 && corner.b > 240)) { pass = false notes.push(`corner is not white: rgb(${corner.r},${corner.g},${corner.b})`) } if (black > 100) { pass = false notes.push(`${black} near-black px present (transparent flattened to black?)`) } console.log(`corner=rgb(${corner.r},${corner.g},${corner.b}) whitePx=${white} blackPx=${black}`) console.log(notes.length ? 'NOTES:\n ' + notes.join('\n ') : 'NOTES: none') console.log(pass ? 'RESULT: PASS' : 'RESULT: FAIL') if (!win.isDestroyed()) win.close() app.exit(pass ? 0 : 1) })