@@ -8,7 +8,6 @@ const DEFAULT_REDRAW_OPTIONS = {
88 enabled : true , // Master switch to enable/disable redraw detection
99 screenRedraw : true , // Enable screen redraw detection
1010 networkMonitor : true , // Enable network activity monitoring
11- diffThreshold : 0.1 , // Percentage threshold for screen diff (0.01 = 0.01%)
1211} ;
1312
1413// Factory function that creates redraw functionality with the provided system instance
@@ -20,10 +19,6 @@ const createRedraw = (
2019) => {
2120 // Merge default options with provided defaults
2221 const baseOptions = { ...DEFAULT_REDRAW_OPTIONS , ...defaultOptions } ;
23- // Support legacy redrawThresholdPercent number argument
24- if ( typeof defaultOptions === 'number' ) {
25- baseOptions . diffThreshold = defaultOptions ;
26- }
2722
2823 const networkUpdateInterval = 15000 ;
2924
@@ -35,7 +30,13 @@ const createRedraw = (
3530
3631 let measurements = [ ] ;
3732 let networkSettled = true ;
38- let screenHasRedrawn = null ;
33+
34+ // Screen stability tracking
35+ let initialScreenImage = null ; // The image captured at start() - reference point
36+ let lastScreenImage = null ; // Previous frame for consecutive comparison
37+ let hasChangedFromInitial = false ; // Has screen changed from initial state?
38+ let consecutiveFramesStable = false ; // Are consecutive frames now stable?
39+ let screenMeasurements = [ ] ; // Track consecutive frame diffs for stability detection
3940
4041 // Track network interval to ensure only one exists
4142 let networkInterval = null ;
@@ -45,7 +46,11 @@ const createRedraw = (
4546 lastRxBytes = null ;
4647 measurements = [ ] ;
4748 networkSettled = true ;
48- screenHasRedrawn = false ;
49+ initialScreenImage = null ;
50+ lastScreenImage = null ;
51+ hasChangedFromInitial = false ;
52+ consecutiveFramesStable = false ;
53+ screenMeasurements = [ ] ;
4954 } ;
5055
5156 const parseNetworkStats = ( thisRxBytes , thisTxBytes ) => {
@@ -85,6 +90,40 @@ const createRedraw = (
8590 }
8691 } ;
8792
93+ // Parse screen diff stats for consecutive frame stability
94+ // Detects when consecutive frames have stopped changing
95+ const parseConsecutiveDiffStats = ( diffPercent ) => {
96+ screenMeasurements . push ( diffPercent ) ;
97+
98+ // Keep last 10 measurements for stability detection
99+ if ( screenMeasurements . length > 10 ) {
100+ screenMeasurements . shift ( ) ;
101+ }
102+
103+ // Need at least 2 measurements to determine stability
104+ if ( screenMeasurements . length < 2 ) {
105+ consecutiveFramesStable = false ;
106+ return ;
107+ }
108+
109+ let avgDiff = screenMeasurements . reduce ( ( acc , d ) => acc + d , 0 ) / screenMeasurements . length ;
110+
111+ let stdDevDiff = Math . sqrt (
112+ screenMeasurements . reduce ( ( acc , d ) => acc + Math . pow ( d - avgDiff , 2 ) , 0 ) /
113+ screenMeasurements . length ,
114+ ) ;
115+
116+ let zIndexDiff = stdDevDiff !== 0 ? ( diffPercent - avgDiff ) / stdDevDiff : 0 ;
117+
118+ // Consecutive frames are stable when z-index is negative (current diff is below average)
119+ // or diff is essentially zero (< 0.1% accounts for compression artifacts)
120+ if ( screenMeasurements . length >= 2 && ( diffPercent < 0.1 || zIndexDiff < 0 ) ) {
121+ consecutiveFramesStable = true ;
122+ } else {
123+ consecutiveFramesStable = false ;
124+ }
125+ } ;
126+
88127 async function updateNetwork ( ) {
89128 if ( sandbox && sandbox . instanceSocketConnected ) {
90129 let network = await sandbox . send ( {
@@ -141,8 +180,6 @@ const createRedraw = (
141180 }
142181 }
143182
144- let startImage = null ;
145-
146183 // Start network monitoring only when needed
147184 function startNetworkMonitoring ( ) {
148185 if ( ! networkInterval ) {
@@ -187,17 +224,18 @@ const createRedraw = (
187224 startNetworkMonitoring ( ) ;
188225 }
189226
190- // Only capture start image if screen redraw is enabled
227+ // Capture initial image for screen stability monitoring
191228 if ( currentOptions . screenRedraw ) {
192- startImage = await system . captureScreenPNG ( 0.25 , true ) ;
193- emitter . emit ( events . log . debug , `[redraw] start() - captured startImage: ${ startImage } ` ) ;
229+ initialScreenImage = await system . captureScreenPNG ( 0.25 , true ) ;
230+ lastScreenImage = initialScreenImage ;
231+ emitter . emit ( events . log . debug , `[redraw] start() - captured initial image: ${ initialScreenImage } ` ) ;
194232 }
195233
196- return startImage ;
234+ return initialScreenImage ;
197235 }
198236
199237 async function checkCondition ( resolve , startTime , timeoutMs , options ) {
200- const { enabled, screenRedraw, networkMonitor, diffThreshold } = options ;
238+ const { enabled, screenRedraw, networkMonitor } = options ;
201239
202240 // If redraw is disabled, resolve immediately
203241 if ( ! enabled ) {
@@ -207,33 +245,51 @@ const createRedraw = (
207245
208246 let nowImage = screenRedraw ? await system . captureScreenPNG ( 0.25 , true ) : null ;
209247 let timeElapsed = Date . now ( ) - startTime ;
210- let diffPercent = 0 ;
248+ let diffFromInitial = 0 ;
249+ let diffFromLast = 0 ;
211250 let isTimeout = timeElapsed > timeoutMs ;
212251
213- // Check screen redraw if enabled and we have a start image to compare against
214- if ( screenRedraw && ! screenHasRedrawn && startImage && nowImage ) {
215- emitter . emit ( events . log . debug , `[redraw] checkCondition() - comparing images: ${ JSON . stringify ( { startImage, nowImage } ) } ` ) ;
216- diffPercent = await imageDiffPercent ( startImage , nowImage ) ;
217- emitter . emit ( events . log . debug , `[redraw] checkCondition() - diffPercent: ${ diffPercent } , threshold: ${ diffThreshold } ` ) ;
218- screenHasRedrawn = diffPercent > diffThreshold ;
219- emitter . emit ( events . log . debug , `[redraw] checkCondition() - screenHasRedrawn: ${ screenHasRedrawn } ` ) ;
220- } else if ( screenRedraw && ! startImage ) {
221- // If no start image was captured, capture one now and wait for next check
222- emitter . emit ( events . log . debug , '[redraw] checkCondition() - no startImage, capturing now' ) ;
223- startImage = await system . captureScreenPNG ( 0.25 , true ) ;
252+ // Screen stability detection:
253+ // 1. Check if screen has changed from initial (detect transition)
254+ // 2. Check if consecutive frames are stable (detect settling)
255+ if ( screenRedraw && nowImage ) {
256+ // Compare to initial image - has the screen changed at all?
257+ if ( initialScreenImage && ! hasChangedFromInitial ) {
258+ diffFromInitial = await imageDiffPercent ( initialScreenImage , nowImage ) ;
259+ emitter . emit ( events . log . debug , `[redraw] checkCondition() - diffFromInitial: ${ diffFromInitial } ` ) ;
260+ // Consider changed if diff > 0.1% (accounts for compression artifacts)
261+ if ( diffFromInitial > 0.1 ) {
262+ hasChangedFromInitial = true ;
263+ emitter . emit ( events . log . debug , `[redraw] checkCondition() - screen has changed from initial!` ) ;
264+ }
265+ }
266+
267+ // Compare consecutive frames - has the screen stopped changing?
268+ if ( lastScreenImage && lastScreenImage !== initialScreenImage ) {
269+ diffFromLast = await imageDiffPercent ( lastScreenImage , nowImage ) ;
270+ emitter . emit ( events . log . debug , `[redraw] checkCondition() - diffFromLast: ${ diffFromLast } ` ) ;
271+ parseConsecutiveDiffStats ( diffFromLast ) ;
272+ emitter . emit ( events . log . debug , `[redraw] checkCondition() - consecutiveFramesStable: ${ consecutiveFramesStable } , measurements: ${ screenMeasurements . length } ` ) ;
273+ }
274+
275+ // Update last image for next comparison
276+ lastScreenImage = nowImage ;
224277 }
225278
226- // If screen redraw is disabled, consider it as "redrawn"
227- const effectiveScreenRedrawn = screenRedraw ? screenHasRedrawn : true ;
279+ // Screen is settled when: it has changed from initial AND consecutive frames are now stable
280+ const screenSettled = hasChangedFromInitial && consecutiveFramesStable ;
281+
282+ // If screen redraw is disabled, consider it as "settled"
283+ const effectiveScreenSettled = screenRedraw ? screenSettled : true ;
228284 // If network monitor is disabled, consider it as "settled"
229285 const effectiveNetworkSettled = networkMonitor ? networkSettled : true ;
230286
231- // Log redraw status
287+ // Log redraw status - show both change detection and stability
232288 let redrawText = ! screenRedraw
233289 ? theme . dim ( `disabled` )
234- : effectiveScreenRedrawn
290+ : effectiveScreenSettled
235291 ? theme . green ( `y` )
236- : theme . dim ( `${ diffPercent } / ${ diffThreshold } %` ) ;
292+ : theme . dim ( `${ hasChangedFromInitial ? '✓' : '?' } → ${ consecutiveFramesStable ? '✓' : diffFromLast . toFixed ( 1 ) } %` ) ;
237293 let networkText = ! networkMonitor
238294 ? theme . dim ( `disabled` )
239295 : effectiveNetworkSettled
@@ -248,9 +304,11 @@ const createRedraw = (
248304 emitter . emit ( events . redraw . status , {
249305 redraw : {
250306 enabled : screenRedraw ,
251- hasRedrawn : effectiveScreenRedrawn ,
252- diffPercent,
253- threshold : diffThreshold ,
307+ settled : effectiveScreenSettled ,
308+ hasChangedFromInitial,
309+ consecutiveFramesStable,
310+ diffFromInitial,
311+ diffFromLast,
254312 text : redrawText ,
255313 } ,
256314 network : {
@@ -268,9 +326,11 @@ const createRedraw = (
268326 } ,
269327 } ) ;
270328
271- if ( ( effectiveScreenRedrawn && effectiveNetworkSettled ) || isTimeout ) {
329+ if ( ( effectiveScreenSettled && effectiveNetworkSettled ) || isTimeout ) {
272330 emitter . emit ( events . redraw . complete , {
273- screenHasRedrawn : effectiveScreenRedrawn ,
331+ screenSettled : effectiveScreenSettled ,
332+ hasChangedFromInitial,
333+ consecutiveFramesStable,
274334 networkSettled : effectiveNetworkSettled ,
275335 isTimeout,
276336 timeElapsed,
0 commit comments