@auraindustry/aurajs 0.1.1 → 0.1.5

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.
Files changed (144) hide show
  1. package/README.md +7 -0
  2. package/benchmarks/perf-thresholds.json +27 -0
  3. package/package.json +6 -1
  4. package/src/ai-guidance.mjs +302 -0
  5. package/src/asset-pack.mjs +2 -1
  6. package/src/authored-project.mjs +498 -2
  7. package/src/authored-runtime.mjs +14 -0
  8. package/src/bin-integrity.mjs +33 -26
  9. package/src/build-contract/capabilities.mjs +87 -1
  10. package/src/build-contract/constants.mjs +1 -0
  11. package/src/build-contract.mjs +2 -0
  12. package/src/bundler.mjs +143 -13
  13. package/src/cli.mjs +681 -13
  14. package/src/commands/packs.mjs +741 -0
  15. package/src/commands/project-authoring.mjs +128 -1
  16. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
  17. package/src/conformance/cases/core-runtime-cases.mjs +6 -2
  18. package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
  19. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1126 -10
  20. package/src/conformance-mobile.mjs +166 -0
  21. package/src/conformance.mjs +89 -30
  22. package/src/evidence-bundle.mjs +242 -0
  23. package/src/external-package-surface.mjs +1 -1
  24. package/src/headless-test/runtime-coordinator.mjs +186 -33
  25. package/src/headless-test.mjs +2 -0
  26. package/src/helpers/2d/index.mjs +183 -0
  27. package/src/helpers/index.mjs +26 -0
  28. package/src/helpers/starter-utils/adventure-objectives.js +102 -0
  29. package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
  30. package/src/helpers/starter-utils/animation-2d.js +337 -0
  31. package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
  32. package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
  33. package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
  34. package/src/helpers/starter-utils/avatar-3d.js +404 -0
  35. package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
  36. package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
  37. package/src/helpers/starter-utils/core.js +150 -0
  38. package/src/helpers/starter-utils/dialogue-2d.js +351 -0
  39. package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
  40. package/src/helpers/starter-utils/index.js +26 -0
  41. package/src/helpers/starter-utils/inventory-2d.js +268 -0
  42. package/src/helpers/starter-utils/journal-2d.js +267 -0
  43. package/src/helpers/starter-utils/platformer-3d.js +132 -0
  44. package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
  45. package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
  46. package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
  47. package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
  48. package/src/helpers/starter-utils/triggers.js +662 -0
  49. package/src/helpers/starter-utils/tween-2d.js +615 -0
  50. package/src/helpers/starter-utils/wave-director.js +101 -0
  51. package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
  52. package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
  53. package/src/mobile/android/build.mjs +606 -0
  54. package/src/mobile/android/host-artifact.mjs +280 -0
  55. package/src/mobile/ios/build.mjs +1323 -0
  56. package/src/mobile/ios/host-artifact.mjs +819 -0
  57. package/src/mobile/shared/capabilities.mjs +174 -0
  58. package/src/package-integrity.mjs +18 -4
  59. package/src/packs/catalog.mjs +259 -0
  60. package/src/perf-benchmark-runner.mjs +17 -12
  61. package/src/perf-benchmark.mjs +408 -4
  62. package/src/publish-command.mjs +434 -17
  63. package/src/publish-validation.mjs +22 -11
  64. package/src/replay-runtime.mjs +257 -0
  65. package/src/scaffold/config.mjs +2 -0
  66. package/src/scaffold/fs.mjs +8 -1
  67. package/src/scaffold/project-docs.mjs +101 -41
  68. package/src/scaffold.mjs +4 -0
  69. package/src/session-runtime.mjs +4 -3
  70. package/src/web-conformance.mjs +0 -36
  71. package/templates/create/2d/src/runtime/app.js +4 -0
  72. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
  73. package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
  74. package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
  75. package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
  76. package/templates/create/2d-adventure/docs/design/loop.md +4 -3
  77. package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
  78. package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
  79. package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
  80. package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
  81. package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
  82. package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
  83. package/templates/create/2d-survivor/src/runtime/app.js +4 -0
  84. package/templates/create/3d/scenes/gameplay.scene.js +30 -3
  85. package/templates/create/3d/src/runtime/app.js +4 -0
  86. package/templates/create/3d/src/runtime/capabilities.js +5 -0
  87. package/templates/create/3d/src/runtime/materials.js +10 -0
  88. package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
  89. package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
  90. package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
  91. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
  92. package/templates/create/3d-collectathon/src/runtime/app.js +4 -0
  93. package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
  94. package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
  95. package/templates/create/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
  96. package/templates/create/blank/assets/splash/bg.webp +0 -0
  97. package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
  98. package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
  99. package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
  100. package/templates/create/blank/assets/splash/logoholo.webp +0 -0
  101. package/templates/create/blank/src/main.js +5 -1
  102. package/templates/create/blank/src/runtime/splash.js +305 -0
  103. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
  104. package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
  105. package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
  106. package/templates/create/shared/assets/splash/bg.webp +0 -0
  107. package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
  108. package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
  109. package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
  110. package/templates/create/shared/assets/splash/logoholo.webp +0 -0
  111. package/templates/create/shared/src/runtime/splash.js +305 -0
  112. package/templates/create/shared/src/runtime/ui-forms.js +552 -0
  113. package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
  114. package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
  115. package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
  116. package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
  117. package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
  118. package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
  119. package/templates/create/shared/src/starter-utils/index.js +15 -1
  120. package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
  121. package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
  122. package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
  123. package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
  124. package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
  125. package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
  126. package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
  127. package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
  128. package/templates/create/video-cutscene/src/runtime/app.js +4 -0
  129. package/templates/create-bin/play.js +148 -7
  130. package/templates/skills/auramaxx/SKILL.md +46 -0
  131. package/templates/skills/auramaxx/project-requirements.md +68 -0
  132. package/templates/skills/auramaxx/starter-recipes.md +104 -0
  133. package/templates/skills/auramaxx/validation-checklist.md +49 -0
  134. package/templates/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
  135. package/templates/starter/assets/splash/bg.webp +0 -0
  136. package/templates/starter/assets/splash/boot-loop.wav +0 -0
  137. package/templates/starter/assets/splash/boot-sting.wav +0 -0
  138. package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
  139. package/templates/starter/assets/splash/logoholo.webp +0 -0
  140. package/templates/starter/src/main.js +4 -0
  141. package/templates/starter/src/runtime/splash.js +305 -0
  142. package/templates/skills/aurajs/SKILL.md +0 -96
  143. package/templates/skills/aurajs/api-contract-3d.md +0 -7
  144. package/templates/skills/aurajs/api-contract.md +0 -7
