@auraindustry/aurajs 0.1.3 → 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 (108) 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/authored-project.mjs +498 -2
  6. package/src/build-contract/capabilities.mjs +87 -1
  7. package/src/build-contract/constants.mjs +1 -0
  8. package/src/build-contract.mjs +2 -0
  9. package/src/bundler.mjs +143 -13
  10. package/src/cli.mjs +681 -13
  11. package/src/commands/packs.mjs +741 -0
  12. package/src/commands/project-authoring.mjs +128 -1
  13. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
  14. package/src/conformance/cases/core-runtime-cases.mjs +6 -2
  15. package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
  16. package/src/conformance/cases/systems-and-gameplay-cases.mjs +265 -4
  17. package/src/conformance-mobile.mjs +166 -0
  18. package/src/conformance.mjs +89 -30
  19. package/src/evidence-bundle.mjs +242 -0
  20. package/src/headless-test/runtime-coordinator.mjs +186 -33
  21. package/src/headless-test.mjs +2 -0
  22. package/src/helpers/2d/index.mjs +183 -0
  23. package/src/helpers/index.mjs +26 -0
  24. package/src/helpers/starter-utils/adventure-objectives.js +102 -0
  25. package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
  26. package/src/helpers/starter-utils/animation-2d.js +337 -0
  27. package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
  28. package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
  29. package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
  30. package/src/helpers/starter-utils/avatar-3d.js +404 -0
  31. package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
  32. package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
  33. package/src/helpers/starter-utils/core.js +150 -0
  34. package/src/helpers/starter-utils/dialogue-2d.js +351 -0
  35. package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
  36. package/src/helpers/starter-utils/index.js +26 -0
  37. package/src/helpers/starter-utils/inventory-2d.js +268 -0
  38. package/src/helpers/starter-utils/journal-2d.js +267 -0
  39. package/src/helpers/starter-utils/platformer-3d.js +132 -0
  40. package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
  41. package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
  42. package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
  43. package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
  44. package/src/helpers/starter-utils/triggers.js +662 -0
  45. package/src/helpers/starter-utils/tween-2d.js +615 -0
  46. package/src/helpers/starter-utils/wave-director.js +101 -0
  47. package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
  48. package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
  49. package/src/mobile/android/build.mjs +606 -0
  50. package/src/mobile/android/host-artifact.mjs +280 -0
  51. package/src/mobile/ios/build.mjs +1323 -0
  52. package/src/mobile/ios/host-artifact.mjs +819 -0
  53. package/src/mobile/shared/capabilities.mjs +174 -0
  54. package/src/packs/catalog.mjs +259 -0
  55. package/src/perf-benchmark-runner.mjs +17 -12
  56. package/src/perf-benchmark.mjs +408 -4
  57. package/src/publish-command.mjs +303 -6
  58. package/src/replay-runtime.mjs +257 -0
  59. package/src/scaffold/config.mjs +2 -0
  60. package/src/scaffold/fs.mjs +8 -1
  61. package/src/scaffold/project-docs.mjs +43 -1
  62. package/src/scaffold.mjs +4 -0
  63. package/src/session-runtime.mjs +4 -3
  64. package/src/web-conformance.mjs +0 -36
  65. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
  66. package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
  67. package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
  68. package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
  69. package/templates/create/2d-adventure/docs/design/loop.md +4 -3
  70. package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
  71. package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
  72. package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
  73. package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
  74. package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
  75. package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
  76. package/templates/create/3d/scenes/gameplay.scene.js +30 -3
  77. package/templates/create/3d/src/runtime/capabilities.js +5 -0
  78. package/templates/create/3d/src/runtime/materials.js +10 -0
  79. package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
  80. package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
  81. package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
  82. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
  83. package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
  84. package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
  85. package/templates/create/shared/src/runtime/ui-forms.js +552 -0
  86. package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
  87. package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
  88. package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
  89. package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
  90. package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
  91. package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
  92. package/templates/create/shared/src/starter-utils/index.js +15 -1
  93. package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
  94. package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
  95. package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
  96. package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
  97. package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
  98. package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
  99. package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
  100. package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
  101. package/templates/create-bin/play.js +36 -7
  102. package/templates/skills/auramaxx/SKILL.md +46 -0
  103. package/templates/skills/auramaxx/project-requirements.md +68 -0
  104. package/templates/skills/auramaxx/starter-recipes.md +104 -0
  105. package/templates/skills/auramaxx/validation-checklist.md +49 -0
  106. package/templates/skills/aurajs/SKILL.md +0 -96
  107. package/templates/skills/aurajs/api-contract-3d.md +0 -7
  108. package/templates/skills/aurajs/api-contract.md +0 -7
