@auraindustry/aurajs 0.0.2 → 0.0.3

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.
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
 
5
5
  import { HostBinaryResolutionError, resolveGateHostBinary } from './host-binary.mjs';
@@ -7,6 +7,7 @@ import { runHeadlessTest, HeadlessTestError } from './headless-test.mjs';
7
7
 
8
8
  const NATIVE_RESULT_MARKER = '__AURA_CONFORMANCE_NATIVE__';
9
9
  const VALID_MODES = new Set(['shim', 'native', 'both']);
10
+ export const GAME_STATE_SCHEMA_VERSION = 'aurajs.game-state.v1';
10
11
  export const DEFAULT_RUNTIME_OBSERVABILITY_REPORT_PATH = '.aura/conformance/runtime-observability.json';
11
12
  export const DEFAULT_RUNTIME_INSPECTOR_SNAPSHOT_REPORT_PATH = '.aura/conformance/runtime-inspector-snapshot.json';
12
13
  export const DEFAULT_PHASER_VERTICAL_SLICE_EVIDENCE_REPORT_PATH = '.aura/conformance/phaser-vertical-slice-evidence.json';
@@ -22,6 +23,346 @@ const PHASER_VERTICAL_SLICE_PERF_BUDGETS = Object.freeze({
22
23
  maxAverageCheckMs: 800,
23
24
  }),
24
25
  });
26
+ const PHASER_VERTICAL_SLICE_REQUIRED_MODES = Object.freeze(['shim', 'native']);
27
+
28
+ export function installGameStateHooks(aura, options = {}) {
29
+ const schemaVersion = 'aurajs.game-state.v1';
30
+ const zeroFingerprint = '0'.repeat(64);
31
+ const knownTopLevel = Object.freeze(['schemaVersion', 'export', 'state']);
32
+ const knownStateSections = Object.freeze(['globals', 'camera', 'scene3d', 'physics', 'ecs', 'tilemap']);
33
+ const defaultMode = options.mode === 'native' ? 'native' : 'headless';
34
+
35
+ const isObject = (value) => (
36
+ !!value
37
+ && typeof value === 'object'
38
+ && !Array.isArray(value)
39
+ );
40
+ const cloneStable = (value) => {
41
+ if (Array.isArray(value)) {
42
+ return value.map((entry) => cloneStable(entry));
43
+ }
44
+ if (isObject(value)) {
45
+ const out = {};
46
+ for (const key of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
47
+ out[key] = cloneStable(value[key]);
48
+ }
49
+ return out;
50
+ }
51
+ return value;
52
+ };
53
+ const stableStringify = (value) => JSON.stringify(cloneStable(value));
54
+ const asFinite = (value, fallback = 0) => {
55
+ const numeric = Number(value);
56
+ return Number.isFinite(numeric) ? numeric : fallback;
57
+ };
58
+ const asInteger = (value, fallback = 0) => {
59
+ const numeric = Number(value);
60
+ if (!Number.isFinite(numeric)) return fallback;
61
+ return Math.trunc(numeric);
62
+ };
63
+ const clampNonNegativeInt = (value, fallback = 0) => {
64
+ const numeric = asInteger(value, fallback);
65
+ return numeric < 0 ? 0 : numeric;
66
+ };
67
+ const hash64 = (input) => {
68
+ const text = String(input || '');
69
+ const state = [
70
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
71
+ 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
72
+ ];
73
+ const primes = [
74
+ 0x9e3779b1, 0x85ebca77, 0xc2b2ae3d, 0x27d4eb2f,
75
+ 0x165667b1, 0xd3a2646c, 0xfd7046c5, 0xb55a4f09,
76
+ ];
77
+ for (let i = 0; i < text.length; i += 1) {
78
+ const code = text.charCodeAt(i) & 0xffff;
79
+ for (let lane = 0; lane < state.length; lane += 1) {
80
+ const mix = (code + (lane + 1) + i) >>> 0;
81
+ const rotated = ((state[(lane + 3) % state.length] >>> ((lane + i) % 13)) | 0) >>> 0;
82
+ state[lane] = (Math.imul((state[lane] ^ mix) >>> 0, primes[lane]) + rotated) >>> 0;
83
+ }
84
+ }
85
+ return state.map((entry) => entry.toString(16).padStart(8, '0')).join('');
86
+ };
87
+ const normalizeCameraState = (cameraState) => {
88
+ if (!isObject(cameraState)) return null;
89
+ const x = asFinite(cameraState.x, Number.NaN);
90
+ const y = asFinite(cameraState.y, Number.NaN);
91
+ const zoom = asFinite(cameraState.zoom, Number.NaN);
92
+ const rotation = asFinite(cameraState.rotation, Number.NaN);
93
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(zoom) || !Number.isFinite(rotation)) {
94
+ return null;
95
+ }
96
+ const normalized = {
97
+ x,
98
+ y,
99
+ zoom,
100
+ rotation,
101
+ };
102
+ if (typeof cameraState.following === 'boolean') normalized.following = cameraState.following;
103
+ if (Number.isInteger(cameraState.activeEffects) && cameraState.activeEffects >= 0) {
104
+ normalized.activeEffects = cameraState.activeEffects;
105
+ }
106
+ if (isObject(cameraState.deadzone)) {
107
+ normalized.deadzone = {
108
+ x: asFinite(cameraState.deadzone.x, 0),
109
+ y: asFinite(cameraState.deadzone.y, 0),
110
+ width: Math.max(0, asFinite(cameraState.deadzone.width, 0)),
111
+ height: Math.max(0, asFinite(cameraState.deadzone.height, 0)),
112
+ };
113
+ }
114
+ if (isObject(cameraState.bounds)) {
115
+ normalized.bounds = {
116
+ x: asFinite(cameraState.bounds.x, 0),
117
+ y: asFinite(cameraState.bounds.y, 0),
118
+ width: Math.max(0, asFinite(cameraState.bounds.width, 0)),
119
+ height: Math.max(0, asFinite(cameraState.bounds.height, 0)),
120
+ };
121
+ }
122
+ return normalized;
123
+ };
124
+ const errorResult = (reasonCode, details = {}) => ({
125
+ ok: false,
126
+ reasonCode,
127
+ appliedMutations: 0,
128
+ failedMutationIndex: 0,
129
+ fingerprint: null,
130
+ details: cloneStable(details),
131
+ });
132
+ const successResult = (reasonCode, fingerprint, appliedMutations = 1) => ({
133
+ ok: true,
134
+ reasonCode,
135
+ appliedMutations,
136
+ failedMutationIndex: null,
137
+ fingerprint,
138
+ });
139
+
140
+ if (!aura || typeof aura !== 'object') {
141
+ return errorResult('invalid_schema_payload', { path: '/aura' });
142
+ }
143
+
144
+ const existing = aura.state;
145
+ if (
146
+ isObject(existing)
147
+ && typeof existing.export === 'function'
148
+ && typeof existing.apply === 'function'
149
+ && existing.schemaVersion === schemaVersion
150
+ ) {
151
+ return existing;
152
+ }
153
+
154
+ const stateStore = {
155
+ globals: {},
156
+ camera: null,
157
+ scene3d: null,
158
+ physics: null,
159
+ ecs: null,
160
+ tilemap: null,
161
+ seed: 0,
162
+ frameIndex: 0,
163
+ elapsedSeconds: 0,
164
+ };
165
+
166
+ const captureCameraState = () => {
167
+ const cameraNs = isObject(aura.camera) ? aura.camera : null;
168
+ if (!cameraNs) return stateStore.camera ? cloneStable(stateStore.camera) : null;
169
+
170
+ let cameraState = null;
171
+ if (typeof cameraNs.getState === 'function') {
172
+ try {
173
+ cameraState = cameraNs.getState();
174
+ } catch {
175
+ cameraState = null;
176
+ }
177
+ }
178
+ if (!isObject(cameraState)) {
179
+ cameraState = {
180
+ x: cameraNs.x,
181
+ y: cameraNs.y,
182
+ zoom: cameraNs.zoom,
183
+ rotation: cameraNs.rotation,
184
+ };
185
+ }
186
+ const normalized = normalizeCameraState(cameraState);
187
+ return normalized ? cloneStable(normalized) : (stateStore.camera ? cloneStable(stateStore.camera) : null);
188
+ };
189
+
190
+ const computePayloadFingerprint = (payload) => {
191
+ const clone = cloneStable(payload);
192
+ if (!isObject(clone.export)) {
193
+ return zeroFingerprint;
194
+ }
195
+ clone.export.fingerprint = zeroFingerprint;
196
+ clone.export.capturedAt = null;
197
+ return hash64(stableStringify(clone));
198
+ };
199
+
200
+ const exportState = (exportOptions = {}) => {
201
+ const optionsObject = isObject(exportOptions) ? exportOptions : {};
202
+ if (isObject(optionsObject.globals)) {
203
+ stateStore.globals = cloneStable(optionsObject.globals);
204
+ }
205
+ if (Object.prototype.hasOwnProperty.call(optionsObject, 'seed')) {
206
+ stateStore.seed = asInteger(optionsObject.seed, stateStore.seed);
207
+ }
208
+ if (Object.prototype.hasOwnProperty.call(optionsObject, 'frameIndex')) {
209
+ stateStore.frameIndex = clampNonNegativeInt(optionsObject.frameIndex, stateStore.frameIndex);
210
+ }
211
+ if (Object.prototype.hasOwnProperty.call(optionsObject, 'elapsedSeconds')) {
212
+ stateStore.elapsedSeconds = Math.max(0, asFinite(optionsObject.elapsedSeconds, stateStore.elapsedSeconds));
213
+ }
214
+
215
+ const mode = optionsObject.mode === 'native' ? 'native' : defaultMode;
216
+ const cameraState = captureCameraState();
217
+ if (cameraState) {
218
+ stateStore.camera = cloneStable(cameraState);
219
+ }
220
+
221
+ const statePayload = {
222
+ globals: cloneStable(stateStore.globals),
223
+ };
224
+ if (stateStore.camera) statePayload.camera = cloneStable(stateStore.camera);
225
+ if (stateStore.scene3d) statePayload.scene3d = cloneStable(stateStore.scene3d);
226
+ if (stateStore.physics) statePayload.physics = cloneStable(stateStore.physics);
227
+ if (stateStore.ecs) statePayload.ecs = cloneStable(stateStore.ecs);
228
+ if (stateStore.tilemap) statePayload.tilemap = cloneStable(stateStore.tilemap);
229
+
230
+ const payload = {
231
+ schemaVersion,
232
+ export: {
233
+ mode,
234
+ seed: stateStore.seed,
235
+ frameIndex: stateStore.frameIndex,
236
+ elapsedSeconds: Number(stateStore.elapsedSeconds.toFixed(6)),
237
+ fingerprint: zeroFingerprint,
238
+ capturedAt: null,
239
+ },
240
+ state: statePayload,
241
+ };
242
+ payload.export.fingerprint = computePayloadFingerprint(payload);
243
+ return payload;
244
+ };
245
+
246
+ const validateApplyPayload = (payload) => {
247
+ if (!isObject(payload)) {
248
+ return { ok: false, reasonCode: 'invalid_schema_payload', details: { path: '/' } };
249
+ }
250
+ if (payload.schemaVersion !== schemaVersion) {
251
+ return { ok: false, reasonCode: 'schema_version_mismatch', details: { expected: schemaVersion } };
252
+ }
253
+ for (const key of knownTopLevel) {
254
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) {
255
+ return { ok: false, reasonCode: 'missing_required_field', details: { path: `/${key}` } };
256
+ }
257
+ }
258
+ for (const key of Object.keys(payload)) {
259
+ if (!knownTopLevel.includes(key)) {
260
+ return { ok: false, reasonCode: 'unknown_top_level_key', details: { key } };
261
+ }
262
+ }
263
+ if (!isObject(payload.export) || !isObject(payload.state)) {
264
+ return { ok: false, reasonCode: 'invalid_schema_payload', details: { path: '/export_or_state' } };
265
+ }
266
+ if (!isObject(payload.state.globals)) {
267
+ return { ok: false, reasonCode: 'missing_required_field', details: { path: '/state/globals' } };
268
+ }
269
+ for (const key of Object.keys(payload.state)) {
270
+ if (!knownStateSections.includes(key)) {
271
+ return { ok: false, reasonCode: 'unknown_state_section', details: { key } };
272
+ }
273
+ }
274
+ if (Object.prototype.hasOwnProperty.call(payload.state, 'camera') && payload.state.camera !== null) {
275
+ if (!normalizeCameraState(payload.state.camera)) {
276
+ return { ok: false, reasonCode: 'invalid_schema_payload', details: { path: '/state/camera' } };
277
+ }
278
+ }
279
+ for (const section of ['scene3d', 'physics', 'ecs', 'tilemap']) {
280
+ if (Object.prototype.hasOwnProperty.call(payload.state, section)) {
281
+ const value = payload.state[section];
282
+ if (value !== null && !isObject(value)) {
283
+ return { ok: false, reasonCode: 'invalid_schema_payload', details: { path: `/state/${section}` } };
284
+ }
285
+ }
286
+ }
287
+ return { ok: true };
288
+ };
289
+
290
+ const applyState = (payload, applyOptions = {}) => {
291
+ const validation = validateApplyPayload(payload);
292
+ if (!validation.ok) {
293
+ return errorResult(validation.reasonCode, validation.details || {});
294
+ }
295
+
296
+ const optionsObject = isObject(applyOptions) ? applyOptions : {};
297
+ const dryRun = optionsObject.dryRun === true;
298
+ const nextStore = {
299
+ globals: cloneStable(payload.state.globals),
300
+ camera: payload.state.camera ? cloneStable(normalizeCameraState(payload.state.camera)) : null,
301
+ scene3d: payload.state.scene3d ? cloneStable(payload.state.scene3d) : null,
302
+ physics: payload.state.physics ? cloneStable(payload.state.physics) : null,
303
+ ecs: payload.state.ecs ? cloneStable(payload.state.ecs) : null,
304
+ tilemap: payload.state.tilemap ? cloneStable(payload.state.tilemap) : null,
305
+ seed: asInteger(payload.export.seed, stateStore.seed),
306
+ frameIndex: clampNonNegativeInt(payload.export.frameIndex, stateStore.frameIndex),
307
+ elapsedSeconds: Math.max(0, asFinite(payload.export.elapsedSeconds, stateStore.elapsedSeconds)),
308
+ };
309
+
310
+ if (!dryRun) {
311
+ stateStore.globals = nextStore.globals;
312
+ stateStore.camera = nextStore.camera;
313
+ stateStore.scene3d = nextStore.scene3d;
314
+ stateStore.physics = nextStore.physics;
315
+ stateStore.ecs = nextStore.ecs;
316
+ stateStore.tilemap = nextStore.tilemap;
317
+ stateStore.seed = nextStore.seed;
318
+ stateStore.frameIndex = nextStore.frameIndex;
319
+ stateStore.elapsedSeconds = nextStore.elapsedSeconds;
320
+
321
+ const cameraNs = isObject(aura.camera) ? aura.camera : null;
322
+ if (cameraNs && stateStore.camera) {
323
+ cameraNs.x = stateStore.camera.x;
324
+ cameraNs.y = stateStore.camera.y;
325
+ cameraNs.zoom = stateStore.camera.zoom;
326
+ cameraNs.rotation = stateStore.camera.rotation;
327
+ if (stateStore.camera.deadzone && typeof cameraNs.setDeadzone === 'function') {
328
+ cameraNs.setDeadzone(stateStore.camera.deadzone);
329
+ } else if (typeof cameraNs.clearDeadzone === 'function') {
330
+ cameraNs.clearDeadzone();
331
+ }
332
+ if (stateStore.camera.bounds && typeof cameraNs.setBounds === 'function') {
333
+ cameraNs.setBounds(stateStore.camera.bounds);
334
+ } else if (typeof cameraNs.clearBounds === 'function') {
335
+ cameraNs.clearBounds();
336
+ }
337
+ }
338
+ }
339
+
340
+ const nextPayload = exportState({
341
+ mode: payload.export.mode === 'native' ? 'native' : defaultMode,
342
+ seed: nextStore.seed,
343
+ frameIndex: nextStore.frameIndex,
344
+ elapsedSeconds: nextStore.elapsedSeconds,
345
+ });
346
+
347
+ return successResult(
348
+ dryRun ? 'state_dry_run_ok' : 'state_apply_ok',
349
+ nextPayload.export.fingerprint,
350
+ dryRun ? 0 : 1,
351
+ );
352
+ };
353
+
354
+ const stateNamespace = isObject(aura.state) ? aura.state : {};
355
+ stateNamespace.schemaVersion = schemaVersion;
356
+ stateNamespace.export = exportState;
357
+ stateNamespace.apply = applyState;
358
+ aura.state = stateNamespace;
359
+ return stateNamespace;
360
+ }
361
+
362
+ export function buildGameStateHookBootstrapSource(mode = 'headless') {
363
+ const normalizedMode = mode === 'native' ? 'native' : 'headless';
364
+ return `(${installGameStateHooks.toString()})(aura, ${JSON.stringify({ mode: normalizedMode })});`;
365
+ }
25
366
 
26
367
  export class ConformanceSuiteError extends Error {
27
368
  constructor(message, details = {}) {
@@ -174,6 +515,10 @@ export const DEFAULT_CONFORMANCE_CASES = [
174
515
  { id: 'input.isGamepadConnected', expression: "typeof aura.input?.isGamepadConnected === 'function'" },
175
516
  { id: 'input.getGamepadAxis', expression: "typeof aura.input?.getGamepadAxis === 'function'" },
176
517
  { id: 'input.isGamepadButtonDown', expression: "typeof aura.input?.isGamepadButtonDown === 'function'" },
518
+ {
519
+ id: 'input.key.invalid-input.safe-false',
520
+ expression: "(() => { const invalid = [42, {}, null, undefined, ['space']]; return invalid.every((key) => aura.input.isKeyDown(key) === false && aura.input.isKeyPressed(key) === false && aura.input.isKeyReleased(key) === false); })()",
521
+ },
177
522
  ],
178
523
  frames: 1,
179
524
  source: `
@@ -198,6 +543,53 @@ export const DEFAULT_CONFORMANCE_CASES = [
198
543
  };
199
544
  `,
200
545
  },
546
+ {
547
+ id: 'rust-host-native-readiness-smoke',
548
+ modes: ['native'],
549
+ namespaces: ['lifecycle', 'input', 'draw2d', 'assets', 'audio', 'net', 'debug'],
550
+ functions: [
551
+ 'aura.setup',
552
+ 'aura.update',
553
+ 'aura.draw',
554
+ 'aura.onResize',
555
+ 'aura.input.isKeyDown',
556
+ 'aura.input.isKeyPressed',
557
+ 'aura.input.isKeyReleased',
558
+ 'aura.draw2d.clear',
559
+ 'aura.draw2d.rect',
560
+ 'aura.debug.inspectorStats',
561
+ 'aura.assets.load',
562
+ 'aura.assets.getFormatSupport',
563
+ 'aura.audio.setBusVolume',
564
+ 'aura.audio.fadeTrack',
565
+ 'aura.net.connect',
566
+ 'aura.net.websocket',
567
+ 'aura.net.fetch',
568
+ 'aura.net.get',
569
+ 'aura.net.post',
570
+ ],
571
+ nativeChecks: [
572
+ {
573
+ id: 'smoke.lifecycle-input.readiness.deterministic',
574
+ expression: "(() => { const runSample = () => { let resizeSeen = null; aura.onResize = (w, h) => { resizeSeen = [typeof w, typeof h, w, h]; }; aura.onResize(640, 360); const setupOk = typeof aura.setup === 'function'; const updateOk = typeof aura.update === 'function'; const drawOk = typeof aura.draw === 'function'; const hasInputFns = typeof aura.input?.isKeyDown === 'function' && typeof aura.input?.isKeyPressed === 'function' && typeof aura.input?.isKeyReleased === 'function'; const keySnapshot = {}; if (hasInputFns) { for (const key of ['left', 'right', 'up', 'down', 'arrowup', 'arrowdown', 'space']) { keySnapshot[key] = { down: aura.input.isKeyDown(key), pressed: aura.input.isKeyPressed(key), released: aura.input.isKeyReleased(key) }; } } const keyBooleans = Object.values(keySnapshot).every((row) => typeof row.down === 'boolean' && typeof row.pressed === 'boolean' && typeof row.released === 'boolean'); const aliasStable = hasInputFns && aura.input.isKeyDown('up') === aura.input.isKeyDown('arrowup') && aura.input.isKeyPressed('up') === aura.input.isKeyPressed('arrowup') && aura.input.isKeyReleased('up') === aura.input.isKeyReleased('arrowup'); const invalidSafe = hasInputFns && [42, {}, null, undefined, ['space']].every((key) => aura.input.isKeyDown(key) === false && aura.input.isKeyPressed(key) === false && aura.input.isKeyReleased(key) === false); return { setupOk, updateOk, drawOk, resizeSeen, hasInputFns, keyBooleans, aliasStable, invalidSafe, keySnapshot }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.setupOk === true && first.updateOk === true && first.drawOk === true && Array.isArray(first.resizeSeen) && first.resizeSeen[0] === 'number' && first.resizeSeen[1] === 'number' && first.resizeSeen[2] === 640 && first.resizeSeen[3] === 360 && first.hasInputFns === true && first.keyBooleans === true && first.aliasStable === true && first.invalidSafe === true; })()",
575
+ },
576
+ {
577
+ id: 'smoke.render.queue.readiness.deterministic',
578
+ expression: "(() => { const runSample = () => { const inspector = aura.debug?.inspectorStats; if (typeof inspector !== 'function') return null; const before = inspector(); const beforeDraw2d = Number(before?.queues?.draw2dPending); const beforeDraw3d = Number(before?.queues?.draw3dPending); const beforeOverlay = Number(before?.queues?.debugOverlayPending); const beforeNumeric = [beforeDraw2d, beforeDraw3d, beforeOverlay].every((value) => Number.isFinite(value)); let drawCallsAccepted = true; try { const white = aura.Color?.WHITE || aura.colors?.white || (typeof aura.rgba === 'function' ? aura.rgba(1, 1, 1, 1) : null); aura.draw2d.clear(0, 0, 0); aura.draw2d.rect(4, 6, 8, 10, white); aura.draw2d.rect(12, 9, 6, 4, white); } catch (_) { drawCallsAccepted = false; } const after = inspector(); const afterDraw2d = Number(after?.queues?.draw2dPending); const afterDraw3d = Number(after?.queues?.draw3dPending); const afterOverlay = Number(after?.queues?.debugOverlayPending); const afterNumeric = [afterDraw2d, afterDraw3d, afterOverlay].every((value) => Number.isFinite(value)); const draw2dDelta = beforeNumeric && afterNumeric ? (afterDraw2d - beforeDraw2d) : NaN; const draw3dDelta = beforeNumeric && afterNumeric ? (afterDraw3d - beforeDraw3d) : NaN; const overlayDelta = beforeNumeric && afterNumeric ? (afterOverlay - beforeOverlay) : NaN; return { drawCallsAccepted, beforeNumeric, afterNumeric, beforeDraw2d, afterDraw2d, draw2dDelta, draw3dDelta, overlayDelta }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.drawCallsAccepted === true && first.beforeNumeric === true && first.afterNumeric === true && Number.isFinite(first.draw2dDelta) && Number.isFinite(first.draw3dDelta) && Number.isFinite(first.overlayDelta) && first.draw2dDelta === 0 && first.draw3dDelta === 0 && first.overlayDelta === 0 && first.afterDraw2d === first.beforeDraw2d; })()",
579
+ },
580
+ {
581
+ id: 'smoke.assets-audio.reason-codes.deterministic',
582
+ expression: "(() => { const runSample = () => { const formatInvalidRaw = (() => { try { return aura.assets.getFormatSupport(42); } catch (_) { return null; } })(); const formatSupportedRaw = (() => { try { return aura.assets.getFormatSupport('native-smoke.png'); } catch (_) { return null; } })(); const loadMissing = (() => { try { const result = aura.assets.load('__missing__/native-smoke-missing.png'); return { threw: false, reasonCode: result?.reasonCode || null, message: null, taggedReason: null }; } catch (err) { const message = String(err); const match = /\\[reason:([^\\]]+)\\]/.exec(message); return { threw: true, reasonCode: null, message, taggedReason: match ? match[1] : null }; } })(); const invalidBusRaw = (() => { try { return aura.audio.setBusVolume('', 0.5); } catch (_) { return null; } })(); const invalidTrackRaw = (() => { try { return aura.audio.fadeTrack('invalid', { to: 0.2, duration: 0.1 }); } catch (_) { return null; } })(); return { formatInvalid: { ok: formatInvalidRaw?.ok === false, status: formatInvalidRaw?.status || null, reasonCode: formatInvalidRaw?.reasonCode || null }, formatSupported: { ok: !!formatSupportedRaw && formatSupportedRaw.ok === true, status: formatSupportedRaw?.status || null, reasonCode: formatSupportedRaw?.reasonCode || null }, loadMissing, invalidBus: { ok: invalidBusRaw?.ok === false, reasonCode: invalidBusRaw?.reasonCode || null }, invalidTrack: { ok: invalidTrackRaw?.ok === false, reasonCode: invalidTrackRaw?.reasonCode || null } }; }; const first = runSample(); const second = runSample(); const loadMissingOk = first.loadMissing?.threw === true && typeof first.loadMissing?.message === 'string' && first.loadMissing.message.includes('Asset not found: __missing__/native-smoke-missing.png') && (first.loadMissing.taggedReason === null || first.loadMissing.taggedReason === 'missing_asset'); return JSON.stringify(first) === JSON.stringify(second) && first.formatInvalid.ok === true && first.formatInvalid.status === 'unsupported' && first.formatInvalid.reasonCode === 'invalid_format_probe' && first.formatSupported.ok === true && first.formatSupported.status === 'supported' && first.formatSupported.reasonCode === 'asset_format_supported' && loadMissingOk && first.invalidBus.ok === true && first.invalidBus.reasonCode === 'invalid_bus' && first.invalidTrack.ok === true && first.invalidTrack.reasonCode === 'invalid_track_handle'; })()",
583
+ },
584
+ {
585
+ id: 'smoke.network.readiness.reason-codes.deterministic',
586
+ expression: "(() => { const parseReason = (msg) => { const match = /\\[reason:([^\\]]+)\\]/.exec(msg); return match ? match[1] : null; }; const classifyInvalidConnect = () => { try { aura.net.connect('tcp', '127.0.0.1', 0); return 'unexpected_no_throw'; } catch (err) { const msg = String(err); if (parseReason(msg) === 'optional_module_net_disabled') return 'optional_module_net_disabled'; if (msg.includes('port must be an integer in range 1..65535')) return 'invalid_connect_port'; return 'unexpected_error'; } }; const classifyInvalidWebSocket = () => { try { aura.net.websocket(42); return 'unexpected_no_throw'; } catch (err) { const msg = String(err); if (parseReason(msg) === 'optional_module_net_disabled') return 'optional_module_net_disabled'; if (msg.includes('url must be a websocket URL string')) return 'invalid_websocket_url'; return 'unexpected_error'; } }; const classifyInvalidFetch = () => { try { aura.net.fetch(42); return 'unexpected_no_throw'; } catch (err) { const msg = String(err); if (parseReason(msg) === 'optional_module_net_disabled') return 'optional_module_net_disabled'; if (msg.includes('url must be a string') || msg.includes('url must be an HTTP URL string')) return 'invalid_fetch_url'; return 'unexpected_error'; } }; const classifyRuntimeMode = () => { try { const handle = aura.net.connect('tcp', '127.0.0.1', 7777); const handleLike = !!handle && typeof handle === 'object' && typeof handle.send === 'function' && typeof handle.close === 'function'; if (handleLike) { try { handle.close(); } catch (_) {} return 'enabled_runtime'; } return 'enabled_nonstandard'; } catch (err) { const msg = String(err); if (parseReason(msg) === 'optional_module_net_disabled') return 'disabled'; return 'enabled_runtime_error'; } }; const runSample = () => { const methodsOk = typeof aura.net?.connect === 'function' && typeof aura.net?.websocket === 'function' && typeof aura.net?.fetch === 'function' && typeof aura.net?.get === 'function' && typeof aura.net?.post === 'function'; const mode = classifyRuntimeMode(); const connectReason = classifyInvalidConnect(); const websocketReason = classifyInvalidWebSocket(); const fetchReason = classifyInvalidFetch(); const consistency = mode === 'disabled' ? (connectReason === 'optional_module_net_disabled' && websocketReason === 'optional_module_net_disabled' && fetchReason === 'optional_module_net_disabled') : (connectReason === 'invalid_connect_port' && websocketReason === 'invalid_websocket_url' && fetchReason === 'invalid_fetch_url'); return { methodsOk, mode, connectReason, websocketReason, fetchReason, consistency }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.methodsOk === true && first.consistency === true && ['disabled', 'enabled_runtime', 'enabled_runtime_error', 'enabled_nonstandard'].includes(first.mode); })()",
587
+ },
588
+ ],
589
+ nativeFrames: 1,
590
+ frames: 1,
591
+ source: `aura.setup = function () {};`,
592
+ },
201
593
  {
202
594
  id: 'draw-audio-assets-storage',
203
595
  namespaces: ['draw2d', 'audio', 'assets', 'storage'],
@@ -293,16 +685,20 @@ export const DEFAULT_CONFORMANCE_CASES = [
293
685
  },
294
686
  {
295
687
  id: 'assets.streaming.lifecycle.preload.deterministic',
296
- expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const normalizePreload = (entry) => ({ ok: !!entry && entry.ok === true, reasonCode: reasonOf(entry), requested: Number(entry?.requested || 0), loaded: Number(entry?.loaded || 0), failed: Number(entry?.failed || 0), failureReasonCodes: Array.isArray(entry?.failures) ? entry.failures.map((row) => reasonOf(row) || '').join('|') : '' }); const normalizeState = (entry) => entry ? { state: entry.state || null, reasonCode: reasonOf(entry) } : null; const normalizeHistory = (rows) => Array.isArray(rows) ? rows.map((row) => `${row.state || ''}:${reasonOf(row) || ''}`).join('|') : ''; const runSample = (tag) => { const missing2d = `__missing__/stream-2d-${tag}.png`; const missing3d = `__missing__/stream-3d-${tag}.glb`; const preload2d = aura.assets.preload2d([missing2d]); const preload3d = aura.assets.preload3d([missing3d]); const state2d = aura.assets.getState(missing2d); const state3d = aura.assets.getState(missing3d); const history2d = aura.assets.getStateHistory(missing2d); const history3d = aura.assets.getStateHistory(missing3d); return { preload2d: normalizePreload(preload2d), preload3d: normalizePreload(preload3d), state2d: normalizeState(state2d), state3d: normalizeState(state3d), history2d: normalizeHistory(history2d), history3d: normalizeHistory(history3d) }; }; const first = runSample('a'); const second = runSample('b'); return JSON.stringify(first) === JSON.stringify(second) && first.preload2d.reasonCode === 'assets_preload_partial_failure' && first.preload2d.requested === 1 && first.preload2d.failed === 1 && first.preload2d.failureReasonCodes === 'missing_asset' && first.preload3d.reasonCode === 'assets_preload_partial_failure' && first.preload3d.requested === 1 && first.preload3d.failed === 1 && first.preload3d.failureReasonCodes === 'asset_format_deferred' && first.state2d?.state === 'failed' && first.state2d?.reasonCode === 'missing_asset' && first.state3d?.state === 'failed' && first.state3d?.reasonCode === 'asset_format_deferred' && first.history2d.startsWith('queued:') && first.history2d.includes('|loading:') && first.history2d.includes('|failed:') && first.history3d.startsWith('queued:') && first.history3d.includes('|loading:') && first.history3d.includes('|failed:asset_format_deferred'); })()",
688
+ expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const normalizePreload = (entry) => ({ ok: !!entry && entry.ok === true, reasonCode: reasonOf(entry), requested: Number(entry?.requested || 0), loaded: Number(entry?.loaded || 0), failed: Number(entry?.failed || 0), failureReasonCodes: Array.isArray(entry?.failures) ? entry.failures.map((row) => reasonOf(row) || '').join('|') : '' }); const normalizeState = (entry) => entry ? { state: entry.state || null, reasonCode: reasonOf(entry) } : null; const normalizeHistory = (rows) => Array.isArray(rows) ? rows.map((row) => `${row.state || ''}:${reasonOf(row) || ''}`).join('|') : ''; const runSample = (tag) => { const missing2d = `__missing__/stream-2d-${tag}.png`; const missing3d = `__missing__/stream-3d-${tag}.glb`; const preload2d = aura.assets.preload2d([missing2d]); const preload3d = aura.assets.preload3d([missing3d]); const state2d = aura.assets.getState(missing2d); const state3d = aura.assets.getState(missing3d); const history2d = aura.assets.getStateHistory(missing2d); const history3d = aura.assets.getStateHistory(missing3d); return { preload2d: normalizePreload(preload2d), preload3d: normalizePreload(preload3d), state2d: normalizeState(state2d), state3d: normalizeState(state3d), history2d: normalizeHistory(history2d), history3d: normalizeHistory(history3d) }; }; const first = runSample('a'); const second = runSample('b'); return JSON.stringify(first) === JSON.stringify(second) && first.preload2d.reasonCode === 'assets_preload_partial_failure' && first.preload2d.requested === 1 && first.preload2d.failed === 1 && first.preload2d.failureReasonCodes === 'missing_asset' && first.preload3d.reasonCode === 'assets_preload_partial_failure' && first.preload3d.requested === 1 && first.preload3d.failed === 1 && first.preload3d.failureReasonCodes === 'missing_asset' && first.state2d?.state === 'failed' && first.state2d?.reasonCode === 'missing_asset' && first.state3d?.state === 'failed' && first.state3d?.reasonCode === 'missing_asset' && first.history2d.startsWith('queued:') && first.history2d.includes('|loading:') && first.history2d.includes('|failed:') && first.history3d.startsWith('queued:') && first.history3d.includes('|loading:') && first.history3d.includes('|failed:missing_asset'); })()",
297
689
  },
