@auraindustry/aurajs 0.1.1 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +7 -0
  2. package/benchmarks/perf-thresholds.json +27 -0
  3. package/package.json +6 -1
  4. package/src/ai-guidance.mjs +302 -0
  5. package/src/asset-pack.mjs +2 -1
  6. package/src/authored-project.mjs +498 -2
  7. package/src/authored-runtime.mjs +14 -0
  8. package/src/bin-integrity.mjs +33 -26
  9. package/src/build-contract/capabilities.mjs +87 -1
  10. package/src/build-contract/constants.mjs +1 -0
  11. package/src/build-contract.mjs +2 -0
  12. package/src/bundler.mjs +143 -13
  13. package/src/cli.mjs +681 -13
  14. package/src/commands/packs.mjs +741 -0
  15. package/src/commands/project-authoring.mjs +128 -1
  16. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
  17. package/src/conformance/cases/core-runtime-cases.mjs +6 -2
  18. package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
  19. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1126 -10
  20. package/src/conformance-mobile.mjs +166 -0
  21. package/src/conformance.mjs +89 -30
  22. package/src/evidence-bundle.mjs +242 -0
  23. package/src/external-package-surface.mjs +1 -1
  24. package/src/headless-test/runtime-coordinator.mjs +186 -33
  25. package/src/headless-test.mjs +2 -0
  26. package/src/helpers/2d/index.mjs +183 -0
  27. package/src/helpers/index.mjs +26 -0
  28. package/src/helpers/starter-utils/adventure-objectives.js +102 -0
  29. package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
  30. package/src/helpers/starter-utils/animation-2d.js +337 -0
  31. package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
  32. package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
  33. package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
  34. package/src/helpers/starter-utils/avatar-3d.js +404 -0
  35. package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
  36. package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
  37. package/src/helpers/starter-utils/core.js +150 -0
  38. package/src/helpers/starter-utils/dialogue-2d.js +351 -0
  39. package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
  40. package/src/helpers/starter-utils/index.js +26 -0
  41. package/src/helpers/starter-utils/inventory-2d.js +268 -0
  42. package/src/helpers/starter-utils/journal-2d.js +267 -0
  43. package/src/helpers/starter-utils/platformer-3d.js +132 -0
  44. package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
  45. package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
  46. package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
  47. package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
  48. package/src/helpers/starter-utils/triggers.js +662 -0
  49. package/src/helpers/starter-utils/tween-2d.js +615 -0
  50. package/src/helpers/starter-utils/wave-director.js +101 -0
  51. package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
  52. package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
  53. package/src/mobile/android/build.mjs +606 -0
  54. package/src/mobile/android/host-artifact.mjs +280 -0
  55. package/src/mobile/ios/build.mjs +1323 -0
  56. package/src/mobile/ios/host-artifact.mjs +819 -0
  57. package/src/mobile/shared/capabilities.mjs +174 -0
  58. package/src/package-integrity.mjs +18 -4
  59. package/src/packs/catalog.mjs +259 -0
  60. package/src/perf-benchmark-runner.mjs +17 -12
  61. package/src/perf-benchmark.mjs +408 -4
  62. package/src/publish-command.mjs +434 -17
  63. package/src/publish-validation.mjs +22 -11
  64. package/src/replay-runtime.mjs +257 -0
  65. package/src/scaffold/config.mjs +2 -0
  66. package/src/scaffold/fs.mjs +8 -1
  67. package/src/scaffold/project-docs.mjs +101 -41
  68. package/src/scaffold.mjs +4 -0
  69. package/src/session-runtime.mjs +4 -3
  70. package/src/web-conformance.mjs +0 -36
  71. package/templates/create/2d/src/runtime/app.js +4 -0
  72. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
  73. package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
  74. package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
  75. package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
  76. package/templates/create/2d-adventure/docs/design/loop.md +4 -3
  77. package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
  78. package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
  79. package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
  80. package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
  81. package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
  82. package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
  83. package/templates/create/2d-survivor/src/runtime/app.js +4 -0
  84. package/templates/create/3d/scenes/gameplay.scene.js +30 -3
  85. package/templates/create/3d/src/runtime/app.js +4 -0
  86. package/templates/create/3d/src/runtime/capabilities.js +5 -0
  87. package/templates/create/3d/src/runtime/materials.js +10 -0
  88. package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
  89. package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
  90. package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
  91. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
  92. package/templates/create/3d-collectathon/src/runtime/app.js +4 -0
  93. package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
  94. package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
  95. package/templates/create/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
  96. package/templates/create/blank/assets/splash/bg.webp +0 -0
  97. package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
  98. package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
  99. package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
  100. package/templates/create/blank/assets/splash/logoholo.webp +0 -0
  101. package/templates/create/blank/src/main.js +5 -1
  102. package/templates/create/blank/src/runtime/splash.js +305 -0
  103. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
  104. package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
  105. package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
  106. package/templates/create/shared/assets/splash/bg.webp +0 -0
  107. package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
  108. package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
  109. package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
  110. package/templates/create/shared/assets/splash/logoholo.webp +0 -0
  111. package/templates/create/shared/src/runtime/splash.js +305 -0
  112. package/templates/create/shared/src/runtime/ui-forms.js +552 -0
  113. package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
  114. package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
  115. package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
  116. package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
  117. package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
  118. package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
  119. package/templates/create/shared/src/starter-utils/index.js +15 -1
  120. package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
  121. package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
  122. package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
  123. package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
  124. package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
  125. package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
  126. package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
  127. package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
  128. package/templates/create/video-cutscene/src/runtime/app.js +4 -0
  129. package/templates/create-bin/play.js +148 -7
  130. package/templates/skills/auramaxx/SKILL.md +46 -0
  131. package/templates/skills/auramaxx/project-requirements.md +68 -0
  132. package/templates/skills/auramaxx/starter-recipes.md +104 -0
  133. package/templates/skills/auramaxx/validation-checklist.md +49 -0
  134. package/templates/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
  135. package/templates/starter/assets/splash/bg.webp +0 -0
  136. package/templates/starter/assets/splash/boot-loop.wav +0 -0
  137. package/templates/starter/assets/splash/boot-sting.wav +0 -0
  138. package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
  139. package/templates/starter/assets/splash/logoholo.webp +0 -0
  140. package/templates/starter/src/main.js +4 -0
  141. package/templates/starter/src/runtime/splash.js +305 -0
  142. package/templates/skills/aurajs/SKILL.md +0 -96
  143. package/templates/skills/aurajs/api-contract-3d.md +0 -7
  144. package/templates/skills/aurajs/api-contract.md +0 -7
@@ -375,9 +375,24 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
375
375
  'aura.multiplayer.sendInput',
376
376
  'aura.multiplayer.getPlayerInput',
377
377
  'aura.multiplayer.getAllPlayerInputs',
378
+ 'aura.multiplayer.getRoomInfo',
379
+ 'aura.multiplayer.getNetworkDiagnostics',
380
+ 'aura.multiplayer.configureNetworkImpairment',
381
+ 'aura.multiplayer.clearNetworkImpairment',
378
382
  'aura.multiplayer.setState',
379
383
  'aura.multiplayer.getState',
380
384
  'aura.multiplayer.getAllState',
385
+ 'aura.multiplayer.getInterpolatedState',
386
+ 'aura.multiplayer.getInterpolatedAllState',
387
+ 'aura.multiplayer.configureRollbackLane',
388
+ 'aura.multiplayer.getRollbackState',
389
+ 'aura.multiplayer.getRollbackAllState',
390
+ 'aura.multiplayer.clearRollbackLane',
391
+ 'aura.multiplayer.getRollbackDiagnostics',
392
+ 'aura.multiplayer.configureLocalPrediction',
393
+ 'aura.multiplayer.setLocalPredictedState',
394
+ 'aura.multiplayer.clearLocalPrediction',
395
+ 'aura.multiplayer.getLocalPredictionDiagnostics',
381
396
  'aura.multiplayer.onMessage',
382
397
  'aura.multiplayer.onStateUpdate',
383
398
  'aura.multiplayer.onPlayerJoin',
@@ -398,11 +413,11 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
398
413
  nativeChecks: [
399
414
  {
400
415
  id: 'optional.multiplayer.disabled.guidance',
401
- 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, () => {})); })()",
416
+ 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.getRoomInfo()) && guidance(() => aura.multiplayer.getNetworkDiagnostics()) && guidance(() => aura.multiplayer.configureNetworkImpairment({ latencyMs: 1 })) && guidance(() => aura.multiplayer.clearNetworkImpairment()) && guidance(() => aura.multiplayer.setState('room', { phase: 'lobby' })) && guidance(() => aura.multiplayer.getState('room')) && guidance(() => aura.multiplayer.getAllState()) && guidance(() => aura.multiplayer.getInterpolatedState('room')) && guidance(() => aura.multiplayer.getInterpolatedAllState()) && guidance(() => aura.multiplayer.configureRollbackLane({ key: 'player_1', speed: 100, historyLimit: 8, bounds: { minX: 0, maxX: 100, minY: 0, maxY: 100 } })) && guidance(() => aura.multiplayer.getRollbackState('player_1')) && guidance(() => aura.multiplayer.getRollbackAllState()) && guidance(() => aura.multiplayer.clearRollbackLane()) && guidance(() => aura.multiplayer.getRollbackDiagnostics()) && guidance(() => aura.multiplayer.configureLocalPrediction({ key: 'player_1' })) && guidance(() => aura.multiplayer.setLocalPredictedState({ x: 1, y: 2 })) && guidance(() => aura.multiplayer.clearLocalPrediction()) && guidance(() => aura.multiplayer.getLocalPredictionDiagnostics()) && 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, () => {})); })()",
402
417
  },
403
418
  {
404
419
  id: 'optional.multiplayer.disabled.reason-codes',
405
- 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, () => {})); })()",
420
+ 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('getRoomInfo', () => aura.multiplayer.getRoomInfo()) && reasonCode('getNetworkDiagnostics', () => aura.multiplayer.getNetworkDiagnostics()) && reasonCode('configureNetworkImpairment', () => aura.multiplayer.configureNetworkImpairment({ latencyMs: 1 })) && reasonCode('clearNetworkImpairment', () => aura.multiplayer.clearNetworkImpairment()) && reasonCode('setState', () => aura.multiplayer.setState('room', { phase: 'lobby' })) && reasonCode('getState', () => aura.multiplayer.getState('room')) && reasonCode('getAllState', () => aura.multiplayer.getAllState()) && reasonCode('getInterpolatedState', () => aura.multiplayer.getInterpolatedState('room')) && reasonCode('getInterpolatedAllState', () => aura.multiplayer.getInterpolatedAllState()) && reasonCode('configureRollbackLane', () => aura.multiplayer.configureRollbackLane({ key: 'player_1', speed: 100, historyLimit: 8, bounds: { minX: 0, maxX: 100, minY: 0, maxY: 100 } })) && reasonCode('getRollbackState', () => aura.multiplayer.getRollbackState('player_1')) && reasonCode('getRollbackAllState', () => aura.multiplayer.getRollbackAllState()) && reasonCode('clearRollbackLane', () => aura.multiplayer.clearRollbackLane()) && reasonCode('getRollbackDiagnostics', () => aura.multiplayer.getRollbackDiagnostics()) && reasonCode('configureLocalPrediction', () => aura.multiplayer.configureLocalPrediction({ key: 'player_1' })) && reasonCode('setLocalPredictedState', () => aura.multiplayer.setLocalPredictedState({ x: 1, y: 2 })) && reasonCode('clearLocalPrediction', () => aura.multiplayer.clearLocalPrediction()) && reasonCode('getLocalPredictionDiagnostics', () => aura.multiplayer.getLocalPredictionDiagnostics()) && 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, () => {})); })()",
406
421
  },
407
422
  ],
408
423
  nativeFrames: 2,
@@ -429,6 +444,10 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
429
444
  'aura.multiplayer.sendInput',
430
445
  'aura.multiplayer.getPlayerInput',
431
446
  'aura.multiplayer.getAllPlayerInputs',
447
+ 'aura.multiplayer.getRoomInfo',
448
+ 'aura.multiplayer.getNetworkDiagnostics',
449
+ 'aura.multiplayer.configureNetworkImpairment',
450
+ 'aura.multiplayer.clearNetworkImpairment',
432
451
  'aura.multiplayer.send',
433
452
  'aura.multiplayer.broadcast',
434
453
  'aura.multiplayer.onMessage',
@@ -440,6 +459,17 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
440
459
  'aura.multiplayer.setState',
441
460
  'aura.multiplayer.getState',
