@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,221 @@
1
+ import {
2
+ createInteractionPromptState,
3
+ createRectTrigger2D,
4
+ createTriggerTracker,
5
+ stepRectTriggers2D,
6
+ updateInteractionPromptState,
7
+ } from './triggers.js';
8
+ import { addInventoryItem2D, hasInventoryItem2D, removeInventoryItem2D } from './inventory-2d.js';
9
+
10
+ function finite(value, fallback = 0) {
11
+ const numeric = Number(value);
12
+ return Number.isFinite(numeric) ? numeric : fallback;
13
+ }
14
+
15
+ function normalizeText(value, fallback = null) {
16
+ if (typeof value === 'string' && value.trim().length > 0) {
17
+ return value.trim();
18
+ }
19
+ return fallback;
20
+ }
21
+
22
+ function normalizeKind(value) {
23
+ switch (value) {
24
+ case 'pickup':
25
+ case 'lock':
26
+ case 'checkpoint':
27
+ case 'prop':
28
+ return value;
29
+ default:
30
+ return 'prop';
31
+ }
32
+ }
33
+
34
+ function objectProperty(object, name) {
35
+ if (!object || typeof object !== 'object') return null;
36
+ const properties = object.properties && typeof object.properties === 'object' ? object.properties : null;
37
+ return properties && Object.prototype.hasOwnProperty.call(properties, name) ? properties[name] : null;
38
+ }
39
+
40
+ function normalizeInteractable(definition, index) {
41
+ const fallbackId = `interactable-${index + 1}`;
42
+ const id = normalizeText(definition?.id, fallbackId);
43
+ const width = Math.max(4, finite(definition?.w ?? definition?.width, definition?.point === true ? 16 : 0));
44
+ const height = Math.max(4, finite(definition?.h ?? definition?.height, definition?.point === true ? 16 : 0));
45
+ const x = definition?.point === true
46
+ ? finite(definition?.x) - (width * 0.5)
47
+ : finite(definition?.x);
48
+ const y = definition?.point === true
49
+ ? finite(definition?.y) - (height * 0.5)
50
+ : finite(definition?.y);
51
+ return {
52
+ id,
53
+ label: normalizeText(definition?.label ?? definition?.name, id),
54
+ kind: normalizeKind(normalizeText(definition?.kind ?? definition?.type)),
55
+ x,
56
+ y,
57
+ w: width,
58
+ h: height,
59
+ priority: Number.isFinite(Number(definition?.priority)) ? Number(definition.priority) : 0,
60
+ promptText: normalizeText(definition?.promptText ?? definition?.prompt),
61
+ lockedPromptText: normalizeText(definition?.lockedPromptText),
62
+ openedPromptText: normalizeText(definition?.openedPromptText),
63
+ interactionKey: normalizeText(definition?.interactionKey, 'E'),
64
+ requiredItemId: normalizeText(definition?.requiredItemId),
65
+ requiredQuantity: Math.max(1, finite(definition?.requiredQuantity, 1)),
66
+ consumeRequiredItem: definition?.consumeRequiredItem === true,
67
+ grantItem: definition?.grantItem && typeof definition.grantItem === 'object' ? { ...definition.grantItem } : null,
68
+ checkpointId: normalizeText(definition?.checkpointId, id),
69
+ once: definition?.once !== false,
70
+ collected: definition?.collected === true,
71
+ opened: definition?.opened === true,
72
+ activated: definition?.activated === true,
73
+ disabled: definition?.disabled === true,
74
+ };
75
+ }
76
+
77
+ function promptForInteractable(interactable, inventory) {
78
+ if (!interactable || interactable.disabled === true) return null;
79
+ if (interactable.kind === 'pickup' && interactable.collected === true) return null;
80
+ if (interactable.kind === 'lock') {
81
+ if (interactable.opened === true) return interactable.openedPromptText || null;
82
+ if (interactable.requiredItemId && !hasInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity)) {
83
+ return interactable.lockedPromptText || `${interactable.label} is locked`;
84
+ }
85
+ }
86
+ if (interactable.kind === 'checkpoint' && interactable.activated === true && interactable.once === true) {
87
+ return null;
88
+ }
89
+ if (interactable.kind === 'prop' && interactable.activated === true && interactable.once === true) {
90
+ return null;
91
+ }
92
+ return interactable.promptText || `Interact with ${interactable.label}`;
93
+ }
94
+
95
+ function buildTrigger(interactable, inventory) {
96
+ const promptText = promptForInteractable(interactable, inventory);
97
+ if (!promptText) return null;
98
+ return createRectTrigger2D(interactable.id, interactable.x, interactable.y, interactable.w, interactable.h, {
99
+ kind: interactable.kind,
100
+ role: interactable.kind,
101
+ priority: interactable.priority,
102
+ promptText,
103
+ interactionKey: interactable.interactionKey,
104
+ });
105
+ }
106
+
107
+ function successfulInteraction(interactable) {
108
+ if (!interactable) return false;
109
+ return interactable.kind === 'pickup'
110
+ ? interactable.collected === true
111
+ : interactable.kind === 'lock'
112
+ ? interactable.opened === true
113
+ : interactable.activated === true;
114
+ }
115
+
116
+ export function createAdventureInteractable2D(definition = {}, index = 0) {
117
+ return normalizeInteractable(definition, index);
118
+ }
119
+
120
+ export function createAdventureInteractablesFromTilemapObjects2D(objects = [], defaults = {}) {
121
+ return (Array.isArray(objects) ? objects : []).map((object, index) => createAdventureInteractable2D({
122
+ id: normalizeText(object?.id != null ? String(object.id) : null, `object-${index + 1}`),
123
+ label: object?.name,
124
+ kind: normalizeText(object?.type, normalizeText(objectProperty(object, 'kind'), defaults.kind)),
125
+ x: finite(object?.worldX ?? object?.x),
126
+ y: finite(object?.worldY ?? object?.y),
127
+ width: finite(object?.width, defaults.width),
128
+ height: finite(object?.height, defaults.height),
129
+ point: object?.point === true,
130
+ promptText: normalizeText(objectProperty(object, 'promptText'), defaults.promptText),
131
+ lockedPromptText: normalizeText(objectProperty(object, 'lockedPromptText'), defaults.lockedPromptText),
132
+ requiredItemId: normalizeText(objectProperty(object, 'requiredItemId'), defaults.requiredItemId),
133
+ requiredQuantity: finite(objectProperty(object, 'requiredQuantity'), defaults.requiredQuantity),
134
+ checkpointId: normalizeText(objectProperty(object, 'checkpointId'), defaults.checkpointId),
135
+ }, index));
136
+ }
137
+
138
+ export function createAdventureInteractionState2D(options = {}) {
139
+ return {
140
+ tracker: createTriggerTracker({ subjectId: normalizeText(options.subjectId, 'player'), dimension: '2d' }),
141
+ prompt: createInteractionPromptState({ prompt: normalizeText(options.prompt, 'Interact') }),
142
+ activeCheckpointId: normalizeText(options.activeCheckpointId),
143
+ lastResult: null,
144
+ };
145
+ }
146
+
147
+ export function stepAdventureInteractables2D(
148
+ state,
149
+ subjectBounds,
150
+ interactables = [],
151
+ options = {},
152
+ ) {
153
+ const inventory = options.inventory || null;
154
+ const normalized = (Array.isArray(interactables) ? interactables : []).map((entry, index) => createAdventureInteractable2D(entry, index));
155
+ const triggers = normalized
156
+ .map((entry) => buildTrigger(entry, inventory))
157
+ .filter(Boolean);
158
+ const triggerStep = stepRectTriggers2D(state?.tracker, subjectBounds, triggers);
159
+ updateInteractionPromptState(state.prompt, triggerStep, {
160
+ interactPressed: options.interactPressed === true,
161
+ });
162
+
163
+ const result = {
164
+ reasonCode: state.prompt.reasonCode,
165
+ acceptedId: state.prompt.acceptedTriggerId || null,
166
+ collectedIds: [],
167
+ openedIds: [],
168
+ activatedIds: [],
169
+ checkpointId: null,
170
+ promptText: state.prompt.prompt,
171
+ };
172
+
173
+ if (!result.acceptedId) {
174
+ state.lastResult = result;
175
+ return result;
176
+ }
177
+
178
+ const interactable = normalized.find((entry) => entry.id === result.acceptedId) || null;
179
+ if (!interactable) {
180
+ state.lastResult = result;
181
+ return result;
182
+ }
183
+
184
+ if (interactable.kind === 'pickup') {
185
+ if (!interactable.collected && interactable.grantItem && inventory) {
186
+ addInventoryItem2D(inventory, interactable.grantItem);
187
+ }
188
+ interactable.collected = true;
189
+ result.collectedIds.push(interactable.id);
190
+ } else if (interactable.kind === 'lock') {
191
+ const unlocked = !interactable.requiredItemId
192
+ || hasInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity);
193
+ if (!unlocked) {
194
+ result.reasonCode = 'interaction_locked';
195
+ state.lastResult = result;
196
+ return result;
197
+ }
198
+ if (interactable.consumeRequiredItem === true && interactable.requiredItemId) {
199
+ removeInventoryItem2D(inventory, interactable.requiredItemId, interactable.requiredQuantity);
200
+ }
201
+ interactable.opened = true;
202
+ result.openedIds.push(interactable.id);
203
+ } else if (interactable.kind === 'checkpoint') {
204
+ interactable.activated = true;
205
+ state.activeCheckpointId = interactable.checkpointId;
206
+ result.checkpointId = interactable.checkpointId;
207
+ result.activatedIds.push(interactable.id);
208
+ } else {
209
+ interactable.activated = true;
210
+ result.activatedIds.push(interactable.id);
211
+ }
212
+
213
+ if (interactable.once === true && successfulInteraction(interactable) && interactable.kind !== 'checkpoint') {
214
+ interactable.disabled = true;
215
+ }
216
+ if (result.reasonCode === 'interaction_accepted') {
217
+ result.reasonCode = `interaction_${interactable.kind}_accepted`;
218
+ }
219
+ state.lastResult = result;
220
+ return result;
221
+ }
@@ -0,0 +1,337 @@
1
+ let nextSpriteLibraryId = 1;
2
+ let nextSpriteAnimatorId = 1;
3
+
4
+ function finite(value, fallback = 0) {
5
+ const numeric = Number(value);
6
+ return Number.isFinite(numeric) ? numeric : fallback;
7
+ }
8
+
9
+ function positiveNumber(value, fallback, label) {
10
+ const numeric = finite(value, fallback);
11
+ if (!(numeric > 0)) {
12
+ throw new Error(`${label} must be a positive number.`);
13
+ }
14
+ return numeric;
15
+ }
16
+
17
+ function normalizeIdentifier(value, fallbackPrefix) {
18
+ const raw = typeof value === 'string' && value.trim().length > 0
19
+ ? value.trim()
20
+ : `${fallbackPrefix}-${nextSpriteLibraryId}`;
21
+ const normalized = raw
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, '-')
24
+ .replace(/^-+|-+$/g, '');
25
+ return normalized || `${fallbackPrefix}-${nextSpriteLibraryId}`;
26
+ }
27
+
28
+ function normalizeStateName(value) {
29
+ const name = typeof value === 'string' ? value.trim() : '';
30
+ if (!name) {
31
+ throw new Error('sprite animation state names must be non-empty strings.');
32
+ }
33
+ return name;
34
+ }
35
+
36
+ function normalizeFrameEntry(value, stateName) {
37
+ if (!Number.isInteger(value) || value < 0) {
38
+ throw new Error(`sprite animation state "${stateName}" must use non-negative integer frame indices.`);
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function buildFrameSequence(spec, stateName) {
44
+ if (Array.isArray(spec)) {
45
+ if (spec.length === 0) {
46
+ throw new Error(`sprite animation state "${stateName}" must include at least one frame.`);
47
+ }
48
+ return spec.map((entry) => normalizeFrameEntry(entry, stateName));
49
+ }
50
+
51
+ if (Number.isInteger(spec) && spec >= 0) {
52
+ return [spec];
53
+ }
54
+
55
+ const from = Number.isInteger(spec?.from) ? spec.from : null;
56
+ const count = Number.isInteger(spec?.count) ? spec.count : null;
57
+ if (from != null && count != null) {
58
+ if (from < 0 || count <= 0) {
59
+ throw new Error(`sprite animation state "${stateName}" must use a non-negative from index and positive count.`);
60
+ }
61
+ return Array.from({ length: count }, (_, index) => from + index);
62
+ }
63
+
64
+ if (Array.isArray(spec?.frames)) {
65
+ if (spec.frames.length === 0) {
66
+ throw new Error(`sprite animation state "${stateName}" must include at least one frame.`);
67
+ }
68
+ return spec.frames.map((entry) => normalizeFrameEntry(entry, stateName));
69
+ }
70
+
71
+ if (Number.isInteger(spec?.frame) && spec.frame >= 0) {
72
+ return [spec.frame];
73
+ }
74
+
75
+ throw new Error(`sprite animation state "${stateName}" must declare frames or from/count.`);
76
+ }
77
+
78
+ function normalizeStateSpec(stateName, spec, defaultFrameDuration) {
79
+ const frames = buildFrameSequence(spec, stateName);
80
+ return {
81
+ frames,
82
+ frameDuration: positiveNumber(spec?.frameDuration, defaultFrameDuration, `sprite animation state "${stateName}" frameDuration`),
83
+ loop: spec?.loop !== false,
84
+ };
85
+ }
86
+
87
+ function clipNameForState(library, stateName) {
88
+ return `${library.libraryId}__${stateName}`;
89
+ }
90
+
91
+ function libraryStateEntries(library) {
92
+ return Object.entries(library.states || {});
93
+ }
94
+
95
+ function resolveMachineState(runtime, animator) {
96
+ return runtime.aura.anim2d.getState(animator.machineId);
97
+ }
98
+
99
+ function resolveLibraryFromRuntime(runtime, animator) {
100
+ return runtime.libraries.get(animator.libraryId) || null;
101
+ }
102
+
103
+ function resolveFrameRect(library, frameIndex) {
104
+ const frame = Number.isInteger(frameIndex) && frameIndex >= 0 ? frameIndex : 0;
105
+ const column = frame % library.columns;
106
+ const row = Math.floor(frame / library.columns);
107
+ return {
108
+ frame,
109
+ frameX: column * library.frameWidth,
110
+ frameY: row * library.frameHeight,
111
+ frameW: library.frameWidth,
112
+ frameH: library.frameHeight,
113
+ };
114
+ }
115
+
116
+ function requireAnim2d(aura) {
117
+ if (
118
+ !aura?.anim2d
119
+ || typeof aura.anim2d.registerClip !== 'function'
120
+ || typeof aura.anim2d.createMachine !== 'function'
121
+ || typeof aura.anim2d.defineState !== 'function'
122
+ || typeof aura.anim2d.play !== 'function'
123
+ || typeof aura.anim2d.update !== 'function'
124
+ || typeof aura.anim2d.getState !== 'function'
125
+ ) {
126
+ throw new Error('sprite animation helpers require aura.anim2d register/create/define/play/update/getState support.');
127
+ }
128
+ }
129
+
130
+ export function createSpriteAnimationLibrary2D(options = {}) {
131
+ const image = typeof options.image === 'string' && options.image.trim().length > 0
132
+ ? options.image.trim()
133
+ : null;
134
+ if (!image) {
135
+ throw new Error('sprite animation library image is required.');
136
+ }
137
+
138
+ const frameWidth = positiveNumber(options.frameWidth, 0, 'sprite animation frameWidth');
139
+ const frameHeight = positiveNumber(options.frameHeight, 0, 'sprite animation frameHeight');
140
+ const columns = Math.max(1, Math.floor(positiveNumber(options.columns, 1, 'sprite animation columns')));
141
+ const defaultFrameDuration = positiveNumber(options.defaultFrameDuration, 1 / 12, 'sprite animation defaultFrameDuration');
142
+ const anchorX = finite(options.anchorX, 0.5);
143
+ const anchorY = finite(options.anchorY, 0.5);
144
+ const rawStates = options.states && typeof options.states === 'object' ? options.states : null;
145
+
146
+ if (!rawStates || Object.keys(rawStates).length === 0) {
147
+ throw new Error('sprite animation libraries must declare at least one state.');
148
+ }
149
+
150
+ const states = {};
151
+ for (const [rawStateName, spec] of Object.entries(rawStates)) {
152
+ const stateName = normalizeStateName(rawStateName);
153
+ states[stateName] = normalizeStateSpec(stateName, spec, defaultFrameDuration);
154
+ }
155
+
156
+ const orderedStateNames = Object.keys(states);
157
+ const defaultState = (
158
+ typeof options.defaultState === 'string'
159
+ && Object.prototype.hasOwnProperty.call(states, options.defaultState.trim())
160
+ ? options.defaultState.trim()
161
+ : orderedStateNames[0]
162
+ );
163
+ const name = typeof options.name === 'string' && options.name.trim().length > 0
164
+ ? options.name.trim()
165
+ : image;
166
+ const libraryId = `sprite-lib-${nextSpriteLibraryId}-${normalizeIdentifier(name, 'sprite-lib')}`;
167
+ nextSpriteLibraryId += 1;
168
+
169
+ return {
170
+ libraryId,
171
+ name,
172
+ image,
173
+ frameWidth,
174
+ frameHeight,
175
+ columns,
176
+ anchorX,
177
+ anchorY,
178
+ defaultFrameDuration,
179
+ defaultState,
180
+ states,
181
+ };
182
+ }
183
+
184
+ export function hasSpriteAnimationState2D(library, stateName) {
185
+ return !!library && typeof stateName === 'string' && Object.prototype.hasOwnProperty.call(library.states || {}, stateName);
186
+ }
187
+
188
+ export function createSpriteAnimationRuntime2D(aura) {
189
+ requireAnim2d(aura);
190
+ return {
191
+ aura,
192
+ libraries: new Map(),
193
+ registeredLibraries: new Set(),
194
+ animators: new Map(),
195
+ };
196
+ }
197
+
198
+ export function registerSpriteAnimationLibrary2D(runtime, library) {
199
+ if (!runtime?.aura) {
200
+ throw new Error('sprite animation runtime is required.');
201
+ }
202
+ requireAnim2d(runtime.aura);
203
+ if (!library?.libraryId) {
204
+ throw new Error('sprite animation library is required.');
205
+ }
206
+ runtime.libraries.set(library.libraryId, library);
207
+ if (runtime.registeredLibraries.has(library.libraryId)) {
208
+ return true;
209
+ }
210
+
211
+ for (const [stateName, state] of libraryStateEntries(library)) {
212
+ const registered = runtime.aura.anim2d.registerClip(
213
+ clipNameForState(library, stateName),
214
+ state.frames,
215
+ {
216
+ frameDuration: state.frameDuration,
217
+ loop: state.loop,
218
+ },
219
+ );
220
+ if (registered !== true) {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ runtime.registeredLibraries.add(library.libraryId);
226
+ return true;
227
+ }
228
+
229
+ export function createSpriteAnimator2D(runtime, library, options = {}) {
230
+ if (registerSpriteAnimationLibrary2D(runtime, library) !== true) {
231
+ throw new Error(`failed to register sprite animation library "${library?.libraryId || 'unknown'}".`);
232
+ }
233
+
234
+ const machineId = runtime.aura.anim2d.createMachine(null);
235
+ for (const [stateName] of libraryStateEntries(library)) {
236
+ const defined = runtime.aura.anim2d.defineState(machineId, stateName, clipNameForState(library, stateName));
237
+ if (defined !== true) {
238
+ throw new Error(`failed to define sprite animation state "${stateName}".`);
239
+ }
240
+ }
241
+
242
+ const initialState = hasSpriteAnimationState2D(library, options.initialState) ? options.initialState : library.defaultState;
243
+ const animator = {
244
+ animatorId: nextSpriteAnimatorId,
245
+ libraryId: library.libraryId,
246
+ machineId,
247
+ defaultState: initialState,
248
+ };
249
+ nextSpriteAnimatorId += 1;
250
+ runtime.animators.set(animator.animatorId, animator);
251
+
252
+ if (initialState) {
253
+ runtime.aura.anim2d.play(machineId, initialState);
254
+ }
255
+
256
+ return animator;
257
+ }
258
+
259
+ export function playSpriteAnimator2D(runtime, animator, stateName) {
260
+ if (!hasSpriteAnimationState2D(resolveLibraryFromRuntime(runtime, animator), stateName)) {
261
+ return false;
262
+ }
263
+ return runtime.aura.anim2d.play(animator.machineId, stateName) === true;
264
+ }
265
+
266
+ export function syncSpriteAnimatorState2D(runtime, animator, stateName) {
267
+ const snapshot = resolveMachineState(runtime, animator);
268
+ if (snapshot?.state === stateName) {
269
+ return true;
270
+ }
271
+ return playSpriteAnimator2D(runtime, animator, stateName);
272
+ }
273
+
274
+ export function stepSpriteAnimationRuntime2D(runtime, dt) {
275
+ runtime.aura.anim2d.update(dt);
276
+ return true;
277
+ }
278
+
279
+ export function getSpriteAnimatorState2D(runtime, animator) {
280
+ return resolveMachineState(runtime, animator);
281
+ }
282
+
283
+ export function resolveSpriteAnimatorFrame2D(runtime, animator) {
284
+ const library = resolveLibraryFromRuntime(runtime, animator);
285
+ const snapshot = resolveMachineState(runtime, animator);
286
+ if (!library || !snapshot) {
287
+ return null;
288
+ }
289
+
290
+ return {
291
+ ...snapshot,
292
+ image: library.image,
293
+ anchorX: library.anchorX,
294
+ anchorY: library.anchorY,
295
+ ...resolveFrameRect(library, snapshot.frame),
296
+ };
297
+ }
298
+
299
+ export function drawSpriteAnimator2D(runtime, animator, x, y, options = {}) {
300
+ const frame = resolveSpriteAnimatorFrame2D(runtime, animator);
301
+ if (!frame || typeof runtime?.aura?.draw2d?.sprite !== 'function') {
302
+ return false;
303
+ }
304
+
305
+ const width = positiveNumber(options.width, frame.frameW, 'sprite draw width');
306
+ const height = positiveNumber(options.height, frame.frameH, 'sprite draw height');
307
+ const anchorX = finite(options.anchorX, frame.anchorX);
308
+ const anchorY = finite(options.anchorY, frame.anchorY);
309
+ const spriteOptions = { ...options };
310
+ delete spriteOptions.anchorX;
311
+ delete spriteOptions.anchorY;
312
+ delete spriteOptions.width;
313
+ delete spriteOptions.height;
314
+
315
+ runtime.aura.draw2d.sprite(
316
+ frame.image,
317
+ x - (width * anchorX),
318
+ y - (height * anchorY),
319
+ {
320
+ ...spriteOptions,
321
+ width,
322
+ height,
323
+ frameX: frame.frameX,
324
+ frameY: frame.frameY,
325
+ frameW: frame.frameW,
326
+ frameH: frame.frameH,
327
+ },
328
+ );
329
+ return true;
330
+ }
331
+
332
+ export function resolveSpriteFrameRect2D(library, frameIndex) {
333
+ if (!library?.libraryId) {
334
+ throw new Error('sprite animation library is required.');
335
+ }
336
+ return resolveFrameRect(library, frameIndex);
337
+ }