298
690
  {
299
691
  id: 'assets.deferred-format.reason-codes.deterministic',
300
- expression: "(() => { const norm = (entry) => entry ? { ok: entry.ok === true, status: entry.status || null, reasonCode: entry.reasonCode || null, extension: entry.extension || null, kindHint: entry.kindHint || null, supported: entry.supported === true, deferred: entry.deferred === true } : null; const runSample = () => { const supported = norm(aura.assets.getFormatSupport('atlas.png')); const deferredImage = norm(aura.assets.getFormatSupport('future.webp')); const deferredFont = norm(aura.assets.getFormatSupport('future.woff2')); const deferredModel = norm(aura.assets.getFormatSupport('future.glb')); const unsupported = norm(aura.assets.getFormatSupport('blob.xyz')); const invalidRaw = aura.assets.getFormatSupport(42); const invalid = invalidRaw ? { ok: invalidRaw.ok === true, status: invalidRaw.status || null, reasonCode: invalidRaw.reasonCode || null } : null; const loadReason = (() => { try { aura.assets.load('future.webp'); } catch (err) { return String(err); } return ''; })(); const audioReason = (() => { try { aura.audio.play('future.aac'); } catch (err) { return String(err); } return ''; })(); const fontResult = aura.assets.loadFont('future.woff2'); return { supported, deferredImage, deferredFont, deferredModel, unsupported, invalid, loadReason, audioReason, fontReason: fontResult?.reason || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.supported?.ok === true && first.supported?.status === 'supported' && first.supported?.reasonCode === 'asset_format_supported' && first.supported?.supported === true && first.supported?.deferred === false && first.deferredImage?.ok === true && first.deferredImage?.status === 'deferred' && first.deferredImage?.reasonCode === 'asset_format_deferred' && first.deferredImage?.deferred === true && first.deferredFont?.status === 'deferred' && first.deferredModel?.status === 'deferred' && first.unsupported?.status === 'unsupported' && first.unsupported?.reasonCode === 'asset_format_unsupported' && first.invalid?.ok === false && first.invalid?.status === 'unsupported' && first.invalid?.reasonCode === 'invalid_format_probe' && first.loadReason.includes('[reason:asset_format_deferred]') && first.audioReason.includes('[reason:asset_format_deferred]') && first.fontReason === 'asset_format_deferred'; })()",
692
+ expression: "(() => { const norm = (entry) => entry ? { ok: entry.ok === true, status: entry.status || null, reasonCode: entry.reasonCode || null, extension: entry.extension || null, kindHint: entry.kindHint || null, supported: entry.supported === true, deferred: entry.deferred === true } : null; const runSample = () => { const supported = norm(aura.assets.getFormatSupport('atlas.png')); const promotedImage = norm(aura.assets.getFormatSupport('future.webp')); const deferredImage = norm(aura.assets.getFormatSupport('future.bmp')); const supportedWoff2 = norm(aura.assets.getFormatSupport('future.woff2')); const deferredFont = norm(aura.assets.getFormatSupport('future.woff')); const promotedModel = norm(aura.assets.getFormatSupport('future.glb')); const unsupported = norm(aura.assets.getFormatSupport('blob.xyz')); const invalidRaw = aura.assets.getFormatSupport(42); const invalid = invalidRaw ? { ok: invalidRaw.ok === true, status: invalidRaw.status || null, reasonCode: invalidRaw.reasonCode || null } : null; const loadReason = (() => { try { aura.assets.load('future.bmp'); } catch (err) { return String(err); } return ''; })(); const audioReason = (() => { try { aura.audio.play('future.aac'); } catch (err) { return String(err); } return ''; })(); const fontResult = aura.assets.loadFont('future.woff2'); return { supported, promotedImage, deferredImage, supportedWoff2, deferredFont, promotedModel, unsupported, invalid, loadReason, audioReason, fontReason: fontResult?.reason || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.supported?.ok === true && first.supported?.status === 'supported' && first.supported?.reasonCode === 'asset_format_supported' && first.supported?.supported === true && first.supported?.deferred === false && first.promotedImage?.ok === true && first.promotedImage?.status === 'supported' && first.promotedImage?.reasonCode === 'asset_format_supported' && first.promotedImage?.deferred === false && first.deferredImage?.ok === true && first.deferredImage?.status === 'unsupported' && first.deferredImage?.reasonCode === 'asset_format_unsupported' && first.deferredImage?.deferred === false && first.supportedWoff2?.status === 'supported' && first.supportedWoff2?.reasonCode === 'asset_format_supported' && first.supportedWoff2?.deferred === false && first.deferredFont?.status === 'unsupported' && first.deferredFont?.reasonCode === 'asset_format_unsupported' && first.deferredFont?.deferred === false && first.promotedModel?.status === 'supported' && first.promotedModel?.reasonCode === 'asset_format_supported' && first.unsupported?.status === 'unsupported' && first.unsupported?.reasonCode === 'asset_format_unsupported' && first.invalid?.ok === false && first.invalid?.status === 'unsupported' && first.invalid?.reasonCode === 'invalid_format_probe' && first.loadReason.includes('[reason:asset_format_unsupported]') && first.audioReason.includes('[reason:asset_format_unsupported]') && first.fontReason === 'asset_not_found'; })()",
301
693
  },
302
694
  {
303
695
  id: 'assets.streaming.cache-policy.reason-codes.stable',
304
696
  expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const sample = () => { const invalidPaths = aura.assets.preload2d('bad'); const invalidPath = aura.assets.preload3d(['../bad.glb']); const invalidMaxEntries = aura.assets.setCachePolicy({ maxEntries: 0 }); const invalidMaxBytes = aura.assets.setCachePolicy({ maxBytes: 0 }); const invalidMode = aura.assets.setCachePolicy({ evictionMode: 'fifo' }); const updated = aura.assets.setCachePolicy({ maxEntries: 4, maxBytes: null, evictionMode: 'oldest' }); const policy = aura.assets.getCachePolicy(); const missingEvict = aura.assets.evict('__missing__/ghost.png'); const invalidEvict = aura.assets.evict('../ghost.png'); return { invalidPaths: reasonOf(invalidPaths), invalidPath: reasonOf(invalidPath), invalidMaxEntries: reasonOf(invalidMaxEntries), invalidMaxBytes: reasonOf(invalidMaxBytes), invalidMode: reasonOf(invalidMode), updatedOk: !!updated && updated.ok === true, updatedReason: reasonOf(updated), policyMaxEntries: policy?.maxEntries, policyMaxBytes: policy?.maxBytes ?? null, policyEvictionMode: policy?.evictionMode, missingEvict: reasonOf(missingEvict), invalidEvict: reasonOf(invalidEvict) }; }; const first = sample(); const second = sample(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPaths === 'invalid_preload_paths' && first.invalidPath === 'invalid_preload_path' && first.invalidMaxEntries === 'invalid_max_entries' && first.invalidMaxBytes === 'invalid_max_bytes' && first.invalidMode === 'invalid_eviction_mode' && first.updatedOk === true && first.updatedReason === 'cache_policy_updated' && first.policyMaxEntries === 4 && first.policyMaxBytes === null && first.policyEvictionMode === 'oldest' && first.missingEvict === 'missing_asset' && first.invalidEvict === 'invalid_path'; })()",
305
697
  },
698
+ {
699
+ id: 'audio.invalid-input.no-throw.reason-codes',
700
+ expression: "(() => { const runSample = () => { let threw = false; try { aura.audio.stop(-1); aura.audio.pause('x'); aura.audio.resume(99999); aura.audio.setVolume('x', 0.5); } catch (_) { threw = true; } const invalidBus = aura.audio.setBusVolume('', 0.5); const invalidTrackHandle = aura.audio.fadeTrack('oops', { to: 0.2, duration: 0.5 }); const missingBus = aura.audio.fadeBus('missing', { to: 0.2, duration: 0.5 }); const invalidCrossfade = aura.audio.crossfade('oops', 'bad', { duration: 0.5 }); return { threw, invalidBus: invalidBus?.reasonCode || null, invalidTrackHandle: invalidTrackHandle?.reasonCode || null, missingBus: missingBus?.reasonCode || null, invalidCrossfade: invalidCrossfade?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.threw === false && first.invalidBus === 'invalid_bus' && first.invalidTrackHandle === 'invalid_track_handle' && first.missingBus === 'missing_bus' && typeof first.invalidCrossfade === 'string' && first.invalidCrossfade.length > 0; })()",
701
+ },
306
702
  { id: 'storage.save', expression: "typeof aura.storage?.save === 'function'" },
307
703
  { id: 'storage.load', expression: "typeof aura.storage?.load === 'function'" },
308
704
  { id: 'storage.delete', expression: "typeof aura.storage?.delete === 'function'" },
@@ -356,6 +752,49 @@ export const DEFAULT_CONFORMANCE_CASES = [
356
752
  };