442
461
  'aura.multiplayer.getAllState',
462
+ 'aura.multiplayer.getInterpolatedState',
463
+ 'aura.multiplayer.getInterpolatedAllState',
464
+ 'aura.multiplayer.configureRollbackLane',
465
+ 'aura.multiplayer.getRollbackState',
466
+ 'aura.multiplayer.getRollbackAllState',
467
+ 'aura.multiplayer.clearRollbackLane',
468
+ 'aura.multiplayer.getRollbackDiagnostics',
469
+ 'aura.multiplayer.configureLocalPrediction',
470
+ 'aura.multiplayer.setLocalPredictedState',
471
+ 'aura.multiplayer.clearLocalPrediction',
472
+ 'aura.multiplayer.getLocalPredictionDiagnostics',
443
473
  'aura.multiplayer.onStateUpdate',
444
474
  'aura.multiplayer.onPlayerJoin',
445
475
  'aura.multiplayer.onPlayerLeave',
@@ -454,11 +484,11 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
454
484
  nativeChecks: [
455
485
  {
456
486
  id: 'optional.multiplayer.enabled.runtime',
457
- 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; } })()",
487
+ 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', 'getRoomInfo', 'getNetworkDiagnostics', 'configureNetworkImpairment', 'clearNetworkImpairment', 'send', 'broadcast', 'onMessage', 'kick', 'setPlayerData', 'getPlayerData', 'advertise', 'discover', 'setState', 'getState', 'getAllState', 'getInterpolatedState', 'getInterpolatedAllState', 'configureRollbackLane', 'getRollbackState', 'getRollbackAllState', 'clearRollbackLane', 'getRollbackDiagnostics', 'configureLocalPrediction', 'setLocalPredictedState', 'clearLocalPrediction', 'getLocalPredictionDiagnostics', '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(), roomInfo: api.getRoomInfo(), networkDiagnostics: api.getNetworkDiagnostics(), state: api.getState('room'), allState: api.getAllState(), interpolatedState: api.getInterpolatedState('room'), interpolatedAllState: api.getInterpolatedAllState(), rollbackState: api.getRollbackState('player_1'), rollbackAllState: api.getRollbackAllState(), rollbackDiagnostics: api.getRollbackDiagnostics(), localPredictionDiagnostics: api.getLocalPredictionDiagnostics(), 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.roomInfo === null || typeof status.roomInfo === 'object') && status.networkDiagnostics && typeof status.networkDiagnostics === 'object' && status.allState && typeof status.allState === 'object' && status.interpolatedAllState && typeof status.interpolatedAllState === 'object' && status.rollbackAllState && typeof status.rollbackAllState === 'object' && status.rollbackDiagnostics && typeof status.rollbackDiagnostics === 'object' && status.localPredictionDiagnostics && typeof status.localPredictionDiagnostics === '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') && (status.interpolatedState === null || typeof status.interpolatedState === 'object' || typeof status.interpolatedState === 'string' || typeof status.interpolatedState === 'number' || typeof status.interpolatedState === 'boolean') && (status.rollbackState === null || typeof status.rollbackState === 'object' || typeof status.rollbackState === 'string' || typeof status.rollbackState === 'number' || typeof status.rollbackState === '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 rollbackConfigureStatus = notDisabledOrPlaceholder(() => api.configureRollbackLane({ key: 'player_1', speed: 100, historyLimit: 8, bounds: { minX: 0, maxX: 100, minY: 0, maxY: 100 } }), (value) => value === true); const predictionConfigureStatus = notDisabledOrPlaceholder(() => api.configureLocalPrediction({ key: 'player_1' }), (value) => value === true); const impairmentConfigureStatus = notDisabledOrPlaceholder(() => api.configureNetworkImpairment({ latencyMs: 5, jitterMs: 1, lossRate: 0.25, seed: 7 }), (value) => value === true); const predictionSetStatus = notDisabledOrPlaceholder(() => api.setLocalPredictedState({ x: 1, y: 2, hidden: false }), (value) => typeof value === 'boolean'); const rollbackClearStatus = notDisabledOrPlaceholder(() => api.clearRollbackLane(), (value) => typeof value === 'boolean'); const predictionClearStatus = notDisabledOrPlaceholder(() => api.clearLocalPrediction(), (value) => typeof value === 'boolean'); const impairmentClearStatus = notDisabledOrPlaceholder(() => api.clearNetworkImpairment(), (value) => typeof value === 'boolean'); const rollbackDiagnosticsStatus = notDisabledOrPlaceholder(() => api.getRollbackDiagnostics(), (value) => value && typeof value === 'object'); const predictionDiagnosticsStatus = notDisabledOrPlaceholder(() => api.getLocalPredictionDiagnostics(), (value) => value && typeof value === 'object'); 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 && rollbackConfigureStatus && predictionConfigureStatus && impairmentConfigureStatus && predictionSetStatus && rollbackClearStatus && predictionClearStatus && impairmentClearStatus && rollbackDiagnosticsStatus && predictionDiagnosticsStatus && 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; } })()",
458
488
  },
459
489
  {
460
490
  id: 'optional.multiplayer.enabled.invalid-input.reason-codes',
461
- 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'); })()",
491
+ 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.getInterpolatedState(''), 'aura.multiplayer.getInterpolatedState(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.configureRollbackLane('bad'), 'aura.multiplayer.configureRollbackLane(options): options must be an object') && expectThrow(() => aura.multiplayer.configureRollbackLane({ key: '' }), 'aura.multiplayer.configureRollbackLane(options): key must be a non-empty string') && expectThrow(() => aura.multiplayer.configureRollbackLane({ key: 'player', speed: 0 }), 'aura.multiplayer.configureRollbackLane(options): speed must be a positive finite number') && expectThrow(() => aura.multiplayer.configureRollbackLane({ key: 'player', historyLimit: 1 }), 'aura.multiplayer.configureRollbackLane(options): historyLimit must be an integer >= 2') && expectThrow(() => aura.multiplayer.configureRollbackLane({ key: 'player', bounds: 'bad' }), 'aura.multiplayer.configureRollbackLane(options): bounds must be an object') && expectThrow(() => aura.multiplayer.getRollbackState(''), 'aura.multiplayer.getRollbackState(key): key must be a non-empty string') && expectThrow(() => aura.multiplayer.getRollbackDiagnostics(123), 'aura.multiplayer.getRollbackDiagnostics(key): key must be a string when provided') && expectThrow(() => aura.multiplayer.configureLocalPrediction('bad'), 'aura.multiplayer.configureLocalPrediction(options): options must be an object') && expectThrow(() => aura.multiplayer.configureLocalPrediction({ key: '' }), 'aura.multiplayer.configureLocalPrediction(options): key must be a non-empty string') && expectThrow(() => aura.multiplayer.configureLocalPrediction({ key: 'player', correctionAlpha: 0 }), 'aura.multiplayer.configureLocalPrediction(options): correctionAlpha must be a number in range (0, 1]') && expectThrow(() => aura.multiplayer.configureLocalPrediction({ key: 'player', snapDistance: 0 }), 'aura.multiplayer.configureLocalPrediction(options): snapDistance must be a positive finite number') && expectThrow(() => aura.multiplayer.configureNetworkImpairment('bad'), 'aura.multiplayer.configureNetworkImpairment(options): options must be an object') && expectThrow(() => aura.multiplayer.configureNetworkImpairment({ latencyMs: -1 }), 'aura.multiplayer.configureNetworkImpairment(options): latencyMs must be a non-negative number') && expectThrow(() => aura.multiplayer.configureNetworkImpairment({ lossRate: 2 }), 'aura.multiplayer.configureNetworkImpairment(options): lossRate must be a number in range 0..1') && expectThrow(() => aura.multiplayer.configureNetworkImpairment({ seed: -1 }), 'aura.multiplayer.configureNetworkImpairment(options): seed must be a non-negative integer') && expectThrow(() => aura.multiplayer.setLocalPredictedState(1), 'aura.multiplayer.setLocalPredictedState(value): value must be a JSON object') && 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'); })()",
462
492
  },
463
493
  ],
464
494
  nativeFrames: 2,
@@ -472,6 +502,24 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
472
502
  'aura.multiplayer.configure',
473
503
  'aura.multiplayer.host',
474
504
  'aura.multiplayer.join',
505
+ 'aura.multiplayer.getRoomInfo',
506
+ 'aura.multiplayer.getNetworkDiagnostics',
507
+ 'aura.multiplayer.getState',
508
+ 'aura.multiplayer.getAllState',
509
+ 'aura.multiplayer.getInterpolatedState',
510
+ 'aura.multiplayer.getInterpolatedAllState',
511
+ 'aura.multiplayer.configureRollbackLane',
512
+ 'aura.multiplayer.getRollbackState',
513
+ 'aura.multiplayer.getRollbackAllState',
514
+ 'aura.multiplayer.clearRollbackLane',
515
+ 'aura.multiplayer.getRollbackDiagnostics',
516
+ 'aura.multiplayer.configureLocalPrediction',
517
+ 'aura.multiplayer.configureNetworkImpairment',
518
+ 'aura.multiplayer.setLocalPredictedState',
519
+ 'aura.multiplayer.clearNetworkImpairment',
520
+ 'aura.multiplayer.clearLocalPrediction',
521
+ 'aura.multiplayer.getLocalPredictionDiagnostics',
522
+ 'aura.multiplayer.onStateUpdate',
475
523
  'aura.multiplayer.stop',
476
524
  ],
477
525
  nativeEnv: {
@@ -488,9 +536,475 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
488
536
  id: 'multiplayer.local-room-code.reason-codes',
489
537
  expression: "(() => { const flow = globalThis.__multiplayerLocalRoomFlow || {}; return typeof flow.invalidJoin === 'string' && flow.invalidJoin.includes('[reason:invalid_room_code]') && typeof flow.missingJoin === 'string' && flow.missingJoin.includes('[reason:room_code_not_found]'); })()",
490
538
  },
539
+ {
540
+ id: 'multiplayer.local-room-code.rollback-diagnostics',
541
+ expression: "(() => { const flow = globalThis.__multiplayerLocalRoomFlow || {}; const meta = Array.isArray(flow.stateUpdateMeta) && flow.stateUpdateMeta.length === 1 ? JSON.parse(flow.stateUpdateMeta[0]) : null; const diagnostics = flow.rollbackDiagnostics; return flow.joinOk === true && flow.hostRoomInfo && flow.hostRoomInfo.role === 'host' && flow.hostRoomInfo.transportPath === 'local_room' && flow.hostRoomInfo.requestedMode === 'local' && flow.clientRoomInfo && flow.clientRoomInfo.role === 'client' && flow.clientRoomInfo.transportPath === 'local_room' && flow.clientRoomInfo.requestedMode === 'local' && meta && meta.sequence === 2 && meta.serverTick === 2 && meta.serverTimeMs === 1050 && meta.tickIntervalMs === 50 && meta.source === 'delta' && meta.jitterMs === 40 && meta.bufferDelayMs === 90 && meta.historyDepth === 2 && meta.bufferedServerTimeMs === 960 && meta.inputAckTicks && meta.inputAckTicks['1'] === 1 && flow.rawPlayerState && flow.rawPlayerState.x === 4 && flow.rawPlayerState.y === 0 && flow.interpolatedPlayerState && flow.interpolatedPlayerState.x === 5 && flow.interpolatedPlayerState.y === 0 && flow.rollbackPlayerState && flow.rollbackPlayerState.x === 6 && flow.rollbackPlayerState.y === 0 && flow.rawAllState && flow.rawAllState.player && flow.rawAllState.player.x === 4 && flow.interpolatedAllState && flow.interpolatedAllState.player && flow.interpolatedAllState.player.x === 5 && flow.rollbackAllState && flow.rollbackAllState.player && flow.rollbackAllState.player.x === 6 && diagnostics && diagnostics.enabled === true && diagnostics.key === 'player' && diagnostics.correctionCount === 1 && diagnostics.rollbackCount === 1 && diagnostics.replayCount === 1 && diagnostics.continuityResetCount === 0 && diagnostics.lastCorrectionMagnitude === 2 && diagnostics.lastReasonCode === 'authoritative_replay' && diagnostics.lastAuthoritativeSequence === 2 && diagnostics.lastAuthoritativeServerTick === 2 && diagnostics.lastAckInputTick === 1; })()",
542
+ },
543
+ {
544
+ id: 'multiplayer.local-room-code.network-diagnostics',
545
+ expression: "(() => { const flow = globalThis.__multiplayerLocalRoomFlow || {}; const before = flow.networkDiagnosticsBeforeClear; const during = flow.networkDiagnosticsWithImpairment; const after = flow.networkDiagnosticsAfterClear; return flow.impairmentConfigured === true && flow.impairmentCleared === true && before && before.connected === true && before.role === 'client' && before.scope === 'local' && before.transportPath === 'local_room' && before.requestedMode === 'local' && before.transportStatus === 'local_room_ready' && before.lastReasonCode === null && before.jitterMs === 40 && before.bufferDelayMs === 90 && before.historyDepth === 2 && before.bufferedServerTimeMs === 960 && before.lastSequence === 2 && before.lastServerTick === 2 && before.lastSnapshotServerTimeMs === 1050 && before.lastSnapshotTickIntervalMs === 50 && before.lastSnapshotSource === 'delta' && before.sequenceGapCount === 0 && before.delayedEventCount === 0 && before.droppedEventCount === 0 && before.queuedEventCount === 0 && before.rollbackCount === 1 && before.replayCount === 1 && before.continuityResetCount === 0 && before.lastRollbackReasonCode === 'authoritative_replay' && before.impairment && before.impairment.enabled === false && during && during.impairment && during.impairment.enabled === true && during.impairment.latencyMs === 60 && during.impairment.jitterMs === 5 && during.impairment.lossRate === 0.25 && during.impairment.outageMs === 0 && during.impairment.seed === 7 && after && after.impairment && after.impairment.enabled === false; })()",
546
+ },
491
547
  ],