@@ -0,0 +1,615 @@
1
+ function finite(value, fallback = 0) {
2
+ const numeric = Number(value);
3
+ return Number.isFinite(numeric) ? numeric : fallback;
4
+ }
5
+
6
+ function positiveNumber(value, fallback, label) {
7
+ const numeric = finite(value, fallback);
8
+ if (!(numeric > 0)) {
9
+ throw new Error(`${label} must be a positive number.`);
10
+ }
11
+ return numeric;
12
+ }
13
+
14
+ function positiveInteger(value, fallback, label) {
15
+ const numeric = Math.floor(finite(value, fallback));
16
+ if (!(numeric >= 1)) {
17
+ throw new Error(`${label} must be a positive integer.`);
18
+ }
19
+ return numeric;
20
+ }
21
+
22
+ function normalizePath(path) {
23
+ if (Array.isArray(path) && path.length > 0) {
24
+ return path.map((segment) => {
25
+ if ((typeof segment !== 'string' && typeof segment !== 'number') || String(segment).length === 0) {
26
+ throw new Error('tween path segments must be non-empty strings or numbers.');
27
+ }
28
+ return String(segment);
29
+ });
30
+ }
31
+
32
+ if (typeof path === 'string' && path.trim().length > 0) {
33
+ return path
34
+ .split('.')
35
+ .map((segment) => segment.trim())
36
+ .filter(Boolean);
37
+ }
38
+
39
+ throw new Error('tween path must be a non-empty string or path array.');
40
+ }
41
+
42
+ function getPathParent(target, pathSegments) {
43
+ let current = target;
44
+ for (let index = 0; index < pathSegments.length - 1; index += 1) {
45
+ const key = pathSegments[index];
46
+ if (!current || typeof current !== 'object' || !(key in current)) {
47
+ throw new Error(`tween path "${pathSegments.join('.')}" is missing segment "${key}".`);
48
+ }
49
+ current = current[key];
50
+ }
51
+ return current;
52
+ }
53
+
54
+ function getNumericPathValue(target, pathSegments) {
55
+ const parent = getPathParent(target, pathSegments);
56
+ const key = pathSegments[pathSegments.length - 1];
57
+ const value = parent?.[key];
58
+ const numeric = Number(value);
59
+ if (!Number.isFinite(numeric)) {
60
+ throw new Error(`tween path "${pathSegments.join('.')}" must resolve to a finite numeric value.`);
61
+ }
62
+ return numeric;
63
+ }
64
+
65
+ function setNumericPathValue(target, pathSegments, value) {
66
+ const parent = getPathParent(target, pathSegments);
67
+ const key = pathSegments[pathSegments.length - 1];
68
+ parent[key] = value;
69
+ }
70
+
71
+ function normalizeEasing(value, fallback = 'linear') {
72
+ if (typeof value !== 'string' || value.trim().length === 0) {
73
+ return fallback;
74
+ }
75
+ return value.trim();
76
+ }
77
+
78
+ function clonePlan(plan) {
79
+ return JSON.parse(JSON.stringify(plan));
80
+ }
81
+
82
+ function buildPlanState(motionId) {
83
+ return {
84
+ motionId,
85
+ active: true,
86
+ completed: false,
87
+ cancelled: false,
88
+ };
89
+ }
90
+
91
+ function requireTween(aura) {
92
+ if (
93
+ !aura?.tween
94
+ || typeof aura.tween.create !== 'function'
95
+ || typeof aura.tween.pause !== 'function'
96
+ || typeof aura.tween.resume !== 'function'
97
+ || typeof aura.tween.cancel !== 'function'
98
+ || typeof aura.tween.onUpdate !== 'function'
99
+ || typeof aura.tween.onComplete !== 'function'
100
+ || typeof aura.tween.update !== 'function'
101
+ || typeof aura.tween.getState !== 'function'
102
+ ) {
103
+ throw new Error('tween 2d helpers require aura.tween create/pause/resume/cancel/onUpdate/onComplete/update/getState support.');
104
+ }
105
+ }
106
+
107
+ function createController(onCancel = null) {
108
+ return {
109
+ active: true,
110
+ completed: false,
111
+ cancelled: false,
112
+ reversePlan: null,
113
+ listeners: [],
114
+ finish(reversePlan = null) {
115
+ if (!this.active) return;
116
+ this.active = false;
117
+ this.completed = true;
118
+ this.reversePlan = reversePlan ? clonePlan(reversePlan) : null;
119
+ for (const listener of this.listeners.splice(0)) {
120
+ listener(this);
121
+ }
122
+ },
123
+ cancel() {
124
+ if (!this.active) return false;
125
+ this.active = false;
126
+ this.cancelled = true;
127
+ if (typeof onCancel === 'function') {
128
+ onCancel();
129
+ }
130
+ for (const listener of this.listeners.splice(0)) {
131
+ listener(this);
132
+ }
133
+ return true;
134
+ },
135
+ onSettled(listener) {
136
+ if (typeof listener !== 'function') return;
137
+ if (!this.active) {
138
+ listener(this);
139
+ return;
140
+ }
141
+ this.listeners.push(listener);
142
+ },
143
+ };
144
+ }
145
+
146
+ function tweenResultOk(result, label) {
147
+ if (!result || result.ok !== true) {
148
+ throw new Error(`${label} failed: ${result?.reason || 'unknown_reason'}.`);
149
+ }
150
+ return result;
151
+ }
152
+
153
+ function propertyReversePlan(plan, from, to) {
154
+ return propertyTween2D(plan.path, {
155
+ from: to,
156
+ to: from,
157
+ duration: plan.duration,
158
+ easing: plan.easing,
159
+ });
160
+ }
161
+
162
+ function runTweenPlanInternal(runtime, target, plan) {
163
+ if (!plan || typeof plan !== 'object') {
164
+ throw new Error('tween plans must be plain objects.');
165
+ }
166
+
167
+ switch (plan.type) {
168
+ case 'property': {
169
+ const pathSegments = normalizePath(plan.path);
170
+ const from = Object.prototype.hasOwnProperty.call(plan, 'from')
171
+ ? finite(plan.from, NaN)
172
+ : getNumericPathValue(target, pathSegments);
173
+ if (!Number.isFinite(from)) {
174
+ throw new Error(`tween property "${pathSegments.join('.')}" must start from a finite numeric value.`);
175
+ }
176
+
177
+ let to = null;
178
+ if (Object.prototype.hasOwnProperty.call(plan, 'to')) {
179
+ to = finite(plan.to, NaN);
180
+ } else if (Object.prototype.hasOwnProperty.call(plan, 'by')) {
181
+ to = from + finite(plan.by, NaN);
182
+ }
183
+ if (!Number.isFinite(to)) {
184
+ throw new Error(`tween property "${pathSegments.join('.')}" must declare a finite "to" or "by" value.`);
185
+ }
186
+
187
+ const duration = positiveNumber(plan.duration, 0, `tween property "${pathSegments.join('.')}" duration`);
188
+ const easing = normalizeEasing(plan.easing, 'linear');
189
+ setNumericPathValue(target, pathSegments, from);
190
+ const created = tweenResultOk(
191
+ runtime.aura.tween.create({ from, to, duration, easing, playing: true }),
192
+ `tween property "${pathSegments.join('.')}" create`,
193
+ );
194
+ const tweenId = created.tweenId;
195
+ tweenResultOk(
196
+ runtime.aura.tween.onUpdate(tweenId, ({ value }) => {
197
+ setNumericPathValue(target, pathSegments, value);
198
+ }),
199
+ `tween property "${pathSegments.join('.')}" onUpdate`,
200
+ );
201
+
202
+ const controller = createController(() => {
203
+ runtime.aura.tween.cancel(tweenId);
204
+ });
205
+ tweenResultOk(
206
+ runtime.aura.tween.onComplete(tweenId, () => {
207
+ setNumericPathValue(target, pathSegments, to);
208
+ controller.finish(propertyReversePlan(plan, from, to));
209
+ }),
210
+ `tween property "${pathSegments.join('.')}" onComplete`,
211
+ );
212
+ return controller;
213
+ }
214
+ case 'delay': {
215
+ const duration = positiveNumber(plan.duration, 0, 'tween delay duration');
216
+ const created = tweenResultOk(
217
+ runtime.aura.tween.create({ from: 0, to: 1, duration, easing: 'linear', playing: true }),
218
+ 'tween delay create',
219
+ );
220
+ const tweenId = created.tweenId;
221
+ const controller = createController(() => {
222
+ runtime.aura.tween.cancel(tweenId);
223
+ });
224
+ tweenResultOk(
225
+ runtime.aura.tween.onComplete(tweenId, () => {
226
+ controller.finish(delayTween2D(duration));
227
+ }),
228
+ 'tween delay onComplete',
229
+ );
230
+ return controller;
231
+ }
232
+ case 'sequence': {
233
+ const steps = Array.isArray(plan.steps) ? plan.steps.slice() : [];
234
+ let activeChild = null;
235
+ const reverseSteps = [];
236
+ const controller = createController(() => {
237
+ activeChild?.cancel();
238
+ });
239
+ let index = 0;
240
+
241
+ const startNext = () => {
242
+ if (!controller.active) return;
243
+ if (index >= steps.length) {
244
+ controller.finish(reverseSteps.length > 0 ? sequenceTweens2D(reverseSteps.slice().reverse()) : null);
245
+ return;
246
+ }
247
+ activeChild = runTweenPlanInternal(runtime, target, steps[index]);
248
+ activeChild.onSettled((childState) => {
249
+ if (!controller.active) return;
250
+ if (childState.cancelled) {
251
+ controller.cancel();
252
+ return;
253
+ }
254
+ if (childState.reversePlan) {
255
+ reverseSteps.push(childState.reversePlan);
256
+ }
257
+ index += 1;
258
+ startNext();
259
+ });
260
+ };
261
+
262
+ startNext();
263
+ return controller;
264
+ }
265
+ case 'parallel': {
266
+ const steps = Array.isArray(plan.steps) ? plan.steps.slice() : [];
267
+ if (steps.length === 0) {
268
+ const controller = createController();
269
+ controller.finish(null);
270
+ return controller;
271
+ }
272
+
273
+ const children = [];
274
+ const reverseSteps = [];
275
+ let remaining = steps.length;
276
+ const controller = createController(() => {
277
+ for (const child of children) {
278
+ child.cancel();
279
+ }
280
+ });
281
+
282
+ for (const childPlan of steps) {
283
+ const child = runTweenPlanInternal(runtime, target, childPlan);
284
+ children.push(child);
285
+ child.onSettled((childState) => {
286
+ if (!controller.active) return;
287
+ if (childState.cancelled) {
288
+ controller.cancel();
289
+ return;
290
+ }
291
+ if (childState.reversePlan) {
292
+ reverseSteps.push(childState.reversePlan);
293
+ }
294
+ remaining -= 1;
295
+ if (remaining <= 0) {
296
+ controller.finish(reverseSteps.length > 0 ? parallelTweens2D(reverseSteps) : null);
297
+ }
298
+ });
299
+ }
300
+
301
+ return controller;
302
+ }
303
+ case 'repeat': {
304
+ const count = positiveInteger(plan.count, 1, 'repeat tween count');
305
+ let activeChild = null;
306
+ const reverseSteps = [];
307
+ let index = 0;
308
+ const controller = createController(() => {
309
+ activeChild?.cancel();
310
+ });
311
+
312
+ const runIteration = () => {
313
+ if (!controller.active) return;
314
+ if (index >= count) {
315
+ controller.finish(reverseSteps.length > 0 ? sequenceTweens2D(reverseSteps.slice().reverse()) : null);
316
+ return;
317
+ }
318
+ activeChild = runTweenPlanInternal(runtime, target, plan.step);
319
+ activeChild.onSettled((childState) => {
320
+ if (!controller.active) return;
321
+ if (childState.cancelled) {
322
+ controller.cancel();
323
+ return;
324
+ }
325
+ if (childState.reversePlan) {
326
+ reverseSteps.push(childState.reversePlan);
327
+ }
328
+ index += 1;
329
+ runIteration();
330
+ });
331
+ };
332
+
333
+ runIteration();
334
+ return controller;
335
+ }
336
+ case 'yoyo': {
337
+ const cycles = positiveInteger(plan.count, 1, 'yoyo tween count');
338
+ let activeChild = null;
339
+ let completedCycles = 0;
340
+ const controller = createController(() => {
341
+ activeChild?.cancel();
342
+ });
343
+
344
+ const runForward = () => {
345
+ if (!controller.active) return;
346
+ activeChild = runTweenPlanInternal(runtime, target, plan.step);
347
+ activeChild.onSettled((childState) => {
348
+ if (!controller.active) return;
349
+ if (childState.cancelled) {
350
+ controller.cancel();
351
+ return;
352
+ }
353
+ if (!childState.reversePlan) {
354
+ completedCycles += 1;
355
+ if (completedCycles >= cycles) {
356
+ controller.finish(null);
357
+ } else {
358
+ runForward();
359
+ }
360
+ return;
361
+ }
362
+ runReverse(childState.reversePlan);
363
+ });
364
+ };
365
+
366
+ const runReverse = (reversePlan) => {
367
+ if (!controller.active) return;
368
+ activeChild = runTweenPlanInternal(runtime, target, reversePlan);
369
+ activeChild.onSettled((childState) => {
370
+ if (!controller.active) return;
371
+ if (childState.cancelled) {
372
+ controller.cancel();
373
+ return;
374
+ }
375
+ completedCycles += 1;
376
+ if (completedCycles >= cycles) {
377
+ controller.finish(clonePlan(plan.step));
378
+ return;
379
+ }
380
+ runForward();
381
+ });
382
+ };
383
+
384
+ runForward();
385
+ return controller;
386
+ }
387
+ case 'settle': {
388
+ const pathSegments = normalizePath(plan.path);
389
+ const current = getNumericPathValue(target, pathSegments);
390
+ const targetValue = finite(plan.to, NaN);
391
+ if (!Number.isFinite(targetValue)) {
392
+ throw new Error(`settle tween "${pathSegments.join('.')}" requires a finite "to" value.`);
393
+ }
394
+ const duration = positiveNumber(plan.duration, 0, `settle tween "${pathSegments.join('.')}" duration`);
395
+ const overshoot = Math.abs(finite(plan.overshoot, 0.1));
396
+ const direction = current <= targetValue ? 1 : -1;
397
+ const overshootValue = targetValue + (overshoot * direction);
398
+ const sequence = sequenceTweens2D([
399
+ propertyTween2D(pathSegments, {
400
+ from: current,
401
+ to: overshootValue,
402
+ duration: duration * 0.45,
403
+ easing: normalizeEasing(plan.easingOut, 'easeOutCubic'),
404
+ }),
405
+ propertyTween2D(pathSegments, {
406
+ from: overshootValue,
407
+ to: targetValue,
408
+ duration: duration * 0.55,
409
+ easing: normalizeEasing(plan.easingIn, 'easeInOutQuad'),
410
+ }),
411
+ ]);
412
+ return runTweenPlanInternal(runtime, target, sequence);
413
+ }
414
+ default:
415
+ throw new Error(`unsupported tween plan type "${plan.type}".`);
416
+ }
417
+ }
418
+
419
+ export function propertyTween2D(path, options = {}) {
420
+ const plan = {
421
+ type: 'property',
422
+ path: normalizePath(path),
423
+ duration: positiveNumber(options.duration, 0, 'property tween duration'),
424
+ easing: normalizeEasing(options.easing, 'linear'),
425
+ };
426
+ if (Object.prototype.hasOwnProperty.call(options, 'from')) {
427
+ plan.from = finite(options.from, NaN);
428
+ }
429
+ if (Object.prototype.hasOwnProperty.call(options, 'to')) {
430
+ plan.to = finite(options.to, NaN);
431
+ }
432
+ if (Object.prototype.hasOwnProperty.call(options, 'by')) {
433
+ plan.by = finite(options.by, NaN);
434
+ }
435
+ return plan;
436
+ }
437
+
438
+ export function delayTween2D(duration) {
439
+ return {
440
+ type: 'delay',
441
+ duration: positiveNumber(duration, 0, 'delay tween duration'),
442
+ };
443
+ }
444
+
445
+ export function sequenceTweens2D(steps = []) {
446
+ return {
447
+ type: 'sequence',
448
+ steps: Array.isArray(steps) ? steps.map((step) => clonePlan(step)) : [],
449
+ };
450
+ }
451
+
452
+ export function parallelTweens2D(steps = []) {
453
+ return {
454
+ type: 'parallel',
455
+ steps: Array.isArray(steps) ? steps.map((step) => clonePlan(step)) : [],
456
+ };
457
+ }
458
+
459
+ export function repeatTween2D(step, count = 1) {
460
+ return {
461
+ type: 'repeat',
462
+ step: clonePlan(step),
463
+ count: positiveInteger(count, 1, 'repeat tween count'),
464
+ };
465
+ }
466
+
467
+ export function yoyoTween2D(step, count = 1) {
468
+ return {
469
+ type: 'yoyo',
470
+ step: clonePlan(step),
471
+ count: positiveInteger(count, 1, 'yoyo tween count'),
472
+ };
473
+ }
474
+
475
+ export function moveTo2D(options = {}) {
476
+ const duration = positiveNumber(options.duration, 0, 'moveTo2D duration');
477
+ const easing = normalizeEasing(options.easing, 'easeOutQuad');
478
+ const steps = [];
479
+ if (Number.isFinite(Number(options.x))) {
480
+ steps.push(propertyTween2D('x', { to: Number(options.x), duration, easing }));
481
+ }
482
+ if (Number.isFinite(Number(options.y))) {
483
+ steps.push(propertyTween2D('y', { to: Number(options.y), duration, easing }));
484
+ }
485
+ if (steps.length === 0) {
486
+ throw new Error('moveTo2D requires at least one of x or y.');
487
+ }
488
+ return steps.length === 1 ? steps[0] : parallelTweens2D(steps);
489
+ }
490
+
491
+ export function driftTo2D(options = {}) {
492
+ return moveTo2D({
493
+ ...options,
494
+ easing: normalizeEasing(options.easing, 'easeInOutQuad'),
495
+ });
496
+ }
497
+
498
+ export function fadeTo2D(options = {}) {
499
+ return propertyTween2D(options.path || 'alpha', {
500
+ to: finite(options.alpha, NaN),
501
+ duration: positiveNumber(options.duration, 0, 'fadeTo2D duration'),
502
+ easing: normalizeEasing(options.easing, 'linear'),
503
+ });
504
+ }
505
+
506
+ export function scaleTo2D(options = {}) {
507
+ return propertyTween2D(options.path || 'scale', {
508
+ to: finite(options.scale, NaN),
509
+ duration: positiveNumber(options.duration, 0, 'scaleTo2D duration'),
510
+ easing: normalizeEasing(options.easing, 'easeOutQuad'),
511
+ });
512
+ }
513
+
514
+ export function rotateTo2D(options = {}) {
515
+ return propertyTween2D(options.path || 'rotation', {
516
+ to: finite(options.rotation, NaN),
517
+ duration: positiveNumber(options.duration, 0, 'rotateTo2D duration'),
518
+ easing: normalizeEasing(options.easing, 'easeOutQuad'),
519
+ });
520
+ }
521
+
522
+ export function pulse2D(options = {}) {
523
+ const duration = positiveNumber(options.duration, 0.18, 'pulse2D duration');
524
+ return yoyoTween2D(
525
+ propertyTween2D(options.path || 'scale', {
526
+ by: finite(options.amount, 0.08),
527
+ duration: duration * 0.5,
528
+ easing: normalizeEasing(options.easing, 'easeOutQuad'),
529
+ }),
530
+ positiveInteger(options.repeats, 1, 'pulse2D repeats'),
531
+ );
532
+ }
533
+
534
+ export function punch2D(options = {}) {
535
+ const duration = positiveNumber(options.duration, 0.14, 'punch2D duration');
536
+ return yoyoTween2D(
537
+ propertyTween2D(options.path || 'scale', {
538
+ by: finite(options.amount, 0.12),
539
+ duration: duration * 0.5,
540
+ easing: normalizeEasing(options.easing, 'easeOutCubic'),
541
+ }),
542
+ positiveInteger(options.repeats, 1, 'punch2D repeats'),
543
+ );
544
+ }
545
+
546
+ export function settleTo2D(options = {}) {
547
+ return {
548
+ type: 'settle',
549
+ path: normalizePath(options.path || 'y'),
550
+ to: finite(options.to, NaN),
551
+ overshoot: Math.abs(finite(options.overshoot, 0.12)),
552
+ duration: positiveNumber(options.duration, 0.24, 'settleTo2D duration'),
553
+ easingOut: normalizeEasing(options.easingOut, 'easeOutCubic'),
554
+ easingIn: normalizeEasing(options.easingIn, 'easeInOutQuad'),
555
+ };
556
+ }
557
+
558
+ export function createTweenComposer2D(aura) {
559
+ requireTween(aura);
560
+ return {
561
+ aura,
562
+ nextMotionId: 1,
563
+ motions: new Map(),
564
+ };
565
+ }
566
+
567
+ export function playTweenPlan2D(runtime, target, plan, options = {}) {
568
+ if (!runtime?.aura) {
569
+ throw new Error('tween composer runtime is required.');
570
+ }
571
+ const motionId = runtime.nextMotionId++;
572
+ const controller = runTweenPlanInternal(runtime, target, plan);
573
+ const state = buildPlanState(motionId);
574
+ const handle = {
575
+ motionId,
576
+ target,
577
+ plan: clonePlan(plan),
578
+ state,
579
+ cancel() {
580
+ if (!state.active) return false;
581
+ controller.cancel();
582
+ return true;
583
+ },
584
+ };
585
+
586
+ runtime.motions.set(motionId, handle);
587
+ controller.onSettled((controllerState) => {
588
+ state.active = false;
589
+ state.completed = controllerState.completed;
590
+ state.cancelled = controllerState.cancelled;
591
+ runtime.motions.delete(motionId);
592
+ if (controllerState.completed && typeof options.onComplete === 'function') {
593
+ options.onComplete(handle);
594
+ }
595
+ });
596
+ return handle;
597
+ }
598
+
599
+ export function cancelTweenPlan2D(runtime, handle) {
600
+ if (!runtime?.motions || !handle?.motionId) return false;
601
+ const active = runtime.motions.get(handle.motionId);
602
+ return active ? active.cancel() : false;
603
+ }
604
+
605
+ export function stepTweenComposer2D(runtime, dt) {
606
+ return runtime.aura.tween.update(dt);
607
+ }
608
+
609
+ export function getTweenHandleState2D(handle) {
610
+ return handle?.state ? { ...handle.state } : null;
611
+ }
612
+
613
+ export function isTweenHandleActive2D(handle) {
614
+ return handle?.state?.active === true;
615
+ }
@@ -0,0 +1,101 @@
1
+ import { createIntervalSpawner, tickIntervalSpawner } from './core.js';
2
+
3
+ export function createWaveDirector(options = {}) {
4
+ const waves = Array.isArray(options.waves) ? options.waves : [];
5
+ const loop = options.loop === true;
6
+ const initialDelay = Math.max(0, Number(options.initialDelay) || 0);
7
+ const betweenWaveDelay = Math.max(0, Number(options.betweenWaveDelay) || 0);
8
+
9
+ return {
10
+ waves,
11
+ loop,
12
+ initialDelay,
13
+ betweenWaveDelay,
14
+ delayRemaining: initialDelay,
15
+ waveIndex: 0,
16
+ waveElapsed: 0,
17
+ waveSpawned: 0,
18
+ activeSpawner: null,
19
+ completed: waves.length === 0,
20
+ };
21
+ }
22
+
23
+ export function activeWave(director) {
24
+ if (!director || director.completed) return null;
25
+ return director.waves[director.waveIndex] || null;
26
+ }
27
+
28
+ export function stepWaveDirector(director, dt, onSpawn = null) {
29
+ if (!director || director.completed) {
30
+ return { spawned: 0, waveIndex: -1, completed: true };
31
+ }
32
+
33
+ const frameDt = Math.max(0, Number(dt) || 0);
34
+ if (director.delayRemaining > 0) {
35
+ director.delayRemaining = Math.max(0, director.delayRemaining - frameDt);
36
+ return { spawned: 0, waveIndex: director.waveIndex, completed: director.completed };
37
+ }
38
+
39
+ const wave = activeWave(director);
40
+ if (!wave) {
41
+ director.completed = true;
42
+ return { spawned: 0, waveIndex: -1, completed: true };
43
+ }
44
+
45
+ if (!director.activeSpawner) {
46
+ const interval = Math.max(0.04, Number(wave.spawnEvery) || 0.8);
47
+ director.activeSpawner = createIntervalSpawner(interval, { startReady: true });
48
+ director.waveElapsed = 0;
49
+ director.waveSpawned = 0;
50
+ }
51
+
52
+ director.waveElapsed += frameDt;
53
+
54
+ const maxSpawns = Number.isFinite(Number(wave.maxSpawns))
55
+ ? Math.max(0, Math.floor(Number(wave.maxSpawns)))
56
+ : Number.POSITIVE_INFINITY;
57
+ const maxDuration = Number.isFinite(Number(wave.durationSeconds))
58
+ ? Math.max(0, Number(wave.durationSeconds))
59
+ : Number.POSITIVE_INFINITY;
60
+
61
+ let spawned = 0;
62
+ tickIntervalSpawner(director.activeSpawner, frameDt, () => {
63
+ if (director.waveSpawned >= maxSpawns) return;
64
+ director.waveSpawned += 1;
65
+ spawned += 1;
66
+ if (typeof onSpawn === 'function') {
67
+ onSpawn({
68
+ waveIndex: director.waveIndex,
69
+ wave,
70
+ spawnedInWave: director.waveSpawned,
71
+ });
72
+ }
73
+ });
74
+
75
+ const waveDone = (
76
+ director.waveSpawned >= maxSpawns
77
+ || director.waveElapsed >= maxDuration
78
+ );
79
+
80
+ if (waveDone) {
81
+ director.waveIndex += 1;
82
+ if (director.waveIndex >= director.waves.length) {
83
+ if (director.loop) {
84
+ director.waveIndex = 0;
85
+ } else {
86
+ director.completed = true;
87
+ }
88
+ }
89
+
90
+ director.delayRemaining = director.completed ? 0 : director.betweenWaveDelay;
91
+ director.waveElapsed = 0;
92
+ director.waveSpawned = 0;
93
+ director.activeSpawner = null;
94
+ }
95
+
96
+ return {
97
+ spawned,
98
+ waveIndex: director.completed ? -1 : director.waveIndex,
99
+ completed: director.completed,
100
+ };
101
+ }