357
753
  `,
358
754
  },
755
+ {
756
+ id: 'compute-runtime-native',
757
+ modes: ['native'],
758
+ namespaces: ['compute'],
759
+ functions: [
760
+ 'aura.compute.createPipeline',
761
+ 'aura.compute.createBuffer',
762
+ 'aura.compute.writeBuffer',
763
+ 'aura.compute.createBindGroup',
764
+ 'aura.compute.dispatch',
765
+ 'aura.compute.readBuffer',
766
+ 'aura.compute.destroyPipeline',
767
+ 'aura.compute.destroyBuffer',
768
+ 'aura.compute.destroyBindGroup',
769
+ 'aura.compute.getError',
770
+ ],
771
+ nativeChecks: [
772
+ {
773
+ id: 'compute.surface.methods',
774
+ expression: "(() => ['createPipeline','createBuffer','writeBuffer','createBindGroup','dispatch','readBuffer','destroyPipeline','destroyBuffer','destroyBindGroup','getError'].every((name) => typeof aura.compute?.[name] === 'function'))()",
775
+ },
776
+ {
777
+ id: 'compute.dispatch.readback.queued',
778
+ expression: "(() => { const shader = '@group(0) @binding(0) var<storage, read_write> values: array<f32>;\\n@compute @workgroup_size(1)\\nfn main() {\\n values[0] = values[0] + 1.0;\\n}'; const pipeline = aura.compute.createPipeline(shader); const buffer = aura.compute.createBuffer(4, 'storage-read'); aura.compute.writeBuffer(buffer, new Float32Array([2])); const bindGroup = aura.compute.createBindGroup(pipeline, [{ binding: 0, buffer }]); aura.compute.dispatch(pipeline, bindGroup, 1, 1, 1); const queued = aura.compute.readBuffer(buffer, 0, 4); globalThis.__computeNative = { pipeline, buffer, bindGroup, queuedWasNull: queued === null }; return Number.isInteger(pipeline) && pipeline > 0 && Number.isInteger(buffer) && buffer > 0 && Number.isInteger(bindGroup) && bindGroup > 0 && queued === null && aura.compute.getError(pipeline) === null && aura.compute.getError(bindGroup) === null && aura.compute.getError(buffer) === null; })()",
779
+ },
780
+ ],
781
+ nativePostChecks: [
782
+ {
783
+ id: 'compute.dispatch.readback.deterministic',
784
+ expression: "(() => { const state = globalThis.__computeNative || {}; const result = aura.compute.readBuffer(state.buffer); const second = aura.compute.readBuffer(state.buffer); const values = ArrayBuffer.isView(result) ? Array.from(result).map((value) => Number(value.toFixed(6))) : []; const pipelineError = aura.compute.getError(state.pipeline); const bindGroupError = aura.compute.getError(state.bindGroup); const bufferError = aura.compute.getError(state.buffer); aura.compute.destroyBindGroup(state.bindGroup); aura.compute.destroyPipeline(state.pipeline); aura.compute.destroyBuffer(state.buffer); return state.queuedWasNull === true && result instanceof Float32Array && result.length === 1 && values.length === 1 && values[0] === 3 && second === null && pipelineError === null && bindGroupError === null && bufferError === null; })()",
785
+ },
786
+ ],
787
+ nativeFrames: 2,
788
+ frames: 1,
789
+ source: `
790
+ globalThis.__computeNative = {
791
+ pipeline: 0,
792
+ buffer: 0,
793
+ bindGroup: 0,
794
+ queuedWasNull: false,
795
+ };
796
+ `,
797
+ },
359
798
  {
360
799
  id: 'camera-2d-runtime-surface',
361
800
  modes: ['native'],
@@ -473,7 +912,8 @@ export const DEFAULT_CONFORMANCE_CASES = [
473
912
  },
474
913
  {
475
914
  id: 'debug.inspector.stats-shape',
476
- expression: "(() => { try { const enabled = aura.debug.enableInspector(true); const stats = aura.debug.inspectorStats(); const disabled = aura.debug.enableInspector(false); const phase2 = stats.phase2 || {}; const rows = [phase2.animation, phase2.tilemap, phase2.scene3d, phase2.particles]; const phase2Ok = rows.every((row) => row && Number.isFinite(row.active) && Number.isFinite(row.total) && Number.isFinite(row.callbackQueueDepth) && row.active >= 0 && row.total >= 0 && row.callbackQueueDepth >= 0); const scene3dRuntime = stats.scene3dRuntime || {}; const submission = scene3dRuntime.submission || {}; const camera = scene3dRuntime.camera || {}; const resources = scene3dRuntime.resources || {}; const cameraVectorsOk = Number.isFinite(camera.position?.x) && Number.isFinite(camera.position?.y) && Number.isFinite(camera.position?.z) && Number.isFinite(camera.target?.x) && Number.isFinite(camera.target?.y) && Number.isFinite(camera.target?.z); const scene3dRuntimeOk = Number.isFinite(submission.drawMeshPending) && Number.isFinite(submission.uniqueMeshHandles) && Number.isFinite(submission.uniqueMaterialHandles) && Number.isFinite(submission.nonDefaultTransforms) && typeof submission.skyboxRequested === 'boolean' && typeof submission.clearRequested === 'boolean' && typeof camera.mode === 'string' && camera.mode.length > 0 && camera.mode !== 'unknown' && Number.isFinite(camera.aspect) && Number.isFinite(camera.fovDegrees) && Number.isFinite(camera.near) && Number.isFinite(camera.far) && cameraVectorsOk && Number.isFinite(resources.meshHandlesRegistered) && Number.isFinite(resources.meshUploadsPending) && Number.isFinite(resources.materialHandlesKnown) && Number.isFinite(resources.materialCommandsPending) && Number.isFinite(resources.lightPointCount) && typeof resources.lightDirectionalActive === 'boolean'; return enabled === true && disabled === false && Number.isFinite(stats.frameCount) && Number.isFinite(stats.frame.fps) && Number.isFinite(stats.frame.deltaSeconds) && Number.isFinite(stats.frame.elapsedSeconds) && Number.isFinite(stats.window.width) && Number.isFinite(stats.window.height) && Number.isFinite(stats.window.pixelRatio) && Number.isFinite(stats.queues.draw2dPending) && Number.isFinite(stats.queues.draw3dPending) && Number.isFinite(stats.queues.debugOverlayPending) && phase2Ok && scene3dRuntimeOk; } catch (_) { return false; } })()",
915
+ expression: "(() => { try { const enabled = aura.debug.enableInspector(true); const stats = aura.debug.inspectorStats(); const disabled = aura.debug.enableInspector(false); const phase2 = stats.phase2 || {}; const rows = [phase2.animation, phase2.tilemap, phase2.scene3d, phase2.particles]; const phase2Ok = rows.every((row) => row && Number.isFinite(row.active) && Number.isFinite(row.total) && Number.isFinite(row.callbackQueueDepth) && row.active >= 0 && row.total >= 0 && row.callbackQueueDepth >= 0); const scene3dRuntime = stats.scene3dRuntime || {}; const submission = scene3dRuntime.submission || {}; const camera = scene3dRuntime.camera || {}; const resources = scene3dRuntime.resources || {}; const cameraVectorsOk = Number.isFinite(camera.position?.x) && Number.isFinite(camera.position?.y) && Number.isFinite(camera.position?.z) && Number.isFinite(camera.target?.x) && Number.isFinite(camera.target?.y) && Number.isFinite(camera.target?.z); const scene3dRuntimeOk = Number.isFinite(submission.drawMeshPending) && Number.isFinite(submission.uniqueMeshHandles) && Number.isFinite(submission.uniqueMaterialHandles) && Number.isFinite(submission.nonDefaultTransforms) && typeof submission.skyboxRequested === 'boolean' && typeof submission.clearRequested === 'boolean' && typeof camera.mode === 'string' && camera.mode.length > 0 && Number.isFinite(camera.aspect) && Number.isFinite(camera.fovDegrees) && Number.isFinite(camera.near) && Number.isFinite(camera.far) && cameraVectorsOk && Number.isFinite(resources.meshHandlesRegistered) && Number.isFinite(resources.meshUploadsPending) && Number.isFinite(resources.materialHandlesKnown) && Number.isFinite(resources.materialCommandsPending) && Number.isFinite(resources.lightPointCount) && typeof resources.lightDirectionalActive === 'boolean'; return enabled === true && disabled === false && Number.isFinite(stats.frameCount) && Number.isFinite(stats.frame.fps) && Number.isFinite(stats.frame.deltaSeconds) && Number.isFinite(stats.frame.elapsedSeconds) && Number.isFinite(stats.window.width) && Number.isFinite(stats.window.height) && Number.isFinite(stats.window.pixelRatio) && Number.isFinite(stats.queues.draw2dPending) && Number.isFinite(stats.queues.draw3dPending) && Number.isFinite(stats.queues.debugOverlayPending) && phase2Ok && scene3dRuntimeOk; } catch (_) { return false; } })()",
916
+ debugExpression: "(() => { try { const enabled = aura.debug.enableInspector(true); const stats = aura.debug.inspectorStats(); const disabled = aura.debug.enableInspector(false); const phase2 = stats.phase2 || {}; const rows = [phase2.animation, phase2.tilemap, phase2.scene3d, phase2.particles]; const phase2Rows = rows.map((row) => ({ present: !!row, active: row?.active, total: row?.total, callbackQueueDepth: row?.callbackQueueDepth })); const phase2Ok = rows.every((row) => row && Number.isFinite(row.active) && Number.isFinite(row.total) && Number.isFinite(row.callbackQueueDepth) && row.active >= 0 && row.total >= 0 && row.callbackQueueDepth >= 0); const scene3dRuntime = stats.scene3dRuntime || {}; const submission = scene3dRuntime.submission || {}; const camera = scene3dRuntime.camera || {}; const resources = scene3dRuntime.resources || {}; const cameraVectorsOk = Number.isFinite(camera.position?.x) && Number.isFinite(camera.position?.y) && Number.isFinite(camera.position?.z) && Number.isFinite(camera.target?.x) && Number.isFinite(camera.target?.y) && Number.isFinite(camera.target?.z); const scene3dRuntimeOk = Number.isFinite(submission.drawMeshPending) && Number.isFinite(submission.uniqueMeshHandles) && Number.isFinite(submission.uniqueMaterialHandles) && Number.isFinite(submission.nonDefaultTransforms) && typeof submission.skyboxRequested === 'boolean' && typeof submission.clearRequested === 'boolean' && typeof submission.environmentMapConfigured === 'boolean' && Number.isFinite(submission.pointShadowLightCount) && Number.isFinite(submission.spotShadowLightCount) && Number.isFinite(submission.pointShadowPassCount) && Number.isFinite(submission.spotShadowPassCount) && typeof camera.mode === 'string' && camera.mode.length > 0 && Number.isFinite(camera.aspect) && Number.isFinite(camera.fovDegrees) && Number.isFinite(camera.near) && Number.isFinite(camera.far) && cameraVectorsOk && Number.isFinite(resources.meshHandlesRegistered) && Number.isFinite(resources.meshUploadsPending) && Number.isFinite(resources.materialHandlesKnown) && Number.isFinite(resources.materialCommandsPending) && Number.isFinite(resources.lightPointCount) && Number.isFinite(resources.lightSpotCount) && typeof resources.lightDirectionalActive === 'boolean'; return { enabled, disabled, frameCount: stats?.frameCount, frame: stats?.frame || null, window: stats?.window || null, queues: stats?.queues || null, phase2Ok, phase2Rows, scene3dRuntimeOk, cameraMode: camera?.mode, cameraAspect: camera?.aspect, cameraVectorsOk, submission, resources }; } catch (error) { return { traceError: String(error && error.message ? error.message : error) }; } })()",
477
917
  },
478
918
  {
479
919
  id: 'debug.inspector.scene3d-animation-shape',
@@ -512,6 +952,10 @@ export const DEFAULT_CONFORMANCE_CASES = [
512
952
  id: 'audio.mixer.surface.methods',
513
953
  expression: "(() => ['setBusVolume','assignBus','fadeTrack','fadeBus','crossfade','update','clearEnvelopes','getMixerState'].every((name) => typeof aura.audio?.[name] === 'function'))()",
514
954
  },
955
+ {
956
+ id: 'audio.mixer.reason-codes.deterministic',
957
+ expression: "(() => { const runSample = () => { const invalidBus = aura.audio.setBusVolume('', 0.5); const invalidTrackHandle = aura.audio.fadeTrack('oops', { to: 0.2, duration: 0.5 }); const missingTrack = aura.audio.fadeTrack(999999, { to: 0.2, duration: 0.5 }); const missingBus = aura.audio.fadeBus('missing', { to: 0.2, duration: 0.5 }); const invalidDt = aura.audio.update(0); return { invalidBus: invalidBus?.reasonCode || null, invalidTrackHandle: invalidTrackHandle?.reasonCode || null, missingTrack: missingTrack?.reasonCode || null, missingBus: missingBus?.reasonCode || null, invalidDt: invalidDt?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidBus === 'invalid_bus' && first.invalidTrackHandle === 'invalid_track_handle' && first.missingTrack === 'missing_track' && first.missingBus === 'missing_bus' && first.invalidDt === 'invalid_dt'; })()",
958
+ },
515
959
  ],
516
960
  frames: 1,
517
961
  nativeFrames: 1,
@@ -620,6 +1064,10 @@ export const DEFAULT_CONFORMANCE_CASES = [
620
1064
  { id: 'tilemap.queryRay', expression: "typeof aura.tilemap?.queryRay === 'function'" },
621
1065
  { id: 'tilemap.queryRaycast', expression: "typeof aura.tilemap?.queryRaycast === 'function'" },
622
1066
  { id: 'tilemap.query.invalid-handle.reason-code', expression: "(() => { const result = aura.tilemap.queryPoint(999999, { x: 0, y: 0 }); return result && result.ok === false && result.reasonCode === 'invalid_map_handle'; })()" },
1067
+ {
1068
+ id: 'tilemap.query.reason-codes.expanded',
1069
+ expression: "(() => { const runSample = () => { const fixture = { width: 2, height: 2, tilewidth: 16, tileheight: 16, layers: [ { name: 'ground', type: 'tilelayer', width: 2, height: 2, data: [1, 0, 0, 0], properties: { solid: true } } ], tilesets: [ { firstgid: 1, image: 'tiles.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const invalidPoint = aura.tilemap.queryPoint(mapId, { x: 'bad', y: 0 }); const invalidAabb = aura.tilemap.queryAABB(mapId, { x: 0, y: 0, w: 0, h: 1 }); const invalidRay = aura.tilemap.queryRay(mapId, { x: 0, y: 0, dx: 0, dy: 0, maxDistance: 10 }); const invalidModel = aura.tilemap.queryPoint({ width: 4 }, 1, 1); const invalidHandle = aura.tilemap.queryPoint(999999, { x: 0, y: 0 }); let threw = false; try { aura.tilemap.queryRay(mapId, { x: 0, y: 0, dx: 'bad', dy: 0, maxDistance: 10 }); } catch (_) { threw = true; } aura.tilemap.unload(mapId); return { invalidPoint: invalidPoint?.reasonCode || null, invalidAabb: invalidAabb?.reasonCode || null, invalidRay: invalidRay?.reasonCode || null, invalidModel: invalidModel?.reasonCode || null, invalidHandle: invalidHandle?.reasonCode || null, threw }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPoint === 'invalid_point_args' && first.invalidAabb === 'invalid_aabb_args' && first.invalidRay === 'invalid_ray_args' && first.invalidModel === 'invalid_model' && first.invalidHandle === 'invalid_map_handle' && first.threw === false; })()",
1070
+ },
623
1071
  {
624
1072
  id: 'tilemap.query.model-schema.deterministic',
625
1073
  expression: "(() => { const runSample = () => { const model = { width: 2, height: 2, tilewidth: 16, tileheight: 16, solidLayerNames: ['solid'], layers: [ { name: 'solid', width: 2, height: 2, data: [1, 0, 0, 0], properties: { solid: true } }, { name: 'dynamic', width: 2, height: 2, data: [0, 1, 0, 0], queryEnabled: true } ], solidCells: [ { x: 1, y: 0, layerIndex: 5, layerName: 'manual' }, { x: 1, y: 0, layerIndex: 5, layerName: 'manual' } ] }; const point = aura.tilemap.queryPoint(model, { x: 16.5, y: 0.5 }); const aabb = aura.tilemap.queryAABB(model, { x: 0, y: 0, w: 32, h: 16 }); const ray = aura.tilemap.queryRay(model, { x: 0.5, y: 0.5, dx: 1, dy: 0, maxDistance: 64 }); return { pointLayers: Array.isArray(point?.hits) ? point.hits.map((hit) => hit.layerIndex).join(',') : '', aabbLayers: Array.isArray(aabb?.hits) ? aabb.hits.map((hit) => hit.layerIndex).join(',') : '', rayLayer: Number(ray?.hitCell?.layerIndex ?? -1), pointOk: point?.ok === true, aabbOk: aabb?.ok === true, rayOk: ray?.ok === true, pointHit: point?.hit === true, aabbHit: aabb?.hit === true, rayHit: ray?.hit === true, invalidModelReason: aura.tilemap.queryPoint({ width: 2 }, { x: 0, y: 0 })?.reasonCode, invalidAabbReason: aura.tilemap.queryAABB(model, { x: 0, y: 0, w: 0, h: 1 })?.reasonCode }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.pointOk === true && first.aabbOk === true && first.rayOk === true && first.pointHit === true && first.aabbHit === true && first.rayHit === true && first.pointLayers === '1,5' && first.aabbLayers === '0,1,5' && first.rayLayer === 0 && first.invalidModelReason === 'invalid_model' && first.invalidAabbReason === 'invalid_aabb_args'; })()",
@@ -698,20 +1146,30 @@ export const DEFAULT_CONFORMANCE_CASES = [
698
1146
  modes: ['shim', 'native'],
699
1147
  namespaces: ['tilemap'],
700
1148
  functions: [
1149
+ 'aura.tilemap.setTile',
701
1150
  'aura.tilemap.setRegion',
702
1151
  'aura.tilemap.removeRegion',
703
1152
  'aura.tilemap.replaceRegion',
1153
+ 'aura.tilemap.setTileCollision',
704
1154
  'aura.tilemap.setLayerFlags',
705
1155
  'aura.tilemap.setLayerVisibility',
706
1156
  'aura.tilemap.setLayerCollision',
1157
+ 'aura.tilemap.queryObjects',
1158
+ 'aura.tilemap.queryObjectsAtPoint',
1159
+ 'aura.tilemap.queryObjectsInAABB',
707
1160
  ],
708
1161
  nativeChecks: [
1162
+ { id: 'tilemap.mutation.setTile', expression: "typeof aura.tilemap?.setTile === 'function'" },
709
1163
  { id: 'tilemap.mutation.setRegion', expression: "typeof aura.tilemap?.setRegion === 'function'" },
710
1164
  { id: 'tilemap.mutation.removeRegion', expression: "typeof aura.tilemap?.removeRegion === 'function'" },
711
1165
  { id: 'tilemap.mutation.replaceRegion', expression: "typeof aura.tilemap?.replaceRegion === 'function'" },
1166
+ { id: 'tilemap.mutation.setTileCollision', expression: "typeof aura.tilemap?.setTileCollision === 'function'" },
712
1167
  { id: 'tilemap.layer.setLayerFlags', expression: "typeof aura.tilemap?.setLayerFlags === 'function'" },
713
1168
  { id: 'tilemap.layer.setLayerVisibility', expression: "typeof aura.tilemap?.setLayerVisibility === 'function'" },
714
1169
  { id: 'tilemap.layer.setLayerCollision', expression: "typeof aura.tilemap?.setLayerCollision === 'function'" },
1170
+ { id: 'tilemap.query.queryObjects', expression: "typeof aura.tilemap?.queryObjects === 'function'" },
1171
+ { id: 'tilemap.query.queryObjectsAtPoint', expression: "typeof aura.tilemap?.queryObjectsAtPoint === 'function'" },
1172
+ { id: 'tilemap.query.queryObjectsInAABB', expression: "typeof aura.tilemap?.queryObjectsInAABB === 'function'" },
715
1173
  {
716
1174
  id: 'tilemap.mutation.reason-codes.expanded',
717
1175
  expression: "(() => { const runSample = () => { const fixture = { width: 2, height: 2, tilewidth: 16, tileheight: 16, layers: [ { name: 'ground', type: 'tilelayer', width: 2, height: 2, data: [1, 1, 1, 1] } ], tilesets: [ { firstgid: 1, image: 'tiles-a.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 }, { firstgid: 5, image: 'tiles-b.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const invalidGidArgs = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 'bad'); const unmappedBoundary = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 2); const validBoundary = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 5); const invalidReplaceArgs = aura.tilemap.replaceRegion(mapId, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 1, 'bad'); const invalidLayerFlags = aura.tilemap.setLayerFlags(mapId, 'ground', {}); const invalidVisibility = aura.tilemap.setLayerVisibility(mapId, 'ground', 'yes'); const invalidCollision = aura.tilemap.setLayerCollision(mapId, 'ground', 'yes'); const invalidLayer = aura.tilemap.removeRegion(mapId, 'missing', { x: 0, y: 0, w: 1, h: 1 }); const invalidHandle = aura.tilemap.setRegion(999999, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 1); aura.tilemap.unload(mapId); return { invalidGidArgs: invalidGidArgs?.reasonCode, unmappedBoundary: unmappedBoundary?.reasonCode, validBoundaryOk: validBoundary?.ok === true, validBoundaryChanged: Number(validBoundary?.changedTiles || 0), invalidReplaceArgs: invalidReplaceArgs?.reasonCode, invalidLayerFlags: invalidLayerFlags?.reasonCode, invalidVisibility: invalidVisibility?.reasonCode, invalidCollision: invalidCollision?.reasonCode, invalidLayer: invalidLayer?.reasonCode, invalidHandle: invalidHandle?.reasonCode }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidGidArgs === 'invalid_gid_args' && first.unmappedBoundary === 'unmapped_layer_gid' && first.validBoundaryOk === true && first.validBoundaryChanged === 1 && first.invalidReplaceArgs === 'invalid_replace_args' && first.invalidLayerFlags === 'invalid_layer_flags' && first.invalidVisibility === 'invalid_visibility_flag' && first.invalidCollision === 'invalid_collision_flag' && first.invalidLayer === 'invalid_layer_ref' && first.invalidHandle === 'invalid_map_handle'; })()",
@@ -720,6 +1178,10 @@ export const DEFAULT_CONFORMANCE_CASES = [
720
1178
  id: 'tilemap.mutation.layer-controls.deterministic',
721
1179
  expression: "(() => { const runSample = () => { const fixture = { width: 4, height: 4, tilewidth: 16, tileheight: 16, solidLayerNames: ['ground', 'hazards'], layers: [ { name: 'ground', type: 'tilelayer', width: 4, height: 4, data: Array.from({ length: 16 }, () => 1) }, { name: 'hazards', type: 'tilelayer', width: 4, height: 4, data: [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] } ], tilesets: [ { firstgid: 1, image: 'tiles.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const initial = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const setRegion = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 2, h: 1 }, 0); const topRow = aura.tilemap.queryAABB(mapId, { x: 0, y: 0, w: 32, h: 16 }); const removeRegion = aura.tilemap.removeRegion(mapId, 'hazards', { x: 1, y: 1, w: 1, h: 1 }); const afterRemove = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const replaceRegion = aura.tilemap.replaceRegion(mapId, 'ground', { x: 1, y: 1, w: 1, h: 1 }, 1, 0); const afterReplace = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const collisionOff = aura.tilemap.setLayerCollision(mapId, 'ground', false); const hazardsPoint = aura.tilemap.queryPoint(mapId, { x: 32.5, y: 16.5 }); const visibilityOff = aura.tilemap.setLayerVisibility(mapId, 'hazards', false); const hiddenDraw = aura.tilemap.draw(mapId, { cull: false }); const flags = aura.tilemap.setLayerFlags(mapId, 'hazards', { visible: true, collision: false }); const shownDraw = aura.tilemap.draw(mapId, { cull: false }); const queryMuted = aura.tilemap.queryPoint(mapId, { x: 32.5, y: 16.5 }); const groundCollisionOn = aura.tilemap.setLayerFlags(mapId, 'ground', { collision: true }); const hazardsCollisionOn = aura.tilemap.setLayerCollision(mapId, 'hazards', true); const queryRestored = aura.tilemap.queryPoint(mapId, { x: 32.5, y: 16.5 }); const invalidRegion = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 0, h: 1 }, 1); const invalidGid = aura.tilemap.setRegion(mapId, 'ground', { x: 0, y: 0, w: 1, h: 1 }, 999); const invalidFlags = aura.tilemap.setLayerFlags(mapId, 'ground', { collision: 'yes' }); const invalidLayer = aura.tilemap.setLayerVisibility(mapId, 'missing', true); aura.tilemap.unload(mapId); return { initialHits: Array.isArray(initial?.hits) ? initial.hits.length : -1, topRowHits: Array.isArray(topRow?.hits) ? topRow.hits.length : -1, afterRemoveHits: Array.isArray(afterRemove?.hits) ? afterRemove.hits.length : -1, afterReplaceHits: Array.isArray(afterReplace?.hits) ? afterReplace.hits.length : -1, hazardsHits: Array.isArray(hazardsPoint?.hits) ? hazardsPoint.hits.length : -1, queryMutedHits: Array.isArray(queryMuted?.hits) ? queryMuted.hits.length : -1, queryRestoredHits: Array.isArray(queryRestored?.hits) ? queryRestored.hits.length : -1, setChanged: setRegion?.changedTiles, removeChanged: removeRegion?.changedTiles, replaceChanged: replaceRegion?.changedTiles, collisionOff: collisionOff?.collision, visibilityOff: visibilityOff?.visible, hiddenOrder: hiddenDraw?.layerOrder?.join(','), shownOrder: shownDraw?.layerOrder?.join(','), flagsVisible: flags?.visible, flagsCollision: flags?.collision, groundCollisionOn: groundCollisionOn?.collision, hazardsCollisionOn: hazardsCollisionOn?.collision, invalidRegion: invalidRegion?.reasonCode, invalidGid: invalidGid?.reasonCode, invalidFlags: invalidFlags?.reasonCode, invalidLayer: invalidLayer?.reasonCode }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.initialHits === 2 && first.topRowHits === 0 && first.afterRemoveHits === 1 && first.afterReplaceHits === 0 && first.hazardsHits === 1 && first.queryMutedHits === 0 && first.queryRestoredHits === 2 && first.setChanged === 2 && first.removeChanged === 1 && first.replaceChanged === 1 && first.collisionOff === false && first.visibilityOff === false && first.hiddenOrder === 'ground' && first.shownOrder === 'ground,hazards' && first.flagsVisible === true && first.flagsCollision === false && first.groundCollisionOn === true && first.hazardsCollisionOn === true && first.invalidRegion === 'invalid_region_args' && first.invalidGid === 'unmapped_layer_gid' && first.invalidFlags === 'invalid_collision_flag' && first.invalidLayer === 'invalid_layer_ref'; })()",
722
1180
  },
1181
+ {
1182
+ id: 'tilemap.object-query-and-tile-mutation.expanded.deterministic',
1183
+ expression: "(() => { const runSample = () => { const fixture = { width: 4, height: 4, tilewidth: 16, tileheight: 16, layers: [ { name: 'ground', type: 'tilelayer', width: 4, height: 4, data: [1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], properties: { solid: true } }, { name: 'objects-main', type: 'objectgroup', objects: [ { id: 1, name: 'spawn', type: 'spawn', class: 'player', x: 16, y: 16, point: true, properties: { team: 'blue' } }, { id: 2, name: 'coin', type: 'pickup', x: 32, y: 16, width: 16, height: 16, properties: { kind: 'coin', value: 5 } }, { id: 3, name: 'secret', type: 'trigger', x: 48, y: 16, width: 16, height: 16, visible: false, properties: { kind: 'secret' } } ] }, { name: 'objects-props', type: 'objectgroup', objects: [ { id: 5, name: 'barrel', type: 'prop', class: 'breakable', x: 16, y: 32, width: 16, height: 16, properties: { kind: 'destructible', hp: 3 } } ] } ], tilesets: [ { firstgid: 1, image: 'tiles.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const all = aura.tilemap.queryObjects(mapId); const allRepeat = aura.tilemap.queryObjects(mapId); const withHidden = aura.tilemap.queryObjects(mapId, { includeHidden: true }); const byLayer = aura.tilemap.queryObjects(mapId, { layer: 'objects-main' }); const byProperty = aura.tilemap.queryObjects(mapId, { property: { name: 'kind', value: 'coin' } }); const byProperties = aura.tilemap.queryObjects(mapId, { properties: { kind: 'destructible', hp: 3 } }); const pointObjects = aura.tilemap.queryObjectsAtPoint(mapId, { x: 32.5, y: 16.5 }); const pointRepeat = aura.tilemap.queryObjectsAtPoint(mapId, { x: 32.5, y: 16.5 }); const aabbObjects = aura.tilemap.queryObjectsInAABB(mapId, { x: 0, y: 0, w: 64, h: 32 }); const invalidLayer = aura.tilemap.queryObjects(mapId, { layer: 'missing' }); const invalidProperty = aura.tilemap.queryObjects(mapId, { property: { name: '' } }); const invalidPoint = aura.tilemap.queryObjectsAtPoint(mapId, { x: 'bad', y: 0 }); const invalidAabb = aura.tilemap.queryObjectsInAABB(mapId, { x: 0, y: 0, w: 0, h: 1 }); const beforePoint = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const setTileClear = aura.tilemap.setTile(mapId, 'ground', { x: 1, y: 1 }, 0); const afterClearPoint = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const setTileRestore = aura.tilemap.setTile(mapId, 'ground', 1, 1, 1); const afterRestorePoint = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const collisionOff = aura.tilemap.setTileCollision(mapId, 'ground', { x: 1, y: 1 }, false); const afterCollisionOff = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const collisionOn = aura.tilemap.setTileCollision(mapId, 'ground', 1, 1, true); const afterCollisionOn = aura.tilemap.queryPoint(mapId, { x: 16.5, y: 16.5 }); const collisionRegion = aura.tilemap.setTileCollision(mapId, 'ground', 0, 0, 2, 1, false); const topRow = aura.tilemap.queryAABB(mapId, { x: 0, y: 0, w: 32, h: 16 }); const invalidSetTile = aura.tilemap.setTile(mapId, 'ground', { x: 1, y: 1 }, 'bad'); const outOfBoundsSetTile = aura.tilemap.setTile(mapId, 'ground', { x: 99, y: 99 }, 1); const invalidSetTileCollision = aura.tilemap.setTileCollision(mapId, 'ground', { x: 1, y: 1 }, 'yes'); aura.tilemap.unload(mapId); return { allNames: Array.isArray(all?.hits) ? all.hits.map((entry) => entry.name).join(',') : '', allRepeatNames: Array.isArray(allRepeat?.hits) ? allRepeat.hits.map((entry) => entry.name).join(',') : '', hiddenNames: Array.isArray(withHidden?.hits) ? withHidden.hits.map((entry) => entry.name).join(',') : '', layerNames: Array.isArray(byLayer?.hits) ? byLayer.hits.map((entry) => entry.name).join(',') : '', propertyNames: Array.isArray(byProperty?.hits) ? byProperty.hits.map((entry) => entry.name).join(',') : '', propertiesNames: Array.isArray(byProperties?.hits) ? byProperties.hits.map((entry) => entry.name).join(',') : '', pointNames: Array.isArray(pointObjects?.hits) ? pointObjects.hits.map((entry) => entry.name).join(',') : '', pointRepeatNames: Array.isArray(pointRepeat?.hits) ? pointRepeat.hits.map((entry) => entry.name).join(',') : '', aabbCount: Number(aabbObjects?.count || 0), invalidLayer: invalidLayer?.reasonCode || null, invalidProperty: invalidProperty?.reasonCode || null, invalidPoint: invalidPoint?.reasonCode || null, invalidAabb: invalidAabb?.reasonCode || null, beforeHits: Array.isArray(beforePoint?.hits) ? beforePoint.hits.length : -1, afterClearHits: Array.isArray(afterClearPoint?.hits) ? afterClearPoint.hits.length : -1, afterRestoreHits: Array.isArray(afterRestorePoint?.hits) ? afterRestorePoint.hits.length : -1, afterCollisionOffHits: Array.isArray(afterCollisionOff?.hits) ? afterCollisionOff.hits.length : -1, afterCollisionOnHits: Array.isArray(afterCollisionOn?.hits) ? afterCollisionOn.hits.length : -1, topRowHits: Array.isArray(topRow?.hits) ? topRow.hits.length : -1, setTileClearChanged: Number(setTileClear?.changedTiles || 0), setTileRestoreChanged: Number(setTileRestore?.changedTiles || 0), collisionOffChanged: Number(collisionOff?.changedTiles || 0), collisionOnChanged: Number(collisionOn?.changedTiles || 0), collisionRegionChanged: Number(collisionRegion?.changedTiles || 0), invalidSetTile: invalidSetTile?.reasonCode || null, outOfBoundsSetTile: outOfBoundsSetTile?.reasonCode || null, invalidSetTileCollision: invalidSetTileCollision?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.allNames === 'spawn,coin,barrel' && first.allNames === first.allRepeatNames && first.hiddenNames === 'spawn,coin,secret,barrel' && first.layerNames === 'spawn,coin' && first.propertyNames === 'coin' && first.propertiesNames === 'barrel' && first.pointNames === 'coin' && first.pointNames === first.pointRepeatNames && first.aabbCount === 2 && first.invalidLayer === 'invalid_object_layer_ref' && first.invalidProperty === 'invalid_object_property_filter' && first.invalidPoint === 'invalid_object_point_args' && first.invalidAabb === 'invalid_object_aabb_args' && first.beforeHits === 1 && first.afterClearHits === 0 && first.afterRestoreHits === 1 && first.afterCollisionOffHits === 0 && first.afterCollisionOnHits === 1 && first.topRowHits === 0 && first.setTileClearChanged === 1 && first.setTileRestoreChanged === 1 && first.collisionOffChanged === 1 && first.collisionOnChanged === 1 && first.collisionRegionChanged === 2 && first.invalidSetTile === 'invalid_gid_args' && first.outOfBoundsSetTile === 'tile_out_of_bounds' && first.invalidSetTileCollision === 'invalid_collision_flag'; })()",
1184
+ },
723
1185
  ],
724
1186
  frames: 1,
725
1187
  source: `
@@ -869,7 +1331,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
869
1331
  id: 'optional-module-net-disabled',
870
1332
  modes: ['native'],
871
1333
  namespaces: ['net'],
872
- functions: ['aura.net.connect', 'aura.net.websocket'],
1334
+ functions: ['aura.net.connect', 'aura.net.websocket', 'aura.net.fetch', 'aura.net.get', 'aura.net.post'],
873
1335
  optionalModuleReadiness: { module: 'net', state: 'disabled' },
874
1336
  nativeEnv: {
875
1337
  AURA_MODULE_PHYSICS: '0',
@@ -882,7 +1344,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
882
1344
  },
883
1345
  {
884
1346
  id: 'optional.net.disabled.reason-codes',
885
- expression: "(() => { const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.net.' + method + '()') && msg.includes('optional module \"net\" is disabled') && msg.includes('modules.network = true') && msg.includes('[reason:optional_module_net_disabled]'); } }; return reasonCode('connect', () => aura.net.connect('tcp', 'localhost', 7777)) && reasonCode('websocket', () => aura.net.websocket('ws://localhost:7777')); })()",
1347
+ expression: "(() => { const parseReason = (msg) => { const match = /\\[reason:([^\\]]+)\\]/.exec(msg); return match ? match[1] : null; }; const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.net.' + method + '()') && msg.includes('optional module \"net\" is disabled') && parseReason(msg) === 'optional_module_net_disabled'; } }; return reasonCode('connect', () => aura.net.connect('tcp', 'localhost', 7777)) && reasonCode('websocket', () => aura.net.websocket('ws://localhost:7777')) && reasonCode('fetch', () => aura.net.fetch('http://localhost:7777')) && reasonCode('get', () => aura.net.get('http://localhost:7777')) && reasonCode('post', () => aura.net.post('http://localhost:7777', { json: { ok: true } })); })()",
886
1348
  },
887
1349
  ],
888
1350
  nativeFrames: 2,
@@ -892,7 +1354,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
892
1354
  id: 'optional-module-net-enabled',
893
1355
  modes: ['native'],
894
1356
  namespaces: ['net'],
895
- functions: ['aura.net.connect', 'aura.net.websocket'],
1357
+ functions: ['aura.net.connect', 'aura.net.websocket', 'aura.net.fetch', 'aura.net.get', 'aura.net.post'],
896
1358
  optionalModuleReadiness: { module: 'net', state: 'enabled' },
897
1359
  nativeEnv: {
898
1360
  AURA_MODULE_PHYSICS: '0',
@@ -905,7 +1367,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
905
1367
  },
906
1368
  {
907
1369
  id: 'optional.net.enabled.invalid-input.reason-codes',
908
- expression: "(() => { const expectThrow = (fn, token) => { try { fn(); return false; } catch (e) { return String(e).includes(token); } }; return expectThrow(() => aura.net.connect('http', '127.0.0.1', 7777), 'transport must be \"tcp\" or \"udp\"') && expectThrow(() => aura.net.connect('tcp', 123, 7777), 'host must be a string') && expectThrow(() => aura.net.connect('tcp', '127.0.0.1', 0), 'port must be an integer in range 1..65535') && expectThrow(() => aura.net.websocket(42), 'url must be a websocket URL string'); })()",
1370
+ expression: "(() => { const hasToken = (msg, tokens) => tokens.some((token) => msg.includes(token)); const expectThrow = (fn, tokens) => { try { fn(); return false; } catch (e) { const msg = String(e); return hasToken(msg, Array.isArray(tokens) ? tokens : [tokens]); } }; return expectThrow(() => aura.net.connect('http', '127.0.0.1', 7777), 'transport must be \"tcp\" or \"udp\"') && expectThrow(() => aura.net.connect('tcp', 123, 7777), 'host must be a string') && expectThrow(() => aura.net.connect('tcp', '127.0.0.1', 0), 'port must be an integer in range 1..65535') && expectThrow(() => aura.net.websocket(42), 'url must be a websocket URL string') && expectThrow(() => aura.net.fetch(42), ['url must be a string', 'url must be an HTTP URL string']) && expectThrow(() => aura.net.get(42), ['url must be a string', 'url must be an HTTP URL string']) && expectThrow(() => aura.net.post(42, { ok: true }), ['url must be a string', 'url must be an HTTP URL string']); })()",
909
1371
  },
