@@ -11,6 +11,52 @@ import {
1111} from '/samples/phase-17/shared/overlayCycleInput.js' ;
1212
1313const overlayRuntimePreferenceMemoryStore = new Map ( ) ;
14+ const OVERLAY_RUNTIME_DEFAULT_PRESET_DEFINITIONS = Object . freeze ( [
15+ Object . freeze ( {
16+ id : 'minimal' ,
17+ label : 'Minimal' ,
18+ description : 'Hide overlays and keep controls lightweight.' ,
19+ profile : Object . freeze ( {
20+ visibility : false ,
21+ keybindProfile : Object . freeze ( {
22+ id : 'preset-minimal' ,
23+ cycleKey : LEVEL17_OVERLAY_CYCLE_KEY ,
24+ contextInputMap : null ,
25+ } ) ,
26+ } ) ,
27+ } ) ,
28+ Object . freeze ( {
29+ id : 'debug' ,
30+ label : 'Debug' ,
31+ description : 'Enable baseline debug overlays with default cycle key.' ,
32+ profile : Object . freeze ( {
33+ visibility : true ,
34+ keybindProfile : Object . freeze ( {
35+ id : 'preset-debug' ,
36+ cycleKey : LEVEL17_OVERLAY_CYCLE_KEY ,
37+ } ) ,
38+ } ) ,
39+ } ) ,
40+ Object . freeze ( {
41+ id : 'full-telemetry' ,
42+ label : 'Full Telemetry' ,
43+ description : 'Enable telemetry-focused overlay controls with top-layer emphasis mapping.' ,
44+ profile : Object . freeze ( {
45+ visibility : true ,
46+ keybindProfile : Object . freeze ( {
47+ id : 'preset-full-telemetry' ,
48+ cycleKey : LEVEL17_OVERLAY_CYCLE_KEY ,
49+ contextInputMap : Object . freeze ( {
50+ byStackPosition : Object . freeze ( {
51+ top : Object . freeze ( {
52+ 'cycle-next' : 'cycle-prev' ,
53+ } ) ,
54+ } ) ,
55+ } ) ,
56+ } ) ,
57+ } ) ,
58+ } ) ,
59+ ] ) ;
1460
1561function normalizeRuntimeExtensionEntry ( entry ) {
1662 if ( ! entry || typeof entry !== 'object' ) {
@@ -392,6 +438,107 @@ function applyOverlayRuntimePreferencePayload(runtime, validatedPayload) {
392438 return true ;
393439}
394440
441+ function createOverlayRuntimePreferencePayloadFromValidated ( validatedPayload ) {
442+ if ( ! validatedPayload || typeof validatedPayload !== 'object' ) {
443+ return null ;
444+ }
445+ const payload = {
446+ version : Number . isInteger ( Number ( validatedPayload . version ) ) && Number ( validatedPayload . version ) > 0
447+ ? Number ( validatedPayload . version )
448+ : 1 ,
449+ } ;
450+ if ( validatedPayload . hasVisibility ) {
451+ payload . visibility = validatedPayload . visibility === true ;
452+ }
453+ if ( validatedPayload . hasLayout ) {
454+ payload . layout = cloneJsonCompatibleValue ( validatedPayload . layout ) || { } ;
455+ }
456+ if ( validatedPayload . hasKeybindProfile ) {
457+ const keybindProfile = { } ;
458+ if ( Object . prototype . hasOwnProperty . call ( validatedPayload . keybindProfile , 'id' ) ) {
459+ keybindProfile . id = String ( validatedPayload . keybindProfile . id || '' ) . trim ( ) ;
460+ }
461+ if ( Object . prototype . hasOwnProperty . call ( validatedPayload . keybindProfile , 'cycleKey' ) ) {
462+ keybindProfile . cycleKey = String ( validatedPayload . keybindProfile . cycleKey || '' ) . trim ( ) || LEVEL17_OVERLAY_CYCLE_KEY ;
463+ }
464+ if ( validatedPayload . keybindProfile . contextInputMapSpecified === true ) {
465+ keybindProfile . contextInputMap = validatedPayload . keybindProfile . contextInputMap === null
466+ ? null
467+ : ( cloneJsonCompatibleValue ( validatedPayload . keybindProfile . contextInputMap ) || { } ) ;
468+ }
469+ payload . keybindProfile = keybindProfile ;
470+ }
471+ return payload ;
472+ }
473+
474+ function normalizeOverlayRuntimePresetEntry ( preset , index = 0 ) {
475+ if ( ! preset || typeof preset !== 'object' || Array . isArray ( preset ) ) {
476+ return null ;
477+ }
478+ const id = String ( preset . id || `preset-${ index + 1 } ` ) . trim ( ) ;
479+ if ( ! id ) {
480+ return null ;
481+ }
482+ const label = String ( preset . label || id ) . trim ( ) || id ;
483+ const description = String ( preset . description || '' ) . trim ( ) ;
484+ const profileInput = preset . profile && typeof preset . profile === 'object'
485+ ? preset . profile
486+ : ( preset . preferences && typeof preset . preferences === 'object' ? preset . preferences : null ) ;
487+ if ( ! profileInput ) {
488+ return null ;
489+ }
490+ const validatedProfile = validateOverlayRuntimePreferencePayload ( profileInput ) ;
491+ if ( ! validatedProfile . valid || ! validatedProfile . value ) {
492+ return null ;
493+ }
494+ const profilePayload = createOverlayRuntimePreferencePayloadFromValidated ( validatedProfile . value ) ;
495+ if ( ! profilePayload ) {
496+ return null ;
497+ }
498+ return Object . freeze ( {
499+ id,
500+ label,
501+ description,
502+ profile : Object . freeze ( profilePayload ) ,
503+ } ) ;
504+ }
505+
506+ function normalizeOverlayRuntimePresetLibrary ( presets ) {
507+ if ( ! Array . isArray ( presets ) || presets . length === 0 ) {
508+ return Object . freeze ( [ ] ) ;
509+ }
510+ const normalized = [ ] ;
511+ for ( let i = 0 ; i < presets . length ; i += 1 ) {
512+ const candidate = normalizeOverlayRuntimePresetEntry ( presets [ i ] , i ) ;
513+ if ( ! candidate ) {
514+ continue ;
515+ }
516+ if ( normalized . some ( ( entry ) => entry . id === candidate . id ) ) {
517+ continue ;
518+ }
519+ normalized . push ( candidate ) ;
520+ }
521+ return Object . freeze ( normalized ) ;
522+ }
523+
524+ const OVERLAY_RUNTIME_DEFAULT_PRESET_LIBRARY = normalizeOverlayRuntimePresetLibrary ( OVERLAY_RUNTIME_DEFAULT_PRESET_DEFINITIONS ) ;
525+
526+ function resolveOverlayRuntimePresetFromLibrary ( library = [ ] , presetOrId = '' ) {
527+ if ( typeof presetOrId === 'string' ) {
528+ const requestedId = String ( presetOrId || '' ) . trim ( ) ;
529+ if ( ! requestedId ) {
530+ return null ;
531+ }
532+ for ( let i = 0 ; i < library . length ; i += 1 ) {
533+ if ( library [ i ] ?. id === requestedId ) {
534+ return library [ i ] ;
535+ }
536+ }
537+ return null ;
538+ }
539+ return normalizeOverlayRuntimePresetEntry ( presetOrId ) ;
540+ }
541+
395542function buildOverlayRuntimeLayoutPreferenceSnapshot ( runtime ) {
396543 const snapshot = { } ;
397544 const overrides = runtime ?. interactionLayoutOverrides ;
@@ -1489,6 +1636,7 @@ export function createOverlayGameplayRuntime({
14891636 interactionGestureLastDown : false ,
14901637 interactionContextInputMap : null ,
14911638 interactionAdaptiveUiRules : Object . freeze ( [ ] ) ,
1639+ interactionPresetLibrary : Object . freeze ( [ ] ) ,
14921640 interactionPreferenceStorageKey : normalizeOverlayRuntimePreferenceStorageKey ( preferenceStorageKey ) ,
14931641 interactionPreferenceStorage : preferenceStorage && typeof preferenceStorage === 'object'
14941642 ? preferenceStorage
@@ -1534,6 +1682,75 @@ export function setOverlayGameplayRuntimePreferenceStorageKey(runtime, preferenc
15341682 return true ;
15351683}
15361684
1685+ export function getOverlayGameplayRuntimeDefaultPresets ( ) {
1686+ return OVERLAY_RUNTIME_DEFAULT_PRESET_LIBRARY ;
1687+ }
1688+
1689+ export function setOverlayGameplayRuntimePresetLibrary ( runtime , presets = [ ] ) {
1690+ if ( ! runtime ) {
1691+ return false ;
1692+ }
1693+ runtime . interactionPresetLibrary = normalizeOverlayRuntimePresetLibrary ( presets ) ;
1694+ return true ;
1695+ }
1696+
1697+ export function getOverlayGameplayRuntimePresetLibrary ( runtime , { includeDefaults = true } = { } ) {
1698+ const customLibrary = Array . isArray ( runtime ?. interactionPresetLibrary )
1699+ ? runtime . interactionPresetLibrary
1700+ : [ ] ;
1701+ if ( includeDefaults === false ) {
1702+ return customLibrary ;
1703+ }
1704+ if ( customLibrary . length === 0 ) {
1705+ return OVERLAY_RUNTIME_DEFAULT_PRESET_LIBRARY ;
1706+ }
1707+ const combined = [ ...OVERLAY_RUNTIME_DEFAULT_PRESET_LIBRARY ] ;
1708+ for ( let i = 0 ; i < customLibrary . length ; i += 1 ) {
1709+ const preset = customLibrary [ i ] ;
1710+ if ( ! preset || typeof preset !== 'object' ) {
1711+ continue ;
1712+ }
1713+ const existingIndex = combined . findIndex ( ( entry ) => entry . id === preset . id ) ;
1714+ if ( existingIndex >= 0 ) {
1715+ combined [ existingIndex ] = preset ;
1716+ continue ;
1717+ }
1718+ combined . push ( preset ) ;
1719+ }
1720+ return Object . freeze ( combined ) ;
1721+ }
1722+
1723+ export function applyOverlayGameplayRuntimePreset ( runtime , presetOrId , options = { } ) {
1724+ if ( ! runtime ) {
1725+ return Object . freeze ( {
1726+ success : false ,
1727+ errors : Object . freeze ( [ 'Overlay runtime is required for preset application.' ] ) ,
1728+ presetId : '' ,
1729+ } ) ;
1730+ }
1731+
1732+ const presetLibrary = getOverlayGameplayRuntimePresetLibrary ( runtime , {
1733+ includeDefaults : options ?. includeDefaults !== false ,
1734+ } ) ;
1735+ const resolvedPreset = resolveOverlayRuntimePresetFromLibrary ( presetLibrary , presetOrId ) ;
1736+ if ( ! resolvedPreset ) {
1737+ return Object . freeze ( {
1738+ success : false ,
1739+ errors : Object . freeze ( [ 'Requested overlay preset is missing or invalid.' ] ) ,
1740+ presetId : '' ,
1741+ } ) ;
1742+ }
1743+
1744+ const importResult = importOverlayGameplayRuntimeProfile ( runtime , resolvedPreset . profile , {
1745+ persist : options ?. persist !== false ,
1746+ } ) ;
1747+ return Object . freeze ( {
1748+ success : importResult . success === true ,
1749+ errors : importResult . errors ,
1750+ presetId : resolvedPreset . id ,
1751+ } ) ;
1752+ }
1753+
15371754export function setOverlayGameplayRuntimeKeybindProfile ( runtime , { id = '' , cycleKey = '' , contextInputMap = undefined } = { } ) {
15381755 if ( ! runtime ) {
15391756 return false ;
0 commit comments