aurasu 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/aura.capabilities.json +26 -0
- package/aura.config.json +1 -1
- package/bin/play.js +1185 -18
- package/config/gameplay/game.config.js +11 -0
- package/content/gameplay/beatmap.content.js +17 -0
- package/package.json +9 -3
- package/prefabs/playfield.prefab.js +17 -0
- package/scenes/gameplay.scene.js +2255 -0
- package/src/main.js +12 -2224
- package/src/runtime/app-state.js +100 -0
- package/src/runtime/app.js +1060 -0
- package/src/runtime/capabilities.js +14 -0
- package/src/runtime/project-inspector.js +615 -0
- package/src/runtime/project-registry.js +91 -0
- package/src/runtime/scene-flow.js +290 -0
- package/src/runtime/scene-registry.js +25 -0
- package/src/runtime/screen-shell.js +222 -0
- package/src/runtime/ui-settings.js +227 -0
- package/src/runtime/ui-theme.js +297 -0
- package/ui/hud.screen.js +14 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAppState,
|
|
3
|
+
ensureSessionState,
|
|
4
|
+
ensureUiState,
|
|
5
|
+
normalizeAppState,
|
|
6
|
+
} from './app-state.js';
|
|
7
|
+
import {
|
|
8
|
+
applyUiTheme as applySharedUiTheme,
|
|
9
|
+
ensureUiPreferences as ensureSharedUiPreferences,
|
|
10
|
+
inspectUiTheme as inspectSharedUiTheme,
|
|
11
|
+
resetUiPreferences as resetSharedUiPreferences,
|
|
12
|
+
updateUiPreferences as updateSharedUiPreferences,
|
|
13
|
+
} from './ui-theme.js';
|
|
14
|
+
import projectRegistry, { resolveProjectScreen } from './project-registry.js';
|
|
15
|
+
import { createProjectInspector } from './project-inspector.js';
|
|
16
|
+
import { createSceneRegistry } from './scene-registry.js';
|
|
17
|
+
import { createSceneFlow } from './scene-flow.js';
|
|
18
|
+
import { createScreenShell } from './screen-shell.js';
|
|
19
|
+
import { assertRuntimeCapabilities } from './capabilities.js';
|
|
20
|
+
|
|
21
|
+
const PROJECT_STATE_SCHEMA = "aurajs.authored-project-state.v1";
|
|
22
|
+
const PROJECT_CONTINUITY_STATE_SCHEMA = 'aurajs.project-continuity-state.v1';
|
|
23
|
+
const PROJECT_CONTINUITY_STATE_VERSION = 1;
|
|
24
|
+
|
|
25
|
+
function cloneProjectValue(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map((entry) => cloneProjectValue(entry));
|
|
28
|
+
}
|
|
29
|
+
if (value && typeof value === 'object') {
|
|
30
|
+
const output = {};
|
|
31
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
32
|
+
output[key] = cloneProjectValue(entry);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function captureSceneSnapshot(scene, sceneId = null) {
|
|
40
|
+
if (!scene || typeof scene !== 'object') {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
sceneId: sceneId || null,
|
|
44
|
+
reasonCode: 'project_state_scene_missing',
|
|
45
|
+
detail: null,
|
|
46
|
+
hasSnapshot: false,
|
|
47
|
+
snapshot: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const exportFn = typeof scene.exportState === 'function'
|
|
51
|
+
? scene.exportState
|
|
52
|
+
: typeof scene.getState === 'function'
|
|
53
|
+
? scene.getState
|
|
54
|
+
: null;
|
|
55
|
+
if (!exportFn) {
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
sceneId: sceneId || null,
|
|
59
|
+
reasonCode: 'project_state_scene_skipped',
|
|
60
|
+
detail: null,
|
|
61
|
+
hasSnapshot: false,
|
|
62
|
+
snapshot: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const snapshot = exportFn.call(scene);
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
sceneId: sceneId || null,
|
|
70
|
+
reasonCode: snapshot == null ? 'project_state_scene_empty' : 'project_state_scene_capture_ok',
|
|
71
|
+
detail: null,
|
|
72
|
+
hasSnapshot: snapshot != null,
|
|
73
|
+
snapshot: snapshot == null ? null : cloneProjectValue(snapshot),
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
sceneId: sceneId || null,
|
|
79
|
+
reasonCode: 'project_state_scene_capture_failed',
|
|
80
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
81
|
+
hasSnapshot: false,
|
|
82
|
+
snapshot: null,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function applySceneSnapshot(scene, snapshot, sceneId = null) {
|
|
88
|
+
if (!scene || snapshot == null) {
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
sceneId: sceneId || null,
|
|
92
|
+
reasonCode: 'project_state_scene_skipped',
|
|
93
|
+
detail: null,
|
|
94
|
+
restored: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const applyFn = typeof scene.applyState === 'function'
|
|
98
|
+
? scene.applyState
|
|
99
|
+
: typeof scene.importState === 'function'
|
|
100
|
+
? scene.importState
|
|
101
|
+
: null;
|
|
102
|
+
if (!applyFn) {
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
sceneId: sceneId || null,
|
|
106
|
+
reasonCode: 'project_state_scene_skipped',
|
|
107
|
+
detail: null,
|
|
108
|
+
restored: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = applyFn.call(scene, cloneProjectValue(snapshot));
|
|
113
|
+
if (result === false) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
sceneId: sceneId || null,
|
|
117
|
+
reasonCode: 'project_state_scene_apply_failed',
|
|
118
|
+
detail: null,
|
|
119
|
+
restored: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (result && typeof result === 'object' && result.ok === false) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
sceneId: sceneId || null,
|
|
126
|
+
reasonCode: typeof result.reasonCode === 'string' ? result.reasonCode : 'project_state_scene_apply_failed',
|
|
127
|
+
detail: result.detail || null,
|
|
128
|
+
restored: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
sceneId: sceneId || null,
|
|
134
|
+
reasonCode: result && typeof result.reasonCode === 'string'
|
|
135
|
+
? result.reasonCode
|
|
136
|
+
: 'project_state_scene_apply_ok',
|
|
137
|
+
detail: result && typeof result === 'object' ? result.detail || null : null,
|
|
138
|
+
restored: true,
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
sceneId: sceneId || null,
|
|
144
|
+
reasonCode: 'project_state_scene_apply_failed',
|
|
145
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
146
|
+
restored: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeProjectContinuityState(snapshot, fallbackTemplate) {
|
|
152
|
+
if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const source = snapshot.continuity && typeof snapshot.continuity === 'object' && !Array.isArray(snapshot.continuity)
|
|
157
|
+
? snapshot.continuity
|
|
158
|
+
: snapshot;
|
|
159
|
+
const sceneStateSource = source.sceneStates && typeof source.sceneStates === 'object' && !Array.isArray(source.sceneStates)
|
|
160
|
+
? source.sceneStates
|
|
161
|
+
: source.scenes && typeof source.scenes === 'object' && !Array.isArray(source.scenes)
|
|
162
|
+
? source.scenes
|
|
163
|
+
: {};
|
|
164
|
+
const sceneStates = {};
|
|
165
|
+
for (const sceneId of Object.keys(sceneStateSource).sort()) {
|
|
166
|
+
sceneStates[sceneId] = cloneProjectValue(sceneStateSource[sceneId]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
schema: PROJECT_CONTINUITY_STATE_SCHEMA,
|
|
171
|
+
version: Number.isInteger(Number(source.version)) && Number(source.version) > 0
|
|
172
|
+
? Number(source.version)
|
|
173
|
+
: PROJECT_CONTINUITY_STATE_VERSION,
|
|
174
|
+
template: typeof source.template === 'string' && source.template.trim()
|
|
175
|
+
? source.template.trim()
|
|
176
|
+
: fallbackTemplate,
|
|
177
|
+
activeSceneId: typeof source.activeSceneId === 'string' && source.activeSceneId.trim()
|
|
178
|
+
? source.activeSceneId.trim()
|
|
179
|
+
: null,
|
|
180
|
+
currentSceneData: cloneProjectValue(source.currentSceneData ?? null),
|
|
181
|
+
appState: source.appState && typeof source.appState === 'object' && !Array.isArray(source.appState)
|
|
182
|
+
? cloneProjectValue(source.appState)
|
|
183
|
+
: null,
|
|
184
|
+
sceneFlow: source.sceneFlow && typeof source.sceneFlow === 'object' && !Array.isArray(source.sceneFlow)
|
|
185
|
+
? cloneProjectValue(source.sceneFlow)
|
|
186
|
+
: null,
|
|
187
|
+
screenShell: source.screenShell && typeof source.screenShell === 'object' && !Array.isArray(source.screenShell)
|
|
188
|
+
? cloneProjectValue(source.screenShell)
|
|
189
|
+
: null,
|
|
190
|
+
sceneStates,
|
|
191
|
+
ownership: source.ownership && typeof source.ownership === 'object' && !Array.isArray(source.ownership)
|
|
192
|
+
? cloneProjectValue(source.ownership)
|
|
193
|
+
: null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeContinuitySource(source) {
|
|
198
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
kind: typeof source.kind === 'string' && source.kind.trim() ? source.kind.trim() : null,
|
|
203
|
+
name: typeof source.name === 'string' && source.name.trim() ? source.name.trim() : null,
|
|
204
|
+
path: typeof source.path === 'string' && source.path.trim() ? source.path.trim() : null,
|
|
205
|
+
stateSchemaVersion: typeof source.stateSchemaVersion === 'string' && source.stateSchemaVersion.trim()
|
|
206
|
+
? source.stateSchemaVersion.trim()
|
|
207
|
+
: null,
|
|
208
|
+
payloadFingerprint: typeof source.payloadFingerprint === 'string' && source.payloadFingerprint.trim()
|
|
209
|
+
? source.payloadFingerprint.trim()
|
|
210
|
+
: null,
|
|
211
|
+
reasonCode: typeof source.reasonCode === 'string' && source.reasonCode.trim()
|
|
212
|
+
? source.reasonCode.trim()
|
|
213
|
+
: null,
|
|
214
|
+
migration: source.migration && typeof source.migration === 'object' && !Array.isArray(source.migration)
|
|
215
|
+
? cloneProjectValue(source.migration)
|
|
216
|
+
: null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function createContinuitySummary(
|
|
221
|
+
sceneResults,
|
|
222
|
+
{
|
|
223
|
+
phase = 'restore',
|
|
224
|
+
reasonCode = phase === 'capture' ? 'project_state_capture_ok' : 'project_state_apply_ok',
|
|
225
|
+
activeSceneId = null,
|
|
226
|
+
continuityVersion = PROJECT_CONTINUITY_STATE_VERSION,
|
|
227
|
+
detail = null,
|
|
228
|
+
source = null,
|
|
229
|
+
} = {},
|
|
230
|
+
) {
|
|
231
|
+
const normalizedSceneResults = Array.isArray(sceneResults)
|
|
232
|
+
? sceneResults.map((entry) => cloneProjectValue(entry))
|
|
233
|
+
: [];
|
|
234
|
+
const skippedSceneIds = normalizedSceneResults
|
|
235
|
+
.filter((entry) => entry?.reasonCode === 'project_state_scene_skipped')
|
|
236
|
+
.map((entry) => entry.sceneId)
|
|
237
|
+
.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
238
|
+
const failedSceneIds = normalizedSceneResults
|
|
239
|
+
.filter((entry) => entry?.ok === false)
|
|
240
|
+
.map((entry) => entry.sceneId)
|
|
241
|
+
.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
242
|
+
const capturedSceneIds = normalizedSceneResults
|
|
243
|
+
.filter((entry) => entry?.hasSnapshot === true)
|
|
244
|
+
.map((entry) => entry.sceneId)
|
|
245
|
+
.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
246
|
+
const restoredSceneIds = normalizedSceneResults
|
|
247
|
+
.filter((entry) => entry?.restored === true)
|
|
248
|
+
.map((entry) => entry.sceneId)
|
|
249
|
+
.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
|
|
250
|
+
return {
|
|
251
|
+
phase,
|
|
252
|
+
reasonCode,
|
|
253
|
+
activeSceneId: typeof activeSceneId === 'string' && activeSceneId.trim() ? activeSceneId.trim() : null,
|
|
254
|
+
continuityVersion: Number.isInteger(Number(continuityVersion)) && Number(continuityVersion) > 0
|
|
255
|
+
? Number(continuityVersion)
|
|
256
|
+
: PROJECT_CONTINUITY_STATE_VERSION,
|
|
257
|
+
totalScenes: normalizedSceneResults.length,
|
|
258
|
+
capturedSceneIds,
|
|
259
|
+
restoredSceneIds,
|
|
260
|
+
skippedSceneIds,
|
|
261
|
+
failedSceneIds,
|
|
262
|
+
detail: typeof detail === 'string' && detail.trim() ? detail.trim() : null,
|
|
263
|
+
source: normalizeContinuitySource(source),
|
|
264
|
+
sceneResults: normalizedSceneResults,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function createApp() {
|
|
269
|
+
const projectTitle = projectRegistry.projectTitle || 'AuraJS Game';
|
|
270
|
+
const appState = createAppState({
|
|
271
|
+
projectTitle,
|
|
272
|
+
template: projectRegistry.template || "blank",
|
|
273
|
+
});
|
|
274
|
+
normalizeAppState(appState, {
|
|
275
|
+
projectTitle,
|
|
276
|
+
template: projectRegistry.template || "blank",
|
|
277
|
+
});
|
|
278
|
+
ensureSharedUiPreferences(appState);
|
|
279
|
+
let sceneRegistry = null;
|
|
280
|
+
let sceneFlow = null;
|
|
281
|
+
let screenShell = null;
|
|
282
|
+
let seededSceneId = null;
|
|
283
|
+
let runtimeStarted = false;
|
|
284
|
+
let paused = false;
|
|
285
|
+
const recordingState = {
|
|
286
|
+
supported: false,
|
|
287
|
+
active: false,
|
|
288
|
+
audioSupported: false,
|
|
289
|
+
path: null,
|
|
290
|
+
reasonCode: null,
|
|
291
|
+
};
|
|
292
|
+
const currentSceneId = () => sceneFlow?.getState().currentSceneId || seededSceneId || null;
|
|
293
|
+
function readPendingDevRestoreMeta() {
|
|
294
|
+
const runtime = globalThis.aura;
|
|
295
|
+
return normalizeContinuitySource(
|
|
296
|
+
runtime?.__aurajsPendingDevRestore && typeof runtime.__aurajsPendingDevRestore === 'object'
|
|
297
|
+
? runtime.__aurajsPendingDevRestore
|
|
298
|
+
: runtime?.__aurajsDevRestore,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
function ensureRuntimeContinuityState() {
|
|
302
|
+
normalizeAppState(appState, {
|
|
303
|
+
projectTitle,
|
|
304
|
+
template: projectRegistry.template || "blank",
|
|
305
|
+
});
|
|
306
|
+
const runtimeState = appState.runtime && typeof appState.runtime === 'object' && !Array.isArray(appState.runtime)
|
|
307
|
+
? appState.runtime
|
|
308
|
+
: {};
|
|
309
|
+
const continuityState = runtimeState.continuity && typeof runtimeState.continuity === 'object' && !Array.isArray(runtimeState.continuity)
|
|
310
|
+
? runtimeState.continuity
|
|
311
|
+
: {
|
|
312
|
+
lastCapture: null,
|
|
313
|
+
lastRestore: null,
|
|
314
|
+
};
|
|
315
|
+
runtimeState.continuity = continuityState;
|
|
316
|
+
appState.runtime = runtimeState;
|
|
317
|
+
return continuityState;
|
|
318
|
+
}
|
|
319
|
+
function recordContinuityCapture(summary) {
|
|
320
|
+
const continuityState = ensureRuntimeContinuityState();
|
|
321
|
+
continuityState.lastCapture = summary ? cloneProjectValue(summary) : null;
|
|
322
|
+
return continuityState.lastCapture;
|
|
323
|
+
}
|
|
324
|
+
function recordContinuityRestore(summary) {
|
|
325
|
+
const continuityState = ensureRuntimeContinuityState();
|
|
326
|
+
continuityState.lastRestore = summary ? cloneProjectValue(summary) : null;
|
|
327
|
+
return continuityState.lastRestore;
|
|
328
|
+
}
|
|
329
|
+
function replaceAppState(nextValue) {
|
|
330
|
+
if (!nextValue || typeof nextValue !== 'object' || Array.isArray(nextValue)) {
|
|
331
|
+
return appState;
|
|
332
|
+
}
|
|
333
|
+
for (const key of Object.keys(appState)) {
|
|
334
|
+
delete appState[key];
|
|
335
|
+
}
|
|
336
|
+
for (const [key, value] of Object.entries(cloneProjectValue(nextValue))) {
|
|
337
|
+
appState[key] = value;
|
|
338
|
+
}
|
|
339
|
+
normalizeAppState(appState, {
|
|
340
|
+
projectTitle,
|
|
341
|
+
template: projectRegistry.template || "blank",
|
|
342
|
+
});
|
|
343
|
+
ensureSharedUiPreferences(appState);
|
|
344
|
+
paused = appState.runtime?.paused === true;
|
|
345
|
+
return appState;
|
|
346
|
+
}
|
|
347
|
+
function syncAppStateRuntime(previousSceneId = null) {
|
|
348
|
+
normalizeAppState(appState, {
|
|
349
|
+
projectTitle,
|
|
350
|
+
template: projectRegistry.template || "blank",
|
|
351
|
+
});
|
|
352
|
+
const recordingSnapshot = getRecordingState();
|
|
353
|
+
const runtimeState = appState.runtime && typeof appState.runtime === 'object' && !Array.isArray(appState.runtime)
|
|
354
|
+
? appState.runtime
|
|
355
|
+
: {};
|
|
356
|
+
const continuityState = runtimeState.continuity && typeof runtimeState.continuity === 'object' && !Array.isArray(runtimeState.continuity)
|
|
357
|
+
? cloneProjectValue(runtimeState.continuity)
|
|
358
|
+
: {
|
|
359
|
+
lastCapture: null,
|
|
360
|
+
lastRestore: null,
|
|
361
|
+
};
|
|
362
|
+
const shellState = typeof screenShell?.getState === 'function'
|
|
363
|
+
? screenShell.getState()
|
|
364
|
+
: null;
|
|
365
|
+
appState.projectTitle = projectTitle;
|
|
366
|
+
appState.template = projectRegistry.template || "blank";
|
|
367
|
+
appState.runtime = {
|
|
368
|
+
...runtimeState,
|
|
369
|
+
startSceneId: projectRegistry.startSceneId || null,
|
|
370
|
+
currentSceneId: currentSceneId(),
|
|
371
|
+
previousSceneId: previousSceneId ?? runtimeState.previousSceneId ?? null,
|
|
372
|
+
hudScreenId: shellState?.hud?.screenId || null,
|
|
373
|
+
overlayScreenId: shellState?.overlay?.screenId || null,
|
|
374
|
+
modalScreenIds: Array.isArray(shellState?.modals) ? shellState.modals.map((entry) => entry.screenId) : [],
|
|
375
|
+
paused,
|
|
376
|
+
recordingSupported: recordingSnapshot.supported === true,
|
|
377
|
+
recordingActive: recordingSnapshot.active === true,
|
|
378
|
+
continuity: continuityState,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const appContext = {
|
|
382
|
+
projectTitle,
|
|
383
|
+
projectRegistry,
|
|
384
|
+
appState,
|
|
385
|
+
replaceAppState,
|
|
386
|
+
getAppState() {
|
|
387
|
+
return appState;
|
|
388
|
+
},
|
|
389
|
+
ensureSessionState(featureKey, seed = {}) {
|
|
390
|
+
return ensureSessionState(appState, featureKey, seed);
|
|
391
|
+
},
|
|
392
|
+
ensureUiState(featureKey, seed = {}) {
|
|
393
|
+
return ensureUiState(appState, featureKey, seed);
|
|
394
|
+
},
|
|
395
|
+
ensureUiPreferences(seed = null) {
|
|
396
|
+
return ensureSharedUiPreferences(appState, seed);
|
|
397
|
+
},
|
|
398
|
+
getSessionState(featureKey, fallback = null) {
|
|
399
|
+
const key = typeof featureKey === 'string' ? featureKey.trim() : '';
|
|
400
|
+
if (!key) return fallback;
|
|
401
|
+
return appState.session?.[key] ?? fallback;
|
|
402
|
+
},
|
|
403
|
+
getUiState(featureKey, fallback = null) {
|
|
404
|
+
const key = typeof featureKey === 'string' ? featureKey.trim() : '';
|
|
405
|
+
if (!key) return fallback;
|
|
406
|
+
return appState.ui?.[key] ?? fallback;
|
|
407
|
+
},
|
|
408
|
+
getUiPreferences() {
|
|
409
|
+
return ensureSharedUiPreferences(appState);
|
|
410
|
+
},
|
|
411
|
+
updateUiPreferences(patch = {}) {
|
|
412
|
+
return updateSharedUiPreferences(appState, patch);
|
|
413
|
+
},
|
|
414
|
+
resetUiPreferences(seed = null) {
|
|
415
|
+
return resetSharedUiPreferences(appState, seed);
|
|
416
|
+
},
|
|
417
|
+
applyUiTheme(options = {}) {
|
|
418
|
+
return applySharedUiTheme(appState, options);
|
|
419
|
+
},
|
|
420
|
+
getUiThemeState() {
|
|
421
|
+
return inspectSharedUiTheme(appState);
|
|
422
|
+
},
|
|
423
|
+
getActiveSceneId: currentSceneId,
|
|
424
|
+
getCurrentScenePayload() {
|
|
425
|
+
return sceneFlow?.current?.()?.data ?? null;
|
|
426
|
+
},
|
|
427
|
+
getSceneFlowState() {
|
|
428
|
+
return sceneFlow?.getState() || null;
|
|
429
|
+
},
|
|
430
|
+
getScreenShellState() {
|
|
431
|
+
return screenShell?.getState() || null;
|
|
432
|
+
},
|
|
433
|
+
replaceScene(sceneId, data = null) {
|
|
434
|
+
const previousSceneId = currentSceneId();
|
|
435
|
+
const result = sceneFlow?.replace(sceneId, data);
|
|
436
|
+
if (!result?.ok) {
|
|
437
|
+
throw new Error(`Unknown scene "${sceneId}". [reason:${result?.reasonCode || 'scene_flow_unavailable'}]`);
|
|
438
|
+
}
|
|
439
|
+
syncAppStateRuntime(previousSceneId);
|
|
440
|
+
return result;
|
|
441
|
+
},
|
|
442
|
+
pushScene(sceneId, data = null) {
|
|
443
|
+
const previousSceneId = currentSceneId();
|
|
444
|
+
const result = sceneFlow?.push(sceneId, data);
|
|
445
|
+
if (!result?.ok) {
|
|
446
|
+
throw new Error(`Unable to push scene "${sceneId}". [reason:${result?.reasonCode || 'scene_flow_unavailable'}]`);
|
|
447
|
+
}
|
|
448
|
+
syncAppStateRuntime(previousSceneId);
|
|
449
|
+
return result;
|
|
450
|
+
},
|
|
451
|
+
popScene(result = null) {
|
|
452
|
+
const previousSceneId = currentSceneId();
|
|
453
|
+
const outcome = sceneFlow?.pop(result);
|
|
454
|
+
if (!outcome?.ok) {
|
|
455
|
+
throw new Error(`Unable to pop scene. [reason:${outcome?.reasonCode || 'scene_flow_unavailable'}]`);
|
|
456
|
+
}
|
|
457
|
+
syncAppStateRuntime(previousSceneId);
|
|
458
|
+
return outcome;
|
|
459
|
+
},
|
|
460
|
+
canPopScene() {
|
|
461
|
+
return sceneFlow?.canPop() === true;
|
|
462
|
+
},
|
|
463
|
+
setHudScreen(screenId, data = null) {
|
|
464
|
+
const result = screenShell?.setHud(screenId, data) || null;
|
|
465
|
+
syncAppStateRuntime(currentSceneId());
|
|
466
|
+
return result;
|
|
467
|
+
},
|
|
468
|
+
clearHudScreen() {
|
|
469
|
+
const result = screenShell?.clearHud() || null;
|
|
470
|
+
syncAppStateRuntime(currentSceneId());
|
|
471
|
+
return result;
|
|
472
|
+
},
|
|
473
|
+
showOverlayScreen(screenId, data = null) {
|
|
474
|
+
const result = screenShell?.showOverlay(screenId, data) || null;
|
|
475
|
+
syncAppStateRuntime(currentSceneId());
|
|
476
|
+
return result;
|
|
477
|
+
},
|
|
478
|
+
clearOverlayScreen() {
|
|
479
|
+
const result = screenShell?.clearOverlay() || null;
|
|
480
|
+
syncAppStateRuntime(currentSceneId());
|
|
481
|
+
return result;
|
|
482
|
+
},
|
|
483
|
+
pushModalScreen(screenId, data = null) {
|
|
484
|
+
const result = screenShell?.pushModal(screenId, data) || null;
|
|
485
|
+
syncAppStateRuntime(currentSceneId());
|
|
486
|
+
return result;
|
|
487
|
+
},
|
|
488
|
+
popModalScreen(result = null) {
|
|
489
|
+
const outcome = screenShell?.popModal(result) || null;
|
|
490
|
+
syncAppStateRuntime(currentSceneId());
|
|
491
|
+
return outcome;
|
|
492
|
+
},
|
|
493
|
+
jumpToScene(sceneId, data = null) {
|
|
494
|
+
return jumpToScene(sceneId, data);
|
|
495
|
+
},
|
|
496
|
+
swapScene(sceneId, data = null) {
|
|
497
|
+
return this.replaceScene(sceneId, data);
|
|
498
|
+
},
|
|
499
|
+
setPaused(nextValue = true) {
|
|
500
|
+
return setPaused(nextValue);
|
|
501
|
+
},
|
|
502
|
+
togglePaused() {
|
|
503
|
+
return togglePaused();
|
|
504
|
+
},
|
|
505
|
+
restartCurrentScene() {
|
|
506
|
+
return restartCurrentScene();
|
|
507
|
+
},
|
|
508
|
+
restartStartScene() {
|
|
509
|
+
return restartStartScene();
|
|
510
|
+
},
|
|
511
|
+
get sceneRegistry() {
|
|
512
|
+
return sceneRegistry;
|
|
513
|
+
},
|
|
514
|
+
get sceneFlow() {
|
|
515
|
+
return sceneFlow;
|
|
516
|
+
},
|
|
517
|
+
get screenShell() {
|
|
518
|
+
return screenShell;
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
sceneRegistry = createSceneRegistry(appContext);
|
|
522
|
+
seededSceneId = sceneRegistry.defaultSceneId || Object.keys(sceneRegistry.scenes || {})[0] || null;
|
|
523
|
+
const resolveScene = (sceneId) => sceneRegistry.scenes[sceneId] || null;
|
|
524
|
+
screenShell = createScreenShell({ resolveScreen: resolveProjectScreen });
|
|
525
|
+
sceneFlow = createSceneFlow({
|
|
526
|
+
initialSceneId: seededSceneId,
|
|
527
|
+
resolveScene,
|
|
528
|
+
onEnter(info) {
|
|
529
|
+
if (!runtimeStarted) return;
|
|
530
|
+
info.current?.scene?.setup?.(info.current.data);
|
|
531
|
+
info.current?.scene?.onEnter?.(info);
|
|
532
|
+
},
|
|
533
|
+
onExit(info) {
|
|
534
|
+
info.current?.scene?.onExit?.(info);
|
|
535
|
+
info.current?.scene?.shutdown?.(info);
|
|
536
|
+
},
|
|
537
|
+
onPause(info) {
|
|
538
|
+
info.current?.scene?.onPause?.(info);
|
|
539
|
+
},
|
|
540
|
+
onResume(info) {
|
|
541
|
+
info.current?.scene?.onResume?.(info);
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
syncAppStateRuntime();
|
|
545
|
+
const activeScene = () => {
|
|
546
|
+
const sceneId = currentSceneId();
|
|
547
|
+
return sceneId ? sceneRegistry.scenes[sceneId] || null : null;
|
|
548
|
+
};
|
|
549
|
+
function clearPlaytestScreens() {
|
|
550
|
+
const shellState = typeof screenShell?.getState === 'function' ? screenShell.getState() : null;
|
|
551
|
+
if (!shellState || typeof screenShell?.applyState !== 'function') {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
return screenShell.applyState({
|
|
555
|
+
schema: shellState.schema || 'aurajs.screen-shell.v1',
|
|
556
|
+
hud: shellState.hud || null,
|
|
557
|
+
overlay: null,
|
|
558
|
+
modals: [],
|
|
559
|
+
mutationCount: Number.isInteger(Number(shellState.mutationCount))
|
|
560
|
+
? Number(shellState.mutationCount) + 1
|
|
561
|
+
: 1,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function setPaused(nextValue = false) {
|
|
565
|
+
paused = nextValue === true;
|
|
566
|
+
syncAppStateRuntime();
|
|
567
|
+
return paused;
|
|
568
|
+
}
|
|
569
|
+
function togglePaused() {
|
|
570
|
+
return setPaused(!paused);
|
|
571
|
+
}
|
|
572
|
+
function getPaused() {
|
|
573
|
+
return paused === true;
|
|
574
|
+
}
|
|
575
|
+
function getRecordingState() {
|
|
576
|
+
const runtime = globalThis.aura;
|
|
577
|
+
const state = runtime?.window && typeof runtime.window.getRecordingState === 'function'
|
|
578
|
+
? runtime.window.getRecordingState()
|
|
579
|
+
: null;
|
|
580
|
+
recordingState.supported = state?.supported === true;
|
|
581
|
+
recordingState.active = state?.active === true;
|
|
582
|
+
recordingState.audioSupported = state?.audioSupported === true;
|
|
583
|
+
recordingState.path = typeof state?.path === 'string' ? state.path : null;
|
|
584
|
+
recordingState.reasonCode = typeof state?.reasonCode === 'string' ? state.reasonCode : null;
|
|
585
|
+
return {
|
|
586
|
+
supported: recordingState.supported === true,
|
|
587
|
+
active: recordingState.active === true,
|
|
588
|
+
audioSupported: recordingState.audioSupported === true,
|
|
589
|
+
path: recordingState.path || null,
|
|
590
|
+
reasonCode: recordingState.reasonCode || null,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function toggleRecording() {
|
|
594
|
+
const runtime = globalThis.aura;
|
|
595
|
+
if (!runtime?.window || typeof runtime.window.toggleRecording !== 'function') {
|
|
596
|
+
return {
|
|
597
|
+
ok: false,
|
|
598
|
+
reasonCode: 'recording_unavailable',
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
runtime.window.toggleRecording();
|
|
602
|
+
return {
|
|
603
|
+
ok: true,
|
|
604
|
+
reasonCode: 'recording_toggle_requested',
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function restartCurrentScene() {
|
|
608
|
+
const current = sceneFlow?.current?.();
|
|
609
|
+
if (!current?.sceneId) {
|
|
610
|
+
return {
|
|
611
|
+
ok: false,
|
|
612
|
+
reasonCode: 'scene_flow_unavailable',
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
clearPlaytestScreens();
|
|
616
|
+
paused = false;
|
|
617
|
+
const result = sceneFlow.replace(current.sceneId, cloneProjectValue(current.data));
|
|
618
|
+
if (result?.ok) {
|
|
619
|
+
syncAppStateRuntime(current.sceneId);
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
function jumpToScene(sceneId, data = null) {
|
|
624
|
+
const targetSceneId = typeof sceneId === 'string' ? sceneId.trim() : '';
|
|
625
|
+
if (!targetSceneId) {
|
|
626
|
+
return {
|
|
627
|
+
ok: false,
|
|
628
|
+
reasonCode: 'scene_flow_invalid_scene_id',
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
if (!sceneRegistry?.scenes?.[targetSceneId]) {
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
reasonCode: 'scene_flow_unknown_scene',
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const previousSceneId = currentSceneId();
|
|
638
|
+
clearPlaytestScreens();
|
|
639
|
+
while (sceneFlow?.canPop?.()) {
|
|
640
|
+
const popped = sceneFlow.pop();
|
|
641
|
+
if (!popped?.ok) {
|
|
642
|
+
syncAppStateRuntime(previousSceneId);
|
|
643
|
+
return popped;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
paused = false;
|
|
647
|
+
const result = sceneFlow.replace(targetSceneId, cloneProjectValue(data));
|
|
648
|
+
if (result?.ok) {
|
|
649
|
+
syncAppStateRuntime(previousSceneId);
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
function restartStartScene() {
|
|
654
|
+
const startSceneId = projectRegistry.startSceneId || seededSceneId || currentSceneId();
|
|
655
|
+
if (!startSceneId) {
|
|
656
|
+
return {
|
|
657
|
+
ok: false,
|
|
658
|
+
reasonCode: 'scene_flow_unavailable',
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
return jumpToScene(startSceneId, null);
|
|
662
|
+
}
|
|
663
|
+
function exportProjectState() {
|
|
664
|
+
const scenes = {};
|
|
665
|
+
const sceneCaptureResults = [];
|
|
666
|
+
for (const sceneId of Object.keys(sceneRegistry.scenes || {}).sort()) {
|
|
667
|
+
const captureResult = captureSceneSnapshot(sceneRegistry.scenes[sceneId], sceneId);
|
|
668
|
+
sceneCaptureResults.push({
|
|
669
|
+
sceneId,
|
|
670
|
+
ok: captureResult.ok === true,
|
|
671
|
+
reasonCode: captureResult.reasonCode || null,
|
|
672
|
+
detail: captureResult.detail || null,
|
|
673
|
+
hasSnapshot: captureResult.hasSnapshot === true,
|
|
674
|
+
});
|
|
675
|
+
if (captureResult.snapshot != null) {
|
|
676
|
+
scenes[sceneId] = captureResult.snapshot;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const captureReasonCode = sceneCaptureResults.some((entry) => entry.ok !== true || entry.reasonCode === 'project_state_scene_skipped')
|
|
680
|
+
? 'project_state_capture_partial'
|
|
681
|
+
: 'project_state_capture_ok';
|
|
682
|
+
recordContinuityCapture(createContinuitySummary(sceneCaptureResults, {
|
|
683
|
+
phase: 'capture',
|
|
684
|
+
reasonCode: captureReasonCode,
|
|
685
|
+
activeSceneId: currentSceneId(),
|
|
686
|
+
}));
|
|
687
|
+
syncAppStateRuntime();
|
|
688
|
+
const continuity = normalizeProjectContinuityState({
|
|
689
|
+
schema: PROJECT_CONTINUITY_STATE_SCHEMA,
|
|
690
|
+
version: PROJECT_CONTINUITY_STATE_VERSION,
|
|
691
|
+
template: projectRegistry.template || "blank",
|
|
692
|
+
activeSceneId: currentSceneId(),
|
|
693
|
+
currentSceneData: sceneFlow?.current?.()?.data ?? null,
|
|
694
|
+
appState,
|
|
695
|
+
sceneFlow: sceneFlow.getState(),
|
|
696
|
+
screenShell: screenShell.getState(),
|
|
697
|
+
sceneStates: scenes,
|
|
698
|
+
ownership: projectRegistry.continuity || null,
|
|
699
|
+
}, projectRegistry.template || "blank");
|
|
700
|
+
return {
|
|
701
|
+
schema: PROJECT_STATE_SCHEMA,
|
|
702
|
+
template: continuity.template,
|
|
703
|
+
activeSceneId: continuity.activeSceneId,
|
|
704
|
+
currentSceneData: continuity.currentSceneData,
|
|
705
|
+
appState: continuity.appState,
|
|
706
|
+
sceneFlow: continuity.sceneFlow,
|
|
707
|
+
screenShell: continuity.screenShell,
|
|
708
|
+
scenes: continuity.sceneStates,
|
|
709
|
+
continuity,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function applyProjectState(snapshot) {
|
|
713
|
+
if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
714
|
+
return { ok: false, reasonCode: 'project_state_invalid' };
|
|
715
|
+
}
|
|
716
|
+
if (snapshot.schema && snapshot.schema !== PROJECT_STATE_SCHEMA) {
|
|
717
|
+
return { ok: false, reasonCode: 'project_state_invalid' };
|
|
718
|
+
}
|
|
719
|
+
const continuity = normalizeProjectContinuityState(
|
|
720
|
+
snapshot,
|
|
721
|
+
projectRegistry.template || "blank",
|
|
722
|
+
);
|
|
723
|
+
if (!continuity) {
|
|
724
|
+
return { ok: false, reasonCode: 'project_state_invalid' };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const beforeSceneId = currentSceneId();
|
|
728
|
+
const restoreSource = readPendingDevRestoreMeta();
|
|
729
|
+
if (continuity.appState) {
|
|
730
|
+
replaceAppState(continuity.appState);
|
|
731
|
+
}
|
|
732
|
+
const flowResult = sceneFlow.applyState(continuity.sceneFlow || null);
|
|
733
|
+
if (!flowResult?.ok) {
|
|
734
|
+
const continuitySummary = createContinuitySummary([], {
|
|
735
|
+
phase: 'restore',
|
|
736
|
+
reasonCode: flowResult?.reasonCode || 'project_state_apply_failed',
|
|
737
|
+
activeSceneId: beforeSceneId,
|
|
738
|
+
continuityVersion: continuity.version,
|
|
739
|
+
detail: flowResult?.detail || null,
|
|
740
|
+
source: restoreSource,
|
|
741
|
+
});
|
|
742
|
+
recordContinuityRestore(continuitySummary);
|
|
743
|
+
syncAppStateRuntime(beforeSceneId);
|
|
744
|
+
return {
|
|
745
|
+
ok: false,
|
|
746
|
+
reasonCode: flowResult?.reasonCode || 'project_state_apply_failed',
|
|
747
|
+
detail: flowResult?.detail || null,
|
|
748
|
+
continuity: continuitySummary,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const shellResult = screenShell.applyState(continuity.screenShell || {
|
|
753
|
+
schema: 'aurajs.screen-shell.v1',
|
|
754
|
+
hud: null,
|
|
755
|
+
overlay: null,
|
|
756
|
+
modals: [],
|
|
757
|
+
mutationCount: 0,
|
|
758
|
+
});
|
|
759
|
+
if (!shellResult?.ok) {
|
|
760
|
+
const continuitySummary = createContinuitySummary([], {
|
|
761
|
+
phase: 'restore',
|
|
762
|
+
reasonCode: shellResult?.reasonCode || 'project_state_apply_failed',
|
|
763
|
+
activeSceneId: currentSceneId(),
|
|
764
|
+
continuityVersion: continuity.version,
|
|
765
|
+
detail: shellResult?.detail || null,
|
|
766
|
+
source: restoreSource,
|
|
767
|
+
});
|
|
768
|
+
recordContinuityRestore(continuitySummary);
|
|
769
|
+
syncAppStateRuntime(beforeSceneId);
|
|
770
|
+
return {
|
|
771
|
+
ok: false,
|
|
772
|
+
reasonCode: shellResult?.reasonCode || 'project_state_apply_failed',
|
|
773
|
+
detail: shellResult?.detail || null,
|
|
774
|
+
continuity: continuitySummary,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const sceneSnapshots = continuity.sceneStates;
|
|
779
|
+
const sceneRestoreResults = [];
|
|
780
|
+
for (const [sceneId, sceneSnapshot] of Object.entries(sceneSnapshots)) {
|
|
781
|
+
const result = applySceneSnapshot(sceneRegistry.scenes[sceneId] || null, sceneSnapshot, sceneId);
|
|
782
|
+
sceneRestoreResults.push({
|
|
783
|
+
sceneId,
|
|
784
|
+
ok: result.ok === true,
|
|
785
|
+
reasonCode: result.reasonCode || null,
|
|
786
|
+
detail: result.detail || null,
|
|
787
|
+
restored: result.restored === true,
|
|
788
|
+
});
|
|
789
|
+
if (!result.ok) {
|
|
790
|
+
const continuitySummary = createContinuitySummary(sceneRestoreResults, {
|
|
791
|
+
phase: 'restore',
|
|
792
|
+
reasonCode: result.reasonCode || 'project_state_scene_apply_failed',
|
|
793
|
+
activeSceneId: currentSceneId(),
|
|
794
|
+
continuityVersion: continuity.version,
|
|
795
|
+
detail: result.detail || null,
|
|
796
|
+
source: restoreSource,
|
|
797
|
+
});
|
|
798
|
+
recordContinuityRestore(continuitySummary);
|
|
799
|
+
syncAppStateRuntime(beforeSceneId);
|
|
800
|
+
return {
|
|
801
|
+
ok: false,
|
|
802
|
+
reasonCode: result.reasonCode || 'project_state_scene_apply_failed',
|
|
803
|
+
failedSceneId: sceneId,
|
|
804
|
+
detail: result.detail || null,
|
|
805
|
+
continuity: continuitySummary,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const afterSceneId = currentSceneId();
|
|
811
|
+
if (runtimeStarted && afterSceneId && afterSceneId !== beforeSceneId) {
|
|
812
|
+
const scene = activeScene();
|
|
813
|
+
scene?.setup?.(sceneFlow.current()?.data);
|
|
814
|
+
}
|
|
815
|
+
const restoreReasonCode = sceneRestoreResults.some((entry) => entry.reasonCode === 'project_state_scene_skipped')
|
|
816
|
+
? 'project_state_apply_partial'
|
|
817
|
+
: 'project_state_apply_ok';
|
|
818
|
+
const continuitySummary = createContinuitySummary(sceneRestoreResults, {
|
|
819
|
+
phase: 'restore',
|
|
820
|
+
reasonCode: restoreReasonCode,
|
|
821
|
+
activeSceneId: afterSceneId || null,
|
|
822
|
+
continuityVersion: continuity.version,
|
|
823
|
+
source: restoreSource,
|
|
824
|
+
});
|
|
825
|
+
recordContinuityRestore(continuitySummary);
|
|
826
|
+
syncAppStateRuntime(beforeSceneId);
|
|
827
|
+
|
|
828
|
+
return {
|
|
829
|
+
ok: true,
|
|
830
|
+
reasonCode: restoreReasonCode,
|
|
831
|
+
activeSceneId: afterSceneId || null,
|
|
832
|
+
continuityVersion: continuity.version,
|
|
833
|
+
continuity: continuitySummary,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function installProjectStateBridge(app) {
|
|
837
|
+
const auraRef = globalThis.aura;
|
|
838
|
+
if (!auraRef || typeof auraRef !== 'object') {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
auraRef.project = {
|
|
842
|
+
schemaVersion: PROJECT_STATE_SCHEMA,
|
|
843
|
+
continuitySchemaVersion: PROJECT_CONTINUITY_STATE_SCHEMA,
|
|
844
|
+
getState: () => app.getState(),
|
|
845
|
+
exportState: () => app.getState(),
|
|
846
|
+
getContinuityState: () => app.appState?.runtime?.continuity || null,
|
|
847
|
+
applyState: (snapshot) => app.applyState(snapshot),
|
|
848
|
+
apply: (snapshot) => app.applyState(snapshot),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
const projectInspector = createProjectInspector({
|
|
852
|
+
projectTitle,
|
|
853
|
+
sceneRegistry,
|
|
854
|
+
getActiveSceneId: currentSceneId,
|
|
855
|
+
appState,
|
|
856
|
+
getUiThemeState: () => inspectSharedUiTheme(appState),
|
|
857
|
+
onSelectScene: jumpToScene,
|
|
858
|
+
onRestartScene: restartCurrentScene,
|
|
859
|
+
onRestartApp: restartStartScene,
|
|
860
|
+
getPaused,
|
|
861
|
+
togglePaused,
|
|
862
|
+
getRecordingState,
|
|
863
|
+
toggleRecording,
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
function renderScreenEntry(entry) {
|
|
867
|
+
if (!entry) return;
|
|
868
|
+
const screen = resolveProjectScreen(entry.screenId);
|
|
869
|
+
if (typeof screen === 'function') {
|
|
870
|
+
screen(entry.data || {}, appContext);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const drawScreen = typeof screen?.draw === 'function'
|
|
874
|
+
? screen.draw
|
|
875
|
+
: typeof screen?.render === 'function'
|
|
876
|
+
? screen.render
|
|
877
|
+
: null;
|
|
878
|
+
if (drawScreen) {
|
|
879
|
+
drawScreen(entry.data || {}, appContext);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const app = {
|
|
884
|
+
...appContext,
|
|
885
|
+
template: projectRegistry.template || "blank",
|
|
886
|
+
projectInspector,
|
|
887
|
+
get projectInspectorEnabled() {
|
|
888
|
+
return projectInspector.enabled;
|
|
889
|
+
},
|
|
890
|
+
get projectInspectorMinimized() {
|
|
891
|
+
return projectInspector.minimized;
|
|
892
|
+
},
|
|
893
|
+
get paused() {
|
|
894
|
+
return paused;
|
|
895
|
+
},
|
|
896
|
+
get activeSceneId() {
|
|
897
|
+
return currentSceneId();
|
|
898
|
+
},
|
|
899
|
+
get sceneRegistry() {
|
|
900
|
+
return sceneRegistry;
|
|
901
|
+
},
|
|
902
|
+
get sceneFlow() {
|
|
903
|
+
return sceneFlow;
|
|
904
|
+
},
|
|
905
|
+
get screenShell() {
|
|
906
|
+
return screenShell;
|
|
907
|
+
},
|
|
908
|
+
inspectProject() {
|
|
909
|
+
return projectInspector.snapshot();
|
|
910
|
+
},
|
|
911
|
+
get appState() {
|
|
912
|
+
return appState;
|
|
913
|
+
},
|
|
914
|
+
getProjectInspectorState() {
|
|
915
|
+
return projectInspector.snapshot();
|
|
916
|
+
},
|
|
917
|
+
getSceneFlowState() {
|
|
918
|
+
return sceneFlow.getState();
|
|
919
|
+
},
|
|
920
|
+
getScreenShellState() {
|
|
921
|
+
return screenShell.getState();
|
|
922
|
+
},
|
|
923
|
+
getState() {
|
|
924
|
+
return exportProjectState();
|
|
925
|
+
},
|
|
926
|
+
exportState() {
|
|
927
|
+
return exportProjectState();
|
|
928
|
+
},
|
|
929
|
+
applyState(snapshot) {
|
|
930
|
+
return applyProjectState(snapshot);
|
|
931
|
+
},
|
|
932
|
+
replaceScene(sceneId, data = null) {
|
|
933
|
+
const previousSceneId = currentSceneId();
|
|
934
|
+
const result = sceneFlow.replace(sceneId, data);
|
|
935
|
+
if (!result.ok) {
|
|
936
|
+
throw new Error(`Unknown scene "${sceneId}". [reason:${result.reasonCode}]`);
|
|
937
|
+
}
|
|
938
|
+
syncAppStateRuntime(previousSceneId);
|
|
939
|
+
return result;
|
|
940
|
+
},
|
|
941
|
+
pushScene(sceneId, data = null) {
|
|
942
|
+
const previousSceneId = currentSceneId();
|
|
943
|
+
const result = sceneFlow.push(sceneId, data);
|
|
944
|
+
if (!result.ok) {
|
|
945
|
+
throw new Error(`Unable to push scene "${sceneId}". [reason:${result.reasonCode}]`);
|
|
946
|
+
}
|
|
947
|
+
syncAppStateRuntime(previousSceneId);
|
|
948
|
+
return result;
|
|
949
|
+
},
|
|
950
|
+
popScene(result = null) {
|
|
951
|
+
const previousSceneId = currentSceneId();
|
|
952
|
+
const outcome = sceneFlow.pop(result);
|
|
953
|
+
if (!outcome.ok) {
|
|
954
|
+
throw new Error(`Unable to pop scene. [reason:${outcome.reasonCode}]`);
|
|
955
|
+
}
|
|
956
|
+
syncAppStateRuntime(previousSceneId);
|
|
957
|
+
return outcome;
|
|
958
|
+
},
|
|
959
|
+
jumpToScene(sceneId, data = null) {
|
|
960
|
+
return jumpToScene(sceneId, data);
|
|
961
|
+
},
|
|
962
|
+
canPopScene() {
|
|
963
|
+
return sceneFlow.canPop();
|
|
964
|
+
},
|
|
965
|
+
setHudScreen(screenId, data = null) {
|
|
966
|
+
const result = screenShell.setHud(screenId, data);
|
|
967
|
+
syncAppStateRuntime(currentSceneId());
|
|
968
|
+
return result;
|
|
969
|
+
},
|
|
970
|
+
clearHudScreen() {
|
|
971
|
+
const result = screenShell.clearHud();
|
|
972
|
+
syncAppStateRuntime(currentSceneId());
|
|
973
|
+
return result;
|
|
974
|
+
},
|
|
975
|
+
showOverlayScreen(screenId, data = null) {
|
|
976
|
+
const result = screenShell.showOverlay(screenId, data);
|
|
977
|
+
syncAppStateRuntime(currentSceneId());
|
|
978
|
+
return result;
|
|
979
|
+
},
|
|
980
|
+
clearOverlayScreen() {
|
|
981
|
+
const result = screenShell.clearOverlay();
|
|
982
|
+
syncAppStateRuntime(currentSceneId());
|
|
983
|
+
return result;
|
|
984
|
+
},
|
|
985
|
+
pushModalScreen(screenId, data = null) {
|
|
986
|
+
const result = screenShell.pushModal(screenId, data);
|
|
987
|
+
syncAppStateRuntime(currentSceneId());
|
|
988
|
+
return result;
|
|
989
|
+
},
|
|
990
|
+
popModalScreen(result = null) {
|
|
991
|
+
const outcome = screenShell.popModal(result);
|
|
992
|
+
syncAppStateRuntime(currentSceneId());
|
|
993
|
+
return outcome;
|
|
994
|
+
},
|
|
995
|
+
toggleProjectInspector(nextValue = null) {
|
|
996
|
+
if (typeof nextValue === 'boolean') {
|
|
997
|
+
return projectInspector.setEnabled(nextValue);
|
|
998
|
+
}
|
|
999
|
+
return projectInspector.toggle();
|
|
1000
|
+
},
|
|
1001
|
+
setProjectInspectorMinimized(nextValue = null) {
|
|
1002
|
+
if (typeof nextValue === 'boolean') {
|
|
1003
|
+
return projectInspector.setMinimized(nextValue);
|
|
1004
|
+
}
|
|
1005
|
+
return projectInspector.setMinimized(false);
|
|
1006
|
+
},
|
|
1007
|
+
toggleProjectInspectorMinimized() {
|
|
1008
|
+
return projectInspector.toggleMinimized();
|
|
1009
|
+
},
|
|
1010
|
+
setPaused(nextValue = true) {
|
|
1011
|
+
return setPaused(nextValue);
|
|
1012
|
+
},
|
|
1013
|
+
togglePaused() {
|
|
1014
|
+
return togglePaused();
|
|
1015
|
+
},
|
|
1016
|
+
getRecordingState() {
|
|
1017
|
+
return getRecordingState();
|
|
1018
|
+
},
|
|
1019
|
+
toggleRecording() {
|
|
1020
|
+
return toggleRecording();
|
|
1021
|
+
},
|
|
1022
|
+
restartCurrentScene() {
|
|
1023
|
+
return restartCurrentScene();
|
|
1024
|
+
},
|
|
1025
|
+
restartStartScene() {
|
|
1026
|
+
return restartStartScene();
|
|
1027
|
+
},
|
|
1028
|
+
setup() {
|
|
1029
|
+
assertRuntimeCapabilities();
|
|
1030
|
+
runtimeStarted = true;
|
|
1031
|
+
paused = false;
|
|
1032
|
+
const setupResult = activeScene()?.setup?.();
|
|
1033
|
+
syncAppStateRuntime();
|
|
1034
|
+
return setupResult;
|
|
1035
|
+
},
|
|
1036
|
+
update(dt) {
|
|
1037
|
+
projectInspector.update();
|
|
1038
|
+
if (!paused) {
|
|
1039
|
+
activeScene()?.update?.(dt);
|
|
1040
|
+
}
|
|
1041
|
+
syncAppStateRuntime();
|
|
1042
|
+
},
|
|
1043
|
+
draw() {
|
|
1044
|
+
applySharedUiTheme(appState);
|
|
1045
|
+
activeScene()?.draw?.();
|
|
1046
|
+
const shellState = screenShell.getState();
|
|
1047
|
+
renderScreenEntry(shellState.hud);
|
|
1048
|
+
renderScreenEntry(shellState.overlay);
|
|
1049
|
+
for (const modal of shellState.modals) {
|
|
1050
|
+
renderScreenEntry(modal);
|
|
1051
|
+
}
|
|
1052
|
+
projectInspector.draw();
|
|
1053
|
+
},
|
|
1054
|
+
swapScene(nextSceneId, data = null) {
|
|
1055
|
+
return appContext.replaceScene(nextSceneId, data);
|
|
1056
|
+
},
|
|
1057
|
+
};
|
|
1058
|
+
installProjectStateBridge(app);
|
|
1059
|
+
return app;
|
|
1060
|
+
}
|