Skip to content

Commit 57e0835

Browse files
author
DavidQ
committed
Add overlay preset library.
- Provides predefined configurations - Enables quick switching of overlay setups
1 parent ea2ac27 commit 57e0835

7 files changed

Lines changed: 321 additions & 18 deletions

File tree

docs/dev/CODEX_COMMANDS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ MODEL: GPT-5.4
22
REASONING: medium
33

44
COMMAND:
5-
Implement profile export/import:
6-
- Export overlay preferences to JSON
7-
- Import with validation
8-
- Ensure compatibility with persistence system
5+
Implement overlay preset library:
6+
- Define preset schema
7+
- Provide default presets
8+
- Allow applying presets to profiles
9+
- Ensure compatibility with export/import and persistence
910
- Update roadmap status only

docs/dev/COMMIT_COMMENT.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
Add overlay profile export/import.
1+
Add overlay preset library.
22

3-
- Enables sharing and portability of user settings
3+
- Provides predefined configurations
4+
- Enables quick switching of overlay setups
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[ ] Export works
2-
[ ] Import works
3-
[ ] Validation prevents bad data
1+
[ ] Presets load correctly
2+
[ ] Presets apply without conflicts
3+
[ ] Compatible with persistence
44
[ ] Roadmap updated

docs/dev/roadmaps/MASTER_ROADMAP_HIGH_LEVEL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@
640640
- [x] Level 22 adaptive overlay UI rules added (visibility/size/emphasis responsive to gameplay state, overlay context, and telemetry)
641641
- [x] Level 22 overlay preferences persistence added (visibility, layout overrides, and keybind profile restored on runtime load)
642642
- [x] Level 22 overlay profile export/import added (validated JSON portability with persistence-system compatibility)
643+
- [x] Level 22 overlay preset library added (schema, defaults, and preset-to-profile apply with export/import+persistence compatibility)
643644

644645
### Sample Phase Tracks
645646
- [x] 3D phase normalized

docs/pr/BUILD_PR.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
# BUILD_PR_LEVEL_22_6_OVERLAY_PROFILE_EXPORT_IMPORT
1+
# BUILD_PR_LEVEL_22_7_OVERLAY_PRESET_LIBRARY
22

33
## Purpose
4-
Enable export and import of overlay user profiles.
4+
Provide a library of predefined overlay configurations (presets).
55

66
## Scope
7-
- Export preferences to JSON
8-
- Import preferences from JSON
9-
- Validate schema and compatibility
7+
- Define preset schema
8+
- Include default presets (minimal, debug, full telemetry)
9+
- Allow loading presets into active profile
1010

1111
## Test Steps
12-
1. Export profile
13-
2. Import profile
14-
3. Verify settings applied
12+
1. Load preset
13+
2. Verify overlay changes
14+
3. Ensure compatibility with persistence
1515

1616
## Expected
17-
- Profiles portable across sessions/devices
17+
- Presets apply correctly
18+
- No conflicts with user settings

samples/phase-17/shared/overlayGameplayRuntime.js

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,52 @@ import {
1111
} from '/samples/phase-17/shared/overlayCycleInput.js';
1212

1313
const 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

1561
function 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+
395542
function 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+
15371754
export function setOverlayGameplayRuntimeKeybindProfile(runtime, { id = '', cycleKey = '', contextInputMap = undefined } = {}) {
15381755
if (!runtime) {
15391756
return false;

0 commit comments

Comments
 (0)