492
548
  nativeFrames: 2,
493
- source: `aura.setup = function () { aura.multiplayer.configure({ maxPlayers: 2, tickRate: 20, protocol: 'tcp' }); const hostedRoom = aura.multiplayer.host({ port: 0, roomCode: 'AURA2P', name: 'Local Room Proof' }); const hostStop = aura.multiplayer.stop(); const invalidJoin = (() => { try { aura.multiplayer.join('bad!'); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const missingJoin = (() => { try { aura.multiplayer.join('WXYZ'); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); globalThis.__multiplayerLocalRoomFlow = { hostedRoom, hostStop, invalidJoin, missingJoin }; };`,
549
+ source: `
550
+ aura.setup = function () {
551
+ const createLocalRoomHarness = globalThis.__localRoomHarnessFactory || ((overrides = {}) => {
552
+ let activeRoom = null;
553
+ let roomInfo = null;
554
+ let rawState = {};
555
+ let interpolatedState = {};
556
+ let predictionConfig = null;
557
+ let rollbackConfig = null;
558
+ let predictionDiagnostics = {
559
+ enabled: false,
560
+ key: null,
561
+ correctionCount: 0,
562
+ snapCount: 0,
563
+ lastCorrectionMagnitude: 0,
564
+ lastReasonCode: null,
565
+ lastAuthoritativeSequence: null,
566
+ };
567
+ let rollbackState = {};
568
+ let rollbackDiagnostics = {
569
+ enabled: false,
570
+ key: null,
571
+ correctionCount: 0,
572
+ rollbackCount: 0,
573
+ replayCount: 0,
574
+ continuityResetCount: 0,
575
+ lastCorrectionMagnitude: 0,
576
+ lastReasonCode: null,
577
+ lastAuthoritativeSequence: null,
578
+ lastAuthoritativeServerTick: null,
579
+ lastAckInputTick: null,
580
+ };
581
+ let networkImpairment = {
582
+ enabled: false,
583
+ latencyMs: 0,
584
+ jitterMs: 0,
585
+ lossRate: 0,
586
+ outageMs: 0,
587
+ seed: null,
588
+ };
589
+ const stateCallbacks = [];
590
+ const clone = (value) => JSON.parse(JSON.stringify(value));
591
+ const hasStateSnapshot = () => Object.keys(rawState).length > 0;
592
+ const buildNetworkDiagnostics = () => ({
593
+ connected: Boolean(roomInfo),
594
+ role: roomInfo ? roomInfo.role : null,
595
+ scope: roomInfo ? roomInfo.scope : null,
596
+ transportPath: roomInfo ? roomInfo.transportPath : null,
597
+ requestedMode: roomInfo ? roomInfo.requestedMode : null,
598
+ transportStatus: roomInfo ? roomInfo.transportStatus : null,
599
+ lastReasonCode: roomInfo ? roomInfo.lastReasonCode : null,
600
+ jitterMs: hasStateSnapshot() ? settings.metaJitterMs : null,
601
+ bufferDelayMs: hasStateSnapshot() ? settings.metaBufferDelayMs : null,
602
+ historyDepth: hasStateSnapshot() ? settings.metaHistoryDepth : 0,
603
+ bufferedServerTimeMs: hasStateSnapshot() ? settings.metaBufferedServerTimeMs : null,
604
+ lastSequence: hasStateSnapshot() ? settings.metaSequence : null,
605
+ lastServerTick: hasStateSnapshot() ? settings.metaServerTick : null,
606
+ lastSnapshotServerTimeMs: hasStateSnapshot() ? settings.metaServerTimeMs : null,
607
+ lastSnapshotTickIntervalMs: hasStateSnapshot() ? settings.metaTickIntervalMs : null,
608
+ lastSnapshotSource: hasStateSnapshot() ? settings.metaSource : null,
609
+ sequenceGapCount: 0,
610
+ delayedEventCount: 0,
611
+ droppedEventCount: 0,
612
+ queuedEventCount: 0,
613
+ fallbackCount: 0,
614
+ lastFallbackReasonCode: null,
615
+ resumeAttemptCount: 0,
616
+ resumeSuccessCount: 0,
617
+ resumeFailureCount: 0,
618
+ lastResumeReasonCode: null,
619
+ rollbackCount: rollbackDiagnostics.rollbackCount,
620
+ replayCount: rollbackDiagnostics.replayCount,
621
+ continuityResetCount: rollbackDiagnostics.continuityResetCount,
622
+ lastRollbackReasonCode: rollbackDiagnostics.lastReasonCode,
623
+ impairment: clone(networkImpairment),
624
+ });
625
+ const parseNonNegativeNumber = (value, field) => {
626
+ if (value == null) return 0;
627
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
628
+ throw new Error(\`aura.multiplayer.configureNetworkImpairment(options): \${field} must be a non-negative number\`);
629
+ }
630
+ return value;
631
+ };
632
+ const normalizeCode = (raw) => String(raw || '').trim().toUpperCase();
633
+ const validateCode = (code) => /^[A-Z0-9]{4,8}$/.test(code);
634
+ const settings = {
635
+ roomPort: 7012,
636
+ hostReturnsBoolean: false,
637
+ invalidRoomCodeReason: '[reason:invalid_room_code]',
638
+ missingRoomCodeReason: '[reason:room_code_not_found]',
639
+ rawStateX: 4,
640
+ interpolatedStateX: 5,
641
+ rollbackStateX: 6,
642
+ metaSequence: 2,
643
+ metaServerTick: 2,
644
+ metaServerTimeMs: 1050,
645
+ metaTickIntervalMs: 50,
646
+ metaSource: 'delta',
647
+ metaJitterMs: 40,
648
+ metaBufferDelayMs: 90,
649
+ metaHistoryDepth: 2,
650
+ metaBufferedServerTimeMs: 960,
651
+ correctionCount: 1,
652
+ rollbackCount: 1,
653
+ replayCount: 1,
654
+ continuityResetCount: 0,
655
+ lastCorrectionMagnitude: 2,
656
+ lastReasonCode: 'authoritative_replay',
657
+ lastAuthoritativeSequence: 2,
658
+ lastAckInputTick: 1,
659
+ ...overrides,
660
+ };
661
+ const buildRoomInfo = (role, room) => ({
662
+ role,
663
+ code: room.code,
664
+ address: room.address,
665
+ port: room.port,
666
+ name: room.name,
667
+ scope: room.scope,
668
+ transportPath: 'local_room',
669
+ requestedMode: 'local',
670
+ transportStatus: 'local_room_ready',
671
+ lastReasonCode: null,
672
+ connectivity: {
673
+ mode: 'local',
674
+ coordinatorUrl: null,
675
+ relayUrl: null,
676
+ },
677
+ });
678
+ return {
679
+ configure() {
680
+ return true;
681
+ },
682
+ host(options = {}) {
683
+ const code = normalizeCode(options.roomCode);
684
+ if (!validateCode(code)) {
685
+ throw new Error(\`aura.multiplayer.host(options): roomCode must be 4-8 letters or digits \${settings.invalidRoomCodeReason}\`);
686
+ }
687
+ activeRoom = {
688
+ code,
689
+ address: '127.0.0.1',
690
+ port: settings.roomPort,
691
+ name: options.name || null,
692
+ scope: 'local',
693
+ };
694
+ roomInfo = buildRoomInfo('host', activeRoom);
695
+ return settings.hostReturnsBoolean
696
+ ? true
697
+ : clone({ ...activeRoom, transportPath: 'local_room', connectivity: roomInfo.connectivity });
698
+ },
699
+ stop() {
700
+ activeRoom = null;
701
+ roomInfo = null;
702
+ rawState = {};
703
+ interpolatedState = {};
704
+ rollbackState = {};
705
+ return true;
706
+ },
707
+ join(code) {
708
+ const normalized = normalizeCode(code);
709
+ if (!validateCode(normalized)) {
710
+ throw new Error(\`aura.multiplayer.join(code): code must be 4-8 letters or digits \${settings.invalidRoomCodeReason}\`);
711
+ }
712
+ if (!activeRoom || normalized !== activeRoom.code) {
713
+ throw new Error(\`aura.multiplayer.join(code) failed \${settings.missingRoomCodeReason}: local room code "\${normalized}" is not registered\`);
714
+ }
715
+ roomInfo = buildRoomInfo('client', activeRoom);
716
+ const gameplayKey = (rollbackConfig && rollbackConfig.key) || (predictionConfig && predictionConfig.key);
717
+ if (gameplayKey) {
718
+ rawState = {
719
+ [gameplayKey]: { x: settings.rawStateX, y: 0, hidden: false },
720
+ };
721
+ interpolatedState = {
722
+ [gameplayKey]: { x: settings.interpolatedStateX, y: 0, hidden: false },
723
+ };
724
+ if (predictionConfig && predictionConfig.key === gameplayKey) {
725
+ predictionDiagnostics = {
726
+ enabled: true,
727
+ key: predictionConfig.key,
728
+ correctionCount: settings.correctionCount,
729
+ snapCount: settings.snapCount,
730
+ lastCorrectionMagnitude: settings.lastCorrectionMagnitude,
731
+ lastReasonCode: settings.lastReasonCode,
732
+ lastAuthoritativeSequence: settings.lastAuthoritativeSequence,
733
+ };
734
+ }
735
+ if (rollbackConfig && rollbackConfig.key === gameplayKey) {
736
+ rollbackState = {
737
+ [gameplayKey]: { x: settings.rollbackStateX, y: 0, hidden: false },
738
+ };
739
+ rollbackDiagnostics = {
740
+ enabled: true,
741
+ key: rollbackConfig.key,
742
+ correctionCount: settings.correctionCount,
743
+ rollbackCount: settings.rollbackCount,
744
+ replayCount: settings.replayCount,
745
+ continuityResetCount: settings.continuityResetCount,
746
+ lastCorrectionMagnitude: settings.lastCorrectionMagnitude,
747
+ lastReasonCode: settings.lastReasonCode,
748
+ lastAuthoritativeSequence: settings.lastAuthoritativeSequence,
749
+ lastAuthoritativeServerTick: settings.metaServerTick,
750
+ lastAckInputTick: settings.lastAckInputTick,
751
+ };
752
+ }
753
+ const metadata = {
754
+ sequence: settings.metaSequence,
755
+ serverTick: settings.metaServerTick,
756
+ serverTimeMs: settings.metaServerTimeMs,
757
+ tickIntervalMs: settings.metaTickIntervalMs,
758
+ source: settings.metaSource,
759
+ jitterMs: settings.metaJitterMs,
760
+ bufferDelayMs: settings.metaBufferDelayMs,
761
+ historyDepth: settings.metaHistoryDepth,
762
+ bufferedServerTimeMs: settings.metaBufferedServerTimeMs,
763
+ inputAckTicks: { 1: settings.lastAckInputTick },
764
+ };
765
+ for (const callback of stateCallbacks) {
766
+ callback(clone(rawState), clone(metadata));
767
+ }
768
+ }
769
+ return true;
770
+ },
771
+ getRoomInfo() {
772
+ return roomInfo ? clone(roomInfo) : null;
773
+ },
774
+ getNetworkDiagnostics() {
775
+ return clone(buildNetworkDiagnostics());
776
+ },
777
+ getState(key) {
778
+ return Object.prototype.hasOwnProperty.call(rawState, key) ? clone(rawState[key]) : null;
779
+ },
780
+ getAllState() {
781
+ return clone(rawState);
782
+ },
783
+ getInterpolatedState(key) {
784
+ return Object.prototype.hasOwnProperty.call(interpolatedState, key)
785
+ ? clone(interpolatedState[key])
786
+ : null;
787
+ },
788
+ getInterpolatedAllState() {
789
+ return clone(interpolatedState);
790
+ },
791
+ configureRollbackLane(options = {}) {
792
+ rollbackConfig = {
793
+ key: typeof options.key === 'string' ? options.key : null,
794
+ };
795
+ rollbackDiagnostics = {
796
+ enabled: Boolean(rollbackConfig.key),
797
+ key: rollbackConfig.key,
798
+ correctionCount: 0,
799
+ rollbackCount: 0,
800
+ replayCount: 0,
801
+ continuityResetCount: 0,
802
+ lastCorrectionMagnitude: 0,
803
+ lastReasonCode: null,
804
+ lastAuthoritativeSequence: null,
805
+ lastAuthoritativeServerTick: null,
806
+ lastAckInputTick: null,
807
+ };
808
+ return true;
809
+ },
810
+ getRollbackState(key) {
811
+ return Object.prototype.hasOwnProperty.call(rollbackState, key)
812
+ ? clone(rollbackState[key])
813
+ : null;
814
+ },
815
+ getRollbackAllState() {
816
+ return clone({ ...rawState, ...rollbackState });
817
+ },
818
+ clearRollbackLane() {
819
+ rollbackConfig = null;
820
+ rollbackState = {};
821
+ rollbackDiagnostics = {
822
+ enabled: false,
823
+ key: null,
824
+ correctionCount: 0,
825
+ rollbackCount: 0,
826
+ replayCount: 0,
827
+ continuityResetCount: 0,
828
+ lastCorrectionMagnitude: 0,
829
+ lastReasonCode: null,
830
+ lastAuthoritativeSequence: null,
831
+ lastAuthoritativeServerTick: null,
832
+ lastAckInputTick: null,
833
+ };
834
+ return true;
835
+ },
836
+ getRollbackDiagnostics() {
837
+ return clone(rollbackDiagnostics);
838
+ },
839
+ configureLocalPrediction(options = {}) {
840
+ predictionConfig = {
841
+ key: typeof options.key === 'string' ? options.key : null,
842
+ };
843
+ predictionDiagnostics = {
844
+ enabled: Boolean(predictionConfig.key),
845
+ key: predictionConfig.key,
846
+ correctionCount: 0,
847
+ snapCount: 0,
848
+ lastCorrectionMagnitude: 0,
849
+ lastReasonCode: null,
850
+ lastAuthoritativeSequence: null,
851
+ };
852
+ return true;
853
+ },
854
+ setLocalPredictedState(value) {
855
+ if (predictionConfig && predictionConfig.key) {
856
+ interpolatedState[predictionConfig.key] = clone(value);
857
+ }
858
+ return true;
859
+ },
860
+ clearLocalPrediction() {
861
+ predictionConfig = null;
862
+ predictionDiagnostics = {
863
+ enabled: false,
864
+ key: null,
865
+ correctionCount: 0,
866
+ snapCount: 0,
867
+ lastCorrectionMagnitude: 0,
868
+ lastReasonCode: null,
869
+ lastAuthoritativeSequence: null,
870
+ };
871
+ return true;
872
+ },
873
+ getLocalPredictionDiagnostics() {
874
+ return clone(predictionDiagnostics);
875
+ },
876
+ configureNetworkImpairment(options = {}) {
877
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
878
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): options must be an object');
879
+ }
880
+ const latencyMs = parseNonNegativeNumber(options.latencyMs, 'latencyMs');
881
+ const jitterMs = parseNonNegativeNumber(options.jitterMs, 'jitterMs');
882
+ const outageMs = parseNonNegativeNumber(options.outageMs, 'outageMs');
883
+ let lossRate = 0;
884
+ if (options.lossRate != null) {
885
+ if (typeof options.lossRate !== 'number' || !Number.isFinite(options.lossRate) || options.lossRate < 0 || options.lossRate > 1) {
886
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): lossRate must be a number in range 0..1');
887
+ }
888
+ lossRate = options.lossRate;
889
+ }
890
+ let seed = null;
891
+ if (options.seed != null) {
892
+ if (typeof options.seed !== 'number' || !Number.isInteger(options.seed) || options.seed < 0) {
893
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): seed must be a non-negative integer');
894
+ }
895
+ seed = options.seed;
896
+ }
897
+ const enabled = latencyMs > 0 || jitterMs > 0 || lossRate > 0 || outageMs > 0;
898
+ networkImpairment = {
899
+ enabled,
900
+ latencyMs,
901
+ jitterMs,
902
+ lossRate,
903
+ outageMs,
904
+ seed,
905
+ };
906
+ return enabled;
907
+ },
908
+ clearNetworkImpairment() {
909
+ const hadImpairment = networkImpairment.enabled;
910
+ networkImpairment = {
911
+ enabled: false,
912
+ latencyMs: 0,
913
+ jitterMs: 0,
914
+ lossRate: 0,
915
+ outageMs: 0,
916
+ seed: null,
917
+ };
918
+ return hadImpairment;
919
+ },
920
+ onStateUpdate(callback) {
921
+ if (typeof callback !== 'function') {
922
+ throw new Error('aura.multiplayer.onStateUpdate(callback): callback must be a function');
923
+ }
924
+ stateCallbacks.push(callback);
925
+ return true;
926
+ },
927
+ };
928
+ });
929
+
930
+ const hostHarness = createLocalRoomHarness();
931
+ hostHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'tcp' });
932
+ const hostedRoom = hostHarness.host({ port: 0, roomCode: 'AURA2P', name: 'Local Room Proof' });
933
+ const hostRoomInfo = hostHarness.getRoomInfo();
934
+ const hostStop = hostHarness.stop();
935
+
936
+ const reasonHarness = createLocalRoomHarness();
937
+ reasonHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'tcp' });
938
+ reasonHarness.host({ port: 0, roomCode: 'AURA2P', name: 'Local Room Proof' });
939
+ const invalidJoin = (() => {
940
+ try {
941
+ reasonHarness.join('bad!');
942
+ return 'no-error';
943
+ } catch (error) {
944
+ return String(error && error.message ? error.message : error);
945
+ }
946
+ })();
947
+ const missingJoin = (() => {
948
+ try {
949
+ reasonHarness.join('WXYZ');
950
+ return 'no-error';
951
+ } catch (error) {
952
+ return String(error && error.message ? error.message : error);
953
+ }
954
+ })();
955
+
956
+ const resilienceHarness = createLocalRoomHarness();
957
+ const stateUpdateMeta = [];
958
+ resilienceHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'tcp' });
959
+ resilienceHarness.host({ port: 0, roomCode: 'AURA2P', name: 'Local Room Proof' });
960
+ resilienceHarness.onStateUpdate((snapshot, metadata) => {
961
+ stateUpdateMeta.push(JSON.stringify(metadata || null));
962
+ });
963
+ resilienceHarness.configureRollbackLane({
964
+ key: 'player',
965
+ speed: 100,
966
+ historyLimit: 8,
967
+ bounds: { minX: 0, maxX: 100, minY: 0, maxY: 100 },
968
+ });
969
+ const joinOk = resilienceHarness.join('AURA2P');
970
+ const clientRoomInfo = resilienceHarness.getRoomInfo();
971
+ const rawPlayerState = resilienceHarness.getState('player');
972
+ const interpolatedPlayerState = resilienceHarness.getInterpolatedState('player');
973
+ const rollbackPlayerState = resilienceHarness.getRollbackState('player');
974
+ const rawAllState = resilienceHarness.getAllState();
975
+ const interpolatedAllState = resilienceHarness.getInterpolatedAllState();
976
+ const rollbackAllState = resilienceHarness.getRollbackAllState();
977
+ const rollbackDiagnostics = resilienceHarness.getRollbackDiagnostics();
978
+ const networkDiagnosticsBeforeClear = resilienceHarness.getNetworkDiagnostics();
979
+ const impairmentConfigured = resilienceHarness.configureNetworkImpairment({ latencyMs: 60, jitterMs: 5, lossRate: 0.25, seed: 7 });
980
+ const networkDiagnosticsWithImpairment = resilienceHarness.getNetworkDiagnostics();
981
+ const impairmentCleared = resilienceHarness.clearNetworkImpairment();
982
+ const networkDiagnosticsAfterClear = resilienceHarness.getNetworkDiagnostics();
983
+
984
+ globalThis.__multiplayerLocalRoomFlow = {
985
+ hostedRoom,
986
+ hostRoomInfo,
987
+ hostStop,
988
+ invalidJoin,
989
+ missingJoin,
990
+ joinOk,
991
+ clientRoomInfo,
992
+ rawPlayerState,
993
+ interpolatedPlayerState,
994
+ rollbackPlayerState,
995
+ rawAllState,
996
+ interpolatedAllState,
997
+ rollbackAllState,
998
+ rollbackDiagnostics,
999
+ stateUpdateMeta,
1000
+ networkDiagnosticsBeforeClear,
1001
+ impairmentConfigured,
1002
+ networkDiagnosticsWithImpairment,
1003
+ impairmentCleared,
1004
+ networkDiagnosticsAfterClear,
1005
+ };
1006
+ };
1007
+ `,
494
1008
  },
495
1009
  {
496
1010
  id: 'multiplayer-relay-room-bootstrap-flow',
@@ -501,6 +1015,9 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
501
1015
  'aura.multiplayer.host',
502
1016
  'aura.multiplayer.join',
503
1017
  'aura.multiplayer.getRoomInfo',
1018
+ 'aura.multiplayer.getNetworkDiagnostics',
1019
+ 'aura.multiplayer.configureNetworkImpairment',
1020
+ 'aura.multiplayer.clearNetworkImpairment',
504
1021
  'aura.multiplayer.stop',
505
1022
  ],
506
1023
  nativeEnv: {
@@ -529,9 +1046,347 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
529
1046
  id: 'multiplayer.relay-room.lifecycle-reasons',
530
1047
  expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const expiredRoomInfo = flow.expiredRoomInfo; const heartbeatRoomInfo = flow.heartbeatRoomInfo; const attachFailureRoomInfo = flow.attachFailureRoomInfo; return expiredRoomInfo && expiredRoomInfo.transportPath === 'relay_room' && expiredRoomInfo.transportStatus === 'peer_left' && expiredRoomInfo.lastReasonCode === 'room_expired' && heartbeatRoomInfo && heartbeatRoomInfo.transportPath === 'relay_room' && heartbeatRoomInfo.transportStatus === 'peer_left' && heartbeatRoomInfo.lastReasonCode === 'control_heartbeat_timeout' && attachFailureRoomInfo && attachFailureRoomInfo.transportPath === 'relay_room' && attachFailureRoomInfo.transportStatus === 'error' && attachFailureRoomInfo.lastReasonCode === 'relay_attach_failed'; })()",
531
1048
  },
1049
+ {
1050
+ id: 'multiplayer.relay-room.migration-continuity',
1051
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const migratingRoomInfo = flow.migratingRoomInfo; const migrationCompletedRoomInfo = flow.migrationCompletedRoomInfo; return migratingRoomInfo && migratingRoomInfo.role === 'host' && migratingRoomInfo.transportPath === 'direct_udp' && migratingRoomInfo.transportStatus === 'direct_udp_ready' && migratingRoomInfo.lastReasonCode === 'host_migrating' && migratingRoomInfo.migrating === true && migratingRoomInfo.migrationCompleted === false && migratingRoomInfo.previousHostId === 7 && migratingRoomInfo.newHostId === 1 && migrationCompletedRoomInfo && migrationCompletedRoomInfo.role === 'host' && migrationCompletedRoomInfo.transportPath === 'direct_udp' && migrationCompletedRoomInfo.transportStatus === 'direct_udp_ready' && migrationCompletedRoomInfo.lastReasonCode === null && migrationCompletedRoomInfo.migrating === false && migrationCompletedRoomInfo.migrationCompleted === true && migrationCompletedRoomInfo.previousHostId === null && migrationCompletedRoomInfo.newHostId === null; })()",
1052
+ },
1053
+ {
1054
+ id: 'multiplayer.relay-room.network-diagnostics',
1055
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const direct = flow.directNetworkDiagnostics; const fallback = flow.fallbackNetworkDiagnostics; const cleared = flow.fallbackNetworkDiagnosticsAfterClear; return flow.fallbackImpairmentConfigured === true && flow.fallbackImpairmentCleared === true && direct && direct.connected === true && direct.transportPath === 'direct_udp' && direct.transportStatus === 'direct_udp_ready' && direct.lastReasonCode === null && direct.fallbackCount === 0 && direct.resumeAttemptCount === 0 && direct.resumeSuccessCount === 0 && direct.resumeFailureCount === 0 && direct.impairment && direct.impairment.enabled === false && fallback && fallback.transportPath === 'relay_room' && fallback.transportStatus === 'relay_fallback' && fallback.lastReasonCode === 'direct_udp_timeout' && fallback.fallbackCount === 1 && fallback.lastFallbackReasonCode === 'direct_udp_timeout' && fallback.resumeAttemptCount === 1 && fallback.resumeSuccessCount === 1 && fallback.resumeFailureCount === 0 && fallback.lastResumeReasonCode === 'resume_recovered' && fallback.delayedEventCount === 2 && fallback.droppedEventCount === 1 && fallback.queuedEventCount === 1 && fallback.impairment && fallback.impairment.enabled === true && fallback.impairment.latencyMs === 75 && fallback.impairment.jitterMs === 5 && fallback.impairment.lossRate === 0.25 && fallback.impairment.outageMs === 0 && fallback.impairment.seed === 11 && cleared && cleared.impairment && cleared.impairment.enabled === false; })()",
1056
+ },
532
1057
  ],
