'use strict'; const {assert,functionSource,app,css,vm}=require('./helpers/app-source'); const fs=require('fs'),path=require('path'),browserBenchmark=fs.readFileSync(path.join(__dirname,'browser-performance-benchmark.js'),'utf8'), frameScheduler=fs.readFileSync(path.join(__dirname,'../client/input/frame-scheduler.js'),'utf8'), dragModule=fs.readFileSync(path.join(__dirname,'../client/input/drag.js'),'utf8'); assert(app.includes('MAX_RENDER_FPS=30')&&app.includes('AUXILIARY_FPS=30')&&app.includes('DRAG_TARGET_FPS=30')&&app.includes('DRAG_FRAME_INTERVAL=1000/DRAG_TARGET_FPS')&&app.includes('CAMERA_DISPLAY_WATCHDOG_MS=34'),'Interaction and auxiliary frame budgets are not separated'); assert(app.includes('BEND_INTERACTION_SCHEDULER')&&app.includes('bend-field-interaction-scheduler-variant')&&app.includes('batteryDischargingTime'),'Scheduler rollout variant or battery telemetry guardrail is missing'); assert(app.includes('DRAG_MAX_LIVE_CATCHUP_CELLS=6')&&app.includes('DRAG_MAX_RELEASE_CATCHUP_CELLS=24')&&functionSource('extendPointerTo').includes('catchupPending')&&functionSource('processBoardDragFrame').includes('catchupPending'),'Large pointer jumps are not bounded and resumed across drag frames'); const cursorInput=functionSource('updateCustomCursorFromPointer'),cursorCommit=functionSource('commitCustomCursorFrame'),cursorSync=functionSource('syncCursorAppearance'); assert(cursorInput.includes('scheduleCustomCursorFrame()')&&!cursorInput.includes('customEmojiCursor.style.transform')&&cursorCommit.includes('customEmojiCursor.style.transform=`translate3d(')&&functionSource('scheduleCustomCursorFrame').includes('customCursorLastDraw+DRAG_FRAME_INTERVAL')&&functionSource('scheduleCustomCursorFrame').includes('requestAnimationFrame(paint)')&&!cursorCommit.includes('Math.exp('),'DOM cursor does not coalesce the latest pointer sample through its display-backed 60 Hz commit lane'); assert(app.includes("addEventListener('pointermove',updateCustomCursorFromPointer")&&app.includes("addEventListener('pointerrawupdate',updateCustomCursorFromPointer"),'DOM cursor does not tolerate sparse raw-input delivery'); assert(cursorInput.includes("classList.contains('is-drawing')")&&css.includes('body.is-drawing #customEmojiCursor'),'Custom cursor is not hidden during pickup dragging'); assert(cursorSync.includes('dataset.cursorMode=presentation.mode')&&!cursorSync.includes('nativeSupported')&&!css.includes('data-cursor-mode="native"'),'Custom cursor skins are not forced through the board-safe DOM renderer'); const cameraQueue=functionSource('queueCameraInteraction'),cameraApply=functionSource('applyCamera'); assert(cameraQueue.includes('pendingCameraInteraction=next')&&cameraQueue.includes('cameraInteractionScheduler.push(next)')&&app.includes('watchdogDelay:CAMERA_DISPLAY_WATCHDOG_MS')&&frameScheduler.includes('requestFrame(step)'),'Camera does not use a latest-input 60 Hz queue with a missed-vsync watchdog'); assert(functionSource('zoomAt').includes('queueCameraInteraction')&&!functionSource('zoomAt').includes('applyCamera(')&&app.includes("window.addEventListener('pointermove',movePan,true)")&&app.includes("window.addEventListener('pointerup',stopPan,true)")&&app.includes("viewport.addEventListener('lostpointercapture',stopPan,true)"),'Wheel zoom bypasses the capped camera lane or pan lifecycle is not resilient outside the viewport'); assert(cameraApply.includes('positionCachedWorldOverview')&&functionSource('positionCachedWorldOverview').includes('translate3d'),'Overview position is not updated in the camera fast path'); assert(functionSource('scheduleWorldOverview').includes('requestIdleCallback')&&functionSource('scheduleWorldOverview').includes("interactionActive('overview')"),'Overview rebuilds are not idle and interaction-safe'); const dragSchedule=functionSource('scheduleBoardDragFrame'),dragFrame=functionSource('processBoardDragFrame'); assert(dragSchedule.includes('requestFrame')&&functionSource('ensureBoardDragScheduler').includes('watchdogDelay:DRAG_DISPLAY_WATCHDOG_MS')&&functionSource('ensureBoardDragScheduler').includes('interval:DRAG_FRAME_INTERVAL')&&dragModule.includes("'idle','armed','running','draining','settling','cancelled'"),'Pickup visuals or logic are not capped to their 60 Hz lane'); assert(functionSource('queueBoardPointerMove').includes('appendBoardPointerSamples')&&functionSource('queueBoardPointerMove').includes('scheduleBoardDragFrame')&&!functionSource('queueBoardPointerMove').includes('updatePickupHandleOverlay')&&!functionSource('queueBoardPointerMove').includes('markVisualFrame')&&!functionSource('queueBoardPointerMove').includes('extendPointerTo'),'Pointer events still directly mutate pickup presentation or perform forbidden model work'); assert(!app.includes('function commitBoardDragFromInputDeadline('),'Pickup still has a direct input-deadline commit path outside the shared 60 Hz scheduler'); assert(!app.includes('DRAG_INPUT_RESCUE_MS')&&!app.includes('function rescuePickupVisualFromInput('),'An input-rate pickup presentation path can still exceed 60 Hz'); assert(functionSource('setBoardPointerSample').includes('dragVisualActiveUntil=perfNow()+40')&&dragFrame.includes('b.dragVisualX=move.clientX')&&dragFrame.includes('updateDrawingHandlePosition')&&dragFrame.includes('perfNow()=32'),'Complex pickup handles are not compositor-driven or the SVG live tail is repainted every display frame'); assert(dragFrame.includes('updatePickupHandleOverlay')&&dragFrame.includes('usesLightweightDragOverlay')&&dragFrame.includes('dirtyBoards.delete(b)')&&app.includes('LIGHTWEIGHT_DRAG_BOARD_CELLS=120')&&functionSource('updatePickupHandleOverlay').includes('translate3d')&&css.includes('#pickupHandleOverlay{position:fixed'),'Large-board pickup still repaints the SVG during the gesture instead of using the lightweight display-rate handle'); assert(functionSource('activateBoardPointerDrag').includes('setPickupScenePresentation(b,true)')&&functionSource('setLightweightDragPresentation').includes("style.opacity=next?'0':''")&&functionSource('setPickupScenePresentation').includes('setLightweightDragPresentation(board,next&&board===activeBoard)')&&functionSource('scheduleInteractionSettlePresentation').includes('setPickupScenePresentation(null,false)'),'Active large-board decorative layers are not compositor-suppressed during pickup and restored after settlement'); assert(dragFrame.includes("recordInteractionCommit('pickupVisual'")&&dragFrame.includes('workDuration'),'Pickup cadence, input age, or callback work is not instrumented'); assert(dragFrame.includes('timestamp<=b.dragVisualActiveUntil')&&dragFrame.includes("recordInteractionCommit('pickupVisual',timestamp,move.inputAt,false)")&&!css.includes('transition:transform 16.67ms linear'),'Pickup warm visual ticks are not measured or the knob overlay still adds transition lag'); assert(dragFrame.includes('freshVisualInput')&&dragFrame.includes('freshLogicalInput'),'Pickup latency instrumentation resamples stale input on warm display frames'); assert(app.includes('DRAG_MAX_LOGICAL_SAMPLES_PER_FRAME=4')&&app.includes('DRAG_MODEL_BUDGET_MS=.5')&&dragFrame.includes('scheduler.takeLogical(1)')&&dragFrame.includes('perfNow()-modelStartedgateConnectEffect'),'Large-board gate decoration still mutates the SVG inside the finishing interaction task'); assert(bind.includes("flushBoardPointerMove(b,e,()=>finishPointer(e,'settled'))")&&bind.includes('b.releaseDrain?.pointerId===e.pointerId'),'Pointer-up settlement does not wait for the bounded drain or lost capture can cancel an active drain'); assert(functionSource('runDeferredSave').includes("interactionActive('persistence')"),'Ordinary persistence is not deferred during interactions'); assert(functionSource('pullCloudWorld').includes('await waitForInteractionSettle()')&&!app.includes('applyWorldSignal'),'Server reconciliation can run broad refresh work during a gesture or retired cross-tab reconciliation remains'); assert(functionSource('createPuzzleWorker').includes("perfCount('workerResultsDeferredDuringInteraction')")&&functionSource('createPuzzleWorker').includes("waitForInteractionSettle(null,'worker').then(deliver)"),'Puzzle-worker promise continuations can still run during an active gesture'); assert(functionSource('desiredInteractiveBoardIds').includes('for(const id of ids)')&&!app.includes('INTERACTIVE_DETAIL_CELL_BUDGET')&&!functionSource('ensureBoards').includes('makeStaticBoard(meta)'),'Visible puzzles can still be hidden behind a clicked-only summary LOD'); assert(functionSource('observeInteractionFrame').includes('workDuration>slowWorkThreshold')&&!functionSource('observeInteractionFrame').includes('elapsed>24'),'Quality fallback still mistakes frame spacing for callback overload'); assert(!app.includes('interactionQualityDowngradePending')&&!app.includes('autoReducedEffects')&&functionSource('applyUiSettings').includes("classList.toggle('reduced-effects',uiSettings.lightweightRendering)"),'Interaction still activates an automatic lightweight visual mode'); assert(functionSource('refreshInteractionState').includes("topbar?.classList.toggle('drawing-active'")&&functionSource('refreshInteractionState').includes("world?.classList.toggle('camera-interacting'")&&functionSource('refreshInteractionState').includes('!usesLightweightDragOverlay(drawingBoard)')&&!css.includes('body.is-interacting .gate-dot')&&!css.includes('body.is-drawing #topbar'),'Pickup styling still invalidates the entire document or a lightweight large-board subtree'); assert(functionSource('recordInteractionCommit').includes('InputAge')&&functionSource('observeInteractionFrame').includes('interactionDroppedFrameRatio')&&app.includes("perfCount('longTasks')"),'Interaction latency, dropped-frame, or long-task diagnostics are missing'); assert(functionSource('ensureBoards').includes("interactionActive('world')")&&functionSource('ensureBoards').includes('lodPassesDeferredDuringInteraction'),'Detailed and overview LOD work is not deferred during gestures'); assert(functionSource('drawingForPath').includes('pathCellIndex:new Map')&&functionSource('extendOne').includes('pathCellIndex?.set'),'Long pickup paths do not retain a cell index'); assert(functionSource('rewindActivePathToCell').includes('index===path.cells.length-1)return false'),'A pickup hovering on its current tip is incorrectly classified as a geometry-changing rewind'); assert(functionSource('makeBoard').includes('gateIndexesByCell')&&functionSource('gateCandidatesInCell').includes('gateIndexesByCell'),'Gate lookup is not indexed by cell'); assert(functionSource('makeBoard').includes('endpointIndexesByCell')&&functionSource('nearestEndpointAtPoint').includes('endpointCandidatesNearPoint'),'Open endpoint lookup is not indexed by cell'); assert(app.includes('REALTIME_CURSOR_INTERVAL=50')&&functionSource('queueRealtimeCursor').includes('vx:')&&functionSource('applyRemotePlayer').includes('lead=.035'),'Remote cursors are not sent at 20 Hz with velocity-based short extrapolation'); for(const probe of['measureDisplayCadence','measureCursorCadence','claimLatencyProbe','pickupEdgePanProbe','pinchZoomProbe','panCadenceProbe']) assert(browserBenchmark.includes(`function ${probe}`)||browserBenchmark.includes(`function ${probe}(`),`Browser performance matrix is missing ${probe}`); assert(browserBenchmark.includes('--force-device-scale-factor=1'),'Browser performance matrix must normalize device scale for stable display-cadence measurements'); const pickupCadenceProbeSource=browserBenchmark.slice(browserBenchmark.indexOf('async function pickupCadenceProbe'),browserBenchmark.indexOf('async function claimLatencyProbe')); assert(pickupCadenceProbeSource.includes("deepClone(metaState('B0'))")&&pickupCadenceProbeSource.includes('data.states.B0=')&&pickupCadenceProbeSource.includes("persistNow({skipCloud:true})")&&pickupCadenceProbeSource.includes("snapshot:await client.evaluate('BEND_PERF.snapshot()')"),'Continuous pickup benchmark does not capture clean cadence data and restore its original board state'); assert(browserBenchmark.includes('async function restoreOriginState')&&browserBenchmark.includes('replaceObjectContents(current,deepClone(snapshot))')&&browserBenchmark.includes('setTimeout(resolve,80)')&&browserBenchmark.includes("applySnapshot();changed('B0')")&&browserBenchmark.includes("centerMeta(data.metas.B0,{select:false})")&&browserBenchmark.match(/restoreOriginState\(client,pristineOriginState\)/g)?.length>=4,'Browser matrix does not settle, revision-stamp, and canonically reset origin state and camera between pickup probes'); assert(browserBenchmark.includes('pickup probes did not restore a pristine origin board')&&browserBenchmark.includes("metaState('B0').solved===true"),'Browser matrix does not verify scenario isolation and solve completion before panning'); assert(browserBenchmark.includes("spawnSync(taskkill,['/pid',String(child.pid),'/T','/F']")&&browserBenchmark.includes('stopBrowserTree(edge)')&&browserBenchmark.includes('fs.rmSync(temporaryRoot,{recursive:true,force:true})'),'Browser benchmark does not guarantee process-tree and temporary-profile cleanup'); for(const profile of['small','medium','large','complex','long-line'])assert(browserBenchmark.includes(`name:'${profile}'`),`Browser benchmark profile ${profile} is missing`); const longCells=Array.from({length:250},(_,index)=>[0,index]),longPath={startGate:0,endGate:null,openGate:null,cells:longCells.map(cell=>[...cell])}, longState={paths:[longPath]},longOccupancy=new Map(longCells.map(cell=>[`${cell[0]},${cell[1]}`,0])),longDrawing={pathIndex:0,pointerId:1,pathCellIndex:new Map(longCells.map((cell,index)=>[`${cell[0]},${cell[1]}`,index]))}, longBoard={id:'L',p:{g:Array.from({length:100},()=>[0,0,'N'])},drawing:longDrawing,crossingKeySet:new Set()},longContext={ activePath:()=>longPath,ckey:(r,c)=>`${r},${c}`,warpPairForCell:()=>null,isWarpTransition:()=>false,pathIndexesAtCell:()=>[],metaState:()=>longState, applyCalls:0,applyBoardCommand:(_board,mutate)=>{longContext.applyCalls++;mutate();return true} }; vm.createContext(longContext);vm.runInContext(`${functionSource('rewindActivePathToCell')}\nthis.rewindActivePathToCell=rewindActivePathToCell;`,longContext); assert(longContext.rewindActivePathToCell(longBoard,[0,10],longOccupancy)&&longPath.cells.length===11&&longDrawing.pathCellIndex.size===11&&longOccupancy.size===11&&longContext.applyCalls===1,'A 250-cell pickup does not rewind with one suffix splice'); const traversalContext={PAD:0,CELL:1,ckey:(r,c)=>`${r},${c}`};vm.createContext(traversalContext); vm.runInContext(`${functionSource('cellsCrossedBySegment')}\nthis.cellsCrossedBySegment=cellsCrossedBySegment;`,traversalContext); const validLongCells=new Set(longCells.map(([r,c])=>`${r},${c}`)),forward=traversalContext.cellsCrossedBySegment([.5,.5],[249.5,.5],{preferredAxis:'H',validCells:validLongCells}); assert(forward.length===249&&forward[248][1]===249,'A 250-cell pointer segment does not preserve every forward grid cell'); let remainingCatchup=forward.length,batches=0,maxBatch=0;while(remainingCatchup){const batch=Math.min(24,remainingCatchup);maxBatch=Math.max(maxBatch,batch);remainingCatchup-=batch;batches++} assert(maxBatch===24&&batches===11,'A 250-cell catch-up is not bounded to constant-size frame batches'); { let progress=0,extendCalls=0,completed=0,scheduled=0; const releaseContext={ DRAG_MAX_LOGICAL_SAMPLES_PER_FRAME:4,DRAG_MODEL_BUDGET_MS:.5,DRAG_MAX_RELEASE_CATCHUP_CELLS:24, perfStart:()=>0,perfNow:()=>0,perfEnd:()=>0,pointerInsideBoardScreen:()=>true,eventToSvg:(_board,move)=>[move.clientX,move.clientY],cellAt:(_board,point)=>[0,point[0]], extendPointerTo:board=>{extendCalls++;progress+=Math.min(24,250-progress);board.drawing.catchupPending=progress<250}, usesLightweightDragOverlay:()=>false,renderDragFrame:()=>{},recordInteractionCommit:()=>{},perfCount:()=>{}, scheduleBoardPointerReleaseDrain:()=>{scheduled++;return true},ensureBoardDragScheduler:()=>({beginSettling:()=>true}),queueMicrotask:callback=>callback() }; vm.createContext(releaseContext);vm.runInContext(`${releaseDrain}\nthis.processBoardPointerReleaseDrain=processBoardPointerReleaseDrain;`,releaseContext); const drain={pointerId:1,moves:[{pointerId:1,clientX:250,clientY:0,inputAt:1}],index:0,inputAt:1,tracedIndexes:new Set(),onComplete:()=>completed++}, board={drawing:{pointerId:1,catchupPending:false},lastFlushPointerCells:[],logicalPointerCellTrace:[],releaseDrain:drain}; while(board.releaseDrain){const before=extendCalls;releaseContext.processBoardPointerReleaseDrain(board,drain,extendCalls*17);assert(extendCalls-before<=4,'A release frame exceeded its logical-sample work bound')} assert(progress===250&&extendCalls===11&&completed===1&&scheduled===2,'Bounded release drain lost, duplicated, or incompletely settled the final 250-cell intent'); } const endpointState={paths:Array.from({length:100},(_,index)=>({endGate:null,detachedStart:index%2===0,cells:[[0,index],[1,index]]}))}, endpointContext={Map,ckey:(r,c)=>`${r},${c}`,metaState:()=>endpointState}; vm.createContext(endpointContext);vm.runInContext(`${functionSource('rebuildEndpointIndexes')}\nthis.rebuildEndpointIndexes=rebuildEndpointIndexes;`,endpointContext); const endpointBoard={id:'E',endpointIndexesByCell:new Map()},endpointIndex=endpointContext.rebuildEndpointIndexes(endpointBoard); assert(endpointIndex.size===150&&endpointIndex.get('1,99')[0].index===99,'The 100-path endpoint index is incomplete'); const remoteContext={Number,Map,realtimePresenceId:null,remotePlayers:new Map(),schedulePresenceRender:()=>{},scheduleMinimap:()=>{}}; vm.createContext(remoteContext);vm.runInContext(`${functionSource('applyRemotePlayer')}\nthis.applyRemotePlayer=applyRemotePlayer;`,remoteContext); assert(remoteContext.applyRemotePlayer({presenceId:'R',x:10,y:20,vx:2,vy:-1,sentAt:123,name:'R'}),'Remote cursor sample was rejected'); const remoteSample=remoteContext.remotePlayers.get('R'); assert(Math.abs(remoteSample.targetX-10.07)<1e-9&&Math.abs(remoteSample.targetY-19.965)<1e-9&&remoteSample.sentAt===123,'Remote cursor velocity extrapolation is incorrect'); for(const refreshRate of[60,120,144]){ const displayInterval=1000/refreshRate,visualCommits=[],logicalCommits=[];let nextVisual=0,nextLogical=0; for(let timestamp=0;timestamp<1000;timestamp+=displayInterval){ if(!nextVisual||timestamp+.01>=nextVisual){visualCommits.push(timestamp);do nextVisual=(nextVisual||timestamp)+1000/60;while(nextVisual<=timestamp+.01)} if(!nextLogical||timestamp+2>=nextLogical){logicalCommits.push(timestamp);do nextLogical=(nextLogical||timestamp)+1000/60;while(nextLogical<=timestamp+2)} } assert(visualCommits.length>=59&&visualCommits.length<=61,`${refreshRate} Hz display produced ${visualCommits.length} pickup presentation frames instead of about 60`); assert(logicalCommits.length>=59&&logicalCommits.length<=61,`${refreshRate} Hz display does not retain a 60 Hz pickup logic lane`); } console.log('Display-rate cursor/pan and 60 Hz pickup scheduler regression test passed');