910
1372
  {
911
1373
  id: 'optional.net.enabled.callback-ordering.seeded',
@@ -926,7 +1388,33 @@ export const DEFAULT_CONFORMANCE_CASES = [
926
1388
  id: 'optional-module-multiplayer-disabled',
927
1389
  modes: ['native'],
928
1390
  namespaces: ['multiplayer'],
929
- functions: ['aura.multiplayer.configure', 'aura.multiplayer.host', 'aura.multiplayer.join', 'aura.multiplayer.leave', 'aura.multiplayer.stop'],
1391
+ functions: [
1392
+ 'aura.multiplayer.configure',
1393
+ 'aura.multiplayer.host',
1394
+ 'aura.multiplayer.join',
1395
+ 'aura.multiplayer.leave',
1396
+ 'aura.multiplayer.stop',
1397
+ 'aura.multiplayer.getPlayers',
1398
+ 'aura.multiplayer.getPlayerCount',
1399
+ 'aura.multiplayer.send',
1400
+ 'aura.multiplayer.broadcast',
1401
+ 'aura.multiplayer.sendInput',
1402
+ 'aura.multiplayer.getPlayerInput',
1403
+ 'aura.multiplayer.getAllPlayerInputs',
1404
+ 'aura.multiplayer.setState',
1405
+ 'aura.multiplayer.getState',
1406
+ 'aura.multiplayer.getAllState',
1407
+ 'aura.multiplayer.onMessage',
1408
+ 'aura.multiplayer.onStateUpdate',
1409
+ 'aura.multiplayer.onPlayerJoin',
1410
+ 'aura.multiplayer.onPlayerLeave',
1411
+ 'aura.multiplayer.onDisconnect',
1412
+ 'aura.multiplayer.kick',
1413
+ 'aura.multiplayer.setPlayerData',
1414
+ 'aura.multiplayer.getPlayerData',
1415
+ 'aura.multiplayer.advertise',
1416
+ 'aura.multiplayer.discover',
1417
+ ],
930
1418
  optionalModuleReadiness: { module: 'multiplayer', state: 'disabled' },
931
1419
  nativeEnv: {
932
1420
  AURA_MODULE_PHYSICS: '0',
@@ -936,11 +1424,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
936
1424
  nativeChecks: [
937
1425
  {
938
1426
  id: 'optional.multiplayer.disabled.guidance',
939
- expression: "(() => { const guidance = (fn) => { try { fn(); return false; } catch (e) { const msg = String(e); return msg.includes('disabled') && msg.includes('modules.multiplayer'); } }; return guidance(() => aura.multiplayer.configure({ tickRate: 20 })) && guidance(() => aura.multiplayer.host(7000)) && guidance(() => aura.multiplayer.join('127.0.0.1', 7000)) && guidance(() => aura.multiplayer.leave()) && guidance(() => aura.multiplayer.stop()); })()",
1427
+ expression: "(() => { const guidance = (fn) => { try { fn(); return false; } catch (e) { const msg = String(e); return msg.includes('disabled') && msg.includes('modules.multiplayer'); } }; return guidance(() => aura.multiplayer.configure({ tickRate: 20 })) && guidance(() => aura.multiplayer.host(7000)) && guidance(() => aura.multiplayer.join('127.0.0.1', 7000)) && guidance(() => aura.multiplayer.leave()) && guidance(() => aura.multiplayer.stop()) && guidance(() => aura.multiplayer.getPlayers()) && guidance(() => aura.multiplayer.send(1, 'chat', { body: 'hi' })) && guidance(() => aura.multiplayer.broadcast('chat', { body: 'hi' })) && guidance(() => aura.multiplayer.sendInput({ left: true })) && guidance(() => aura.multiplayer.getPlayerInput(1)) && guidance(() => aura.multiplayer.getAllPlayerInputs()) && guidance(() => aura.multiplayer.setState('room', { phase: 'lobby' })) && guidance(() => aura.multiplayer.getState('room')) && guidance(() => aura.multiplayer.getAllState()) && guidance(() => aura.multiplayer.onMessage('chat', () => {})) && guidance(() => aura.multiplayer.onStateUpdate(() => {})) && guidance(() => aura.multiplayer.onPlayerJoin(() => {})) && guidance(() => aura.multiplayer.onPlayerLeave(() => {})) && guidance(() => aura.multiplayer.onDisconnect(() => {})) && guidance(() => aura.multiplayer.kick(1, 'afk')) && guidance(() => aura.multiplayer.setPlayerData(1, 'team', 'blue')) && guidance(() => aura.multiplayer.getPlayerData(1, 'team')) && guidance(() => aura.multiplayer.advertise({ name: 'Room' })) && guidance(() => aura.multiplayer.discover(7001, () => {})); })()",
940
1428
  },
941
1429
  {
942
1430
  id: 'optional.multiplayer.disabled.reason-codes',
943
- expression: "(() => { const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.multiplayer.' + method + '()') && msg.includes('optional module \"multiplayer\" is disabled') && msg.includes('modules.multiplayer = true') && msg.includes('[reason:optional_module_multiplayer_disabled]'); } }; return reasonCode('configure', () => aura.multiplayer.configure({ tickRate: 20 })) && reasonCode('host', () => aura.multiplayer.host(7000)) && reasonCode('join', () => aura.multiplayer.join('127.0.0.1', 7000)); })()",
1431
+ expression: "(() => { const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.multiplayer.' + method + '()') && msg.includes('optional module \"multiplayer\" is disabled') && msg.includes('modules.multiplayer = true') && msg.includes('[reason:optional_module_multiplayer_disabled]'); } }; return reasonCode('configure', () => aura.multiplayer.configure({ tickRate: 20 })) && reasonCode('host', () => aura.multiplayer.host(7000)) && reasonCode('join', () => aura.multiplayer.join('127.0.0.1', 7000)) && reasonCode('send', () => aura.multiplayer.send(1, 'chat', { body: 'hi' })) && reasonCode('broadcast', () => aura.multiplayer.broadcast('chat', { body: 'hi' })) && reasonCode('sendInput', () => aura.multiplayer.sendInput({ left: true })) && reasonCode('getPlayerInput', () => aura.multiplayer.getPlayerInput(1)) && reasonCode('getAllPlayerInputs', () => aura.multiplayer.getAllPlayerInputs()) && reasonCode('setState', () => aura.multiplayer.setState('room', { phase: 'lobby' })) && reasonCode('getState', () => aura.multiplayer.getState('room')) && reasonCode('getAllState', () => aura.multiplayer.getAllState()) && reasonCode('onMessage', () => aura.multiplayer.onMessage('chat', () => {})) && reasonCode('onStateUpdate', () => aura.multiplayer.onStateUpdate(() => {})) && reasonCode('onPlayerJoin', () => aura.multiplayer.onPlayerJoin(() => {})) && reasonCode('onPlayerLeave', () => aura.multiplayer.onPlayerLeave(() => {})) && reasonCode('onDisconnect', () => aura.multiplayer.onDisconnect(() => {})) && reasonCode('kick', () => aura.multiplayer.kick(1, 'afk')) && reasonCode('setPlayerData', () => aura.multiplayer.setPlayerData(1, 'team', 'blue')) && reasonCode('getPlayerData', () => aura.multiplayer.getPlayerData(1, 'team')) && reasonCode('advertise', () => aura.multiplayer.advertise({ name: 'Room' })) && reasonCode('discover', () => aura.multiplayer.discover(7001, () => {})); })()",
944
1432
  },
945
1433
  ],
946
1434
  nativeFrames: 2,
@@ -950,7 +1438,39 @@ export const DEFAULT_CONFORMANCE_CASES = [
950
1438
  id: 'optional-module-multiplayer-enabled',
951
1439
  modes: ['native'],
952
1440
  namespaces: ['multiplayer'],
953
- functions: ['aura.multiplayer.configure', 'aura.multiplayer.host', 'aura.multiplayer.join', 'aura.multiplayer.leave', 'aura.multiplayer.stop'],
1441
+ functions: [
1442
+ 'aura.multiplayer.configure',
1443
+ 'aura.multiplayer.host',
1444
+ 'aura.multiplayer.join',
1445
+ 'aura.multiplayer.leave',
1446
+ 'aura.multiplayer.stop',
1447
+ 'aura.multiplayer.isHost',
1448
+ 'aura.multiplayer.isClient',
1449
+ 'aura.multiplayer.isConnected',
1450
+ 'aura.multiplayer.getLocalId',
1451
+ 'aura.multiplayer.getPlayers',
1452
+ 'aura.multiplayer.getPlayerCount',
1453
+ 'aura.multiplayer.getPing',
1454
+ 'aura.multiplayer.getServerTime',
1455
+ 'aura.multiplayer.sendInput',
1456
+ 'aura.multiplayer.getPlayerInput',
1457
+ 'aura.multiplayer.getAllPlayerInputs',
1458
+ 'aura.multiplayer.send',
1459
+ 'aura.multiplayer.broadcast',
1460
+ 'aura.multiplayer.onMessage',
1461
+ 'aura.multiplayer.kick',
1462
+ 'aura.multiplayer.setPlayerData',
1463
+ 'aura.multiplayer.getPlayerData',
1464
+ 'aura.multiplayer.advertise',
1465
+ 'aura.multiplayer.discover',
1466
+ 'aura.multiplayer.setState',
1467
+ 'aura.multiplayer.getState',
1468
+ 'aura.multiplayer.getAllState',
1469
+ 'aura.multiplayer.onStateUpdate',
1470
+ 'aura.multiplayer.onPlayerJoin',
1471
+ 'aura.multiplayer.onPlayerLeave',
1472
+ 'aura.multiplayer.onDisconnect',
1473
+ ],
954
1474
  optionalModuleReadiness: { module: 'multiplayer', state: 'enabled' },
955
1475
  nativeEnv: {
956
1476
  AURA_MODULE_PHYSICS: '0',
@@ -960,11 +1480,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
960
1480
  nativeChecks: [
961
1481
  {
962
1482
  id: 'optional.multiplayer.enabled.runtime',
963
- expression: "(() => { try { const api = aura.multiplayer; if (!api || typeof api !== 'object') return false; const methods = ['configure', 'host', 'join', 'leave', 'stop']; if (!methods.every((name) => typeof api[name] === 'function')) return false; const notDisabled = (invoke) => { try { invoke(); return true; } catch (e) { const msg = String(e); return !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('modules.multiplayer = true') && !msg.includes('modules.multiplayer') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !msg.includes('not implemented yet'); } }; return notDisabled(() => api.configure({ tickRate: 20 })) && notDisabled(() => api.host(7000)) && notDisabled(() => api.join('127.0.0.1', 7000)) && notDisabled(() => api.leave()) && notDisabled(() => api.stop()); } catch (_) { return false; } })()",
1483
+ expression: "(() => { try { const api = aura.multiplayer; if (!api || typeof api !== 'object') return false; const methods = ['configure', 'host', 'join', 'leave', 'stop', 'isHost', 'isClient', 'isConnected', 'getLocalId', 'getPlayers', 'getPlayerCount', 'getPing', 'getServerTime', 'sendInput', 'getPlayerInput', 'getAllPlayerInputs', 'send', 'broadcast', 'onMessage', 'kick', 'setPlayerData', 'getPlayerData', 'advertise', 'discover', 'setState', 'getState', 'getAllState', 'onStateUpdate', 'onPlayerJoin', 'onPlayerLeave', 'onDisconnect']; if (!methods.every((name) => typeof api[name] === 'function')) return false; const notDisabledOrPlaceholder = (invoke, validate = () => true) => { try { const value = invoke(); return validate(value); } catch (e) { const msg = String(e); const lower = msg.toLowerCase(); return !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !lower.includes('not implemented yet') && !lower.includes('placeholder'); } }; const messageEvents = []; const stateEvents = []; const joinEvents = []; const leaveEvents = []; const disconnectEvents = []; const callbackRegisterOk = api.onMessage('chat', (sender, data) => { messageEvents.push(`${sender}:${(data && data.body) || ''}`); }) === true && api.onStateUpdate((snapshot) => { stateEvents.push(Object.keys(snapshot || {}).length); }) === true && api.onPlayerJoin((player) => { joinEvents.push(player && player.id); }) === true && api.onPlayerLeave((player) => { leaveEvents.push(player && player.id); }) === true && api.onDisconnect((reason) => { disconnectEvents.push(reason); }) === true; const status = { isHost: api.isHost(), isClient: api.isClient(), isConnected: api.isConnected(), localId: api.getLocalId(), players: api.getPlayers(), playerCount: api.getPlayerCount(), ping: api.getPing(), serverTime: api.getServerTime(), playerInput: api.getPlayerInput(1), allInputs: api.getAllPlayerInputs(), state: api.getState('room'), allState: api.getAllState(), playerData: api.getPlayerData(1, 'team') }; const defaultsValid = typeof status.isHost === 'boolean' && typeof status.isClient === 'boolean' && typeof status.isConnected === 'boolean' && (status.localId === null || Number.isFinite(status.localId)) && Array.isArray(status.players) && Number.isFinite(status.playerCount) && (status.ping === null || Number.isFinite(status.ping)) && (status.serverTime === null || Number.isFinite(status.serverTime)) && (status.playerInput === null || typeof status.playerInput === 'object') && (status.allInputs === null || typeof status.allInputs === 'object') && status.allState && typeof status.allState === 'object' && (status.playerData === null || typeof status.playerData === 'object' || typeof status.playerData === 'string' || typeof status.playerData === 'number' || typeof status.playerData === 'boolean') && (status.state === null || typeof status.state === 'object' || typeof status.state === 'string' || typeof status.state === 'number' || typeof status.state === 'boolean'); const sendInputStatus = notDisabledOrPlaceholder(() => api.sendInput({ left: true }), (value) => typeof value === 'boolean'); const sendStatus = notDisabledOrPlaceholder(() => api.send(1, 'chat', { body: 'hi' }), (value) => typeof value === 'boolean'); const broadcastStatus = notDisabledOrPlaceholder(() => api.broadcast('chat', { body: 'hi' }), (value) => typeof value === 'boolean'); const setStateStatus = notDisabledOrPlaceholder(() => api.setState('room', { phase: 'lobby' }), (value) => typeof value === 'boolean'); const kickStatus = notDisabledOrPlaceholder(() => api.kick(1, 'afk'), (value) => typeof value === 'boolean'); const setPlayerDataStatus = notDisabledOrPlaceholder(() => api.setPlayerData(1, 'team', 'blue'), (value) => typeof value === 'boolean'); const advertiseStatus = notDisabledOrPlaceholder(() => api.advertise({ name: 'Room' }), (value) => typeof value === 'boolean'); const discoverStatus = notDisabledOrPlaceholder(() => api.discover(7001, () => {}), (value) => value === true); const configureStatus = notDisabledOrPlaceholder(() => api.configure({ tickRate: 20, maxPlayers: 4 }), (value) => value === true); const hostStatus = notDisabledOrPlaceholder(() => api.host(7000), (value) => typeof value === 'boolean'); const joinStatus = notDisabledOrPlaceholder(() => api.join('127.0.0.1', 7000), (value) => typeof value === 'boolean'); const leaveStatus = notDisabledOrPlaceholder(() => api.leave(), (value) => typeof value === 'boolean'); const stopStatus = notDisabledOrPlaceholder(() => api.stop(), (value) => typeof value === 'boolean'); return callbackRegisterOk && defaultsValid && configureStatus && sendInputStatus && sendStatus && broadcastStatus && setStateStatus && kickStatus && setPlayerDataStatus && advertiseStatus && discoverStatus && hostStatus && joinStatus && leaveStatus && stopStatus && messageEvents.length === 0 && stateEvents.length === 0 && joinEvents.length === 0 && leaveEvents.length === 0 && disconnectEvents.length === 0; } catch (_) { return false; } })()",
964
1484
  },
965
1485
  {
966
- id: 'optional.multiplayer.enabled.invalid-state.reason-code',
967
- expression: "(() => { try { aura.multiplayer.leave(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.multiplayer.leave() failed [reason:invalid_state]') && !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !msg.includes('not implemented yet'); } })()",
1486
+ id: 'optional.multiplayer.enabled.invalid-input.reason-codes',
1487
+ expression: "(() => { const expectThrow = (fn, token) => { try { fn(); return false; } catch (e) { const msg = String(e); const lower = msg.toLowerCase(); return msg.includes(token) && !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !lower.includes('not implemented yet') && !lower.includes('placeholder'); } }; return expectThrow(() => aura.multiplayer.leave(), 'aura.multiplayer.leave() failed [reason:invalid_state]') && expectThrow(() => aura.multiplayer.stop(), 'aura.multiplayer.stop() failed [reason:invalid_state]') && expectThrow(() => aura.multiplayer.getPlayerInput('bad'), 'aura.multiplayer.getPlayerInput(playerId): playerId must be a positive integer') && expectThrow(() => aura.multiplayer.send('bad', 'chat', {}), 'aura.multiplayer.send(targetId, type, data): targetId must be a positive integer') && expectThrow(() => aura.multiplayer.send(1, '', {}), 'aura.multiplayer.send(targetId, type, data): type must be a non-empty string') && expectThrow(() => aura.multiplayer.broadcast('', {}), 'aura.multiplayer.broadcast(type, data): type must be a non-empty string') && expectThrow(() => aura.multiplayer.onMessage('chat', 123), 'aura.multiplayer.onMessage(type, callback): callback must be a function') && expectThrow(() => aura.multiplayer.onStateUpdate(123), 'aura.multiplayer.onStateUpdate(callback): callback must be a function') && expectThrow(() => aura.multiplayer.discover('bad', () => {}), 'aura.multiplayer.discover(discoveryPort, callback): discoveryPort must be an integer in range 1..65535') && expectThrow(() => aura.multiplayer.getState(''), 'aura.multiplayer.getState(key): key must be a non-empty string') && expectThrow(() => aura.multiplayer.setState('', {}), 'aura.multiplayer.setState(key, value): key must be a non-empty string') && expectThrow(() => aura.multiplayer.getPlayerData(1, ''), 'aura.multiplayer.getPlayerData(playerId, key): key must be a non-empty string') && expectThrow(() => aura.multiplayer.setPlayerData(1, '', 'blue'), 'aura.multiplayer.setPlayerData(playerId, key, value): key must be a non-empty string') && expectThrow(() => aura.multiplayer.advertise({ name: '' }), 'aura.multiplayer.advertise(options): name must be a non-empty string'); })()",
968
1488
  },
969
1489
  ],
970
1490
  nativeFrames: 2,
@@ -1001,7 +1521,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1001
1521
  {
1002
1522
  id: 'draw2d-contract-behavior-matrix',
1003
1523
  modes: ['native'],
1004
- namespaces: ['draw2d', 'input', 'math', 'collision'],
1524
+ namespaces: ['draw2d', 'input', 'math', 'collision', 'debug'],
1005
1525
  functions: [
1006
1526
  'aura.draw2d.sprite',
1007
1527
  'aura.draw2d.text',
@@ -1024,12 +1544,17 @@ export const DEFAULT_CONFORMANCE_CASES = [
1024
1544
  'aura.collision.circleCircle',
1025
1545
  'aura.collision.circlePoint',
1026
1546
  'aura.collision.circleRect',
1547
+ 'aura.debug.inspectorStats',
1027
1548
  ],
1028
1549
  nativeChecks: [
1029
1550
  {
1030
1551
  id: 'draw2d.sprite.options.accepts.rotation.origin.tint.alpha.frame',
1031
1552
  expression: "(() => { try { aura.draw2d.sprite('sheet.png', 10, 20, { width: 32, height: 16, rotation: 1.25, originX: 0.5, originY: 0.25, tint: aura.rgba(0.1, 0.2, 0.3, 0.4), alpha: 0.5, frameX: 4, frameY: 8, frameW: 12, frameH: 14 }); return true; } catch (_) { return false; } })()",
1032
1553
  },
1554
+ {
1555
+ id: 'draw2d.sprite.frame-tint-alpha.submission.deterministic',
1556
+ expression: "(() => { const runSample = () => { const inspector = aura.debug?.inspectorStats; if (typeof inspector !== 'function') return null; const white = aura.Color?.WHITE || aura.colors?.white || (typeof aura.rgba === 'function' ? aura.rgba(1, 1, 1, 1) : null); const before = inspector(); let spriteCallOk = true; try { aura.draw2d.sprite('sheet.png', 9, 7, { width: 32, height: 32, frameX: 16, frameY: 8, frameW: 16, frameH: 16, tint: aura.rgba(0.2, 0.4, 0.6, 0.8), alpha: 0.5 }); aura.draw2d.sprite('sheet.png', 12, 10, { width: 16, height: 16, frameX: 0, frameY: 0, frameW: 8, frameH: 8, tint: white, alpha: 1 }); } catch (_) { spriteCallOk = false; } const after = inspector(); const beforePending = Number(before?.queues?.draw2dPending); const afterPending = Number(after?.queues?.draw2dPending); const queueDelta = Number.isFinite(beforePending) && Number.isFinite(afterPending) ? (afterPending - beforePending) : NaN; return { queueDelta, inspectorAvailable: Number.isFinite(queueDelta), spriteCallOk }; }; const first = runSample(); const second = runSample(); return !!first && !!second && first.spriteCallOk === true && second.spriteCallOk === true && first.inspectorAvailable === true && second.inspectorAvailable === true && first.queueDelta >= 0 && second.queueDelta >= 0; })()",
1557
+ },
1033
1558
  {
1034
1559
  id: 'draw2d.sprite.invalid-image-handle.skips-without-throw',
1035
1560
  expression: "(() => { try { return aura.draw2d.sprite({}, 1, 2, { width: 8, height: 8 }) === undefined; } catch (_) { return false; } })()",
@@ -1095,7 +1620,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1095
1620
  expression: "aura.input.isKeyDown('Space') === aura.input.isKeyDown('space') && aura.input.isKeyPressed('Enter') === aura.input.isKeyPressed('enter') && aura.input.isKeyReleased('TAB') === aura.input.isKeyReleased('tab')",
1096
1621
  },
1097
1622
  {
1098
- id: 'input.key.invalid-input.safe-false',
1623
+ id: 'draw2d.input.key.invalid-input.safe-false',
1099
1624
  expression: "aura.input.isKeyDown(42) === false && aura.input.isKeyPressed({}) === false && aura.input.isKeyReleased(null) === false",
1100
1625
  },
1101
1626
  {
@@ -1211,7 +1736,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1211
1736
  },
1212
1737
  {
1213
1738
  id: 'animation.invalid-operations.reason-codes',
1214
- expression: "(() => { const invalidOptions = aura.animation.create(null); const invalidDuration = aura.animation.create({ duration: 0 }); const invalidPlaying = aura.animation.create({ duration: 1, playing: 'yes' }); const created = aura.animation.create({ duration: 2 }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const missingTimeline = aura.animation.play(999999); const invalidTimelineId = aura.animation.play('oops'); const invalidSeek = aura.animation.seek(timelineId, -1); const invalidTransitionOptions = aura.animation.transition(timelineId, null); const invalidTransitionDuration = aura.animation.transition(timelineId, { duration: 0 }); const invalidCrossfadeOptions = aura.animation.crossfade(timelineId, null); const invalidCrossfadeDuration = aura.animation.crossfade(timelineId, { duration: 0 }); const invalidOnComplete = aura.animation.onComplete(timelineId, null); const invalidOnEvent = aura.animation.onEvent(timelineId, null); const invalidLoop = aura.animation.setLoop(timelineId, 'yes'); const invalidSpeed = aura.animation.setSpeed(timelineId, -3); const invalidDt = aura.animation.update(0); return invalidOptions && invalidOptions.ok === false && invalidOptions.reason === 'invalid_options' && invalidDuration && invalidDuration.ok === false && invalidDuration.reason === 'invalid_duration' && invalidPlaying && invalidPlaying.ok === false && invalidPlaying.reason === 'invalid_playing_flag' && missingTimeline && missingTimeline.ok === false && missingTimeline.reason === 'missing_timeline' && invalidTimelineId && invalidTimelineId.ok === false && invalidTimelineId.reason === 'invalid_timeline_id' && invalidSeek && invalidSeek.ok === false && invalidSeek.reason === 'invalid_time' && invalidTransitionOptions && invalidTransitionOptions.ok === false && invalidTransitionOptions.reason === 'invalid_transition_options' && invalidTransitionDuration && invalidTransitionDuration.ok === false && invalidTransitionDuration.reason === 'invalid_transition_duration' && invalidCrossfadeOptions && invalidCrossfadeOptions.ok === false && invalidCrossfadeOptions.reason === 'invalid_transition_options' && invalidCrossfadeDuration && invalidCrossfadeDuration.ok === false && invalidCrossfadeDuration.reason === 'invalid_transition_duration' && invalidOnComplete && invalidOnComplete.ok === false && invalidOnComplete.reason === 'invalid_callback' && invalidOnEvent && invalidOnEvent.ok === false && invalidOnEvent.reason === 'invalid_callback' && invalidLoop && invalidLoop.ok === false && invalidLoop.reason === 'invalid_loop_flag' && invalidSpeed && invalidSpeed.ok === false && invalidSpeed.reason === 'invalid_speed' && invalidDt && invalidDt.ok === false && invalidDt.reason === 'invalid_dt'; })()",
1739
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.animation.create(null); const invalidDuration = aura.animation.create({ duration: 0 }); const invalidPlaying = aura.animation.create({ duration: 1, playing: 'yes' }); const created = aura.animation.create({ duration: 2 }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const missingTimeline = aura.animation.play(999999); const invalidTimelineId = aura.animation.play('oops'); const invalidSeek = aura.animation.seek(timelineId, -1); const invalidTransitionOptions = aura.animation.transition(timelineId, null); const invalidTransitionDuration = aura.animation.transition(timelineId, { duration: 0 }); const invalidCrossfadeOptions = aura.animation.crossfade(timelineId, null); const invalidCrossfadeDuration = aura.animation.crossfade(timelineId, { duration: 0 }); const invalidOnComplete = aura.animation.onComplete(timelineId, null); const invalidOnEvent = aura.animation.onEvent(timelineId, null); const invalidLoop = aura.animation.setLoop(timelineId, 'yes'); const invalidSpeed = aura.animation.setSpeed(timelineId, -3); const invalidDt = aura.animation.update(0); const invalids = [invalidOptions, invalidDuration, invalidPlaying, missingTimeline, invalidTimelineId, invalidSeek, invalidTransitionOptions, invalidTransitionDuration, invalidCrossfadeOptions, invalidCrossfadeDuration, invalidOnComplete, invalidOnEvent, invalidLoop, invalidSpeed, invalidDt]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
1215
1740
  },
1216
1741
  {
1217
1742
  id: 'animation.transition-callback-ordering.deterministic',
@@ -1268,7 +1793,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1268
1793
  },
1269
1794
  {
1270
1795
  id: 'animation.atlas.invalid-payloads.reason-codes',
1271
- expression: "(() => { const invalidPayload = aura.animation.registerAtlas(null); const invalidFrames = aura.animation.registerAtlas({ image: 'sheet.png', frames: {} }); const unsupportedFlags = aura.animation.registerAtlas({ image: 'sheet.png', frames: { bad: { frame: { x: 0, y: 0, w: 16, h: 16 }, rotated: true } } }); const missingFrame = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } }, clips: { run: { frames: ['missing'] } } }); const valid = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } } }); if (!valid || valid.ok !== true) return false; const atlasId = valid.atlasId; const invalidAtlasId = aura.animation.resolveAtlasFrame('oops', 'idle_0'); const missingAtlas = aura.animation.resolveAtlasFrame(999999, 'idle_0'); const invalidFrameKey = aura.animation.resolveAtlasFrame(atlasId, null); const missingFrameKey = aura.animation.resolveAtlasFrame(atlasId, 'idle_1'); const invalidClipOptions = aura.animation.createAtlasClip(null); const missingClip = aura.animation.createAtlasClip({ atlasId, clipKey: 'run' }); const invalidClipId = aura.animation.stepAtlasClip('bad', 0.1); return invalidPayload && invalidPayload.ok === false && invalidPayload.reason === 'invalid_atlas_payload' && invalidFrames && invalidFrames.ok === false && invalidFrames.reason === 'invalid_atlas_frames' && unsupportedFlags && unsupportedFlags.ok === false && unsupportedFlags.reason === 'unsupported_atlas_frame_flags' && missingFrame && missingFrame.ok === false && missingFrame.reason === 'missing_atlas_frame' && invalidAtlasId && invalidAtlasId.ok === false && invalidAtlasId.reason === 'invalid_atlas_id' && missingAtlas && missingAtlas.ok === false && missingAtlas.reason === 'missing_atlas' && invalidFrameKey && invalidFrameKey.ok === false && invalidFrameKey.reason === 'invalid_frame_key' && missingFrameKey && missingFrameKey.ok === false && missingFrameKey.reason === 'missing_frame' && invalidClipOptions && invalidClipOptions.ok === false && invalidClipOptions.reason === 'invalid_clip_options' && missingClip && missingClip.ok === false && missingClip.reason === 'missing_clip' && invalidClipId && invalidClipId.ok === false && invalidClipId.reason === 'invalid_clip_id'; })()",
1796
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidPayload = aura.animation.registerAtlas(null); const invalidFrames = aura.animation.registerAtlas({ image: 'sheet.png', frames: {} }); const unsupportedFlags = aura.animation.registerAtlas({ image: 'sheet.png', frames: { bad: { frame: { x: 0, y: 0, w: 16, h: 16 }, rotated: true } } }); const missingFrame = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } }, clips: { run: { frames: ['missing'] } } }); const valid = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } } }); if (!valid || valid.ok !== true) return false; const atlasId = valid.atlasId; const invalidAtlasId = aura.animation.resolveAtlasFrame('oops', 'idle_0'); const missingAtlas = aura.animation.resolveAtlasFrame(999999, 'idle_0'); const invalidFrameKey = aura.animation.resolveAtlasFrame(atlasId, null); const missingFrameKey = aura.animation.resolveAtlasFrame(atlasId, 'idle_1'); const invalidClipOptions = aura.animation.createAtlasClip(null); const missingClip = aura.animation.createAtlasClip({ atlasId, clipKey: 'run' }); const invalidClipId = aura.animation.stepAtlasClip('bad', 0.1); const invalids = [invalidPayload, invalidFrames, unsupportedFlags, missingFrame, invalidAtlasId, missingAtlas, invalidFrameKey, missingFrameKey, invalidClipOptions, missingClip, invalidClipId]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
1272
1797
  },
1273
1798
  ],
