bend_puzzle/test/browser-performance-benchmark.js
33333-33333 9d70afb4cc
Some checks failed
BEND FIELD CI / release (push) Has been cancelled
BEND FIELD CI / production-bridge (push) Has been cancelled
t
2026-08-01 22:31:04 +09:00

723 lines
73 KiB
JavaScript

'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';
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`)}
const temporaryRoot=fs.mkdtempSync(path.join(os.tmpdir(),'bend-field-browser-benchmark-'));
const profilePath=path.join(temporaryRoot,'edge-profile');
const worldDbName='bend-field:v30:linkfield-single-world-20260801:world';
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 publicDir=path.join(temporaryRoot,'public'),dataRoot=path.join(temporaryRoot,'server-data');
process.env.HOST='127.0.0.1';process.env.PORT='0';process.env.LINK_FIELD_TEST_DATA_ROOT=dataRoot;process.env.LINK_FIELD_PUBLIC_DIR=publicDir;process.env.LINK_FIELD_APACHE_BRIDGE='0';
const service=require('../scripts/service-control');await service.deployPublicFiles(publicDir);
const application=require('../server'),result=await application.main(),server=result.server;serverPort=result.port;server._linkfieldClose=()=>application.closeApplication(result);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});
applyRealtimeReaction({id:'benchmark-reaction',emoji:REACTION_EMOJIS[0],style:'firework',x:worldX,y:worldY,createdAt:reactionNow,expiresAt:reactionNow+Math.max(3400,${steps}*25)});
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;
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)}};
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){
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}"]`),
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(()=>{});
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})()`);
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'))");
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})()`);
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);
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})()`);
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();
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)};
return{left:rect.left+40,top:rect.top+40,width:Math.max(120,rect.width-80),height:Math.max(120,rect.height-80)};
})()`);
try{
const dispatches=[];
for(let index=0;index<steps;index++){
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}));
await sleep(8);
}
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');
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)}`);
return snapshot;
}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")}
}
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)}`);
}
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);
}
function timing(snapshot,name){return snapshot.timings?.[name]||{count:0,p50:0,p95:0,p99:0,max:0}}
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,
controls,overviewBelow,overviewAbove,northHudGap,
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'),
dragLimit=cpuRate===1?8:16,functionalDragLimit=cpuRate===1?18:24,minimapLimit=cpuRate===1?20:33,lodLimit=cpuRate===1?40:100;
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`);
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})`);
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`);
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`);
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);
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`);
}
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)})`);
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');
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'}`);
}
for(const name of ['minimapDrawsDuringInteraction','lodPassesDuringInteraction','persistenceDuringInteraction','worldRefreshesDuringInteraction'])
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`);
assert((cadenceSnapshot.rates.dragFramesPerSecond||0)<=35,`${profile}/${cpuRate}x pickup presentation exceeded the 30 FPS ceiling (${(cadenceSnapshot.rates.dragFramesPerSecond||0).toFixed(1)} FPS)`);
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`})}
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)}`)}
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})}`);
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})()");
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);
const panLongTasks=(await client.evaluate('BEND_PERF.snapshot().counters.interactionLongTasks||0'))-panLongTaskBaseline;await client.evaluate("realtimeReactions.delete('benchmark-overview-effect');true");
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()');
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
};
}
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});
const report={capturedAt:new Date().toISOString(),browserPath:edgePath,benchmarkHost,status:'running',effects:{},profiles:[]};
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)||''})");
assert(state.ready==='true'&&state.version==='v48.0'&&state.boards===1&&state.origin&&state.worldGeneration==='linkfield-single-world-20260801',`Real-browser startup state is incomplete: ${JSON.stringify(state)}`);
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);
const displayCadence=await measureDisplayCadence(client);report.displayCadence=displayCadence;
assert(displayCadence.count>=60&&displayCadence.p50<=20,`Headless display baseline is not 60 Hz: ${JSON.stringify(displayCadence)}`);
const gameplayBudgets=await measureGameplaySimplificationBudgets(client);report.gameplayBudgets=gameplayBudgets;
const cursorModes=await measureCursorModes(client);report.cursorModes=cursorModes;
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)}`);
const cursorCadence=await measureCursorCadence(client);report.cursorCadence=cursorCadence;
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`);
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')}})}`);
const starter=await readStarterRows(client),results=[];
for(const profile of runProfiles)for(const cpuRate of cpuRates){
const result=await measureScenario(client,starter,profile,cpuRate);results.push(result);report.profiles=results.map(profileSummaryRow);validateMeasurement(result);
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}`);
}
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}`);
console.log('Real-browser performance benchmark passed');
}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;
}finally{
client?.close();
stopBrowserTree(edge);
if(server._linkfieldClose)await server._linkfieldClose();else{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)});