@@ -24,6 +24,8 @@ function normalizeRuntimeExtensionEntry(entry) {
2424
2525 const layerOrderRaw = Number ( entry . layerOrder ) ;
2626 const layerOrder = Number . isFinite ( layerOrderRaw ) ? layerOrderRaw : 0 ;
27+ const visualPriorityRaw = Number ( entry . visualPriority ) ;
28+ const visualPriority = Number . isFinite ( visualPriorityRaw ) ? visualPriorityRaw : layerOrder ;
2729 const compose = entry . compose === true ;
2830 const panelWidthRaw = Number ( entry . panelWidth ) ;
2931 const panelHeightRaw = Number ( entry . panelHeight ) ;
@@ -36,6 +38,7 @@ function normalizeRuntimeExtensionEntry(entry) {
3638 onRender,
3739 compose,
3840 layerOrder,
41+ visualPriority,
3942 panelWidth,
4043 panelHeight,
4144 } ) ;
@@ -171,6 +174,87 @@ function getComposedRuntimeFrames(runtime, activeOverlayId) {
171174 return frames ;
172175}
173176
177+ function deriveRenderHierarchy ( frames ) {
178+ if ( ! Array . isArray ( frames ) || frames . length === 0 ) {
179+ return [ ] ;
180+ }
181+
182+ const ordered = [ ...frames ] ;
183+ ordered . sort ( ( left , right ) => {
184+ const leftIsActive = left . isActive === true ;
185+ const rightIsActive = right . isActive === true ;
186+ if ( leftIsActive !== rightIsActive ) {
187+ return leftIsActive ? 1 : - 1 ;
188+ }
189+ if ( left . extension . visualPriority !== right . extension . visualPriority ) {
190+ return left . extension . visualPriority - right . extension . visualPriority ;
191+ }
192+ if ( left . extension . layerOrder !== right . extension . layerOrder ) {
193+ return left . extension . layerOrder - right . extension . layerOrder ;
194+ }
195+ return left . registrationIndex - right . registrationIndex ;
196+ } ) ;
197+
198+ for ( let i = 0 ; i < ordered . length ; i += 1 ) {
199+ const frame = ordered [ i ] ;
200+ frame . visualPriorityRank = i ;
201+ frame . visualTier = frame . isActive === true ? 'primary' : 'secondary' ;
202+ frame . readabilityOpacity = frame . isActive === true ? 1 : 0.84 ;
203+ frame . hiddenByClutter = false ;
204+ }
205+
206+ return ordered ;
207+ }
208+
209+ function resolveMaxVisibleCompositionLayers ( renderer ) {
210+ const canvasSize = renderer ?. getCanvasSize ?. ( ) || { width : 960 , height : 540 } ;
211+ const height = Math . max ( 180 , Number ( canvasSize . height ) || 540 ) ;
212+ if ( height <= 360 ) {
213+ return 2 ;
214+ }
215+ if ( height <= 540 ) {
216+ return 3 ;
217+ }
218+ return 4 ;
219+ }
220+
221+ function applyCompositionReadabilityLimits ( frames , renderer ) {
222+ if ( ! Array . isArray ( frames ) || frames . length === 0 ) {
223+ return [ ] ;
224+ }
225+
226+ const maxVisibleLayers = Math . max ( 2 , resolveMaxVisibleCompositionLayers ( renderer ) ) ;
227+ for ( let i = 0 ; i < frames . length ; i += 1 ) {
228+ frames [ i ] . hiddenByClutter = false ;
229+ }
230+ if ( frames . length <= maxVisibleLayers ) {
231+ return frames ;
232+ }
233+
234+ const activeFrame = frames . find ( ( frame ) => frame . isActive === true ) || null ;
235+ const selected = [ ] ;
236+ if ( activeFrame ) {
237+ const supportFrames = frames . filter ( ( frame ) => frame !== activeFrame ) ;
238+ const supportLimit = Math . max ( 0 , maxVisibleLayers - 1 ) ;
239+ const start = Math . max ( 0 , supportFrames . length - supportLimit ) ;
240+ for ( let i = start ; i < supportFrames . length ; i += 1 ) {
241+ selected . push ( supportFrames [ i ] ) ;
242+ }
243+ selected . push ( activeFrame ) ;
244+ } else {
245+ const start = Math . max ( 0 , frames . length - maxVisibleLayers ) ;
246+ for ( let i = start ; i < frames . length ; i += 1 ) {
247+ selected . push ( frames [ i ] ) ;
248+ }
249+ }
250+
251+ const selectedSet = new Set ( selected ) ;
252+ for ( let i = 0 ; i < frames . length ; i += 1 ) {
253+ frames [ i ] . hiddenByClutter = ! selectedSet . has ( frames [ i ] ) ;
254+ }
255+ return frames . filter ( ( frame ) => selectedSet . has ( frame ) ) ;
256+ }
257+
174258function attachCompositionSlots ( frames , renderer , safeZones = [ ] ) {
175259 if ( ! Array . isArray ( frames ) || frames . length === 0 ) {
176260 return frames || [ ] ;
@@ -370,16 +454,24 @@ export function getOverlayGameplayRuntimeInteractionSnapshot(runtime) {
370454export function getOverlayGameplayRuntimeCompositionSnapshot ( runtime , context = { } ) {
371455 const activeOverlayId = String ( context ?. activeOverlayId || '' ) . trim ( ) ;
372456 const safeZones = resolveLayoutSafeZones ( context ) ;
373- const frames = attachCompositionSlots (
457+ const frames = deriveRenderHierarchy ( attachCompositionSlots (
374458 getComposedRuntimeFrames ( runtime , activeOverlayId ) ,
375459 context ?. renderer ,
376460 safeZones
377- ) ;
461+ ) ) ;
462+ const visibleFrames = applyCompositionReadabilityLimits ( frames , context ?. renderer ) ;
463+ const visibleSet = new Set ( visibleFrames ) ;
378464 return frames . map ( ( frame , index ) => ( {
379465 index,
380466 count : frames . length ,
467+ visibleCount : visibleFrames . length ,
381468 registrationIndex : frame . registrationIndex ,
382469 layerOrder : frame . extension . layerOrder ,
470+ visualPriority : frame . extension . visualPriority ,
471+ visualPriorityRank : frame . visualPriorityRank ,
472+ visualTier : frame . visualTier ,
473+ readabilityOpacity : frame . readabilityOpacity ,
474+ hiddenByClutter : ! visibleSet . has ( frame ) ,
383475 compose : frame . extension . compose === true ,
384476 isActive : frame . isActive === true ,
385477 overlayId : frame . extension . overlayId ,
@@ -521,10 +613,15 @@ export function renderOverlayGameplayRuntime(runtime, context = {}) {
521613
522614 const activeOverlayId = String ( context . activeOverlayId || '' ) . trim ( ) ;
523615 const safeZones = resolveLayoutSafeZones ( context ) ;
524- const frames = attachCompositionSlots (
525- getComposedRuntimeFrames ( runtime , activeOverlayId ) ,
526- context . renderer ,
527- safeZones
616+ const frames = applyCompositionReadabilityLimits (
617+ deriveRenderHierarchy (
618+ attachCompositionSlots (
619+ getComposedRuntimeFrames ( runtime , activeOverlayId ) ,
620+ context . renderer ,
621+ safeZones
622+ )
623+ ) ,
624+ context . renderer
528625 ) ;
529626 if ( frames . length === 0 ) {
530627 return 0 ;
@@ -544,6 +641,11 @@ export function renderOverlayGameplayRuntime(runtime, context = {}) {
544641 count : frames . length ,
545642 registrationIndex : frame . registrationIndex ,
546643 layerOrder : frame . extension . layerOrder ,
644+ visualPriority : frame . extension . visualPriority ,
645+ visualPriorityRank : frame . visualPriorityRank ,
646+ visualTier : frame . visualTier ,
647+ readabilityOpacity : frame . readabilityOpacity ,
648+ hiddenByClutter : frame . hiddenByClutter === true ,
547649 compose : frame . extension . compose === true ,
548650 isActive : frame . isActive === true ,
549651 slot : frame . slot ,
0 commit comments