@auraindustry/aurajs 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/asset-pack.mjs +2 -1
  3. package/src/authored-runtime.mjs +14 -0
  4. package/src/bin-integrity.mjs +33 -26
  5. package/src/conformance/cases/systems-and-gameplay-cases.mjs +861 -6
  6. package/src/external-package-surface.mjs +1 -1
  7. package/src/package-integrity.mjs +18 -4
  8. package/src/publish-command.mjs +133 -13
  9. package/src/publish-validation.mjs +22 -11
  10. package/src/scaffold/project-docs.mjs +58 -40
  11. package/templates/create/2d/src/runtime/app.js +4 -0
  12. package/templates/create/2d-survivor/src/runtime/app.js +4 -0
  13. package/templates/create/3d/src/runtime/app.js +4 -0
  14. package/templates/create/3d-collectathon/src/runtime/app.js +4 -0
  15. package/templates/create/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
  16. package/templates/create/blank/assets/splash/bg.webp +0 -0
  17. package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
  18. package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
  19. package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
  20. package/templates/create/blank/assets/splash/logoholo.webp +0 -0
  21. package/templates/create/blank/src/main.js +5 -1
  22. package/templates/create/blank/src/runtime/splash.js +305 -0
  23. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
  24. package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
  25. package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
  26. package/templates/create/shared/assets/splash/bg.webp +0 -0
  27. package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
  28. package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
  29. package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
  30. package/templates/create/shared/assets/splash/logoholo.webp +0 -0
  31. package/templates/create/shared/src/runtime/splash.js +305 -0
  32. package/templates/create/video-cutscene/src/runtime/app.js +4 -0
  33. package/templates/create-bin/play.js +114 -2
  34. package/templates/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
  35. package/templates/starter/assets/splash/bg.webp +0 -0
  36. package/templates/starter/assets/splash/boot-loop.wav +0 -0
  37. package/templates/starter/assets/splash/boot-sting.wav +0 -0
  38. package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
  39. package/templates/starter/assets/splash/logoholo.webp +0 -0
  40. package/templates/starter/src/main.js +4 -0
  41. package/templates/starter/src/runtime/splash.js +305 -0
@@ -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',