ddd
This commit is contained in:
parent
0a343ecbc5
commit
c3a6f5ff37
80 changed files with 2512 additions and 1625 deletions
|
|
@ -1,73 +1,44 @@
|
|||
'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');
|
||||
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('AUXILIARY_FPS=30')&&app.includes('DRAG_TARGET_FPS=60')&&app.includes('DRAG_FRAME_INTERVAL=1000/DRAG_TARGET_FPS')&&app.includes('CAMERA_DISPLAY_WATCHDOG_MS=18'),'Interaction and auxiliary frame budgets are not separated');
|
||||
assert(app.includes('AUXILIARY_FPS=30')&&app.includes('DRAG_TARGET_FPS=60')&&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_CATCHUP_CELLS=24')&&functionSource('extendPointerTo').includes('catchupPending')&&functionSource('processBoardDragFrame').includes('catchupPending'),'Large pointer jumps are not bounded and resumed across drag frames');
|
||||
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 cursor=functionSource('updateCustomCursorFromPointer'),cursorSync=functionSource('syncCursorAppearance');
|
||||
assert(cursor.includes('requestAnimationFrame')&&!cursor.includes('FRAME_INTERVAL'),'DOM cursor fallback is not synchronized to every display frame');
|
||||
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(cursor.includes('customCursorActiveUntil=perfNow()+40')&&cursor.includes('customCursorFrame=requestAnimationFrame(step)')&&cursor.includes('Math.exp(-elapsed/8)'),'DOM cursor does not keep and smooth its display-rAF loop between active pointer events');
|
||||
assert(!cursor.includes("classList.contains('is-drawing')")&&!css.includes('body.is-drawing #customEmojiCursor'),'Custom cursor is hidden during pickup dragging');
|
||||
assert(cursorSync.includes('nativeSupported')&&cursorSync.includes('--active-native-cursor')&&css.includes('data-cursor-mode="native"'),'Native cursor assets or the DOM fallback switch are missing');
|
||||
|
||||
const cursorClasses=new Set(),cursorFrames=[],cursorRafQueue=[],cursorTransforms=[],cursorPerfSamples=new Map();
|
||||
let cursorClock=0,cursorRafId=0;
|
||||
const cursorContext={
|
||||
document:{body:{dataset:{cursorMode:'dom'}}},
|
||||
customEmojiCursor:{classList:{contains:name=>cursorClasses.has(name),add:name=>cursorClasses.add(name),remove:name=>cursorClasses.delete(name)},style:{set transform(value){cursorTransforms.push(value)}}},
|
||||
perfNow:()=>cursorClock,
|
||||
requestAnimationFrame:callback=>{cursorRafQueue.push(callback);return++cursorRafId},
|
||||
markVisualFrame:timestamp=>cursorFrames.push(timestamp),
|
||||
perfObserve:(name,value)=>{const samples=cursorPerfSamples.get(name)||[];samples.push(value);cursorPerfSamples.set(name,samples);return value},
|
||||
perfCount:()=>{}
|
||||
};
|
||||
vm.createContext(cursorContext);
|
||||
vm.runInContext(`const interactionCommitAt=Object.create(null),interactionInputAt=Object.create(null);${functionSource('recordInteractionCommit')}let customCursorFrame=0,customCursorX=0,customCursorY=0,customCursorInputAt=0,customCursorActiveUntil=0,customCursorRenderedX=NaN,customCursorRenderedY=NaN,customCursorLastFrameAt=0,customCursorInputRevision=0,customCursorCommittedRevision=0;${cursor};this.updateCustomCursorFromPointer=updateCustomCursorFromPointer;`,cursorContext);
|
||||
let cursorEventAt=0;
|
||||
for(let frameAt=1000/60;frameAt<=1100;frameAt+=1000/60){
|
||||
while(cursorEventAt<=1000&&cursorEventAt<=frameAt+.001){
|
||||
cursorClock=cursorEventAt;cursorContext.updateCustomCursorFromPointer({clientX:cursorEventAt,clientY:cursorEventAt/2,timeStamp:cursorEventAt||.001,pointerType:'mouse',target:{isConnected:true}});
|
||||
cursorEventAt+=1000/30;
|
||||
}
|
||||
cursorClock=frameAt;
|
||||
for(const callback of cursorRafQueue.splice(0))callback(frameAt);
|
||||
}
|
||||
const cursorFrameGaps=cursorFrames.slice(1).map((timestamp,index)=>timestamp-cursorFrames[index]).sort((a,b)=>a-b),
|
||||
cursorMedianGap=cursorFrameGaps[Math.floor((cursorFrameGaps.length-1)*.5)],
|
||||
sortedCursorAges=[...(cursorPerfSamples.get('cursorInputAge')||[])].sort((a,b)=>a-b),cursorP95Age=sortedCursorAges[Math.floor((sortedCursorAges.length-1)*.95)],
|
||||
measuredCursorGaps=[...(cursorPerfSamples.get('cursorFrameGap')||[])].sort((a,b)=>a-b),measuredCursorMedianGap=measuredCursorGaps[Math.floor((measuredCursorGaps.length-1)*.5)],
|
||||
changedCursorTransforms=cursorTransforms.filter((value,index)=>index===0||value!==cursorTransforms[index-1]).length;
|
||||
assert(cursorFrames.length>=60&&cursorMedianGap<=20,`Warm DOM cursor loop produced ${cursorFrames.length} frames with a ${cursorMedianGap} ms median gap`);
|
||||
assert(measuredCursorGaps.length>=30&&measuredCursorMedianGap<=20,`Production cursor instrumentation captured ${measuredCursorGaps.length} gaps with a ${measuredCursorMedianGap} ms median`);
|
||||
assert(changedCursorTransforms>=55,`Warm DOM cursor loop changed its transform on only ${changedCursorTransforms} display frames`);
|
||||
assert(sortedCursorAges.length>=25,`Production cursor instrumentation captured only ${sortedCursorAges.length} fresh-input latency samples`);
|
||||
assert(cursorP95Age<25,`Warm DOM cursor loop produced ${cursorP95Age} ms p95 input age`);
|
||||
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('requestAnimationFrame')&&cameraQueue.includes('CAMERA_DISPLAY_WATCHDOG_MS')&&cameraQueue.includes('cancelAnimationFrame')&&cameraQueue.includes('cameraInteractionLastDraw+DRAG_FRAME_INTERVAL'),'Camera does not use a latest-input 60 Hz queue with a missed-vsync watchdog');
|
||||
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("classList.contains('is-interacting')"),'Overview rebuilds are not idle and interaction-safe');
|
||||
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('requestAnimationFrame')&&dragSchedule.includes('DRAG_DISPLAY_WATCHDOG_MS')&&dragSchedule.includes('cancelAnimationFrame')&&dragSchedule.includes('clearTimeout')&&dragSchedule.includes('b.dragVisualLastFrameAt+DRAG_FRAME_INTERVAL')&&dragSchedule.includes('INTERACTION_FRAME_TOLERANCE_MS')&&dragFrame.includes('DRAG_FRAME_INTERVAL'),'Pickup visuals or logic are not capped to their 60 Hz lane');
|
||||
assert(functionSource('queueBoardPointerMove').includes('appendBoardPointerSamples')&&functionSource('queueBoardPointerMove').includes('commitBoardDragFromInputDeadline')&&functionSource('queueBoardPointerMove').includes('scheduleBoardDragFrame')&&!functionSource('queueBoardPointerMove').includes('updatePickupHandleOverlay')&&!functionSource('queueBoardPointerMove').includes('markVisualFrame')&&!functionSource('queueBoardPointerMove').includes('extendPointerTo'),'Pointer events mutate presentation or model work outside the capped scheduler');
|
||||
assert(functionSource('commitBoardDragFromInputDeadline').includes('b.dragVisualLastFrameAt+DRAG_FRAME_INTERVAL')&&functionSource('commitBoardDragFromInputDeadline').includes('INTERACTION_FRAME_TOLERANCE_MS')&&functionSource('commitBoardDragFromInputDeadline').includes('processBoardDragFrame(b,now)'),'Pickup input deadline fallback is not governed by the 60 Hz presentation ceiling');
|
||||
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('Math.exp(-visualElapsed/8)')&&dragFrame.includes('updateDrawingHandlePosition')&&dragFrame.includes('perfNow()<b.dragVisualActiveUntil'),'Pickup visual lane does not remain warm and ease toward sparse pointer samples at display cadence');
|
||||
assert(functionSource('setBoardPointerSample').includes('dragVisualActiveUntil=perfNow()+40')&&dragFrame.includes('b.dragVisualX=move.clientX')&&dragFrame.includes('updateDrawingHandlePosition')&&dragFrame.includes('perfNow()<b.dragVisualActiveUntil')&&!dragFrame.includes('visualBlend'),'Pickup visual lane does not track the latest pointer sample directly');
|
||||
assert(functionSource('renderDragFrame').includes('translate3d')&&functionSource('renderDragFrame').includes('tailNow-drawing.lastTailRenderAt>=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 sample gaps are not compositor-interpolated or warm visual ticks are not measured');
|
||||
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('b.pointerMoveSamples.shift()')&&dragFrame.includes('perfNow()-modelStarted<DRAG_MODEL_BUDGET_MS')&&dragFrame.includes('logicalSlotDue&&(freshLogicalInput||velocity[0]||velocity[1]||b.drawing?.catchupPending||b.pointerMoveSamples?.length)'),'Pickup logic does not time-bound ordered-sample catch-up or warm frames still run model traversal without pending work');
|
||||
assert(functionSource('setBoardPointerSample').includes('trimBoardPointerSamples(samples)')&&functionSource('trimBoardPointerSamples').includes('leastTurn')&&functionSource('trimBoardPointerSamples').includes('samples.splice(removeIndex,1)'),'Bounded pickup input overflow does not preserve sharp turns');
|
||||
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()-modelStarted<DRAG_MODEL_BUDGET_MS')&&dragFrame.includes('logicalSlotDue&&(freshLogicalInput||velocity[0]||velocity[1]||b.drawing?.catchupPending||scheduler.hasLogical())'),'Pickup logic does not time-bound ordered-sample catch-up or warm frames still run model traversal without pending work');
|
||||
assert(functionSource('ensureBoardDragScheduler').includes('trim:trimBoardPointerSamples')&&functionSource('trimBoardPointerSamples').includes('leastTurn')&&functionSource('trimBoardPointerSamples').includes('samples.splice(removeIndex,1)'),'Bounded pickup input overflow does not preserve sharp turns');
|
||||
assert(dragFrame.includes('pointerInsideBoardScreen(b,logicalMove)?eventToSvg')&&functionSource('pointerInsideBoardScreen').includes('boardScreenRect'),'Off-board edge panning can still traverse and prematurely finish the pickup path');
|
||||
assert(dragFrame.includes("perfEnd('pickupModelWork'")&&dragFrame.includes("perfEnd('pickupVisualWork'"),'Pickup model and visual callback costs are not independently instrumented');
|
||||
assert(functionSource('clearDragRender').includes("style.display='none'")&&!functionSource('clearDragRender').includes('replaceChildren'),'Drag cache nodes are destroyed between gestures');
|
||||
const releaseDrain=functionSource('processBoardPointerReleaseDrain'),releaseFlush=functionSource('flushBoardPointerMove');
|
||||
assert(releaseDrain.includes('DRAG_MAX_LOGICAL_SAMPLES_PER_FRAME')&&releaseDrain.includes('DRAG_MODEL_BUDGET_MS')&&releaseDrain.includes('DRAG_MAX_RELEASE_CATCHUP_CELLS')&&releaseDrain.includes('scheduleBoardPointerReleaseDrain(b)')&&!releaseFlush.includes('Infinity'),'Pointer release does not drain final intent in bounded frame slices');
|
||||
|
||||
const bind=functionSource('bindBoard');
|
||||
assert(bind.indexOf('beginPendingClaimPointer')<bind.indexOf('await ensureBoardClaimForInput'),'Shared-session pickup preview does not start before the ownership round-trip');
|
||||
|
|
@ -76,18 +47,19 @@ assert(bind.includes('activateBoardPointerDrag')&&functionSource('activateBoardP
|
|||
assert(functionSource('selectBoard').includes('alreadySelected')&&functionSource('renderDragFrame').includes('pickupStartFullBoardRenders'),'Pickup start does not avoid and measure full-board redraws');
|
||||
assert(functionSource('applyBoardCommand').includes('pointerInteraction&&b.drawing?.pointerId==null')&&bind.includes('b.drawing=null;b.armedGate=null;refreshInteractionState()')&&bind.includes('scheduleBoardCommandSettlement(b,{paint:true,invalidate:true,pathIndex:finishedPathIndex})'),'Pickup completion does not end interaction state before deferring broad render and cache work');
|
||||
assert(functionSource('safeRelease').includes('scheduleInteractionSettlePresentation')&&!functionSource('safeRelease').includes('queueMicrotask')&&functionSource('scheduleInteractionSettlePresentation').includes('requestAnimationFrame')&&!functionSource('scheduleInteractionSettlePresentation').includes('ensureBoards()'),'Pickup release still runs broad settlement work inside the pointer task');
|
||||
assert(functionSource('finalizeAtGate').includes('deferSettlement:true')&&functionSource('scheduleBoardCommandSettlement').includes('requestAnimationFrame')&&functionSource('scheduleBoardCommandSettlement').includes("classList.contains('is-interacting')"),'Pickup completion still performs render, persistence, or solve settlement inside the active drag callback');
|
||||
assert(functionSource('finalizeAtGate').includes('deferSettlement:true')&&functionSource('scheduleBoardCommandSettlement').includes('requestAnimationFrame')&&functionSource('scheduleBoardCommandSettlement').includes("interactionActive('persistence')"),'Pickup completion still performs render, persistence, or solve settlement inside the active drag callback');
|
||||
assert(functionSource('finalizeAtGate').includes('usesLightweightDragOverlay(b)')&&functionSource('finalizeAtGate').includes('requestAnimationFrame(()=>gateConnectEffect'),'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("classList?.contains('is-interacting')"),'Ordinary persistence is not deferred during interactions');
|
||||
assert(functionSource('runDeferredSave').includes("interactionActive('persistence')"),'Ordinary persistence is not deferred during interactions');
|
||||
assert(functionSource('applyWorldSignal').includes('await waitForInteractionSettle()')&&functionSource('pullCloudWorld').includes('await waitForInteractionSettle()'),'Cross-tab or cloud reconciliation can still run broad refresh work during a gesture');
|
||||
assert(functionSource('createPuzzleWorker').includes("perfCount('workerResultsDeferredDuringInteraction')")&&functionSource('createPuzzleWorker').includes('waitForInteractionSettle().then(deliver)'),'Puzzle-worker promise continuations can still run during an active gesture');
|
||||
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(functionSource('refreshInteractionState').includes('interactionQualityDowngradePending'),'Quality changes are not deferred until the gesture ends');
|
||||
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("classList?.contains?.('is-interacting')")&&functionSource('ensureBoards').includes('lodPassesDeferredDuringInteraction'),'Detailed and overview LOD work is not deferred during gestures');
|
||||
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');
|
||||
|
|
@ -118,6 +90,21 @@ const validLongCells=new Set(longCells.map(([r,c])=>`${r},${c}`)),forward=traver
|
|||
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};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue