@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,174 @@
1
+ import { MOBILE_CAPABILITY_REPORT_SCHEMA } from '../../build-contract/constants.mjs';
2
+
3
+ export const MOBILE_BUILD_TARGETS = new Set(['android', 'ios', 'mobile']);
4
+
5
+ const MOBILE_UNSUPPORTED_API_RULES = Object.freeze([
6
+ Object.freeze({
7
+ api: 'aura.input.isKeyDown',
8
+ capability: 'keyboardInput',
9
+ reasonCode: 'mobile_keyboard_first_control_deferred',
10
+ }),
11
+ Object.freeze({
12
+ api: 'aura.input.isKeyPressed',
13
+ capability: 'keyboardInput',
14
+ reasonCode: 'mobile_keyboard_first_control_deferred',
15
+ }),
16
+ Object.freeze({
17
+ api: 'aura.input.isKeyReleased',
18
+ capability: 'keyboardInput',
19
+ reasonCode: 'mobile_keyboard_first_control_deferred',
20
+ }),
21
+ Object.freeze({
22
+ api: 'aura.input.pressed',
23
+ capability: 'keyboardInput',
24
+ reasonCode: 'mobile_keyboard_first_control_deferred',
25
+ }),
26
+ Object.freeze({
27
+ api: 'aura.input.justPressed',
28
+ capability: 'keyboardInput',
29
+ reasonCode: 'mobile_keyboard_first_control_deferred',
30
+ }),
31
+ Object.freeze({
32
+ api: 'aura.input.released',
33
+ capability: 'keyboardInput',
34
+ reasonCode: 'mobile_keyboard_first_control_deferred',
35
+ }),
36
+ Object.freeze({
37
+ api: 'aura.window.setCursorLocked',
38
+ capability: 'cursorLock',
39
+ reasonCode: 'mobile_cursor_lock_unsupported',
40
+ }),
41
+ Object.freeze({
42
+ api: 'aura.input.getMouseWheel',
43
+ capability: 'mouseWheel',
44
+ reasonCode: 'mobile_mouse_wheel_unsupported',
45
+ }),
46
+ Object.freeze({
47
+ api: 'aura.window.setSize',
48
+ capability: 'windowManagement',
49
+ reasonCode: 'mobile_window_management_unsupported',
50
+ }),
51
+ Object.freeze({
52
+ api: 'aura.window.setFullscreen',
53
+ capability: 'windowManagement',
54
+ reasonCode: 'mobile_window_management_unsupported',
55
+ }),
56
+ Object.freeze({
57
+ api: 'aura.window.minimize',
58
+ capability: 'windowManagement',
59
+ reasonCode: 'mobile_window_management_unsupported',
60
+ }),
61
+ Object.freeze({
62
+ api: 'aura.window.restore',
63
+ capability: 'windowManagement',
64
+ reasonCode: 'mobile_window_management_unsupported',
65
+ }),
66
+ Object.freeze({
67
+ api: 'aura.window.focus',
68
+ capability: 'windowManagement',
69
+ reasonCode: 'mobile_window_management_unsupported',
70
+ }),
71
+ Object.freeze({
72
+ api: 'aura.window.getSafeAreaInsets',
73
+ capability: 'safeArea',
74
+ reasonCode: 'mobile_safe_area_unavailable',
75
+ }),
76
+ Object.freeze({
77
+ api: 'aura.window.setOrientationLock',
78
+ capability: 'orientationControl',
79
+ reasonCode: 'mobile_orientation_control_unavailable',
80
+ }),
81
+ Object.freeze({
82
+ api: 'aura.input.gyro',
83
+ capability: 'sensor',
84
+ reasonCode: 'mobile_sensor_unavailable',
85
+ }),
86
+ Object.freeze({
87
+ api: 'aura.input.accelerometer',
88
+ capability: 'sensor',
89
+ reasonCode: 'mobile_sensor_unavailable',
90
+ }),
91
+ ]);
92
+
93
+ const MOBILE_DEFERRED_SURFACES = Object.freeze([
94
+ Object.freeze({
95
+ surface: 'keyboardFirstControls',
96
+ status: 'deferred',
97
+ reasonCode: 'mobile_keyboard_first_control_deferred',
98
+ }),
99
+ Object.freeze({
100
+ surface: 'multiTouchPublicContract',
101
+ status: 'deferred',
102
+ reasonCode: null,
103
+ }),
104
+ Object.freeze({
105
+ surface: 'orientationControl',
106
+ status: 'deferred',
107
+ reasonCode: 'mobile_orientation_control_unavailable',
108
+ }),
109
+ Object.freeze({
110
+ surface: 'safeArea',
111
+ status: 'deferred',
112
+ reasonCode: 'mobile_safe_area_unavailable',
113
+ }),
114
+ Object.freeze({
115
+ surface: 'sensors',
116
+ status: 'deferred',
117
+ reasonCode: 'mobile_sensor_unavailable',
118
+ }),
119
+ ]);
120
+
121
+ function normalizeRequiredApis(requiredApis) {
122
+ if (!Array.isArray(requiredApis)) {
123
+ return [];
124
+ }
125
+ return [...new Set(requiredApis
126
+ .filter((entry) => typeof entry === 'string')
127
+ .map((entry) => entry.trim())
128
+ .filter(Boolean))]
129
+ .sort((a, b) => a.localeCompare(b));
130
+ }
131
+
132
+ export function normalizeMobileBuildTarget(target) {
133
+ const normalized = typeof target === 'string' ? target.trim().toLowerCase() : '';
134
+ if (!MOBILE_BUILD_TARGETS.has(normalized)) {
135
+ throw new Error(`Unsupported mobile build target: ${target}`);
136
+ }
137
+ return normalized;
138
+ }
139
+
140
+ export function buildMobileCapabilityReport({ target, capabilityDeclaration } = {}) {
141
+ const normalizedTarget = normalizeMobileBuildTarget(target || 'mobile');
142
+ const requiredApis = normalizeRequiredApis(capabilityDeclaration?.requiredApis);
143
+ const unsupportedApis = MOBILE_UNSUPPORTED_API_RULES
144
+ .filter((rule) => requiredApis.includes(rule.api))
145
+ .map((rule) => ({
146
+ api: rule.api,
147
+ capability: rule.capability,
148
+ status: 'unsupported',
149
+ reasonCode: rule.reasonCode,
150
+ }));
151
+
152
+ return {
153
+ schema: MOBILE_CAPABILITY_REPORT_SCHEMA,
154
+ schemaVersion: '1.0.0',
155
+ buildTarget: normalizedTarget,
156
+ source: typeof capabilityDeclaration?.source === 'string' ? capabilityDeclaration.source : null,
157
+ reasonCode: unsupportedApis.length > 0
158
+ ? 'mobile_required_api_unsupported'
159
+ : 'mobile_capability_contract_ok',
160
+ ok: unsupportedApis.length === 0,
161
+ input: {
162
+ primaryPointer: {
163
+ status: 'supported',
164
+ reasonCode: null,
165
+ },
166
+ multiTouch: {
167
+ status: 'deferred',
168
+ reasonCode: null,
169
+ },
170
+ },
171
+ unsupportedApis,
172
+ deferredSurfaces: MOBILE_DEFERRED_SURFACES,
173
+ };
174
+ }
@@ -0,0 +1,259 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, relative, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export const PACK_CATALOG_SCHEMA = 'aurajs.pack-catalog.v1';
6
+ export const PROJECT_PACKS_SCHEMA = 'aurajs.project-packs.v1';
7
+ export const PROJECT_PACKS_MANIFEST_PATH = 'aura.packs.json';
8
+
9
+ const PACKS_DIR = dirname(fileURLToPath(import.meta.url));
10
+ const CLI_PACKAGE_ROOT = resolve(PACKS_DIR, '../..');
11
+ const REPO_ROOT = resolve(CLI_PACKAGE_ROOT, '../..');
12
+ const WORKSPACE_PACKAGES_ROOT = resolve(REPO_ROOT, 'packages');
13
+ const CLI_PACKAGE = JSON.parse(readFileSync(resolve(CLI_PACKAGE_ROOT, 'package.json'), 'utf8'));
14
+ const CLI_PACKAGE_VERSION = String(CLI_PACKAGE.version || '0.1.0');
15
+ const PUBLIC_PACK_GITHUB_REPOSITORY = 'Aura-Industry/auramaxx';
16
+
17
+ function createPublicPackGithub(packDirName) {
18
+ return {
19
+ repository: PUBLIC_PACK_GITHUB_REPOSITORY,
20
+ ref: 'main',
21
+ packPath: `packs/${packDirName}`,
22
+ };
23
+ }
24
+
25
+ const PACK_REGISTRY = [
26
+ {
27
+ id: 'ai-starters',
28
+ kind: 'starter-pack',
29
+ title: 'AI Starters',
30
+ description: 'AI-first recipes, prompts, and starter overlays for AuraJS projects.',
31
+ packageName: '@auraindustry/aurajs-pack-ai-starters',
32
+ workspaceDirName: 'aurajs-pack-ai-starters',
33
+ surfaces: ['recipes', 'skills', 'starter-overlays'],
34
+ recommendedFor: ['ai', '2d', 'starter-authoring'],
35
+ github: createPublicPackGithub('aurajs-pack-ai-starters'),
36
+ },
37
+ {
38
+ id: 'assets-2d-core',
39
+ kind: 'asset-pack',
40
+ title: '2D Core Assets',
41
+ description: 'Optional placeholder-ready 2D palette, HUD, and audio planning assets.',
42
+ packageName: '@auraindustry/aurajs-pack-assets-2d-core',
43
+ workspaceDirName: 'aurajs-pack-assets-2d-core',
44
+ surfaces: ['assets', 'palettes', 'audio-planning'],
45
+ recommendedFor: ['2d', 'prototype', 'placeholder-art'],
46
+ github: createPublicPackGithub('aurajs-pack-assets-2d-core'),
47
+ },
48
+ {
49
+ id: 'ui-hud-2d',
50
+ kind: 'ui-pack',
51
+ title: 'UI HUD 2D',
52
+ description: 'Reusable 2D HUD themes, layout payloads, and widget planning files.',
53
+ packageName: '@auraindustry/aurajs-pack-ui-hud-2d',
54
+ workspaceDirName: 'aurajs-pack-ui-hud-2d',
55
+ surfaces: ['themes', 'layouts', 'widgets'],
56
+ recommendedFor: ['2d', 'hud', 'ui'],
57
+ github: createPublicPackGithub('aurajs-pack-ui-hud-2d'),
58
+ },
59
+ {
60
+ id: 'audio-2d-core',
61
+ kind: 'audio-pack',
62
+ title: 'Audio 2D Core',
63
+ description: 'Structured 2D scene audio defaults, bus layout, stingers, and bark planning payloads.',
64
+ packageName: '@auraindustry/aurajs-pack-audio-2d-core',
65
+ workspaceDirName: 'aurajs-pack-audio-2d-core',
66
+ surfaces: ['audio-config', 'barks', 'stingers'],
67
+ recommendedFor: ['2d', 'audio', 'scene-audio'],
68
+ github: createPublicPackGithub('aurajs-pack-audio-2d-core'),
69
+ },
70
+ ];
71
+
72
+ const BUNDLE_REGISTRY = [
73
+ {
74
+ id: 'ai-2d-bundle',
75
+ kind: 'bundle',
76
+ title: 'AI 2D Bundle',
77
+ description: 'Recommended optional pack set for AI-assisted 2D AuraJS projects.',
78
+ packageName: '@auraindustry/aurajs-bundle-ai-2d',
79
+ workspaceDirName: 'aurajs-bundle-ai-2d',
80
+ memberPackIds: ['ai-starters', 'assets-2d-core', 'ui-hud-2d', 'audio-2d-core'],
81
+ recommendedFor: ['ai', '2d', 'flagship-prototype'],
82
+ github: createPublicPackGithub('aurajs-bundle-ai-2d'),
83
+ },
84
+ ];
85
+
86
+ function toPosixPath(value) {
87
+ return String(value || '').split(/[/\\]+/g).filter(Boolean).join('/');
88
+ }
89
+
90
+ function normalizeRelativeFileSpec(projectRoot, targetRoot) {
91
+ const relativePath = relative(projectRoot, targetRoot);
92
+ const normalized = toPosixPath(relativePath);
93
+ if (!normalized || normalized === '.') {
94
+ return 'file:.';
95
+ }
96
+ if (normalized.startsWith('.')) {
97
+ return `file:${normalized}`;
98
+ }
99
+ return `file:./${normalized}`;
100
+ }
101
+
102
+ function buildPackEntry(entry) {
103
+ const workspacePath = resolve(WORKSPACE_PACKAGES_ROOT, entry.workspaceDirName);
104
+ return {
105
+ ...entry,
106
+ workspacePath,
107
+ workspaceAvailable: existsSync(workspacePath),
108
+ versionRange: `^${CLI_PACKAGE_VERSION}`,
109
+ github: entry.github ? { ...entry.github } : null,
110
+ };
111
+ }
112
+
113
+ function buildBundleEntry(entry) {
114
+ const workspacePath = resolve(WORKSPACE_PACKAGES_ROOT, entry.workspaceDirName);
115
+ return {
116
+ ...entry,
117
+ workspacePath,
118
+ workspaceAvailable: existsSync(workspacePath),
119
+ versionRange: `^${CLI_PACKAGE_VERSION}`,
120
+ github: entry.github ? { ...entry.github } : null,
121
+ };
122
+ }
123
+
124
+ export function listPackRegistry() {
125
+ return PACK_REGISTRY.map(buildPackEntry);
126
+ }
127
+
128
+ export function listBundleRegistry() {
129
+ return BUNDLE_REGISTRY.map(buildBundleEntry);
130
+ }
131
+
132
+ export function findPackEntry(id) {
133
+ return listPackRegistry().find((entry) => entry.id === id) || null;
134
+ }
135
+
136
+ export function findBundleEntry(id) {
137
+ return listBundleRegistry().find((entry) => entry.id === id) || null;
138
+ }
139
+
140
+ export function findPackOrBundleEntry(id) {
141
+ return findPackEntry(id) || findBundleEntry(id);
142
+ }
143
+
144
+ export function expandPackSelection(id) {
145
+ const pack = findPackEntry(id);
146
+ if (pack) {
147
+ return {
148
+ requested: pack,
149
+ bundles: [],
150
+ packs: [pack],
151
+ };
152
+ }
153
+ const bundle = findBundleEntry(id);
154
+ if (!bundle) {
155
+ return null;
156
+ }
157
+ const packs = bundle.memberPackIds
158
+ .map((memberId) => findPackEntry(memberId))
159
+ .filter(Boolean);
160
+ return {
161
+ requested: bundle,
162
+ bundles: [bundle],
163
+ packs,
164
+ };
165
+ }
166
+
167
+ export function resolvePackInstallSpec(entry, { projectRoot, workspace = false } = {}) {
168
+ if (workspace && projectRoot && entry.workspaceAvailable) {
169
+ return {
170
+ mode: 'workspace',
171
+ spec: normalizeRelativeFileSpec(projectRoot, entry.workspacePath),
172
+ };
173
+ }
174
+ return {
175
+ mode: 'package',
176
+ spec: entry.versionRange,
177
+ };
178
+ }
179
+
180
+ export function createPackCatalogReport() {
181
+ return {
182
+ schema: PACK_CATALOG_SCHEMA,
183
+ corePackage: CLI_PACKAGE.name,
184
+ coreVersion: CLI_PACKAGE_VERSION,
185
+ packs: listPackRegistry().map((entry) => ({
186
+ id: entry.id,
187
+ kind: entry.kind,
188
+ title: entry.title,
189
+ description: entry.description,
190
+ packageName: entry.packageName,
191
+ versionRange: entry.versionRange,
192
+ workspaceAvailable: entry.workspaceAvailable,
193
+ github: entry.github ? { ...entry.github } : null,
194
+ recommendedFor: [...entry.recommendedFor],
195
+ surfaces: [...entry.surfaces],
196
+ })),
197
+ bundles: listBundleRegistry().map((entry) => ({
198
+ id: entry.id,
199
+ kind: entry.kind,
200
+ title: entry.title,
201
+ description: entry.description,
202
+ packageName: entry.packageName,
203
+ versionRange: entry.versionRange,
204
+ workspaceAvailable: entry.workspaceAvailable,
205
+ github: entry.github ? { ...entry.github } : null,
206
+ recommendedFor: [...entry.recommendedFor],
207
+ memberPackIds: [...entry.memberPackIds],
208
+ })),
209
+ };
210
+ }
211
+
212
+ export function normalizeProjectPackManifest(value) {
213
+ const safe = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
214
+ return {
215
+ schema: PROJECT_PACKS_SCHEMA,
216
+ selected: Array.isArray(safe.selected)
217
+ ? safe.selected
218
+ .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
219
+ .map((entry) => ({
220
+ id: typeof entry.id === 'string' ? entry.id : null,
221
+ kind: typeof entry.kind === 'string' ? entry.kind : 'starter-pack',
222
+ sourceMode: entry.sourceMode === 'github'
223
+ ? 'github'
224
+ : entry.sourceMode === 'workspace'
225
+ ? 'workspace'
226
+ : 'package',
227
+ }))
228
+ .filter((entry) => entry.id)
229
+ : [],
230
+ packs: Array.isArray(safe.packs)
231
+ ? safe.packs
232
+ .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
233
+ .map((entry) => ({
234
+ id: typeof entry.id === 'string' ? entry.id : null,
235
+ kind: typeof entry.kind === 'string' ? entry.kind : 'starter-pack',
236
+ packageName: typeof entry.packageName === 'string' ? entry.packageName : null,
237
+ spec: typeof entry.spec === 'string' ? entry.spec : null,
238
+ installMode: entry.installMode === 'workspace' ? 'workspace' : 'package',
239
+ sourceMode: entry.sourceMode === 'github'
240
+ ? 'github'
241
+ : entry.sourceMode === 'workspace'
242
+ ? 'workspace'
243
+ : 'package',
244
+ vendoredPaths: Array.isArray(entry.vendoredPaths) ? entry.vendoredPaths.map((item) => String(item)) : [],
245
+ }))
246
+ .filter((entry) => entry.id)
247
+ : [],
248
+ bundles: Array.isArray(safe.bundles)
249
+ ? safe.bundles
250
+ .filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
251
+ .map((entry) => ({
252
+ id: typeof entry.id === 'string' ? entry.id : null,
253
+ packageName: typeof entry.packageName === 'string' ? entry.packageName : null,
254
+ memberPackIds: Array.isArray(entry.memberPackIds) ? entry.memberPackIds.map((item) => String(item)) : [],
255
+ }))
256
+ .filter((entry) => entry.id)
257
+ : [],
258
+ };
259
+ }
@@ -77,19 +77,24 @@ function parseCliArgs(argv) {
77
77
  }
78
78
 
79
79
  function mapApplicableScenes(report, sceneThresholds = {}) {
80
- const reportSceneIds = new Set((report?.scenes || []).map((scene) => scene.id));
80
+ const scenesById = new Map((report?.scenes || []).map((scene) => [scene.id, scene]));
81
81
  return Object.entries(sceneThresholds)
82
- .filter(([sceneId]) => reportSceneIds.has(sceneId))
83
- .map(([sceneId, limits]) => ({
84
- id: sceneId,
85
- maxAvgFrameTimeMs: limits.maxAvgFrameTimeMs,
86
- maxJitterMs: limits.maxJitterMs,
87
- minFps: limits.minFps,
88
- maxP50FrameTimeMs: limits.maxP50FrameTimeMs,
89
- maxP95FrameTimeMs: limits.maxP95FrameTimeMs,
90
- maxP99FrameTimeMs: limits.maxP99FrameTimeMs,
91
- maxStutterBurstCount: limits.maxStutterBurstCount,
92
- }));
82
+ .filter(([sceneId]) => scenesById.has(sceneId))
83
+ .map(([sceneId, limits]) => {
84
+ const scene = scenesById.get(sceneId) || {};
85
+ return {
86
+ id: sceneId,
87
+ lane: typeof scene.lane === 'string' ? scene.lane : null,
88
+ surfaceTags: Array.isArray(scene.surfaceTags) ? [...scene.surfaceTags] : [],
89
+ maxAvgFrameTimeMs: limits.maxAvgFrameTimeMs,
90
+ maxJitterMs: limits.maxJitterMs,
91
+ minFps: limits.minFps,
92
+ maxP50FrameTimeMs: limits.maxP50FrameTimeMs,
93
+ maxP95FrameTimeMs: limits.maxP95FrameTimeMs,
94
+ maxP99FrameTimeMs: limits.maxP99FrameTimeMs,
95
+ maxStutterBurstCount: limits.maxStutterBurstCount,
96
+ };
97
+ });
93
98
  }
94
99
 
95
100
  function bucketFailures(failures) {