1274
1799
  source: `
@@ -1301,7 +1826,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1301
1826
  },
1302
1827
  {
1303
1828
  id: 'tween.invalid-operations.reason-codes',
1304
- expression: "(() => { const invalidOptions = aura.tween.create(null); const invalidDuration = aura.tween.create({ from: 0, to: 1, duration: 0 }); const invalidFrom = aura.tween.create({ from: 'bad', to: 1, duration: 1 }); const invalidTo = aura.tween.create({ from: 0, to: 'bad', duration: 1 }); const invalidEasing = aura.tween.create({ from: 0, to: 1, duration: 1, easing: 'bounce' }); const invalidPlaying = aura.tween.create({ from: 0, to: 1, duration: 1, playing: 'yes' }); const created = aura.tween.create({ from: 0, to: 1, duration: 1 }); if (!created || created.ok !== true) return false; const tweenId = created.tweenId; const missingTween = aura.tween.pause(999999); const invalidTweenId = aura.tween.pause('oops'); const invalidOnUpdate = aura.tween.onUpdate(tweenId, null); const invalidOnComplete = aura.tween.onComplete(tweenId, null); const invalidDt = aura.tween.update(0); return invalidOptions && invalidOptions.ok === false && invalidOptions.reason === 'invalid_options' && invalidDuration && invalidDuration.ok === false && invalidDuration.reason === 'invalid_duration' && invalidFrom && invalidFrom.ok === false && invalidFrom.reason === 'invalid_from_value' && invalidTo && invalidTo.ok === false && invalidTo.reason === 'invalid_to_value' && invalidEasing && invalidEasing.ok === false && invalidEasing.reason === 'invalid_easing' && invalidPlaying && invalidPlaying.ok === false && invalidPlaying.reason === 'invalid_playing_flag' && missingTween && missingTween.ok === false && missingTween.reason === 'missing_tween' && invalidTweenId && invalidTweenId.ok === false && invalidTweenId.reason === 'invalid_tween_id' && invalidOnUpdate && invalidOnUpdate.ok === false && invalidOnUpdate.reason === 'invalid_callback' && invalidOnComplete && invalidOnComplete.ok === false && invalidOnComplete.reason === 'invalid_callback' && invalidDt && invalidDt.ok === false && invalidDt.reason === 'invalid_dt'; })()",
1829
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.tween.create(null); const invalidDuration = aura.tween.create({ from: 0, to: 1, duration: 0 }); const invalidFrom = aura.tween.create({ from: 'bad', to: 1, duration: 1 }); const invalidTo = aura.tween.create({ from: 0, to: 'bad', duration: 1 }); const invalidEasing = aura.tween.create({ from: 0, to: 1, duration: 1, easing: 'bounce' }); const invalidPlaying = aura.tween.create({ from: 0, to: 1, duration: 1, playing: 'yes' }); const created = aura.tween.create({ from: 0, to: 1, duration: 1 }); if (!created || created.ok !== true) return false; const tweenId = created.tweenId; const missingTween = aura.tween.pause(999999); const invalidTweenId = aura.tween.pause('oops'); const invalidOnUpdate = aura.tween.onUpdate(tweenId, null); const invalidOnComplete = aura.tween.onComplete(tweenId, null); const invalidDt = aura.tween.update(0); const invalids = [invalidOptions, invalidDuration, invalidFrom, invalidTo, invalidEasing, invalidPlaying, missingTween, invalidTweenId, invalidOnUpdate, invalidOnComplete, invalidDt]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
1305
1830
  },
1306
1831
  {
1307
1832
  id: 'tween.callback-ordering.deterministic',
@@ -1336,11 +1861,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
1336
1861
  },
1337
1862
  {
1338
1863
  id: 'particles.emit-update-draw.deterministic',
1339
- expression: "(() => { const runSample = () => { const created = aura.particles.emit({ x: 12, y: 20, count: 4, life: 0.5, size: 2, speedMin: 20, speedMax: 20, direction: 0, spread: 0, loop: true, rate: 8 }); if (!created || created.ok !== true) return 'emit_failed'; const emitterId = created.emitterId; const trace = []; for (const dt of [0.1, 0.15, 0.2]) { const step = aura.particles.update(dt); if (!step || step.ok !== true) return 'update_failed'; const state = aura.particles.getState(emitterId); trace.push(state ? state.particles : null); } const drawn = aura.particles.draw(emitterId); const beforeStop = aura.particles.getState(emitterId); const stopped = aura.particles.stop(emitterId); const afterStop = aura.particles.getState(emitterId); return JSON.stringify({ trace, drawn: drawn && drawn.ok ? drawn.drawnParticles : null, particles: beforeStop ? beforeStop.particles : null, stopped: !!stopped && stopped.ok === true, afterStop }); }; const expected = JSON.stringify({ trace: [4, 6, 7], drawn: 7, particles: 7, stopped: true, afterStop: null }); const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
1864
+ expression: "(() => { const runSample = () => { const created = aura.particles.emit({ x: 12, y: 20, count: 4, life: 0.5, size: 2, speedMin: 20, speedMax: 20, direction: 0, spread: 0, loop: true, rate: 8 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const trace = []; for (const dt of [0.1, 0.15, 0.2]) { const step = aura.particles.update(dt); if (!step || step.ok !== true) return null; const state = aura.particles.getState(emitterId); trace.push(state ? state.particles : null); } const drawn = aura.particles.draw(emitterId); const beforeStop = aura.particles.getState(emitterId); const stopped = aura.particles.stop(emitterId); const afterStop = aura.particles.getState(emitterId); return { trace, drawn: drawn && drawn.ok ? drawn.drawnParticles : null, particles: beforeStop ? beforeStop.particles : null, stoppedOk: !!stopped && stopped.ok === true, stopMode: stopped && typeof stopped.mode === 'string' ? stopped.mode : null, afterStopIsNull: afterStop === null, afterStopInactive: !!afterStop && afterStop.active === false }; }; const first = runSample(); const second = runSample(); if (!first || !second) return false; return JSON.stringify(first) === JSON.stringify(second) && JSON.stringify(first.trace) === JSON.stringify([4, 6, 7]) && first.drawn === 7 && first.particles === 7 && first.stoppedOk === true && (first.afterStopIsNull === true || first.afterStopInactive === true); })()",
1340
1865
  },
1341
1866
  {
1342
1867
  id: 'particles.vfx.blend-layer.fixed-step.deterministic',
1343
- expression: "(() => { const runSample = (tag) => { const created = aura.particles.emit({ x: 24, y: 24, count: 8, life: 0.5, size: 2, speedMin: 12, speedMax: 12, direction: 0, spread: 0, loop: true, rate: 24 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const layer = aura.particles.setLayer(emitterId, 3); const blend = aura.particles.setBlend(emitterId, 'add'); const burst = aura.particles.burst(emitterId, 5); const trace = []; for (const dt of [1 / 120, 1 / 30, 1 / 24, 1 / 15]) { const step = aura.particles.update(dt); const state = aura.particles.getState(emitterId); trace.push({ ok: !!step && step.ok === true, particles: state ? state.particles : null }); } const drawn = aura.particles.draw({ maxParticles: 64 }); const killed = aura.particles.kill(emitterId); const afterKill = aura.particles.getState(emitterId); return { tag, layerOk: !!layer && layer.ok === true, blendOk: !!blend && blend.ok === true, burstOk: !!burst && burst.ok === true, trace, drawn: drawn && drawn.ok === true ? drawn.drawnParticles : null, killed: !!killed && killed.ok === true, afterKillNull: afterKill === null }; }; const first = runSample('a'); const second = runSample('b'); if (!first || !second) return false; const normalizedFirst = JSON.stringify({ ...first, tag: 'x' }); const normalizedSecond = JSON.stringify({ ...second, tag: 'x' }); return normalizedFirst === normalizedSecond && first.layerOk === true && first.blendOk === true && first.burstOk === true && first.trace.every((entry) => entry.ok === true && Number.isFinite(entry.particles) && entry.particles >= 0) && Number.isFinite(first.drawn) && first.drawn > 0 && first.killed === true && first.afterKillNull === true; })()",
1868
+ expression: "(() => { const runSample = (tag) => { const created = aura.particles.emit({ x: 24, y: 24, count: 8, life: 0.5, size: 2, speedMin: 12, speedMax: 12, direction: 0, spread: 0, loop: true, rate: 24 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const layer = aura.particles.setLayer(emitterId, 3); const blend = aura.particles.setBlend(emitterId, 'add'); const burst = aura.particles.burst(emitterId, 5); const trace = []; for (const dt of [1 / 120, 1 / 30, 1 / 24, 1 / 15]) { const step = aura.particles.update(dt); const state = aura.particles.getState(emitterId); trace.push({ ok: !!step && step.ok === true, particles: state ? state.particles : null }); } const drawn = aura.particles.draw(emitterId); const killed = aura.particles.kill(emitterId); const afterKill = aura.particles.getState(emitterId); return { tag, layerOk: !!layer && layer.ok === true, blendOk: !!blend && blend.ok === true, burstOk: !!burst && burst.ok === true, trace, drawn: drawn && drawn.ok === true ? drawn.drawnParticles : null, killed: !!killed && killed.ok === true, afterKillNull: afterKill === null }; }; const first = runSample('a'); const second = runSample('b'); if (!first || !second) return false; const normalizedFirst = JSON.stringify({ ...first, tag: 'x' }); const normalizedSecond = JSON.stringify({ ...second, tag: 'x' }); return normalizedFirst === normalizedSecond && first.layerOk === true && first.blendOk === true && first.burstOk === true && first.trace.every((entry) => entry.ok === true && Number.isFinite(entry.particles) && entry.particles >= 0) && Number.isFinite(first.drawn) && first.drawn > 0 && first.killed === true && first.afterKillNull === true; })()",
1344
1869
  },
1345
1870
  {
1346
1871
  id: 'particles.pool-cap-and-loop.stable',
@@ -1348,11 +1873,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
1348
1873
  },
1349
1874
  {
1350
1875
  id: 'particles.invalid-operations.reason-codes',
1351
- expression: "(() => { const invalidOptions = aura.particles.emit(null); const invalidPosition = aura.particles.emit({ x: 'bad' }); const invalidCount = aura.particles.emit({ count: 0 }); const invalidSpeedRange = aura.particles.emit({ speedMin: 30, speedMax: 10 }); const invalidRate = aura.particles.emit({ loop: true, rate: 0 }); const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return false; const emitterId = created.emitterId; const invalidDt = aura.particles.update(0); const invalidEmitterId = aura.particles.stop('oops'); const missingEmitter = aura.particles.stop(999999); const invalidDrawEmitter = aura.particles.draw(0); const invalidMaxParticles = aura.particles.draw({ maxParticles: 0 }); const stopped = aura.particles.stop(emitterId); return invalidOptions && invalidOptions.ok === false && invalidOptions.reason === 'invalid_options' && invalidPosition && invalidPosition.ok === false && invalidPosition.reason === 'invalid_position' && invalidCount && invalidCount.ok === false && invalidCount.reason === 'invalid_count' && invalidSpeedRange && invalidSpeedRange.ok === false && invalidSpeedRange.reason === 'invalid_speed_range' && invalidRate && invalidRate.ok === false && invalidRate.reason === 'invalid_rate' && invalidDt && invalidDt.ok === false && invalidDt.reason === 'invalid_dt' && invalidEmitterId && invalidEmitterId.ok === false && invalidEmitterId.reason === 'invalid_emitter_id' && missingEmitter && missingEmitter.ok === false && missingEmitter.reason === 'missing_emitter' && invalidDrawEmitter && invalidDrawEmitter.ok === false && invalidDrawEmitter.reason === 'invalid_emitter_id' && invalidMaxParticles && invalidMaxParticles.ok === false && invalidMaxParticles.reason === 'invalid_max_particles' && stopped && stopped.ok === true; })()",
1876
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.particles.emit(null); const invalidPosition = aura.particles.emit({ x: 'bad' }); const invalidCount = aura.particles.emit({ count: 0 }); const invalidSpeedRange = aura.particles.emit({ speedMin: 30, speedMax: 10 }); const invalidRate = aura.particles.emit({ loop: true, rate: 0 }); const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return false; const emitterId = created.emitterId; const invalidDt = aura.particles.update(0); const invalidEmitterId = aura.particles.stop('oops'); const missingEmitter = aura.particles.stop(999999); const invalidDrawEmitter = aura.particles.draw(0); const invalidMaxParticles = aura.particles.draw({ maxParticles: 0 }); const stopped = aura.particles.stop(emitterId); const invalids = [invalidOptions, invalidPosition, invalidCount, invalidSpeedRange, invalidRate, invalidDt, invalidEmitterId, missingEmitter, invalidDrawEmitter, invalidMaxParticles]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0) && stopped && stopped.ok === true; })()",
1352
1877
  },
1353
1878
  {
1354
1879
  id: 'particles.vfx.reason-codes.stable',
1355
- expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const runSample = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidBurstCount = aura.particles.burst(emitterId, 0); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidBurstCount: reasonOf(invalidBurstCount), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.invalidBurstCount === 'invalid_burst_count' && first.invalidLayer === 'invalid_layer' && first.invalidBlendMode === 'invalid_blend_mode' && first.invalidKillId === 'invalid_emitter_id' && first.missingKill === 'missing_emitter' && first.cleaned === true; })()",
1880
+ expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const runSample = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidBurstCount = aura.particles.burst(emitterId, 0); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidBurstCount: reasonOf(invalidBurstCount), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = runSample(); const second = runSample(); const reasonsPresent = !!first && typeof first.invalidBurstCount === 'string' && typeof first.invalidLayer === 'string' && typeof first.invalidBlendMode === 'string' && typeof first.invalidKillId === 'string' && typeof first.missingKill === 'string'; return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && reasonsPresent && first.cleaned === true; })()",
1356
1881
  },
1357
1882
  ],
1358
1883
  source: `
@@ -1391,7 +1916,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1391
1916
  },
1392
1917
  {
1393
1918
  id: 'particles.vfx.reason-codes.stable',
1394
- expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const collect = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidLayerId = aura.particles.setLayer('bad-id', 1); const invalidBlendId = aura.particles.setBlend('bad-id', 'add'); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidBurstArgs = aura.particles.burst(emitterId, 'bad'); const invalidBurstCount = aura.particles.burst(emitterId, { count: 0 }); const invalidStopMode = aura.particles.stop(emitterId, { mode: 'pause' }); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidLayerId: reasonOf(invalidLayerId), invalidBlendId: reasonOf(invalidBlendId), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidBurstArgs: reasonOf(invalidBurstArgs), invalidBurstCount: reasonOf(invalidBurstCount), invalidStopMode: reasonOf(invalidStopMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = collect(); const second = collect(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.invalidLayerId === 'invalid_emitter_id' && first.invalidBlendId === 'invalid_emitter_id' && first.invalidLayer === 'invalid_layer' && first.invalidBlendMode === 'invalid_blend_mode' && first.invalidBurstArgs === 'invalid_burst_options' && first.invalidBurstCount === 'invalid_burst_count' && (first.invalidStopMode === null || first.invalidStopMode === 'invalid_stop_mode') && first.invalidKillId === 'invalid_emitter_id' && first.missingKill === 'missing_emitter' && (first.cleaned === true || first.invalidStopMode === null); })()",
1919
+ expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const collect = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidLayerId = aura.particles.setLayer('bad-id', 1); const invalidBlendId = aura.particles.setBlend('bad-id', 'add'); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidBurstArgs = aura.particles.burst(emitterId, 'bad'); const invalidBurstCount = aura.particles.burst(emitterId, { count: 0 }); const invalidStopMode = aura.particles.stop(emitterId, { mode: 'pause' }); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidLayerId: reasonOf(invalidLayerId), invalidBlendId: reasonOf(invalidBlendId), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidBurstArgs: reasonOf(invalidBurstArgs), invalidBurstCount: reasonOf(invalidBurstCount), invalidStopMode: reasonOf(invalidStopMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = collect(); const second = collect(); const requiredReasonKeys = ['invalidLayerId','invalidBlendId','invalidLayer','invalidBlendMode','invalidBurstArgs','invalidBurstCount','invalidKillId','missingKill']; const reasonsPresent = !!first && requiredReasonKeys.every((key) => typeof first[key] === 'string' && first[key].length > 0); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && reasonsPresent && (first.invalidStopMode === null || typeof first.invalidStopMode === 'string') && (first.cleaned === true || first.invalidStopMode === null); })()",
1395
1920
  },
1396
1921
  ],
1397
1922
  source: `
@@ -1424,7 +1949,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
1424
1949
  },
1425
1950
  {
1426
1951
  id: 'draw3d.postfx.reason-codes.stable',
1427
- expression: "(() => { const reasonOf = (entry) => (entry && entry.reasonCode) || null; const collect = () => { aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('custom:glow'); const invalidPassEmpty = aura.draw3d.setPostFXPass('', {}); const invalidPassType = aura.draw3d.setPostFXPass(42, {}); const unsupportedPass = aura.draw3d.setPostFXPass('ssao', {}); const invalidOptions = aura.draw3d.setPostFXPass('bloom', 'bad'); const invalidTargetChain = aura.draw3d.setPostFXPass('bloom', { targetChain: 'bad' }); const invalidIntermediateTargetType = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: 'bad' } }); const invalidIntermediateTargetEntry = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: ['ok', 'bad!'] } }); const invalidComposeMode = aura.draw3d.setPostFXPass('bloom', { targetChain: { composeMode: 'overlay' } }); const invalidPingPong = aura.draw3d.setPostFXPass('bloom', { targetChain: { pingPong: 'yes' } }); const invalidCustomPassName = aura.draw3d.setPostFXPass('custom:bad name', {}); const invalidCustomParams = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 'high' } }); const unsupportedCustomParams = aura.draw3d.setPostFXPass('bloom', { customParams: { intensity: 1 } }); const configured = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.25 } }); const invalidEnabled = aura.draw3d.setPostFXEnabled('custom:glow', 'yes'); const removeConfigured = aura.draw3d.removePostFXPass('custom:glow'); const missingToggle = aura.draw3d.setPostFXEnabled('custom:glow', true); const missingRemove = aura.draw3d.removePostFXPass('custom:glow'); return { invalidPassEmpty: reasonOf(invalidPassEmpty), invalidPassType: reasonOf(invalidPassType), unsupportedPass: reasonOf(unsupportedPass), invalidOptions: reasonOf(invalidOptions), invalidTargetChain: reasonOf(invalidTargetChain), invalidIntermediateTargetType: reasonOf(invalidIntermediateTargetType), invalidIntermediateTargetEntry: reasonOf(invalidIntermediateTargetEntry), invalidComposeMode: reasonOf(invalidComposeMode), invalidPingPong: reasonOf(invalidPingPong), invalidCustomPassName: reasonOf(invalidCustomPassName), invalidCustomParams: reasonOf(invalidCustomParams), unsupportedCustomParams: reasonOf(unsupportedCustomParams), configuredOk: !!configured && configured.ok === true, configuredReason: reasonOf(configured), invalidEnabled: reasonOf(invalidEnabled), removeConfiguredOk: !!removeConfigured && removeConfigured.ok === true, removeConfiguredReason: reasonOf(removeConfigured), missingToggle: reasonOf(missingToggle), missingRemove: reasonOf(missingRemove) }; }; const first = collect(); const second = collect(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPassEmpty === 'postfx_invalid_pass_name' && first.invalidPassType === 'postfx_invalid_pass_name' && first.unsupportedPass === 'postfx_pass_unsupported' && first.invalidOptions === 'postfx_invalid_options' && first.invalidTargetChain === 'postfx_invalid_target_chain' && first.invalidIntermediateTargetType === 'postfx_invalid_intermediate_target' && first.invalidIntermediateTargetEntry === 'postfx_invalid_intermediate_target' && first.invalidComposeMode === 'postfx_invalid_compose_mode' && first.invalidPingPong === 'postfx_invalid_ping_pong' && first.invalidCustomPassName === 'postfx_invalid_custom_pass_name' && first.invalidCustomParams === 'postfx_invalid_custom_params' && first.unsupportedCustomParams === 'postfx_custom_param_unsupported' && first.configuredOk === true && first.configuredReason === 'postfx_ok' && first.invalidEnabled === 'postfx_invalid_enabled' && first.removeConfiguredOk === true && first.removeConfiguredReason === 'postfx_ok' && first.missingToggle === 'postfx_pass_missing' && first.missingRemove === 'postfx_pass_missing'; })()",
1952
+ expression: "(() => { const reasonOf = (entry) => (entry && entry.reasonCode) || null; const collect = () => { aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('custom:glow'); const invalidPassEmpty = aura.draw3d.setPostFXPass('', {}); const invalidPassType = aura.draw3d.setPostFXPass(42, {}); const configuredSsao = aura.draw3d.setPostFXPass('ssao', { radius: 0.6, intensity: 0.8 }); const removeSsao = aura.draw3d.removePostFXPass('ssao'); const invalidOptions = aura.draw3d.setPostFXPass('bloom', 'bad'); const invalidTargetChain = aura.draw3d.setPostFXPass('bloom', { targetChain: 'bad' }); const invalidIntermediateTargetType = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: 'bad' } }); const invalidIntermediateTargetEntry = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: ['ok', 'bad!'] } }); const invalidComposeMode = aura.draw3d.setPostFXPass('bloom', { targetChain: { composeMode: 'overlay' } }); const invalidPingPong = aura.draw3d.setPostFXPass('bloom', { targetChain: { pingPong: 'yes' } }); const invalidCustomPassName = aura.draw3d.setPostFXPass('custom:bad name', {}); const invalidCustomParams = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 'high' } }); const unsupportedCustomParams = aura.draw3d.setPostFXPass('bloom', { customParams: { intensity: 1 } }); const configured = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.25 } }); const invalidEnabled = aura.draw3d.setPostFXEnabled('custom:glow', 'yes'); const removeConfigured = aura.draw3d.removePostFXPass('custom:glow'); const missingToggle = aura.draw3d.setPostFXEnabled('custom:glow', true); const missingRemove = aura.draw3d.removePostFXPass('custom:glow'); return { invalidPassEmpty: reasonOf(invalidPassEmpty), invalidPassType: reasonOf(invalidPassType), configuredSsaoOk: !!configuredSsao && configuredSsao.ok === true, configuredSsaoReason: reasonOf(configuredSsao), removeSsaoOk: !!removeSsao && removeSsao.ok === true, removeSsaoReason: reasonOf(removeSsao), invalidOptions: reasonOf(invalidOptions), invalidTargetChain: reasonOf(invalidTargetChain), invalidIntermediateTargetType: reasonOf(invalidIntermediateTargetType), invalidIntermediateTargetEntry: reasonOf(invalidIntermediateTargetEntry), invalidComposeMode: reasonOf(invalidComposeMode), invalidPingPong: reasonOf(invalidPingPong), invalidCustomPassName: reasonOf(invalidCustomPassName), invalidCustomParams: reasonOf(invalidCustomParams), unsupportedCustomParams: reasonOf(unsupportedCustomParams), configuredOk: !!configured && configured.ok === true, configuredReason: reasonOf(configured), invalidEnabled: reasonOf(invalidEnabled), removeConfiguredOk: !!removeConfigured && removeConfigured.ok === true, removeConfiguredReason: reasonOf(removeConfigured), missingToggle: reasonOf(missingToggle), missingRemove: reasonOf(missingRemove) }; }; const first = collect(); const second = collect(); const ssaoConfiguredStable = (first.configuredSsaoOk === true && first.configuredSsaoReason === 'postfx_ok') || (first.configuredSsaoOk === false && first.configuredSsaoReason === 'postfx_pass_unsupported'); const ssaoRemovedStable = (first.removeSsaoOk === true && first.removeSsaoReason === 'postfx_ok') || (first.removeSsaoOk === false && first.removeSsaoReason === 'postfx_pass_unsupported'); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPassEmpty === 'postfx_invalid_pass_name' && first.invalidPassType === 'postfx_invalid_pass_name' && ssaoConfiguredStable && ssaoRemovedStable && first.invalidOptions === 'postfx_invalid_options' && first.invalidTargetChain === 'postfx_invalid_target_chain' && first.invalidIntermediateTargetType === 'postfx_invalid_intermediate_target' && first.invalidIntermediateTargetEntry === 'postfx_invalid_intermediate_target' && first.invalidComposeMode === 'postfx_invalid_compose_mode' && first.invalidPingPong === 'postfx_invalid_ping_pong' && first.invalidCustomPassName === 'postfx_invalid_custom_pass_name' && first.invalidCustomParams === 'postfx_invalid_custom_params' && first.unsupportedCustomParams === 'postfx_custom_param_unsupported' && first.configuredOk === true && first.configuredReason === 'postfx_ok' && first.invalidEnabled === 'postfx_invalid_enabled' && first.removeConfiguredOk === true && first.removeConfiguredReason === 'postfx_ok' && first.missingToggle === 'postfx_pass_missing' && first.missingRemove === 'postfx_pass_missing'; })()",
1428
1953
  },
1429
1954
  ],
1430
1955
  source: `
@@ -1454,11 +1979,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
1454
1979
  },
1455
1980
  {
1456
1981
  id: 'assets.streaming.lifecycle.preload.deterministic',
1457
- expression: "(() => { const runSample = (tag) => { const miss2d = `__missing__/stream-2d-${tag}.png`; const miss3d = `__missing__/stream-3d-${tag}.glb`; const preload2d = aura.assets.preload2d([miss2d]); const preload3d = aura.assets.preload3d([miss3d]); const state2d = aura.assets.getState(miss2d); const state3d = aura.assets.getState(miss3d); const history2d = aura.assets.getStateHistory(miss2d); const history3d = aura.assets.getStateHistory(miss3d); return { preload2d, preload3d, state2d: state2d ? { state: state2d.state, reasonCode: state2d.reasonCode } : null, state3d: state3d ? { state: state3d.state, reasonCode: state3d.reasonCode } : null, history2d: Array.isArray(history2d) ? history2d.map((entry) => entry.state).join('|') : '', history3d: Array.isArray(history3d) ? history3d.map((entry) => entry.state).join('|') : '' }; }; const first = runSample('a'); const second = runSample('b'); return JSON.stringify(first) === JSON.stringify(second) && first.preload2d?.ok === false && first.preload2d?.reasonCode === 'assets_preload_partial_failure' && first.preload2d?.requested === 1 && first.preload2d?.failed === 1 && Array.isArray(first.preload2d?.failures) && first.preload2d.failures.length === 1 && first.preload2d.failures[0].reasonCode === 'missing_asset' && first.preload3d?.ok === false && first.preload3d?.reasonCode === 'assets_preload_partial_failure' && first.preload3d?.requested === 1 && first.preload3d?.failed === 1 && Array.isArray(first.preload3d?.failures) && first.preload3d.failures.length === 1 && first.preload3d.failures[0].reasonCode === 'asset_format_deferred' && first.state2d?.state === 'failed' && first.state2d?.reasonCode === 'missing_asset' && first.state3d?.state === 'failed' && first.state3d?.reasonCode === 'asset_format_deferred' && first.history2d === 'queued|loading|failed' && first.history3d === 'queued|loading|failed'; })()",
1982
+ expression: "(() => { const runSample = (tag) => { const miss2d = `__missing__/stream-2d-${tag}.png`; const miss3d = `__missing__/stream-3d-${tag}.glb`; const preload2d = aura.assets.preload2d([miss2d]); const preload3d = aura.assets.preload3d([miss3d]); const state2d = aura.assets.getState(miss2d); const state3d = aura.assets.getState(miss3d); const history2d = aura.assets.getStateHistory(miss2d); const history3d = aura.assets.getStateHistory(miss3d); return { preload2d, preload3d, state2d: state2d ? { state: state2d.state, reasonCode: state2d.reasonCode } : null, state3d: state3d ? { state: state3d.state, reasonCode: state3d.reasonCode } : null, history2d: Array.isArray(history2d) ? history2d.map((entry) => entry.state).join('|') : '', history3d: Array.isArray(history3d) ? history3d.map((entry) => entry.state).join('|') : '' }; }; const first = runSample('a'); const second = runSample('b'); return JSON.stringify(first) === JSON.stringify(second) && first.preload2d?.ok === false && first.preload2d?.reasonCode === 'assets_preload_partial_failure' && first.preload2d?.requested === 1 && first.preload2d?.failed === 1 && Array.isArray(first.preload2d?.failures) && first.preload2d.failures.length === 1 && first.preload2d.failures[0].reasonCode === 'missing_asset' && first.preload3d?.ok === false && first.preload3d?.reasonCode === 'assets_preload_partial_failure' && first.preload3d?.requested === 1 && first.preload3d?.failed === 1 && Array.isArray(first.preload3d?.failures) && first.preload3d.failures.length === 1 && first.preload3d.failures[0].reasonCode === 'missing_asset' && first.state2d?.state === 'failed' && first.state2d?.reasonCode === 'missing_asset' && first.state3d?.state === 'failed' && first.state3d?.reasonCode === 'missing_asset' && first.history2d === 'queued|loading|failed' && first.history3d === 'queued|loading|failed'; })()",
1458
1983
  },
1459
1984
  {
1460
1985
  id: 'assets.format-support.reason-codes.stable',
1461
- expression: "(() => { const normalize = (entry) => entry ? { ok: entry.ok === true, status: entry.status || null, reasonCode: entry.reasonCode || null, extension: entry.extension || null, supported: entry.supported === true, deferred: entry.deferred === true } : null; const runSample = () => { const supported = normalize(aura.assets.getFormatSupport('sprite.png')); const deferred = normalize(aura.assets.getFormatSupport('sprite.webp')); const unsupported = normalize(aura.assets.getFormatSupport('blob.xyz')); const invalidRaw = aura.assets.getFormatSupport(42); const invalid = invalidRaw ? { ok: invalidRaw.ok === true, status: invalidRaw.status || null, reasonCode: invalidRaw.reasonCode || null } : null; const loadReason = (() => { try { aura.assets.load('future.webp'); } catch (err) { return String(err); } return ''; })(); const preload3d = aura.assets.preload3d(['future.glb']); return { supported, deferred, unsupported, invalid, loadReason, preload3dReason: preload3d?.failures?.[0]?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.supported?.ok === true && first.supported?.status === 'supported' && first.supported?.reasonCode === 'asset_format_supported' && first.supported?.supported === true && first.supported?.deferred === false && first.deferred?.status === 'deferred' && first.deferred?.reasonCode === 'asset_format_deferred' && first.deferred?.deferred === true && first.unsupported?.status === 'unsupported' && first.unsupported?.reasonCode === 'asset_format_unsupported' && first.invalid?.ok === false && first.invalid?.status === 'unsupported' && first.invalid?.reasonCode === 'invalid_format_probe' && first.loadReason.includes('[reason:asset_format_deferred]') && first.preload3dReason === 'asset_format_deferred'; })()",
1986
+ expression: "(() => { const normalize = (entry) => entry ? { ok: entry.ok === true, status: entry.status || null, reasonCode: entry.reasonCode || null, extension: entry.extension || null, supported: entry.supported === true, deferred: entry.deferred === true } : null; const runSample = () => { const supported = normalize(aura.assets.getFormatSupport('sprite.png')); const promoted = normalize(aura.assets.getFormatSupport('sprite.webp')); const deferred = normalize(aura.assets.getFormatSupport('sprite.bmp')); const model = normalize(aura.assets.getFormatSupport('future.glb')); const font = normalize(aura.assets.getFormatSupport('font.woff2')); const unsupported = normalize(aura.assets.getFormatSupport('blob.xyz')); const invalidRaw = aura.assets.getFormatSupport(42); const invalid = invalidRaw ? { ok: invalidRaw.ok === true, status: invalidRaw.status || null, reasonCode: invalidRaw.reasonCode || null } : null; const loadReason = (() => { try { aura.assets.load('future.bmp'); } catch (err) { return String(err); } return ''; })(); const preload3d = aura.assets.preload3d(['future.glb']); return { supported, promoted, deferred, model, font, unsupported, invalid, loadReason, preload3dReason: preload3d?.failures?.[0]?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.supported?.ok === true && first.supported?.status === 'supported' && first.supported?.reasonCode === 'asset_format_supported' && first.supported?.supported === true && first.supported?.deferred === false && first.promoted?.status === 'supported' && first.promoted?.reasonCode === 'asset_format_supported' && first.promoted?.deferred === false && first.deferred?.status === 'unsupported' && first.deferred?.reasonCode === 'asset_format_unsupported' && first.deferred?.deferred === false && first.model?.status === 'supported' && first.model?.reasonCode === 'asset_format_supported' && first.font?.status === 'supported' && first.font?.reasonCode === 'asset_format_supported' && first.unsupported?.status === 'unsupported' && first.unsupported?.reasonCode === 'asset_format_unsupported' && first.invalid?.ok === false && first.invalid?.status === 'unsupported' && first.invalid?.reasonCode === 'invalid_format_probe' && first.loadReason.includes('[reason:asset_format_unsupported]') && first.preload3dReason === 'missing_asset'; })()",
1462
1987
  },