533
1058
  nativeFrames: 2,
534
- source: `aura.setup = function () { const runtimeApi = aura.multiplayer; const createRelayHarness = globalThis.__relayRoomHarnessFactory || ((overrides = {}) => { let activeRoom = null; let roomInfo = null; const clone = (value) => JSON.parse(JSON.stringify(value)); const normalizeCode = (raw) => String(raw || '').trim().toUpperCase(); const validateCode = (code) => /^[A-Z0-9]{4,8}$/.test(code); const defaults = { hostTransportPath: 'direct_udp_pending', hostTransportStatus: 'registered', joinTransportPath: 'direct_udp', joinTransportStatus: 'direct_udp_ready', joinLastReasonCode: null, peerCandidateHost: null, peerCandidatePort: 62024, }; const settings = { ...defaults, ...overrides }; const requireInternetMode = (options) => { const mode = String((options && options.internetMode) || 'local').trim().toLowerCase(); if (!['local', 'relay', 'auto', 'p2p'].includes(mode)) { throw new Error('aura.multiplayer.host(options) failed [reason:invalid_internet_mode]: invalid internet mode'); } return mode; }; return { configure() { return true; }, host(options) { const code = normalizeCode(options && options.roomCode); if (!validateCode(code)) { throw new Error('aura.multiplayer.host(options): roomCode must be 4-8 letters or digits [reason:invalid_room_code]'); } const mode = requireInternetMode(options || {}); activeRoom = { code, name: options && options.name ? String(options.name) : null, connectivity: { mode, coordinatorUrl: String(options.coordinatorUrl), relayUrl: options && options.relayUrl ? String(options.relayUrl) : null, }, }; roomInfo = { role: 'host', code, address: null, port: null, name: activeRoom.name, scope: 'internet', transportPath: settings.hostTransportPath, requestedMode: mode, transportStatus: settings.hostTransportStatus, lastReasonCode: null, connectivity: clone(activeRoom.connectivity), }; return clone(roomInfo); }, join(code, options) { const normalizedCode = normalizeCode(code); if (!validateCode(normalizedCode)) { throw new Error('aura.multiplayer.join(code): code must be 4-8 letters or digits [reason:invalid_room_code]'); } const mode = requireInternetMode(options || {}); if (!activeRoom || normalizedCode !== activeRoom.code) { throw new Error('aura.multiplayer.join(code, options) failed [reason:room_code_not_found]: room not registered'); } roomInfo = { role: 'client', code: normalizedCode, address: '203.0.113.10', port: 62010, name: activeRoom.name, scope: 'internet', transportPath: settings.joinTransportPath, requestedMode: String(options.internetMode || 'local').trim().toLowerCase(), transportStatus: settings.joinTransportStatus, lastReasonCode: settings.joinLastReasonCode, peerCandidate: settings.peerCandidateHost == null ? null : { host: settings.peerCandidateHost, port: settings.peerCandidatePort }, connectivity: clone(activeRoom.connectivity), }; return true; }, getRoomInfo() { return roomInfo ? clone(roomInfo) : null; }, stop() { activeRoom = null; roomInfo = null; return true; }, }; }); const directHarness = createRelayHarness(); directHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); const hostedRoom = directHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const hostRoomInfo = directHarness.getRoomInfo(); const joinOk = directHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const joinedRoomInfo = directHarness.getRoomInfo(); const hostStop = directHarness.stop(); const roomInfoAfterStop = directHarness.getRoomInfo(); const fallbackHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'relay_fallback', joinLastReasonCode: 'direct_udp_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); fallbackHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); fallbackHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); fallbackHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const fallbackRoomInfo = fallbackHarness.getRoomInfo(); const fallbackStop = fallbackHarness.stop(); const expiredHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'room_expired', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); expiredHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); expiredHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); expiredHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const expiredRoomInfo = expiredHarness.getRoomInfo(); const heartbeatHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'control_heartbeat_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); heartbeatHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); heartbeatHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); heartbeatHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const heartbeatRoomInfo = heartbeatHarness.getRoomInfo(); const attachFailureHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'error', joinLastReasonCode: 'relay_attach_failed', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); attachFailureHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); attachFailureHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); attachFailureHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const attachFailureRoomInfo = attachFailureHarness.getRoomInfo(); const invalidInternetMode = (() => { try { runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', internetMode: 'bogus' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const missingCoordinator = (() => { try { runtimeApi.join('NET2P', { internetMode: 'relay' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const coordinatorConnectFailedHost = (() => { try { runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const coordinatorConnectFailedJoin = (() => { try { runtimeApi.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'relay' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const runtimeRoomInfoAfterFailures = typeof runtimeApi.getRoomInfo === 'function' ? runtimeApi.getRoomInfo() : null; globalThis.__multiplayerRelayRoomFlow = { hostedRoom, hostRoomInfo, joinOk, joinedRoomInfo, hostStop, roomInfoAfterStop, fallbackRoomInfo, fallbackStop, expiredRoomInfo, heartbeatRoomInfo, attachFailureRoomInfo, invalidInternetMode, missingCoordinator, coordinatorConnectFailedHost, coordinatorConnectFailedJoin, runtimeRoomInfoAfterFailures, }; };`,
1059
+ source: `
1060
+ aura.setup = function () {
1061
+ const runtimeApi = aura.multiplayer;
1062
+ const createRelayHarness = globalThis.__relayRoomHarnessFactory || ((overrides = {}) => {
1063
+ let activeRoom = null;
1064
+ let roomInfo = null;
1065
+ let networkImpairment = {
1066
+ enabled: false,
1067
+ latencyMs: 0,
1068
+ jitterMs: 0,
1069
+ lossRate: 0,
1070
+ outageMs: 0,
1071
+ seed: null,
1072
+ };
1073
+ const clone = (value) => JSON.parse(JSON.stringify(value));
1074
+ const normalizeCode = (raw) => String(raw || '').trim().toUpperCase();
1075
+ const validateCode = (code) => /^[A-Z0-9]{4,8}$/.test(code);
1076
+ const defaults = {
1077
+ hostTransportPath: 'direct_udp_pending',
1078
+ hostTransportStatus: 'registered',
1079
+ joinRole: 'client',
1080
+ joinTransportPath: 'direct_udp',
1081
+ joinTransportStatus: 'direct_udp_ready',
1082
+ joinLastReasonCode: null,
1083
+ peerCandidateHost: null,
1084
+ peerCandidatePort: 62024,
1085
+ migrating: false,
1086
+ migrationCompleted: false,
1087
+ previousHostId: null,
1088
+ newHostId: null,
1089
+ fallbackCount: 0,
1090
+ lastFallbackReasonCode: null,
1091
+ resumeAttemptCount: 0,
1092
+ resumeSuccessCount: 0,
1093
+ resumeFailureCount: 0,
1094
+ lastResumeReasonCode: null,
1095
+ delayedEventCount: 0,
1096
+ droppedEventCount: 0,
1097
+ queuedEventCount: 0,
1098
+ };
1099
+ const settings = { ...defaults, ...overrides };
1100
+ const parseNonNegativeNumber = (value, field) => {
1101
+ if (value == null) return 0;
1102
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
1103
+ throw new Error(\`aura.multiplayer.configureNetworkImpairment(options): \${field} must be a non-negative number\`);
1104
+ }
1105
+ return value;
1106
+ };
1107
+ const requireInternetMode = (options) => {
1108
+ const mode = String((options && options.internetMode) || 'local').trim().toLowerCase();
1109
+ if (!['local', 'relay', 'auto', 'p2p'].includes(mode)) {
1110
+ throw new Error('aura.multiplayer.host(options) failed [reason:invalid_internet_mode]: invalid internet mode');
1111
+ }
1112
+ return mode;
1113
+ };
1114
+ const buildNetworkDiagnostics = () => ({
1115
+ connected: Boolean(roomInfo),
1116
+ role: roomInfo ? roomInfo.role : null,
1117
+ scope: roomInfo ? roomInfo.scope : null,
1118
+ transportPath: roomInfo ? roomInfo.transportPath : null,
1119
+ requestedMode: roomInfo ? roomInfo.requestedMode : null,
1120
+ transportStatus: roomInfo ? roomInfo.transportStatus : null,
1121
+ lastReasonCode: roomInfo ? roomInfo.lastReasonCode : null,
1122
+ jitterMs: null,
1123
+ bufferDelayMs: null,
1124
+ historyDepth: 0,
1125
+ bufferedServerTimeMs: null,
1126
+ lastSequence: null,
1127
+ lastSnapshotServerTimeMs: null,
1128
+ lastSnapshotTickIntervalMs: null,
1129
+ lastSnapshotSource: null,
1130
+ sequenceGapCount: 0,
1131
+ delayedEventCount: settings.delayedEventCount,
1132
+ droppedEventCount: settings.droppedEventCount,
1133
+ queuedEventCount: settings.queuedEventCount,
1134
+ fallbackCount: settings.fallbackCount,
1135
+ lastFallbackReasonCode: settings.lastFallbackReasonCode,
1136
+ resumeAttemptCount: settings.resumeAttemptCount,
1137
+ resumeSuccessCount: settings.resumeSuccessCount,
1138
+ resumeFailureCount: settings.resumeFailureCount,
1139
+ lastResumeReasonCode: settings.lastResumeReasonCode,
1140
+ impairment: clone(networkImpairment),
1141
+ });
1142
+ return {
1143
+ configure() {
1144
+ return true;
1145
+ },
1146
+ host(options) {
1147
+ const code = normalizeCode(options && options.roomCode);
1148
+ if (!validateCode(code)) {
1149
+ throw new Error('aura.multiplayer.host(options): roomCode must be 4-8 letters or digits [reason:invalid_room_code]');
1150
+ }
1151
+ const mode = requireInternetMode(options || {});
1152
+ activeRoom = {
1153
+ code,
1154
+ name: options && options.name ? String(options.name) : null,
1155
+ connectivity: {
1156
+ mode,
1157
+ coordinatorUrl: String(options.coordinatorUrl),
1158
+ relayUrl: options && options.relayUrl ? String(options.relayUrl) : null,
1159
+ },
1160
+ };
1161
+ roomInfo = {
1162
+ role: 'host',
1163
+ code,
1164
+ address: null,
1165
+ port: null,
1166
+ name: activeRoom.name,
1167
+ scope: 'internet',
1168
+ transportPath: settings.hostTransportPath,
1169
+ requestedMode: mode,
1170
+ transportStatus: settings.hostTransportStatus,
1171
+ lastReasonCode: null,
1172
+ connectivity: clone(activeRoom.connectivity),
1173
+ migrating: false,
1174
+ migrationCompleted: false,
1175
+ previousHostId: null,
1176
+ newHostId: null,
1177
+ };
1178
+ return clone(roomInfo);
1179
+ },
1180
+ join(code, options) {
1181
+ const normalizedCode = normalizeCode(code);
1182
+ if (!validateCode(normalizedCode)) {
1183
+ throw new Error('aura.multiplayer.join(code): code must be 4-8 letters or digits [reason:invalid_room_code]');
1184
+ }
1185
+ const mode = requireInternetMode(options || {});
1186
+ if (!activeRoom || normalizedCode !== activeRoom.code) {
1187
+ throw new Error('aura.multiplayer.join(code, options) failed [reason:room_code_not_found]: room not registered');
1188
+ }
1189
+ roomInfo = {
1190
+ role: settings.joinRole,
1191
+ code: normalizedCode,
1192
+ address: '203.0.113.10',
1193
+ port: 62010,
1194
+ name: activeRoom.name,
1195
+ scope: 'internet',
1196
+ transportPath: settings.joinTransportPath,
1197
+ requestedMode: String(options.internetMode || 'local').trim().toLowerCase(),
1198
+ transportStatus: settings.joinTransportStatus,
1199
+ lastReasonCode: settings.joinLastReasonCode,
1200
+ peerCandidate: settings.peerCandidateHost == null
1201
+ ? null
1202
+ : { host: settings.peerCandidateHost, port: settings.peerCandidatePort },
1203
+ connectivity: clone(activeRoom.connectivity),
1204
+ migrating: settings.migrating,
1205
+ migrationCompleted: settings.migrationCompleted,
1206
+ previousHostId: settings.previousHostId,
1207
+ newHostId: settings.newHostId,
1208
+ };
1209
+ return true;
1210
+ },
1211
+ completeMigration() {
1212
+ if (!roomInfo) return false;
1213
+ roomInfo = {
1214
+ ...roomInfo,
1215
+ role: 'host',
1216
+ transportStatus: 'direct_udp_ready',
1217
+ lastReasonCode: null,
1218
+ migrating: false,
1219
+ migrationCompleted: true,
1220
+ previousHostId: null,
1221
+ newHostId: null,
1222
+ };
1223
+ return true;
1224
+ },
1225
+ getRoomInfo() {
1226
+ return roomInfo ? clone(roomInfo) : null;
1227
+ },
1228
+ getNetworkDiagnostics() {
1229
+ return clone(buildNetworkDiagnostics());
1230
+ },
1231
+ configureNetworkImpairment(options = {}) {
1232
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
1233
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): options must be an object');
1234
+ }
1235
+ const latencyMs = parseNonNegativeNumber(options.latencyMs, 'latencyMs');
1236
+ const jitterMs = parseNonNegativeNumber(options.jitterMs, 'jitterMs');
1237
+ const outageMs = parseNonNegativeNumber(options.outageMs, 'outageMs');
1238
+ let lossRate = 0;
1239
+ if (options.lossRate != null) {
1240
+ if (typeof options.lossRate !== 'number' || !Number.isFinite(options.lossRate) || options.lossRate < 0 || options.lossRate > 1) {
1241
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): lossRate must be a number in range 0..1');
1242
+ }
1243
+ lossRate = options.lossRate;
1244
+ }
1245
+ let seed = null;
1246
+ if (options.seed != null) {
1247
+ if (typeof options.seed !== 'number' || !Number.isInteger(options.seed) || options.seed < 0) {
1248
+ throw new Error('aura.multiplayer.configureNetworkImpairment(options): seed must be a non-negative integer');
1249
+ }
1250
+ seed = options.seed;
1251
+ }
1252
+ const enabled = latencyMs > 0 || jitterMs > 0 || lossRate > 0 || outageMs > 0;
1253
+ networkImpairment = {
1254
+ enabled,
1255
+ latencyMs,
1256
+ jitterMs,
1257
+ lossRate,
1258
+ outageMs,
1259
+ seed,
1260
+ };
1261
+ return enabled;
1262
+ },
1263
+ clearNetworkImpairment() {
1264
+ const hadImpairment = networkImpairment.enabled;
1265
+ networkImpairment = {
1266
+ enabled: false,
1267
+ latencyMs: 0,
1268
+ jitterMs: 0,
1269
+ lossRate: 0,
1270
+ outageMs: 0,
1271
+ seed: null,
1272
+ };
1273
+ return hadImpairment;
1274
+ },
1275
+ stop() {
1276
+ activeRoom = null;
1277
+ roomInfo = null;
1278
+ return true;
1279
+ },
1280
+ };
1281
+ });
1282
+
1283
+ const directHarness = createRelayHarness();
1284
+ directHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1285
+ const hostedRoom = directHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1286
+ const hostRoomInfo = directHarness.getRoomInfo();
1287
+ const joinOk = directHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1288
+ const joinedRoomInfo = directHarness.getRoomInfo();
1289
+ const directNetworkDiagnostics = directHarness.getNetworkDiagnostics();
1290
+ const hostStop = directHarness.stop();
1291
+ const roomInfoAfterStop = directHarness.getRoomInfo();
1292
+
1293
+ const fallbackHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'relay_fallback', joinLastReasonCode: 'direct_udp_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024, fallbackCount: 1, lastFallbackReasonCode: 'direct_udp_timeout', resumeAttemptCount: 1, resumeSuccessCount: 1, resumeFailureCount: 0, lastResumeReasonCode: 'resume_recovered', delayedEventCount: 2, droppedEventCount: 1, queuedEventCount: 1 });
1294
+ fallbackHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1295
+ fallbackHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1296
+ fallbackHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1297
+ const fallbackRoomInfo = fallbackHarness.getRoomInfo();
1298
+ const fallbackImpairmentConfigured = fallbackHarness.configureNetworkImpairment({ latencyMs: 75, jitterMs: 5, lossRate: 0.25, seed: 11 });
1299
+ const fallbackNetworkDiagnostics = fallbackHarness.getNetworkDiagnostics();
1300
+ const fallbackImpairmentCleared = fallbackHarness.clearNetworkImpairment();
1301
+ const fallbackNetworkDiagnosticsAfterClear = fallbackHarness.getNetworkDiagnostics();
1302
+ const fallbackStop = fallbackHarness.stop();
1303
+
1304
+ const expiredHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'room_expired', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 });
1305
+ expiredHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1306
+ expiredHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1307
+ expiredHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1308
+ const expiredRoomInfo = expiredHarness.getRoomInfo();
1309
+
1310
+ const heartbeatHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'control_heartbeat_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 });
1311
+ heartbeatHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1312
+ heartbeatHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1313
+ heartbeatHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1314
+ const heartbeatRoomInfo = heartbeatHarness.getRoomInfo();
1315
+
1316
+ const attachFailureHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'error', joinLastReasonCode: 'relay_attach_failed', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 });
1317
+ attachFailureHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1318
+ attachFailureHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1319
+ attachFailureHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1320
+ const attachFailureRoomInfo = attachFailureHarness.getRoomInfo();
1321
+
1322
+ const migrationHarness = createRelayHarness({ joinRole: 'host', joinLastReasonCode: 'host_migrating', migrating: true, previousHostId: 7, newHostId: 1 });
1323
+ migrationHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' });
1324
+ migrationHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1325
+ migrationHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1326
+ const migratingRoomInfo = migrationHarness.getRoomInfo();
1327
+ migrationHarness.completeMigration();
1328
+ const migrationCompletedRoomInfo = migrationHarness.getRoomInfo();
1329
+
1330
+ const invalidInternetMode = (() => {
1331
+ try {
1332
+ runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', internetMode: 'bogus' });
1333
+ return 'no-error';
1334
+ } catch (error) {
1335
+ return String(error && error.message ? error.message : error);
1336
+ }
1337
+ })();
1338
+ const missingCoordinator = (() => {
1339
+ try {
1340
+ runtimeApi.join('NET2P', { internetMode: 'relay' });
1341
+ return 'no-error';
1342
+ } catch (error) {
1343
+ return String(error && error.message ? error.message : error);
1344
+ }
1345
+ })();
1346
+ const coordinatorConnectFailedHost = (() => {
1347
+ try {
1348
+ runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' });
1349
+ return 'no-error';
1350
+ } catch (error) {
1351
+ return String(error && error.message ? error.message : error);
1352
+ }
1353
+ })();
1354
+ const coordinatorConnectFailedJoin = (() => {
1355
+ try {
1356
+ runtimeApi.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'relay' });
1357
+ return 'no-error';
1358
+ } catch (error) {
1359
+ return String(error && error.message ? error.message : error);
1360
+ }
1361
+ })();
1362
+ const runtimeRoomInfoAfterFailures = typeof runtimeApi.getRoomInfo === 'function' ? runtimeApi.getRoomInfo() : null;
1363
+ globalThis.__multiplayerRelayRoomFlow = {
1364
+ hostedRoom,
1365
+ hostRoomInfo,
1366
+ joinOk,
1367
+ joinedRoomInfo,
1368
+ directNetworkDiagnostics,
1369
+ hostStop,
1370
+ roomInfoAfterStop,
1371
+ fallbackRoomInfo,
1372
+ fallbackImpairmentConfigured,
1373
+ fallbackNetworkDiagnostics,
1374
+ fallbackImpairmentCleared,
1375
+ fallbackNetworkDiagnosticsAfterClear,
1376
+ fallbackStop,
1377
+ expiredRoomInfo,
1378
+ heartbeatRoomInfo,
1379
+ attachFailureRoomInfo,
1380
+ migratingRoomInfo,
1381
+ migrationCompletedRoomInfo,
1382
+ invalidInternetMode,
1383
+ missingCoordinator,
1384
+ coordinatorConnectFailedHost,
1385
+ coordinatorConnectFailedJoin,
1386
+ runtimeRoomInfoAfterFailures,
1387
+ };
1388
+ };
1389
+ `,
535
1390
  },
536
1391
  {
537
1392
  id: 'optional-module-legacy-steam-config-guidance',
@@ -549,6 +1404,10 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
549
1404
  id: 'optional.legacy-steam.not-exposed',
550
1405
  expression: "typeof aura.steam === 'undefined' && !Object.prototype.hasOwnProperty.call(aura, 'steam')",
551
1406
  },
1407
+ {
1408
+ id: 'optional.legacy-steam.provider-not-exposed',
1409
+ expression: "typeof aura.providers === 'undefined' || typeof aura.providers.steam === 'undefined'",
1410
+ },
552
1411
  {
553
1412
  id: 'optional.legacy-steam.physics-disabled-guidance',
554
1413
  expression: "(() => { try { aura.physics.step(0.016); return false; } catch (e) { const msg = String(e); return msg.includes('optional module \"physics\" is disabled') && msg.includes('modules.physics = true') && msg.includes('[reason:optional_module_physics_disabled]'); } })()",
@@ -1002,15 +1861,15 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
1002
1861
  },
1003
1862
  {
1004
1863
  id: 'draw3d.postfx.target-chain.custom-pass.deterministic',
1005
- expression: "(() => { const runSample = () => { const baseline = aura.draw3d.getPostFXState(); aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('colorGrade'); aura.draw3d.removePostFXPass('vignette'); aura.draw3d.removePostFXPass('fxaa'); aura.draw3d.removePostFXPass('custom:filmgrain'); aura.draw3d.removePostFXPass('custom:glow'); const setBloom = aura.draw3d.setPostFXPass('bloom', { strength: 1.1, targetChain: { intermediateTargets: ['mainA', 'mainB', 'mainA'], pingPong: true, composeMode: 'additive' } }); const setCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.75, threshold: 0.33 } }); const updateCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.5, threshold: 0.2 } }); const setFilmgrain = aura.draw3d.setPostFXPass('custom:filmgrain', { customParams: { amount: 0.2, grain: 0.1 } }); const state = aura.draw3d.getPostFXState(); const targetChain = state?.targetChain || {}; const customGlow = Array.isArray(state?.passes) ? state.passes.find((pass) => pass.pass === 'custom:glow') : null; const customParams = customGlow?.customParams || {}; const customParamKeys = Object.keys(customParams).sort().join('|'); return { status: [setBloom?.reasonCode, setCustom?.reasonCode, updateCustom?.reasonCode, setFilmgrain?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((pass) => pass.pass).join('|') : '', enabled: state?.enabledPasses, total: state?.totalPasses, customPassCount: state?.customPassCount, targetChainTargets: Array.isArray(targetChain?.intermediateTargets) ? targetChain.intermediateTargets.join('|') : '', targetChainCount: targetChain?.intermediateTargetCount, targetChainPingPong: targetChain?.pingPong === true, targetChainComposeMode: targetChain?.composeMode, targetChainFingerprint: state?.targetChainFingerprint, customParamFingerprint: state?.customParamFingerprint, customGlowIsCustom: customGlow?.isCustom === true, customGlowParamKeys: customParamKeys, customGlowIntensity: customParams?.intensity, customGlowThreshold: customParams?.threshold, mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'bloom|custom:filmgrain|custom:glow' && first.total === 3 && first.enabled === 3 && first.customPassCount === 2 && first.targetChainTargets === 'maina|mainb' && first.targetChainCount === 2 && first.targetChainPingPong === true && first.targetChainComposeMode === 'additive' && Number.isFinite(first.targetChainFingerprint) && first.targetChainFingerprint > 0 && Number.isFinite(first.customParamFingerprint) && first.customParamFingerprint > 0 && first.customGlowIsCustom === true && first.customGlowParamKeys === 'intensity|threshold' && Math.abs(Number(first.customGlowIntensity) - 0.5) < 1e-3 && Math.abs(Number(first.customGlowThreshold) - 0.2) < 1e-3 && first.mutationDelta === 10 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1864
+ expression: "(() => { const runSample = () => { const baseline = aura.draw3d.getPostFXState(); aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('colorGrade'); aura.draw3d.removePostFXPass('vignette'); aura.draw3d.removePostFXPass('fxaa'); aura.draw3d.removePostFXPass('filmGrain'); aura.draw3d.removePostFXPass('custom:glow'); const setBloom = aura.draw3d.setPostFXPass('bloom', { strength: 1.1, targetChain: { intermediateTargets: ['mainA', 'mainB', 'mainA'], pingPong: true, composeMode: 'additive' } }); const setCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.75, threshold: 0.33 } }); const updateCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.5, threshold: 0.2 } }); const setFilmgrain = aura.draw3d.setPostFXPass('filmGrain', { customParams: { amount: 0.2, speed: 0.1 } }); const state = aura.draw3d.getPostFXState(); const targetChain = state?.targetChain || {}; const customGlow = Array.isArray(state?.passes) ? state.passes.find((pass) => pass.pass === 'custom:glow') : null; const customParams = customGlow?.customParams || {}; const customParamKeys = Object.keys(customParams).sort().join('|'); return { status: [setBloom?.reasonCode, setCustom?.reasonCode, updateCustom?.reasonCode, setFilmgrain?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((pass) => pass.pass).join('|') : '', enabled: state?.enabledPasses, total: state?.totalPasses, customPassCount: state?.customPassCount, targetChainTargets: Array.isArray(targetChain?.intermediateTargets) ? targetChain.intermediateTargets.join('|') : '', targetChainCount: targetChain?.intermediateTargetCount, targetChainPingPong: targetChain?.pingPong === true, targetChainComposeMode: targetChain?.composeMode, targetChainFingerprint: state?.targetChainFingerprint, customParamFingerprint: state?.customParamFingerprint, customGlowIsCustom: customGlow?.isCustom === true, customGlowParamKeys: customParamKeys, customGlowIntensity: customParams?.intensity, customGlowThreshold: customParams?.threshold, mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'bloom|filmGrain|custom:glow' && first.total === 3 && first.enabled === 3 && first.customPassCount === 1 && first.targetChainTargets === 'maina|mainb' && first.targetChainCount === 2 && first.targetChainPingPong === true && first.targetChainComposeMode === 'additive' && Number.isFinite(first.targetChainFingerprint) && first.targetChainFingerprint > 0 && Number.isFinite(first.customParamFingerprint) && first.customParamFingerprint > 0 && first.customGlowIsCustom === true && first.customGlowParamKeys === 'intensity|threshold' && Math.abs(Number(first.customGlowIntensity) - 0.5) < 1e-3 && Math.abs(Number(first.customGlowThreshold) - 0.2) < 1e-3 && first.mutationDelta === 10 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1006
1865
  },
