Skip to content

Commit 003a6b3

Browse files
author
DavidQ
committed
Add overlay user sharing system.
- Enables sharing of overlay profiles and presets - Supports cross-environment portability
1 parent 57e0835 commit 003a6b3

7 files changed

Lines changed: 323 additions & 22 deletions

File tree

docs/dev/CODEX_COMMANDS.md

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

44
COMMAND:
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
5+
Implement overlay user sharing system:
6+
- Create shareable profile packages
7+
- Import with validation and compatibility checks
8+
- Integrate with preset and persistence systems
109
- Update roadmap status only

docs/dev/COMMIT_COMMENT.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Add overlay preset library.
1+
Add overlay user sharing system.
22

3-
- Provides predefined configurations
4-
- Enables quick switching of overlay setups
3+
- Enables sharing of overlay profiles and presets
4+
- Supports cross-environment portability
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[ ] Presets load correctly
2-
[ ] Presets apply without conflicts
3-
[ ] Compatible with persistence
1+
[ ] Share package export works
2+
[ ] Import works across environments
3+
[ ] Validation prevents incompatible data
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
@@ -641,6 +641,7 @@
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)
643643
- [x] Level 22 overlay preset library added (schema, defaults, and preset-to-profile apply with export/import+persistence compatibility)
644+
- [x] Level 22 overlay user sharing system added (shareable profile packages with validated import compatibility across preset and persistence systems)
644645

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

docs/pr/BUILD_PR.md

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

33
## Purpose
4-
Provide a library of predefined overlay configurations (presets).
4+
Enable sharing of overlay profiles and presets between users.
55

66
## Scope
7-
- Define preset schema
8-
- Include default presets (minimal, debug, full telemetry)
9-
- Allow loading presets into active profile
7+
- Generate shareable profile packages (JSON)
8+
- Import shared profiles safely
9+
- Provide version compatibility checks
1010

1111
## Test Steps
12-
1. Load preset
13-
2. Verify overlay changes
14-
3. Ensure compatibility with persistence
12+
1. Export share package
13+
2. Import into another environment
14+
3. Validate correct behavior
1515

1616
## Expected
17-
- Presets apply correctly
18-
- No conflicts with user settings
17+
- Profiles transferable between users
18+
- Safe import with validation

samples/phase-17/shared/overlayGameplayRuntime.js

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

