2026-07-30 15:13:53 +09:00
'use strict' ;
const fs = require ( 'fs' ) ;
const os = require ( 'os' ) ;
const path = require ( 'path' ) ;
const http = require ( 'http' ) ;
const { spawn , spawnSync } = require ( 'child_process' ) ;
const { assert , root } = require ( './helpers/app-source' ) ;
const edgePath = process . env . BEND _FIELD _EDGE _PATH || 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe' ;
const sleep = milliseconds => new Promise ( resolve => setTimeout ( resolve , milliseconds ) ) ;
let serverPort = 0 ;
const debuggingPort = 20000 + Math . floor ( Math . random ( ) * 1000 ) , benchmarkHost = process . env . BEND _FIELD _BENCHMARK _HOST || 'localhost' ;
const startupOnly = process . env . BEND _FIELD _STARTUP _ONLY === '1' ;
2026-08-01 16:06:14 +09:00
const effectVisualFixture = JSON . parse ( fs . readFileSync ( path . join ( root , 'test' , 'fixtures' , 'effect-visual-checkpoints.json' ) , 'utf8' ) ) ;
const benchmarkOutputPath = process . env . BEND _FIELD _BENCHMARK _OUTPUT || path . join ( root , 'test-results' , 'browser-performance-benchmark.json' ) ;
function writeBenchmarkReport ( report ) { fs . mkdirSync ( path . dirname ( benchmarkOutputPath ) , { recursive : true } ) ; fs . writeFileSync ( benchmarkOutputPath , ` ${ JSON . stringify ( report , null , 2 ) } \n ` ) }
2026-07-30 15:13:53 +09:00
const temporaryRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'bend-field-browser-benchmark-' ) ) ;
const profilePath = path . join ( temporaryRoot , 'edge-profile' ) ;
2026-08-01 16:06:14 +09:00
const worldDbName = 'bend-field:v30:linkfield-single-world-20260801:world' ;
2026-07-30 15:13:53 +09:00
const allProfiles = [
{ name : 'small' , boards : 16 , mode : 'grid' } ,
{ name : 'medium' , boards : 128 , mode : 'grid' } ,
{ name : 'large' , boards : 512 , mode : 'grid' } ,
{ name : 'complex' , boards : 16 , mode : 'complex' , chunks : 10 } ,
{ name : 'long-line' , boards : 256 , mode : 'line' }
] ;
const requestedProfile = process . env . BEND _FIELD _BENCHMARK _PROFILE || '' , profiles = requestedProfile ? allProfiles . filter ( profile => profile . name === requestedProfile ) : allProfiles ;
if ( requestedProfile && ! profiles . length ) throw new Error ( ` Unknown benchmark profile: ${ requestedProfile } ` ) ;
function stopBrowserTree ( child ) {
if ( ! child ? . pid ) return ;
if ( process . platform === 'win32' ) {
const taskkill = path . join ( process . env . SystemRoot || 'C:\\Windows' , 'System32' , 'taskkill.exe' ) ,
result = spawnSync ( taskkill , [ '/pid' , String ( child . pid ) , '/T' , '/F' ] , { stdio : 'ignore' , windowsHide : true } ) ;
if ( result . status !== 0 && ! child . killed ) try { child . kill ( ) } catch ( _ ) { }
} else if ( ! child . killed ) child . kill ( 'SIGTERM' ) ;
}
class CdpClient {
constructor ( url ) { this . url = url ; this . sequence = 0 ; this . pending = new Map ( ) ; this . events = [ ] ; this . socket = null }
async connect ( ) {
this . socket = new WebSocket ( this . url ) ;
await new Promise ( ( resolve , reject ) => {
const timer = setTimeout ( ( ) => reject ( new Error ( 'CDP WebSocket connection timed out' ) ) , 10000 ) ;
this . socket . addEventListener ( 'open' , ( ) => { clearTimeout ( timer ) ; resolve ( ) } , { once : true } ) ;
this . socket . addEventListener ( 'error' , event => { clearTimeout ( timer ) ; reject ( event . error || new Error ( 'CDP WebSocket failed' ) ) } , { once : true } ) ;
} ) ;
this . socket . addEventListener ( 'message' , event => {
const message = JSON . parse ( event . data ) ;
if ( ! message . id ) { this . events . push ( message ) ; if ( this . events . length > 200 ) this . events . shift ( ) ; return }
const pending = this . pending . get ( message . id ) ; if ( ! pending ) return ; this . pending . delete ( message . id ) ;
if ( message . error ) pending . reject ( new Error ( ` ${ pending . method } : ${ message . error . message } ` ) ) ;
else pending . resolve ( message . result ) ;
} ) ;
}
send ( method , params = { } ) {
const id = ++ this . sequence ;
return new Promise ( ( resolve , reject ) => {
this . pending . set ( id , { resolve , reject , method } ) ;
this . socket . send ( JSON . stringify ( { id , method , params } ) ) ;
} ) ;
}
async evaluate ( expression ) {
const response = await this . send ( 'Runtime.evaluate' , { expression , awaitPromise : true , returnByValue : true , userGesture : true } ) ;
if ( response . exceptionDetails ) throw new Error ( response . exceptionDetails . exception ? . description || response . exceptionDetails . text || 'Browser evaluation failed' ) ;
return response . result . value ;
}
close ( ) { try { this . socket ? . close ( ) } catch ( _ ) { } }
}
async function waitFor ( check , { timeout = 20000 , interval = 100 , label = 'condition' } = { } ) {
const deadline = Date . now ( ) + timeout ; let lastError = null ;
while ( Date . now ( ) < deadline ) {
try { const value = await check ( ) ; if ( value ) return value } catch ( error ) { lastError = error }
await sleep ( interval ) ;
}
throw lastError || new Error ( ` Timed out waiting for ${ label } ` ) ;
}
async function endpoint ( ) {
return waitFor ( async ( ) => {
const response = await fetch ( ` http://127.0.0.1: ${ debuggingPort } /json/list ` , { signal : AbortSignal . timeout ( 1500 ) } ) ;
if ( ! response . ok ) return null ;
const targets = await response . json ( ) ;
return targets . find ( target => target . type === 'page' && target . webSocketDebuggerUrl ) ;
} , { label : 'Edge DevTools endpoint' } ) ;
}
async function startStaticServer ( ) {
const types = { '.html' : 'text/html; charset=utf-8' , '.js' : 'text/javascript; charset=utf-8' , '.css' : 'text/css; charset=utf-8' , '.svg' : 'image/svg+xml' , '.ttf' : 'font/ttf' , '.ico' : 'image/x-icon' } ;
const server = http . createServer ( ( request , response ) => {
const pathname = new URL ( request . url , 'http://localhost' ) . pathname ;
const relative = pathname === '/' ? 'index.html' : decodeURIComponent ( pathname . slice ( 1 ) ) ;
const file = path . resolve ( root , relative ) ;
if ( file !== root && ! file . startsWith ( root + path . sep ) ) { response . writeHead ( 403 ) ; response . end ( ) ; return }
fs . readFile ( file , ( error , body ) => {
if ( error ) { response . writeHead ( error . code === 'ENOENT' ? 404 : 500 ) ; response . end ( ) ; return }
response . writeHead ( 200 , { 'content-type' : types [ path . extname ( file ) ] || 'application/octet-stream' , 'cache-control' : 'no-store' } ) ;
response . end ( body ) ;
} ) ;
} ) ;
await new Promise ( ( resolve , reject ) => {
server . once ( 'error' , reject ) ; server . listen ( 0 , '127.0.0.1' , ( ) => { server . off ( 'error' , reject ) ; resolve ( ) } ) ;
} ) ;
serverPort = server . address ( ) . port ;
return server ;
}
async function ready ( client ) {
try {
await waitFor ( ( ) => client . evaluate ( "document.body?.dataset?.ready==='true'&&Boolean(globalThis.BEND_PERF)" ) ,
{ timeout : 30000 , label : 'game startup' } ) ;
} catch ( error ) {
const state = await client . evaluate ( "({href:location.href,ready:document.body?.dataset?.ready,hasPerf:Boolean(globalThis.BEND_PERF),status:document.querySelector('#statusMessage')?.textContent||'',text:document.body?.innerText?.slice(0,500)||''})" ) . catch ( ( ) => null ) ;
const exception = client . events . findLast ? . ( event => event . method === 'Runtime.exceptionThrown' ) ? . params ? . exceptionDetails ;
const consoleError = client . events . findLast ? . ( event => event . method === 'Runtime.consoleAPICalled' && event . params ? . type === 'error' ) ? . params ;
const consoleText = consoleError ? . args ? . map ( argument => argument . description || argument . value || '' ) . join ( ' ' ) || '' ;
throw new Error ( ` ${ error . message } : ${ JSON . stringify ( state ) } ${ exception ? . exception ? . description || exception ? . text || consoleText } ` ) ;
}
await client . evaluate ( "document.querySelector('#normalPlayBtn')?.click();true" ) ;
await sleep ( 350 ) ;
}
async function readStarterRows ( client ) {
const expression = ` (async()=>{
const db = await new Promise ( ( resolve , reject ) => { const request = indexedDB . open ( $ { JSON . stringify ( worldDbName ) } ) ; request . onsuccess = ( ) => resolve ( request . result ) ; request . onerror = ( ) => reject ( request . error ) } ) ;
const read = request => new Promise ( ( resolve , reject ) => { request . onsuccess = ( ) => resolve ( request . result ) ; request . onerror = ( ) => reject ( request . error ) } ) ;
let tx = db . transaction ( 'control' , 'readonly' ) , control = await read ( tx . objectStore ( 'control' ) . get ( 'active' ) ) ;
await new Promise ( ( resolve , reject ) => { tx . oncomplete = resolve ; tx . onerror = ( ) => reject ( tx . error ) ; tx . onabort = ( ) => reject ( tx . error ) } ) ;
const epoch = control ? . activeEpoch ; tx = db . transaction ( [ 'worlds' , 'boardIndex' , 'boardPuzzles' , 'boardStates' ] , 'readonly' ) ;
const [ world , index , puzzle , state ] = await Promise . all ( [
read ( tx . objectStore ( 'worlds' ) . get ( epoch ) ) , read ( tx . objectStore ( 'boardIndex' ) . get ( [ epoch , 'B0' ] ) ) ,
read ( tx . objectStore ( 'boardPuzzles' ) . get ( [ epoch , 'B0' ] ) ) , read ( tx . objectStore ( 'boardStates' ) . get ( [ epoch , 'B0' ] ) )
] ) ;
await new Promise ( ( resolve , reject ) => { tx . oncomplete = resolve ; tx . onerror = ( ) => reject ( tx . error ) ; tx . onabort = ( ) => reject ( tx . error ) } ) ;
db . close ( ) ; return { control , world , index , puzzle , state } ;
} ) ( ) ` ;
return waitFor ( async ( ) => {
const rows = await client . evaluate ( expression ) ;
return rows ? . control ? . activeFormat === 2 && rows ? . world ? . global && rows ? . index && rows ? . puzzle ? . puzzle && rows ? . state ? . value ? rows : null ;
} , { timeout : 20000 , label : 'starter IndexedDB rows' } ) ;
}
async function populateWorld ( client , starter , profile ) {
const expression = ` (async()=>{
const starter = $ { JSON . stringify ( starter ) } , profile = $ { JSON . stringify ( profile ) } , count = profile . boards ;
const makeComplexPuzzle = ( source , chunkCount ) => {
const puzzle = structuredClone ( source ) , columns = 5 , g = [ ] , n = [ ] , valid = [ ] , solution = [ ] ;
for ( let chunk = 0 ; chunk < chunkCount ; chunk ++ ) {
const rowOffset = Math . floor ( chunk / columns ) * 5 , columnOffset = ( chunk % columns ) * 5 , gateOffset = g . length ;
for ( const gate of source . g ) g . push ( [ gate [ 0 ] + rowOffset , gate [ 1 ] + columnOffset , gate [ 2 ] ] ) ;
for ( const number of source . n || [ ] ) n . push ( [ number [ 0 ] + rowOffset , number [ 1 ] + columnOffset , number [ 2 ] ] ) ;
for ( const cell of source . valid || [ ] ) valid . push ( [ cell [ 0 ] + rowOffset , cell [ 1 ] + columnOffset ] ) ;
for ( const path of source . solution || [ ] ) solution . push ( { ... structuredClone ( path ) , startGate : path . startGate + gateOffset , endGate : path . endGate + gateOffset , cells : path . cells . map ( cell => [ cell [ 0 ] + rowOffset , cell [ 1 ] + columnOffset ] ) } ) ;
}
puzzle . g = g ; puzzle . n = n ; puzzle . valid = valid ; puzzle . solution = solution ; puzzle . bounds = { w : Math . min ( columns , chunkCount ) * 5 , h : Math . ceil ( chunkCount / columns ) * 5 } ; puzzle . level = 10 ; puzzle . difficulty = 10 ; puzzle . maxTurns = Math . max ( ... solution . map ( path => path . cells . length ) ) ; puzzle . totalTurns = solution . reduce ( ( sum , path ) => sum + path . cells . length , 0 ) ; return puzzle ;
} ;
const starterPuzzle = starter . puzzle . puzzle , starterState = starter . state . value ,
complexPuzzle = profile . mode === 'complex' ? makeComplexPuzzle ( starterPuzzle , profile . chunks || 10 ) : null ,
complexChunks = profile . mode === 'complex' ? Array . from ( { length : profile . chunks || 10 } , ( _ , index ) => [ index % 5 , Math . floor ( index / 5 ) ] ) : null ;
const db = await new Promise ( ( resolve , reject ) => { const request = indexedDB . open ( $ { JSON . stringify ( worldDbName ) } ) ; request . onsuccess = ( ) => resolve ( request . result ) ; request . onerror = ( ) => reject ( request . error ) } ) ;
const tx = db . transaction ( [ 'control' , 'worlds' , 'boardIndex' , 'boardPuzzles' , 'boardStates' , 'outboxV2' , 'recoveryV2' , 'tombstonesV2' ] , 'readwrite' ) ,
indexStore = tx . objectStore ( 'boardIndex' ) , puzzleStore = tx . objectStore ( 'boardPuzzles' ) , stateStore = tx . objectStore ( 'boardStates' ) ;
for ( const name of [ 'worlds' , 'boardIndex' , 'boardPuzzles' , 'boardStates' , 'outboxV2' , 'recoveryV2' , 'tombstonesV2' ] ) tx . objectStore ( name ) . clear ( ) ;
const columns = Math . ceil ( Math . sqrt ( count ) ) , spacingX = profile . mode === 'complex' ? 7 : 2 , spacingY = profile . mode === 'complex' ? 4 : 2 ;
for ( let index = 0 ; index < count ; index ++ ) {
const id = 'B' + index , x = profile . mode === 'line' ? index : ( index % columns ) * spacingX , y = profile . mode === 'line' ? 0 : Math . floor ( index / columns ) * spacingY ,
metaRev = 100000 + index , stateRev = 200000 + index , author = 'benchmark' , puzzle = profile . mode === 'complex' ? structuredClone ( complexPuzzle ) : structuredClone ( starterPuzzle ) ,
chunks = profile . mode === 'complex' ? structuredClone ( complexChunks ) : structuredClone ( starter . index . chunks ) , state = structuredClone ( starterState ) ;
state . paths = profile . mode === 'line' && index > 0 ? [ { startGate : 0 , endGate : 3 , openGate : null , cells : [ [ 0 , 0 ] , [ 0 , 1 ] , [ 0 , 2 ] , [ 0 , 3 ] , [ 0 , 4 ] ] , colorIndex : 0 , startColorIndex : 0 , endColorIndex : 0 } ] : [ ] ;
state . solved = false ; state . specialProgress = { crossings : [ ] } ; state . rev = stateRev ; state . revAuthor = author ;
indexStore . put ( { epoch : starter . control . activeEpoch , id , number : index , x , y , chunks , level : profile . mode === 'complex' ? 10 : starter . index . level , targetLevel : profile . mode === 'complex' ? 10 : starter . index . targetLevel , seed : index + 1 , axis : starter . index . axis , entrySide : null , sealedSides : [ ] , metaRev , stateRev , metaRevAuthor : author , stateRevAuthor : author , revAuthor : author , solved : false , expanded : false , scoreAwarded : 0 , hasProgress : state . paths . length > 0 , specialFlags : { crossing : false , warp : false , lock : false } , shop : null } ) ;
puzzleStore . put ( { epoch : starter . control . activeEpoch , id , metaRev , revAuthor : author , generatorVersion : starter . puzzle . generatorVersion , puzzle } ) ;
stateStore . put ( { epoch : starter . control . activeEpoch , id , stateRev , revAuthor : author , value : state } ) ;
}
const current = structuredClone ( starter . world . global ) , epoch = starter . control . activeEpoch ;
current . worldEpoch = epoch ; current . nextId = count ; current . solved = 0 ; current . score = 0 ; current . bonusScore = 0 ; current . bonusEvents = { } ;
current . globalRev = 300000 + count ; current . globalRevAuthor = 'benchmark' ; current . updatedAt = Date . now ( ) ; current . clockFloor = Date . now ( ) ;
const world = { ... starter . world , epoch , status : 'active' , global : current , boardCount : count , solvedCount : 0 , score : 0 , bounds : { minX : 0 , minY : 0 , maxX : profile . mode === 'line' ? count : columns * spacingX , maxY : profile . mode === 'line' ? 1 : Math . ceil ( count / columns ) * spacingY } , source : { kind : 'benchmark' } , progress : null } ;
tx . objectStore ( 'worlds' ) . put ( world ) ; tx . objectStore ( 'control' ) . put ( { key : 'active' , activeFormat : 2 , activeEpoch : epoch , activationVerified : true } ) ;
await new Promise ( ( resolve , reject ) => { tx . oncomplete = resolve ; tx . onerror = ( ) => reject ( tx . error ) ; tx . onabort = ( ) => reject ( tx . error ) } ) ;
db . close ( ) ; localStorage . clear ( ) ; sessionStorage . clear ( ) ; return { count , solution : profile . mode === 'complex' ? complexPuzzle . solution : starterPuzzle . solution } ;
} ) ( ) ` ;
const result = await client . evaluate ( expression ) ; assert ( result ? . count === profile . boards , 'Synthetic benchmark world was not written completely' ) ; return result . solution ;
}
async function reload ( client ) {
await client . evaluate ( "document.body.dataset.ready='reloading';true" ) ;
await client . send ( 'Page.reload' , { ignoreCache : true } ) ;
await ready ( client ) ;
await client . evaluate ( "centerMeta(data.metas.B0);ensureBoards();renderAll();true" ) ;
await waitFor ( ( ) => client . evaluate ( "Boolean(document.querySelector('.board-card[data-id=\"B0\"]:not(.board-static)'))" ) ,
{ timeout : 15000 , label : 'interactive origin board' } ) ;
await sleep ( 500 ) ;
}
async function waitForPersistenceIdle ( client ) {
await client . evaluate ( "(async()=>{if(hasPendingPersistence())await persistNow({skipCloud:true});return true})()" ) ;
await waitFor ( ( ) => client . evaluate ( "!hasPendingPersistence()&&!saveTimer" ) , { timeout : 20000 , label : 'benchmark persistence idle' } ) ;
await client . evaluate ( "persistQueue.then(()=>true)" ) ;
}
async function prepareInteractionMeasurement ( client ) {
await waitForPersistenceIdle ( client ) ;
await client . evaluate ( "globalThis.gc?.();true" ) ;
await sleep ( 120 ) ;
await client . evaluate ( 'BEND_PERF.reset();true' ) ;
}
async function restoreOriginState ( client , state ) {
const restored = await client . evaluate ( ` (async()=>{
const board = rendered . get ( 'B0' ) , snapshot = $ { JSON . stringify ( state ) } , nextFrame = ( ) => new Promise ( resolve => requestAnimationFrame ( ( ) => resolve ( ) ) ) ,
applySnapshot = ( ) => {
if ( board ) { cancelBoardDragFrame ( board ) ; clearDragRender ( board ) ; board . drawing = null ; board . armedGate = null ; if ( board . pendingClaimPointer ) clearPendingClaimPointer ( board , board . pendingClaimPointer . pointerId ) }
realtimeHeldPointers . clear ( ) ; const current = metaState ( 'B0' ) ; replaceObjectContents ( current , deepClone ( snapshot ) ) ; normalizedStateObjects . add ( current ) ; return current ;
} ;
applySnapshot ( ) ; refreshInteractionState ( ) ; await nextFrame ( ) ; await new Promise ( resolve => setTimeout ( resolve , 80 ) ) ;
const settledPathCount = metaState ( 'B0' ) . paths . length ; applySnapshot ( ) ; changed ( 'B0' ) ;
if ( board ) renderBoardNow ( board ) ; refreshInteractionState ( ) ; centerMeta ( data . metas . B0 , { select : false } ) ; await nextFrame ( ) ; await persistNow ( { skipCloud : true } ) ;
return { pathCount : metaState ( 'B0' ) . paths . length , settledPathCount , solved : metaState ( 'B0' ) . solved , drawing : Boolean ( board ? . drawing ) , pending : Boolean ( board ? . pendingClaimPointer ) } ;
} ) ( ) ` );
assert ( restored . pathCount === ( state . paths || [ ] ) . length && restored . solved === Boolean ( state . solved ) && ! restored . drawing && ! restored . pending , ` Canonical origin restore failed: ${ JSON . stringify ( restored ) } ` ) ;
}
async function pointFor ( client , selector ) {
const point = await client . evaluate ( ` (()=>{const element=document.querySelector( ${ JSON . stringify ( selector ) } );if(!element)return null;const rect=element.getBoundingClientRect();return{x:rect.left+rect.width/2,y:rect.top+rect.height/2}})() ` ) ;
if ( ! point ) throw new Error ( ` Missing benchmark target: ${ selector } ` ) ;
return point ;
}
async function pointForCell ( client , boardId , row , column ) {
const point = await client . evaluate ( ` (()=>{const board=rendered.get( ${ JSON . stringify ( boardId ) } );if(!board)return null;const rect=board.svg.getBoundingClientRect(),vb=board.svg.viewBox.baseVal,x=PAD+( ${ column } +.5)*CELL,y=PAD+( ${ row } +.5)*CELL;return{x:rect.left+x*rect.width/vb.width,y:rect.top+y*rect.height/vb.height}})() ` ) ;
if ( ! point ) throw new Error ( ` Missing benchmark board cell: ${ boardId } ${ row } , ${ column } ` ) ;
return point ;
}
async function pointsForCells ( client , boardId , cells ) {
const points = await client . evaluate ( ` (()=>{const board=rendered.get( ${ JSON . stringify ( boardId ) } ),cells= ${ JSON . stringify ( cells ) } ;if(!board)return null;const rect=board.svg.getBoundingClientRect(),vb=board.svg.viewBox.baseVal;return cells.map(([row,column])=>{const x=PAD+(column+.5)*CELL,y=PAD+(row+.5)*CELL;return{x:rect.left+x*rect.width/vb.width,y:rect.top+y*rect.height/vb.height}})})() ` ) ;
if ( ! points ) throw new Error ( ` Missing benchmark board cells: ${ boardId } ` ) ;
return points ;
}
async function safePanPoint ( client , selector ) {
const point = await client . evaluate ( ` (()=>{const card=document.querySelector( ${ JSON . stringify ( selector ) } ),view=document.querySelector('#viewport')?.getBoundingClientRect(),rect=card?.getBoundingClientRect();if(!card||!view||!rect)return null;for(const fy of[.18,.32,.5,.68,.82])for(const fx of[.18,.32,.5,.68,.82]){const x=rect.left+rect.width*fx,y=rect.top+rect.height*fy;if(x<view.left||x>view.right||y<view.top||y>view.bottom)continue;const target=document.elementFromPoint(x,y);if(target?.closest?.( ${ JSON . stringify ( selector ) } )&&!target.closest?.('button,a,input,select,textarea,[role="button"]'))return{x,y}}return null})() ` ) ;
return point || pointFor ( client , selector ) ;
}
async function solveOrigin ( client , solution , cpuRate = 1 ) {
for ( const pathRow of solution ) {
const start = await pointFor ( client , ` .board-card[data-id="B0"] .gate-hit[data-gate=" ${ pathRow . startGate } "] ` ) ,
end = await pointFor ( client , ` .board-card[data-id="B0"] .gate-hit[data-gate=" ${ pathRow . endGate } "] ` ) ;
const cellPoints = await pointsForCells ( client , 'B0' , pathRow . cells ) ;
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mousePressed' , x : start . x , y : start . y , button : 'left' , buttons : 1 , clickCount : 1 } ) ;
await sleep ( 20 ) ;
for ( const point of cellPoints ) {
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseMoved' , x : point . x , y : point . y , button : 'left' , buttons : 1 } ) ;
await sleep ( cpuRate === 1 ? 8 : 16 ) ;
}
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseMoved' , x : end . x , y : end . y , button : 'left' , buttons : 1 } ) ;
await sleep ( 25 * Math . max ( 1 , cpuRate ) ) ;
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseReleased' , x : end . x , y : end . y , button : 'left' , buttons : 0 , clickCount : 1 } ) ;
await waitFor ( ( ) => client . evaluate ( "!rendered.get('B0')?.drawing" ) , { timeout : 3000 * Math . max ( 1 , cpuRate ) , label : ` origin path ${ pathRow . startGate } - ${ pathRow . endGate } release ` } ) ;
await sleep ( 45 * Math . max ( 1 , cpuRate ) ) ;
}
}
async function viewportCenter ( client ) {
return client . evaluate ( "(()=>{const r=document.querySelector('#viewport').getBoundingClientRect();return{x:r.left+r.width/2,y:r.top+r.height/2}})()" ) ;
}
async function pan ( client , steps = 24 , { button = 'right' , selector = null , startPoint = null } = { } ) {
const center = await viewportCenter ( client ) , start = startPoint || ( selector ? await safePanPoint ( client , selector ) : { x : center . x + 180 , y : center . y + 80 } ) , buttons = button === 'left' ? 1 : 2 ;
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mousePressed' , x : start . x , y : start . y , button , buttons , clickCount : 1 } ) ;
for ( let index = 1 ; index <= steps ; index ++ ) {
await client . send ( 'Input.dispatchMouseEvent' , {
type : 'mouseMoved' , x : start . x - index * 9 , y : start . y - index * 4 , button : 'none' , buttons
} ) ;
await sleep ( 12 ) ;
}
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseReleased' , x : start . x - steps * 9 , y : start . y - steps * 4 , button , buttons : 0 , clickCount : 1 } ) ;
await sleep ( 250 ) ;
return start ;
}
async function panCadenceProbe ( client , steps = 24 ) {
return client . evaluate ( ` new Promise(resolve=>{
const viewport = document . querySelector ( '#viewport' ) , rect = viewport . getBoundingClientRect ( ) , pointerId = 91 , startX = rect . left + rect . width * . 7 , startY = rect . top + rect . height * . 6 ;
const [ worldX , worldY ] = worldUnitAtClient ( startX , startY ) , reactionNow = Date . now ( ) , remoteId = 'benchmark-remote' ;
applyRemotePlayer ( { presenceId : remoteId , playerId : 'benchmark' , name : 'Remote' , cursorStyle : 'default' , x : worldX , y : worldY , vx : . 08 , vy : . 03 , sentAt : reactionNow } ) ;
2026-08-01 16:06:14 +09:00
applyRealtimeReaction ( { id : 'benchmark-reaction' , emoji : REACTION _EMOJIS [ 0 ] , style : 'firework' , x : worldX , y : worldY , createdAt : reactionNow , expiresAt : reactionNow + Math . max ( 3400 , $ { steps } * 25 ) } ) ;
2026-07-30 15:13:53 +09:00
const dispatch = ( type , index , buttons ) => viewport . dispatchEvent ( new PointerEvent ( type , { bubbles : true , cancelable : true , pointerId , pointerType : 'mouse' , isPrimary : true , button : type === 'pointerdown' || type === 'pointerup' ? 2 : - 1 , buttons , clientX : startX - index * 7 , clientY : startY - index * 3 } ) ) ;
dispatch ( 'pointerdown' , 0 , 2 ) ; let index = 0 ;
2026-08-01 16:06:14 +09:00
const tick = ( ) => { index ++ ; dispatch ( 'pointermove' , index , 2 ) ; if ( index < $ { steps } ) requestAnimationFrame ( tick ) ; else { dispatch ( 'pointerup' , index , 0 ) ; setTimeout ( ( ) => { remotePlayers . delete ( remoteId ) ; realtimeReactions . delete ( 'benchmark-reaction' ) ; resolve ( index ) } , 80 ) } } ;
2026-07-30 15:13:53 +09:00
requestAnimationFrame ( tick ) ;
} ) ` );
}
async function pinchZoomProbe ( client ) {
return client . evaluate ( ` new Promise(resolve=>{
const viewport = document . querySelector ( '#viewport' ) , rect = viewport . getBoundingClientRect ( ) , cx = rect . left + rect . width / 2 , cy = rect . top + rect . height / 2 , before = cam . scale ;
const dispatch = ( type , pointerId , x ) => viewport . dispatchEvent ( new PointerEvent ( type , { bubbles : true , cancelable : true , pointerId , pointerType : 'touch' , isPrimary : pointerId === 111 , button : type === 'pointerdown' ? 0 : - 1 , buttons : type === 'pointerup' ? 0 : 1 , clientX : x , clientY : cy } ) ) ;
dispatch ( 'pointerdown' , 111 , cx - 45 ) ; dispatch ( 'pointerdown' , 112 , cx + 45 ) ; let frame = 0 ;
const tick = ( ) => { frame ++ ; dispatch ( 'pointermove' , 111 , cx - 45 - frame * 5 ) ; dispatch ( 'pointermove' , 112 , cx + 45 + frame * 5 ) ; if ( frame < 8 ) requestAnimationFrame ( tick ) ; else { dispatch ( 'pointerup' , 111 , cx - 85 ) ; dispatch ( 'pointerup' , 112 , cx + 85 ) ; setTimeout ( ( ) => resolve ( { before , after : cam . scale , changed : Math . abs ( cam . scale - before ) > . 01 } ) , 100 ) } } ;
requestAnimationFrame ( tick ) ;
} ) ` );
}
async function pickupEdgePanProbe ( client , pathRow ) {
2026-08-01 16:06:14 +09:00
const originalState = await client . evaluate ( "deepClone(metaState('B0'))" ) ;
await client . evaluate ( "(()=>{const r=document.querySelector('#viewport').getBoundingClientRect(),p=worldUnitAtClient(r.left+r.width/2,r.top+r.height/2),now=trustedNow();applyRealtimeReaction({id:'benchmark-edge-pan-effect',emoji:REACTION_EMOJIS[0],style:'comet',x:p[0],y:p[1],createdAt:now,expiresAt:now+12000});return true})()" ) ;
const start = await pointFor ( client , ` .board-card[data-id="B0"] .gate-hit[data-gate=" ${ pathRow . startGate } "] ` ) ,
2026-07-30 15:13:53 +09:00
edge = await client . evaluate ( "(()=>{const r=document.querySelector('#viewport').getBoundingClientRect();return{x:r.right-2,y:r.top+r.height/2}})()" ) ,
before = await client . evaluate ( "({x:cam.x,y:cam.y,frames:BEND_PERF.snapshot().counters.dragFrames||0})" ) ;
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mousePressed' , x : start . x , y : start . y , button : 'left' , buttons : 1 , clickCount : 1 } ) ;
try {
await waitFor ( ( ) => client . evaluate ( "Boolean(rendered.get('B0')?.drawing)" ) , { timeout : 5000 , label : 'pickup edge-pan activation' } ) ;
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseMoved' , x : edge . x , y : edge . y , button : 'left' , buttons : 1 } ) ; await sleep ( 350 ) ;
const after = await client . evaluate ( "({x:cam.x,y:cam.y,frames:BEND_PERF.snapshot().counters.dragFrames||0})" ) ;
return { cameraMoved : Math . hypot ( after . x - before . x , after . y - before . y ) > 5 , dragFrames : after . frames - before . frames } ;
} finally {
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseReleased' , x : edge . x , y : edge . y , button : 'left' , buttons : 0 , clickCount : 1 } ) . catch ( ( ) => { } ) ;
2026-08-01 16:06:14 +09:00
await client . evaluate ( ` (()=>{realtimeReactions.delete('benchmark-edge-pan-effect');data.states.B0= ${ JSON . stringify ( originalState ) } ;normalizedStateObjects.add(data.states.B0);markStateDirty('B0');renderBoardNow(rendered.get('B0'));return true})() ` ) ;
2026-07-30 15:13:53 +09:00
await client . evaluate ( "(async()=>{await persistNow({skipCloud:true});return true})()" ) ;
}
}
async function pickupCadenceProbe ( client , pathRow , steps = 120 , sampleDelay = 8 ) {
const start = await pointFor ( client , ` .board-card[data-id="B0"] .gate-hit[data-gate=" ${ pathRow . startGate } "] ` ) ,
center = await pointForCell ( client , 'B0' , pathRow . cells [ 0 ] [ 0 ] , pathRow . cells [ 0 ] [ 1 ] ) ,
originalState = await client . evaluate ( "deepClone(metaState('B0'))" ) ;
2026-08-01 16:06:14 +09:00
await client . evaluate ( ` (()=>{const p=worldUnitAtClient( ${ start . x } , ${ start . y } ),now=trustedNow();applyRealtimeReaction({id:'benchmark-pickup-effect',emoji:REACTION_EMOJIS[0],style:'firework',x:p[0],y:p[1],createdAt:now,expiresAt:now+ ${ Math . max ( 12000 , steps * sampleDelay * 2 ) } });return true})() ` ) ;
2026-07-30 15:13:53 +09:00
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mousePressed' , x : start . x , y : start . y , button : 'left' , buttons : 1 , clickCount : 1 } ) ;
try {
await waitFor ( ( ) => client . evaluate ( "Boolean(rendered.get('B0')?.drawing)" ) , { timeout : 5000 , label : 'continuous pickup activation' } ) ;
const pendingMoves = [ ] ;
for ( let index = 0 ; index < steps ; index ++ ) {
pendingMoves . push ( client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseMoved' , x : center . x + ( index % 2 ? 3 : - 3 ) , y : center . y + ( ( index % 3 ) - 1 ) * 2 , button : 'left' , buttons : 1 } ) ) ;
await sleep ( sampleDelay ) ;
}
await Promise . all ( pendingMoves ) ;
return { moves : steps , reason : 'complete' , snapshot : await client . evaluate ( 'BEND_PERF.snapshot()' ) } ;
} finally {
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseReleased' , x : center . x , y : center . y , button : 'left' , buttons : 0 , clickCount : 1 } ) . catch ( ( ) => { } ) ;
await sleep ( 120 ) ;
2026-08-01 16:06:14 +09:00
await client . evaluate ( ` (()=>{realtimeReactions.delete('benchmark-pickup-effect');data.states.B0= ${ JSON . stringify ( originalState ) } ;normalizedStateObjects.add(data.states.B0);markStateDirty('B0');renderBoardNow(rendered.get('B0'));return true})() ` ) ;
2026-07-30 15:13:53 +09:00
await client . evaluate ( "(async()=>{await persistNow({skipCloud:true});return true})()" ) ;
}
}
async function claimLatencyProbe ( client , pathRow , approve ) {
return client . evaluate ( ` (async()=>{
const board = rendered . get ( 'B0' ) , state = metaState ( 'B0' ) , originalState = deepClone ( state ) , originalEnsure = ensureBoardClaimForInput , pointerId = $ { approve ? 93 : 94 } ; let resolveClaim ;
const
wait = milliseconds => new Promise ( resolve => setTimeout ( resolve , milliseconds ) ) , nextFrame = ( ) => new Promise ( resolve => requestAnimationFrame ( resolve ) ) ;
const cleanup = ( ) => { cancelBoardDragFrame ( board ) ; clearDragRender ( board ) ; board . drawing = null ; board . armedGate = null ; clearPendingClaimPointer ( board , pointerId , { release : false } ) ; realtimeHeldPointers . delete ( pointerId ) ; try { if ( board . svg . hasPointerCapture ? . ( pointerId ) ) board . svg . releasePointerCapture ( pointerId ) } catch ( _ ) { } data . states . B0 = originalState ; normalizedStateObjects . add ( originalState ) ; renderBoardNow ( board ) ; refreshInteractionState ( ) } ;
try {
const cells = $ { JSON . stringify ( pathRow . cells ) } , startGate = $ { pathRow . startGate } , startColor = canonicalGateColorIndex ( board . meta , startGate , null ) ;
state . paths = [ { startGate , endGate : null , openGate : null , cells : cells . slice ( 0 , 2 ) . map ( cell => [ ... cell ] ) , colorIndex : startColor , startColorIndex : startColor , endColorIndex : null } ] ; board . drawing = null ; renderBoardNow ( board ) ;
const hit = board . pathLayer . querySelector ( '.endpoint-hit[data-path-index="0"][data-endpoint-side="end"]' ) , rect = board . svg . getBoundingClientRect ( ) , vb = board . svg . viewBox . baseVal ,
screenPoint = ( [ row , column ] ) => ( { clientX : rect . left + ( PAD + ( column + . 5 ) * CELL ) * rect . width / vb . width , clientY : rect . top + ( PAD + ( row + . 5 ) * CELL ) * rect . height / vb . height } ) ,
down = screenPoint ( cells [ 1 ] ) , latest = screenPoint ( cells [ Math . min ( 2 , cells . length - 1 ) ] ) , before = JSON . stringify ( state . paths ) , fullBefore = perfCounters . fullBoardRenders || 0 ;
ensureBoardClaimForInput = ( ) => new Promise ( resolve => { resolveClaim = resolve } ) ;
hit . dispatchEvent ( new PointerEvent ( 'pointerdown' , { bubbles : true , cancelable : true , pointerId , pointerType : 'mouse' , isPrimary : true , button : 0 , buttons : 1 , ... down } ) ) ;
await nextFrame ( ) ; board . svg . dispatchEvent ( new PointerEvent ( 'pointermove' , { bubbles : true , cancelable : true , pointerId , pointerType : 'mouse' , isPrimary : true , button : - 1 , buttons : 1 , ... latest } ) ) ;
await wait ( 70 ) ; await nextFrame ( ) ;
const pending = board . pendingClaimPointer , previewWithinFrame = Boolean ( pending ? . preview ? . getAttribute ? . ( 'transform' ) ) , tracksLatest = Boolean ( pending && Math . abs ( pending . clientX - latest . clientX ) < . 1 && Math . abs ( pending . clientY - latest . clientY ) < . 1 ) ,
modelUntouched = JSON . stringify ( state . paths ) === before && ! board . drawing ;
resolveClaim ( $ { approve } ) ; await wait ( 20 ) ; await nextFrame ( ) ; await nextFrame ( ) ;
const approved = Boolean ( board . drawing ? . pointerId === pointerId ) , processed = board . lastProcessedPointerMove ,
noJump = $ { approve } ? Boolean ( processed && Math . abs ( processed . clientX - latest . clientX ) < . 1 && Math . abs ( processed . clientY - latest . clientY ) < . 1 ) : ! board . drawing && ! board . pendingClaimPointer && JSON . stringify ( state . paths ) === before ,
fullBoardRenders = ( perfCounters . fullBoardRenders || 0 ) - fullBefore ;
return { previewWithinFrame , tracksLatest , modelUntouched , approved , noJump , fullBoardRenders } ;
} finally { ensureBoardClaimForInput = originalEnsure ; cleanup ( ) }
} ) ( ) ` );
}
async function measureCursorModes ( client ) {
return client . evaluate ( ` (async()=>{
const emoji = CURSOR _ITEMS . find ( item => item . cursorEmoji && ! item . flagAsset ) , flag = CURSOR _ITEMS . find ( item => item . flagAsset ) , nextFrame = ( ) => new Promise ( resolve => requestAnimationFrame ( resolve ) ) ;
try {
localStorage . removeItem ( 'bend-field-cursor-renderer' ) ; syncCursorAppearance ( 'default' ) ; const defaultMode = document . body . dataset . cursorMode ;
syncCursorAppearance ( emoji . cursorStyle ) ; const emojiMode = document . body . dataset . cursorMode ;
syncCursorAppearance ( flag . cursorStyle ) ; const flagMode = document . body . dataset . cursorMode ;
localStorage . setItem ( 'bend-field-cursor-renderer' , 'dom' ) ; syncCursorAppearance ( emoji . cursorStyle ) ;
const viewport = document . querySelector ( '#viewport' ) , cursorEvent = 'onpointerrawupdate' in window ? 'pointerrawupdate' : 'pointermove' , first = new PointerEvent ( cursorEvent , { bubbles : true , pointerId : 95 , pointerType : 'mouse' , clientX : 120 , clientY : 130 } ) ;
viewport . dispatchEvent ( first ) ; await nextFrame ( ) ; const firstTransform = customEmojiCursor . style . transform , visibleBeforeDrag = customEmojiCursor . classList . contains ( 'visible' ) ;
document . body . classList . add ( 'is-drawing' ) ; viewport . dispatchEvent ( new PointerEvent ( cursorEvent , { bubbles : true , pointerId : 95 , pointerType : 'mouse' , clientX : 160 , clientY : 170 } ) ) ; await nextFrame ( ) ;
return { defaultMode , emojiMode , flagMode , domMode : document . body . dataset . cursorMode , visibleBeforeDrag , visibleDuringDrag : customEmojiCursor . classList . contains ( 'visible' ) , movedDuringDrag : firstTransform !== customEmojiCursor . style . transform } ;
} finally { document . body . classList . remove ( 'is-drawing' ) ; localStorage . removeItem ( 'bend-field-cursor-renderer' ) ; syncCursorAppearance ( 'default' ) }
} ) ( ) ` );
}
async function measureDisplayCadence ( client , frames = 90 ) {
return client . evaluate ( ` new Promise(resolve=>{
const gaps = [ ] ; let previous = 0 , count = 0 ;
const tick = timestamp => { if ( previous ) gaps . push ( timestamp - previous ) ; previous = timestamp ; if ( ++ count < $ { frames } ) requestAnimationFrame ( tick ) ; else { const sorted = gaps . sort ( ( a , b ) => a - b ) , pick = q => sorted [ Math . floor ( ( sorted . length - 1 ) * q ) ] || 0 ; resolve ( { count : gaps . length , p50 : pick ( . 5 ) , p95 : pick ( . 95 ) , max : sorted [ sorted . length - 1 ] || 0 } ) } } ;
requestAnimationFrame ( tick ) ;
} ) ` );
}
async function measureCursorCadence ( client , steps = 180 ) {
const setup = await client . evaluate ( ` (()=>{
const emoji = CURSOR _ITEMS . find ( item => item . cursorEmoji && ! item . flagAsset ) , rect = document . querySelector ( '#viewport' ) . getBoundingClientRect ( ) ;
2026-07-31 12:54:46 +09:00
localStorage . setItem ( 'bend-field-cursor-renderer' , 'dom' ) ; syncCursorAppearance ( emoji . cursorStyle ) ; BEND _PERF . reset ( ) ; globalThis . _ _benchmarkCursorCommits = [ ] ;
globalThis . _ _benchmarkCommitCustomCursorFrame = commitCustomCursorFrame ; commitCustomCursorFrame = ( sample , timestamp ) => { globalThis . _ _benchmarkCursorCommits . push ( { timestamp , inputAt : sample . inputAt , revision : sample . revision } ) ; return globalThis . _ _benchmarkCommitCustomCursorFrame ( sample , timestamp ) } ;
2026-07-30 15:13:53 +09:00
return { left : rect . left + 40 , top : rect . top + 40 , width : Math . max ( 120 , rect . width - 80 ) , height : Math . max ( 120 , rect . height - 80 ) } ;
} ) ( ) ` );
try {
2026-07-31 12:54:46 +09:00
const dispatches = [ ] ;
2026-07-30 15:13:53 +09:00
for ( let index = 0 ; index < steps ; index ++ ) {
2026-07-31 12:54:46 +09:00
dispatches . push ( client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseMoved' , x : setup . left + ( index * 7 ) % setup . width , y : setup . top + ( index * 3 ) % setup . height , button : 'none' , buttons : 0 } ) ) ;
2026-07-30 15:13:53 +09:00
await sleep ( 8 ) ;
}
2026-07-31 12:54:46 +09:00
await Promise . all ( dispatches ) ;
await sleep ( 120 ) ; const measured = await client . evaluate ( ` (()=>{
const snapshot = BEND _PERF . snapshot ( ) , commits = globalThis . _ _benchmarkCursorCommits || [ ] , gaps = commits . slice ( 1 ) . map ( ( entry , index ) => entry . timestamp - commits [ index ] . timestamp ) ;
return { snapshot , diagnostic : { interval : DRAG _FRAME _INTERVAL , tolerance : INTERACTION _FRAME _TOLERANCE _MS , commitCount : commits . length , gaps : gaps . slice ( 0 , 20 ) , lastDraw : customCursorLastDraw , inputRevision : customCursorInputRevision , committedRevision : customCursorCommittedRevision } } ;
} ) ( ) ` ),snapshot=measured.snapshot,gap=timing(snapshot,'cursorFrameGap'),age=timing(snapshot,'cursorInputAge');
2026-08-01 16:06:14 +09:00
assert ( gap . count >= 6 && gap . p50 <= 40 , ` DOM cursor cadence missed the capped 30 FPS acceptance: ${ JSON . stringify ( { gap , diagnostic : measured . diagnostic } )} ` ) ;
assert ( age . count >= 8 && age . p95 < 45 , ` DOM cursor input age missed acceptance: ${ JSON . stringify ( age ) } ` ) ;
2026-07-30 15:13:53 +09:00
return snapshot ;
2026-07-31 12:54:46 +09:00
} finally { await client . evaluate ( "if(globalThis.__benchmarkCommitCustomCursorFrame)commitCustomCursorFrame=globalThis.__benchmarkCommitCustomCursorFrame;delete globalThis.__benchmarkCommitCustomCursorFrame;delete globalThis.__benchmarkCursorCommits;localStorage.removeItem('bend-field-cursor-renderer');syncCursorAppearance('default');true" ) }
2026-07-30 15:13:53 +09:00
}
2026-08-01 16:06:14 +09:00
async function measureEffectsAndCosmetics ( client , { cpuRate = 1 , visual = true , inventory = true , aurora = true , memory = true , singles = true } = { } ) {
await client . send ( 'Emulation.setCPUThrottlingRate' , { rate : cpuRate } ) ;
return client . evaluate ( ` (async()=>{
const options = $ { JSON . stringify ( { cpuRate , visual , inventory , aurora , memory , singles , visualCases : effectVisualFixture . cases , visualWidth : effectVisualFixture . width , visualHeight : effectVisualFixture . height , perChannelTolerance : effectVisualFixture . perChannelTolerance , maximumChangedPixelRatio : effectVisualFixture . maximumChangedPixelRatio } ) } , wait = milliseconds => new Promise ( resolve => setTimeout ( resolve , milliseconds ) ) ,
styles = [ 'classic' , 'giant' , 'laser' , 'orbit' , 'firework' , 'comet' ] , emoji = REACTION _EMOJIS [ 0 ] ,
runSet = async ( runStyles , label , duration = 900 ) => {
for ( const style of new Set ( runStyles ) ) BEND _PERF . warmEffectCache ( emoji , style ) ; await new Promise ( resolve => requestAnimationFrame ( resolve ) ) ; await wait ( 80 ) ;
BEND _PERF . reset ( ) ; const rect = document . querySelector ( '#viewport' ) . getBoundingClientRect ( ) , center = worldUnitAtClient ( rect . left + rect . width / 2 , rect . top + rect . height / 2 ) , now = trustedNow ( ) ;
runStyles . forEach ( ( style , index ) => { const effectDuration = reactionDurationForStyle ( style ) , angle = index * Math . PI * 2 / Math . max ( 1 , runStyles . length ) , radius = runStyles . length > 1 ? 1.35 : 0 ; applyRealtimeReaction ( { id : 'effect-benchmark-' + label + '-' + index , emoji , style , x : center [ 0 ] + Math . cos ( angle ) * radius , y : center [ 1 ] + Math . sin ( angle ) * radius , createdAt : now - effectDuration * . 22 , expiresAt : now + effectDuration * . 78 } ) } ) ;
await wait ( duration ) ; const snapshot = BEND _PERF . snapshot ( ) ; realtimeReactions . clear ( ) ; reactionDirty = true ; scheduleReactionRender ( true ) ; await wait ( 90 ) ; return snapshot ;
} ,
loadPixels = dataUrl => new Promise ( ( resolve , reject ) => { const image = new Image ( ) ; image . onload = ( ) => { const canvas = document . createElement ( 'canvas' ) ; canvas . width = image . naturalWidth ; canvas . height = image . naturalHeight ; const context = canvas . getContext ( '2d' , { willReadFrequently : true } ) ; context . drawImage ( image , 0 , 0 ) ; resolve ( { width : canvas . width , height : canvas . height , data : context . getImageData ( 0 , 0 , canvas . width , canvas . height ) . data } ) } ; image . onerror = reject ; image . src = dataUrl } ) ,
compareSamples = async ( directUrl , cachedUrl ) => { const [ a , b ] = await Promise . all ( [ loadPixels ( directUrl ) , loadPixels ( cachedUrl ) ] ) ; let changed = 0 , active = 0 , alphaDelta = 0 , minX = a . width , minY = a . height , maxX = - 1 , maxY = - 1 ; for ( let offset = 0 ; offset < a . data . length ; offset += 4 ) { const pixel = offset / 4 , x = pixel % a . width , y = Math . floor ( pixel / a . width ) , visible = a . data [ offset + 3 ] > 0 || b . data [ offset + 3 ] > 0 ; if ( visible ) { active ++ ; minX = Math . min ( minX , x ) ; minY = Math . min ( minY , y ) ; maxX = Math . max ( maxX , x ) ; maxY = Math . max ( maxY , y ) } if ( Math . abs ( a . data [ offset ] - b . data [ offset ] ) > options . perChannelTolerance || Math . abs ( a . data [ offset + 1 ] - b . data [ offset + 1 ] ) > options . perChannelTolerance || Math . abs ( a . data [ offset + 2 ] - b . data [ offset + 2 ] ) > options . perChannelTolerance || Math . abs ( a . data [ offset + 3 ] - b . data [ offset + 3 ] ) > options . perChannelTolerance ) changed ++ ; alphaDelta += Math . abs ( a . data [ offset + 3 ] - b . data [ offset + 3 ] ) } return { changed , total : a . width * a . height , ratio : changed / ( a . width * a . height ) , active , alphaDelta , bounds : [ minX , minY , maxX , maxY ] } } ,
result = { cpuRate : options . cpuRate , singles : { } , visual : [ ] , visualGate : { perChannelTolerance : options . perChannelTolerance , maximumChangedPixelRatio : options . maximumChangedPixelRatio , width : options . visualWidth , height : options . visualHeight } , inventory : null , aurora : null , memory : null } ;
BEND _PERF . clearEffectCaches ( ) ;
if ( options . singles ) for ( const style of styles ) result . singles [ style ] = await runSet ( [ style ] , style , style === 'classic' ? 620 : 1200 ) ;
result . overlap4 = await runSet ( [ 'giant' , 'laser' , 'orbit' , 'firework' ] , 'overlap4' , 1100 ) ;
result . overlap8 = await runSet ( [ 'giant' , 'laser' , 'orbit' , 'firework' , 'comet' , 'laser' , 'orbit' , 'firework' ] , 'overlap8' , 1100 ) ;
if ( options . cpuRate === 1 ) {
const board = rendered . get ( 'B0' ) , setup = { } ; BEND _PERF . reset ( ) ; for ( let index = 0 ; index < 40 ; index ++ ) { const style = styles [ index % styles . length ] , duration = reactionDurationForStyle ( style ) , id = 'effect-publish-' + index , now = trustedNow ( ) ; applyRealtimeReaction ( { id , emoji , style , x : 0 , y : 0 , createdAt : now , expiresAt : now + duration } ) ; realtimeReactions . delete ( id ) } cancelReactionRenderScheduler ( true ) ; setup . reaction = BEND _PERF . snapshot ( ) ;
if ( board ) { cleanupGemEffects ( ) ; BEND _PERF . reset ( ) ; for ( let index = 0 ; index < 30 ; index ++ ) { playGemCollectionAnimation ( board , 100000 ) ; cleanupGemEffects ( ) } setup . gem = BEND _PERF . snapshot ( ) ; skipCompletionVisuals ( ) ; BEND _PERF . reset ( ) ; for ( let index = 0 ; index < 30 ; index ++ ) { completionEffect ( board , 1000 ) ; finishCompletionVisual ( board . id , false ) } setup . completion = BEND _PERF . snapshot ( ) }
result . setup = setup ;
}
if ( options . visual ) {
for ( const { style , life } of options . visualCases ) {
const direct = BEND _PERF . renderReactionSample ( { style , emoji , life , width : options . visualWidth , height : options . visualHeight , glyphCache : false , pathCache : false } ) , cached = BEND _PERF . renderReactionSample ( { style , emoji , life , width : options . visualWidth , height : options . visualHeight , glyphCache : true , pathCache : true } ) ;
result . visual . push ( { style , life , ... await compareSamples ( direct . dataUrl , cached . dataUrl ) } ) ;
}
}
if ( options . inventory ) {
const previousCursor = data . cursorStyle , previousLineColor = data . lineColorStyle , previousInventoryEntries = inventoryEntries , syntheticEntries = STORE _ITEMS . map ( ( item , index ) => ( { meta : null , st : null , store : null , purchase : { id : item . id , itemId : item . id , boughtAt : index + 1 , paidCost : 0 } , personal : true } ) ) ; inventoryEntries = itemId => itemId ? syntheticEntries . filter ( entry => entry . purchase . id === itemId ) : [ ... syntheticEntries ] ; invalidateEconomyCaches ( ) ; await wait ( 100 ) ; BEND _PERF . reset ( ) ; const resourcesBefore = performance . getEntriesByType ( 'resource' ) . filter ( entry => entry . name . includes ( '/assets/flags/' ) ) . length ;
openInventory ( ) ; await new Promise ( resolve => requestAnimationFrame ( ( ) => requestAnimationFrame ( resolve ) ) ) ; const renderSnapshot = BEND _PERF . snapshot ( ) ; inventoryPanel . scrollTop = Math . min ( 480 , Math . max ( 0 , inventoryPanel . scrollHeight - inventoryPanel . clientHeight ) ) ; const scrollBefore = inventoryPanel . scrollTop , auroraItem = STORE _ITEMS . find ( item => item . aurora === true ) , focusTarget = auroraItem ? inventoryItemViews . get ( auroraItem . id ) ? . use : null , nodesBefore = inventoryList . querySelectorAll ( '*' ) . length ; focusTarget ? . focus ( { preventScroll : true } ) ; BEND _PERF . reset ( ) ;
for ( let index = 0 ; index < 40 ; index ++ ) { data . lineColorStyle = index % 2 ? previousLineColor : auroraItem . id ; patchInventoryItems ( [ auroraItem . id , previousLineColor ] ) } await new Promise ( resolve => requestAnimationFrame ( resolve ) ) ;
const patchSnapshot = BEND _PERF . snapshot ( ) , resourcesAfter = performance . getEntriesByType ( 'resource' ) . filter ( entry => entry . name . includes ( '/assets/flags/' ) ) . length ; result . inventory = { renderSnapshot , patchSnapshot , scrollBefore , scrollAfter : inventoryPanel . scrollTop , focusRetained : ! focusTarget || document . activeElement === focusTarget , resourceDelta : resourcesAfter - resourcesBefore , mounted : inventoryList . querySelectorAll ( '[data-item-id]' ) . length , nodesBefore , nodesAfter : inventoryList . querySelectorAll ( '*' ) . length } ;
closeInventory ( false ) ; inventoryEntries = previousInventoryEntries ; invalidateEconomyCaches ( ) ; data . cursorStyle = previousCursor ; data . lineColorStyle = previousLineColor ; syncCursorAppearance ( previousCursor ) ; syncCosmeticAppearance ( ) ; renderInventoryPanel ( ) ;
}
if ( options . aurora ) {
const svg = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) , fragment = document . createDocumentFragment ( ) ; svg . setAttribute ( 'aria-hidden' , 'true' ) ; svg . style . cssText = 'position:absolute;width:1px;height:1px;overflow:visible;pointer-events:none' ;
for ( let index = 0 ; index < 500 ; index ++ ) { const path = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'path' ) ; path . setAttribute ( 'class' , 'path line-effect-aurora' ) ; path . setAttribute ( 'd' , \ ` M0 \$ {index%25} L100 \$ {index%25} \` );fragment.append(path)}svg.append(fragment);world.append(svg);const previousCount=auroraVisiblePathCount;auroraVisiblePathCount+=500;BEND_PERF.reset();updateAuroraAnimationState();await wait(4300);const active=BEND_PERF.snapshot();auroraVisiblePathCount=0;updateAuroraAnimationState();BEND_PERF.reset();await wait(2300);const inactive=BEND_PERF.snapshot();
Object . defineProperty ( document , 'visibilityState' , { value : 'hidden' , configurable : true } ) ; auroraVisiblePathCount = 500 ; document . dispatchEvent ( new Event ( 'visibilitychange' ) ) ; BEND _PERF . reset ( ) ; await wait ( 2300 ) ; const hidden = BEND _PERF . snapshot ( ) ; delete document . visibilityState ; auroraVisiblePathCount = previousCount ; svg . remove ( ) ; document . dispatchEvent ( new Event ( 'visibilitychange' ) ) ; updateAuroraAnimationState ( ) ; result . aurora = { active , inactive , hidden } ;
}
if ( options . memory && globalThis . gc && performance . memory ) {
const board = rendered . get ( 'B0' ) ; BEND _PERF . clearEffectCaches ( ) ; cleanupGemEffects ( ) ; skipCompletionVisuals ( ) ; globalThis . gc ( ) ; await wait ( 80 ) ; const before = performance . memory . usedJSHeapSize ;
for ( let index = 0 ; index < 100 ; index ++ ) { const style = styles [ index % styles . length ] , duration = reactionDurationForStyle ( style ) , id = 'effect-memory-' + index , now = trustedNow ( ) ; applyRealtimeReaction ( { id , emoji , style , x : 0 , y : 0 , createdAt : now - duration * . 5 , expiresAt : now + duration * . 5 } ) ; reactionDirty = true ; drawReactionLayer ( ) ; realtimeReactions . delete ( id ) ; cancelReactionRenderScheduler ( true ) ; if ( board ) { playGemCollectionAnimation ( board , 100000 ) ; cleanupGemEffects ( ) ; completionEffect ( board , 1000 ) ; finishCompletionVisual ( board . id , false ) } }
reactionDirty = true ; drawReactionLayer ( ) ; cancelReactionRenderScheduler ( true ) ; cleanupGemEffects ( ) ; skipCompletionVisuals ( ) ; BEND _PERF . clearEffectCaches ( ) ; globalThis . gc ( ) ; await wait ( 120 ) ; globalThis . gc ( ) ; result . memory = { before , after : performance . memory . usedJSHeapSize , delta : performance . memory . usedJSHeapSize - before , active : realtimeReactions . size , gemBatches : activeGemBatches . size , completionVisuals : activeCompletionVisuals . size , effectNodes : document . querySelectorAll ( '.gem-particle,.completion-flash,.completion-burst' ) . length , cacheEntries : ( BEND _PERF . snapshot ( ) . gauges . reactionGlyphCacheEntries || 0 ) + ( BEND _PERF . snapshot ( ) . gauges . reactionStaticPathCacheEntries || 0 ) } ;
}
return result ;
} ) ( ) ` );
}
function validateEffectsAndCosmetics ( result , { mobile = false } = { } ) {
const cpuRate = result . cpuRate , normalSpeed = cpuRate === 1 , singleLimit = normalSpeed ? 6 : 40 , fourLimit = normalSpeed ? 12 : 80 , longTaskLimit = normalSpeed ? 50 : 500 , minFrames = normalSpeed ? 8 : 5 , minGaps = normalSpeed ? 7 : 4 ;
for ( const [ style , snapshot ] of Object . entries ( result . singles || { } ) ) {
const draw = timing ( snapshot , ` reactionStyle. ${ style } ` ) , gap = timing ( snapshot , 'reactionFrameGap' ) ;
assert ( draw . count >= minFrames && ( ! normalSpeed || draw . p95 <= singleLimit && draw . p99 <= 8 ) , ` ${ mobile ? 'mobile ' : '' } ${ style } / ${ cpuRate } x reaction work missed acceptance: ${ JSON . stringify ( { draw , singleLimit , minFrames } )} ` ) ;
if ( normalSpeed ) assert ( gap . count >= minGaps && gap . p50 <= 45 , ` ${ style } / ${ cpuRate } x single-effect median cadence missed acceptance: ${ JSON . stringify ( { gap , draw : timing ( snapshot , 'reactionFrame' ) , rates : snapshot . rates , counters : snapshot . counters } )} ` ) ;
assert ( timing ( snapshot , 'reactionFrame' ) . max < longTaskLimit , ` ${ style } / ${ cpuRate } x reaction callback exceeded its stress ceiling: ${ JSON . stringify ( timing ( snapshot , 'reactionFrame' ) ) } ` ) ;
}
const four = timing ( result . overlap4 , 'reactionFrame' ) , eight = timing ( result . overlap8 , 'reactionFrame' ) , fourGap = timing ( result . overlap4 , 'reactionFrameGap' ) ;
assert ( four . count >= minFrames && ( ! normalSpeed || four . p95 <= fourLimit && four . p99 <= 20 ) && four . max < longTaskLimit , ` ${ mobile ? 'mobile ' : '' } four-effect/ ${ cpuRate } x reaction missed its work budget: ${ JSON . stringify ( { four , fourGap , fourLimit , minFrames , rates : result . overlap4 . rates , counters : result . overlap4 . counters } )} ` ) ;
assert ( eight . count >= ( normalSpeed ? 6 : 4 ) && eight . max < longTaskLimit && ( result . overlap8 . gauges . peakVisibleReactions || 0 ) >= 8 , ` Eight-effect overload did not render every valid reaction: ${ JSON . stringify ( { eight , peakVisible : result . overlap8 . gauges . peakVisibleReactions , visible : result . overlap8 . gauges . visibleReactions } )} ` ) ;
for ( const snapshot of [ result . overlap4 , result . overlap8 ] ) {
assert ( ( snapshot . rates . reactionDeadlineTimerCallbacksPerSecond || 0 ) <= 37.5 , ` Reaction deadline timer exceeded the 37.5 callbacks/s short-window envelope: ${ snapshot . rates . reactionDeadlineTimerCallbacksPerSecond } ` ) ;
assert ( ( snapshot . rates . reactionDrawRafCallbacksPerSecond || 0 ) <= 37.5 , ` Reaction draw RAF exceeded the 37.5 callbacks/s short-window envelope: ${ snapshot . rates . reactionDrawRafCallbacksPerSecond } ` ) ;
}
if ( result . visual ? . length ) { const worst = result . visual . reduce ( ( a , b ) => a . ratio > b . ratio ? a : b ) , limit = result . visualGate ? . maximumChangedPixelRatio ? ? . 005 ; assert ( worst . ratio <= limit , ` Cached effect pixels changed beyond tolerance: ${ JSON . stringify ( { worst , gate : result . visualGate } )} ` ) }
if ( result . setup ) { assert ( timing ( result . setup . reaction , 'reactionPublish' ) . p95 <= 2 , ` Reaction publication exceeded 2 ms: ${ JSON . stringify ( timing ( result . setup . reaction , 'reactionPublish' ) ) } ` ) ; assert ( timing ( result . setup . gem , 'gemEffectSetup' ) . p95 <= 3 , ` Gem setup exceeded 3 ms: ${ JSON . stringify ( timing ( result . setup . gem , 'gemEffectSetup' ) ) } ` ) ; assert ( timing ( result . setup . completion , 'completionEffectSetup' ) . p95 <= 3 , ` Completion setup exceeded 3 ms: ${ JSON . stringify ( timing ( result . setup . completion , 'completionEffectSetup' ) ) } ` ) }
if ( result . inventory ) {
const render = timing ( result . inventory . renderSnapshot , 'inventoryRender' ) , initialPatch = timing ( result . inventory . renderSnapshot , 'inventoryPatch' ) , initial = render . count ? render : initialPatch , patch = timing ( result . inventory . patchSnapshot , 'inventoryPatch' ) ;
assert ( initial . count && initial . p95 <= 100 , ` Full cosmetic inventory render exceeded 100 ms: ${ JSON . stringify ( { render , initialPatch } )} ` ) ; assert ( patch . count >= 20 && patch . p95 <= 8 , ` Cosmetic equip patch exceeded 8 ms: ${ JSON . stringify ( { patch } )} ` ) ;
assert ( result . inventory . scrollAfter === result . inventory . scrollBefore , ` Cosmetic equip moved inventory scroll from ${ result . inventory . scrollBefore } to ${ result . inventory . scrollAfter } ` ) ; assert ( result . inventory . focusRetained , 'Cosmetic equip moved keyboard focus' ) ; assert ( result . inventory . nodesAfter === result . inventory . nodesBefore , 'Cosmetic equip rebuilt inventory nodes' ) ;
assert ( ( result . inventory . renderSnapshot . counters . longTasks || 0 ) === 0 && ( result . inventory . patchSnapshot . counters . longTasks || 0 ) === 0 , ` Full cosmetic inventory produced a long task: ${ JSON . stringify ( { render , patch , mounted : result . inventory . mounted } )} ` ) ;
}
if ( result . aurora ) { const active = timing ( result . aurora . active , 'auroraTick' ) ; assert ( active . p95 <= . 25 , ` Aurora tick exceeded 0.25 ms: ${ JSON . stringify ( active ) } ` ) ; const writes = result . aurora . active . counters . auroraColorWrites || 0 ; assert ( writes >= 2 && writes <= 3 , ` Aurora did not select one curated color every two seconds: ${ writes } ` ) ; for ( const [ name , snapshot ] of Object . entries ( { inactive : result . aurora . inactive , hidden : result . aurora . hidden } ) ) assert ( ! ( snapshot . counters . auroraColorWrites || 0 ) && ! timing ( snapshot , 'auroraTick' ) . count , ` Aurora performed work while ${ name } : ${ JSON . stringify ( snapshot ) } ` ) }
if ( result . memory ) assert ( result . memory . active === 0 && result . memory . gemBatches === 0 && result . memory . completionVisuals === 0 && result . memory . effectNodes === 0 && result . memory . cacheEntries === 0 && result . memory . delta <= 2 * 1024 * 1024 , ` Effect cleanup retained too much state: ${ JSON . stringify ( result . memory ) } ` ) ;
}
2026-07-30 15:13:53 +09:00
async function zoom ( client , deltaY , repetitions ) {
const center = await viewportCenter ( client ) ;
for ( let index = 0 ; index < repetitions ; index ++ ) {
await client . send ( 'Input.dispatchMouseEvent' , { type : 'mouseWheel' , x : center . x , y : center . y , deltaX : 0 , deltaY } ) ;
await sleep ( 22 ) ;
}
await sleep ( 300 ) ;
}
2026-08-01 16:06:14 +09:00
function timing ( snapshot , name ) { return snapshot . timings ? . [ name ] || { count : 0 , p50 : 0 , p95 : 0 , p99 : 0 , max : 0 } }
2026-07-30 15:13:53 +09:00
async function measureGameplaySimplificationBudgets ( client ) {
const result = await client . evaluate ( ` (async()=>{
const percentile = ( rows , p ) => { const ordered = [ ... rows ] . sort ( ( a , b ) => a - b ) ; return ordered [ Math . min ( ordered . length - 1 , Math . floor ( ordered . length * p ) ) ] || 0 } ;
const sample = ( operation , count = 2000 ) => { const rows = [ ] ; for ( let index = 0 ; index < count ; index ++ ) { const started = performance . now ( ) ; operation ( index ) ; rows . push ( performance . now ( ) - started ) } return { p95 : percentile ( rows , . 95 ) , max : Math . max ( ... rows ) } } ;
const snap = sample ( index => directionFromDelta ( CELL * ( index % 2 ? . 46 : . 6 ) , index % 2 ? 0 : CELL * . 55 ) ) ;
const pointerSamples = sample ( ( ) => pointerEventSamples ( { pointerId : 1 , pointerType : 'pen' , getCoalescedEvents : ( ) => [ { clientX : 10 , clientY : 20 } , { clientX : 20 , clientY : 20 } , { clientX : 20 , clientY : 10 } ] } ) ) ;
const rect = getMinimapRect ( ) , event = { clientX : rect . left + rect . width * . 5 , clientY : rect . top + rect . height * . 5 } ;
const minimap = sample ( ( ) => minimapWorldPoint ( event ) ) ;
const noise = sample ( ( ) => paintNoiseBackground ( false ) , 80 ) ;
const controls = {
origin : Boolean ( document . querySelector ( '#minimapOriginBtn' ) ) ,
random : Boolean ( document . querySelector ( '#minimapRandomBtn' ) ) ,
unsolved : Boolean ( document . querySelector ( '#minimapUnsolvedBtn' ) ) ,
current : Boolean ( document . querySelector ( '#minimapCurrentBtn' ) )
} ;
const originalScale = cam . scale ; cam . scale = OVERVIEW _ZOOM _THRESHOLD - . 001 ; const overviewBelow = inWorldOverview ( ) ;
cam . scale = OVERVIEW _ZOOM _THRESHOLD + . 001 ; const overviewAbove = inWorldOverview ( ) ; cam . scale = originalScale ; inWorldOverview ( ) ;
const active = rendered . get ( activeBoard ) ; let northHudGap = null ;
if ( active ) { positionBoardLabel ( active ) ; if ( active . label . dataset . side === 'N' ) { const placement = hudPlacementCandidates ( active . meta ) [ 0 ] , edgeY = PAD + placement . dy * UNIT ; northHudGap = edgeY - parseFloat ( active . label . style . top ) } }
let heartbeats = 0 ; const heartbeat = setInterval ( ( ) => heartbeats ++ , 0 ) , workerStarted = performance . now ( ) ;
const workerResult = await verifyPuzzleUniquenessAsync ( data . metas . B0 . puzzle , 2000 ) ;
const workerElapsed = performance . now ( ) - workerStarted ; clearInterval ( heartbeat ) ;
return {
snap , pointerSamples , minimap , noise , workerElapsed , heartbeats , workerStatus : workerResult . status ,
2026-07-31 12:54:46 +09:00
controls , overviewBelow , overviewAbove , northHudGap ,
2026-07-30 15:13:53 +09:00
cellHitCount : document . querySelectorAll ( '.cell-hit' ) . length ,
cellShapeCount : document . querySelectorAll ( '.board-card .cell-shape' ) . length ,
renderedBoardCount : rendered . size ,
visibleDetailedCount : [ ... visibleMetaIds ( ) ] . filter ( id => data . metas [ id ] ? . puzzle && rendered . has ( id ) ) . length ,
visiblePuzzleCount : [ ... visibleMetaIds ( ) ] . filter ( id => data . metas [ id ] ? . puzzle ) . length ,
fpsVisible : Boolean ( document . querySelector ( '#fpsCounter' ) ) ,
difficultyItems : STORE _ITEMS . filter ( item => item . id === 'level-min-10' || item . id === 'level-max-10' ) . length
} ;
} ) ( ) ` );
assert ( result . snap . p95 < 1 , ` Pointer snap p95 ${ result . snap . p95 . toFixed ( 3 ) } ms exceeded 1 ms ` ) ;
assert ( result . pointerSamples . p95 < 1 , ` Pointer-sample p95 ${ result . pointerSamples . p95 . toFixed ( 3 ) } ms exceeded 1 ms ` ) ;
assert ( result . minimap . p95 < 1 , ` Minimap conversion p95 ${ result . minimap . p95 . toFixed ( 3 ) } ms exceeded 1 ms ` ) ;
assert ( result . noise . p95 < 10 && result . noise . max < 20 , ` Noise update p95/max ${ result . noise . p95 . toFixed ( 3 ) } / ${ result . noise . max . toFixed ( 3 ) } ms exceeded the budget ` ) ;
assert ( result . controls . origin && result . controls . random && ! result . controls . unsolved && ! result . controls . current , 'Minimap teleport controls do not match origin + random' ) ;
assert ( result . overviewBelow && ! result . overviewAbove , 'World overview retained zoom hysteresis' ) ;
if ( result . northHudGap != null ) assert ( Math . abs ( result . northHudGap - 22 ) < . 01 , ` Top puzzle HUD gap is ${ result . northHudGap } , expected 22 world pixels ` ) ;
assert ( result . cellHitCount === 0 && result . cellShapeCount > 0 , 'Detailed boards still allocate one hit node per cell or lack compound cell paths' ) ;
assert ( result . visibleDetailedCount === result . visiblePuzzleCount , 'A visible valid board was hidden from detailed rendering' ) ;
assert ( result . fpsVisible && result . difficultyItems === 0 , 'FPS display is missing or retired difficulty items remain' ) ;
assert ( result . heartbeats > 0 && [ 'unique' , 'multiple' , 'unsolved' , 'timeout' , 'invalid' ] . includes ( result . workerStatus ) , 'Uniqueness verification did not yield to the main thread' ) ;
return result ;
}
function validateMeasurement ( result ) {
const { snapshot , cpuRate , profile } = result , cadenceSnapshot = result . pickupCadence || snapshot ,
drag = timing ( snapshot , 'processBoardDragFrame' ) ,
probeDrag = timing ( cadenceSnapshot , 'processBoardDragFrame' ) ,
camera = timing ( snapshot , 'commitCameraInteraction' ) ,
dragGap = timing ( cadenceSnapshot , 'pickupVisualFrameGap' ) , cameraGap = timing ( snapshot , 'cameraFrameGap' ) ,
dragAge = timing ( cadenceSnapshot , 'pickupVisualInputAge' ) , cameraAge = timing ( snapshot , 'cameraInputAge' ) ,
minimap = timing ( snapshot , 'drawMinimap' ) , ensure = timing ( snapshot , 'ensureBoards' ) , save = timing ( snapshot , 'persistDirtyToDb' ) ,
overview = timing ( snapshot , 'drawWorldOverview' ) , mirrorChunk = timing ( snapshot , 'mirrorChunkWrite' ) ,
2026-08-01 16:06:14 +09:00
dragLimit = cpuRate === 1 ? 8 : 16 , functionalDragLimit = cpuRate === 1 ? 18 : 24 , minimapLimit = cpuRate === 1 ? 20 : 33 , lodLimit = cpuRate === 1 ? 40 : 100 ;
2026-07-30 15:13:53 +09:00
assert ( drag . count + probeDrag . count >= 5 , ` ${ profile } / ${ cpuRate } x captured only ${ drag . count } functional and ${ probeDrag . count } continuous-input drag frames: ${ JSON . stringify ( result . pickupProbe ) } ` ) ;
assert ( camera . count >= 5 , ` ${ profile } / ${ cpuRate } x did not capture frame-coalesced camera work ` ) ;
assert ( minimap . count >= 2 , ` ${ profile } / ${ cpuRate } x did not capture minimap work ` ) ;
assert ( ensure . count >= 2 , ` ${ profile } / ${ cpuRate } x did not capture LOD work ` ) ;
assert ( save . count >= 1 , ` ${ profile } / ${ cpuRate } x did not capture an autosave ` ) ;
assert ( overview . count >= 1 , ` ${ profile } / ${ cpuRate } x did not capture overview rendering ` ) ;
2026-08-01 16:06:14 +09:00
assert ( cameraGap . count >= 4 && ( cpuRate !== 1 || dragGap . count >= 4 ) , ` ${ profile } / ${ cpuRate } x did not capture enough real interaction cadence samples (pickup ${ dragGap . count } , camera ${ cameraGap . count } ) ` ) ;
2026-07-30 15:13:53 +09:00
if ( cpuRate === 1 ) {
const approved = result . claimApproved , denied = result . claimDenied ;
assert ( approved ? . previewWithinFrame && approved . tracksLatest && approved . modelUntouched && approved . approved && approved . noJump && approved . fullBoardRenders === 0 , ` ${ profile } claim approval preview/commit failed: ${ JSON . stringify ( approved ) } ` ) ;
assert ( denied ? . previewWithinFrame && denied . tracksLatest && denied . modelUntouched && ! denied . approved && denied . noJump && denied . fullBoardRenders === 0 , ` ${ profile } claim denial rollback failed: ${ JSON . stringify ( denied ) } ` ) ;
assert ( result . edgePan ? . cameraMoved && result . edgePan . dragFrames >= 2 , ` ${ profile } pickup edge-pan did not retain continuous drag frames: ${ JSON . stringify ( result . edgePan ) } ` ) ;
}
assert ( result . pinch ? . changed , ` ${ profile } / ${ cpuRate } x pinch zoom did not change camera scale: ${ JSON . stringify ( result . pinch ) } ` ) ;
assert ( result . overviewPathsObserved === 0 , ` ${ profile } / ${ cpuRate } x far overview rendered route lines ` ) ;
assert ( ( snapshot . counters . overviewCacheBuilds || 0 ) > result . overviewBuildBaseline , ` ${ profile } / ${ cpuRate } x long overview pan did not rebuild the exhausted cache after settlement ` ) ;
2026-07-31 12:54:46 +09:00
assert ( ( snapshot . counters . overviewBuildsDuringInteraction || 0 ) >= 1 && ( snapshot . counters . overviewBuildsDuringInteraction || 0 ) <= 60 , ` ${ profile } / ${ cpuRate } x long overview pan did not use a bounded in-gesture cache refresh ` ) ;
2026-07-30 15:13:53 +09:00
if ( cpuRate === 1 ) {
const cadenceHot = Object . entries ( cadenceSnapshot . timings || { } ) . filter ( ( [ , value ] ) => value . max > 1 ) . sort ( ( a , b ) => b [ 1 ] . max - a [ 1 ] . max ) . slice ( 0 , 12 ) ;
2026-08-01 16:06:14 +09:00
assert ( dragGap . p50 <= 40 && dragGap . p95 <= 50 , ` ${ profile } pickup visual median/p95 gap ${ dragGap . p50 . toFixed ( 2 ) } / ${ dragGap . p95 . toFixed ( 2 ) } ms exceeded the capped 30 FPS cadence budget; active timings ${ JSON . stringify ( cadenceHot ) } ` ) ;
assert ( cameraGap . p50 <= 40 , ` ${ profile } camera median gap ${ cameraGap . p50 . toFixed ( 2 ) } ms exceeded the capped 30 FPS cadence budget ` ) ;
assert ( dragAge . p95 < 45 , ` ${ profile } pickup input age ${ dragAge . p95 . toFixed ( 2 ) } ms exceeded 45 ms ` ) ;
assert ( cameraAge . p95 < 45 , ` ${ profile } camera input age ${ cameraAge . p95 . toFixed ( 2 ) } ms exceeded 45 ms ` ) ;
2026-07-30 15:13:53 +09:00
}
2026-07-31 12:54:46 +09:00
assert ( drag . p95 <= functionalDragLimit && probeDrag . p95 <= dragLimit , ` ${ profile } / ${ cpuRate } x drag work exceeded acceptance (functional ${ drag . p95 . toFixed ( 2 ) } / ${ functionalDragLimit } ms, cadence ${ probeDrag . p95 . toFixed ( 2 ) } / ${ dragLimit } ms, model ${ timing ( cadenceSnapshot , 'pickupModelWork' ) . p95 . toFixed ( 2 ) } , visual ${ timing ( cadenceSnapshot , 'pickupVisualWork' ) . p95 . toFixed ( 2 ) } , render ${ timing ( cadenceSnapshot , 'renderDragFrame' ) . p95 . toFixed ( 2 ) } ) ` ) ;
2026-07-30 15:13:53 +09:00
assert ( camera . p95 <= dragLimit , ` ${ profile } / ${ cpuRate } x camera p95 ${ camera . p95 . toFixed ( 2 ) } ms exceeded acceptance ` ) ;
assert ( minimap . p95 <= minimapLimit , ` ${ profile } / ${ cpuRate } x minimap p95 ${ minimap . p95 . toFixed ( 2 ) } ms exceeded acceptance ` ) ;
assert ( ensure . p95 <= lodLimit , ` ${ profile } / ${ cpuRate } x LOD p95 ${ ensure . p95 . toFixed ( 2 ) } ms exceeded acceptance ` ) ;
if ( mirrorChunk . count ) assert ( mirrorChunk . max < 50 , ` ${ profile } / ${ cpuRate } x mirror chunk write ${ mirrorChunk . max . toFixed ( 2 ) } ms became a long task ` ) ;
if ( cpuRate === 1 ) {
const modelWork = timing ( snapshot , 'pickupModelWork' ) , visualWork = timing ( snapshot , 'pickupVisualWork' ) , dragRender = timing ( snapshot , 'renderDragFrame' ) ;
2026-07-31 12:54:46 +09:00
assert ( result . pickupProbeLongTasks === 0 && result . pickupLongTasks === 0 && result . panLongTasks === 0 , ` ${ profile } recorded a 50 ms long task during continuous pickup ( ${ result . pickupProbeLongTasks } ), real pickup ( ${ result . pickupLongTasks } ), or the ten-second pan ( ${ result . panLongTasks } ); last ${ snapshot . gauges . lastInteractionLongTaskMs || 0 } ms; phases preview ${ timing ( snapshot , 'pickupPointerDownPreview' ) . max . toFixed ( 1 ) } , commit ${ timing ( snapshot , 'pickupPointerDownCommit' ) . max . toFixed ( 1 ) } , finish ${ timing ( snapshot , 'pickupPointerFinish' ) . max . toFixed ( 1 ) } , drag ${ drag . max . toFixed ( 1 ) } , model ${ modelWork . max . toFixed ( 1 ) } [prepare ${ timing ( snapshot , 'pickupModelPrepare' ) . max . toFixed ( 1 ) } , topology ${ timing ( snapshot , 'pickupModelTopology' ) . max . toFixed ( 1 ) } , traversal ${ timing ( snapshot , 'pickupModelTraversal' ) . max . toFixed ( 1 ) } , cell ${ timing ( snapshot , 'pickupModelCell' ) . max . toFixed ( 1 ) } ], visual ${ visualWork . max . toFixed ( 1 ) } , render ${ dragRender . max . toFixed ( 1 ) } ; ${ snapshot . gauges . lastInteractionLoafScripts || 'no LoAF attribution' } ` ) ;
2026-07-30 15:13:53 +09:00
}
2026-07-31 12:54:46 +09:00
for ( const name of [ 'minimapDrawsDuringInteraction' , 'lodPassesDuringInteraction' , 'persistenceDuringInteraction' , 'worldRefreshesDuringInteraction' ] )
2026-07-30 15:13:53 +09:00
assert ( ( snapshot . counters [ name ] || 0 ) === 0 && ( cadenceSnapshot . counters [ name ] || 0 ) === 0 , ` ${ profile } / ${ cpuRate } x ran ${ name } during an active gesture ` ) ;
assert ( snapshot . gauges . renderedBoards >= snapshot . gauges . visibleUnsolvedBoards , ` ${ profile } / ${ cpuRate } x omitted a visible unsolved board from detailed rendering ` ) ;
2026-08-01 16:06:14 +09:00
assert ( ( cadenceSnapshot . rates . dragFramesPerSecond || 0 ) <= 35 , ` ${ profile } / ${ cpuRate } x pickup presentation exceeded the 30 FPS ceiling ( ${ ( cadenceSnapshot . rates . dragFramesPerSecond || 0 ) . toFixed ( 1 ) } FPS) ` ) ;
2026-07-30 15:13:53 +09:00
assert ( snapshot . gauges . domNodes < 18000 , ` ${ profile } / ${ cpuRate } x DOM size is not viewport-bounded ` ) ;
}
async function measureScenario ( client , starter , profile , cpuRate ) {
await client . send ( 'Emulation.setCPUThrottlingRate' , { rate : 1 } ) ;
await client . evaluate ( "if(typeof lifecyclePersistenceSuppressed!=='undefined')lifecyclePersistenceSuppressed=true;true" ) ;
const solution = await populateWorld ( client , starter , profile ) ;
await reload ( client ) ;
const pristineOriginState = await client . evaluate ( "deepClone(metaState('B0'))" ) ;
await client . send ( 'Emulation.setCPUThrottlingRate' , { rate : cpuRate } ) ;
const claimApproved = cpuRate === 1 ? await claimLatencyProbe ( client , solution [ 0 ] , true ) : null ;
if ( cpuRate === 1 ) await restoreOriginState ( client , pristineOriginState ) ;
const claimDenied = cpuRate === 1 ? await claimLatencyProbe ( client , solution [ 0 ] , false ) : null ;
if ( cpuRate === 1 ) await restoreOriginState ( client , pristineOriginState ) ;
const edgePan = cpuRate === 1 ? await pickupEdgePanProbe ( client , solution [ 0 ] ) : null ;
if ( cpuRate === 1 ) await restoreOriginState ( client , pristineOriginState ) ;
if ( cpuRate === 1 ) await sleep ( 300 ) ;
await prepareInteractionMeasurement ( client ) ;
const pickupProbe = await pickupCadenceProbe ( client , solution [ 0 ] , cpuRate === 1 ? 180 : 120 , cpuRate === 1 ? 8 : 16 ) ;
const pickupCadence = pickupProbe . snapshot ; delete pickupProbe . snapshot ;
const pickupProbeLongTasks = pickupCadence ? . counters . interactionLongTasks || 0 ;
await restoreOriginState ( client , pristineOriginState ) ;
await sleep ( 300 ) ;
await prepareInteractionMeasurement ( client ) ;
const preSolveState = await client . evaluate ( "({pathCount:metaState('B0').paths.length,solved:metaState('B0').solved,drawing:Boolean(rendered.get('B0')?.drawing),pending:Boolean(rendered.get('B0')?.pendingClaimPointer)})" ) ;
assert ( preSolveState . pathCount === 0 && ! preSolveState . solved && ! preSolveState . drawing && ! preSolveState . pending , ` ${ profile . name } / ${ cpuRate } x pickup probes did not restore a pristine origin board: ${ JSON . stringify ( preSolveState ) } ` ) ;
await solveOrigin ( client , solution , cpuRate ) ;
try { await waitFor ( ( ) => client . evaluate ( "metaState('B0').solved===true" ) , { timeout : 5000 * Math . max ( 1 , cpuRate ) , label : ` ${ profile . name } / ${ cpuRate } x solved origin ` } ) }
2026-07-31 12:54:46 +09:00
catch ( error ) { const diagnostic = await client . evaluate ( "(()=>{const state=metaState('B0'),board=rendered.get('B0');return{solved:state.solved,paths:state.paths.map(path=>({startGate:path.startGate,endGate:path.endGate,openGate:path.openGate,cells:path.cells})),drawing:board?.drawing?{pathIndex:board.drawing.pathIndex,catchupPending:board.drawing.catchupPending}:null,drag:board?.dragScheduler?.inspect?.()||null,logicalTrace:board?.logicalPointerCellTrace||[],flushTrace:board?.lastFlushPointerCells||[]}})()" ) ; throw new Error ( ` ${ error . message } : ${ JSON . stringify ( diagnostic ) } ` ) }
2026-07-30 15:13:53 +09:00
await sleep ( 850 ) ;
const pickupLongTasks = await client . evaluate ( 'BEND_PERF.snapshot().counters.interactionLongTasks||0' ) ;
await client . evaluate ( 'finishCompletionVisual("B0",true);centerMeta(data.metas.B0);true' ) ; await sleep ( 350 ) ;
const leftStart = await safePanPoint ( client , '.board-card[data-id="B0"]' ) ,
leftProbe = await client . evaluate ( ` (()=>{const target=document.elementFromPoint( ${ leftStart . x } , ${ leftStart . y } );return{tag:target?.tagName||null,classes:target?.getAttribute?.('class')||null,board:target?.closest?.('.board-card')?.dataset?.id||null,allowed:leftFieldPanAllowed({button:0,target})}})() ` ) ;
const beforeLeftPan = await client . evaluate ( '({x:cam.x,y:cam.y,solved:metaState("B0").solved})' ) ;
assert ( beforeLeftPan . solved && leftProbe . allowed , ` ${ profile . name } / ${ cpuRate } x solved-board left drag is not eligible for panning: ${ JSON . stringify ( { leftStart , leftProbe , beforeLeftPan } )} ` ) ;
2026-08-01 16:06:14 +09:00
await client . evaluate ( "(()=>{const r=document.querySelector('#viewport').getBoundingClientRect(),p=worldUnitAtClient(r.left+r.width/2,r.top+r.height/2),now=trustedNow();applyRealtimeReaction({id:'benchmark-overview-effect',emoji:REACTION_EMOJIS[0],style:'orbit',x:p[0],y:p[1],createdAt:now,expiresAt:now+20000});return true})()" ) ;
2026-07-30 15:13:53 +09:00
await zoom ( client , 180 , 15 ) ;
const overviewPathsObserved = await client . evaluate ( 'drawWorldOverview();BEND_PERF.snapshot().gauges.overviewPaths||0' ) ;
const overviewBuildBaseline = await client . evaluate ( 'BEND_PERF.snapshot().counters.overviewCacheBuilds||0' ) ;
const panLongTaskBaseline = await client . evaluate ( 'BEND_PERF.snapshot().counters.interactionLongTasks||0' ) ;
await pan ( client ) ;
await panCadenceProbe ( client , cpuRate === 1 ? 600 : 60 ) ;
await waitFor ( ( ) => client . evaluate ( ` (()=>{if(!inWorldOverview()||!overviewCache)return false;const[centerX,centerY]=cameraCenterInChunks();return!overviewDirty&&Math.abs(centerX-overviewCache.anchorX)*overviewCache.unit<=overviewCache.overscan*.82&&Math.abs(centerY-overviewCache.anchorY)*overviewCache.unit<=overviewCache.overscan*.82})() ` ) , { timeout : 10000 , label : 'settled overview cache rebuild' } ) ;
const pinch = await pinchZoomProbe ( client ) ;
await sleep ( 80 ) ;
2026-08-01 16:06:14 +09:00
const panLongTasks = ( await client . evaluate ( 'BEND_PERF.snapshot().counters.interactionLongTasks||0' ) ) - panLongTaskBaseline ; await client . evaluate ( "realtimeReactions.delete('benchmark-overview-effect');true" ) ;
2026-07-30 15:13:53 +09:00
await zoom ( client , - 180 , 15 ) ;
await sleep ( 650 ) ;
try { await waitFor ( ( ) => client . evaluate ( "!hasPendingPersistence()" ) , { timeout : 20000 , label : 'durable persistence drain' } ) }
catch ( error ) {
const diagnostic = await client . evaluate ( ` ({globalDirty,dirtyMetaIds:[...dirtyMetaIds],dirtyStateIds:[...dirtyStateIds],deletedBoardIds:[...deletedBoardIds],cloudOutboxDeleteKeys:[...cloudOutboxDeleteKeys],saveTimer:Boolean(saveTimer),status:saveStatusEl?.dataset?.state||null,message:statusMessage?.textContent||null,storageFormat:activeStorageFormat,epoch:data?.worldEpoch}) ` ) ;
throw new Error ( ` ${ error . message } : ${ JSON . stringify ( diagnostic ) } ` ) ;
}
await sleep ( 500 ) ;
const snapshot = await client . evaluate ( 'BEND_PERF.snapshot()' ) ;
2026-08-01 16:06:14 +09:00
return { profile : profile . name , boards : profile . boards , cpuRate , snapshot , pickupCadence , pickupProbe , overviewPathsObserved , overviewBuildBaseline , pickupProbeLongTasks , pickupLongTasks , panLongTasks , claimApproved , claimDenied , edgePan , pinch } ;
}
function profileSummaryRow ( result ) {
return {
profile : result . profile , boards : result . boards , cpuRate : result . cpuRate ,
dragP95 : timing ( result . snapshot , 'processBoardDragFrame' ) . p95 ,
cameraP95 : timing ( result . snapshot , 'commitCameraInteraction' ) . p95 ,
dragGapP95 : timing ( result . pickupCadence || result . snapshot , 'pickupVisualFrameGap' ) . p95 ,
dragInputAgeP95 : timing ( result . pickupCadence || result . snapshot , 'pickupVisualInputAge' ) . p95 ,
cameraGapP50 : timing ( result . snapshot , 'cameraFrameGap' ) . p50 ,
cameraInputAgeP95 : timing ( result . snapshot , 'cameraInputAge' ) . p95 ,
minimapP95 : timing ( result . snapshot , 'drawMinimap' ) . p95 ,
lodP95 : timing ( result . snapshot , 'ensureBoards' ) . p95 ,
saveP95 : timing ( result . snapshot , 'persistDirtyToDb' ) . p95 ,
overviewP95 : timing ( result . snapshot , 'drawWorldOverview' ) . p95 ,
renderedBoards : result . snapshot . gauges . renderedBoards ,
staticBoards : result . snapshot . gauges . staticBoards ,
domNodes : result . snapshot . gauges . domNodes ,
longTasks : result . snapshot . counters . longTasks || 0
} ;
2026-07-30 15:13:53 +09:00
}
async function main ( runProfiles = profiles , cpuRates = [ 1 , 4 ] ) {
if ( ! fs . existsSync ( edgePath ) ) throw new Error ( ` Microsoft Edge was not found at ${ edgePath } ` ) ;
const server = await startStaticServer ( ) ;
const edge = spawn ( edgePath , [
'--headless' , '--no-first-run' , '--no-sandbox' , '--force-device-scale-factor=1' , '--js-flags=--expose-gc' , '--disable-dev-shm-usage' , '--disable-extensions' , '--disable-background-networking' , '--disable-background-timer-throttling' , '--disable-renderer-backgrounding' , '--disable-backgrounding-occluded-windows' , '--disable-crash-reporter' , '--disable-breakpad' , '--disable-component-update' , '--disable-features=OptimizationGuideModelDownloading,OnDeviceModelService,PromptApiForGeminiNano' ,
` --remote-debugging-port= ${ debuggingPort } ` , '--remote-allow-origins=*' , ` --user-data-dir= ${ profilePath } ` ,
` --host-resolver-rules=MAP ${ benchmarkHost } 127.0.0.1 ` , '--window-size=1440,1000' , 'about:blank'
] , { stdio : 'ignore' , windowsHide : true } ) ;
2026-08-01 16:06:14 +09:00
const report = { capturedAt : new Date ( ) . toISOString ( ) , browserPath : edgePath , benchmarkHost , status : 'running' , effects : { } , profiles : [ ] } ;
2026-07-30 15:13:53 +09:00
let client = null ;
try {
const target = await endpoint ( ) ; client = new CdpClient ( target . webSocketDebuggerUrl ) ; await client . connect ( ) ;
await client . send ( 'Page.enable' ) ; await client . send ( 'Runtime.enable' ) ;
await client . send ( 'Page.navigate' , { url : ` http:// ${ benchmarkHost } : ${ serverPort } / ` } ) ;
if ( startupOnly ) {
await sleep ( 12000 ) ;
const state = await client . evaluate ( "({ready:document.body?.dataset?.ready||null,version:document.querySelector('.brand small')?.textContent||null,boards:typeof data!=='undefined'?Object.keys(data.metas).length:null,origin:typeof data!=='undefined'&&Boolean(data.metas?.B0?.puzzle),worldGeneration:typeof data!=='undefined'?data.worldGeneration:null,turnFont:getComputedStyle(document.querySelector('.board-svg text')||document.body).fontFamily,status:document.querySelector('#statusMessage')?.textContent||'',text:document.body?.innerText?.slice(0,300)||''})" ) ;
2026-08-01 16:06:14 +09:00
assert ( state . ready === 'true' && state . version === 'v47.87' && state . boards === 1 && state . origin && state . worldGeneration === 'linkfield-single-world-20260801' , ` Real-browser startup state is incomplete: ${ JSON . stringify ( state ) } ` ) ;
2026-07-30 15:13:53 +09:00
assert ( /DotGothic16|Press Start 2P|MS Gothic|monospace/i . test ( state . turnFont ) , 'Dot-styled game font is not active in the browser' ) ;
console . log ( ` Real-browser startup passed: ${ JSON . stringify ( state ) } ` ) ; return ;
}
await ready ( client ) ; await sleep ( 1000 ) ;
2026-08-01 16:06:14 +09:00
const displayCadence = await measureDisplayCadence ( client ) ; report . displayCadence = displayCadence ;
2026-07-30 15:13:53 +09:00
assert ( displayCadence . count >= 60 && displayCadence . p50 <= 20 , ` Headless display baseline is not 60 Hz: ${ JSON . stringify ( displayCadence ) } ` ) ;
2026-08-01 16:06:14 +09:00
const gameplayBudgets = await measureGameplaySimplificationBudgets ( client ) ; report . gameplayBudgets = gameplayBudgets ;
const cursorModes = await measureCursorModes ( client ) ; report . cursorModes = cursorModes ;
2026-07-31 12:54:46 +09:00
assert ( cursorModes . defaultMode === 'default' && cursorModes . emojiMode === 'dom' && cursorModes . flagMode === 'dom' && cursorModes . domMode === 'dom' && cursorModes . visibleBeforeDrag && ! cursorModes . visibleDuringDrag && ! cursorModes . movedDuringDrag , ` Cursor mode runtime probe failed: ${ JSON . stringify ( cursorModes ) } ` ) ;
2026-08-01 16:06:14 +09:00
const cursorCadence = await measureCursorCadence ( client ) ; report . cursorCadence = cursorCadence ;
2026-07-30 15:13:53 +09:00
console . log ( ` Display cadence | median ${ displayCadence . p50 . toFixed ( 2 ) } ms | p95 ${ displayCadence . p95 . toFixed ( 2 ) } ms ` ) ;
console . log ( ` Gameplay budgets | snap ${ gameplayBudgets . snap . p95 . toFixed ( 3 ) } ms | pointer samples ${ gameplayBudgets . pointerSamples . p95 . toFixed ( 3 ) } ms | minimap ${ gameplayBudgets . minimap . p95 . toFixed ( 3 ) } ms | noise max ${ gameplayBudgets . noise . max . toFixed ( 3 ) } ms | worker ${ gameplayBudgets . workerElapsed . toFixed ( 1 ) } ms ` ) ;
console . log ( ` Cursor cadence | median gap ${ timing ( cursorCadence , 'cursorFrameGap' ) . p50 . toFixed ( 2 ) } ms | input p95 ${ timing ( cursorCadence , 'cursorInputAge' ) . p95 . toFixed ( 2 ) } ms ` ) ;
2026-08-01 16:06:14 +09:00
const effects1x = await measureEffectsAndCosmetics ( client , { cpuRate : 1 , visual : true , inventory : true , aurora : true , memory : true , singles : true } ) ; report . effects . desktop1x = effects1x ; validateEffectsAndCosmetics ( effects1x ) ;
const effects4x = await measureEffectsAndCosmetics ( client , { cpuRate : 4 , visual : false , inventory : false , aurora : false , memory : false , singles : true } ) ; report . effects . desktop4x = effects4x ; validateEffectsAndCosmetics ( effects4x ) ;
await client . send ( 'Emulation.setDeviceMetricsOverride' , { width : 390 , height : 844 , deviceScaleFactor : 1 , mobile : true , screenWidth : 390 , screenHeight : 844 } ) ; await sleep ( 220 ) ;
const effectsMobile = await measureEffectsAndCosmetics ( client , { cpuRate : 1 , visual : false , inventory : false , aurora : false , memory : false , singles : false } ) ; report . effects . mobile = effectsMobile ; validateEffectsAndCosmetics ( effectsMobile , { mobile : true } ) ;
await client . send ( 'Emulation.clearDeviceMetricsOverride' ) ; await client . send ( 'Emulation.setCPUThrottlingRate' , { rate : 1 } ) ; await sleep ( 220 ) ;
console . log ( ` Effects | single firework ${ timing ( effects1x . singles . firework , 'reactionStyle.firework' ) . p95 . toFixed ( 2 ) } ms | overlap4 ${ timing ( effects1x . overlap4 , 'reactionFrame' ) . p95 . toFixed ( 2 ) } ms | overlap8 ${ timing ( effects1x . overlap8 , 'reactionFrame' ) . p95 . toFixed ( 2 ) } ms | visual max ${ ( Math . max ( ... effects1x . visual . map ( row => row . ratio ) ) * 100 ) . toFixed ( 3 ) } % ` ) ;
console . log ( ` EFFECT_BENCHMARK_JSON= ${ JSON . stringify ( { desktop1x : { single : Object . fromEntries ( Object . entries ( effects1x . singles ) . map ( ( [ style , snapshot ] ) => [ style , timing ( snapshot , ` reactionStyle. ${ style } ` ) ] ) ) , overlap4 : timing ( effects1x . overlap4 , 'reactionFrame' ) , overlap8 : timing ( effects1x . overlap8 , 'reactionFrame' ) , inventory : effects1x . inventory , memory : effects1x . memory , aurora : timing ( effects1x . aurora . active , 'auroraTick' ) , setup : effects1x . setup } ,desktop4x:{overlap4:timing(effects4x.overlap4,'reactionFrame'),overlap8:timing(effects4x.overlap8,'reactionFrame')},mobile:{overlap4:timing(effectsMobile.overlap4,'reactionFrame'),overlap8:timing(effectsMobile.overlap8,'reactionFrame')}})} ` ) ;
2026-07-30 15:13:53 +09:00
const starter = await readStarterRows ( client ) , results = [ ] ;
for ( const profile of runProfiles ) for ( const cpuRate of cpuRates ) {
2026-08-01 16:06:14 +09:00
const result = await measureScenario ( client , starter , profile , cpuRate ) ; results . push ( result ) ; report . profiles = results . map ( profileSummaryRow ) ; validateMeasurement ( result ) ;
2026-07-30 15:13:53 +09:00
const drag = timing ( result . snapshot , 'processBoardDragFrame' ) , camera = timing ( result . snapshot , 'commitCameraInteraction' ) , minimap = timing ( result . snapshot , 'drawMinimap' ) ,
ensure = timing ( result . snapshot , 'ensureBoards' ) , save = timing ( result . snapshot , 'persistDirtyToDb' ) ;
console . log ( ` ${ profile . name . padEnd ( 6 ) } ${ cpuRate } x CPU | drag p95 ${ drag . p95 . toFixed ( 2 ) } ms | camera ${ camera . p95 . toFixed ( 2 ) } ms | minimap ${ minimap . p95 . toFixed ( 2 ) } ms | LOD ${ ensure . p95 . toFixed ( 2 ) } ms | save ${ save . p95 . toFixed ( 2 ) } ms | DOM ${ result . snapshot . gauges . domNodes } ` ) ;
}
2026-08-01 16:06:14 +09:00
const profileSummary = results . map ( profileSummaryRow ) ; report . profiles = profileSummary ; console . log ( ` BROWSER_BENCHMARK_JSON= ${ JSON . stringify ( profileSummary ) } ` ) ;
report . status = 'passed' ; writeBenchmarkReport ( report ) ; console . log ( ` Browser benchmark report: ${ benchmarkOutputPath } ` ) ;
2026-07-30 15:13:53 +09:00
console . log ( 'Real-browser performance benchmark passed' ) ;
2026-08-01 16:06:14 +09:00
} catch ( error ) {
report . status = 'failed' ; report . failure = { name : error ? . name || 'Error' , message : error ? . message || String ( error ) , stack : error ? . stack || '' } ;
try { writeBenchmarkReport ( report ) ; console . error ( ` Browser benchmark failure report: ${ benchmarkOutputPath } ` ) } catch ( reportError ) { console . error ( ` Browser benchmark report write failed: ${ reportError . message } ` ) }
throw error ;
2026-07-30 15:13:53 +09:00
} finally {
client ? . close ( ) ;
stopBrowserTree ( edge ) ;
server . closeAllConnections ? . ( ) ; await new Promise ( resolve => server . close ( ( ) => resolve ( ) ) ) ;
for ( let attempt = 0 ; attempt < 8 ; attempt ++ ) {
try { fs . rmSync ( temporaryRoot , { recursive : true , force : true } ) ; break }
catch ( error ) { if ( attempt === 7 ) console . warn ( ` Benchmark cleanup deferred: ${ error . message } ` ) ; await sleep ( 150 ) }
}
}
}
module . exports = { main , allProfiles } ;
if ( require . main === module ) main ( ) . then ( ( ) => process . exit ( 0 ) ) . catch ( error => { console . error ( error ) ; process . exit ( 1 ) } ) ;