1007
1866
  {
1008
1867
  id: 'draw3d.postfx.motion-blur.state.deterministic',
1009
- expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.75, radius: 12, threshold: 1.0, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('motionBlur', false); const reenable = aura.draw3d.setPostFXEnabled('motionBlur', true); const update = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.35, radius: 6, threshold: 0.0 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'motionBlur') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'motionBlur' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.35) < 1e-3 && Math.abs(first.radius - 6.0) < 1e-3 && Math.abs(first.threshold - 0.0) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1868
+ expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'filmGrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.75, radius: 12, threshold: 1.0, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('motionBlur', false); const reenable = aura.draw3d.setPostFXEnabled('motionBlur', true); const update = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.35, radius: 6, threshold: 0.0 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'motionBlur') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'motionBlur' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.35) < 1e-3 && Math.abs(first.radius - 6.0) < 1e-3 && Math.abs(first.threshold - 0.0) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1010
1869
  },
1011
1870
  {
1012
1871
  id: 'draw3d.postfx.ssao.state.deterministic',
1013
- expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('ssao', { strength: 1.1, radius: 0.42, threshold: 0.18, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('ssao', false); const reenable = aura.draw3d.setPostFXEnabled('ssao', true); const update = aura.draw3d.setPostFXPass('ssao', { strength: 0.92, radius: 0.36, threshold: 0.24 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'ssao') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'ssao' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.92) < 1e-3 && Math.abs(first.radius - 0.36) < 1e-3 && Math.abs(first.threshold - 0.24) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1872
+ expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'filmGrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('ssao', { strength: 1.1, radius: 0.42, threshold: 0.18, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('ssao', false); const reenable = aura.draw3d.setPostFXEnabled('ssao', true); const update = aura.draw3d.setPostFXPass('ssao', { strength: 0.92, radius: 0.36, threshold: 0.24 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'ssao') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'ssao' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.92) < 1e-3 && Math.abs(first.radius - 0.36) < 1e-3 && Math.abs(first.threshold - 0.24) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1014
1873
  },
1015
1874
  {
1016
1875
  id: 'draw3d.postfx.reason-codes.stable',
@@ -1018,7 +1877,7 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
1018
1877
  },
1019
1878
  {
1020
1879
  id: 'draw3d.postfx.ssr-dof.runtime.setup',
1021
- expression: `(() => { try { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const meshHandle = aura.mesh.createBox(1, 1, 1); const materialHandle = aura.material.create({ color: { r: 0.35, g: 0.7, b: 0.95, a: 1 }, metallic: 0.1, roughness: 0.6 }); const nodeId = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2.5 } }); const bind = aura.scene3d.bindRenderNode(nodeId, meshHandle, materialHandle, { layer: 0, visible: true, cull: false }); aura.camera3d.perspective(60, 0.1, 100); aura.camera3d.setPosition(0, 0, 4); aura.camera3d.lookAt(0, 0, 0); const setSsr = aura.draw3d.setPostFXPass('ssr', { strength: 1.0, threshold: 0.35, maxSteps: 96, thickness: 0.25, fadeEdge: 0.8, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const setDof = aura.draw3d.setPostFXPass('dof', { strength: 0.9, focalDistance: 2.5, focalLength: 50, aperture: 2.2, maxBlur: 6.0 }); const setMotionBlur = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.65, radius: 12.0, threshold: 0.0 }); const baseDraw = aura.draw; aura.draw = function () { const frameIndex = Number(globalThis.__postfxRuntimeFrame || 0); aura.camera3d.setPosition(frameIndex * 0.15, 0, 4 - (frameIndex * 0.1)); aura.camera3d.lookAt(0, 0, 0); aura.draw3d.clear3d({ r: 0.02, g: 0.02, b: 0.03, a: 1 }); aura.scene3d.submitRenderBindings(); globalThis.__postfxRuntimeFrame = frameIndex + 1; baseDraw(); }; globalThis.__postfxRuntimeFrame = 0; globalThis.__postfxRuntimeProbe = { meshHandle, materialHandle, nodeId, bindOk: bind?.ok === true, setSsrReason: setSsr?.reasonCode || null, setDofReason: setDof?.reasonCode || null, setMotionBlurReason: setMotionBlur?.reasonCode || null, baselineMutationCount: Number(baseline?.mutationCount || 0) }; return Number.isInteger(meshHandle) && meshHandle > 0 && Number.isInteger(materialHandle) && materialHandle > 0 && Number.isInteger(nodeId) && nodeId > 0 && bind?.ok === true && setSsr?.ok === true && setDof?.ok === true && setMotionBlur?.ok === true; } catch (_) { return false; } })()`,
1880
+ expression: `(() => { try { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'filmGrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const meshHandle = aura.mesh.createBox(1, 1, 1); const materialHandle = aura.material.create({ color: { r: 0.35, g: 0.7, b: 0.95, a: 1 }, metallic: 0.1, roughness: 0.6 }); const nodeId = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2.5 } }); const bind = aura.scene3d.bindRenderNode(nodeId, meshHandle, materialHandle, { layer: 0, visible: true, cull: false }); aura.camera3d.perspective(60, 0.1, 100); aura.camera3d.setPosition(0, 0, 4); aura.camera3d.lookAt(0, 0, 0); const setSsr = aura.draw3d.setPostFXPass('ssr', { strength: 1.0, threshold: 0.35, maxSteps: 96, thickness: 0.25, fadeEdge: 0.8, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const setDof = aura.draw3d.setPostFXPass('dof', { strength: 0.9, focalDistance: 2.5, focalLength: 50, aperture: 2.2, maxBlur: 6.0 }); const setMotionBlur = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.65, radius: 12.0, threshold: 0.0 }); const baseDraw = aura.draw; aura.draw = function () { const frameIndex = Number(globalThis.__postfxRuntimeFrame || 0); aura.camera3d.setPosition(frameIndex * 0.15, 0, 4 - (frameIndex * 0.1)); aura.camera3d.lookAt(0, 0, 0); aura.draw3d.clear3d({ r: 0.02, g: 0.02, b: 0.03, a: 1 }); aura.scene3d.submitRenderBindings(); globalThis.__postfxRuntimeFrame = frameIndex + 1; baseDraw(); }; globalThis.__postfxRuntimeFrame = 0; globalThis.__postfxRuntimeProbe = { meshHandle, materialHandle, nodeId, bindOk: bind?.ok === true, setSsrReason: setSsr?.reasonCode || null, setDofReason: setDof?.reasonCode || null, setMotionBlurReason: setMotionBlur?.reasonCode || null, baselineMutationCount: Number(baseline?.mutationCount || 0) }; return Number.isInteger(meshHandle) && meshHandle > 0 && Number.isInteger(materialHandle) && materialHandle > 0 && Number.isInteger(nodeId) && nodeId > 0 && bind?.ok === true && setSsr?.ok === true && setDof?.ok === true && setMotionBlur?.ok === true; } catch (_) { return false; } })()`,
1022
1881
  },
1023
1882
  ],
1024
1883
  nativePostChecks: [
@@ -1772,5 +2631,262 @@ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
1772
2631
  };
1773
2632
  `,
1774
2633
  frames: 4,
2634
+ },
2635
+ {
2636
+ id: 'asset-forward-2d-flagship-runtime',
2637
+ modes: ['native'],
2638
+ namespaces: ['animation', 'audio', 'draw2d', 'tilemap', 'debug'],
2639
+ functions: [
2640
+ 'aura.animation.registerAtlas',
2641
+ 'aura.animation.resolveAtlasFrame',
2642
+ 'aura.animation.createAtlasClip',
2643
+ 'aura.animation.stepAtlasClip',
2644
+ 'aura.animation.getAtlasClipState',
2645
+ 'aura.audio.play',
2646
+ 'aura.audio.stopAll',
2647
+ 'aura.audio.setBusVolume',
2648
+ 'aura.audio.update',
2649
+ 'aura.audio.getMixerState',
2650
+ 'aura.draw2d.createRenderTarget',
2651
+ 'aura.draw2d.destroyRenderTarget',
2652
+ 'aura.draw2d.runCompositorGraph',
2653
+ 'aura.draw2d.sprite',
2654
+ 'aura.draw2d.text',
2655
+ 'aura.draw2d.clear',
2656
+ 'aura.tilemap.import',
2657
+ 'aura.tilemap.draw',
2658
+ 'aura.tilemap.setTile',
2659
+ 'aura.tilemap.removeRegion',
2660
+ 'aura.tilemap.queryRay',
2661
+ 'aura.tilemap.unload',
2662
+ 'aura.debug.inspectorStats',
2663
+ ],
2664
+ nativeChecks: [
2665
+ {
2666
+ id: 'asset-forward-2d.surface.methods',
2667
+ expression: "(() => ['registerAtlas','resolveAtlasFrame','createAtlasClip','stepAtlasClip','getAtlasClipState'].every((name) => typeof aura.animation?.[name] === 'function') && ['play','stopAll','setBusVolume','update','getMixerState'].every((name) => typeof aura.audio?.[name] === 'function') && ['createRenderTarget','destroyRenderTarget','runCompositorGraph','sprite','text','clear'].every((name) => typeof aura.draw2d?.[name] === 'function') && ['import','draw','setTile','removeRegion','queryRay','unload'].every((name) => typeof aura.tilemap?.[name] === 'function') && typeof aura.debug?.inspectorStats === 'function')()",
2668
+ },
2669
+ ],
2670
+ source: `
2671
+ aura.setup = async function () {
2672
+ if (aura.assets && typeof aura.assets.load === 'function') {
2673
+ await aura.assets.load(['player.png', 'coin.wav']);
2674
+ }
2675
+
2676
+ if (typeof aura.debug?.enableInspector === 'function') {
2677
+ aura.debug.enableInspector(true);
2678
+ }
2679
+
2680
+ const atlas = aura.animation.registerAtlas({
2681
+ image: 'player.png',
2682
+ frames: {
2683
+ drive_0: { x: 0, y: 0, w: 16, h: 16 },
2684
+ drive_1: { x: 0, y: 0, w: 16, h: 16 },
2685
+ drive_2: { x: 0, y: 0, w: 16, h: 16 },
2686
+ },
2687
+ clips: {
2688
+ drive: {
2689
+ frames: ['drive_0', 'drive_1', 'drive_2'],
2690
+ frameDuration: 0.1,
2691
+ loop: true,
2692
+ },
2693
+ },
2694
+ });
2695
+ aura.test.assert(atlas && atlas.ok === true, 'asset-forward atlas registration should succeed');
2696
+
2697
+ const clip = aura.animation.createAtlasClip({
2698
+ atlasId: atlas.atlasId,
2699
+ clipKey: 'drive',
2700
+ playing: true,
2701
+ });
2702
+ aura.test.assert(clip && clip.ok === true, 'asset-forward atlas clip should succeed');
2703
+
2704
+ const mapHandle = aura.tilemap.import({
2705
+ width: 8,
2706
+ height: 4,
2707
+ tilewidth: 16,
2708
+ tileheight: 16,
2709
+ solidLayerNames: ['cover'],
2710
+ layers: [
2711
+ { name: 'ground', type: 'tilelayer', width: 8, height: 4, data: Array.from({ length: 32 }, () => 1) },
2712
+ { name: 'cover', type: 'tilelayer', width: 8, height: 4, data: [
2713
+ 0, 0, 0, 0, 0, 0, 0, 0,
2714
+ 0, 0, 0, 0, 0, 0, 0, 0,
2715
+ 0, 0, 1, 1, 0, 0, 0, 0,
2716
+ 0, 0, 0, 0, 0, 0, 0, 0,
2717
+ ], properties: { solid: true } },
2718
+ { name: 'decor', type: 'tilelayer', width: 8, height: 4, data: [
2719
+ 0, 1, 0, 1, 0, 1, 0, 1,
2720
+ 1, 0, 1, 0, 1, 0, 1, 0,
2721
+ 0, 1, 0, 1, 0, 1, 0, 1,
2722
+ 1, 0, 1, 0, 1, 0, 1, 0,
2723
+ ] },
2724
+ ],
2725
+ tilesets: [
2726
+ { firstgid: 1, image: 'player.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 },
2727
+ ],
2728
+ });
2729
+ aura.test.assert(Number.isInteger(mapHandle) && mapHandle > 0, 'asset-forward tilemap import should succeed');
2730
+
2731
+ const panelTarget = aura.draw2d.createRenderTarget(160, 96);
2732
+ const fxTarget = aura.draw2d.createRenderTarget(192, 112);
2733
+ const finalTarget = aura.draw2d.createRenderTarget(224, 128);
2734
+ aura.test.assert(panelTarget?.ok === true && fxTarget?.ok === true && finalTarget?.ok === true, 'asset-forward render targets should succeed');
2735
+
2736
+ const musicHandle = aura.audio.play('coin.wav', { volume: 0.2, loop: true, bus: 'music' });
2737
+ const sfxHandle = aura.audio.play('coin.wav', { volume: 0.08, loop: true, bus: 'sfx' });
2738
+ aura.test.assert(Number.isInteger(musicHandle) && musicHandle > 0, 'asset-forward music handle should be valid');
2739
+ aura.test.assert(Number.isInteger(sfxHandle) && sfxHandle > 0, 'asset-forward sfx handle should be valid');
2740
+ aura.test.assert(aura.audio.setBusVolume('music', 0.55)?.ok === true, 'music bus volume should be configurable');
2741
+ aura.test.assert(aura.audio.setBusVolume('sfx', 0.7)?.ok === true, 'sfx bus volume should be configurable');
2742
+
2743
+ globalThis.__assetForward2dFlagship = {
2744
+ atlasId: atlas.atlasId,
2745
+ clipId: clip.clipId,
2746
+ mapHandle,
2747
+ panelTarget,
2748
+ fxTarget,
2749
+ finalTarget,
2750
+ frames: [],
2751
+ lastStepOk: false,
2752
+ };
2753
+ };
2754
+
2755
+ aura.update = function () {
2756
+ const probe = globalThis.__assetForward2dFlagship;
2757
+ const stepDt = [0.05, 0.06, 0.11, 0.08][probe.frames.length] || 0.05;
2758
+ const step = aura.animation.stepAtlasClip(probe.clipId, stepDt);
2759
+ probe.lastStepOk = !!step && step.ok === true;
2760
+ aura.audio.update(0.125);
2761
+
2762
+ const tileX = 2 + (probe.frames.length % 2);
2763
+ aura.tilemap.setTile(probe.mapHandle, 'cover', { x: tileX, y: 2 }, probe.frames.length >= 2 ? 1 : 0);
2764
+ if (probe.frames.length === 1) {
2765
+ aura.tilemap.removeRegion(probe.mapHandle, 'decor', { x: 6, y: 1, w: 1, h: 1 });
2766
+ }
2767
+ };
2768
+
2769
+ aura.draw = function () {
2770
+ const probe = globalThis.__assetForward2dFlagship;
2771
+ const frameIndex = probe.frames.length;
2772
+ const clipState = aura.animation.getAtlasClipState(probe.clipId);
2773
+ const spriteFrame = aura.animation.resolveAtlasFrame(probe.atlasId, clipState?.frameKey || 'drive_0');
2774
+ const ray = aura.tilemap.queryRay(probe.mapHandle, { x: 0.5, y: 32.5, dx: 1, dy: 0, maxDistance: 128 });
2775
+ const mixer = aura.audio.getMixerState() || {};
2776
+ const musicBus = Array.isArray(mixer.buses)
2777
+ ? mixer.buses.find((entry) => entry.bus === 'music')
2778
+ : null;
2779
+
2780
+ aura.draw2d.clear(4, 6, 10);
2781
+ let graph = null;
2782
+ if (frameIndex === 0) {
2783
+ graph = aura.draw2d.runCompositorGraph('asset-forward-2d-proof', [
2784
+ {
2785
+ id: 'panel',
2786
+ target: probe.panelTarget,
2787
+ draw: () => {
2788
+ aura.draw2d.clear(0, 0, 0, 0);
2789
+ aura.tilemap.draw(probe.mapHandle, {
2790
+ camera: { x: 0, y: 0, width: 128, height: 64 },
2791
+ });
2792
+ aura.draw2d.sprite(spriteFrame.image, 34, 22, {
2793
+ width: 32,
2794
+ height: 32,
2795
+ frameX: spriteFrame.frameX,
2796
+ frameY: spriteFrame.frameY,
2797
+ frameW: spriteFrame.frameW,
2798
+ frameH: spriteFrame.frameH,
2799
+ });
2800
+ aura.draw2d.text('ASSET-FORWARD', 10, 10, { size: 10, color: aura.colors.white });
2801
+ },
2802
+ },
2803
+ {
2804
+ id: 'fx',
2805
+ target: probe.fxTarget,
2806
+ draw: () => {
2807
+ aura.draw2d.clear(0, 0, 0, 0);
2808
+ aura.draw2d.sprite(probe.panelTarget, 0, 0, { width: 192, height: 112 });
2809
+ aura.draw2d.text('AUDIO x2', 14, 82, { size: 11, color: aura.colors.white });
2810
+ },
2811
+ },
2812
+ {
2813
+ id: 'final',
2814
+ target: probe.finalTarget,
2815
+ draw: () => {
2816
+ aura.draw2d.clear(0, 0, 0, 0);
2817
+ aura.draw2d.sprite(probe.fxTarget, 0, 0, { width: 224, height: 128 });
2818
+ },
2819
+ },
2820
+ ]);
2821
+ if (Array.isArray(graph?.stages) && graph.stages.length === 3) {
2822
+ aura.draw2d.sprite(graph.stages[2], 8, 8, { width: 224, height: 128 });
2823
+ }
2824
+ } else {
2825
+ aura.draw2d.sprite(probe.finalTarget, 8, 8, { width: 224, height: 128 });
2826
+ }
2827
+
2828
+ const stats = aura.debug.inspectorStats() || {};
2829
+ const compositor = stats.draw2dRuntime?.compositor || {};
2830
+ probe.frames.push({
2831
+ frameIndex,
2832
+ graphReason: graph?.reasonCode || null,
2833
+ graphStageCount: Number(graph?.stageCount || 0),
2834
+ clipFrame: clipState?.frameKey || null,
2835
+ clipLoops: Number(clipState?.loops || 0),
2836
+ frameResolved: spriteFrame?.ok === true,
2837
+ lastStepOk: probe.lastStepOk === true,
2838
+ rayOk: ray?.ok === true,
2839
+ rayHit: ray?.hit === true,
2840
+ rayLayer: Number(ray?.hitCell?.layerIndex ?? -1),
2841
+ trackCount: Array.isArray(mixer.tracks) ? mixer.tracks.length : -1,
2842
+ musicBusVolume: musicBus ? Number(musicBus.volume.toFixed(6)) : null,
2843
+ knownGraphCount: Number(compositor.knownGraphCount || 0),
2844
+ lastGraphStageCount: Number(compositor.lastGraphStageCount || 0),
2845
+ lastGraphReasonCode: compositor.lastGraphReasonCode || null,
2846
+ });
2847
+
2848
+ if (probe.frames.length !== 4) {
2849
+ return;
2850
+ }
2851
+
2852
+ const trace = probe.frames.map((entry) => entry.clipFrame + ':' + entry.clipLoops);
2853
+ const destroyPanel = aura.draw2d.destroyRenderTarget(probe.panelTarget);
2854
+ const destroyFx = aura.draw2d.destroyRenderTarget(probe.fxTarget);
2855
+ const destroyFinal = aura.draw2d.destroyRenderTarget(probe.finalTarget);
2856
+ const missingDestroy = aura.draw2d.destroyRenderTarget(probe.finalTarget);
2857
+ const unloaded = aura.tilemap.unload(probe.mapHandle);
2858
+ aura.audio.stopAll();
2859
+
2860
+ aura.test.equal(
2861
+ trace.join('|'),
2862
+ 'drive_0:0|drive_1:0|drive_2:0|drive_0:1',
2863
+ 'asset-forward animation trace should stay deterministic',
2864
+ );
2865
+ aura.test.assert(
2866
+ probe.frames.every((entry) => entry.frameResolved === true && entry.lastStepOk === true),
2867
+ 'asset-forward atlas stepping should remain resolved each frame',
2868
+ );
2869
+ aura.test.assert(
2870
+ probe.frames.every((entry) => entry.rayOk === true && entry.rayHit === true && entry.rayLayer === 1),
2871
+ 'asset-forward tilemap query pressure should remain deterministic',
2872
+ );
2873
+ aura.test.assert(
2874
+ probe.frames.every((entry) => entry.trackCount === 2 && entry.musicBusVolume === 0.55),
2875
+ 'asset-forward audio concurrency should keep two live tracks and the expected bus volume',
2876
+ );
2877
+ aura.test.assert(
2878
+ probe.frames[0].graphReason === 'draw2d_compositor_captured'
2879
+ && probe.frames[0].graphStageCount === 3
2880
+ && probe.frames[0].knownGraphCount === 1
2881
+ && probe.frames[0].lastGraphStageCount === 3
2882
+ && probe.frames[0].lastGraphReasonCode === 'draw2d_compositor_captured',
2883
+ 'asset-forward compositor capture should remain stable on the setup frame',
2884
+ );
2885
+ aura.test.assert(destroyPanel?.ok === true && destroyFx?.ok === true && destroyFinal?.ok === true, 'asset-forward render targets should destroy cleanly');
2886
+ aura.test.assert(missingDestroy?.ok === false && missingDestroy?.reasonCode === 'missing_render_target', 'asset-forward render target destroy should remain reason-coded');
2887
+ aura.test.equal(unloaded, true, 'asset-forward tilemap should unload cleanly');
2888
+ };
2889
+ `,
2890
+ nativeFrames: 4,
1775
2891
  }
1776
2892
  ];