1313
const overlayRuntimePreferenceMemoryStore = new Map();
14+
const OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT = 'overlay-runtime-share-package';
15+
const OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION = 1;
16+
const OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION = 1;
1417
const OVERLAY_RUNTIME_DEFAULT_PRESET_DEFINITIONS = Object.freeze([
1518
Object.freeze({
1619
id: 'minimal',
@@ -539,6 +542,96 @@ function resolveOverlayRuntimePresetFromLibrary(library = [], presetOrId = '') {
539542
return normalizeOverlayRuntimePresetEntry(presetOrId);
540543
}
541544

545+
function validateOverlayRuntimeSharePackagePayload(payload, runtime = null) {
546+
const errors = [];
547+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
548+
return {
549+
valid: false,
550+
errors: Object.freeze(['Overlay runtime share package must be an object.']),
551+
value: null,
552+
};
553+
}
554+
555+
const format = String(payload.format || '').trim();
556+
if (format !== OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT) {
557+
errors.push(`Overlay runtime share package format must be "${OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT}".`);
558+
}
559+
560+
const packageVersion = Number(payload.packageVersion);
561+
if (!Number.isInteger(packageVersion) || packageVersion <= 0) {
562+
errors.push('Overlay runtime share package version must be a positive integer.');
563+
} else if (packageVersion > OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION) {
564+
errors.push(`Overlay runtime share package version ${packageVersion} is not supported by this runtime.`);
565+
}
566+
567+
if (payload.compatibility !== undefined) {
568+
if (!payload.compatibility || typeof payload.compatibility !== 'object' || Array.isArray(payload.compatibility)) {
569+
errors.push('Overlay runtime share package compatibility must be an object when provided.');
570+
} else if (payload.compatibility.profileSchemaVersion !== undefined) {
571+
const profileSchemaVersion = Number(payload.compatibility.profileSchemaVersion);
572+
if (!Number.isInteger(profileSchemaVersion) || profileSchemaVersion <= 0) {
573+
errors.push('Overlay runtime share package compatibility.profileSchemaVersion must be a positive integer when provided.');
574+
} else if (profileSchemaVersion > OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION) {
575+
errors.push(
576+
`Overlay runtime share package profile schema version ${profileSchemaVersion} is not supported by this runtime.`
577+
);
578+
}
579+
}
580+
}
581+
582+
const validatedProfile = validateOverlayRuntimePreferencePayload(payload.profile);
583+
if (!validatedProfile.valid || !validatedProfile.value) {
584+
for (let i = 0; i < validatedProfile.errors.length; i += 1) {
585+
errors.push(`Share package profile: ${validatedProfile.errors[i]}`);
586+
}
587+
}
588+
589+
let preset = null;
590+
if (Object.prototype.hasOwnProperty.call(payload, 'preset')) {
591+
const normalizedPreset = normalizeOverlayRuntimePresetEntry(payload.preset, 0);
592+
if (!normalizedPreset) {
593+
errors.push('Overlay runtime share package preset is invalid.');
594+
} else {
595+
preset = normalizedPreset;
596+
}
597+
}
598+
599+
const rawPresetId = String(payload.presetId || '').trim();
600+
const presetId = rawPresetId || (preset ? preset.id : '');
601+
if (rawPresetId && preset && preset.id !== rawPresetId) {
602+
errors.push('Overlay runtime share package presetId does not match included preset id.');
603+
}
604+
if (presetId && !preset) {
605+
const availablePreset = resolveOverlayRuntimePresetFromLibrary(
606+
getOverlayGameplayRuntimePresetLibrary(runtime, { includeDefaults: true }),
607+
presetId
608+
);
609+
if (!availablePreset) {
610+
errors.push(`Overlay runtime share package requires preset "${presetId}" which is not available in this runtime.`);
611+
}
612+
}
613+
614+
if (errors.length > 0) {
615+
return {
616+
valid: false,
617+
errors: Object.freeze(errors),
618+
value: null,
619+
};
620+
}
621+
622+
return {
623+
valid: true,
624+
errors: Object.freeze([]),
625+
value: Object.freeze({
626+
format,
627+
packageVersion,
628+
profile: validatedProfile.value,
629+
presetId,
630+
preset,
631+
}),
632+
};
633+
}
634+
542635
function buildOverlayRuntimeLayoutPreferenceSnapshot(runtime) {
543636
const snapshot = {};
544637
const overrides = runtime?.interactionLayoutOverrides;
@@ -1802,14 +1895,53 @@ export function getOverlayGameplayRuntimePreferencesSnapshot(runtime) {
18021895
export function exportOverlayGameplayRuntimeProfile(runtime, { pretty = false } = {}) {
18031896
const snapshot = getOverlayGameplayRuntimePreferencesSnapshot(runtime);
18041897
const payload = {
1805-
version: 1,
1898+
version: OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION,
18061899
visibility: snapshot.visibility,
18071900
layout: snapshot.layout,
18081901
keybindProfile: snapshot.keybindProfile,
18091902
};
18101903
return JSON.stringify(payload, null, pretty ? 2 : 0);
18111904
}
18121905

1906+
export function exportOverlayGameplayRuntimeSharePackage(runtime, options = {}) {
1907+
const snapshot = getOverlayGameplayRuntimePreferencesSnapshot(runtime);
1908+
const profile = {
1909+
version: OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION,
1910+
visibility: snapshot.visibility,
1911+
layout: snapshot.layout,
1912+
keybindProfile: snapshot.keybindProfile,
1913+
};
1914+
1915+
const sharePackage = {
1916+
format: OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT,
1917+
packageVersion: OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION,
1918+
compatibility: {
1919+
profileSchemaVersion: OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION,
1920+
},
1921+
source: String(options?.source || 'overlay-gameplay-runtime').trim() || 'overlay-gameplay-runtime',
1922+
exportedAt: typeof options?.exportedAt === 'string' && options.exportedAt.trim()
1923+
? options.exportedAt.trim()
1924+
: new Date().toISOString(),
1925+
profile,
1926+
};
1927+
1928+
const hasPresetSelection = options?.presetOrId !== undefined && options?.presetOrId !== null && options?.presetOrId !== '';
1929+
if (hasPresetSelection) {
1930+
const presetLibrary = getOverlayGameplayRuntimePresetLibrary(runtime, {
1931+
includeDefaults: options?.includeDefaults !== false,
1932+
});
1933+
const resolvedPreset = resolveOverlayRuntimePresetFromLibrary(presetLibrary, options.presetOrId);
1934+
if (resolvedPreset) {
1935+
sharePackage.presetId = resolvedPreset.id;
1936+
if (options?.includePreset !== false) {
1937+
sharePackage.preset = resolvedPreset;
1938+
}
1939+
}
1940+
}
1941+
1942+
return JSON.stringify(sharePackage, null, options?.pretty === true ? 2 : 0);
1943+
}
1944+
18131945
export function saveOverlayGameplayRuntimePreferences(runtime, options = {}) {
18141946
if (!runtime) {
18151947
return false;
@@ -1894,6 +2026,70 @@ export function importOverlayGameplayRuntimeProfile(runtime, profileInput, optio
18942026
});
18952027
}
18962028

2029+
export function importOverlayGameplayRuntimeSharePackage(runtime, sharePackageInput, options = {}) {
2030+
if (!runtime) {
2031+
return Object.freeze({
2032+
success: false,
2033+
errors: Object.freeze(['Overlay runtime is required for share package import.']),
2034+
presetId: '',
2035+
presetRegistered: false,
2036+
});
2037+
}
2038+
2039+
let parsedInput = null;
2040+
if (typeof sharePackageInput === 'string') {
2041+
try {
2042+
parsedInput = JSON.parse(sharePackageInput);
2043+
} catch {
2044+
return Object.freeze({
2045+
success: false,
2046+
errors: Object.freeze(['Overlay runtime share package JSON is invalid.']),
2047+
presetId: '',
2048+
presetRegistered: false,
2049+
});
2050+
}
2051+
} else {
2052+
parsedInput = cloneJsonCompatibleValue(sharePackageInput);
2053+
}
2054+
2055+
const validated = validateOverlayRuntimeSharePackagePayload(parsedInput, runtime);
2056+
if (!validated.valid || !validated.value) {
2057+
return Object.freeze({
2058+
success: false,
2059+
errors: validated.errors,
2060+
presetId: '',
2061+
presetRegistered: false,
2062+
});
2063+
}
2064+
2065+
let presetRegistered = false;
2066+
if (validated.value.preset && options?.registerPreset !== false) {
2067+
const customLibrary = getOverlayGameplayRuntimePresetLibrary(runtime, { includeDefaults: false });
2068+
setOverlayGameplayRuntimePresetLibrary(runtime, [...customLibrary, validated.value.preset]);
2069+
presetRegistered = true;
2070+
}
2071+
2072+
const profilePayload = createOverlayRuntimePreferencePayloadFromValidated(validated.value.profile);
2073+
const importResult = importOverlayGameplayRuntimeProfile(runtime, profilePayload, {
2074+
persist: options?.persist !== false,
2075+
});
2076+
if (importResult.success !== true) {
2077+
return Object.freeze({
2078+
success: false,
2079+
errors: importResult.errors,
2080+
presetId: validated.value.presetId,
2081+
presetRegistered,
2082+
});
2083+
}
2084+
2085+
return Object.freeze({
2086+
success: true,
2087+
errors: Object.freeze([]),
2088+
presetId: validated.value.presetId,
2089+
presetRegistered,
2090+
});
2091+
}
2092+
18972093
export function setOverlayGameplayRuntimeContextInputMap(runtime, contextInputMap) {
18982094
if (!runtime) {
18992095
return false;

0 commit comments

Comments
 (0)