@@ -254,6 +254,93 @@ function resolveOverlayRuntimeSyncStateContainer(context = {}, gameplayState = n
254254 return null ;
255255}
256256
257+ function resolveOverlayRuntimeEventQueue ( context = { } , gameplayState = null , syncState = null ) {
258+ if ( Array . isArray ( context ?. overlayRuntimeEvents ) ) {
259+ return context . overlayRuntimeEvents ;
260+ }
261+ if ( Array . isArray ( gameplayState ?. overlayRuntimeEvents ) ) {
262+ return gameplayState . overlayRuntimeEvents ;
263+ }
264+ if ( Array . isArray ( syncState ?. events ) ) {
265+ return syncState . events ;
266+ }
267+ return null ;
268+ }
269+
270+ export function enqueueOverlayGameplayRuntimeSyncEvent ( target , event = { } ) {
271+ if ( ! target || typeof target !== 'object' ) {
272+ return false ;
273+ }
274+ if ( ! Array . isArray ( target . overlayRuntimeEvents ) ) {
275+ target . overlayRuntimeEvents = [ ] ;
276+ }
277+ if ( ! Array . isArray ( target . overlayRuntimeEvents ) ) {
278+ return false ;
279+ }
280+ target . overlayRuntimeEvents . push ( {
281+ type : String ( event ?. type || 'overlay-runtime-sync' ) . trim ( ) || 'overlay-runtime-sync' ,
282+ ...( event || { } ) ,
283+ } ) ;
284+ return true ;
285+ }
286+
287+ function normalizeOverlayRuntimeSyncEvent ( event ) {
288+ if ( ! event || typeof event !== 'object' ) {
289+ return null ;
290+ }
291+ const normalizedType = String ( event . type || 'overlay-runtime-sync' ) . trim ( ) || 'overlay-runtime-sync' ;
292+ const hasVisible = event . visible === true || event . visible === false ;
293+ const hasInteractionIndex = Number . isFinite ( Number ( event . interactionIndex ) ) ;
294+ const hasActiveOverlayId = String ( event . activeOverlayId || '' ) . trim ( ) . length > 0 ;
295+ if ( ! hasVisible && ! hasInteractionIndex && ! hasActiveOverlayId && normalizedType === 'overlay-runtime-sync' ) {
296+ return null ;
297+ }
298+ return {
299+ type : normalizedType ,
300+ visible : hasVisible ? event . visible : undefined ,
301+ interactionIndex : hasInteractionIndex ? Number ( event . interactionIndex ) : undefined ,
302+ activeOverlayId : hasActiveOverlayId ? String ( event . activeOverlayId || '' ) . trim ( ) : '' ,
303+ } ;
304+ }
305+
306+ function applyOverlayRuntimeSyncPatch ( runtime , runtimeExtensions , patch = { } ) {
307+ const count = runtimeExtensions . length ;
308+ let desyncCorrected = false ;
309+ if ( patch . visible === true || patch . visible === false ) {
310+ runtime . interactionVisible = patch . visible ;
311+ }
312+
313+ if ( Number . isFinite ( patch . interactionIndex ) && count > 0 ) {
314+ const incomingIndexRaw = Number ( patch . interactionIndex ) ;
315+ const incomingIndex = Math . trunc ( incomingIndexRaw ) ;
316+ if ( incomingIndex < 0 || incomingIndex >= count || incomingIndex !== incomingIndexRaw ) {
317+ desyncCorrected = true ;
318+ }
319+ runtime . interactionIndex = ( ( incomingIndex % count ) + count ) % count ;
320+ }
321+
322+ const requestedOverlayId = String ( patch . activeOverlayId || '' ) . trim ( ) ;
323+ const resolvedOverlayIndex = findRuntimeExtensionIndexByOverlayId ( runtime , requestedOverlayId ) ;
324+ if ( requestedOverlayId ) {
325+ if ( resolvedOverlayIndex >= 0 ) {
326+ if ( runtime . interactionIndex !== resolvedOverlayIndex ) {
327+ runtime . interactionIndex = resolvedOverlayIndex ;
328+ }
329+ } else {
330+ desyncCorrected = true ;
331+ }
332+ }
333+
334+ if ( Number . isFinite ( patch . interactionIndex ) && requestedOverlayId && resolvedOverlayIndex >= 0 && count > 0 ) {
335+ const incomingIndex = ( ( Math . trunc ( Number ( patch . interactionIndex ) ) % count ) + count ) % count ;
336+ if ( incomingIndex !== resolvedOverlayIndex ) {
337+ desyncCorrected = true ;
338+ }
339+ }
340+
341+ return desyncCorrected ;
342+ }
343+
257344function writeOverlayRuntimeSyncSnapshot ( container , snapshot ) {
258345 if ( ! container || typeof container !== 'object' || ! snapshot || typeof snapshot !== 'object' ) {
259346 return false ;
@@ -265,6 +352,8 @@ function writeOverlayRuntimeSyncSnapshot(container, snapshot) {
265352 container . count = snapshot . count ;
266353 container . cycleKey = snapshot . cycleKey ;
267354 container . desyncCorrected = snapshot . desyncCorrected ;
355+ container . eventsProcessed = snapshot . eventsProcessed ;
356+ container . syncMode = snapshot . syncMode ;
268357 return true ;
269358 } catch {
270359 return false ;
@@ -278,43 +367,32 @@ export function synchronizeOverlayGameplayRuntimeState(runtime, context = {}) {
278367
279368 const gameplayState = resolveOverlayGameplayState ( context ) ;
280369 const syncState = resolveOverlayRuntimeSyncStateContainer ( context , gameplayState ) ;
370+ const eventQueue = resolveOverlayRuntimeEventQueue ( context , gameplayState , syncState ) ;
281371 const runtimeExtensions = Array . isArray ( runtime . runtimeExtensions ) ? runtime . runtimeExtensions : [ ] ;
282372 const count = runtimeExtensions . length ;
283373 let desyncCorrected = false ;
284-
285- if ( syncState ) {
286- if ( syncState . visible === true || syncState . visible === false ) {
287- runtime . interactionVisible = syncState . visible ;
288- }
289-
290- const incomingIndexRaw = Number ( syncState . interactionIndex ) ;
291- const hasIncomingIndex = Number . isFinite ( incomingIndexRaw ) ;
292- if ( hasIncomingIndex && count > 0 ) {
293- const incomingIndex = Math . trunc ( incomingIndexRaw ) ;
294- if ( incomingIndex < 0 || incomingIndex >= count || incomingIndex !== incomingIndexRaw ) {
295- desyncCorrected = true ;
374+ let eventsProcessed = 0 ;
375+
376+ if ( Array . isArray ( eventQueue ) && eventQueue . length > 0 ) {
377+ const pending = eventQueue . splice ( 0 , eventQueue . length ) ;
378+ for ( let i = 0 ; i < pending . length ; i += 1 ) {
379+ const event = normalizeOverlayRuntimeSyncEvent ( pending [ i ] ) ;
380+ if ( ! event ) {
381+ continue ;
296382 }
297- runtime . interactionIndex = ( ( incomingIndex % count ) + count ) % count ;
298- }
299-
300- const requestedOverlayId = String ( syncState . activeOverlayId || '' ) . trim ( ) ;
301- const resolvedOverlayIndex = findRuntimeExtensionIndexByOverlayId ( runtime , requestedOverlayId ) ;
302- if ( requestedOverlayId ) {
303- if ( resolvedOverlayIndex >= 0 ) {
304- if ( runtime . interactionIndex !== resolvedOverlayIndex ) {
305- runtime . interactionIndex = resolvedOverlayIndex ;
306- }
307- } else {
308- desyncCorrected = true ;
383+ if ( event . type !== 'overlay-runtime-sync' && event . type !== 'overlay-state-sync' ) {
384+ continue ;
309385 }
386+ const corrected = applyOverlayRuntimeSyncPatch ( runtime , runtimeExtensions , event ) ;
387+ desyncCorrected = desyncCorrected || corrected ;
388+ eventsProcessed += 1 ;
310389 }
390+ }
311391
312- if ( hasIncomingIndex && requestedOverlayId && resolvedOverlayIndex >= 0 && count > 0 ) {
313- const incomingIndex = ( ( Math . trunc ( incomingIndexRaw ) % count ) + count ) % count ;
314- if ( incomingIndex !== resolvedOverlayIndex ) {
315- desyncCorrected = true ;
316- }
317- }
392+ // Compatibility fallback for pre-event producers.
393+ if ( eventsProcessed === 0 && syncState ) {
394+ const corrected = applyOverlayRuntimeSyncPatch ( runtime , runtimeExtensions , syncState ) ;
395+ desyncCorrected = desyncCorrected || corrected ;
318396 }
319397
320398 const interactionIndex = normalizeInteractionIndex ( runtime ) ;
@@ -326,6 +404,8 @@ export function synchronizeOverlayGameplayRuntimeState(runtime, context = {}) {
326404 count,
327405 cycleKey : String ( runtime . interactionCycleKey || LEVEL17_OVERLAY_CYCLE_KEY ) ,
328406 desyncCorrected,
407+ eventsProcessed,
408+ syncMode : eventsProcessed > 0 ? 'events' : 'compat' ,
329409 } ;
330410 writeOverlayRuntimeSyncSnapshot ( syncState , snapshot ) ;
331411 return snapshot ;
0 commit comments