@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,351 @@
1
+ function normalizeId(value, fallback = null) {
2
+ if (typeof value === 'string' && value.trim().length > 0) {
3
+ return value.trim();
4
+ }
5
+ if (typeof value === 'number' && Number.isFinite(value)) {
6
+ return String(value);
7
+ }
8
+ return fallback;
9
+ }
10
+
11
+ function normalizeText(value, fallback = null) {
12
+ if (typeof value === 'string' && value.trim().length > 0) {
13
+ return value.trim();
14
+ }
15
+ return fallback;
16
+ }
17
+
18
+ function normalizeLineEntry(entry, index, fallbackSpeaker = null) {
19
+ const raw = typeof entry === 'string'
20
+ ? { text: entry }
21
+ : (entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null);
22
+ const text = normalizeText(raw?.text ?? raw?.line ?? raw?.label, null);
23
+ if (!text) return null;
24
+ return {
25
+ id: normalizeId(raw?.id, `line-${index + 1}`),
26
+ text,
27
+ speaker: normalizeText(raw?.speaker, fallbackSpeaker),
28
+ cue: normalizeText(raw?.cue),
29
+ };
30
+ }
31
+
32
+ function normalizeChoiceEntry(entry, index) {
33
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
34
+ const label = normalizeText(raw?.label ?? raw?.text ?? raw?.title, null);
35
+ if (!label) return null;
36
+ return {
37
+ id: normalizeId(raw?.id, `choice-${index + 1}`),
38
+ label,
39
+ detail: normalizeText(raw?.detail ?? raw?.description),
40
+ nextNodeId: normalizeId(raw?.nextNodeId ?? raw?.next ?? raw?.target),
41
+ outcomeId: normalizeId(raw?.outcomeId ?? raw?.outcome ?? raw?.result),
42
+ disabled: raw?.disabled === true,
43
+ };
44
+ }
45
+
46
+ function normalizeNodeEntry(entry, index, fallbackSpeaker = null) {
47
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
48
+ const id = normalizeId(raw?.id, `node-${index + 1}`);
49
+ const speaker = normalizeText(raw?.speaker, fallbackSpeaker);
50
+ const rawLines = Array.isArray(raw?.lines)
51
+ ? raw.lines
52
+ : [raw?.line ?? raw?.text].filter((value) => value != null);
53
+ const lines = rawLines
54
+ .map((lineEntry, lineIndex) => normalizeLineEntry(lineEntry, lineIndex, speaker))
55
+ .filter(Boolean);
56
+ const choices = (Array.isArray(raw?.choices) ? raw.choices : [])
57
+ .map((choiceEntry, choiceIndex) => normalizeChoiceEntry(choiceEntry, choiceIndex))
58
+ .filter(Boolean);
59
+ return {
60
+ id,
61
+ label: normalizeText(raw?.label ?? raw?.title, id),
62
+ speaker,
63
+ lines,
64
+ choices,
65
+ nextNodeId: normalizeId(raw?.nextNodeId ?? raw?.next ?? raw?.target),
66
+ outcomeId: normalizeId(raw?.outcomeId ?? raw?.outcome ?? raw?.result),
67
+ };
68
+ }
69
+
70
+ function normalizeConversationEntry(entry, index = 0) {
71
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
72
+ const id = normalizeId(raw?.id, `conversation-${index + 1}`);
73
+ const speaker = normalizeText(raw?.speaker);
74
+ const nodes = (Array.isArray(raw?.nodes) ? raw.nodes : [])
75
+ .map((nodeEntry, nodeIndex) => normalizeNodeEntry(nodeEntry, nodeIndex, speaker))
76
+ .filter(Boolean);
77
+ const nodesById = Object.fromEntries(nodes.map((node) => [node.id, node]));
78
+ const startNodeId = normalizeId(raw?.startNodeId ?? raw?.start, nodes[0]?.id || null);
79
+ return {
80
+ id,
81
+ title: normalizeText(raw?.title ?? raw?.label, id),
82
+ speaker,
83
+ startNodeId: Object.prototype.hasOwnProperty.call(nodesById, startNodeId) ? startNodeId : (nodes[0]?.id || null),
84
+ nodes,
85
+ nodesById,
86
+ };
87
+ }
88
+
89
+ function touchDialogueState(state) {
90
+ if (!state || typeof state !== 'object') return;
91
+ state.revision = Number(state.revision || 0) + 1;
92
+ }
93
+
94
+ function getConversationStore(state) {
95
+ return state && typeof state === 'object' && state.conversations && typeof state.conversations === 'object'
96
+ ? state.conversations
97
+ : null;
98
+ }
99
+
100
+ function getActiveConversation(state) {
101
+ const store = getConversationStore(state);
102
+ const conversationId = normalizeId(state?.activeConversationId);
103
+ if (!store || !conversationId) return null;
104
+ return store[conversationId] || null;
105
+ }
106
+
107
+ function getActiveNode(state, conversation = getActiveConversation(state)) {
108
+ const nodeId = normalizeId(state?.activeNodeId);
109
+ if (!conversation || !nodeId) return null;
110
+ return conversation.nodesById[nodeId] || null;
111
+ }
112
+
113
+ function getNodeViewLineIndex(node, state) {
114
+ if (!node || !Array.isArray(node.lines) || node.lines.length <= 0) return -1;
115
+ const rawIndex = Number(state?.activeLineIndex || 0);
116
+ const safeIndex = Number.isFinite(rawIndex) ? Math.max(0, Math.floor(rawIndex)) : 0;
117
+ return Math.min(safeIndex, node.lines.length - 1);
118
+ }
119
+
120
+ function choicesVisible(node, state) {
121
+ if (!node || !Array.isArray(node.choices) || node.choices.length <= 0) return false;
122
+ if (!Array.isArray(node.lines) || node.lines.length <= 0) return true;
123
+ return getNodeViewLineIndex(node, state) >= node.lines.length - 1;
124
+ }
125
+
126
+ function resetActiveDialogueState(state) {
127
+ state.activeConversationId = null;
128
+ state.activeNodeId = null;
129
+ state.activeLineIndex = 0;
130
+ state.lastChoiceId = null;
131
+ state.visitedNodeIds = [];
132
+ }
133
+
134
+ function transitionToNode(state, conversation, nextNodeId) {
135
+ const targetId = normalizeId(nextNodeId);
136
+ if (!targetId || !conversation?.nodesById?.[targetId]) return false;
137
+ state.activeConversationId = conversation.id;
138
+ state.activeNodeId = targetId;
139
+ state.activeLineIndex = 0;
140
+ state.visitedNodeIds.push(targetId);
141
+ return true;
142
+ }
143
+
144
+ function cloneCompletion(completion) {
145
+ if (!completion || typeof completion !== 'object') return null;
146
+ return {
147
+ conversationId: normalizeId(completion.conversationId),
148
+ outcomeId: normalizeId(completion.outcomeId),
149
+ nodeId: normalizeId(completion.nodeId),
150
+ lineIndex: Number.isFinite(completion.lineIndex) ? completion.lineIndex : -1,
151
+ choiceId: normalizeId(completion.choiceId),
152
+ reason: normalizeId(completion.reason),
153
+ visitedNodeIds: Array.isArray(completion.visitedNodeIds) ? [...completion.visitedNodeIds] : [],
154
+ revision: Number(completion.revision || 0),
155
+ };
156
+ }
157
+
158
+ function registerConversationInternal(state, conversation, { touch = true } = {}) {
159
+ const store = getConversationStore(state);
160
+ if (!store) return null;
161
+ const normalized = normalizeConversationEntry(conversation, state.conversationOrder.length);
162
+ store[normalized.id] = normalized;
163
+ if (!Array.isArray(state.conversationOrder)) {
164
+ state.conversationOrder = [];
165
+ }
166
+ if (!state.conversationOrder.includes(normalized.id)) {
167
+ state.conversationOrder.push(normalized.id);
168
+ }
169
+ if (touch) touchDialogueState(state);
170
+ return normalized;
171
+ }
172
+
173
+ export function createDialogueState2D(options = {}) {
174
+ const state = {
175
+ conversations: {},
176
+ conversationOrder: [],
177
+ activeConversationId: null,
178
+ activeNodeId: null,
179
+ activeLineIndex: 0,
180
+ lastChoiceId: null,
181
+ lastCompletion: null,
182
+ visitedNodeIds: [],
183
+ revision: 0,
184
+ };
185
+
186
+ for (const conversation of (Array.isArray(options.conversations) ? options.conversations : [])) {
187
+ registerConversationInternal(state, conversation, { touch: false });
188
+ }
189
+
190
+ if (options.startConversationId) {
191
+ startDialogueConversation2D(state, options.startConversationId);
192
+ }
193
+
194
+ return state;
195
+ }
196
+
197
+ export function registerDialogueConversation2D(state, conversation) {
198
+ return registerConversationInternal(state, conversation, { touch: true });
199
+ }
200
+
201
+ export function startDialogueConversation2D(state, conversationOrId, options = {}) {
202
+ const conversation = typeof conversationOrId === 'string'
203
+ ? getConversationStore(state)?.[normalizeId(conversationOrId)] || null
204
+ : registerConversationInternal(state, conversationOrId, { touch: true });
205
+ if (!conversation) return getDialogueView2D(state);
206
+
207
+ const startNodeId = normalizeId(options.startNodeId, conversation.startNodeId);
208
+ if (!startNodeId || !conversation.nodesById[startNodeId]) return getDialogueView2D(state);
209
+
210
+ state.activeConversationId = conversation.id;
211
+ state.activeNodeId = startNodeId;
212
+ state.activeLineIndex = 0;
213
+ state.lastChoiceId = null;
214
+ state.visitedNodeIds = [startNodeId];
215
+ touchDialogueState(state);
216
+ return getDialogueView2D(state);
217
+ }
218
+
219
+ export function completeDialogueConversation2D(state, outcomeId = 'completed', options = {}) {
220
+ const conversation = getActiveConversation(state);
221
+ const node = getActiveNode(state, conversation);
222
+ if (!conversation) return cloneCompletion(state?.lastCompletion);
223
+
224
+ const completion = {
225
+ conversationId: conversation.id,
226
+ outcomeId: normalizeId(outcomeId, 'completed'),
227
+ nodeId: normalizeId(options.nodeId, node?.id || null),
228
+ lineIndex: getNodeViewLineIndex(node, state),
229
+ choiceId: normalizeId(options.choiceId, state?.lastChoiceId || null),
230
+ reason: normalizeId(options.reason, 'completed'),
231
+ visitedNodeIds: Array.isArray(state?.visitedNodeIds) ? [...state.visitedNodeIds] : [],
232
+ revision: 0,
233
+ };
234
+
235
+ resetActiveDialogueState(state);
236
+ touchDialogueState(state);
237
+ completion.revision = Number(state.revision || 0);
238
+ state.lastCompletion = completion;
239
+ return cloneCompletion(completion);
240
+ }
241
+
242
+ export function advanceDialogueConversation2D(state) {
243
+ const conversation = getActiveConversation(state);
244
+ const node = getActiveNode(state, conversation);
245
+ if (!conversation || !node) return getDialogueView2D(state);
246
+
247
+ if (choicesVisible(node, state)) {
248
+ return getDialogueView2D(state);
249
+ }
250
+
251
+ const lineIndex = getNodeViewLineIndex(node, state);
252
+ if (lineIndex >= 0 && lineIndex < node.lines.length - 1) {
253
+ state.activeLineIndex = lineIndex + 1;
254
+ touchDialogueState(state);
255
+ return getDialogueView2D(state);
256
+ }
257
+
258
+ if (node.nextNodeId) {
259
+ if (transitionToNode(state, conversation, node.nextNodeId)) {
260
+ touchDialogueState(state);
261
+ return getDialogueView2D(state);
262
+ }
263
+ completeDialogueConversation2D(state, node.outcomeId || 'completed', {
264
+ reason: 'missing_next_node',
265
+ });
266
+ return getDialogueView2D(state);
267
+ }
268
+
269
+ completeDialogueConversation2D(state, node.outcomeId || 'completed', {
270
+ reason: 'node_complete',
271
+ });
272
+ return getDialogueView2D(state);
273
+ }
274
+
275
+ export function chooseDialogueChoice2D(state, choiceId) {
276
+ const conversation = getActiveConversation(state);
277
+ const node = getActiveNode(state, conversation);
278
+ if (!conversation || !node || !choicesVisible(node, state)) {
279
+ return getDialogueView2D(state);
280
+ }
281
+
282
+ const targetId = normalizeId(choiceId);
283
+ const choice = node.choices.find((entry) => entry.id === targetId && entry.disabled !== true) || null;
284
+ if (!choice) return getDialogueView2D(state);
285
+
286
+ state.lastChoiceId = choice.id;
287
+ if (choice.nextNodeId) {
288
+ if (transitionToNode(state, conversation, choice.nextNodeId)) {
289
+ touchDialogueState(state);
290
+ return getDialogueView2D(state);
291
+ }
292
+ completeDialogueConversation2D(state, choice.outcomeId || node.outcomeId || 'completed', {
293
+ choiceId: choice.id,
294
+ reason: 'missing_next_node',
295
+ });
296
+ return getDialogueView2D(state);
297
+ }
298
+
299
+ completeDialogueConversation2D(state, choice.outcomeId || node.outcomeId || 'completed', {
300
+ choiceId: choice.id,
301
+ reason: 'choice_selected',
302
+ });
303
+ return getDialogueView2D(state);
304
+ }
305
+
306
+ export function getDialogueView2D(state) {
307
+ const conversation = getActiveConversation(state);
308
+ const node = getActiveNode(state, conversation);
309
+ const lineIndex = getNodeViewLineIndex(node, state);
310
+ const line = lineIndex >= 0 ? node.lines[lineIndex] || null : null;
311
+ const awaitingChoice = choicesVisible(node, state);
312
+ const choices = awaitingChoice
313
+ ? node.choices.map((entry) => ({
314
+ id: entry.id,
315
+ label: entry.label,
316
+ detail: entry.detail,
317
+ nextNodeId: entry.nextNodeId,
318
+ outcomeId: entry.outcomeId,
319
+ disabled: entry.disabled === true,
320
+ }))
321
+ : [];
322
+ const canAdvance = Boolean(node) && awaitingChoice !== true && (
323
+ (lineIndex >= 0 && lineIndex < node.lines.length - 1)
324
+ || Boolean(node?.nextNodeId)
325
+ || (Boolean(node) && !node?.nextNodeId && choices.length === 0)
326
+ );
327
+
328
+ return {
329
+ active: Boolean(conversation && node),
330
+ conversationId: conversation?.id || null,
331
+ title: conversation?.title || null,
332
+ nodeId: node?.id || null,
333
+ nodeLabel: node?.label || null,
334
+ lineIndex,
335
+ lineCount: Array.isArray(node?.lines) ? node.lines.length : 0,
336
+ line: line ? {
337
+ id: line.id,
338
+ text: line.text,
339
+ speaker: line.speaker,
340
+ cue: line.cue,
341
+ index: lineIndex,
342
+ } : null,
343
+ choices,
344
+ awaitingChoice,
345
+ canAdvance,
346
+ lastChoiceId: normalizeId(state?.lastChoiceId),
347
+ visitedNodeIds: Array.isArray(state?.visitedNodeIds) ? [...state.visitedNodeIds] : [],
348
+ lastCompletion: cloneCompletion(state?.lastCompletion),
349
+ revision: Number(state?.revision || 0),
350
+ };
351
+ }
@@ -1,12 +1,26 @@
1
1
  import { clamp, axisFromKeys, createCooldown, tickCooldown, consumeCooldown, createIntervalSpawner, tickIntervalSpawner, centeredRect, rectsOverlap, removeWhere, distanceSquared2D, distanceSquared3D, tryJump, stepVerticalMotion } from './core.js';
2
2
  import { createSpriteAnimationLibrary2D, hasSpriteAnimationState2D, createSpriteAnimationRuntime2D, registerSpriteAnimationLibrary2D, createSpriteAnimator2D, playSpriteAnimator2D, syncSpriteAnimatorState2D, stepSpriteAnimationRuntime2D, getSpriteAnimatorState2D, resolveSpriteAnimatorFrame2D, drawSpriteAnimator2D, resolveSpriteFrameRect2D } from './animation-2d.js';
3
+ import { createAnimationPackage2D, hasAnimationPackageState2D, registerAnimationPackage2D, createPackagedSpriteAnimator2D, getAnimationPackageLibrary2D, getAnimationPackageState2D } from './animation-packaging-2d.js';
4
+ import { createAtlasAssetManifest2D, createAnimationPackageFromAtlasAsset2D } from './atlas-assets-2d.js';
5
+ import { createSceneAudio2D, configureSceneAudio2D, ensureSceneMusic2D, setSceneMusicVolume2D, duckSceneMusic2D, playSceneStinger2D, pauseSceneAudio2D, resumeSceneAudio2D, syncSceneAudio2D, stopSceneAudio2D } from './scene-audio-2d.js';
3
6
  import { propertyTween2D, delayTween2D, sequenceTweens2D, parallelTweens2D, repeatTween2D, yoyoTween2D, moveTo2D, driftTo2D, fadeTo2D, scaleTo2D, rotateTo2D, pulse2D, punch2D, settleTo2D, createTweenComposer2D, playTweenPlan2D, cancelTweenPlan2D, stepTweenComposer2D, getTweenHandleState2D, isTweenHandleActive2D } from './tween-2d.js';
4
7
  import { createFloatingTextLayer2D, spawnFloatingText2D, stepFloatingTextLayer2D, getFloatingTextRenderState2D, drawFloatingTextLayer2D, createHitFlash2D, triggerHitFlash2D, stepHitFlash2D, isHitFlashVisible2D, createHitSparkLayer2D, spawnHitSpark2D, stepHitSparkLayer2D, drawHitSparkLayer2D, createHoverLift2D, setHoverLiftActive2D, stepHoverLift2D, getHoverLiftOffset2D } from './combat-feedback-2d.js';
8
+ import { applyShieldedDamage2D, stepShieldRecharge2D, restoreShieldedHealth2D, createWeightedDropTable2D, rollWeightedDrop2D, createEncounterPressure2D, recordEncounterKill2D, stepEncounterPressure2D, advanceBossPhase2D } from './combat-runtime-2d.js';
5
9
  import { createRectTrigger2D, createProximityTrigger3D, createVolumeTrigger3D, createTriggerTracker, getTriggerTrackerSnapshot, stepRectTriggers2D, stepVolumeTriggers3D, stepTriggerTracker, resolveTriggerInteraction, createInteractionPromptState, updateInteractionPromptState } from './triggers.js';
6
10
  import { createWaveDirector, activeWave, stepWaveDirector } from './wave-director.js';
7
11
  import { listEnemyArchetypeIds2D, getEnemyArchetype2D, pickEnemyArchetypeForWave2D, spawnEnemy2D } from './enemy-archetypes-2d.js';
12
+ import { createAutoplayController2D, setAutoplayEnabled2D, toggleAutoplayEnabled2D, resolveAutoplayControl2D, createRenderTargetExportState2D, queueRenderTargetExport2D, flushRenderTargetExport2D } from './autoplay-debug-2d.js';
13
+ import { createInventory2D, addInventoryItem2D, removeInventoryItem2D, hasInventoryItem2D, listInventoryItems2D, selectInventoryCategory2D, selectInventoryItem2D, getSelectedInventoryItem2D, setInventoryItemEquipped2D, getInventoryView2D } from './inventory-2d.js';
14
+ import { createDialogueState2D, registerDialogueConversation2D, startDialogueConversation2D, advanceDialogueConversation2D, chooseDialogueChoice2D, completeDialogueConversation2D, getDialogueView2D } from './dialogue-2d.js';
15
+ import { createJournal2D, upsertJournalEntry2D, setJournalEntryStatus2D, listJournalEntries2D, selectJournalCategory2D, selectJournalEntry2D, getSelectedJournalEntry2D, getJournalView2D } from './journal-2d.js';
16
+ import { createTilemapWorld2D, ensureTilemapWorldLoaded2D, unloadTilemapWorld2D, drawTilemapWorld2D, listTilemapWorldObjects2D, findTilemapWorldObject2D, resolveTilemapSpawn2D, queryTilemapWorldPoint2D, queryTilemapWorldAABB2D, isTilemapPointBlocked2D, moveActorThroughTilemapWorld2D } from './tilemap-world-2d.js';
17
+ import { createStreamedWorld2D, listLoadedStreamedWorldRegions2D, findStreamedWorldRegionAtPoint2D, syncStreamedWorldFocus2D, queryStreamedWorldPoint2D, queryStreamedWorldAABB2D, queryStreamedWorldRay2D, unloadStreamedWorld2D } from './streamed-world-2d.js';
18
+ import { captureStreamedWorldSnapshot2D, applyStreamedWorldSnapshot2D, saveStreamedWorldSnapshot2D, loadStreamedWorldSnapshot2D, deleteStreamedWorldSnapshot2D } from './world-persistence-2d.js';
19
+ import { createTilemapNav2D, rebuildTilemapNav2D, invalidateTilemapNav2D, collectTilemapOccupiedCells2D, setTilemapNavOccupiedCells2D, findTilemapPath2D, resolveTilemapStep2D } from './tilemap-nav-2d.js';
20
+ import { createWorldCompositor2D, ensureWorldCompositorTargets2D, destroyWorldCompositor2D, runWorldCompositor2D } from './world-compositor-2d.js';
21
+ import { createAdventureInteractable2D, createAdventureInteractablesFromTilemapObjects2D, createAdventureInteractionState2D, stepAdventureInteractables2D } from './adventure-world-2d.js';
8
22
  import { createCheckpointSystem, updateCheckpointProgress, activeCheckpoint, respawnAtCheckpoint, createGoalZone, isGoalReached, createMovingPlatform3D, stepMovingPlatform3D } from './platformer-3d.js';