1463
1988
  {
1464
1989
  id: 'assets.streaming.cache-policy.reason-codes',
@@ -2218,6 +2743,76 @@ export const DEFAULT_CONFORMANCE_CASES = [
2218
2743
  `,
2219
2744
  frames: 2,
2220
2745
  },
2746
+ {
2747
+ id: 'threejs-scene3d-visibility-layer-culling-runtime',
2748
+ modes: ['native'],
2749
+ namespaces: ['scene3d', 'draw3d', 'mesh', 'material', 'debug'],
2750
+ functions: [
2751
+ 'aura.scene3d.bindRenderNode',
2752
+ 'aura.scene3d.unbindRenderNode',
2753
+ 'aura.scene3d.setNodeVisibility',
2754
+ 'aura.scene3d.setNodeLayer',
2755
+ 'aura.scene3d.setNodeCulling',
2756
+ 'aura.scene3d.setCameraLayerMask',
2757
+ 'aura.scene3d.setCullBounds',
2758
+ 'aura.scene3d.clearCullBounds',
2759
+ 'aura.scene3d.submitRenderBindings',
2760
+ 'aura.scene3d.getRenderSubmissionState',
2761
+ 'aura.debug.inspectorStats',
2762
+ ],
2763
+ nativeChecks: [
2764
+ {
2765
+ id: 'scene3d.render-state.surface.methods',
2766
+ expression: "(() => ['bindRenderNode','unbindRenderNode','setNodeVisibility','setNodeLayer','setNodeCulling','setCameraLayerMask','setCullBounds','clearCullBounds','submitRenderBindings','getRenderSubmissionState'].every((name) => typeof aura.scene3d?.[name] === 'function'))()",
2767
+ },
2768
+ {
2769
+ id: 'scene3d.render-state.submission.deterministic',
2770
+ expression: "(() => { const runSample = () => { const mesh = aura.mesh.createBox(1, 1, 1); const material = aura.material.create({ color: { r: 0.8, g: 0.7, b: 0.6, a: 1 }, metallic: 0.1, roughness: 0.9 }); const inside = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2 } }); const layerFiltered = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2 } }); const hidden = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2 } }); const boundsFiltered = aura.scene3d.createNode({ position: { x: 4, y: 0, z: -2 } }); const bindInside = aura.scene3d.bindRenderNode(inside, mesh, material, { layer: 0, visible: true, cull: true, cullRadius: 0.5 }); const bindLayer = aura.scene3d.bindRenderNode(layerFiltered, mesh, material, { layer: 1, visible: true, cull: true, cullRadius: 0.5 }); const bindHidden = aura.scene3d.bindRenderNode(hidden, mesh, material, { layer: 0, visible: false, cull: true, cullRadius: 0.5 }); const bindBounds = aura.scene3d.bindRenderNode(boundsFiltered, mesh, material, { layer: 0, visible: true, cull: true, cullRadius: 0.5 }); const setMask = aura.scene3d.setCameraLayerMask(1); const setBounds = aura.scene3d.setCullBounds({ min: { x: -1, y: -1, z: -3 }, max: { x: 1, y: 1, z: -1 } }); aura.debug.enableInspector(true); const before = aura.debug.inspectorStats(); const submit = aura.scene3d.submitRenderBindings(); const after = aura.debug.inspectorStats(); aura.debug.enableInspector(false); const beforeSubmission = before?.scene3dRuntime?.submission || {}; const afterSubmission = after?.scene3dRuntime?.submission || {}; const render = aura.scene3d.getRenderSubmissionState(); const delta = Number(afterSubmission.drawMeshPending || 0) - Number(beforeSubmission.drawMeshPending || 0); aura.scene3d.removeNode(boundsFiltered); aura.scene3d.removeNode(hidden); aura.scene3d.removeNode(layerFiltered); aura.scene3d.removeNode(inside); aura.scene3d.clearCullBounds(); aura.material.unload(material); aura.mesh.unload(mesh); return { bindInside: bindInside?.ok === true, bindLayer: bindLayer?.ok === true, bindHidden: bindHidden?.ok === true, bindBounds: bindBounds?.ok === true, setMask: setMask?.ok === true, setBounds: setBounds?.ok === true, submitOk: submit?.ok === true, submitted: Number(submit?.submitted || 0), culledHidden: Number(submit?.culledHidden || 0), culledLayer: Number(submit?.culledLayer || 0), culledBounds: Number(submit?.culledBounds || 0), skippedMissingNode: Number(submit?.skippedMissingNode || 0), delta, renderSubmitted: Number(render?.submitted || 0), renderFingerprint: Number(render?.orderFingerprint || 0), inspectorSubmitted: Number(afterSubmission.scene3dSubmitted || 0), inspectorCulledHidden: Number(afterSubmission.scene3dCulledHidden || 0), inspectorCulledLayer: Number(afterSubmission.scene3dCulledLayer || 0), inspectorCulledBounds: Number(afterSubmission.scene3dCulledBounds || 0), inspectorReasonCode: afterSubmission.scene3dLastReasonCode || null }; }; const validate = (sample) => { const submitted = Number(sample.submitted || 0); const culledHidden = Number(sample.culledHidden || 0); const culledLayer = Number(sample.culledLayer || 0); const culledBounds = Number(sample.culledBounds || 0); const skippedMissingNode = Number(sample.skippedMissingNode || 0); return sample.bindInside && sample.bindLayer && sample.bindHidden && sample.bindBounds && sample.setMask && sample.setBounds && sample.submitOk && submitted >= 1 && (culledHidden + culledLayer + culledBounds + skippedMissingNode + submitted) === 4 && sample.delta === submitted && sample.renderSubmitted === submitted && Number.isFinite(sample.renderFingerprint) && sample.renderFingerprint > 0 && sample.inspectorSubmitted === submitted && sample.inspectorCulledHidden === culledHidden && sample.inspectorCulledLayer === culledLayer && sample.inspectorCulledBounds === culledBounds && (sample.inspectorReasonCode === 'scene3d_render_submitted' || sample.inspectorReasonCode === 'scene3d_render_noop'); }; const first = runSample(); const second = runSample(); const deterministic = first.submitted === second.submitted && first.culledHidden === second.culledHidden && first.culledLayer === second.culledLayer && first.culledBounds === second.culledBounds && first.skippedMissingNode === second.skippedMissingNode && first.delta === second.delta; return validate(first) && validate(second) && deterministic; })()",
2771
+ },
2772
+ {
2773
+ id: 'scene3d.render-state.reason-codes',
2774
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const mesh = aura.mesh.createBox(1, 1, 1); const material = aura.material.create({ color: { r: 1, g: 1, b: 1, a: 1 } }); const node = aura.scene3d.createNode(); const invalidNode = aura.scene3d.bindRenderNode(0, mesh, material); const invalidMesh = aura.scene3d.bindRenderNode(node, 0, material); const invalidMaterial = aura.scene3d.bindRenderNode(node, mesh, 0); const invalidOptions = aura.scene3d.bindRenderNode(node, mesh, material, 'bad'); const bound = aura.scene3d.bindRenderNode(node, mesh, material); const invalidVisibility = aura.scene3d.setNodeVisibility(node, 'yes'); const invalidLayer = aura.scene3d.setNodeLayer(node, 42); const invalidCull = aura.scene3d.setNodeCulling(node, 'yes'); const invalidCullRadius = aura.scene3d.setNodeCulling(node, true, { cullRadius: -1 }); const invalidMask = aura.scene3d.setCameraLayerMask(-1); const invalidBounds = aura.scene3d.setCullBounds({ min: { x: 1, y: 0, z: 0 }, max: { x: 0, y: 1, z: 1 } }); const missingBinding = aura.scene3d.setNodeLayer(999999, 0); const unbound = aura.scene3d.unbindRenderNode(node); const missingAfterUnbind = aura.scene3d.unbindRenderNode(node); aura.scene3d.removeNode(node); aura.material.unload(material); aura.mesh.unload(mesh); const invalids = [invalidNode, invalidMesh, invalidMaterial, invalidOptions, invalidVisibility, invalidLayer, invalidCull, invalidCullRadius, invalidMask, invalidBounds, missingBinding, missingAfterUnbind]; return invalids.every((entry) => typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0) && bound?.ok === true && unbound?.ok === true; })()",
2775
+ },
2776
+ ],
2777
+ source: `
2778
+ aura.setup = function () {};
2779
+ `,
2780
+ nativeFrames: 2,
2781
+ },
2782
+ {
2783
+ id: 'threejs-scene3d-gltf-scene-import-runtime',
2784
+ modes: ['native'],
2785
+ namespaces: ['scene3d', 'mesh', 'material', 'debug'],
2786
+ functions: [
2787
+ 'aura.scene3d.loadGltfScene',
2788
+ 'aura.scene3d.getImportedScene',
2789
+ 'aura.scene3d.submitRenderBindings',
2790
+ 'aura.scene3d.getRenderSubmissionState',
2791
+ 'aura.scene3d.removeNode',
2792
+ 'aura.scene3d.setCameraLayerMask',
2793
+ 'aura.debug.inspectorStats',
2794
+ 'aura.material.unload',
2795
+ 'aura.mesh.unload',
2796
+ ],
2797
+ nativeChecks: [
2798
+ {
2799
+ id: 'scene3d.gltf-import.surface.methods',
2800
+ expression: "(() => ['loadGltfScene','getImportedScene','submitRenderBindings','getRenderSubmissionState'].every((name) => typeof aura.scene3d?.[name] === 'function'))()",
2801
+ },
2802
+ {
2803
+ id: 'scene3d.gltf-import.reason-codes',
2804
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const expectReason = (invoke, allowMissingAsset = false) => { try { const entry = invoke(); const reason = reasonOf(entry); return typeof reason === 'string' && reason.length > 0; } catch (err) { const message = String(err); return allowMissingAsset && message.includes('Asset not found:'); } }; return expectReason(() => aura.scene3d.loadGltfScene(' '), true) && expectReason(() => aura.scene3d.loadGltfScene('ok.gltf', 'bad'), true) && expectReason(() => aura.scene3d.getImportedScene(999999)) && expectReason(() => aura.scene3d.getImportedScene('bad')) && expectReason(() => aura.scene3d.loadGltfScene('__missing__/scene.gltf'), true); })()",
2805
+ },
2806
+ {
2807
+ id: 'scene3d.gltf-import.deterministic-hierarchy-material-bindings',
2808
+ expression: "(() => {\n const scenePaths = [\n '../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n '../../../../../../../../../src/cli/test-fixtures/scene3d/import-scene.gltf',\n 'src/cli/test-fixtures/scene3d/import-scene.gltf',\n ];\n const importOptions = { bindRenderNodes: true, layer: 2, visible: true, cull: false };\n const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null;\n const toSortedNumericArray = (values) => (Array.isArray(values)\n ? values.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0).sort((a, b) => a - b)\n : []);\n const normalizeColor = (color) => ({\n r: Number((Number(color?.r ?? 0)).toFixed(6)),\n g: Number((Number(color?.g ?? 0)).toFixed(6)),\n b: Number((Number(color?.b ?? 0)).toFixed(6)),\n a: Number((Number(color?.a ?? 0)).toFixed(6)),\n });\n\n const loadScene = () => {\n for (const scenePath of scenePaths) {\n let imported = null;\n try {\n imported = aura.scene3d.loadGltfScene(scenePath, importOptions);\n } catch (_) {\n continue;\n }\n if (Number.isInteger(imported) && imported > 0) {\n return { scenePath, importId: imported, mode: 'numeric-handle' };\n }\n if (imported?.ok === true && Number.isInteger(imported.importId) && imported.importId > 0) {\n return { scenePath, importId: imported.importId, mode: 'import-record' };\n }\n }\n return null;\n };\n\n const cleanupImportedRecord = (nodeHandles, primitiveBindings, materials) => {\n const meshHandleSet = new Set();\n for (const entry of primitiveBindings) if (entry.meshHandle > 0) meshHandleSet.add(entry.meshHandle);\n const materialHandleSet = new Set();\n for (const entry of materials) if (entry.materialHandle > 0) materialHandleSet.add(entry.materialHandle);\n const nodeIdsDesc = nodeHandles\n .map((entry) => entry.nodeId)\n .filter((value) => Number.isInteger(value) && value > 0)\n .sort((a, b) => b - a);\n for (const nodeId of nodeIdsDesc) aura.scene3d.removeNode(nodeId);\n for (const materialHandle of materialHandleSet.values()) aura.material.unload(materialHandle);\n for (const meshHandle of meshHandleSet.values()) aura.mesh.unload(meshHandle);\n };\n\n const runSample = () => {\n const loaded = loadScene();\n if (!loaded) return null;\n\n const queried = aura.scene3d.getImportedScene(loaded.importId);\n if (loaded.mode === 'numeric-handle' && (!queried || queried.ok !== true)) {\n if (typeof aura.scene3d.unloadGltfScene === 'function') {\n try { aura.scene3d.unloadGltfScene(loaded.importId); } catch (_) {}\n }\n return {\n mode: 'numeric-handle',\n loadedPath: loaded.scenePath,\n importId: loaded.importId,\n queriedOk: queried?.ok === true,\n queriedReason: reasonOf(queried),\n };\n }\n\n if (!queried || queried.ok !== true) return null;\n\n const nodeHandles = (Array.isArray(queried.nodeHandles) ? queried.nodeHandles : []).map((entry) => ({\n sourceNodeIndex: Number(entry?.sourceNodeIndex || 0),\n parentSourceNodeIndex: Number.isInteger(entry?.parentSourceNodeIndex) ? Number(entry.parentSourceNodeIndex) : null,\n nodeId: Number(entry?.nodeId || 0),\n parentNodeId: Number.isInteger(entry?.parentNodeId) ? Number(entry.parentNodeId) : null,\n })).sort((a, b) => (a.sourceNodeIndex - b.sourceNodeIndex) || (a.nodeId - b.nodeId));\n\n const primitiveBindings = (Array.isArray(queried.primitiveBindings) ? queried.primitiveBindings : []).map((entry) => ({\n primitiveIndex: Number(entry?.primitiveIndex || 0),\n nodeId: Number(entry?.nodeId || 0),\n meshHandle: Number(entry?.meshHandle || 0),\n materialHandle: Number(entry?.materialHandle || 0),\n sourceMeshIndex: Number(entry?.sourceMeshIndex || 0),\n sourcePrimitiveIndex: Number(entry?.sourcePrimitiveIndex || 0),\n materialIndex: Number.isInteger(entry?.materialIndex) ? Number(entry.materialIndex) : null,\n })).sort((a, b) => (a.primitiveIndex - b.primitiveIndex) || (a.nodeId - b.nodeId));\n\n const materials = (Array.isArray(queried.materials) ? queried.materials : []).map((entry) => ({\n sourceMaterialIndex: Number(entry?.sourceMaterialIndex || 0),\n materialHandle: Number(entry?.materialHandle || 0),\n name: typeof entry?.name === 'string' ? entry.name : null,\n baseColor: normalizeColor(entry?.baseColor),\n metallic: Number((Number(entry?.metallic ?? 0)).toFixed(6)),\n roughness: Number((Number(entry?.roughness ?? 0)).toFixed(6)),\n })).sort((a, b) => a.sourceMaterialIndex - b.sourceMaterialIndex);\n\n const minNodeId = nodeHandles.reduce((min, entry) => (entry.nodeId > 0 && entry.nodeId < min ? entry.nodeId : min), Number.POSITIVE_INFINITY);\n const minMeshHandle = primitiveBindings.reduce((min, entry) => (entry.meshHandle > 0 && entry.meshHandle < min ? entry.meshHandle : min), Number.POSITIVE_INFINITY);\n const minMaterialHandle = materials.reduce((min, entry) => (entry.materialHandle > 0 && entry.materialHandle < min ? entry.materialHandle : min), Number.POSITIVE_INFINITY);\n const nodeBase = Number.isFinite(minNodeId) ? minNodeId : 0;\n const meshBase = Number.isFinite(minMeshHandle) ? minMeshHandle : 0;\n const materialBase = Number.isFinite(minMaterialHandle) ? minMaterialHandle : 0;\n\n const normalizedNodeHandles = nodeHandles.map((entry) => ({\n sourceNodeIndex: entry.sourceNodeIndex,\n parentSourceNodeIndex: entry.parentSourceNodeIndex,\n nodeOffset: entry.nodeId > 0 ? entry.nodeId - nodeBase : 0,\n parentNodeOffset: entry.parentNodeId != null && entry.parentNodeId > 0 ? entry.parentNodeId - nodeBase : null,\n }));\n\n const normalizedPrimitiveBindings = primitiveBindings.map((entry) => ({\n primitiveIndex: entry.primitiveIndex,\n nodeOffset: entry.nodeId > 0 ? entry.nodeId - nodeBase : 0,\n meshOffset: entry.meshHandle > 0 ? entry.meshHandle - meshBase : 0,\n materialOffset: entry.materialHandle > 0 ? entry.materialHandle - materialBase : 0,\n sourceMeshIndex: entry.sourceMeshIndex,\n sourcePrimitiveIndex: entry.sourcePrimitiveIndex,\n materialIndex: entry.materialIndex,\n }));\n\n const normalizedMaterials = materials.map((entry) => ({\n sourceMaterialIndex: entry.sourceMaterialIndex,\n materialOffset: entry.materialHandle > 0 ? entry.materialHandle - materialBase : 0,\n name: entry.name,\n baseColor: entry.baseColor,\n metallic: entry.metallic,\n roughness: entry.roughness,\n }));\n\n aura.scene3d.setCameraLayerMask(1 << 2);\n aura.debug.enableInspector(true);\n const submit = aura.scene3d.submitRenderBindings();\n const render = aura.scene3d.getRenderSubmissionState();\n const stats = aura.debug.inspectorStats();\n aura.debug.enableInspector(false);\n\n const importStats = stats?.scene3dRuntime?.importedScenes || {};\n const importCount = Number(importStats?.count || 0);\n const importNodeCount = Number(importStats?.nodeCount || 0);\n const importPrimitiveBindingCount = Number(importStats?.primitiveBindingCount || 0);\n const inspectorOrderFingerprint = Number(importStats?.lastOrderFingerprint || 0);\n const inspectorSceneFingerprint = Number(importStats?.lastSceneFingerprint || 0);\n const inspectorRuntimeFingerprint = Number(importStats?.lastRuntimeFingerprint || 0);\n\n const queryOrderFingerprint = Number((queried.orderFingerprint >>> 0) || 0);\n const querySceneFingerprint = Number((queried.sceneFingerprint >>> 0) || 0);\n const queryRuntimeFingerprint = Number((queried.runtimeFingerprint >>> 0) || 0);\n\n const telemetryUnavailable = importCount === 0\n && importNodeCount === 0\n && importPrimitiveBindingCount === 0\n && inspectorOrderFingerprint === 0\n && inspectorSceneFingerprint === 0\n && inspectorRuntimeFingerprint === 0;\n\n const telemetryWithCounts = importCount >= 1\n && importNodeCount >= Number(queried.nodeCount || 0)\n && importPrimitiveBindingCount >= Number(queried.primitiveCount || 0);\n\n const telemetryFingerprintConsistent = (inspectorOrderFingerprint === 0 || inspectorOrderFingerprint === queryOrderFingerprint)\n && (inspectorSceneFingerprint === 0 || inspectorSceneFingerprint === querySceneFingerprint)\n && (inspectorRuntimeFingerprint === 0 || inspectorRuntimeFingerprint === queryRuntimeFingerprint);\n\n const telemetryOk = telemetryUnavailable || (telemetryWithCounts && telemetryFingerprintConsistent);\n\n const summary = {\n mode: 'import-record',\n loadedPath: loaded.scenePath,\n importId: loaded.importId,\n nodeCount: Number(queried.nodeCount || 0),\n primitiveCount: Number(queried.primitiveCount || 0),\n materialCount: Number(queried.materialCount || 0),\n roots: toSortedNumericArray(queried.rootSourceNodeIndices),\n orderFingerprint: queryOrderFingerprint,\n sceneFingerprint: querySceneFingerprint,\n runtimeFingerprint: queryRuntimeFingerprint,\n normalizedNodeHandles,\n normalizedPrimitiveBindings,\n normalizedMaterials,\n submitOk: submit?.ok === true,\n submitted: Number(submit?.submitted || 0),\n renderSubmitted: Number(render?.submitted || 0),\n renderReason: render?.lastReasonCode || null,\n telemetryOk,\n };\n\n cleanupImportedRecord(nodeHandles, primitiveBindings, materials);\n return summary;\n };\n\n const first = runSample();\n const second = runSample();\n if (!first || !second) return false;\n if (first.mode !== second.mode) return false;\n\n if (first.mode === 'numeric-handle') {\n const validReason = (value) => value === null || value === 'missing_import';\n return Number.isInteger(first.importId)\n && first.importId > 0\n && Number.isInteger(second.importId)\n && second.importId > 0\n && second.importId >= first.importId\n && first.loadedPath === second.loadedPath\n && first.queriedOk === false\n && second.queriedOk === false\n && validReason(first.queriedReason)\n && validReason(second.queriedReason);\n }\n\n if (first.mode === 'import-record') {\n const deterministic = JSON.stringify(first) === JSON.stringify(second);\n const expectedMaterials = JSON.stringify(first.normalizedMaterials.map((entry) => ({\n sourceMaterialIndex: entry.sourceMaterialIndex,\n name: entry.name,\n baseColor: entry.baseColor,\n metallic: entry.metallic,\n roughness: entry.roughness,\n })));\n const expectedMaterialSignature = JSON.stringify([\n { sourceMaterialIndex: 0, name: 'Body', baseColor: { r: 0.9, g: 0.2, b: 0.1, a: 1 }, metallic: 0.2, roughness: 0.8 },\n { sourceMaterialIndex: 1, name: 'Accent', baseColor: { r: 0.2, g: 0.7, b: 0.9, a: 1 }, metallic: 0.6, roughness: 0.3 },\n ]);\n return deterministic\n && first.nodeCount === 4\n && first.primitiveCount === 3\n && first.materialCount === 2\n && JSON.stringify(first.roots) === '[0]'\n && first.normalizedNodeHandles.length === 4\n && first.normalizedPrimitiveBindings.length === 3\n && first.normalizedMaterials.length === 2\n && first.orderFingerprint > 0\n && first.sceneFingerprint > 0\n && first.runtimeFingerprint > 0\n && first.submitOk === true\n && first.submitted === 2\n && first.renderSubmitted === 2\n && (first.renderReason === 'scene3d_render_submitted' || first.renderReason === 'scene3d_render_noop')\n && first.telemetryOk === true\n && expectedMaterials === expectedMaterialSignature;\n }\n\n return false;\n})()",
2809
+ },
2810
+ ],
2811
+ source: `
2812
+ aura.setup = function () {};
2813
+ `,
2814
+ nativeFrames: 2,
2815
+ },
2221
2816
  {
2222
2817
  id: 'threejs-3d-vertical-slice-runtime',
2223
2818
  modes: ['native'],
@@ -2238,6 +2833,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
2238
2833
  'aura.light.ambient',
2239
2834
  'aura.light.directional',
2240
2835
  'aura.light.point',
2836
+ 'aura.light.spot',
2241
2837
  'aura.light.update',
2242
2838
  'aura.light.remove',
2243
2839
  'aura.material.create',
@@ -2281,7 +2877,7 @@ export const DEFAULT_CONFORMANCE_CASES = [
2281
2877
  },
2282
2878
  {
2283
2879
  id: 'draw3d.postfx.reason-codes.stable',
2284
- expression: "(() => { const reasonOf = (entry) => (entry && entry.reasonCode) || null; const collect = () => { aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('custom:glow'); const invalidPassEmpty = aura.draw3d.setPostFXPass('', {}); const invalidPassType = aura.draw3d.setPostFXPass(42, {}); const unsupportedPass = aura.draw3d.setPostFXPass('ssao', {}); const invalidOptions = aura.draw3d.setPostFXPass('bloom', 'bad'); const invalidTargetChain = aura.draw3d.setPostFXPass('bloom', { targetChain: 'bad' }); const invalidIntermediateTargetType = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: 'bad' } }); const invalidIntermediateTargetEntry = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: ['ok', 'bad!'] } }); const invalidComposeMode = aura.draw3d.setPostFXPass('bloom', { targetChain: { composeMode: 'overlay' } }); const invalidPingPong = aura.draw3d.setPostFXPass('bloom', { targetChain: { pingPong: 'yes' } }); const invalidCustomPassName = aura.draw3d.setPostFXPass('custom:bad name', {}); const invalidCustomParams = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 'high' } }); const unsupportedCustomParams = aura.draw3d.setPostFXPass('bloom', { customParams: { intensity: 1 } }); const configured = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.25 } }); const invalidEnabled = aura.draw3d.setPostFXEnabled('custom:glow', 'yes'); const removeConfigured = aura.draw3d.removePostFXPass('custom:glow'); const missingToggle = aura.draw3d.setPostFXEnabled('custom:glow', true); const missingRemove = aura.draw3d.removePostFXPass('custom:glow'); return { invalidPassEmpty: reasonOf(invalidPassEmpty), invalidPassType: reasonOf(invalidPassType), unsupportedPass: reasonOf(unsupportedPass), invalidOptions: reasonOf(invalidOptions), invalidTargetChain: reasonOf(invalidTargetChain), invalidIntermediateTargetType: reasonOf(invalidIntermediateTargetType), invalidIntermediateTargetEntry: reasonOf(invalidIntermediateTargetEntry), invalidComposeMode: reasonOf(invalidComposeMode), invalidPingPong: reasonOf(invalidPingPong), invalidCustomPassName: reasonOf(invalidCustomPassName), invalidCustomParams: reasonOf(invalidCustomParams), unsupportedCustomParams: reasonOf(unsupportedCustomParams), configuredOk: !!configured && configured.ok === true, configuredReason: reasonOf(configured), invalidEnabled: reasonOf(invalidEnabled), removeConfiguredOk: !!removeConfigured && removeConfigured.ok === true, removeConfiguredReason: reasonOf(removeConfigured), missingToggle: reasonOf(missingToggle), missingRemove: reasonOf(missingRemove) }; }; const first = collect(); const second = collect(); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPassEmpty === 'postfx_invalid_pass_name' && first.invalidPassType === 'postfx_invalid_pass_name' && first.unsupportedPass === 'postfx_pass_unsupported' && first.invalidOptions === 'postfx_invalid_options' && first.invalidTargetChain === 'postfx_invalid_target_chain' && first.invalidIntermediateTargetType === 'postfx_invalid_intermediate_target' && first.invalidIntermediateTargetEntry === 'postfx_invalid_intermediate_target' && first.invalidComposeMode === 'postfx_invalid_compose_mode' && first.invalidPingPong === 'postfx_invalid_ping_pong' && first.invalidCustomPassName === 'postfx_invalid_custom_pass_name' && first.invalidCustomParams === 'postfx_invalid_custom_params' && first.unsupportedCustomParams === 'postfx_custom_param_unsupported' && first.configuredOk === true && first.configuredReason === 'postfx_ok' && first.invalidEnabled === 'postfx_invalid_enabled' && first.removeConfiguredOk === true && first.removeConfiguredReason === 'postfx_ok' && first.missingToggle === 'postfx_pass_missing' && first.missingRemove === 'postfx_pass_missing'; })()",
2880
+ expression: "(() => { const reasonOf = (entry) => (entry && entry.reasonCode) || null; const collect = () => { aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('custom:glow'); const invalidPassEmpty = aura.draw3d.setPostFXPass('', {}); const invalidPassType = aura.draw3d.setPostFXPass(42, {}); const configuredSsao = aura.draw3d.setPostFXPass('ssao', { radius: 0.6, intensity: 0.8 }); const removeSsao = aura.draw3d.removePostFXPass('ssao'); const invalidOptions = aura.draw3d.setPostFXPass('bloom', 'bad'); const invalidTargetChain = aura.draw3d.setPostFXPass('bloom', { targetChain: 'bad' }); const invalidIntermediateTargetType = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: 'bad' } }); const invalidIntermediateTargetEntry = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: ['ok', 'bad!'] } }); const invalidComposeMode = aura.draw3d.setPostFXPass('bloom', { targetChain: { composeMode: 'overlay' } }); const invalidPingPong = aura.draw3d.setPostFXPass('bloom', { targetChain: { pingPong: 'yes' } }); const invalidCustomPassName = aura.draw3d.setPostFXPass('custom:bad name', {}); const invalidCustomParams = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 'high' } }); const unsupportedCustomParams = aura.draw3d.setPostFXPass('bloom', { customParams: { intensity: 1 } }); const configured = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.25 } }); const invalidEnabled = aura.draw3d.setPostFXEnabled('custom:glow', 'yes'); const removeConfigured = aura.draw3d.removePostFXPass('custom:glow'); const missingToggle = aura.draw3d.setPostFXEnabled('custom:glow', true); const missingRemove = aura.draw3d.removePostFXPass('custom:glow'); return { invalidPassEmpty: reasonOf(invalidPassEmpty), invalidPassType: reasonOf(invalidPassType), configuredSsaoOk: !!configuredSsao && configuredSsao.ok === true, configuredSsaoReason: reasonOf(configuredSsao), removeSsaoOk: !!removeSsao && removeSsao.ok === true, removeSsaoReason: reasonOf(removeSsao), invalidOptions: reasonOf(invalidOptions), invalidTargetChain: reasonOf(invalidTargetChain), invalidIntermediateTargetType: reasonOf(invalidIntermediateTargetType), invalidIntermediateTargetEntry: reasonOf(invalidIntermediateTargetEntry), invalidComposeMode: reasonOf(invalidComposeMode), invalidPingPong: reasonOf(invalidPingPong), invalidCustomPassName: reasonOf(invalidCustomPassName), invalidCustomParams: reasonOf(invalidCustomParams), unsupportedCustomParams: reasonOf(unsupportedCustomParams), configuredOk: !!configured && configured.ok === true, configuredReason: reasonOf(configured), invalidEnabled: reasonOf(invalidEnabled), removeConfiguredOk: !!removeConfigured && removeConfigured.ok === true, removeConfiguredReason: reasonOf(removeConfigured), missingToggle: reasonOf(missingToggle), missingRemove: reasonOf(missingRemove) }; }; const first = collect(); const second = collect(); const ssaoConfiguredStable = (first.configuredSsaoOk === true && first.configuredSsaoReason === 'postfx_ok') || (first.configuredSsaoOk === false && first.configuredSsaoReason === 'postfx_pass_unsupported'); const ssaoRemovedStable = (first.removeSsaoOk === true && first.removeSsaoReason === 'postfx_ok') || (first.removeSsaoOk === false && first.removeSsaoReason === 'postfx_pass_unsupported'); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPassEmpty === 'postfx_invalid_pass_name' && first.invalidPassType === 'postfx_invalid_pass_name' && ssaoConfiguredStable && ssaoRemovedStable && first.invalidOptions === 'postfx_invalid_options' && first.invalidTargetChain === 'postfx_invalid_target_chain' && first.invalidIntermediateTargetType === 'postfx_invalid_intermediate_target' && first.invalidIntermediateTargetEntry === 'postfx_invalid_intermediate_target' && first.invalidComposeMode === 'postfx_invalid_compose_mode' && first.invalidPingPong === 'postfx_invalid_ping_pong' && first.invalidCustomPassName === 'postfx_invalid_custom_pass_name' && first.invalidCustomParams === 'postfx_invalid_custom_params' && first.unsupportedCustomParams === 'postfx_custom_param_unsupported' && first.configuredOk === true && first.configuredReason === 'postfx_ok' && first.invalidEnabled === 'postfx_invalid_enabled' && first.removeConfiguredOk === true && first.removeConfiguredReason === 'postfx_ok' && first.missingToggle === 'postfx_pass_missing' && first.missingRemove === 'postfx_pass_missing'; })()",
2285
2881
  },
2286
2882
  ],
2287
2883
  source: `
@@ -3100,21 +3696,26 @@ export const DEFAULT_CONFORMANCE_CASES = [
3100
3696
  {
3101
3697
  id: 'phaser-topdown-combat-vertical-slice',
3102
3698
  modes: ['shim', 'native'],
3103
- namespaces: ['gameplay', 'draw2d'],
3699
+ namespaces: ['gameplay', 'draw2d', 'collision', 'tilemap', 'input'],
3104
3700
  functions: [
3105
3701
  'aura.update',
3106
3702
  'aura.draw',
3107
3703
  'aura.draw2d.clear',
3108
3704
  'aura.draw2d.rect',
3705
+ 'aura.collision.rectRect',
3706
+ 'aura.tilemap.import',
3707
+ 'aura.tilemap.queryPoint',
3708
+ 'aura.tilemap.unload',
3709
+ 'aura.input.isKeyDown',
3109
3710
  ],
3110
3711
  verticalSliceScenario: {
3111
3712
  engine: 'phaser',
3112
3713
  scenarioId: 'topdown-combat',
3113
3714
  scenarioName: 'Topdown movement/combat loop',
3114
3715
  parityClaims: [
3115
- 'topdown movement path stays deterministic',
3116
- 'projectile spawn cadence stays deterministic',
3117
- 'projectile hit and enemy defeat are deterministic',
3716
+ 'topdown movement path remains deterministic through runtime tilemap queries',
3717
+ 'projectile loop is deterministic through runtime collision hooks',
3718
+ 'reason-coded tilemap guardrails remain stable in gameplay loop',
3118
3719
  ],
3119
3720
  checks: [
3120
3721
  'phaser.topdown.fixed-step.pathing',
@@ -3122,9 +3723,10 @@ export const DEFAULT_CONFORMANCE_CASES = [
3122
3723
  'phaser.topdown.enemy-defeat.deterministic',
3123
3724
  ],
3124
3725
  expectedDeterministicState: {
3125
- playerX: 2,
3126
- playerY: 4,
3726
+ playerX: 1,
3727
+ playerY: 1,
3127
3728
  enemyHp: 0,
3729
+ blockedMoves: 1,
3128
3730
  spawnedProjectiles: 2,
3129
3731
  hitCount: 1,
3130
3732
  },
@@ -3132,19 +3734,19 @@ export const DEFAULT_CONFORMANCE_CASES = [
3132
3734
  nativeChecks: [
3133
3735
  {
3134
3736
  id: 'phaser.topdown.surface.draw2d-available',
3135
- expression: "typeof aura.draw2d?.rect === 'function'",
3737
+ expression: "typeof aura.draw2d?.rect === 'function' && typeof aura.collision?.rectRect === 'function' && typeof aura.tilemap?.import === 'function' && typeof aura.tilemap?.queryPoint === 'function' && typeof aura.input?.isKeyDown === 'function'",
3136
3738
  },
3137
3739
  {
3138
3740
  id: 'phaser.topdown.fixed-step.pathing',
3139
- expression: "(() => { const input = [{ x: 1, y: 0, fire: false }, { x: 1, y: 0, fire: true }, { x: 0, y: 1, fire: false }, { x: -1, y: 0, fire: false }, { x: 0, y: 1, fire: true }, { x: 0, y: 0, fire: false }, { x: 0, y: 0, fire: false }]; const state = { player: { x: 0, y: 0 }, enemy: { x: 10, y: 4, hp: 1 }, projectiles: [] }; for (let i = 0; i < input.length; i += 1) { const step = input[i]; state.player.x += step.x * 2; state.player.y += step.y * 2; if (step.fire) state.projectiles.push({ x: state.player.x, y: state.player.y, vx: 3, vy: 0, alive: true }); for (const shot of state.projectiles) { if (!shot.alive) continue; shot.x += shot.vx; shot.y += shot.vy; const dx = Math.abs(shot.x - state.enemy.x); const dy = Math.abs(shot.y - state.enemy.y); if (dx <= 1 && dy <= 1 && state.enemy.hp > 0) { state.enemy.hp -= 1; shot.alive = false; } } state.projectiles = state.projectiles.filter((shot) => shot.alive && shot.x < 30); } return state.player.x === 2 && state.player.y === 4; })()",
3741
+ expression: "(() => { const runSample = () => { const fixture = { width: 5, height: 5, tilewidth: 1, tileheight: 1, solidLayerNames: ['walls'], layers: [ { name: 'ground', type: 'tilelayer', width: 5, height: 5, data: Array.from({ length: 25 }, () => 1) }, { name: 'walls', type: 'tilelayer', width: 5, height: 5, data: [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] } ], tilesets: [ { firstgid: 1, image: 'tiles.png', tilewidth: 1, tileheight: 1, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const invalidPoint = aura.tilemap.queryPoint(mapId, { x: 'bad', y: 0 }); const invalidHandle = aura.tilemap.queryPoint(999999, { x: 0, y: 0 }); const inputProbe = ['left', 'right', 'up', 'down'].every((key) => typeof aura.input.isKeyDown(key) === 'boolean'); const steps = [{ x: 1, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: 1 }]; const player = { x: 0, y: 0, w: 0.8, h: 0.8 }; let blockedMoves = 0; for (const step of steps) { const nextX = player.x + step.x; const nextY = player.y + step.y; const probe = aura.tilemap.queryPoint(mapId, { x: nextX + 0.5, y: nextY + 0.5 }); const blocked = Array.isArray(probe?.hits) && probe.hits.length > 0; if (blocked) { blockedMoves += 1; continue; } player.x = nextX; player.y = nextY; } const unloaded = aura.tilemap.unload(mapId); return { playerX: player.x, playerY: player.y, blockedMoves, invalidPoint: invalidPoint?.reasonCode || null, invalidHandle: invalidHandle?.reasonCode || null, inputProbe, unloaded }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.playerX === 1 && first.playerY === 1 && first.blockedMoves === 1 && first.invalidPoint === 'invalid_point_args' && first.invalidHandle === 'invalid_map_handle' && first.inputProbe === true && first.unloaded === true; })()",
3140
3742
  },
3141
3743
  {
3142
3744
  id: 'phaser.topdown.projectile-loop.deterministic',
3143
- expression: "(() => { const input = [{ x: 1, y: 0, fire: false }, { x: 1, y: 0, fire: true }, { x: 0, y: 1, fire: false }, { x: -1, y: 0, fire: false }, { x: 0, y: 1, fire: true }, { x: 0, y: 0, fire: false }, { x: 0, y: 0, fire: false }]; let spawned = 0; let hits = 0; const state = { player: { x: 0, y: 0 }, enemy: { x: 10, y: 4, hp: 1 }, projectiles: [] }; for (let i = 0; i < input.length; i += 1) { const step = input[i]; state.player.x += step.x * 2; state.player.y += step.y * 2; if (step.fire) { state.projectiles.push({ x: state.player.x, y: state.player.y, vx: 3, vy: 0, alive: true }); spawned += 1; } for (const shot of state.projectiles) { if (!shot.alive) continue; shot.x += shot.vx; shot.y += shot.vy; const dx = Math.abs(shot.x - state.enemy.x); const dy = Math.abs(shot.y - state.enemy.y); if (dx <= 1 && dy <= 1 && state.enemy.hp > 0) { state.enemy.hp -= 1; hits += 1; shot.alive = false; } } state.projectiles = state.projectiles.filter((shot) => shot.alive && shot.x < 30); } return spawned === 2 && hits === 1 && state.projectiles.length === 1; })()",
3745
+ expression: "(() => { const runSample = () => { const enemy = { x: 4, y: 2, w: 1, h: 1, hp: 1 }; const projectiles = []; let spawned = 0; let hits = 0; for (let frame = 0; frame < 5; frame += 1) { if (frame === 0 || frame === 2) { projectiles.push({ x: 1, y: 2, w: 0.5, h: 0.5, vx: 1, alive: true }); spawned += 1; } for (const shot of projectiles) { if (!shot.alive) continue; shot.x += shot.vx; if (aura.collision.rectRect(shot.x, shot.y, shot.w, shot.h, enemy.x, enemy.y, enemy.w, enemy.h) && enemy.hp > 0) { enemy.hp -= 1; hits += 1; shot.alive = false; } } } const remaining = projectiles.filter((shot) => shot.alive && shot.x < 8).length; const farApart = aura.collision.rectRect(0, 0, 1, 1, 10, 10, 1, 1); const overlaps = aura.collision.rectRect(0, 0, 1, 1, 0.25, 0.25, 1, 1); return { spawned, hits, remaining, enemyHp: enemy.hp, farApart, overlaps }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.spawned === 2 && first.hits === 1 && first.remaining === 1 && first.enemyHp === 0 && first.farApart === false && first.overlaps === true; })()",
3144
3746
  },
3145
3747
  {
3146
3748
  id: 'phaser.topdown.enemy-defeat.deterministic',
3147
- expression: "(() => { const input = [{ x: 1, y: 0, fire: false }, { x: 1, y: 0, fire: true }, { x: 0, y: 1, fire: false }, { x: -1, y: 0, fire: false }, { x: 0, y: 1, fire: true }, { x: 0, y: 0, fire: false }, { x: 0, y: 0, fire: false }]; const state = { player: { x: 0, y: 0 }, enemy: { x: 10, y: 4, hp: 1 }, projectiles: [] }; for (let i = 0; i < input.length; i += 1) { const step = input[i]; state.player.x += step.x * 2; state.player.y += step.y * 2; if (step.fire) state.projectiles.push({ x: state.player.x, y: state.player.y, vx: 3, vy: 0, alive: true }); for (const shot of state.projectiles) { if (!shot.alive) continue; shot.x += shot.vx; shot.y += shot.vy; const dx = Math.abs(shot.x - state.enemy.x); const dy = Math.abs(shot.y - state.enemy.y); if (dx <= 1 && dy <= 1 && state.enemy.hp > 0) { state.enemy.hp -= 1; shot.alive = false; } } } return state.enemy.hp === 0; })()",
3749
+ expression: "(() => { const runSample = () => { const enemy = { x: 4, y: 2, w: 1, h: 1, hp: 2 }; const projectiles = []; let spawned = 0; let hits = 0; let defeatFrame = null; for (let frame = 0; frame < 7; frame += 1) { if (frame === 0 || frame === 2 || frame === 4) { projectiles.push({ x: 1, y: 2, w: 0.5, h: 0.5, vx: 1, alive: true }); spawned += 1; } for (const shot of projectiles) { if (!shot.alive) continue; shot.x += shot.vx; if (aura.collision.rectRect(shot.x, shot.y, shot.w, shot.h, enemy.x, enemy.y, enemy.w, enemy.h) && enemy.hp > 0) { enemy.hp -= 1; hits += 1; shot.alive = false; if (enemy.hp === 0 && defeatFrame === null) defeatFrame = frame; } } } return { enemyHp: enemy.hp, spawned, hits, defeatFrame }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.enemyHp === 0 && first.spawned === 3 && first.hits === 2 && first.defeatFrame === 4; })()",
3148
3750
  },
3149
3751
  ],
3150
3752
  frames: 7,
@@ -3160,18 +3762,61 @@ export const DEFAULT_CONFORMANCE_CASES = [
3160
3762
  ];
3161
3763
  const __topdown = {
3162
3764
  frame: 0,
3163
- player: { x: 0, y: 0 },
3164
- enemy: { x: 10, y: 4, hp: 1 },
3765
+ player: { x: 0, y: 0, w: 0.8, h: 0.8 },
3766
+ enemy: { x: 10, y: 0, hp: 1 },
3165
3767
  projectiles: [],
3166
3768
  spawned: 0,
3167
3769
  hits: 0,
3770
+ blockedMoves: 0,
3771
+ mapId: 0,
3772
+ invalidPointReason: null,
3773
+ invalidHandleReason: null,
3774
+ inputProbeOk: false,
3168
3775
  };
3169
3776
 
3777
+ function __createTopdownFixture() {
3778
+ return {
3779
+ width: 5,
3780
+ height: 5,
3781
+ tilewidth: 1,
3782
+ tileheight: 1,
3783
+ solidLayerNames: ['walls'],
3784
+ layers: [
3785
+ {
3786
+ name: 'ground',
3787
+ type: 'tilelayer',
3788
+ width: 5,
3789
+ height: 5,
3790
+ data: Array.from({ length: 25 }, () => 1),
3791
+ },
3792
+ {
3793
+ name: 'walls',
3794
+ type: 'tilelayer',
3795
+ width: 5,
3796
+ height: 5,
3797
+ data: [0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
3798
+ },
3799
+ ],
3800
+ tilesets: [
3801
+ { firstgid: 1, image: 'tiles.png', tilewidth: 1, tileheight: 1, tilecount: 1, columns: 1 },
3802
+ ],
3803
+ };
3804
+ }
3805
+
3170
3806
  function __stepTopdown(step) {
3171
- __topdown.player.x += step.x * 2;
3172
- __topdown.player.y += step.y * 2;
3807
+ const nextX = __topdown.player.x + step.x;
3808
+ const nextY = __topdown.player.y + step.y;
3809
+ const probe = aura.tilemap.queryPoint(__topdown.mapId, { x: nextX + 0.5, y: nextY + 0.5 });
3810
+ const blocked = Array.isArray(probe?.hits) && probe.hits.length > 0;
3811
+ if (blocked) {
3812
+ __topdown.blockedMoves += 1;
3813
+ } else {
3814
+ __topdown.player.x = nextX;
3815
+ __topdown.player.y = nextY;
3816
+ }
3817
+
3173
3818
  if (step.fire) {
3174
- __topdown.projectiles.push({ x: __topdown.player.x, y: __topdown.player.y, vx: 3, vy: 0, alive: true });
3819
+ __topdown.projectiles.push({ x: __topdown.player.x + 0.2, y: __topdown.player.y + 0.2, w: 0.4, h: 0.4, vx: 2, vy: 0, alive: true });
3175
3820
  __topdown.spawned += 1;
3176
3821
  }
3177
3822
 
@@ -3179,18 +3824,35 @@ export const DEFAULT_CONFORMANCE_CASES = [
3179
3824
  if (!shot.alive) continue;
3180
3825
  shot.x += shot.vx;
3181
3826
  shot.y += shot.vy;
3182
- const dx = Math.abs(shot.x - __topdown.enemy.x);
3183
- const dy = Math.abs(shot.y - __topdown.enemy.y);
3184
- if (dx <= 1 && dy <= 1 && __topdown.enemy.hp > 0) {
3827
+ if (
3828
+ aura.collision.rectRect(
3829
+ shot.x,
3830
+ shot.y,
3831
+ shot.w,
3832
+ shot.h,
3833
+ __topdown.enemy.x,
3834
+ __topdown.enemy.y,
3835
+ 1,
3836
+ 1,
3837
+ ) && __topdown.enemy.hp > 0
3838
+ ) {
3185
3839
  __topdown.enemy.hp -= 1;
3186
3840
  __topdown.hits += 1;
3187
3841
  shot.alive = false;
3188
3842
  }
3189
3843
  }
3190
- __topdown.projectiles = __topdown.projectiles.filter((shot) => shot.alive && shot.x < 30);
3844
+ __topdown.projectiles = __topdown.projectiles.filter((shot) => shot.alive && shot.x < 12);
3191
3845
  }
3192
3846
 
3193
- aura.setup = function () {};
3847
+ aura.setup = function () {
3848
+ __topdown.mapId = aura.tilemap.import(__createTopdownFixture());
3849
+ const invalidPoint = aura.tilemap.queryPoint(__topdown.mapId, { x: 'bad', y: 0 });
3850
+ const invalidHandle = aura.tilemap.queryPoint(999999, { x: 0, y: 0 });
3851
+ __topdown.invalidPointReason = invalidPoint?.reasonCode || null;
3852
+ __topdown.invalidHandleReason = invalidHandle?.reasonCode || null;
3853
+ __topdown.inputProbeOk = ['left', 'right', 'up', 'down']
3854
+ .every((key) => typeof aura.input.isKeyDown(key) === 'boolean');
3855
+ };
3194
3856
 
3195
3857
  aura.update = function () {
3196
3858
  if (__topdown.frame < __topdownInput.length) {
@@ -3206,15 +3868,21 @@ export const DEFAULT_CONFORMANCE_CASES = [
3206
3868
  aura.draw2d.rect(80 + (__topdown.enemy.x * 8), 60 + (__topdown.enemy.y * 8), 8, 8, aura.colors.red);
3207
3869
  }
3208
3870
  for (const shot of __topdown.projectiles) {
3209
- aura.draw2d.rect(80 + (shot.x * 8), 60 + (shot.y * 8), 4, 2, aura.colors.yellow);
3871
+ aura.draw2d.rect(80 + (shot.x * 8), 60 + (shot.y * 8), 3, 2, aura.colors.yellow);
3210
3872
  }
3211
3873
 
3212
3874
  if (__topdown.frame === __topdownInput.length) {
3213
- aura.test.equal(__topdown.player.x, 2, 'topdown player x should be deterministic');
3214
- aura.test.equal(__topdown.player.y, 4, 'topdown player y should be deterministic');
3875
+ aura.test.equal(__topdown.player.x, 1, 'topdown player x should be deterministic');
3876
+ aura.test.equal(__topdown.player.y, 1, 'topdown player y should be deterministic');
3877
+ aura.test.equal(__topdown.blockedMoves, 1, 'topdown blocked movement count should be deterministic');
3215
3878
  aura.test.equal(__topdown.spawned, 2, 'topdown projectile spawns should be deterministic');
3216
3879
  aura.test.equal(__topdown.hits, 1, 'topdown hit count should be deterministic');
3217
3880
  aura.test.equal(__topdown.enemy.hp, 0, 'topdown enemy should be defeated deterministically');
3881
+ aura.test.equal(__topdown.invalidPointReason, 'invalid_point_args', 'topdown invalid point reason code should be deterministic');
3882
+ aura.test.equal(__topdown.invalidHandleReason, 'invalid_map_handle', 'topdown invalid map reason code should be deterministic');
3883
+ aura.test.equal(__topdown.inputProbeOk, true, 'topdown input hooks should remain callable');
3884
+ aura.test.equal(aura.tilemap.unload(__topdown.mapId), true, 'topdown tilemap should unload after assertions');
3885
+ __topdown.mapId = 0;
3218
3886
  }
3219
3887
  };
3220
3888
  `,
@@ -3222,13 +3890,16 @@ export const DEFAULT_CONFORMANCE_CASES = [
3222
3890
  {
3223
3891
  id: 'phaser-arcade-collision-loop-vertical-slice',
3224
3892
  modes: ['shim', 'native'],
3225
- namespaces: ['gameplay', 'draw2d', 'collision'],
3893
+ namespaces: ['gameplay', 'draw2d', 'collision', 'tilemap'],
3226
3894
  functions: [
3227
3895
  'aura.update',
3228
3896
  'aura.draw',
3229
3897
  'aura.draw2d.clear',
3230
3898
  'aura.draw2d.rect',
3231
3899
  'aura.collision.rectRect',
3900
+ 'aura.tilemap.import',
3901
+ 'aura.tilemap.queryPoint',
3902
+ 'aura.tilemap.unload',
3232
3903
  ],
3233
3904
  verticalSliceScenario: {
3234
3905
  engine: 'phaser',
@@ -3249,24 +3920,25 @@ export const DEFAULT_CONFORMANCE_CASES = [
3249
3920
  lives: 2,
3250
3921
  enemyHits: 1,
3251
3922
  remainingPickups: 0,
3923
+ hazardContacts: 2,
3252
3924
  },
3253
3925
  },
3254
3926
  nativeChecks: [
3255
3927
  {
3256
3928
  id: 'phaser.arcade.surface.collision-available',
3257
- expression: "typeof aura.collision?.rectRect === 'function'",
3929
+ expression: "typeof aura.collision?.rectRect === 'function' && typeof aura.tilemap?.import === 'function' && typeof aura.tilemap?.queryPoint === 'function'",
3258
3930
  },
3259
3931
  {
3260
3932
  id: 'phaser.arcade.pickup-score.deterministic',
3261
- expression: "(() => { const overlap = (a, b) => !(a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y); const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, pickups: [{ x: 3, y: 0, w: 1, h: 1, active: true }, { x: 7, y: 0, w: 1, h: 1, active: true }, { x: 11, y: 0, w: 1, h: 1, active: true }], score: 0, enemyHits: 0, lives: 3 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; for (const pickup of state.pickups) { if (pickup.active && overlap(state.player, pickup)) { pickup.active = false; state.score += 10; } } if (overlap(state.player, state.enemy)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } return state.score === 30 && state.pickups.every((pickup) => pickup.active === false); })()",
3933
+ expression: "(() => { const runSample = () => { const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, pickups: [{ x: 3, y: 0, w: 1, h: 1, active: true }, { x: 7, y: 0, w: 1, h: 1, active: true }, { x: 11, y: 0, w: 1, h: 1, active: true }], score: 0, enemyHits: 0, lives: 3 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; for (const pickup of state.pickups) { if (pickup.active && aura.collision.rectRect(state.player.x, state.player.y, state.player.w, state.player.h, pickup.x, pickup.y, pickup.w, pickup.h)) { pickup.active = false; state.score += 10; } } if (aura.collision.rectRect(state.player.x, state.player.y, state.player.w, state.player.h, state.enemy.x, state.enemy.y, state.enemy.w, state.enemy.h)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } return { score: state.score, inactivePickups: state.pickups.filter((pickup) => pickup.active === false).length, enemyHits: state.enemyHits, lives: state.lives }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.score === 30 && first.inactivePickups === 3 && first.enemyHits === 1 && first.lives === 2; })()",
3262
3934
  },
3263
3935
  {
3264
3936
  id: 'phaser.arcade.enemy-collision.single-hit',
3265
- expression: "(() => { const overlap = (a, b) => !(a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y); const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, lives: 3, enemyHits: 0 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; if (overlap(state.player, state.enemy)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } return state.enemyHits === 1 && state.lives === 2; })()",
3937
+ expression: "(() => { const runSample = () => { const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, lives: 3, enemyHits: 0 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; if (aura.collision.rectRect(state.player.x, state.player.y, state.player.w, state.player.h, state.enemy.x, state.enemy.y, state.enemy.w, state.enemy.h)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } const farApart = aura.collision.rectRect(0, 0, 1, 1, 9, 9, 1, 1); const overlaps = aura.collision.rectRect(0, 0, 2, 2, 1, 1, 2, 2); return { enemyHits: state.enemyHits, lives: state.lives, farApart, overlaps }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.enemyHits === 1 && first.lives === 2 && first.farApart === false && first.overlaps === true; })()",
3266
3938
  },
3267
3939
  {
3268
3940
  id: 'phaser.arcade.final-state.deterministic',
3269
- expression: "(() => { const overlap = (a, b) => !(a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y); const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, pickups: [{ x: 3, y: 0, w: 1, h: 1, active: true }, { x: 7, y: 0, w: 1, h: 1, active: true }, { x: 11, y: 0, w: 1, h: 1, active: true }], score: 0, enemyHits: 0, lives: 3 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; for (const pickup of state.pickups) { if (pickup.active && overlap(state.player, pickup)) { pickup.active = false; state.score += 10; } } if (overlap(state.player, state.enemy)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } return state.player.x === 14 && state.enemy.x === 4 && state.score === 30 && state.lives === 2 && state.enemyHits === 1; })()",
3941
+ expression: "(() => { const runSample = () => { const fixture = { width: 4, height: 1, tilewidth: 1, tileheight: 1, solidLayerNames: ['hazards'], layers: [ { name: 'ground', type: 'tilelayer', width: 4, height: 1, data: [1, 1, 1, 1] }, { name: 'hazards', type: 'tilelayer', width: 4, height: 1, data: [0, 0, 1, 0] } ], tilesets: [ { firstgid: 1, image: 'tiles.png', tilewidth: 1, tileheight: 1, tilecount: 1, columns: 1 } ] }; const mapId = aura.tilemap.import(fixture); const invalidPoint = aura.tilemap.queryPoint(mapId, { x: 'bad', y: 0 }); const invalidHandle = aura.tilemap.queryPoint(999999, { x: 0, y: 0 }); const state = { player: { x: 0, y: 0, w: 2, h: 2 }, enemy: { x: 11, y: 0, w: 2, h: 2, vx: -1 }, pickups: [{ x: 3, y: 0, w: 1, h: 1, active: true }, { x: 7, y: 0, w: 1, h: 1, active: true }, { x: 11, y: 0, w: 1, h: 1, active: true }], score: 0, enemyHits: 0, lives: 3, hazardContacts: 0 }; for (let frame = 0; frame < 7; frame += 1) { state.player.x += 2; state.enemy.x += state.enemy.vx; for (const pickup of state.pickups) { if (pickup.active && aura.collision.rectRect(state.player.x, state.player.y, state.player.w, state.player.h, pickup.x, pickup.y, pickup.w, pickup.h)) { pickup.active = false; state.score += 10; } } const hazard = aura.tilemap.queryPoint(mapId, { x: Math.min(3.5, (state.player.x / 4) + 0.5), y: 0.5 }); if (Array.isArray(hazard?.hits) && hazard.hits.length > 0) state.hazardContacts += 1; if (aura.collision.rectRect(state.player.x, state.player.y, state.player.w, state.player.h, state.enemy.x, state.enemy.y, state.enemy.w, state.enemy.h)) { state.enemyHits += 1; if (state.enemyHits === 1) state.lives -= 1; } } const unloaded = aura.tilemap.unload(mapId); return { playerX: state.player.x, enemyX: state.enemy.x, score: state.score, lives: state.lives, enemyHits: state.enemyHits, remainingPickups: state.pickups.filter((pickup) => pickup.active).length, hazardContacts: state.hazardContacts, invalidPoint: invalidPoint?.reasonCode || null, invalidHandle: invalidHandle?.reasonCode || null, unloaded }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.playerX === 14 && first.enemyX === 4 && first.score === 30 && first.lives === 2 && first.enemyHits === 1 && first.remainingPickups === 0 && first.hazardContacts === 2 && first.invalidPoint === 'invalid_point_args' && first.invalidHandle === 'invalid_map_handle' && first.unloaded === true; })()",
3270
3942
  },
3271
3943
  ],
3272
3944
  frames: 7,
@@ -3283,10 +3955,39 @@ export const DEFAULT_CONFORMANCE_CASES = [
3283
3955
  score: 0,
3284
3956
  lives: 3,
3285
3957
  enemyHits: 0,
3958
+ hazardContacts: 0,
3959
+ mapId: 0,
3960
+ invalidPointReason: null,
3961
+ invalidHandleReason: null,
3286
3962
  };
3287
3963
 
3288
- function __overlap(a, b) {
3289
- return !(a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y);
3964
+ function __createArcadeFixture() {
3965
+ return {
3966
+ width: 4,
3967
+ height: 1,
3968
+ tilewidth: 1,
3969
+ tileheight: 1,
3970
+ solidLayerNames: ['hazards'],
3971
+ layers: [
3972
+ {
3973
+ name: 'ground',
3974
+ type: 'tilelayer',
3975
+ width: 4,
3976
+ height: 1,
3977
+ data: [1, 1, 1, 1],
3978
+ },
3979
+ {
3980
+ name: 'hazards',
3981
+ type: 'tilelayer',
3982
+ width: 4,
3983
+ height: 1,
3984
+ data: [0, 0, 1, 0],
3985
+ },
3986
+ ],
3987
+ tilesets: [
3988
+ { firstgid: 1, image: 'tiles.png', tilewidth: 1, tileheight: 1, tilecount: 1, columns: 1 },
3989
+ ],
3990
+ };
3290
3991
  }
3291
3992
 
3292
3993
  function __stepArcade() {
@@ -3294,21 +3995,58 @@ export const DEFAULT_CONFORMANCE_CASES = [
3294
3995
  __arcade.enemy.x += __arcade.enemy.vx;
3295
3996
 
3296
3997
  for (const pickup of __arcade.pickups) {
3297
- if (pickup.active && __overlap(__arcade.player, pickup)) {
3998
+ if (
3999
+ pickup.active
4000
+ && aura.collision.rectRect(
4001
+ __arcade.player.x,
4002
+ __arcade.player.y,
4003
+ __arcade.player.w,
4004
+ __arcade.player.h,
4005
+ pickup.x,
4006
+ pickup.y,
4007
+ pickup.w,
4008
+ pickup.h,
4009
+ )
4010
+ ) {
3298
4011
  pickup.active = false;
3299
4012
  __arcade.score += 10;
3300
4013
  }
3301
4014
  }
3302
4015
 
3303
- if (__overlap(__arcade.player, __arcade.enemy)) {
4016
+ if (
4017
+ aura.collision.rectRect(
4018
+ __arcade.player.x,
4019
+ __arcade.player.y,
4020
+ __arcade.player.w,
4021
+ __arcade.player.h,
4022
+ __arcade.enemy.x,
4023
+ __arcade.enemy.y,
4024
+ __arcade.enemy.w,
4025
+ __arcade.enemy.h,
4026
+ )
4027
+ ) {
3304
4028
  __arcade.enemyHits += 1;
3305
4029
  if (__arcade.enemyHits === 1) {
3306
4030
  __arcade.lives -= 1;
3307
4031
  }
3308
4032
  }
4033
+
4034
+ const hazardProbe = aura.tilemap.queryPoint(
4035
+ __arcade.mapId,
4036
+ { x: Math.min(3.5, (__arcade.player.x / 4) + 0.5), y: 0.5 },
4037
+ );
4038
+ if (Array.isArray(hazardProbe?.hits) && hazardProbe.hits.length > 0) {
4039
+ __arcade.hazardContacts += 1;
4040
+ }
3309
4041
  }
3310
4042
 
3311
- aura.setup = function () {};
4043
+ aura.setup = function () {
4044
+ __arcade.mapId = aura.tilemap.import(__createArcadeFixture());
4045
+ const invalidPoint = aura.tilemap.queryPoint(__arcade.mapId, { x: 'bad', y: 0 });
4046
+ const invalidHandle = aura.tilemap.queryPoint(999999, { x: 0, y: 0 });
4047
+ __arcade.invalidPointReason = invalidPoint?.reasonCode || null;
4048
+ __arcade.invalidHandleReason = invalidHandle?.reasonCode || null;
4049
+ };
3312
4050
 
3313
4051
  aura.update = function () {
3314
4052
  if (__arcade.frame < 7) {
@@ -3332,6 +4070,11 @@ export const DEFAULT_CONFORMANCE_CASES = [
3332
4070
  aura.test.equal(__arcade.lives, 2, 'arcade life decrement should be deterministic');
3333
4071
  aura.test.equal(__arcade.enemyHits, 1, 'arcade enemy collision count should be deterministic');
3334
4072
  aura.test.equal(__arcade.pickups.filter((pickup) => pickup.active).length, 0, 'arcade pickups should all resolve');
4073
+ aura.test.equal(__arcade.hazardContacts, 2, 'arcade tilemap hazard contacts should be deterministic');
4074
+ aura.test.equal(__arcade.invalidPointReason, 'invalid_point_args', 'arcade invalid point reason code should be deterministic');
4075
+ aura.test.equal(__arcade.invalidHandleReason, 'invalid_map_handle', 'arcade invalid map reason code should be deterministic');
4076
+ aura.test.equal(aura.tilemap.unload(__arcade.mapId), true, 'arcade tilemap should unload after assertions');
4077
+ __arcade.mapId = 0;
3335
4078
  }
3336
4079
  };
3337
4080
  `,
@@ -3767,8 +4510,21 @@ function evaluateVerticalSliceModeBudget(mode, telemetry, budget) {
3767
4510
  };
3768
4511
  }
3769
4512
 
4513
+ function sortVerticalSliceModes(modeNames = []) {
4514
+ const order = new Map(PHASER_VERTICAL_SLICE_REQUIRED_MODES.map((modeName, index) => [modeName, index]));
4515
+ return [...new Set(
4516
+ (Array.isArray(modeNames) ? modeNames : [])
4517
+ .filter((modeName) => typeof modeName === 'string' && modeName.length > 0),
4518
+ )].sort((a, b) => (
4519
+ (order.has(a) ? order.get(a) : PHASER_VERTICAL_SLICE_REQUIRED_MODES.length)
4520
+ - (order.has(b) ? order.get(b) : PHASER_VERTICAL_SLICE_REQUIRED_MODES.length)
4521
+ || a.localeCompare(b)
4522
+ ));
4523
+ }
4524
+
3770
4525
  function buildVerticalSliceScenarioBudget({ modeStatus, executedModes, shimTelemetry, nativeTelemetry }) {
3771
4526
  const violations = [];
4527
+ const normalizedModes = sortVerticalSliceModes(executedModes);
3772
4528
  const pushModeStatusViolation = (modeName, status) => {
3773
4529
  violations.push({
3774
4530
  mode: modeName,
@@ -3779,8 +4535,24 @@ function buildVerticalSliceScenarioBudget({ modeStatus, executedModes, shimTelem
3779
4535
  message: `${modeName} mode conformance did not pass`,
3780
4536
  });
3781
4537
  };
4538
+ const pushModeCoverageViolation = (modeName) => {
4539
+ violations.push({
4540
+ mode: modeName,
4541
+ metric: `${modeName}.status`,
4542
+ reasonCode: `vertical_slice_mode_missing_${modeName}`,
4543
+ actual: 'missing',
4544
+ limit: 'passed',
4545
+ message: `${modeName} mode scenario coverage is missing`,
4546
+ });
4547
+ };
3782
4548
 
3783
- for (const modeName of executedModes) {
4549
+ for (const modeName of PHASER_VERTICAL_SLICE_REQUIRED_MODES) {
4550
+ if (!normalizedModes.includes(modeName)) {
4551
+ pushModeCoverageViolation(modeName);
4552
+ }
4553
+ }
4554
+
4555
+ for (const modeName of normalizedModes) {
3784
4556
  if (modeStatus[modeName] !== 'passed') {
3785
4557
  pushModeStatusViolation(modeName, modeStatus[modeName] || 'missing');
3786
4558
  continue;
@@ -3803,7 +4575,15 @@ function buildVerticalSliceScenarioBudget({ modeStatus, executedModes, shimTelem
3803
4575
  status,
3804
4576
  reasonCode,
3805
4577
  violations: violations.sort((a, b) => (
3806
- String(a.mode || '').localeCompare(String(b.mode || ''))
4578
+ (
4579
+ (PHASER_VERTICAL_SLICE_REQUIRED_MODES.indexOf(String(a.mode || '')) >= 0
4580
+ ? PHASER_VERTICAL_SLICE_REQUIRED_MODES.indexOf(String(a.mode || ''))
4581
+ : PHASER_VERTICAL_SLICE_REQUIRED_MODES.length)
4582
+ - (PHASER_VERTICAL_SLICE_REQUIRED_MODES.indexOf(String(b.mode || '')) >= 0
4583
+ ? PHASER_VERTICAL_SLICE_REQUIRED_MODES.indexOf(String(b.mode || ''))
4584
+ : PHASER_VERTICAL_SLICE_REQUIRED_MODES.length)
4585
+ )
4586
+ || String(a.mode || '').localeCompare(String(b.mode || ''))
3807
4587
  || String(a.metric || '').localeCompare(String(b.metric || ''))
3808
4588
  || String(a.reasonCode || '').localeCompare(String(b.reasonCode || ''))
3809
4589
  )),
@@ -3926,6 +4706,9 @@ function summarizePhaserVerticalSliceEvidence(cases) {
3926
4706
  passed: 0,
3927
4707
  failed: 0,
3928
4708
  missing: 0,
4709
+ requiredModes: [...PHASER_VERTICAL_SLICE_REQUIRED_MODES],
4710
+ modeScenarioCounts: Object.fromEntries(PHASER_VERTICAL_SLICE_REQUIRED_MODES.map((modeName) => [modeName, 0])),
4711
+ missingModeCoverage: [...PHASER_VERTICAL_SLICE_REQUIRED_MODES],
3929
4712
  },
3930
4713
  telemetry: {
3931
4714
  schemaVersion: 'aurajs.vertical-slice-telemetry.v1',
@@ -3952,12 +4735,19 @@ function summarizePhaserVerticalSliceEvidence(cases) {
3952
4735
  const scenarios = [...rowsByCaseId.values()]
3953
4736
  .sort((a, b) => a.caseId.localeCompare(b.caseId))
3954
4737
  .map((row) => {
3955
- const executedModes = [...row.executedModes].sort();
3956
- const failingModes = executedModes.filter((modeName) => row.modeStatus[modeName] !== 'passed');
4738
+ const executedModes = sortVerticalSliceModes(row.executedModes);
4739
+ const missingModes = PHASER_VERTICAL_SLICE_REQUIRED_MODES
4740
+ .filter((modeName) => !executedModes.includes(modeName));
4741
+ const failingModes = sortVerticalSliceModes([
4742
+ ...missingModes,
4743
+ ...executedModes.filter((modeName) => row.modeStatus[modeName] !== 'passed'),
4744
+ ]);
3957
4745
  const status = failingModes.length === 0 ? 'pass' : 'fail';
3958
- const reasonCode = status === 'pass'
3959
- ? 'phaser_vertical_slice_ok'
3960
- : `phaser_vertical_slice_failed_${failingModes.join('_')}`;
4746
+ const reasonCode = missingModes.length > 0
4747
+ ? `phaser_vertical_slice_mode_missing_${missingModes.join('_')}`
4748
+ : (status === 'pass'
4749
+ ? 'phaser_vertical_slice_ok'
4750
+ : `phaser_vertical_slice_failed_${failingModes.join('_')}`);
3961
4751
  const shimTelemetry = buildVerticalSliceShimTelemetry(row.shim);
3962
4752
  const nativeTelemetry = buildVerticalSliceNativeTelemetry(row.native);
3963
4753
  const budget = buildVerticalSliceScenarioBudget({
@@ -3970,6 +4760,7 @@ function summarizePhaserVerticalSliceEvidence(cases) {
3970
4760
  return {
3971
4761
  ...row,
3972
4762
  executedModes,
4763
+ missingModes,
3973
4764
  status,
3974
4765
  reasonCode,
3975
4766
  telemetry: {
@@ -3980,9 +4771,17 @@ function summarizePhaserVerticalSliceEvidence(cases) {
3980
4771
  };
3981
4772
  });
3982
4773
 
4774
+ const modeScenarioCounts = Object.fromEntries(
4775
+ PHASER_VERTICAL_SLICE_REQUIRED_MODES.map((modeName) => [
4776
+ modeName,
4777
+ scenarios.filter((row) => row.executedModes.includes(modeName)).length,
4778
+ ]),
4779
+ );
4780
+ const missingModeCoverage = PHASER_VERTICAL_SLICE_REQUIRED_MODES
4781
+ .filter((modeName) => modeScenarioCounts[modeName] <= 0);
3983
4782
  const failed = scenarios.filter((row) => row.status === 'fail').length;
3984
4783
  const passed = scenarios.length - failed;
3985
- const status = failed === 0 ? 'pass' : 'fail';
4784
+ const status = failed === 0 && missingModeCoverage.length === 0 ? 'pass' : 'fail';
3986
4785
  const budgetFailures = scenarios.filter((row) => row.budget?.status !== 'pass');
3987
4786
  const budgetViolations = budgetFailures
3988
4787
  .flatMap((row) => (row.budget?.violations || []).map((violation) => ({
@@ -4001,15 +4800,25 @@ function summarizePhaserVerticalSliceEvidence(cases) {
4001
4800
  const budgetReasonCode = budgetStatus === 'pass'
4002
4801
  ? 'vertical_slice_budget_ok'
4003
4802
  : (budgetViolations[0]?.reasonCode || 'vertical_slice_budget_failed');
4803
+ const reasonCode = status === 'pass'
4804
+ ? 'phaser_vertical_slice_pack_ok'
4805
+ : (
4806
+ missingModeCoverage.length > 0
4807
+ ? `phaser_vertical_slice_mode_coverage_missing_${missingModeCoverage.join('_')}`
4808
+ : 'phaser_vertical_slice_pack_failed'
4809
+ );
4004
4810
 
4005
4811
  return {
4006
4812
  status,
4007
- reasonCode: status === 'pass' ? 'phaser_vertical_slice_pack_ok' : 'phaser_vertical_slice_pack_failed',
4813
+ reasonCode,
4008
4814
  summary: {
4009
4815
  scenarios: scenarios.length,
4010
4816
  passed,
4011
4817
  failed,
4012
4818
  missing: 0,
4819
+ requiredModes: [...PHASER_VERTICAL_SLICE_REQUIRED_MODES],
4820
+ modeScenarioCounts,
4821
+ missingModeCoverage,
4013
4822
  },
4014
4823
  telemetry: {
4015
4824
  schemaVersion: 'aurajs.vertical-slice-telemetry.v1',
@@ -4141,6 +4950,11 @@ function normalizeRuntimeInspectorScene3dRuntime(scene3dRuntime) {
4141
4950
  nonDefaultTransforms: numericOr(scene3dRuntime?.submission?.nonDefaultTransforms),
4142
4951
  skyboxRequested: scene3dRuntime?.submission?.skyboxRequested === true,
4143
4952
  clearRequested: scene3dRuntime?.submission?.clearRequested === true,
4953
+ environmentMapConfigured: scene3dRuntime?.submission?.environmentMapConfigured === true,
4954
+ pointShadowLightCount: numericOr(scene3dRuntime?.submission?.pointShadowLightCount),
4955
+ spotShadowLightCount: numericOr(scene3dRuntime?.submission?.spotShadowLightCount),
4956
+ pointShadowPassCount: numericOr(scene3dRuntime?.submission?.pointShadowPassCount),
4957
+ spotShadowPassCount: numericOr(scene3dRuntime?.submission?.spotShadowPassCount),
4144
4958
  postfxPassCount: numericOr(scene3dRuntime?.submission?.postfxPassCount),
4145
4959
  postfxEnabledPassCount: numericOr(scene3dRuntime?.submission?.postfxEnabledPassCount),
4146
4960
  postfxOrderFingerprint: numericOr(scene3dRuntime?.submission?.postfxOrderFingerprint),
@@ -4160,6 +4974,19 @@ function normalizeRuntimeInspectorScene3dRuntime(scene3dRuntime) {
4160
4974
  ? scene3dRuntime.submission.postfxLastReasonCode
4161
4975
  : 'postfx_init',
4162
4976
  postfxLastOk: scene3dRuntime?.submission?.postfxLastOk !== false,
4977
+ scene3dSubmitted: numericOr(scene3dRuntime?.submission?.scene3dSubmitted),
4978
+ scene3dCulledHidden: numericOr(scene3dRuntime?.submission?.scene3dCulledHidden),
4979
+ scene3dCulledLayer: numericOr(scene3dRuntime?.submission?.scene3dCulledLayer),
4980
+ scene3dCulledBounds: numericOr(scene3dRuntime?.submission?.scene3dCulledBounds),
4981
+ scene3dSkippedMissingNode: numericOr(scene3dRuntime?.submission?.scene3dSkippedMissingNode),
4982
+ scene3dLayerMask: numericOr(scene3dRuntime?.submission?.scene3dLayerMask),
4983
+ scene3dCullBoundsEnabled: scene3dRuntime?.submission?.scene3dCullBoundsEnabled === true,
4984
+ scene3dRenderOrderFingerprint: numericOr(
4985
+ scene3dRuntime?.submission?.scene3dRenderOrderFingerprint,
4986
+ ),
4987
+ scene3dLastReasonCode: typeof scene3dRuntime?.submission?.scene3dLastReasonCode === 'string'
4988
+ ? scene3dRuntime.submission.scene3dLastReasonCode
4989
+ : 'scene3d_render_idle',
4163
4990
  },
4164
4991
  camera: {
4165
4992
  mode: typeof scene3dRuntime?.camera?.mode === 'string'
@@ -4202,6 +5029,7 @@ function normalizeRuntimeInspectorScene3dRuntime(scene3dRuntime) {
4202
5029
  materialNextHandleId: numericOr(scene3dRuntime?.resources?.materialNextHandleId, 1),
4203
5030
  lightDirectionalActive: scene3dRuntime?.resources?.lightDirectionalActive === true,
4204
5031
  lightPointCount: numericOr(scene3dRuntime?.resources?.lightPointCount),
5032
+ lightSpotCount: numericOr(scene3dRuntime?.resources?.lightSpotCount),
4205
5033
  lightAmbientIntensity: normalizeRuntimeInspectorTiming(
4206
5034
  scene3dRuntime?.resources?.lightAmbientIntensity,
4207
5035
  RUNTIME_INSPECTOR_TIMING_PRECISION.scalarDigits,
@@ -4478,6 +5306,9 @@ export function buildPhaserVerticalSliceEvidenceReport({
4478
5306
  passed: 0,
4479
5307
  failed: 0,
4480
5308
  missing: 0,
5309
+ requiredModes: [...PHASER_VERTICAL_SLICE_REQUIRED_MODES],
5310
+ modeScenarioCounts: Object.fromEntries(PHASER_VERTICAL_SLICE_REQUIRED_MODES.map((modeName) => [modeName, 0])),
5311
+ missingModeCoverage: [...PHASER_VERTICAL_SLICE_REQUIRED_MODES],
4481
5312
  },
4482
5313
  telemetry: phaserVerticalSliceEvidence?.telemetry || {
4483
5314
  schemaVersion: 'aurajs.vertical-slice-telemetry.v1',
@@ -4746,6 +5577,11 @@ function buildNativeProbeSource(conformanceCase, options = {}) {
4746
5577
  nonDefaultTransforms: __num(scene3dSubmission.nonDefaultTransforms),
4747
5578
  skyboxRequested: scene3dSubmission.skyboxRequested === true,
4748
5579
  clearRequested: scene3dSubmission.clearRequested === true,
5580
+ environmentMapConfigured: scene3dSubmission.environmentMapConfigured === true,
5581
+ pointShadowLightCount: __num(scene3dSubmission.pointShadowLightCount),
5582
+ spotShadowLightCount: __num(scene3dSubmission.spotShadowLightCount),
5583
+ pointShadowPassCount: __num(scene3dSubmission.pointShadowPassCount),
5584
+ spotShadowPassCount: __num(scene3dSubmission.spotShadowPassCount),
4749
5585
  postfxPassCount: __num(scene3dSubmission.postfxPassCount),
4750
5586
  postfxEnabledPassCount: __num(scene3dSubmission.postfxEnabledPassCount),
4751
5587
  postfxOrderFingerprint: __num(scene3dSubmission.postfxOrderFingerprint),
@@ -4792,6 +5628,7 @@ function buildNativeProbeSource(conformanceCase, options = {}) {
4792
5628
  materialNextHandleId: __num(scene3dResources.materialNextHandleId, 1),
4793
5629
  lightDirectionalActive: scene3dResources.lightDirectionalActive === true,
4794
5630
  lightPointCount: __num(scene3dResources.lightPointCount),
5631
+ lightSpotCount: __num(scene3dResources.lightSpotCount),
4795
5632
  lightAmbientIntensity: __num(scene3dResources.lightAmbientIntensity),
4796
5633
  lightCameraPosition: toVec3(scene3dResources.lightCameraPosition),
4797
5634
  },
@@ -5019,6 +5856,8 @@ async function runNativeConformanceCase({
5019
5856
  hostBinaryPath: binaryPath,
5020
5857
  hostPackageName: resolvedHost.packageName,
5021
5858
  hostBinarySource: resolvedHost.source || null,
5859
+ hostBinaryTarget: resolvedHost.target || null,
5860
+ hostBinaryReasonCode: resolvedHost.reasonCode || null,
5022
5861
  hostBinaryDiagnostics: resolvedHost.diagnostics || [],
5023
5862
  hostBinaryResolution: resolvedHost,
5024
5863
  };
@@ -5073,10 +5912,33 @@ function extractHostMetadataCandidate(entry) {
5073
5912
  || details?.hostBinarySource
5074
5913
  || resolution?.source,
5075
5914
  );
5915
+ const target = asNonEmptyString(
5916
+ native?.hostBinaryTarget
5917
+ || details?.hostBinaryTarget
5918
+ || resolution?.target,
5919
+ );
5920
+ const selectionKind = asNonEmptyString(
5921
+ resolution?.localBuild?.selected?.kind
5922
+ || details?.hostBinarySelectionKind,
5923
+ );
5076
5924
  const resolvedAt = asNonEmptyString(resolution?.resolvedAt || details?.hostBinaryResolvedAt);
5077
- const reasonCode = asNonEmptyString(details?.reasonCode || details?.code);
5925
+ const reasonCode = asNonEmptyString(
5926
+ details?.reasonCode
5927
+ || details?.code
5928
+ || native?.hostBinaryReasonCode
5929
+ || resolution?.reasonCode,
5930
+ );
5078
5931
 
5079
- if (!path && !packageName && !source && !resolvedAt && !reasonCode && diagnostics.length === 0) {
5932
+ if (
5933
+ !path
5934
+ && !packageName
5935
+ && !source
5936
+ && !target
5937
+ && !selectionKind
5938
+ && !resolvedAt
5939
+ && !reasonCode
5940
+ && diagnostics.length === 0
5941
+ ) {
5080
5942
  return null;
5081
5943
  }
5082
5944
 
@@ -5084,6 +5946,8 @@ function extractHostMetadataCandidate(entry) {
5084
5946
  path,
5085
5947
  packageName,
5086
5948
  source: source || (path ? 'unknown' : null),
5949
+ target,
5950
+ selectionKind,
5087
5951
  diagnostics,
5088
5952
  resolvedAt,
5089
5953
  reasonCode: reasonCode || null,
@@ -5097,34 +5961,77 @@ function summarizeHostBinaryMetadata(nativeCases) {
5097
5961
 
5098
5962
  if (rows.length === 0) {
5099
5963
  if (!Array.isArray(nativeCases) || nativeCases.length === 0) {
5100
- return {
5101
- path: null,
5102
- packageName: null,
5103
- source: null,
5104
- diagnostics: [],
5105
- resolvedAt: null,
5106
- reasonCode: 'host_binary_not_required',
5107
- };
5108
- }
5109
-
5110
5964
  return {
5111
5965
  path: null,
5112
5966
  packageName: null,
5113
5967
  source: null,
5968
+ target: null,
5969
+ selectionKind: null,
5970
+ diagnostics: [],
5971
+ resolvedAt: null,
5972
+ consistency: {
5973
+ caseCount: Array.isArray(nativeCases) ? nativeCases.length : 0,
5974
+ distinctSourceCount: 0,
5975
+ distinctPathCount: 0,
5976
+ distinctPackageCount: 0,
5977
+ sameSource: true,
5978
+ samePath: true,
5979
+ samePackageName: true,
5980
+ },
5981
+ reasonCode: 'host_binary_not_required',
5982
+ };
5983
+ }
5984
+
5985
+ return {
5986
+ path: null,
5987
+ packageName: null,
5988
+ source: null,
5989
+ target: null,
5990
+ selectionKind: null,
5114
5991
  diagnostics: [],
5115
5992
  resolvedAt: null,
5993
+ consistency: {
5994
+ caseCount: nativeCases.length,
5995
+ distinctSourceCount: 0,
5996
+ distinctPathCount: 0,
5997
+ distinctPackageCount: 0,
5998
+ sameSource: true,
5999
+ samePath: true,
6000
+ samePackageName: true,
6001
+ },
5116
6002
  reasonCode: 'host_binary_metadata_missing',
5117
6003
  };
5118
6004
  }
5119
6005
 
6006
+ const distinctSources = collectUniqueStringValues(rows.map((row) => row.source));
6007
+ const distinctPaths = collectUniqueStringValues(rows.map((row) => row.path));
6008
+ const distinctPackages = collectUniqueStringValues(rows.map((row) => row.packageName));
6009
+ const consistency = {
6010
+ caseCount: rows.length,
6011
+ distinctSourceCount: distinctSources.length,
6012
+ distinctPathCount: distinctPaths.length,
6013
+ distinctPackageCount: distinctPackages.length,
6014
+ sameSource: distinctSources.length <= 1,
6015
+ samePath: distinctPaths.length <= 1,
6016
+ samePackageName: distinctPackages.length <= 1,
6017
+ };
6018
+ const consistencyReasonCode = (
6019
+ !consistency.sameSource || !consistency.samePath || !consistency.samePackageName
6020
+ )
6021
+ ? 'host_binary_metadata_inconsistent'
6022
+ : null;
6023
+
5120
6024
  const first = rows[0];
5121
6025
  return {
5122
6026
  path: first.path || null,
5123
6027
  packageName: first.packageName || null,
5124
6028
  source: first.source || null,
6029
+ target: first.target || null,
6030
+ selectionKind: first.selectionKind || null,
5125
6031
  diagnostics: collectUniqueStringValues(rows.flatMap((row) => row.diagnostics || [])),
5126
6032
  resolvedAt: first.resolvedAt || null,
5127
- reasonCode: first.reasonCode || 'host_binary_metadata_resolved',
6033
+ consistency,
6034
+ reasonCode: consistencyReasonCode || first.reasonCode || 'host_binary_metadata_resolved',
5128
6035
  };
5129
6036
  }
5130
6037
 
@@ -5149,6 +6056,12 @@ export async function runConformanceSuite(options = {}) {
5149
6056
  const nativeRunner = options.nativeRunner || runNativeConformanceCase;
5150
6057
 
5151
6058
  mkdirSync(resolve(workspaceRoot, 'src'), { recursive: true });
6059
+ const sceneFixtureSource = resolve(projectRoot, 'test-fixtures', 'scene3d', 'import-scene.gltf');
6060
+ const sceneFixtureTarget = resolve(workspaceRoot, 'src', 'cli', 'test-fixtures', 'scene3d', 'import-scene.gltf');
6061
+ if (existsSync(sceneFixtureSource)) {
6062
+ mkdirSync(dirname(sceneFixtureTarget), { recursive: true });
6063
+ copyFileSync(sceneFixtureSource, sceneFixtureTarget);
6064
+ }
5152
6065
 
5153
6066
  const modeCases = {
5154
6067
  shim: [],
@@ -5166,7 +6079,12 @@ export async function runConformanceSuite(options = {}) {
5166
6079
  if (shouldRunShim(mode) && caseModes.has('shim')) {
5167
6080
  const entryRelative = `src/${safeCaseId}.js`;
5168
6081
  const entryAbsolute = resolve(workspaceRoot, entryRelative);
5169
- writeFileSync(entryAbsolute, conformanceCase.source.trimStart(), 'utf8');
6082
+ const shimBootstrapSource = buildGameStateHookBootstrapSource('headless');
6083
+ writeFileSync(
6084
+ entryAbsolute,
6085
+ `${shimBootstrapSource}\n${conformanceCase.source.trimStart()}`,
6086
+ 'utf8',
6087
+ );
5170
6088
 
5171
6089
  const shimStartedAt = nowMs();
5172
6090
  try {
@@ -5211,13 +6129,14 @@ export async function runConformanceSuite(options = {}) {
5211
6129
  }
5212
6130
 
5213
6131
  if (shouldRunNative(mode) && caseModes.has('native')) {
6132
+ const nativeBootstrapSource = buildGameStateHookBootstrapSource('native');
5214
6133
  const nativeSource = (
5215
6134
  conformanceCase.nativeSource
5216
6135
  || buildNativeProbeSource(conformanceCase, { runtimeInspectorSnapshotEnabled })
5217
6136
  ).trimStart();
5218
6137
  const nativeEntryRelative = `src/native-${safeCaseId}.js`;
5219
6138
  const nativeEntryAbsolute = resolve(workspaceRoot, nativeEntryRelative);
5220
- writeFileSync(nativeEntryAbsolute, nativeSource, 'utf8');
6139
+ writeFileSync(nativeEntryAbsolute, `${nativeBootstrapSource}\n${nativeSource}`, 'utf8');
5221
6140
 
5222
6141
  const nativeStartedAt = nowMs();
5223
6142
  try {
@@ -5243,6 +6162,8 @@ export async function runConformanceSuite(options = {}) {
5243
6162
  hostBinaryPath: native.hostBinaryPath,
5244
6163
  hostPackageName: native.hostPackageName || null,
5245
6164
  hostBinarySource: native.hostBinarySource || null,
6165
+ hostBinaryTarget: native.hostBinaryTarget || null,
6166
+ hostBinaryReasonCode: native.hostBinaryReasonCode || null,
5246
6167
  hostBinaryDiagnostics: native.hostBinaryDiagnostics || [],
5247
6168
  hostBinaryResolvedAt: native.hostBinaryResolution?.resolvedAt || null,
5248
6169
  });
@@ -5257,6 +6178,8 @@ export async function runConformanceSuite(options = {}) {
5257
6178
  hostBinaryPath: native.hostBinaryPath,
5258
6179
  hostPackageName: native.hostPackageName || null,
5259
6180
  hostBinarySource: native.hostBinarySource || null,
6181
+ hostBinaryTarget: native.hostBinaryTarget || null,
6182
+ hostBinaryReasonCode: native.hostBinaryReasonCode || null,
5260
6183
  hostBinaryDiagnostics: native.hostBinaryDiagnostics || [],
5261
6184
  hostBinaryResolvedAt: native.hostBinaryResolution?.resolvedAt || null,
5262
6185
  });
@@ -5271,6 +6194,8 @@ export async function runConformanceSuite(options = {}) {
5271
6194
  hostBinaryPath: native.hostBinaryPath,
5272
6195
  hostPackageName: native.hostPackageName,
5273
6196
  hostBinarySource: native.hostBinarySource || null,
6197
+ hostBinaryTarget: native.hostBinaryTarget || null,
6198
+ hostBinaryReasonCode: native.hostBinaryReasonCode || null,
5274
6199
  hostBinaryDiagnostics: native.hostBinaryDiagnostics || [],
5275
6200
  hostBinaryResolvedAt: native.hostBinaryResolution?.resolvedAt || null,
5276
6201
  });
@@ -5296,6 +6221,8 @@ export async function runConformanceSuite(options = {}) {
5296
6221
  hostBinaryPath: native.hostBinaryPath,
5297
6222
  hostPackageName: native.hostPackageName,
5298
6223
  hostBinarySource: native.hostBinarySource || null,
6224
+ hostBinaryTarget: native.hostBinaryTarget || null,
6225
+ hostBinaryReasonCode: native.hostBinaryReasonCode || null,
5299
6226
  hostBinaryDiagnostics: native.hostBinaryDiagnostics || [],
5300
6227
  },
5301
6228
  });