@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,662 @@
1
+ const TRIGGER_TRACKER_SCHEMA = 'aurajs.trigger-tracker.v1';
2
+ const INTERACTION_PROMPT_SCHEMA = 'aurajs.interaction-prompt.v1';
3
+
4
+ function toFiniteNumber(value, fallback = 0) {
5
+ return Number.isFinite(value) ? Number(value) : fallback;
6
+ }
7
+
8
+ function normalizeText(value) {
9
+ if (typeof value === 'string' && value.trim().length > 0) return value.trim();
10
+ return null;
11
+ }
12
+
13
+ function normalizeSubjectId(value) {
14
+ return normalizeText(value) || 'subject';
15
+ }
16
+
17
+ function normalizeDimensionHint(value) {
18
+ return value === '3d' ? '3d' : '2d';
19
+ }
20
+
21
+ function createFallbackId(prefix, parts) {
22
+ return `${prefix}:${parts.map((value) => String(value)).join(':')}`;
23
+ }
24
+
25
+ function toPoint2D(value) {
26
+ if (!value || typeof value !== 'object') return null;
27
+ if (Number.isFinite(value.x) && Number.isFinite(value.y)) {
28
+ return {
29
+ x: Number(value.x),
30
+ y: Number(value.y),
31
+ };
32
+ }
33
+ if (value.position && typeof value.position === 'object') {
34
+ return toPoint2D(value.position);
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function toRect2D(value) {
40
+ if (!value || typeof value !== 'object') return null;
41
+ const point = toPoint2D(value);
42
+ if (!point) return null;
43
+ const width = Number(value.w ?? value.width);
44
+ const height = Number(value.h ?? value.height);
45
+ if (!Number.isFinite(width) || !Number.isFinite(height)) {
46
+ return {
47
+ x: point.x,
48
+ y: point.y,
49
+ w: 0,
50
+ h: 0,
51
+ point: true,
52
+ };
53
+ }
54
+ return {
55
+ x: point.x,
56
+ y: point.y,
57
+ w: Math.max(0, width),
58
+ h: Math.max(0, height),
59
+ point: width <= 0 || height <= 0,
60
+ };
61
+ }
62
+
63
+ function normalizeRectBounds(options = {}) {
64
+ const width = Number(options.w ?? options.width);
65
+ const height = Number(options.h ?? options.height);
66
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
67
+ return null;
68
+ }
69
+
70
+ if (options.centered === true || Number.isFinite(options.cx) || Number.isFinite(options.cy)) {
71
+ const cx = toFiniteNumber(options.cx, options.x);
72
+ const cy = toFiniteNumber(options.cy, options.y);
73
+ return {
74
+ x: cx - (width * 0.5),
75
+ y: cy - (height * 0.5),
76
+ w: width,
77
+ h: height,
78
+ };
79
+ }
80
+
81
+ return {
82
+ x: toFiniteNumber(options.x),
83
+ y: toFiniteNumber(options.y),
84
+ w: width,
85
+ h: height,
86
+ };
87
+ }
88
+
89
+ function toPoint3D(value) {
90
+ if (!value || typeof value !== 'object') return null;
91
+ if (Number.isFinite(value.x) && Number.isFinite(value.y) && Number.isFinite(value.z)) {
92
+ return {
93
+ x: Number(value.x),
94
+ y: Number(value.y),
95
+ z: Number(value.z),
96
+ };
97
+ }
98
+ if (value.position && typeof value.position === 'object') {
99
+ return toPoint3D(value.position);
100
+ }
101
+ return null;
102
+ }
103
+
104
+ function toRadius3D(value) {
105
+ if (!value || typeof value !== 'object') return 0;
106
+ if (Number.isFinite(value.radius)) return Math.max(0, Number(value.radius));
107
+ if (value.bounds && typeof value.bounds === 'object' && Number.isFinite(value.bounds.radius)) {
108
+ return Math.max(0, Number(value.bounds.radius));
109
+ }
110
+ if (Number.isFinite(value.interactionRadius)) {
111
+ return Math.max(0, Number(value.interactionRadius));
112
+ }
113
+ return 0;
114
+ }
115
+
116
+ function createTriggerMeta(definition, fallbackKind) {
117
+ const promptText = normalizeText(definition.promptText) || normalizeText(definition.prompt);
118
+ const interactionKey = normalizeText(definition.interactionKey) || normalizeText(definition.key);
119
+ return {
120
+ id: normalizeText(definition.id),
121
+ kind: normalizeText(definition.kind) || fallbackKind,
122
+ role: normalizeText(definition.role) || 'trigger',
123
+ priority: Number.isFinite(definition.priority) ? Number(definition.priority) : 0,
124
+ promptText,
125
+ interactionKey,
126
+ interactable: definition.interactable === true
127
+ || definition.interactionEnabled === true
128
+ || Boolean(promptText)
129
+ || Boolean(interactionKey),
130
+ };
131
+ }
132
+
133
+ function normalizeTriggerDefinition2D(definition) {
134
+ if (!definition || typeof definition !== 'object') return null;
135
+ const id = normalizeText(definition.id);
136
+ if (!id) return null;
137
+
138
+ const rectSource = definition.rect && typeof definition.rect === 'object'
139
+ ? { ...definition, ...definition.rect }
140
+ : definition;
141
+ const rect = normalizeRectBounds(rectSource);
142
+ if (!rect) return null;
143
+
144
+ return {
145
+ ...createTriggerMeta(definition, 'zone'),
146
+ id,
147
+ dimension: '2d',
148
+ shape: 'rect',
149
+ ...rect,
150
+ };
151
+ }
152
+
153
+ function normalizeTriggerDefinition3D(definition) {
154
+ if (!definition || typeof definition !== 'object') return null;
155
+ const id = normalizeText(definition.id);
156
+ if (!id) return null;
157
+
158
+ const centerSource = definition.center && typeof definition.center === 'object'
159
+ ? definition.center
160
+ : definition;
161
+ const center = toPoint3D(centerSource);
162
+ if (!center) return null;
163
+
164
+ const shape = definition.shape === 'volume' || definition.kind === 'box' || definition.kind === 'volume'
165
+ ? 'volume'
166
+ : 'proximity';
167
+
168
+ if (shape === 'proximity') {
169
+ const radius = Number(definition.radius);
170
+ if (!Number.isFinite(radius) || radius <= 0) return null;
171
+ return {
172
+ ...createTriggerMeta(definition, 'zone'),
173
+ id,
174
+ dimension: '3d',
175
+ shape,
176
+ x: center.x,
177
+ y: center.y,
178
+ z: center.z,
179
+ radius,
180
+ };
181
+ }
182
+
183
+ const size = definition.size && typeof definition.size === 'object' ? definition.size : {};
184
+ const width = Number(definition.w ?? definition.width ?? size.x);
185
+ const height = Number(definition.h ?? definition.height ?? size.y);
186
+ const depth = Number(definition.d ?? definition.depth ?? size.z);
187
+ if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(depth)) return null;
188
+ if (width <= 0 || height <= 0 || depth <= 0) return null;
189
+ return {
190
+ ...createTriggerMeta(definition, 'zone'),
191
+ id,
192
+ dimension: '3d',
193
+ shape,
194
+ x: center.x,
195
+ y: center.y,
196
+ z: center.z,
197
+ size: {
198
+ x: width,
199
+ y: height,
200
+ z: depth,
201
+ },
202
+ w: width,
203
+ h: height,
204
+ d: depth,
205
+ };
206
+ }
207
+
208
+ function rectContainsPoint(rect, point) {
209
+ return point.x >= rect.x
210
+ && point.x <= rect.x + rect.w
211
+ && point.y >= rect.y
212
+ && point.y <= rect.y + rect.h;
213
+ }
214
+
215
+ function rectsOverlap(left, right) {
216
+ return left.x < right.x + right.w
217
+ && left.x + left.w > right.x
218
+ && left.y < right.y + right.h
219
+ && left.y + left.h > right.y;
220
+ }
221
+
222
+ function isRectTriggerActive(subject, trigger) {
223
+ if (subject.point) return rectContainsPoint(trigger, subject);
224
+ return rectsOverlap(subject, trigger);
225
+ }
226
+
227
+ function isRadiusTriggerActive(subject, trigger) {
228
+ const subjectRadius = toRadius3D(subject);
229
+ const maxRadius = trigger.radius + subjectRadius;
230
+ const dx = subject.x - trigger.x;
231
+ const dy = subject.y - trigger.y;
232
+ const dz = subject.z - trigger.z;
233
+ return ((dx * dx) + (dy * dy) + (dz * dz)) <= (maxRadius * maxRadius);
234
+ }
235
+
236
+ function isBoxTriggerActive(subject, trigger) {
237
+ const subjectRadius = toRadius3D(subject);
238
+ const halfX = trigger.size.x * 0.5;
239
+ const halfY = trigger.size.y * 0.5;
240
+ const halfZ = trigger.size.z * 0.5;
241
+ return Math.abs(subject.x - trigger.x) <= halfX + subjectRadius
242
+ && Math.abs(subject.y - trigger.y) <= halfY + subjectRadius
243
+ && Math.abs(subject.z - trigger.z) <= halfZ + subjectRadius;
244
+ }
245
+
246
+ function createTriggerEntry(trigger, active) {
247
+ if (!trigger) return null;
248
+ return {
249
+ id: trigger.id,
250
+ kind: trigger.kind,
251
+ dimension: trigger.dimension,
252
+ shape: trigger.shape,
253
+ role: trigger.role,
254
+ priority: trigger.priority,
255
+ promptText: trigger.promptText,
256
+ prompt: trigger.promptText,
257
+ interactionKey: trigger.interactionKey,
258
+ interactionEnabled: trigger.interactable,
259
+ interactable: trigger.interactable,
260
+ active,
261
+ };
262
+ }
263
+
264
+ function cloneEntries(entries) {
265
+ return entries.map((entry) => ({ ...entry }));
266
+ }
267
+
268
+ function sortEntries(entries) {
269
+ entries.sort((left, right) => {
270
+ const byPriority = right.priority - left.priority;
271
+ if (byPriority !== 0) return byPriority;
272
+ return left.id.localeCompare(right.id);
273
+ });
274
+ return entries;
275
+ }
276
+
277
+ function resultErr(reasonCode, extras = {}) {
278
+ return {
279
+ ok: false,
280
+ reasonCode,
281
+ ...extras,
282
+ };
283
+ }
284
+
285
+ function buildStepResult(tracker, entriesById, nextActiveIds, subjectId, dimension) {
286
+ const previousActiveIds = Array.isArray(tracker.activeIds) ? tracker.activeIds : [];
287
+ const previousActiveSet = new Set(previousActiveIds);
288
+ const nextActiveSet = new Set(nextActiveIds);
289
+ const previousEntriesById = tracker.entriesById && typeof tracker.entriesById === 'object'
290
+ ? tracker.entriesById
291
+ : {};
292
+
293
+ const active = sortEntries(nextActiveIds
294
+ .map((triggerId) => createTriggerEntry(entriesById.get(triggerId), true))
295
+ .filter(Boolean));
296
+ const activeIds = active.map((entry) => entry.id);
297
+ const activeIdSet = new Set(activeIds);
298
+ const entered = sortEntries(active.filter((entry) => !previousActiveSet.has(entry.id)).map((entry) => ({ ...entry })));
299
+ const stayed = sortEntries(active.filter((entry) => previousActiveSet.has(entry.id)).map((entry) => ({ ...entry })));
300
+ const left = sortEntries(previousActiveIds
301
+ .filter((triggerId) => !nextActiveSet.has(triggerId) && !activeIdSet.has(triggerId))
302
+ .map((triggerId) => createTriggerEntry(entriesById.get(triggerId) || previousEntriesById[triggerId], false))
303
+ .filter(Boolean));
304
+
305
+ tracker.subjectId = subjectId;
306
+ tracker.dimension = dimension;
307
+ tracker.stepCount += 1;
308
+ tracker.mutationCount += 1;
309
+ tracker.activeIds = [...activeIds];
310
+ tracker.entriesById = {
311
+ ...previousEntriesById,
312
+ ...Object.fromEntries(entriesById.entries()),
313
+ };
314
+ tracker.lastStep = {
315
+ entered: cloneEntries(entered),
316
+ stayed: cloneEntries(stayed),
317
+ left: cloneEntries(left),
318
+ active: cloneEntries(active),
319
+ };
320
+
321
+ return {
322
+ ok: true,
323
+ reasonCode: 'trigger_step_ok',
324
+ schema: TRIGGER_TRACKER_SCHEMA,
325
+ dimension,
326
+ subjectId,
327
+ stepCount: tracker.stepCount,
328
+ mutationCount: tracker.mutationCount,
329
+ frame: tracker.stepCount,
330
+ activeIds,
331
+ enteredIds: entered.map((entry) => entry.id),
332
+ stayedIds: stayed.map((entry) => entry.id),
333
+ leftIds: left.map((entry) => entry.id),
334
+ entered: cloneEntries(entered),
335
+ stayed: cloneEntries(stayed),
336
+ left: cloneEntries(left),
337
+ active: cloneEntries(active),
338
+ };
339
+ }
340
+
341
+ function normalizeActiveTriggerEntries(entries) {
342
+ return sortEntries(
343
+ [...entries].filter((entry) => entry && entry.interactable).map((entry) => ({ ...entry })),
344
+ );
345
+ }
346
+
347
+ export function createTriggerTracker(options = {}) {
348
+ return {
349
+ schema: TRIGGER_TRACKER_SCHEMA,
350
+ subjectId: normalizeSubjectId(options.subjectId),
351
+ dimension: normalizeDimensionHint(options.dimension),
352
+ stepCount: 0,
353
+ mutationCount: 0,
354
+ activeIds: [],
355
+ entriesById: {},
356
+ lastStep: {
357
+ entered: [],
358
+ stayed: [],
359
+ left: [],
360
+ active: [],
361
+ },
362
+ };
363
+ }
364
+
365
+ export function getTriggerTrackerSnapshot(tracker) {
366
+ return {
367
+ schema: TRIGGER_TRACKER_SCHEMA,
368
+ subjectId: normalizeSubjectId(tracker?.subjectId),
369
+ dimension: normalizeDimensionHint(tracker?.dimension),
370
+ stepCount: toFiniteNumber(tracker?.stepCount),
371
+ mutationCount: toFiniteNumber(tracker?.mutationCount),
372
+ activeIds: Array.isArray(tracker?.activeIds) ? [...tracker.activeIds] : [],
373
+ lastStep: {
374
+ entered: cloneEntries(Array.isArray(tracker?.lastStep?.entered) ? tracker.lastStep.entered : []),
375
+ stayed: cloneEntries(Array.isArray(tracker?.lastStep?.stayed) ? tracker.lastStep.stayed : []),
376
+ left: cloneEntries(Array.isArray(tracker?.lastStep?.left) ? tracker.lastStep.left : []),
377
+ active: cloneEntries(Array.isArray(tracker?.lastStep?.active) ? tracker.lastStep.active : []),
378
+ },
379
+ };
380
+ }
381
+
382
+ export function stepRectTriggers2D(tracker, subject, triggers = [], options = {}) {
383
+ const rectSubject = toRect2D(subject);
384
+ if (!rectSubject) return resultErr('trigger_invalid_subject');
385
+
386
+ const subjectId = normalizeSubjectId(options.subjectId ?? tracker?.subjectId);
387
+ const normalizedTriggers = [];
388
+ for (const definition of Array.isArray(triggers) ? triggers : []) {
389
+ const trigger = normalizeTriggerDefinition2D(definition);
390
+ if (!trigger) return resultErr('trigger_invalid_definition');
391
+ normalizedTriggers.push(trigger);
392
+ }
393
+
394
+ const entriesById = new Map();
395
+ const activeIds = [];
396
+ for (const trigger of normalizedTriggers) {
397
+ entriesById.set(trigger.id, trigger);
398
+ if (isRectTriggerActive(rectSubject, trigger)) {
399
+ activeIds.push(trigger.id);
400
+ }
401
+ }
402
+
403
+ return buildStepResult(tracker, entriesById, activeIds, subjectId, '2d');
404
+ }
405
+
406
+ export function stepVolumeTriggers3D(tracker, subject, triggers = [], options = {}) {
407
+ const pointSubject = toPoint3D(subject);
408
+ if (!pointSubject) return resultErr('trigger_invalid_subject');
409
+
410
+ const subjectId = normalizeSubjectId(options.subjectId ?? tracker?.subjectId);
411
+ const normalizedTriggers = [];
412
+ for (const definition of Array.isArray(triggers) ? triggers : []) {
413
+ const trigger = normalizeTriggerDefinition3D(definition);
414
+ if (!trigger) return resultErr('trigger_invalid_definition');
415
+ normalizedTriggers.push(trigger);
416
+ }
417
+
418
+ const entriesById = new Map();
419
+ const activeIds = [];
420
+ for (const trigger of normalizedTriggers) {
421
+ entriesById.set(trigger.id, trigger);
422
+ const active = trigger.shape === 'volume'
423
+ ? isBoxTriggerActive(subject, trigger)
424
+ : isRadiusTriggerActive(subject, trigger);
425
+ if (active) {
426
+ activeIds.push(trigger.id);
427
+ }
428
+ }
429
+
430
+ return buildStepResult(tracker, entriesById, activeIds, subjectId, '3d');
431
+ }
432
+
433
+ export function resolveTriggerInteraction(stepResult, options = {}) {
434
+ const activeEntries = normalizeActiveTriggerEntries(Array.isArray(stepResult?.active) ? stepResult.active : []);
435
+ const promptText = normalizeText(options.promptText) || normalizeText(options.prompt);
436
+
437
+ if (activeEntries.length <= 0) {
438
+ return {
439
+ ok: false,
440
+ reasonCode: 'trigger_interaction_unavailable',
441
+ ready: false,
442
+ triggerId: null,
443
+ triggerKind: null,
444
+ promptText: null,
445
+ interactionKey: null,
446
+ interacted: false,
447
+ activeTriggerIds: [],
448
+ };
449
+ }
450
+
451
+ const selected = activeEntries[0];
452
+ return {
453
+ ok: true,
454
+ reasonCode: options.interactPressed === true ? 'trigger_interaction_accepted' : 'trigger_interaction_ready',
455
+ ready: true,
456
+ triggerId: selected.id,
457
+ triggerKind: selected.kind,
458
+ promptText: promptText || selected.promptText,
459
+ interactionKey: selected.interactionKey || null,
460
+ interacted: options.interactPressed === true,
461
+ activeTriggerIds: activeEntries.map((entry) => entry.id),
462
+ };
463
+ }
464
+
465
+ function resolveRectTriggerArgs(args) {
466
+ if (args.length === 1 && args[0] && typeof args[0] === 'object' && !Array.isArray(args[0])) {
467
+ return args[0];
468
+ }
469
+ const [id, x, y, w, h, options = {}] = args;
470
+ return {
471
+ ...options,
472
+ id,
473
+ x,
474
+ y,
475
+ w,
476
+ h,
477
+ };
478
+ }
479
+
480
+ function resolveProximityTriggerArgs(args) {
481
+ if (args.length === 1 && args[0] && typeof args[0] === 'object' && !Array.isArray(args[0])) {
482
+ return args[0];
483
+ }
484
+ const [id, x, y, z, radius, options = {}] = args;
485
+ return {
486
+ ...options,
487
+ id,
488
+ x,
489
+ y,
490
+ z,
491
+ radius,
492
+ };
493
+ }
494
+
495
+ function resolveVolumeTriggerArgs(args) {
496
+ if (args.length === 1 && args[0] && typeof args[0] === 'object' && !Array.isArray(args[0])) {
497
+ return args[0];
498
+ }
499
+ const [id, x, y, z, sizeOrWidth, heightOrOptions, depth, options] = args;
500
+ if (sizeOrWidth && typeof sizeOrWidth === 'object' && !Array.isArray(sizeOrWidth)) {
501
+ return {
502
+ ...(heightOrOptions && typeof heightOrOptions === 'object' && !Array.isArray(heightOrOptions) ? heightOrOptions : {}),
503
+ id,
504
+ x,
505
+ y,
506
+ z,
507
+ size: sizeOrWidth,
508
+ };
509
+ }
510
+ return {
511
+ ...(options && typeof options === 'object' && !Array.isArray(options) ? options : {}),
512
+ id,
513
+ x,
514
+ y,
515
+ z,
516
+ w: sizeOrWidth,
517
+ h: heightOrOptions,
518
+ d: depth,
519
+ };
520
+ }
521
+
522
+ export function createRectTrigger2D(...args) {
523
+ const options = resolveRectTriggerArgs(args);
524
+ const rect = normalizeRectBounds(options);
525
+ if (!rect) {
526
+ throw new TypeError('createRectTrigger2D requires finite positive width and height');
527
+ }
528
+ const promptText = normalizeText(options.promptText) || normalizeText(options.prompt);
529
+ const interactionKey = normalizeText(options.interactionKey) || normalizeText(options.key);
530
+ return {
531
+ id: normalizeText(options.id) || createFallbackId('trigger-2d', [rect.x, rect.y, rect.w, rect.h]),
532
+ kind: normalizeText(options.kind) || 'zone',
533
+ role: normalizeText(options.role) || 'trigger',
534
+ priority: Number.isFinite(options.priority) ? Number(options.priority) : 0,
535
+ promptText,
536
+ interactionKey,
537
+ interactable: options.interactable === true
538
+ || options.interactionEnabled === true
539
+ || Boolean(promptText)
540
+ || Boolean(interactionKey),
541
+ shape: 'rect',
542
+ ...rect,
543
+ };
544
+ }
545
+
546
+ export function createProximityTrigger3D(...args) {
547
+ const options = resolveProximityTriggerArgs(args);
548
+ const center = toPoint3D(options.center || options);
549
+ const radius = Number(options.radius);
550
+ if (!center || !Number.isFinite(radius) || radius <= 0) {
551
+ throw new TypeError('createProximityTrigger3D requires a center and positive radius');
552
+ }
553
+ const promptText = normalizeText(options.promptText) || normalizeText(options.prompt);
554
+ const interactionKey = normalizeText(options.interactionKey) || normalizeText(options.key);
555
+ return {
556
+ id: normalizeText(options.id) || createFallbackId('trigger-3d-proximity', [center.x, center.y, center.z, radius]),
557
+ kind: normalizeText(options.kind) || 'zone',
558
+ role: normalizeText(options.role) || 'trigger',
559
+ priority: Number.isFinite(options.priority) ? Number(options.priority) : 0,
560
+ promptText,
561
+ interactionKey,
562
+ interactable: options.interactable === true
563
+ || options.interactionEnabled === true
564
+ || Boolean(promptText)
565
+ || Boolean(interactionKey),
566
+ shape: 'proximity',
567
+ x: center.x,
568
+ y: center.y,
569
+ z: center.z,
570
+ radius,
571
+ };
572
+ }
573
+
574
+ export function createVolumeTrigger3D(...args) {
575
+ const options = resolveVolumeTriggerArgs(args);
576
+ const center = toPoint3D(options.center || options);
577
+ const size = options.size && typeof options.size === 'object' ? options.size : {};
578
+ const width = Number(options.w ?? options.width ?? size.x);
579
+ const height = Number(options.h ?? options.height ?? size.y);
580
+ const depth = Number(options.d ?? options.depth ?? size.z);
581
+ if (!center || !Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(depth) || width <= 0 || height <= 0 || depth <= 0) {
582
+ throw new TypeError('createVolumeTrigger3D requires a center and positive volume dimensions');
583
+ }
584
+ const promptText = normalizeText(options.promptText) || normalizeText(options.prompt);
585
+ const interactionKey = normalizeText(options.interactionKey) || normalizeText(options.key);
586
+ return {
587
+ id: normalizeText(options.id) || createFallbackId('trigger-3d-volume', [center.x, center.y, center.z, width, height, depth]),
588
+ kind: normalizeText(options.kind) || 'zone',
589
+ role: normalizeText(options.role) || 'trigger',
590
+ priority: Number.isFinite(options.priority) ? Number(options.priority) : 0,
591
+ promptText,
592
+ interactionKey,
593
+ interactable: options.interactable === true
594
+ || options.interactionEnabled === true
595
+ || Boolean(promptText)
596
+ || Boolean(interactionKey),
597
+ shape: 'volume',
598
+ x: center.x,
599
+ y: center.y,
600
+ z: center.z,
601
+ size: {
602
+ x: width,
603
+ y: height,
604
+ z: depth,
605
+ },
606
+ w: width,
607
+ h: height,
608
+ d: depth,
609
+ };
610
+ }
611
+
612
+ export function stepTriggerTracker(tracker, subject, triggers = [], options = {}) {
613
+ const dimension = normalizeDimensionHint(
614
+ options.dimension
615
+ || tracker?.dimension
616
+ || (Array.isArray(triggers) && triggers.some((entry) => entry && (entry.shape === 'volume' || entry.shape === 'proximity' || Number.isFinite(entry.z))) ? '3d' : '2d'),
617
+ );
618
+ if (dimension === '3d') {
619
+ return stepVolumeTriggers3D(tracker, subject, triggers, options);
620
+ }
621
+ return stepRectTriggers2D(tracker, subject, triggers, options);
622
+ }
623
+
624
+ export function createInteractionPromptState(options = {}) {
625
+ return {
626
+ schema: INTERACTION_PROMPT_SCHEMA,
627
+ fallbackPrompt: normalizeText(options.fallbackPrompt) || normalizeText(options.prompt) || 'Interact',
628
+ visible: false,
629
+ ready: false,
630
+ accepted: false,
631
+ triggerId: null,
632
+ triggerKind: null,
633
+ prompt: null,
634
+ interactionKey: null,
635
+ candidateIds: [],
636
+ acceptedTriggerId: null,
637
+ lastAcceptedTriggerId: null,
638
+ reasonCode: 'interaction_unavailable',
639
+ };
640
+ }
641
+
642
+ export function updateInteractionPromptState(promptState, triggerState, options = {}) {
643
+ const resolved = resolveTriggerInteraction(triggerState, options);
644
+ promptState.visible = resolved.ready === true;
645
+ promptState.ready = resolved.ready === true;
646
+ promptState.accepted = resolved.interacted === true;
647
+ promptState.triggerId = resolved.triggerId || null;
648
+ promptState.triggerKind = resolved.triggerKind || null;
649
+ promptState.prompt = resolved.ready === true
650
+ ? (resolved.promptText || promptState.fallbackPrompt)
651
+ : null;
652
+ promptState.interactionKey = resolved.interactionKey || null;
653
+ promptState.candidateIds = Array.isArray(resolved.activeTriggerIds) ? [...resolved.activeTriggerIds] : [];
654
+ promptState.acceptedTriggerId = promptState.accepted ? promptState.triggerId : null;
655
+ promptState.reasonCode = promptState.accepted
656
+ ? 'interaction_accepted'
657
+ : (promptState.ready ? 'interaction_ready' : 'interaction_unavailable');
658
+ if (promptState.acceptedTriggerId != null) {
659
+ promptState.lastAcceptedTriggerId = promptState.acceptedTriggerId;
660
+ }
661
+ return promptState;
662
+ }