9
23
  import { createCharacterAvatar3D, setAvatarInput3D, setAvatarVisual3D, spawnCharacterAvatar3D, tickCharacterAvatar3D, getAvatarState3D, getAvatarRenderTransform3D } from './avatar-3d.js';
10
24
  import { createAdventureObjectives, resetAdventureObjectives, countCompletedObjectives, objectivesComplete, firstPendingObjective, collectAdventureObjectives2D, collectAdventureObjectives3D } from './adventure-objectives.js';
11
25
 
12
- export { clamp, axisFromKeys, createCooldown, tickCooldown, consumeCooldown, createIntervalSpawner, tickIntervalSpawner, centeredRect, rectsOverlap, removeWhere, distanceSquared2D, distanceSquared3D, tryJump, stepVerticalMotion, createSpriteAnimationLibrary2D, hasSpriteAnimationState2D, createSpriteAnimationRuntime2D, registerSpriteAnimationLibrary2D, createSpriteAnimator2D, playSpriteAnimator2D, syncSpriteAnimatorState2D, stepSpriteAnimationRuntime2D, getSpriteAnimatorState2D, resolveSpriteAnimatorFrame2D, drawSpriteAnimator2D, resolveSpriteFrameRect2D, propertyTween2D, delayTween2D, sequenceTweens2D, parallelTweens2D, repeatTween2D, yoyoTween2D, moveTo2D, driftTo2D, fadeTo2D, scaleTo2D, rotateTo2D, pulse2D, punch2D, settleTo2D, createTweenComposer2D, playTweenPlan2D, cancelTweenPlan2D, stepTweenComposer2D, getTweenHandleState2D, isTweenHandleActive2D, createFloatingTextLayer2D, spawnFloatingText2D, stepFloatingTextLayer2D, getFloatingTextRenderState2D, drawFloatingTextLayer2D, createHitFlash2D, triggerHitFlash2D, stepHitFlash2D, isHitFlashVisible2D, createHitSparkLayer2D, spawnHitSpark2D, stepHitSparkLayer2D, drawHitSparkLayer2D, createHoverLift2D, setHoverLiftActive2D, stepHoverLift2D, getHoverLiftOffset2D, createRectTrigger2D, createProximityTrigger3D, createVolumeTrigger3D, createTriggerTracker, getTriggerTrackerSnapshot, stepRectTriggers2D, stepVolumeTriggers3D, stepTriggerTracker, resolveTriggerInteraction, createInteractionPromptState, updateInteractionPromptState, createWaveDirector, activeWave, stepWaveDirector, listEnemyArchetypeIds2D, getEnemyArchetype2D, pickEnemyArchetypeForWave2D, spawnEnemy2D, createCheckpointSystem, updateCheckpointProgress, activeCheckpoint, respawnAtCheckpoint, createGoalZone, isGoalReached, createMovingPlatform3D, stepMovingPlatform3D, createCharacterAvatar3D, setAvatarInput3D, setAvatarVisual3D, spawnCharacterAvatar3D, tickCharacterAvatar3D, getAvatarState3D, getAvatarRenderTransform3D, createAdventureObjectives, resetAdventureObjectives, countCompletedObjectives, objectivesComplete, firstPendingObjective, collectAdventureObjectives2D, collectAdventureObjectives3D };
26
+ export { clamp, axisFromKeys, createCooldown, tickCooldown, consumeCooldown, createIntervalSpawner, tickIntervalSpawner, centeredRect, rectsOverlap, removeWhere, distanceSquared2D, distanceSquared3D, tryJump, stepVerticalMotion, createSpriteAnimationLibrary2D, hasSpriteAnimationState2D, createSpriteAnimationRuntime2D, registerSpriteAnimationLibrary2D, createSpriteAnimator2D, playSpriteAnimator2D, syncSpriteAnimatorState2D, stepSpriteAnimationRuntime2D, getSpriteAnimatorState2D, resolveSpriteAnimatorFrame2D, drawSpriteAnimator2D, resolveSpriteFrameRect2D, createAnimationPackage2D, hasAnimationPackageState2D, registerAnimationPackage2D, createPackagedSpriteAnimator2D, getAnimationPackageLibrary2D, getAnimationPackageState2D, createAtlasAssetManifest2D, createAnimationPackageFromAtlasAsset2D, createSceneAudio2D, configureSceneAudio2D, ensureSceneMusic2D, setSceneMusicVolume2D, duckSceneMusic2D, playSceneStinger2D, pauseSceneAudio2D, resumeSceneAudio2D, syncSceneAudio2D, stopSceneAudio2D, propertyTween2D, delayTween2D, sequenceTweens2D, parallelTweens2D, repeatTween2D, yoyoTween2D, moveTo2D, driftTo2D, fadeTo2D, scaleTo2D, rotateTo2D, pulse2D, punch2D, settleTo2D, createTweenComposer2D, playTweenPlan2D, cancelTweenPlan2D, stepTweenComposer2D, getTweenHandleState2D, isTweenHandleActive2D, createFloatingTextLayer2D, spawnFloatingText2D, stepFloatingTextLayer2D, getFloatingTextRenderState2D, drawFloatingTextLayer2D, createHitFlash2D, triggerHitFlash2D, stepHitFlash2D, isHitFlashVisible2D, createHitSparkLayer2D, spawnHitSpark2D, stepHitSparkLayer2D, drawHitSparkLayer2D, createHoverLift2D, setHoverLiftActive2D, stepHoverLift2D, getHoverLiftOffset2D, applyShieldedDamage2D, stepShieldRecharge2D, restoreShieldedHealth2D, createWeightedDropTable2D, rollWeightedDrop2D, createEncounterPressure2D, recordEncounterKill2D, stepEncounterPressure2D, advanceBossPhase2D, createRectTrigger2D, createProximityTrigger3D, createVolumeTrigger3D, createTriggerTracker, getTriggerTrackerSnapshot, stepRectTriggers2D, stepVolumeTriggers3D, stepTriggerTracker, resolveTriggerInteraction, createInteractionPromptState, updateInteractionPromptState, createWaveDirector, activeWave, stepWaveDirector, listEnemyArchetypeIds2D, getEnemyArchetype2D, pickEnemyArchetypeForWave2D, spawnEnemy2D, createAutoplayController2D, setAutoplayEnabled2D, toggleAutoplayEnabled2D, resolveAutoplayControl2D, createRenderTargetExportState2D, queueRenderTargetExport2D, flushRenderTargetExport2D, createInventory2D, addInventoryItem2D, removeInventoryItem2D, hasInventoryItem2D, listInventoryItems2D, selectInventoryCategory2D, selectInventoryItem2D, getSelectedInventoryItem2D, setInventoryItemEquipped2D, getInventoryView2D, createDialogueState2D, registerDialogueConversation2D, startDialogueConversation2D, advanceDialogueConversation2D, chooseDialogueChoice2D, completeDialogueConversation2D, getDialogueView2D, createJournal2D, upsertJournalEntry2D, setJournalEntryStatus2D, listJournalEntries2D, selectJournalCategory2D, selectJournalEntry2D, getSelectedJournalEntry2D, getJournalView2D, createTilemapWorld2D, ensureTilemapWorldLoaded2D, unloadTilemapWorld2D, drawTilemapWorld2D, listTilemapWorldObjects2D, findTilemapWorldObject2D, resolveTilemapSpawn2D, queryTilemapWorldPoint2D, queryTilemapWorldAABB2D, isTilemapPointBlocked2D, moveActorThroughTilemapWorld2D, createStreamedWorld2D, listLoadedStreamedWorldRegions2D, findStreamedWorldRegionAtPoint2D, syncStreamedWorldFocus2D, queryStreamedWorldPoint2D, queryStreamedWorldAABB2D, queryStreamedWorldRay2D, unloadStreamedWorld2D, captureStreamedWorldSnapshot2D, applyStreamedWorldSnapshot2D, saveStreamedWorldSnapshot2D, loadStreamedWorldSnapshot2D, deleteStreamedWorldSnapshot2D, createTilemapNav2D, rebuildTilemapNav2D, invalidateTilemapNav2D, collectTilemapOccupiedCells2D, setTilemapNavOccupiedCells2D, findTilemapPath2D, resolveTilemapStep2D, createWorldCompositor2D, ensureWorldCompositorTargets2D, destroyWorldCompositor2D, runWorldCompositor2D, createAdventureInteractable2D, createAdventureInteractablesFromTilemapObjects2D, createAdventureInteractionState2D, stepAdventureInteractables2D, createCheckpointSystem, updateCheckpointProgress, activeCheckpoint, respawnAtCheckpoint, createGoalZone, isGoalReached, createMovingPlatform3D, stepMovingPlatform3D, createCharacterAvatar3D, setAvatarInput3D, setAvatarVisual3D, spawnCharacterAvatar3D, tickCharacterAvatar3D, getAvatarState3D, getAvatarRenderTransform3D, createAdventureObjectives, resetAdventureObjectives, countCompletedObjectives, objectivesComplete, firstPendingObjective, collectAdventureObjectives2D, collectAdventureObjectives3D };
@@ -0,0 +1,268 @@
1
+ function normalizeId(value, fallback) {
2
+ if (typeof value === 'string' && value.trim().length > 0) {
3
+ return value.trim();
4
+ }
5
+ return fallback;
6
+ }
7
+
8
+ function normalizeLabel(value, fallback) {
9
+ if (typeof value === 'string' && value.trim().length > 0) {
10
+ return value.trim();
11
+ }
12
+ return fallback;
13
+ }
14
+
15
+ function normalizeQuantity(value, fallback = 1) {
16
+ const numeric = Number(value);
17
+ if (!Number.isFinite(numeric)) return Math.max(0, fallback);
18
+ return Math.max(0, Math.round(numeric));
19
+ }
20
+
21
+ function normalizeCategoryEntry(entry, index) {
22
+ const fallbackId = `category-${index + 1}`;
23
+ const id = normalizeId(entry?.id, fallbackId);
24
+ return {
25
+ id,
26
+ label: normalizeLabel(entry?.label ?? entry?.name, id),
27
+ };
28
+ }
29
+
30
+ function deriveMissingCategories(entries = [], items = []) {
31
+ const categories = Array.isArray(entries)
32
+ ? entries.map((entry, index) => normalizeCategoryEntry(entry, index))
33
+ : [];
34
+ const known = new Set(categories.map((entry) => entry.id));
35
+ for (const item of Array.isArray(items) ? items : []) {
36
+ const categoryId = normalizeId(item?.categoryId ?? item?.category, null);
37
+ if (!categoryId || known.has(categoryId)) continue;
38
+ categories.push({
39
+ id: categoryId,
40
+ label: normalizeLabel(item?.categoryLabel, categoryId),
41
+ });
42
+ known.add(categoryId);
43
+ }
44
+ return categories;
45
+ }
46
+
47
+ function normalizeItemEntry(entry, index, categories = []) {
48
+ const fallbackId = `item-${index + 1}`;
49
+ const id = normalizeId(entry?.id, fallbackId);
50
+ const categoryId = normalizeId(entry?.categoryId ?? entry?.category, categories[0]?.id || 'inventory');
51
+ return {
52
+ id,
53
+ label: normalizeLabel(entry?.label ?? entry?.name, id),
54
+ description: typeof entry?.description === 'string' ? entry.description : null,
55
+ detail: typeof entry?.detail === 'string' ? entry.detail : null,
56
+ badge: typeof entry?.badge === 'string' && entry.badge.trim().length > 0 ? entry.badge.trim() : null,
57
+ categoryId,
58
+ categoryLabel: normalizeLabel(entry?.categoryLabel, categoryId),
59
+ quantity: normalizeQuantity(entry?.quantity, 1),
60
+ equipped: entry?.equipped === true,
61
+ disabled: entry?.disabled === true,
62
+ };
63
+ }
64
+
65
+ function ensureCategory(inventory, categoryId, categoryLabel = null) {
66
+ if (!inventory || !Array.isArray(inventory.categories)) return null;
67
+ const existing = inventory.categories.find((entry) => entry.id === categoryId) || null;
68
+ if (existing) return existing;
69
+ const category = {
70
+ id: normalizeId(categoryId, `category-${inventory.categories.length + 1}`),
71
+ label: normalizeLabel(categoryLabel, categoryId),
72
+ };
73
+ inventory.categories.push(category);
74
+ return category;
75
+ }
76
+
77
+ function firstItemInCategory(inventory, categoryId = null) {
78
+ const items = listInventoryItems2D(inventory, categoryId);
79
+ return items[0] || null;
80
+ }
81
+
82
+ function activeCategoryId(inventory) {
83
+ if (!inventory || !Array.isArray(inventory.categories) || inventory.categories.length <= 0) return null;
84
+ if (inventory.selectedCategoryId && inventory.categories.some((entry) => entry.id === inventory.selectedCategoryId)) {
85
+ return inventory.selectedCategoryId;
86
+ }
87
+ return inventory.categories[0].id;
88
+ }
89
+
90
+ function touchInventory(inventory) {
91
+ if (!inventory || typeof inventory !== 'object') return;
92
+ inventory.revision = Number(inventory.revision || 0) + 1;
93
+ }
94
+
95
+ export function createInventory2D(options = {}) {
96
+ const categories = deriveMissingCategories(options.categories, options.items);
97
+ const inventory = {
98
+ categories,
99
+ items: [],
100
+ selectedCategoryId: categories[0]?.id || null,
101
+ selectedItemId: null,
102
+ revision: 0,
103
+ };
104
+
105
+ for (const [index, entry] of (Array.isArray(options.items) ? options.items : []).entries()) {
106
+ const item = normalizeItemEntry(entry, index, categories);
107
+ if (item.quantity <= 0) continue;
108
+ ensureCategory(inventory, item.categoryId, item.categoryLabel);
109
+ inventory.items.push(item);
110
+ }
111
+
112
+ if (typeof options.selectedCategoryId === 'string') {
113
+ selectInventoryCategory2D(inventory, options.selectedCategoryId);
114
+ }
115
+ if (typeof options.selectedItemId === 'string') {
116
+ selectInventoryItem2D(inventory, options.selectedItemId);
117
+ }
118
+ if (!inventory.selectedItemId) {
119
+ const first = firstItemInCategory(inventory, inventory.selectedCategoryId);
120
+ inventory.selectedItemId = first?.id || null;
121
+ }
122
+
123
+ return inventory;
124
+ }
125
+
126
+ export function addInventoryItem2D(inventory, entry, quantity = null) {
127
+ if (!inventory || !Array.isArray(inventory.items)) return null;
128
+ const item = normalizeItemEntry(entry, inventory.items.length, inventory.categories);
129
+ const delta = quantity == null ? item.quantity : normalizeQuantity(quantity, item.quantity);
130
+ if (delta <= 0) return null;
131
+
132
+ ensureCategory(inventory, item.categoryId, item.categoryLabel);
133
+ const existing = inventory.items.find((candidate) => candidate.id === item.id) || null;
134
+ if (existing) {
135
+ existing.label = item.label;
136
+ existing.description = item.description;
137
+ existing.detail = item.detail;
138
+ existing.badge = item.badge;
139
+ existing.categoryId = item.categoryId;
140
+ existing.categoryLabel = item.categoryLabel;
141
+ existing.equipped = item.equipped === true ? true : existing.equipped === true;
142
+ existing.disabled = item.disabled === true;
143
+ existing.quantity += delta;
144
+ touchInventory(inventory);
145
+ if (!inventory.selectedCategoryId) inventory.selectedCategoryId = existing.categoryId;
146
+ if (!inventory.selectedItemId) inventory.selectedItemId = existing.id;
147
+ return existing;
148
+ }
149
+
150
+ item.quantity = delta;
151
+ inventory.items.push(item);
152
+ if (!inventory.selectedCategoryId) inventory.selectedCategoryId = item.categoryId;
153
+ if (!inventory.selectedItemId) inventory.selectedItemId = item.id;
154
+ touchInventory(inventory);
155
+ return item;
156
+ }
157
+
158
+ export function removeInventoryItem2D(inventory, itemId, quantity = 1) {
159
+ if (!inventory || !Array.isArray(inventory.items)) return false;
160
+ const targetId = normalizeId(itemId, null);
161
+ if (!targetId) return false;
162
+ const index = inventory.items.findIndex((entry) => entry.id === targetId);
163
+ if (index < 0) return false;
164
+ const item = inventory.items[index];
165
+ const delta = normalizeQuantity(quantity, 1);
166
+ if (delta <= 0) return false;
167
+
168
+ item.quantity = Math.max(0, item.quantity - delta);
169
+ if (item.quantity <= 0) {
170
+ inventory.items.splice(index, 1);
171
+ }
172
+ if (inventory.selectedItemId === targetId) {
173
+ inventory.selectedItemId = firstItemInCategory(inventory, inventory.selectedCategoryId)?.id || null;
174
+ }
175
+ if (!inventory.selectedItemId) {
176
+ inventory.selectedCategoryId = activeCategoryId(inventory);
177
+ inventory.selectedItemId = firstItemInCategory(inventory, inventory.selectedCategoryId)?.id || null;
178
+ }
179
+ touchInventory(inventory);
180
+ return true;
181
+ }
182
+
183
+ export function hasInventoryItem2D(inventory, itemId, quantity = 1) {
184
+ if (!inventory || !Array.isArray(inventory.items)) return false;
185
+ const targetId = normalizeId(itemId, null);
186
+ if (!targetId) return false;
187
+ const item = inventory.items.find((entry) => entry.id === targetId) || null;
188
+ return Boolean(item) && item.quantity >= normalizeQuantity(quantity, 1);
189
+ }
190
+
191
+ export function listInventoryItems2D(inventory, categoryId = null) {
192
+ if (!inventory || !Array.isArray(inventory.items)) return [];
193
+ const targetCategory = normalizeId(categoryId, null) || activeCategoryId(inventory);
194
+ if (!targetCategory) return [...inventory.items];
195
+ return inventory.items.filter((entry) => entry.categoryId === targetCategory);
196
+ }
197
+
198
+ export function selectInventoryCategory2D(inventory, categoryId) {
199
+ if (!inventory || !Array.isArray(inventory.categories)) return null;
200
+ const targetId = normalizeId(categoryId, null);
201
+ if (!targetId || !inventory.categories.some((entry) => entry.id === targetId)) return inventory.selectedCategoryId || null;
202
+ inventory.selectedCategoryId = targetId;
203
+ const first = firstItemInCategory(inventory, targetId);
204
+ if (!first) {
205
+ inventory.selectedItemId = null;
206
+ } else if (!hasInventoryItem2D(inventory, inventory.selectedItemId)) {
207
+ inventory.selectedItemId = first.id;
208
+ } else if (!listInventoryItems2D(inventory, targetId).some((entry) => entry.id === inventory.selectedItemId)) {
209
+ inventory.selectedItemId = first.id;
210
+ }
211
+ touchInventory(inventory);
212
+ return inventory.selectedCategoryId;
213
+ }
214
+
215
+ export function selectInventoryItem2D(inventory, itemId) {
216
+ if (!inventory || !Array.isArray(inventory.items)) return null;
217
+ const targetId = normalizeId(itemId, null);
218
+ const item = inventory.items.find((entry) => entry.id === targetId) || null;
219
+ if (!item) return inventory.selectedItemId || null;
220
+ inventory.selectedCategoryId = item.categoryId;
221
+ inventory.selectedItemId = item.id;
222
+ touchInventory(inventory);
223
+ return inventory.selectedItemId;
224
+ }
225
+
226
+ export function getSelectedInventoryItem2D(inventory) {
227
+ if (!inventory || !Array.isArray(inventory.items)) return null;
228
+ return inventory.items.find((entry) => entry.id === inventory.selectedItemId) || null;
229
+ }
230
+
231
+ export function setInventoryItemEquipped2D(inventory, itemId, equipped = true) {
232
+ if (!inventory || !Array.isArray(inventory.items)) return false;
233
+ const targetId = normalizeId(itemId, null);
234
+ const item = inventory.items.find((entry) => entry.id === targetId) || null;
235
+ if (!item) return false;
236
+ item.equipped = equipped === true;
237
+ touchInventory(inventory);
238
+ return true;
239
+ }
240
+
241
+ export function getInventoryView2D(inventory) {
242
+ const categories = Array.isArray(inventory?.categories) ? inventory.categories : [];
243
+ const items = listInventoryItems2D(inventory);
244
+ const selectedItem = getSelectedInventoryItem2D(inventory);
245
+ return {
246
+ selectedCategoryId: activeCategoryId(inventory),
247
+ selectedItemId: selectedItem?.id || null,
248
+ categories: categories.map((entry) => ({
249
+ id: entry.id,
250
+ label: entry.label,
251
+ selected: entry.id === activeCategoryId(inventory),
252
+ count: listInventoryItems2D(inventory, entry.id).reduce((total, item) => total + item.quantity, 0),
253
+ })),
254
+ items: items.map((entry) => ({
255
+ id: entry.id,
256
+ label: entry.label,
257
+ description: entry.description,
258
+ detail: entry.detail,
259
+ badge: entry.badge,
260
+ quantity: entry.quantity,
261
+ equipped: entry.equipped === true,
262
+ selected: entry.id === selectedItem?.id,
263
+ disabled: entry.disabled === true,
264
+ })),
265
+ selectedItem,
266
+ revision: Number(inventory?.revision || 0),
267
+ };
268
+ }