@@ -0,0 +1,253 @@
1
+ function finite(value, fallback = 0) {
2
+ const numeric = Number(value);
3
+ return Number.isFinite(numeric) ? numeric : fallback;
4
+ }
5
+
6
+ function positiveInteger(value, fallback = null) {
7
+ const numeric = Math.floor(finite(value, Number.NaN));
8
+ if (numeric >= 1) return numeric;
9
+ return fallback;
10
+ }
11
+
12
+ function normalizeText(value, fallback) {
13
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : fallback;
14
+ }
15
+
16
+ function resolveAura(auraRef = globalThis.aura) {
17
+ return auraRef && typeof auraRef === 'object' ? auraRef : null;
18
+ }
19
+
20
+ function normalizeStageIds(value) {
21
+ if (!Array.isArray(value) || value.length === 0) {
22
+ return ['scene', 'light', 'final'];
23
+ }
24
+
25
+ const stageIds = [];
26
+ const seen = new Set();
27
+ for (const entry of value) {
28
+ const id = normalizeText(entry, null);
29
+ if (!id || seen.has(id)) continue;
30
+ seen.add(id);
31
+ stageIds.push(id);
32
+ }
33
+ return stageIds.length > 0 ? stageIds : ['scene', 'light', 'final'];
34
+ }
35
+
36
+ function createTarget(aura, width, height) {
37
+ if (!aura?.draw2d || typeof aura.draw2d.createRenderTarget !== 'function') return null;
38
+ const target = aura.draw2d.createRenderTarget(width, height);
39
+ return target && typeof target === 'object' && target.ok === true ? target : null;
40
+ }
41
+
42
+ function destroyTarget(aura, target) {
43
+ if (!target || typeof target !== 'object' || target.ok !== true) return false;
44
+ if (!aura?.draw2d || typeof aura.draw2d.destroyRenderTarget !== 'function') return false;
45
+ aura.draw2d.destroyRenderTarget(target);
46
+ return true;
47
+ }
48
+
49
+ function resolveDimensions(compositor, options = {}) {
50
+ const width = positiveInteger(
51
+ options.width ?? compositor.fixedWidth,
52
+ null,
53
+ );
54
+ const height = positiveInteger(
55
+ options.height ?? compositor.fixedHeight,
56
+ null,
57
+ );
58
+ if (width == null || height == null) return null;
59
+ return { width, height };
60
+ }
61
+
62
+ function getStageRecord(compositor, stageId) {
63
+ return Array.isArray(compositor?.stages)
64
+ ? compositor.stages.find((entry) => entry.id === stageId) || null
65
+ : null;
66
+ }
67
+
68
+ function normalizeStageDefinitions(compositor, stageDefinitions) {
69
+ if (Array.isArray(stageDefinitions)) {
70
+ return compositor.stageIds.map((stageId) => (
71
+ stageDefinitions.find((entry) => entry?.id === stageId) || { id: stageId }
72
+ ));
73
+ }
74
+
75
+ if (stageDefinitions && typeof stageDefinitions === 'object') {
76
+ return compositor.stageIds.map((stageId) => {
77
+ const entry = stageDefinitions[stageId];
78
+ if (typeof entry === 'function') {
79
+ return { id: stageId, draw: entry };
80
+ }
81
+ if (entry && typeof entry === 'object') {
82
+ return { id: stageId, ...entry };
83
+ }
84
+ return { id: stageId };
85
+ });
86
+ }
87
+
88
+ return compositor.stageIds.map((stageId) => ({ id: stageId }));
89
+ }
90
+
91
+ export function createWorldCompositor2D(options = {}) {
92
+ const stageIds = normalizeStageIds(options.stageIds);
93
+ const finalStageId = stageIds.includes(options.finalStageId)
94
+ ? options.finalStageId
95
+ : stageIds[stageIds.length - 1];
96
+ return {
97
+ graphName: normalizeText(options.graphName, 'world-compositor-2d'),
98
+ stageIds,
99
+ finalStageId,
100
+ fixedWidth: positiveInteger(options.width ?? options.fixedWidth, null),
101
+ fixedHeight: positiveInteger(options.height ?? options.fixedHeight, null),
102
+ width: 0,
103
+ height: 0,
104
+ targets: null,
105
+ stages: null,
106
+ };
107
+ }
108
+
109
+ export function destroyWorldCompositor2D(compositor, auraRef = globalThis.aura) {
110
+ const aura = resolveAura(auraRef);
111
+ if (!compositor || typeof compositor !== 'object') return false;
112
+ if (compositor.targets && typeof compositor.targets === 'object') {
113
+ for (const stageId of compositor.stageIds || []) {
114
+ destroyTarget(aura, compositor.targets[stageId]);
115
+ }
116
+ }
117
+ compositor.width = 0;
118
+ compositor.height = 0;
119
+ compositor.targets = null;
120
+ compositor.stages = null;
121
+ return true;
122
+ }
123
+
124
+ export function ensureWorldCompositorTargets2D(compositor, options = {}, auraRef = globalThis.aura) {
125
+ const aura = resolveAura(auraRef);
126
+ if (!compositor || typeof compositor !== 'object') {
127
+ throw new Error('world compositor state is required.');
128
+ }
129
+
130
+ const dimensions = resolveDimensions(compositor, options);
131
+ if (!dimensions) return null;
132
+ if (
133
+ compositor.targets
134
+ && compositor.width === dimensions.width
135
+ && compositor.height === dimensions.height
136
+ ) {
137
+ return compositor.targets;
138
+ }
139
+
140
+ destroyWorldCompositor2D(compositor, aura);
141
+
142
+ const targets = {};
143
+ for (const stageId of compositor.stageIds) {
144
+ const target = createTarget(aura, dimensions.width, dimensions.height);
145
+ if (!target) {
146
+ for (const created of Object.values(targets)) {
147
+ destroyTarget(aura, created);
148
+ }
149
+ return null;
150
+ }
151
+ targets[stageId] = target;
152
+ }
153
+
154
+ compositor.width = dimensions.width;
155
+ compositor.height = dimensions.height;
156
+ compositor.targets = targets;
157
+ return targets;
158
+ }
159
+
160
+ export function runWorldCompositor2D(
161
+ compositor,
162
+ stageDefinitions,
163
+ options = {},
164
+ auraRef = globalThis.aura,
165
+ ) {
166
+ const aura = resolveAura(auraRef);
167
+ if (!compositor || typeof compositor !== 'object') {
168
+ throw new Error('world compositor state is required.');
169
+ }
170
+
171
+ const dimensions = resolveDimensions(compositor, options);
172
+ const fallbackDraw = typeof options.fallbackDraw === 'function' ? options.fallbackDraw : null;
173
+ if (!dimensions) {
174
+ if (fallbackDraw) fallbackDraw({ reasonCode: 'invalid_world_compositor_size' });
175
+ return {
176
+ ok: false,
177
+ reasonCode: 'invalid_world_compositor_size',
178
+ graph: null,
179
+ targets: null,
180
+ finalStage: null,
181
+ width: 0,
182
+ height: 0,
183
+ };
184
+ }
185
+
186
+ const targets = ensureWorldCompositorTargets2D(compositor, dimensions, aura);
187
+ if (!targets) {
188
+ if (fallbackDraw) fallbackDraw({ reasonCode: 'draw2d_render_target_unavailable' });
189
+ return {
190
+ ok: false,
191
+ reasonCode: 'draw2d_render_target_unavailable',
192
+ graph: null,
193
+ targets: null,
194
+ finalStage: null,
195
+ width: dimensions.width,
196
+ height: dimensions.height,
197
+ };
198
+ }
199
+
200
+ const definitions = normalizeStageDefinitions(compositor, stageDefinitions);
201
+ compositor.stages = definitions;
202
+ if (!aura?.draw2d || typeof aura.draw2d.runCompositorGraph !== 'function') {
203
+ if (fallbackDraw) {
204
+ fallbackDraw({
205
+ reasonCode: 'draw2d_compositor_unavailable',
206
+ width: dimensions.width,
207
+ height: dimensions.height,
208
+ targets,
209
+ });
210
+ }
211
+ return {
212
+ ok: false,
213
+ reasonCode: 'draw2d_compositor_unavailable',
214
+ graph: null,
215
+ targets,
216
+ finalStage: targets[compositor.finalStageId] || null,
217
+ width: dimensions.width,
218
+ height: dimensions.height,
219
+ };
220
+ }
221
+
222
+ const graph = aura.draw2d.runCompositorGraph(
223
+ compositor.graphName,
224
+ definitions.map((entry) => ({
225
+ id: entry.id,
226
+ target: targets[entry.id],
227
+ draw: () => {
228
+ if (typeof entry.draw !== 'function') return;
229
+ entry.draw({
230
+ id: entry.id,
231
+ width: dimensions.width,
232
+ height: dimensions.height,
233
+ graphName: compositor.graphName,
234
+ targets,
235
+ aura,
236
+ stage: getStageRecord(compositor, entry.id),
237
+ finalStageId: compositor.finalStageId,
238
+ });
239
+ },
240
+ })),
241
+ );
242
+
243
+ const finalStageIndex = compositor.stageIds.indexOf(compositor.finalStageId);
244
+ return {
245
+ ok: graph?.ok === true,
246
+ reasonCode: graph?.reasonCode || null,
247
+ graph,
248
+ targets,
249
+ finalStage: graph?.stages?.[finalStageIndex] || targets[compositor.finalStageId] || null,
250
+ width: dimensions.width,
251
+ height: dimensions.height,
252
+ };
253
+ }
@@ -0,0 +1,180 @@
1
+ import { syncStreamedWorldFocus2D } from './streamed-world-2d.js';
2
+
3
+ function cloneSnapshot(value) {
4
+ if (value == null) return value;
5
+ return JSON.parse(JSON.stringify(value));
6
+ }
7
+
8
+ function resolveStorage(auraRef = globalThis.aura) {
9
+ const storage = auraRef?.storage;
10
+ if (!storage || typeof storage !== 'object') return null;
11
+ return storage;
12
+ }
13
+
14
+ function storageSave(storage, key, value) {
15
+ if (!storage || typeof key !== 'string' || key.length === 0) return false;
16
+ if (typeof storage.save === 'function') {
17
+ storage.save(key, value);
18
+ return true;
19
+ }
20
+ if (typeof storage.set === 'function') {
21
+ storage.set(key, value);
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+
27
+ function storageLoad(storage, key, fallback = null) {
28
+ if (!storage || typeof key !== 'string' || key.length === 0) return fallback;
29
+ if (typeof storage.load === 'function') {
30
+ return storage.load(key, fallback);
31
+ }
32
+ if (typeof storage.get === 'function') {
33
+ return storage.get(key, fallback);
34
+ }
35
+ return fallback;
36
+ }
37
+
38
+ function storageDelete(storage, key) {
39
+ if (!storage || typeof key !== 'string' || key.length === 0) return false;
40
+ if (typeof storage.delete === 'function') {
41
+ storage.delete(key);
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+
47
+ function normalizeSnapshot(snapshot) {
48
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) return null;
49
+ return {
50
+ version: Number.isFinite(Number(snapshot.version)) ? Number(snapshot.version) : 1,
51
+ scope: typeof snapshot.scope === 'string' ? snapshot.scope : 'streamed-world-2d',
52
+ activeRegionId: typeof snapshot.activeRegionId === 'string' ? snapshot.activeRegionId : null,
53
+ focus: snapshot.focus && typeof snapshot.focus === 'object'
54
+ ? {
55
+ x: Number(snapshot.focus.x) || 0,
56
+ y: Number(snapshot.focus.y) || 0,
57
+ }
58
+ : null,
59
+ world: snapshot.world == null ? null : cloneSnapshot(snapshot.world),
60
+ regions: Array.isArray(snapshot.regions)
61
+ ? snapshot.regions
62
+ .filter((entry) => entry && typeof entry === 'object' && typeof entry.id === 'string')
63
+ .map((entry) => ({
64
+ id: entry.id,
65
+ data: cloneSnapshot(entry.data),
66
+ }))
67
+ : [],
68
+ };
69
+ }
70
+
71
+ export function captureStreamedWorldSnapshot2D(stream, options = {}) {
72
+ if (!stream || !(stream.regionById instanceof Map)) {
73
+ return null;
74
+ }
75
+ const captureRegion = typeof options.captureRegion === 'function' ? options.captureRegion : null;
76
+ const captureWorld = typeof options.captureWorld === 'function' ? options.captureWorld : null;
77
+ const regionIds = Array.isArray(options.regionIds)
78
+ ? options.regionIds.filter((entry) => typeof entry === 'string')
79
+ : stream.regions.map((entry) => entry.id);
80
+ const regions = [];
81
+
82
+ regionIds.forEach((regionId) => {
83
+ const descriptor = stream.regionById.get(regionId) || null;
84
+ if (!descriptor || !captureRegion) return;
85
+ const record = stream.loaded?.get(regionId) || null;
86
+ const data = captureRegion(descriptor, record, stream, options);
87
+ if (data == null) return;
88
+ regions.push({
89
+ id: regionId,
90
+ data: cloneSnapshot(data),
91
+ });
92
+ });
93
+
94
+ return {
95
+ version: 1,
96
+ scope: options.scope ?? 'streamed-world-2d',
97
+ activeRegionId: stream.activeRegionId ?? null,
98
+ focus: stream.focus ? { x: Number(stream.focus.x) || 0, y: Number(stream.focus.y) || 0 } : null,
99
+ world: captureWorld ? cloneSnapshot(captureWorld(stream, options)) : null,
100
+ regions,
101
+ };
102
+ }
103
+
104
+ export function applyStreamedWorldSnapshot2D(stream, snapshot, options = {}) {
105
+ if (!stream || !(stream.regionById instanceof Map)) {
106
+ return {
107
+ ok: false,
108
+ reasonCode: 'invalid_stream',
109
+ appliedRegionIds: [],
110
+ };
111
+ }
112
+ const normalized = normalizeSnapshot(snapshot);
113
+ if (!normalized) {
114
+ return {
115
+ ok: false,
116
+ reasonCode: 'invalid_snapshot',
117
+ appliedRegionIds: [],
118
+ };
119
+ }
120
+ const applyRegion = typeof options.applyRegion === 'function' ? options.applyRegion : null;
121
+ const applyWorld = typeof options.applyWorld === 'function' ? options.applyWorld : null;
122
+ const appliedRegionIds = [];
123
+
124
+ if (applyWorld && normalized.world != null) {
125
+ applyWorld(cloneSnapshot(normalized.world), stream, options);
126
+ }
127
+
128
+ normalized.regions.forEach((entry) => {
129
+ const descriptor = stream.regionById.get(entry.id) || null;
130
+ if (!descriptor || !applyRegion) return;
131
+ const record = stream.loaded?.get(entry.id) || null;
132
+ applyRegion(descriptor, record, cloneSnapshot(entry.data), stream, options);
133
+ appliedRegionIds.push(entry.id);
134
+ });
135
+
136
+ if (options.restoreFocus === true && normalized.focus) {
137
+ syncStreamedWorldFocus2D(stream, options.auraRef, normalized.focus);
138
+ }
139
+
140
+ return {
141
+ ok: true,
142
+ reasonCode: null,
143
+ appliedRegionIds,
144
+ restoredFocus: options.restoreFocus === true && normalized.focus != null,
145
+ };
146
+ }
147
+
148
+ export function saveStreamedWorldSnapshot2D(key, stream, auraRef = globalThis.aura, options = {}) {
149
+ const storage = resolveStorage(auraRef);
150
+ const snapshot = captureStreamedWorldSnapshot2D(stream, options);
151
+ if (!snapshot) {
152
+ return {
153
+ ok: false,
154
+ reasonCode: 'invalid_stream',
155
+ snapshot: null,
156
+ };
157
+ }
158
+ if (!storageSave(storage, key, snapshot)) {
159
+ return {
160
+ ok: false,
161
+ reasonCode: 'storage_unavailable',
162
+ snapshot,
163
+ };
164
+ }
165
+ return {
166
+ ok: true,
167
+ reasonCode: null,
168
+ snapshot,
169
+ };
170
+ }
171
+
172
+ export function loadStreamedWorldSnapshot2D(key, auraRef = globalThis.aura, fallback = null) {
173
+ const storage = resolveStorage(auraRef);
174
+ const snapshot = storageLoad(storage, key, fallback);
175
+ return normalizeSnapshot(snapshot);
176
+ }
177
+
178
+ export function deleteStreamedWorldSnapshot2D(key, auraRef = globalThis.aura) {
179
+ return storageDelete(resolveStorage(auraRef), key);
180
+ }
@@ -1,6 +1,7 @@
1
1
  import { createSceneRegistry } from './scene-registry.js';
2
2
  import { createProjectInspector } from './project-inspector.js';
3
3
  import { assertRuntimeCapabilities } from './capabilities.js';
4
+ import { initSplash, updateSplash, drawSplash, isSplashActive } from './splash.js';
4
5
 
5
6
  export function createApp() {
6
7
  const sceneRegistry = createSceneRegistry({
@@ -26,13 +27,16 @@ export function createApp() {
26
27
  },
27
28
  setup() {
28
29
  assertRuntimeCapabilities();
30
+ initSplash();
29
31
  activeScene()?.setup?.();
30
32
  },
31
33
  update(dt) {
34
+ if (isSplashActive()) { updateSplash(dt); return; }
32
35
  projectInspector.syncInput(globalThis.aura?.input || null);
33
36
  activeScene()?.update?.(dt);
34
37
  },
35
38
  draw() {
39
+ if (isSplashActive()) { drawSplash(); return; }
36
40
  activeScene()?.draw?.();
37
41
  projectInspector.draw({ activeSceneId });
38
42
  },
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from 'node:child_process';
3
+ import { spawn, spawnSync } from 'node:child_process';
4
4
  import { cpSync, createWriteStream, existsSync, mkdirSync, readFileSync } from 'node:fs';
5
5
  import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
6
6
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -58,9 +58,79 @@ const ALL_COMMANDS = ['dev', 'join', 'play', 'fork', 'publish', 'session', 'stat
58
58
  const ROOM_CODE_PATTERN = /^[A-Z0-9]{4,8}$/;
59
59
  const FORK_EXCLUDED_TOP_LEVEL = new Set(['.aura', '.git', '.logs', 'build', 'dist', 'node_modules']);
60
60
 
61
+ function resolveAuraMaxxBinary() {
62
+ return process.platform === 'win32' ? 'auramaxx.cmd' : 'auramaxx';
63
+ }
64
+
65
+ function resolveAuraAliasBinary() {
66
+ return process.platform === 'win32' ? 'aura.cmd' : 'aura';
67
+ }
68
+
69
+ function resolveNpmBinary() {
70
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
71
+ }
72
+
73
+ function resolveInvocationCwd() {
74
+ const forwardedCwd = process.env.AURA_INVOKE_CWD;
75
+ if (forwardedCwd && isAbsolute(forwardedCwd)) {
76
+ return resolve(forwardedCwd);
77
+ }
78
+
79
+ const shellPwd = process.env.PWD;
80
+ if (shellPwd && isAbsolute(shellPwd)) {
81
+ return resolve(shellPwd);
82
+ }
83
+
84
+ return resolve(process.cwd());
85
+ }
86
+
87
+ function canProbeBinary(binary) {
88
+ const probe = spawnSync(binary, ['--help'], {
89
+ stdio: 'ignore',
90
+ env: process.env,
91
+ });
92
+ return !probe.error && probe.status === 0;
93
+ }
94
+
95
+ function isAuraMaxxInstalled() {
96
+ if (process.env.AURAMAXX_CLI_AVAILABLE === '1') {
97
+ return true;
98
+ }
99
+ if (localAuraCli) {
100
+ return true;
101
+ }
102
+ const candidates = [resolveAuraMaxxBinary(), resolveAuraAliasBinary()];
103
+ return candidates.some((binary, index) => candidates.indexOf(binary) === index && canProbeBinary(binary));
104
+ }
105
+
106
+ async function installAuraMaxxGlobally() {
107
+ return new Promise((resolveInstall) => {
108
+ const child = spawn(
109
+ resolveNpmBinary(),
110
+ ['install', '-g', 'auramaxx', '--foreground-scripts'],
111
+ {
112
+ cwd: process.cwd(),
113
+ stdio: 'inherit',
114
+ env: process.env,
115
+ },
116
+ );
117
+
118
+ child.on('error', () => resolveInstall(false));
119
+ child.on('close', (code) => resolveInstall((code ?? 1) === 0));
120
+ });
121
+ }
122
+
61
123
  function resolveLocalAuraCli(startRoot) {
62
124
  let current = resolve(startRoot);
63
125
  while (true) {
126
+ const monorepoCandidate = resolve(current, 'packages', 'aurascript', 'src', 'cli', 'src', 'cli.mjs');
127
+ if (existsSync(monorepoCandidate)) {
128
+ return monorepoCandidate;
129
+ }
130
+ const packageSourceCandidate = resolve(current, 'src', 'cli', 'src', 'cli.mjs');
131
+ if (existsSync(packageSourceCandidate)) {
132
+ return packageSourceCandidate;
133
+ }
64
134
  const candidate = resolve(current, 'node_modules', '@auraindustry', 'aurajs', 'src', 'cli.mjs');
65
135
  if (existsSync(candidate)) {
66
136
  return candidate;
@@ -332,7 +402,7 @@ async function promptSelect(message, options, defaultValue) {
332
402
 
333
403
  function normalizeDisplayPath(targetPath) {
334
404
  const resolvedPath = resolve(targetPath);
335
- const relativePath = relative(process.cwd(), resolvedPath).replaceAll('\\', '/');
405
+ const relativePath = relative(resolveInvocationCwd(), resolvedPath).replaceAll('\\', '/');
336
406
  if (!relativePath) return '.';
337
407
  if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
338
408
  return `./${relativePath}`;
@@ -361,10 +431,10 @@ function resolveUniqueDestination(preferredPath) {
361
431
 
362
432
  function resolveDefaultForkDestination() {
363
433
  const defaultBaseName = `${toPackageShortName(packageName)}-fork`;
364
- const cwd = resolve(process.cwd());
365
- const baseDir = isSubpath(projectRoot, cwd)
434
+ const invocationCwd = resolveInvocationCwd();
435
+ const baseDir = isSubpath(projectRoot, invocationCwd)
366
436
  ? dirname(projectRoot)
367
- : cwd;
437
+ : invocationCwd;
368
438
  return resolveUniqueDestination(resolve(baseDir, defaultBaseName));
369
439
  }
370
440
 
@@ -413,7 +483,7 @@ function parseForkArgs(args) {
413
483
  async function resolveForkDestination(args) {
414
484
  const parsed = parseForkArgs(args);
415
485
  if (parsed.destination) {
416
- return resolve(process.cwd(), parsed.destination);
486
+ return resolve(resolveInvocationCwd(), parsed.destination);
417
487
  }
418
488
 
419
489
  const suggestedDestination = resolveDefaultForkDestination();
@@ -495,6 +565,41 @@ function parseArgs(argv) {
495
565
  };
496
566
  }
497
567
 
568
+ function parsePublishArgs(args) {
569
+ const passthroughArgs = [];
570
+ let npmToken = null;
571
+
572
+ for (let index = 0; index < args.length; index += 1) {
573
+ const token = String(args[index] || '');
574
+ if (token === '--token') {
575
+ if ((index + 1) >= args.length) {
576
+ throw createCliError('publish requires a token value after --token.');
577
+ }
578
+ npmToken = String(args[index + 1] || '').trim();
579
+ index += 1;
580
+ continue;
581
+ }
582
+ if (token.startsWith('--token=')) {
583
+ npmToken = token.slice('--token='.length).trim();
584
+ continue;
585
+ }
586
+ passthroughArgs.push(token);
587
+ }
588
+
589
+ if (npmToken !== null && npmToken.length === 0) {
590
+ throw createCliError('publish requires a non-empty token value after --token.');
591
+ }
592
+
593
+ return {
594
+ commandArgs: passthroughArgs,
595
+ env: npmToken
596
+ ? {
597
+ NODE_AUTH_TOKEN: npmToken,
598
+ }
599
+ : null,
600
+ };
601
+ }
602
+
498
603
  function readJsonIfExists(filePath) {
499
604
  if (!existsSync(filePath)) {
500
605
  return null;
@@ -1020,6 +1125,38 @@ async function chooseDefaultCommand() {
1020
1125
  return choice;
1021
1126
  }
1022
1127
 
1128
+ async function maybeOfferAuraMaxxInstall() {
1129
+ if (isAuraMaxxInstalled()) {
1130
+ return;
1131
+ }
1132
+
1133
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1134
+ return;
1135
+ }
1136
+
1137
+ printSection('AuraMaxx', 'AuraMaxx is not installed globally.');
1138
+ const choice = await promptSelect(
1139
+ ' Install AuraMaxx now?',
1140
+ [
1141
+ { value: 'yes', label: 'Yes, install AuraMaxx' },
1142
+ { value: 'no', label: 'No, continue playing' },
1143
+ ],
1144
+ 'yes',
1145
+ );
1146
+
1147
+ if (choice !== 'yes') {
1148
+ return;
1149
+ }
1150
+
1151
+ printSection('AuraMaxx', 'Installing global CLI...');
1152
+ const installed = await installAuraMaxxGlobally();
1153
+ if (installed) {
1154
+ printSection('AuraMaxx', 'Installed. Continuing play...');
1155
+ return;
1156
+ }
1157
+ printSection('AuraMaxx', 'Install failed. Continuing play...');
1158
+ }
1159
+
1023
1160
  async function main() {
1024
1161
  const parsed = parseArgs(process.argv.slice(2));
1025
1162
 
@@ -1049,6 +1186,7 @@ async function main() {
1049
1186
  if (command === 'play') {
1050
1187
  printBanner('PLAY');
1051
1188
  printSection(toDisplayTitle(packageName), 'Starting packaged local game session...');
1189
+ await maybeOfferAuraMaxxInstall();
1052
1190
  const externalAssets = await maybePrepareExternalAssets('play');
1053
1191
  const multiplayerEnv = await resolveLocalMultiplayerCommandEnv();
1054
1192
  await runCommand(
@@ -1145,8 +1283,11 @@ async function main() {
1145
1283
  printSection(toDisplayTitle(packageName), 'Publish lifecycle detected, skipping wrapper recursion.');
1146
1284
  return;
1147
1285
  }
1286
+ const publish = parsePublishArgs(commandArgs);
1148
1287
  printSection(toDisplayTitle(packageName), 'Publishing npm package (source + assets)...');
1149
- await runCommand('publish', resolveAuraCliInvocation(['publish', ...commandArgs]));
1288
+ await runCommand('publish', resolveAuraCliInvocation(['publish', ...publish.commandArgs]), {
1289
+ env: publish.env,
1290
+ });
1150
1291
  return;
1151
1292
  }
1152
1293