rn-gamekit 0.1.0 → 0.2.1
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.
- package/CHANGELOG.md +75 -0
- package/README.md +97 -16
- package/lib/module/assets/defineAssets.js +28 -3
- package/lib/module/assets/defineAssets.js.map +1 -1
- package/lib/module/assets.js +11 -0
- package/lib/module/assets.js.map +1 -0
- package/lib/module/audio/createGameAudio.js +784 -0
- package/lib/module/audio/createGameAudio.js.map +1 -0
- package/lib/module/audio/errors.js +12 -0
- package/lib/module/audio/errors.js.map +1 -0
- package/lib/module/audio/index.js +5 -0
- package/lib/module/audio/index.js.map +1 -0
- package/lib/module/audio/resolver.js +37 -0
- package/lib/module/audio/resolver.js.map +1 -0
- package/lib/module/audio/types.js +4 -0
- package/lib/module/audio/types.js.map +1 -0
- package/lib/module/audio.js +9 -0
- package/lib/module/audio.js.map +1 -0
- package/lib/module/camera2d/bounds.js +59 -0
- package/lib/module/camera2d/bounds.js.map +1 -0
- package/lib/module/camera2d/extents.js +25 -0
- package/lib/module/camera2d/extents.js.map +1 -0
- package/lib/module/camera2d/follow.js +138 -0
- package/lib/module/camera2d/follow.js.map +1 -0
- package/lib/module/camera2d/index.js +20 -0
- package/lib/module/camera2d/index.js.map +1 -0
- package/lib/module/camera2d/interpolation.js +98 -0
- package/lib/module/camera2d/interpolation.js.map +1 -0
- package/lib/module/camera2d/shake.js +85 -0
- package/lib/module/camera2d/shake.js.map +1 -0
- package/lib/module/camera2d/transform.js +151 -0
- package/lib/module/camera2d/transform.js.map +1 -0
- package/lib/module/camera2d/types.js +4 -0
- package/lib/module/camera2d/types.js.map +1 -0
- package/lib/module/camera2d/validation.js +98 -0
- package/lib/module/camera2d/validation.js.map +1 -0
- package/lib/module/camera2d/visibility.js +138 -0
- package/lib/module/camera2d/visibility.js.map +1 -0
- package/lib/module/camera2d.js +9 -0
- package/lib/module/camera2d.js.map +1 -0
- package/lib/module/collision2d/colliders.js +266 -0
- package/lib/module/collision2d/colliders.js.map +1 -0
- package/lib/module/collision2d/debug.js +105 -0
- package/lib/module/collision2d/debug.js.map +1 -0
- package/lib/module/collision2d/filters.js +40 -0
- package/lib/module/collision2d/filters.js.map +1 -0
- package/lib/module/collision2d/index.js +12 -0
- package/lib/module/collision2d/index.js.map +1 -0
- package/lib/module/collision2d/intersections.js +67 -0
- package/lib/module/collision2d/intersections.js.map +1 -0
- package/lib/module/collision2d/manifolds.js +218 -0
- package/lib/module/collision2d/manifolds.js.map +1 -0
- package/lib/module/collision2d/segments.js +187 -0
- package/lib/module/collision2d/segments.js.map +1 -0
- package/lib/module/collision2d/spatialHash.js +167 -0
- package/lib/module/collision2d/spatialHash.js.map +1 -0
- package/lib/module/collision2d/sweeps.js +255 -0
- package/lib/module/collision2d/sweeps.js.map +1 -0
- package/lib/module/collision2d.js +9 -0
- package/lib/module/collision2d.js.map +1 -0
- package/lib/module/core/input/buttonPad.js +214 -0
- package/lib/module/core/input/buttonPad.js.map +1 -0
- package/lib/module/core/input/createInputBuffer.js +66 -14
- package/lib/module/core/input/createInputBuffer.js.map +1 -1
- package/lib/module/core/session/createGameSession.js +454 -50
- package/lib/module/core/session/createGameSession.js.map +1 -1
- package/lib/module/core/session/deepFreeze.js +92 -2
- package/lib/module/core/session/deepFreeze.js.map +1 -1
- package/lib/module/core/session/types.js +12 -0
- package/lib/module/core/session/types.js.map +1 -1
- package/lib/module/definition/defineGame.js +23 -0
- package/lib/module/definition/defineGame.js.map +1 -1
- package/lib/module/events/defineGameEvents.js +46 -0
- package/lib/module/events/defineGameEvents.js.map +1 -0
- package/lib/module/events/errors.js +16 -0
- package/lib/module/events/errors.js.map +1 -0
- package/lib/module/events/index.js +7 -0
- package/lib/module/events/index.js.map +1 -0
- package/lib/module/events/payload.js +219 -0
- package/lib/module/events/payload.js.map +1 -0
- package/lib/module/events/seed.js +33 -0
- package/lib/module/events/seed.js.map +1 -0
- package/lib/module/events/types.js +4 -0
- package/lib/module/events/types.js.map +1 -0
- package/lib/module/events.js +13 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/geometry/helpers.js +140 -0
- package/lib/module/geometry/helpers.js.map +1 -0
- package/lib/module/geometry/index.js +5 -0
- package/lib/module/geometry/index.js.map +1 -0
- package/lib/module/geometry/validation.js +104 -0
- package/lib/module/geometry/validation.js.map +1 -0
- package/lib/module/geometry.js +9 -0
- package/lib/module/geometry.js.map +1 -0
- package/lib/module/haptics/createGameHaptics.js +214 -0
- package/lib/module/haptics/createGameHaptics.js.map +1 -0
- package/lib/module/haptics/errors.js +12 -0
- package/lib/module/haptics/errors.js.map +1 -0
- package/lib/module/haptics/index.js +5 -0
- package/lib/module/haptics/index.js.map +1 -0
- package/lib/module/haptics/resolver.js +37 -0
- package/lib/module/haptics/resolver.js.map +1 -0
- package/lib/module/haptics/types.js +4 -0
- package/lib/module/haptics/types.js.map +1 -0
- package/lib/module/haptics.js +9 -0
- package/lib/module/haptics.js.map +1 -0
- package/lib/module/index.js +8 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/particles/createParticleSystem.js +481 -0
- package/lib/module/particles/createParticleSystem.js.map +1 -0
- package/lib/module/particles/defineParticleEffect.js +178 -0
- package/lib/module/particles/defineParticleEffect.js.map +1 -0
- package/lib/module/particles/errors.js +9 -0
- package/lib/module/particles/errors.js.map +1 -0
- package/lib/module/particles/index.js +8 -0
- package/lib/module/particles/index.js.map +1 -0
- package/lib/module/particles/sampling.js +121 -0
- package/lib/module/particles/sampling.js.map +1 -0
- package/lib/module/particles/types.js +4 -0
- package/lib/module/particles/types.js.map +1 -0
- package/lib/module/particles.js +4 -0
- package/lib/module/particles.js.map +1 -0
- package/lib/module/react/GameButtonPad.js +344 -0
- package/lib/module/react/GameButtonPad.js.map +1 -0
- package/lib/module/react/GamePointerInput.js +74 -16
- package/lib/module/react/GamePointerInput.js.map +1 -1
- package/lib/module/react/GameView.js +139 -60
- package/lib/module/react/GameView.js.map +1 -1
- package/lib/module/react/assets/createGameAssetStore.js +145 -95
- package/lib/module/react/assets/createGameAssetStore.js.map +1 -1
- package/lib/module/react/assets/useGameAssets.js +60 -35
- package/lib/module/react/assets/useGameAssets.js.map +1 -1
- package/lib/module/react/bindAppLifecycle.js +46 -17
- package/lib/module/react/bindAppLifecycle.js.map +1 -1
- package/lib/module/react/bindGameSession.js +13 -2
- package/lib/module/react/bindGameSession.js.map +1 -1
- package/lib/module/react/camera2d/GameLayer2D.js +50 -0
- package/lib/module/react/camera2d/GameLayer2D.js.map +1 -0
- package/lib/module/react/camera2d/defineGameCamera2D.js +30 -0
- package/lib/module/react/camera2d/defineGameCamera2D.js.map +1 -0
- package/lib/module/react/camera2d/usePresentedCameraBinding.js +177 -0
- package/lib/module/react/camera2d/usePresentedCameraBinding.js.map +1 -0
- package/lib/module/react/cleanupList.js +38 -0
- package/lib/module/react/cleanupList.js.map +1 -0
- package/lib/module/react/lifecycleSource.js +49 -0
- package/lib/module/react/lifecycleSource.js.map +1 -0
- package/lib/module/react/particles/ParticleView.js +308 -0
- package/lib/module/react/particles/ParticleView.js.map +1 -0
- package/lib/module/react/particles/culling.js +115 -0
- package/lib/module/react/particles/culling.js.map +1 -0
- package/lib/module/react/particles/index.js +7 -0
- package/lib/module/react/particles/index.js.map +1 -0
- package/lib/module/react/particles/spriteXForm.js +45 -0
- package/lib/module/react/particles/spriteXForm.js.map +1 -0
- package/lib/module/react/particles/useParticlePresentation.js +204 -0
- package/lib/module/react/particles/useParticlePresentation.js.map +1 -0
- package/lib/module/react/pointerBinding.js +55 -17
- package/lib/module/react/pointerBinding.js.map +1 -1
- package/lib/module/react/pointerCamera.js +28 -0
- package/lib/module/react/pointerCamera.js.map +1 -0
- package/lib/module/react/pointerCoalescer.js +16 -9
- package/lib/module/react/pointerCoalescer.js.map +1 -1
- package/lib/module/react/sprites/GameSprite.js +5 -17
- package/lib/module/react/sprites/GameSprite.js.map +1 -1
- package/lib/module/react/sprites/GameWorld2D.js +112 -9
- package/lib/module/react/sprites/GameWorld2D.js.map +1 -1
- package/lib/module/react/sprites/Sprite.js +48 -63
- package/lib/module/react/sprites/Sprite.js.map +1 -1
- package/lib/module/react/sprites/SpriteBatch.js +53 -28
- package/lib/module/react/sprites/SpriteBatch.js.map +1 -1
- package/lib/module/react/sprites/batchVisibility.js +58 -0
- package/lib/module/react/sprites/batchVisibility.js.map +1 -0
- package/lib/module/react/sprites/spriteSelection.js +103 -0
- package/lib/module/react/sprites/spriteSelection.js.map +1 -0
- package/lib/module/react/sprites/spriteTransform.js +46 -25
- package/lib/module/react/sprites/spriteTransform.js.map +1 -1
- package/lib/module/react/tilemap/TileMapLayer2D.js +217 -0
- package/lib/module/react/tilemap/TileMapLayer2D.js.map +1 -0
- package/lib/module/react/tilemap/tilePresentation.js +374 -0
- package/lib/module/react/tilemap/tilePresentation.js.map +1 -0
- package/lib/module/react/useGameSession.js +70 -0
- package/lib/module/react/useGameSession.js.map +1 -0
- package/lib/module/react/useGameSessionStatus.js +46 -0
- package/lib/module/react/useGameSessionStatus.js.map +1 -0
- package/lib/module/react/viewportBinding.js +5 -1
- package/lib/module/react/viewportBinding.js.map +1 -1
- package/lib/module/react.js +12 -1
- package/lib/module/react.js.map +1 -1
- package/lib/module/scene/defineScene.js +17 -0
- package/lib/module/scene/defineScene.js.map +1 -1
- package/lib/module/sprites/sampleSpriteClip.js +66 -6
- package/lib/module/sprites/sampleSpriteClip.js.map +1 -1
- package/lib/module/sprites/spriteAnimationState.js +10 -20
- package/lib/module/sprites/spriteAnimationState.js.map +1 -1
- package/lib/module/sprites.js +11 -0
- package/lib/module/sprites.js.map +1 -0
- package/lib/module/storage/adapters/asyncStorage.js +57 -0
- package/lib/module/storage/adapters/asyncStorage.js.map +1 -0
- package/lib/module/storage/adapters/memory.js +50 -0
- package/lib/module/storage/adapters/memory.js.map +1 -0
- package/lib/module/storage/errors.js +34 -0
- package/lib/module/storage/errors.js.map +1 -0
- package/lib/module/storage/index.js +11 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/storage/schema.js +151 -0
- package/lib/module/storage/schema.js.map +1 -0
- package/lib/module/storage/serialization.js +249 -0
- package/lib/module/storage/serialization.js.map +1 -0
- package/lib/module/storage/store.js +387 -0
- package/lib/module/storage/store.js.map +1 -0
- package/lib/module/storage/types.js +16 -0
- package/lib/module/storage/types.js.map +1 -0
- package/lib/module/storage/validation.js +111 -0
- package/lib/module/storage/validation.js.map +1 -0
- package/lib/module/storage.js +4 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/testing.js +4 -0
- package/lib/module/testing.js.map +1 -1
- package/lib/module/tilemap/chunkStats.js +27 -0
- package/lib/module/tilemap/chunkStats.js.map +1 -0
- package/lib/module/tilemap/definitions.js +308 -0
- package/lib/module/tilemap/definitions.js.map +1 -0
- package/lib/module/tilemap/errors.js +13 -0
- package/lib/module/tilemap/errors.js.map +1 -0
- package/lib/module/tilemap/index.js +10 -0
- package/lib/module/tilemap/index.js.map +1 -0
- package/lib/module/tilemap/movement.js +379 -0
- package/lib/module/tilemap/movement.js.map +1 -0
- package/lib/module/tilemap/queries.js +114 -0
- package/lib/module/tilemap/queries.js.map +1 -0
- package/lib/module/tilemap/tiledAdapter.js +141 -0
- package/lib/module/tilemap/tiledAdapter.js.map +1 -0
- package/lib/module/tilemap/types.js +4 -0
- package/lib/module/tilemap/types.js.map +1 -0
- package/lib/module/tilemap.js +4 -0
- package/lib/module/tilemap.js.map +1 -0
- package/lib/typescript/src/assets/defineAssets.d.ts +1 -1
- package/lib/typescript/src/assets/defineAssets.d.ts.map +1 -1
- package/lib/typescript/src/assets.d.ts +11 -0
- package/lib/typescript/src/assets.d.ts.map +1 -0
- package/lib/typescript/src/audio/createGameAudio.d.ts +5 -0
- package/lib/typescript/src/audio/createGameAudio.d.ts.map +1 -0
- package/lib/typescript/src/audio/errors.d.ts +6 -0
- package/lib/typescript/src/audio/errors.d.ts.map +1 -0
- package/lib/typescript/src/audio/index.d.ts +4 -0
- package/lib/typescript/src/audio/index.d.ts.map +1 -0
- package/lib/typescript/src/audio/resolver.d.ts +44 -0
- package/lib/typescript/src/audio/resolver.d.ts.map +1 -0
- package/lib/typescript/src/audio/types.d.ts +42 -0
- package/lib/typescript/src/audio/types.d.ts.map +1 -0
- package/lib/typescript/src/audio.d.ts +8 -0
- package/lib/typescript/src/audio.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/bounds.d.ts +25 -0
- package/lib/typescript/src/camera2d/bounds.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/extents.d.ts +17 -0
- package/lib/typescript/src/camera2d/extents.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/follow.d.ts +24 -0
- package/lib/typescript/src/camera2d/follow.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/index.d.ts +19 -0
- package/lib/typescript/src/camera2d/index.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/interpolation.d.ts +17 -0
- package/lib/typescript/src/camera2d/interpolation.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/shake.d.ts +20 -0
- package/lib/typescript/src/camera2d/shake.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/transform.d.ts +44 -0
- package/lib/typescript/src/camera2d/transform.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/types.d.ts +74 -0
- package/lib/typescript/src/camera2d/types.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/validation.d.ts +30 -0
- package/lib/typescript/src/camera2d/validation.d.ts.map +1 -0
- package/lib/typescript/src/camera2d/visibility.d.ts +47 -0
- package/lib/typescript/src/camera2d/visibility.d.ts.map +1 -0
- package/lib/typescript/src/camera2d.d.ts +7 -0
- package/lib/typescript/src/camera2d.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/colliders.d.ts +120 -0
- package/lib/typescript/src/collision2d/colliders.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/debug.d.ts +67 -0
- package/lib/typescript/src/collision2d/debug.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/filters.d.ts +28 -0
- package/lib/typescript/src/collision2d/filters.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/index.d.ts +17 -0
- package/lib/typescript/src/collision2d/index.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/intersections.d.ts +24 -0
- package/lib/typescript/src/collision2d/intersections.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/manifolds.d.ts +39 -0
- package/lib/typescript/src/collision2d/manifolds.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/segments.d.ts +24 -0
- package/lib/typescript/src/collision2d/segments.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/spatialHash.d.ts +50 -0
- package/lib/typescript/src/collision2d/spatialHash.d.ts.map +1 -0
- package/lib/typescript/src/collision2d/sweeps.d.ts +60 -0
- package/lib/typescript/src/collision2d/sweeps.d.ts.map +1 -0
- package/lib/typescript/src/collision2d.d.ts +7 -0
- package/lib/typescript/src/collision2d.d.ts.map +1 -0
- package/lib/typescript/src/core/input/buttonPad.d.ts +59 -0
- package/lib/typescript/src/core/input/buttonPad.d.ts.map +1 -0
- package/lib/typescript/src/core/input/createInputBuffer.d.ts +1 -1
- package/lib/typescript/src/core/input/createInputBuffer.d.ts.map +1 -1
- package/lib/typescript/src/core/input/types.d.ts +7 -0
- package/lib/typescript/src/core/input/types.d.ts.map +1 -1
- package/lib/typescript/src/core/session/createGameSession.d.ts +4 -2
- package/lib/typescript/src/core/session/createGameSession.d.ts.map +1 -1
- package/lib/typescript/src/core/session/deepFreeze.d.ts +21 -1
- package/lib/typescript/src/core/session/deepFreeze.d.ts.map +1 -1
- package/lib/typescript/src/core/session/diagnostics.d.ts +2 -0
- package/lib/typescript/src/core/session/diagnostics.d.ts.map +1 -1
- package/lib/typescript/src/core/session/types.d.ts +69 -4
- package/lib/typescript/src/core/session/types.d.ts.map +1 -1
- package/lib/typescript/src/definition/defineGame.d.ts +12 -1
- package/lib/typescript/src/definition/defineGame.d.ts.map +1 -1
- package/lib/typescript/src/definition/types.d.ts +10 -1
- package/lib/typescript/src/definition/types.d.ts.map +1 -1
- package/lib/typescript/src/events/defineGameEvents.d.ts +26 -0
- package/lib/typescript/src/events/defineGameEvents.d.ts.map +1 -0
- package/lib/typescript/src/events/errors.d.ts +12 -0
- package/lib/typescript/src/events/errors.d.ts.map +1 -0
- package/lib/typescript/src/events/index.d.ts +6 -0
- package/lib/typescript/src/events/index.d.ts.map +1 -0
- package/lib/typescript/src/events/payload.d.ts +21 -0
- package/lib/typescript/src/events/payload.d.ts.map +1 -0
- package/lib/typescript/src/events/seed.d.ts +3 -0
- package/lib/typescript/src/events/seed.d.ts.map +1 -0
- package/lib/typescript/src/events/types.d.ts +42 -0
- package/lib/typescript/src/events/types.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +12 -0
- package/lib/typescript/src/events.d.ts.map +1 -0
- package/lib/typescript/src/geometry/helpers.d.ts +35 -0
- package/lib/typescript/src/geometry/helpers.d.ts.map +1 -0
- package/lib/typescript/src/geometry/index.d.ts +4 -0
- package/lib/typescript/src/geometry/index.d.ts.map +1 -0
- package/lib/typescript/src/geometry/types.d.ts +49 -0
- package/lib/typescript/src/geometry/types.d.ts.map +1 -1
- package/lib/typescript/src/geometry/validation.d.ts +44 -0
- package/lib/typescript/src/geometry/validation.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +7 -0
- package/lib/typescript/src/geometry.d.ts.map +1 -0
- package/lib/typescript/src/haptics/createGameHaptics.d.ts +3 -0
- package/lib/typescript/src/haptics/createGameHaptics.d.ts.map +1 -0
- package/lib/typescript/src/haptics/errors.d.ts +6 -0
- package/lib/typescript/src/haptics/errors.d.ts.map +1 -0
- package/lib/typescript/src/haptics/index.d.ts +4 -0
- package/lib/typescript/src/haptics/index.d.ts.map +1 -0
- package/lib/typescript/src/haptics/resolver.d.ts +22 -0
- package/lib/typescript/src/haptics/resolver.d.ts.map +1 -0
- package/lib/typescript/src/haptics/types.d.ts +37 -0
- package/lib/typescript/src/haptics/types.d.ts.map +1 -0
- package/lib/typescript/src/haptics.d.ts +8 -0
- package/lib/typescript/src/haptics.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/particles/createParticleSystem.d.ts +5 -0
- package/lib/typescript/src/particles/createParticleSystem.d.ts.map +1 -0
- package/lib/typescript/src/particles/defineParticleEffect.d.ts +3 -0
- package/lib/typescript/src/particles/defineParticleEffect.d.ts.map +1 -0
- package/lib/typescript/src/particles/errors.d.ts +5 -0
- package/lib/typescript/src/particles/errors.d.ts.map +1 -0
- package/lib/typescript/src/particles/index.d.ts +6 -0
- package/lib/typescript/src/particles/index.d.ts.map +1 -0
- package/lib/typescript/src/particles/sampling.d.ts +54 -0
- package/lib/typescript/src/particles/sampling.d.ts.map +1 -0
- package/lib/typescript/src/particles/types.d.ts +292 -0
- package/lib/typescript/src/particles/types.d.ts.map +1 -0
- package/lib/typescript/src/particles.d.ts +2 -0
- package/lib/typescript/src/particles.d.ts.map +1 -0
- package/lib/typescript/src/react/GameButtonPad.d.ts +84 -0
- package/lib/typescript/src/react/GameButtonPad.d.ts.map +1 -0
- package/lib/typescript/src/react/GamePointerInput.d.ts.map +1 -1
- package/lib/typescript/src/react/GameView.d.ts +27 -1
- package/lib/typescript/src/react/GameView.d.ts.map +1 -1
- package/lib/typescript/src/react/assets/createGameAssetStore.d.ts.map +1 -1
- package/lib/typescript/src/react/assets/useGameAssets.d.ts.map +1 -1
- package/lib/typescript/src/react/bindAppLifecycle.d.ts +7 -1
- package/lib/typescript/src/react/bindAppLifecycle.d.ts.map +1 -1
- package/lib/typescript/src/react/bindGameSession.d.ts.map +1 -1
- package/lib/typescript/src/react/camera2d/GameLayer2D.d.ts +24 -0
- package/lib/typescript/src/react/camera2d/GameLayer2D.d.ts.map +1 -0
- package/lib/typescript/src/react/camera2d/defineGameCamera2D.d.ts +44 -0
- package/lib/typescript/src/react/camera2d/defineGameCamera2D.d.ts.map +1 -0
- package/lib/typescript/src/react/camera2d/usePresentedCameraBinding.d.ts +22 -0
- package/lib/typescript/src/react/camera2d/usePresentedCameraBinding.d.ts.map +1 -0
- package/lib/typescript/src/react/cleanupList.d.ts +21 -0
- package/lib/typescript/src/react/cleanupList.d.ts.map +1 -0
- package/lib/typescript/src/react/instrumentation.d.ts +33 -3
- package/lib/typescript/src/react/instrumentation.d.ts.map +1 -1
- package/lib/typescript/src/react/lifecycleSource.d.ts +21 -0
- package/lib/typescript/src/react/lifecycleSource.d.ts.map +1 -0
- package/lib/typescript/src/react/particles/ParticleView.d.ts +65 -0
- package/lib/typescript/src/react/particles/ParticleView.d.ts.map +1 -0
- package/lib/typescript/src/react/particles/culling.d.ts +70 -0
- package/lib/typescript/src/react/particles/culling.d.ts.map +1 -0
- package/lib/typescript/src/react/particles/index.d.ts +7 -0
- package/lib/typescript/src/react/particles/index.d.ts.map +1 -0
- package/lib/typescript/src/react/particles/spriteXForm.d.ts +28 -0
- package/lib/typescript/src/react/particles/spriteXForm.d.ts.map +1 -0
- package/lib/typescript/src/react/particles/useParticlePresentation.d.ts +48 -0
- package/lib/typescript/src/react/particles/useParticlePresentation.d.ts.map +1 -0
- package/lib/typescript/src/react/pointerBinding.d.ts +38 -8
- package/lib/typescript/src/react/pointerBinding.d.ts.map +1 -1
- package/lib/typescript/src/react/pointerCamera.d.ts +20 -0
- package/lib/typescript/src/react/pointerCamera.d.ts.map +1 -0
- package/lib/typescript/src/react/pointerCoalescer.d.ts +17 -1
- package/lib/typescript/src/react/pointerCoalescer.d.ts.map +1 -1
- package/lib/typescript/src/react/sprites/GameSprite.d.ts +3 -10
- package/lib/typescript/src/react/sprites/GameSprite.d.ts.map +1 -1
- package/lib/typescript/src/react/sprites/GameWorld2D.d.ts +51 -6
- package/lib/typescript/src/react/sprites/GameWorld2D.d.ts.map +1 -1
- package/lib/typescript/src/react/sprites/Sprite.d.ts.map +1 -1
- package/lib/typescript/src/react/sprites/SpriteBatch.d.ts +22 -1
- package/lib/typescript/src/react/sprites/SpriteBatch.d.ts.map +1 -1
- package/lib/typescript/src/react/sprites/batchVisibility.d.ts +28 -0
- package/lib/typescript/src/react/sprites/batchVisibility.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/spriteSelection.d.ts +58 -0
- package/lib/typescript/src/react/sprites/spriteSelection.d.ts.map +1 -0
- package/lib/typescript/src/react/sprites/spriteTransform.d.ts +27 -12
- package/lib/typescript/src/react/sprites/spriteTransform.d.ts.map +1 -1
- package/lib/typescript/src/react/tilemap/TileMapLayer2D.d.ts +69 -0
- package/lib/typescript/src/react/tilemap/TileMapLayer2D.d.ts.map +1 -0
- package/lib/typescript/src/react/tilemap/tilePresentation.d.ts +217 -0
- package/lib/typescript/src/react/tilemap/tilePresentation.d.ts.map +1 -0
- package/lib/typescript/src/react/useGameSession.d.ts +48 -0
- package/lib/typescript/src/react/useGameSession.d.ts.map +1 -0
- package/lib/typescript/src/react/useGameSessionStatus.d.ts +23 -0
- package/lib/typescript/src/react/useGameSessionStatus.d.ts.map +1 -0
- package/lib/typescript/src/react/viewportBinding.d.ts +5 -1
- package/lib/typescript/src/react/viewportBinding.d.ts.map +1 -1
- package/lib/typescript/src/react.d.ts +18 -1
- package/lib/typescript/src/react.d.ts.map +1 -1
- package/lib/typescript/src/scene/defineScene.d.ts +22 -2
- package/lib/typescript/src/scene/defineScene.d.ts.map +1 -1
- package/lib/typescript/src/scene/types.d.ts +24 -4
- package/lib/typescript/src/scene/types.d.ts.map +1 -1
- package/lib/typescript/src/sprites/sampleSpriteClip.d.ts +26 -2
- package/lib/typescript/src/sprites/sampleSpriteClip.d.ts.map +1 -1
- package/lib/typescript/src/sprites/spriteAnimationState.d.ts +6 -7
- package/lib/typescript/src/sprites/spriteAnimationState.d.ts.map +1 -1
- package/lib/typescript/src/sprites.d.ts +10 -0
- package/lib/typescript/src/sprites.d.ts.map +1 -0
- package/lib/typescript/src/storage/adapters/asyncStorage.d.ts +18 -0
- package/lib/typescript/src/storage/adapters/asyncStorage.d.ts.map +1 -0
- package/lib/typescript/src/storage/adapters/memory.d.ts +16 -0
- package/lib/typescript/src/storage/adapters/memory.d.ts.map +1 -0
- package/lib/typescript/src/storage/errors.d.ts +37 -0
- package/lib/typescript/src/storage/errors.d.ts.map +1 -0
- package/lib/typescript/src/storage/index.d.ts +11 -0
- package/lib/typescript/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/src/storage/schema.d.ts +22 -0
- package/lib/typescript/src/storage/schema.d.ts.map +1 -0
- package/lib/typescript/src/storage/serialization.d.ts +23 -0
- package/lib/typescript/src/storage/serialization.d.ts.map +1 -0
- package/lib/typescript/src/storage/store.d.ts +3 -0
- package/lib/typescript/src/storage/store.d.ts.map +1 -0
- package/lib/typescript/src/storage/types.d.ts +69 -0
- package/lib/typescript/src/storage/types.d.ts.map +1 -0
- package/lib/typescript/src/storage/validation.d.ts +24 -0
- package/lib/typescript/src/storage/validation.d.ts.map +1 -0
- package/lib/typescript/src/storage.d.ts +2 -0
- package/lib/typescript/src/storage.d.ts.map +1 -0
- package/lib/typescript/src/testing.d.ts +1 -0
- package/lib/typescript/src/testing.d.ts.map +1 -1
- package/lib/typescript/src/tilemap/chunkStats.d.ts +14 -0
- package/lib/typescript/src/tilemap/chunkStats.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/definitions.d.ts +38 -0
- package/lib/typescript/src/tilemap/definitions.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/errors.d.ts +7 -0
- package/lib/typescript/src/tilemap/errors.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/index.d.ts +8 -0
- package/lib/typescript/src/tilemap/index.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/movement.d.ts +52 -0
- package/lib/typescript/src/tilemap/movement.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/queries.d.ts +21 -0
- package/lib/typescript/src/tilemap/queries.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/tiledAdapter.d.ts +32 -0
- package/lib/typescript/src/tilemap/tiledAdapter.d.ts.map +1 -0
- package/lib/typescript/src/tilemap/types.d.ts +127 -0
- package/lib/typescript/src/tilemap/types.d.ts.map +1 -0
- package/lib/typescript/src/tilemap.d.ts +2 -0
- package/lib/typescript/src/tilemap.d.ts.map +1 -0
- package/package.json +100 -10
- package/src/assets/defineAssets.ts +31 -5
- package/src/assets.ts +27 -0
- package/src/audio/createGameAudio.ts +749 -0
- package/src/audio/errors.ts +12 -0
- package/src/audio/index.ts +9 -0
- package/src/audio/resolver.ts +74 -0
- package/src/audio/types.ts +47 -0
- package/src/audio.ts +13 -0
- package/src/camera2d/bounds.ts +69 -0
- package/src/camera2d/extents.ts +25 -0
- package/src/camera2d/follow.ts +165 -0
- package/src/camera2d/index.ts +29 -0
- package/src/camera2d/interpolation.ts +113 -0
- package/src/camera2d/shake.ts +90 -0
- package/src/camera2d/transform.ts +148 -0
- package/src/camera2d/types.ts +73 -0
- package/src/camera2d/validation.ts +126 -0
- package/src/camera2d/visibility.ts +181 -0
- package/src/camera2d.ts +6 -0
- package/src/collision2d/colliders.ts +292 -0
- package/src/collision2d/debug.ts +133 -0
- package/src/collision2d/filters.ts +48 -0
- package/src/collision2d/index.ts +74 -0
- package/src/collision2d/intersections.ts +82 -0
- package/src/collision2d/manifolds.ts +191 -0
- package/src/collision2d/segments.ts +176 -0
- package/src/collision2d/spatialHash.ts +211 -0
- package/src/collision2d/sweeps.ts +269 -0
- package/src/collision2d.ts +6 -0
- package/src/core/input/buttonPad.ts +235 -0
- package/src/core/input/createInputBuffer.ts +95 -14
- package/src/core/input/types.ts +7 -0
- package/src/core/session/createGameSession.ts +480 -53
- package/src/core/session/deepFreeze.ts +104 -3
- package/src/core/session/diagnostics.ts +2 -0
- package/src/core/session/types.ts +77 -4
- package/src/definition/defineGame.ts +61 -3
- package/src/definition/types.ts +10 -0
- package/src/events/defineGameEvents.ts +46 -0
- package/src/events/errors.ts +14 -0
- package/src/events/index.ts +14 -0
- package/src/events/payload.ts +234 -0
- package/src/events/seed.ts +33 -0
- package/src/events/types.ts +53 -0
- package/src/events.ts +20 -0
- package/src/geometry/helpers.ts +136 -0
- package/src/geometry/index.ts +14 -0
- package/src/geometry/types.ts +54 -0
- package/src/geometry/validation.ts +131 -0
- package/src/geometry.ts +6 -0
- package/src/haptics/createGameHaptics.ts +214 -0
- package/src/haptics/errors.ts +12 -0
- package/src/haptics/index.ts +3 -0
- package/src/haptics/resolver.ts +58 -0
- package/src/haptics/types.ts +48 -0
- package/src/haptics.ts +7 -0
- package/src/index.ts +37 -1
- package/src/particles/createParticleSystem.ts +505 -0
- package/src/particles/defineParticleEffect.ts +164 -0
- package/src/particles/errors.ts +6 -0
- package/src/particles/index.ts +5 -0
- package/src/particles/sampling.ts +141 -0
- package/src/particles/types.ts +327 -0
- package/src/particles.ts +1 -0
- package/src/react/GameButtonPad.tsx +436 -0
- package/src/react/GamePointerInput.tsx +73 -8
- package/src/react/GameView.tsx +160 -61
- package/src/react/assets/createGameAssetStore.ts +140 -107
- package/src/react/assets/useGameAssets.ts +63 -31
- package/src/react/bindAppLifecycle.ts +55 -19
- package/src/react/bindGameSession.ts +12 -2
- package/src/react/camera2d/GameLayer2D.tsx +43 -0
- package/src/react/camera2d/defineGameCamera2D.ts +59 -0
- package/src/react/camera2d/usePresentedCameraBinding.ts +185 -0
- package/src/react/cleanupList.ts +48 -0
- package/src/react/instrumentation.ts +31 -3
- package/src/react/lifecycleSource.ts +58 -0
- package/src/react/particles/ParticleView.tsx +358 -0
- package/src/react/particles/culling.ts +145 -0
- package/src/react/particles/index.ts +6 -0
- package/src/react/particles/spriteXForm.ts +72 -0
- package/src/react/particles/useParticlePresentation.ts +261 -0
- package/src/react/pointerBinding.ts +74 -16
- package/src/react/pointerCamera.ts +33 -0
- package/src/react/pointerCoalescer.ts +36 -10
- package/src/react/sprites/GameSprite.tsx +4 -33
- package/src/react/sprites/GameWorld2D.tsx +113 -7
- package/src/react/sprites/Sprite.tsx +51 -72
- package/src/react/sprites/SpriteBatch.tsx +74 -22
- package/src/react/sprites/batchVisibility.ts +76 -0
- package/src/react/sprites/spriteSelection.ts +133 -0
- package/src/react/sprites/spriteTransform.ts +46 -26
- package/src/react/tilemap/TileMapLayer2D.tsx +274 -0
- package/src/react/tilemap/tilePresentation.ts +491 -0
- package/src/react/useGameSession.ts +90 -0
- package/src/react/useGameSessionStatus.ts +53 -0
- package/src/react/viewportBinding.ts +5 -1
- package/src/react.ts +18 -1
- package/src/scene/defineScene.ts +47 -5
- package/src/scene/types.ts +37 -3
- package/src/sprites/sampleSpriteClip.ts +82 -14
- package/src/sprites/spriteAnimationState.ts +29 -25
- package/src/sprites.ts +22 -0
- package/src/storage/adapters/asyncStorage.ts +77 -0
- package/src/storage/adapters/memory.ts +58 -0
- package/src/storage/errors.ts +88 -0
- package/src/storage/index.ts +18 -0
- package/src/storage/schema.ts +147 -0
- package/src/storage/serialization.ts +248 -0
- package/src/storage/store.ts +380 -0
- package/src/storage/types.ts +76 -0
- package/src/storage/validation.ts +102 -0
- package/src/storage.ts +1 -0
- package/src/testing.ts +4 -0
- package/src/tilemap/chunkStats.ts +24 -0
- package/src/tilemap/definitions.ts +327 -0
- package/src/tilemap/errors.ts +11 -0
- package/src/tilemap/index.ts +10 -0
- package/src/tilemap/movement.ts +368 -0
- package/src/tilemap/queries.ts +136 -0
- package/src/tilemap/tiledAdapter.ts +165 -0
- package/src/tilemap/types.ts +119 -0
- package/src/tilemap.ts +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssetGroupMap } from '../../assets/types';
|
|
1
2
|
import type { GameDefinition, InputMap, SceneMap } from '../../definition/types';
|
|
2
3
|
import type { SceneTransitionController } from '../../scene/types';
|
|
3
4
|
import { createAnimationFrameDriver, type FrameDriver, type FrameHandle } from '../frameDriver';
|
|
@@ -14,8 +15,54 @@ import {
|
|
|
14
15
|
} from './types';
|
|
15
16
|
import type { SessionDiagnostics } from './diagnostics';
|
|
16
17
|
import { createDeepFreeze, type DeepFreezer } from './deepFreeze';
|
|
18
|
+
import type { GameEventDescriptor } from '../../events/types';
|
|
19
|
+
import type { AnyGameEventEnvelope } from '../../events/types';
|
|
20
|
+
import { GameEventError } from '../../events/errors';
|
|
21
|
+
import { cloneAndValidatePayload } from '../../events/payload';
|
|
17
22
|
|
|
18
23
|
const DEFAULT_FIXED_STEP_MS = 1000 / 60;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A captured failure: an explicit boolean record so an arbitrary thrown
|
|
27
|
+
* value (including `undefined`) is never confused with "no failure".
|
|
28
|
+
*/
|
|
29
|
+
interface CapturedFailure {
|
|
30
|
+
readonly failed: true;
|
|
31
|
+
readonly value: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type FailureRecord = { readonly failed: false } | CapturedFailure;
|
|
35
|
+
|
|
36
|
+
const NO_FAILURE: FailureRecord = { failed: false };
|
|
37
|
+
|
|
38
|
+
/** Run a closure and capture its thrown value, whatever its type. */
|
|
39
|
+
function captureFailure(run: () => void): FailureRecord {
|
|
40
|
+
try {
|
|
41
|
+
run();
|
|
42
|
+
return NO_FAILURE;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return { failed: true, value: error };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Surface a captured failure, throwing `undefined` if that was the value. */
|
|
49
|
+
function throwFailure(record: FailureRecord): void {
|
|
50
|
+
if (record.failed) {
|
|
51
|
+
throw record.value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Immutably compose two failures into one lossless error: an AggregateError
|
|
57
|
+
* with a stable message and ordered `errors`. Neither thrown value is
|
|
58
|
+
* mutated or wrapped away.
|
|
59
|
+
*/
|
|
60
|
+
function composeFailures(first: unknown, second: unknown): unknown {
|
|
61
|
+
return new AggregateError(
|
|
62
|
+
[first, second],
|
|
63
|
+
'Lifecycle operation failed with multiple errors',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
19
66
|
const DEFAULT_MAX_CATCH_UP_STEPS = 5;
|
|
20
67
|
|
|
21
68
|
interface SessionOptions {
|
|
@@ -30,11 +77,14 @@ interface SessionOptions {
|
|
|
30
77
|
interface ErasedScene {
|
|
31
78
|
readonly actions: readonly string[];
|
|
32
79
|
readonly transitions: readonly string[];
|
|
80
|
+
readonly emits?: readonly string[];
|
|
81
|
+
readonly events?: unknown;
|
|
33
82
|
create(): unknown;
|
|
34
83
|
update(frame: {
|
|
35
84
|
readonly state: unknown;
|
|
36
85
|
readonly input: InputFrame<string>;
|
|
37
86
|
readonly transition: SceneTransitionController<string>;
|
|
87
|
+
readonly events: { emit(name: string, payload: unknown): void };
|
|
38
88
|
readonly tick: number;
|
|
39
89
|
readonly sceneTick: number;
|
|
40
90
|
readonly deltaSeconds: number;
|
|
@@ -50,6 +100,11 @@ interface ActiveScene {
|
|
|
50
100
|
readonly definition: ErasedScene;
|
|
51
101
|
state: unknown;
|
|
52
102
|
sceneTick: number;
|
|
103
|
+
/**
|
|
104
|
+
* Set before user cleanup runs: a throwing disposer never runs again —
|
|
105
|
+
* not on retry, not on session disposal — for this scene instance.
|
|
106
|
+
*/
|
|
107
|
+
disposalAttempted: boolean;
|
|
53
108
|
}
|
|
54
109
|
|
|
55
110
|
type TransitionIntent =
|
|
@@ -84,9 +139,10 @@ export function createGameSession<
|
|
|
84
139
|
const TScenes extends SceneMap,
|
|
85
140
|
const TInput extends InputMap,
|
|
86
141
|
const TInitialScene extends keyof TScenes,
|
|
142
|
+
const TEventDefs extends Record<string, GameEventDescriptor<unknown>> = Record<string, never>,
|
|
87
143
|
>(
|
|
88
|
-
definition: GameDefinition<TScenes, TInput, TInitialScene>,
|
|
89
|
-
): GameSession<TScenes, TInput> {
|
|
144
|
+
definition: GameDefinition<TScenes, TInput, TInitialScene, AssetGroupMap, TEventDefs>,
|
|
145
|
+
): GameSession<TScenes, TInput, TEventDefs> {
|
|
90
146
|
return createGameSessionWithDriver(definition, {
|
|
91
147
|
frameDriver: createAnimationFrameDriver(),
|
|
92
148
|
});
|
|
@@ -97,10 +153,11 @@ export function createGameSessionWithDriver<
|
|
|
97
153
|
const TScenes extends SceneMap,
|
|
98
154
|
const TInput extends InputMap,
|
|
99
155
|
const TInitialScene extends keyof TScenes,
|
|
156
|
+
const TEventDefs extends Record<string, GameEventDescriptor<unknown>> = Record<string, never>,
|
|
100
157
|
>(
|
|
101
|
-
definition: GameDefinition<TScenes, TInput, TInitialScene>,
|
|
158
|
+
definition: GameDefinition<TScenes, TInput, TInitialScene, AssetGroupMap, TEventDefs>,
|
|
102
159
|
options: SessionOptions,
|
|
103
|
-
): GameSession<TScenes, TInput> {
|
|
160
|
+
): GameSession<TScenes, TInput, TEventDefs> {
|
|
104
161
|
const fixedStepMs = options.fixedStepMs ?? DEFAULT_FIXED_STEP_MS;
|
|
105
162
|
const maxCatchUpSteps = options.maxCatchUpSteps ?? DEFAULT_MAX_CATCH_UP_STEPS;
|
|
106
163
|
const maxFrameDeltaMs = options.maxFrameDeltaMs ?? fixedStepMs * DEFAULT_MAX_CATCH_UP_STEPS;
|
|
@@ -119,6 +176,37 @@ export function createGameSessionWithDriver<
|
|
|
119
176
|
throw new RangeError('maxFrameDeltaMs must be a finite positive number');
|
|
120
177
|
}
|
|
121
178
|
|
|
179
|
+
// T13: event definitions and per-scene emit declarations.
|
|
180
|
+
const eventDefs = (definition as unknown as { events?: Record<string, GameEventDescriptor<unknown>> }).events;
|
|
181
|
+
const hasEvents = eventDefs !== undefined && eventDefs !== null;
|
|
182
|
+
const allowedEventNames = hasEvents ? new Set(Object.keys(eventDefs!)) : new Set<string>();
|
|
183
|
+
const allowedEmitsByScene = new Map<string, Set<string>>();
|
|
184
|
+
// Validate per-scene emits are declared events and that the scene's `events` reference matches the game's.
|
|
185
|
+
for (const [name, scene] of Object.entries(definition.scenes)) {
|
|
186
|
+
const erased = scene as unknown as ErasedScene;
|
|
187
|
+
const emits = erased.emits ?? [];
|
|
188
|
+
for (const ev of emits) {
|
|
189
|
+
if (!allowedEventNames.has(ev)) {
|
|
190
|
+
throw new Error(`Scene "${name}" declares unknown event "${ev}"`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
allowedEmitsByScene.set(name, new Set(emits as string[]));
|
|
194
|
+
// Runtime identity check for the branded event definitions.
|
|
195
|
+
if (erased.events !== undefined) {
|
|
196
|
+
if (!hasEvents) {
|
|
197
|
+
throw new Error(`Scene "${name}" is bound to events but the game has no events map`);
|
|
198
|
+
}
|
|
199
|
+
if (erased.events !== eventDefs) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Scene "${name}" is bound to a different events object than the game. Pass the same defineGameEvents() result to both defineScene({ events }) and defineGame({ events }).`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
} else if (emits.length > 0) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Scene "${name}" declares emits but is not bound to the game's events. Pass \`events\` to defineScene.`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
122
210
|
const erasedSceneMap: Record<string, ErasedScene> = {};
|
|
123
211
|
for (const [name, scene] of Object.entries(definition.scenes)) {
|
|
124
212
|
const erased = scene as unknown as ErasedScene;
|
|
@@ -144,9 +232,16 @@ export function createGameSessionWithDriver<
|
|
|
144
232
|
const initialState = freezeObject(initialDefinition.create());
|
|
145
233
|
let initialSnapshot: DeepReadonly<unknown>;
|
|
146
234
|
try {
|
|
147
|
-
initialSnapshot = deepFreeze(initialDefinition.snapshot({ state: initialState })
|
|
235
|
+
initialSnapshot = deepFreeze(initialDefinition.snapshot({ state: initialState }), {
|
|
236
|
+
scene: initialSceneName,
|
|
237
|
+
});
|
|
148
238
|
} catch (error) {
|
|
149
|
-
|
|
239
|
+
// GS-SCENE-02: the cleanup failure must not replace the snapshot
|
|
240
|
+
// failure — compose losslessly in a stable order instead.
|
|
241
|
+
const cleanupFailure = captureFailure(() => initialDefinition.dispose?.(initialState));
|
|
242
|
+
if (cleanupFailure.failed) {
|
|
243
|
+
throw composeFailures(error, cleanupFailure.value);
|
|
244
|
+
}
|
|
150
245
|
throw error;
|
|
151
246
|
}
|
|
152
247
|
|
|
@@ -155,6 +250,7 @@ export function createGameSessionWithDriver<
|
|
|
155
250
|
definition: initialDefinition,
|
|
156
251
|
state: initialState,
|
|
157
252
|
sceneTick: 0,
|
|
253
|
+
disposalAttempted: false,
|
|
158
254
|
};
|
|
159
255
|
let sceneName: string = initialSceneName;
|
|
160
256
|
let currentSnapshot: DeepReadonly<unknown> = initialSnapshot;
|
|
@@ -167,6 +263,12 @@ export function createGameSessionWithDriver<
|
|
|
167
263
|
let accumulatorMs = 0;
|
|
168
264
|
let tick = 0;
|
|
169
265
|
let pendingTransition: TransitionIntent | undefined;
|
|
266
|
+
/**
|
|
267
|
+
* Set when an outgoing scene disposal throws: the retained scene is
|
|
268
|
+
* explicitly non-resumable — `start()` refuses to update it until a
|
|
269
|
+
* transition or restart replaces it. Cleared on every successful install.
|
|
270
|
+
*/
|
|
271
|
+
let sceneFaulted = false;
|
|
170
272
|
let hardCutPending = false;
|
|
171
273
|
let publishedThisCallback = false;
|
|
172
274
|
let updateInProgress = false;
|
|
@@ -182,6 +284,129 @@ export function createGameSessionWithDriver<
|
|
|
182
284
|
stepMs: fixedStepMs,
|
|
183
285
|
}) as unknown as CommitFrame<TScenes>;
|
|
184
286
|
const listeners = new Set<(frame: CommitFrame<TScenes>) => void>();
|
|
287
|
+
const statusListeners = new Set<(status: GameSessionStatus) => void>();
|
|
288
|
+
let deliveringStatus = false;
|
|
289
|
+
const queuedStatuses: GameSessionStatus[] = [];
|
|
290
|
+
let releaseStatusListeners = false;
|
|
291
|
+
|
|
292
|
+
// T13: per-session event listeners.
|
|
293
|
+
const eventListeners = new Map<string, Set<(event: AnyGameEventEnvelope) => void | Promise<void>>>();
|
|
294
|
+
|
|
295
|
+
// Centralized, non-recursive error sink for game event listeners.
|
|
296
|
+
// A failing reporter must not pause the session or escape into the fixed-step loop.
|
|
297
|
+
const reportGameEventListenerError = (name: string, error: unknown): void => {
|
|
298
|
+
try {
|
|
299
|
+
console.error(`Game event listener for "${name}" threw:`, error);
|
|
300
|
+
} catch {
|
|
301
|
+
// Swallow reporter failures — the session must remain running and siblings must still run.
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const deliverEvents = (envelopes: readonly AnyGameEventEnvelope[]): void => {
|
|
306
|
+
// Snapshot per tick: a listener added during this tick's delivery receives
|
|
307
|
+
// only later ticks, and a removal prevents only future ticks.
|
|
308
|
+
const snapshots = new Map<string, readonly ((event: AnyGameEventEnvelope) => void | Promise<void>)[]>();
|
|
309
|
+
for (const env of envelopes) {
|
|
310
|
+
if (!snapshots.has(env.name)) {
|
|
311
|
+
const set = eventListeners.get(env.name);
|
|
312
|
+
snapshots.set(env.name, set ? [...set] : []);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const envelope of envelopes) {
|
|
316
|
+
const snapshot = snapshots.get(envelope.name) ?? [];
|
|
317
|
+
for (const listener of snapshot) {
|
|
318
|
+
let result: unknown;
|
|
319
|
+
try {
|
|
320
|
+
result = listener(envelope);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
reportGameEventListenerError(envelope.name, error);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (result !== null && typeof result === 'object' && typeof (result as Promise<unknown>).then === 'function') {
|
|
326
|
+
(result as Promise<unknown>).catch((error) => {
|
|
327
|
+
reportGameEventListenerError(envelope.name, error);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const NO_EVENT_EMITTER = Object.freeze({
|
|
335
|
+
emit() {
|
|
336
|
+
throw new GameEventError('This game has no events or this scene does not emit any events');
|
|
337
|
+
},
|
|
338
|
+
}) as { emit(name: string, payload: unknown): void };
|
|
339
|
+
|
|
340
|
+
// Deliver one transition pass to a snapshot. A throwing listener never
|
|
341
|
+
// aborts the pass: the remaining snapshot listeners still run, and the
|
|
342
|
+
// first failure is rethrown once every queued transition has been
|
|
343
|
+
// delivered (the transition itself is already authoritative).
|
|
344
|
+
const deliverStatusPass = (current: GameSessionStatus): FailureRecord => {
|
|
345
|
+
let firstFailure: FailureRecord = NO_FAILURE;
|
|
346
|
+
for (const listener of [...statusListeners]) {
|
|
347
|
+
try {
|
|
348
|
+
listener(current);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (!firstFailure.failed) {
|
|
351
|
+
firstFailure = { failed: true, value: error };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return firstFailure;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const notifyStatus = (): void => {
|
|
359
|
+
if (deliveringStatus) {
|
|
360
|
+
// Re-entrant transition from inside a listener: queue it so every
|
|
361
|
+
// listener observes complete states, never a half-applied transition.
|
|
362
|
+
queuedStatuses.push(status);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
deliveringStatus = true;
|
|
366
|
+
let firstFailure: FailureRecord = NO_FAILURE;
|
|
367
|
+
try {
|
|
368
|
+
const passFailure = deliverStatusPass(status);
|
|
369
|
+
if (!firstFailure.failed) {
|
|
370
|
+
firstFailure = passFailure;
|
|
371
|
+
}
|
|
372
|
+
while (queuedStatuses.length > 0) {
|
|
373
|
+
const queued = queuedStatuses.shift() as GameSessionStatus;
|
|
374
|
+
const queuedFailure = deliverStatusPass(queued);
|
|
375
|
+
if (!firstFailure.failed) {
|
|
376
|
+
firstFailure = queuedFailure;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} finally {
|
|
380
|
+
deliveringStatus = false;
|
|
381
|
+
if (releaseStatusListeners) {
|
|
382
|
+
releaseStatusListeners = false;
|
|
383
|
+
statusListeners.clear();
|
|
384
|
+
diagnostics?.onStatusListenerCount?.(statusListeners.size);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
throwFailure(firstFailure);
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Status becomes authoritative only after the command's scheduling and
|
|
391
|
+
// input side effects are safe; listeners then observe the committed value.
|
|
392
|
+
const setStatus = (next: GameSessionStatus): void => {
|
|
393
|
+
if (status === next) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
status = next;
|
|
397
|
+
notifyStatus();
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// Error-path pause: complete the transition and notify, but never let a
|
|
401
|
+
// status-listener failure mask the original error that caused the pause.
|
|
402
|
+
const pauseAndRethrow = (original: unknown): never => {
|
|
403
|
+
try {
|
|
404
|
+
pauseInternal();
|
|
405
|
+
} catch {
|
|
406
|
+
// The original error is the actionable one; listeners were notified.
|
|
407
|
+
}
|
|
408
|
+
throw original;
|
|
409
|
+
};
|
|
185
410
|
|
|
186
411
|
const assertLive = () => {
|
|
187
412
|
if (status === 'disposed') {
|
|
@@ -189,7 +414,9 @@ export function createGameSessionWithDriver<
|
|
|
189
414
|
}
|
|
190
415
|
};
|
|
191
416
|
const isDisposed = () => status === 'disposed';
|
|
192
|
-
|
|
417
|
+
// T10.4: gameplay input is rejected at the shared boundary while paused
|
|
418
|
+
// (before the queue and before the accepted-input diagnostics).
|
|
419
|
+
const inputBuffer = createInputBuffer(definition.input, assertLive, () => status === 'paused');
|
|
193
420
|
|
|
194
421
|
// Simulation commits are the only presentation updates (T5): a commit
|
|
195
422
|
// happens on the initial baseline, after every presentation callback that
|
|
@@ -226,7 +453,6 @@ export function createGameSessionWithDriver<
|
|
|
226
453
|
if (status !== 'running') {
|
|
227
454
|
return;
|
|
228
455
|
}
|
|
229
|
-
status = 'paused';
|
|
230
456
|
generation += 1;
|
|
231
457
|
if (frameHandle !== undefined) {
|
|
232
458
|
options.frameDriver.cancelFrame(frameHandle);
|
|
@@ -235,15 +461,28 @@ export function createGameSessionWithDriver<
|
|
|
235
461
|
previousTimestampMs = undefined;
|
|
236
462
|
accumulatorMs = 0;
|
|
237
463
|
inputBuffer.reset();
|
|
464
|
+
setStatus('paused');
|
|
238
465
|
};
|
|
239
466
|
|
|
240
467
|
const commitOrPause = () => {
|
|
241
468
|
try {
|
|
242
469
|
commit();
|
|
243
470
|
} catch (error) {
|
|
244
|
-
|
|
245
|
-
|
|
471
|
+
pauseAndRethrow(error);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Dispose one scene instance exactly once. The attempt flag is set before
|
|
477
|
+
* user cleanup runs, so a throwing disposer is never retried for this
|
|
478
|
+
* instance — by transition retries or by session disposal.
|
|
479
|
+
*/
|
|
480
|
+
const disposeSceneInstance = (scene: ActiveScene, state: unknown): void => {
|
|
481
|
+
if (scene.disposalAttempted) {
|
|
482
|
+
return;
|
|
246
483
|
}
|
|
484
|
+
scene.disposalAttempted = true;
|
|
485
|
+
scene.definition.dispose?.(state);
|
|
247
486
|
};
|
|
248
487
|
|
|
249
488
|
/**
|
|
@@ -277,7 +516,9 @@ export function createGameSessionWithDriver<
|
|
|
277
516
|
try {
|
|
278
517
|
targetState = freezeObject(targetDefinition.create());
|
|
279
518
|
targetCreated = true;
|
|
280
|
-
targetSnapshot = deepFreeze(targetDefinition.snapshot({ state: targetState })
|
|
519
|
+
targetSnapshot = deepFreeze(targetDefinition.snapshot({ state: targetState }), {
|
|
520
|
+
scene: targetName,
|
|
521
|
+
});
|
|
281
522
|
} catch (error) {
|
|
282
523
|
if (targetCreated) {
|
|
283
524
|
try {
|
|
@@ -290,15 +531,17 @@ export function createGameSessionWithDriver<
|
|
|
290
531
|
}
|
|
291
532
|
|
|
292
533
|
try {
|
|
293
|
-
activeScene
|
|
534
|
+
disposeSceneInstance(activeScene, outgoingFinalState);
|
|
294
535
|
} catch (error) {
|
|
295
|
-
// The outgoing scene's dispose already ran (possibly partially) and
|
|
296
|
-
//
|
|
536
|
+
// The outgoing scene's dispose already ran (possibly partially) and
|
|
537
|
+
// is never retried; the old scene remains active but explicitly
|
|
538
|
+
// faulted. The prepared target is still cleaned up, and a failing
|
|
539
|
+
// target cleanup composes losslessly with the outgoing failure.
|
|
540
|
+
sceneFaulted = true;
|
|
297
541
|
if (targetCreated) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// Best effort.
|
|
542
|
+
const targetFailure = captureFailure(() => targetDefinition.dispose?.(targetState));
|
|
543
|
+
if (targetFailure.failed) {
|
|
544
|
+
throw composeFailures(error, targetFailure.value);
|
|
302
545
|
}
|
|
303
546
|
}
|
|
304
547
|
throw error;
|
|
@@ -312,7 +555,9 @@ export function createGameSessionWithDriver<
|
|
|
312
555
|
definition: targetDefinition,
|
|
313
556
|
state: targetState,
|
|
314
557
|
sceneTick: 0,
|
|
558
|
+
disposalAttempted: false,
|
|
315
559
|
};
|
|
560
|
+
sceneFaulted = false;
|
|
316
561
|
sceneName = targetName;
|
|
317
562
|
currentSnapshot = targetSnapshot;
|
|
318
563
|
previousSnapshot = targetSnapshot;
|
|
@@ -383,6 +628,31 @@ export function createGameSessionWithDriver<
|
|
|
383
628
|
});
|
|
384
629
|
};
|
|
385
630
|
|
|
631
|
+
// One scheduler-failure policy for successor scheduling (GS-SESSION-02).
|
|
632
|
+
// The initial `start()` path keeps its own restore-previous-status policy
|
|
633
|
+
// because no running state was ever published there. On failure: clear the
|
|
634
|
+
// handle, invalidate the generation so late callbacks are no-ops,
|
|
635
|
+
// neutralize input via the pause path, and publish paused while preserving
|
|
636
|
+
// the scheduling error — a throwing status listener composes with it
|
|
637
|
+
// instead of masking it. Never retries automatically.
|
|
638
|
+
const failScheduling = (error: unknown): never => {
|
|
639
|
+
frameHandle = undefined;
|
|
640
|
+
try {
|
|
641
|
+
pauseInternal();
|
|
642
|
+
} catch (statusError) {
|
|
643
|
+
throw composeFailures(error, statusError);
|
|
644
|
+
}
|
|
645
|
+
throw error;
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const scheduleSuccessor = (activeGeneration: number): void => {
|
|
649
|
+
try {
|
|
650
|
+
schedule(activeGeneration);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
failScheduling(error);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
386
656
|
const schedule = (activeGeneration: number) => {
|
|
387
657
|
frameHandle = options.frameDriver.requestFrame((timestampMs) => {
|
|
388
658
|
if (status !== 'running' || generation !== activeGeneration) {
|
|
@@ -394,16 +664,17 @@ export function createGameSessionWithDriver<
|
|
|
394
664
|
diagnostics.onDisplayCallback();
|
|
395
665
|
}
|
|
396
666
|
|
|
397
|
-
// A pending external transition commits at the next
|
|
398
|
-
// without advancing
|
|
667
|
+
// A pending external transition commits at the next frame-driver
|
|
668
|
+
// callback — a zero-step callback may carry it — without advancing
|
|
669
|
+
// simulation tick/time. The target scene first updates at the next
|
|
670
|
+
// due fixed step; accumulated timing debt is preserved.
|
|
399
671
|
if (pendingTransition !== undefined) {
|
|
400
672
|
const pending = pendingTransition;
|
|
401
673
|
pendingTransition = undefined;
|
|
402
674
|
try {
|
|
403
675
|
commitTransition(pending, activeScene.state, undefined);
|
|
404
676
|
} catch (error) {
|
|
405
|
-
|
|
406
|
-
throw error;
|
|
677
|
+
pauseAndRethrow(error);
|
|
407
678
|
}
|
|
408
679
|
commitOrPause();
|
|
409
680
|
if (status !== 'running' || generation !== activeGeneration) {
|
|
@@ -417,7 +688,7 @@ export function createGameSessionWithDriver<
|
|
|
417
688
|
commitOrPause();
|
|
418
689
|
}
|
|
419
690
|
if (status === 'running' && generation === activeGeneration) {
|
|
420
|
-
|
|
691
|
+
scheduleSuccessor(activeGeneration);
|
|
421
692
|
}
|
|
422
693
|
return;
|
|
423
694
|
}
|
|
@@ -439,6 +710,42 @@ export function createGameSessionWithDriver<
|
|
|
439
710
|
stepsRan = true;
|
|
440
711
|
const nextTick = tick + 1;
|
|
441
712
|
const nextSceneTick = activeScene.sceneTick + 1;
|
|
713
|
+
const sceneNameForTick = activeScene.name;
|
|
714
|
+
const allowedEmits = allowedEmitsByScene.get(sceneNameForTick) ?? new Set<string>();
|
|
715
|
+
|
|
716
|
+
// T13: per-tick transactional emitter.
|
|
717
|
+
let stagedThisTick: AnyGameEventEnvelope[] = [];
|
|
718
|
+
let ordinal = 0;
|
|
719
|
+
let emitterInvalidated = false;
|
|
720
|
+
const emitter: { emit(name: string, payload: unknown): void } = hasEvents
|
|
721
|
+
? {
|
|
722
|
+
emit(name: string, payload: unknown) {
|
|
723
|
+
if (emitterInvalidated) {
|
|
724
|
+
throw new GameEventError('Game event emitter is only valid during its owning scene update');
|
|
725
|
+
}
|
|
726
|
+
if (!allowedEventNames.has(name)) {
|
|
727
|
+
throw new GameEventError(`Event "${name}" is not declared by this game`);
|
|
728
|
+
}
|
|
729
|
+
if (!allowedEmits.has(name)) {
|
|
730
|
+
throw new GameEventError(`Scene "${sceneNameForTick}" cannot emit event "${name}" (missing from its emits list)`);
|
|
731
|
+
}
|
|
732
|
+
const cloned = cloneAndValidatePayload(payload, name, 'payload');
|
|
733
|
+
const envelope = Object.freeze({
|
|
734
|
+
name,
|
|
735
|
+
payload: cloned,
|
|
736
|
+
tick: nextTick,
|
|
737
|
+
scene: sceneNameForTick,
|
|
738
|
+
sceneTick: nextSceneTick,
|
|
739
|
+
ordinal: ordinal++,
|
|
740
|
+
}) as AnyGameEventEnvelope;
|
|
741
|
+
stagedThisTick.push(envelope);
|
|
742
|
+
},
|
|
743
|
+
}
|
|
744
|
+
: (NO_EVENT_EMITTER as { emit(name: string, payload: unknown): void });
|
|
745
|
+
if (hasEvents) {
|
|
746
|
+
Object.freeze(emitter);
|
|
747
|
+
}
|
|
748
|
+
|
|
442
749
|
const sampleStart = diagnostics === undefined ? 0 : now();
|
|
443
750
|
const input = inputBuffer.sample();
|
|
444
751
|
if (diagnostics !== undefined) {
|
|
@@ -448,18 +755,31 @@ export function createGameSessionWithDriver<
|
|
|
448
755
|
activeUpdateScope = scope;
|
|
449
756
|
updateInProgress = true;
|
|
450
757
|
const updateStart = diagnostics === undefined ? 0 : now();
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
758
|
+
let nextState: unknown;
|
|
759
|
+
try {
|
|
760
|
+
nextState = freezeObject(
|
|
761
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
762
|
+
(activeScene.definition.update as any)({
|
|
763
|
+
state: activeScene.state,
|
|
764
|
+
input,
|
|
765
|
+
transition: makeTransitionController(scope),
|
|
766
|
+
events: emitter,
|
|
767
|
+
tick: nextTick,
|
|
768
|
+
sceneTick: nextSceneTick,
|
|
769
|
+
deltaSeconds: fixedStepMs / 1000,
|
|
770
|
+
elapsedSeconds: (nextTick * fixedStepMs) / 1000,
|
|
771
|
+
sceneElapsedSeconds: (nextSceneTick * fixedStepMs) / 1000,
|
|
772
|
+
}),
|
|
773
|
+
);
|
|
774
|
+
} catch (error) {
|
|
775
|
+
// Update failed: discard staged events, invalidate emitter.
|
|
776
|
+
stagedThisTick = [];
|
|
777
|
+
emitterInvalidated = true;
|
|
778
|
+
activeUpdateScope = undefined;
|
|
779
|
+
updateInProgress = false;
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
emitterInvalidated = true;
|
|
463
783
|
activeUpdateScope = undefined;
|
|
464
784
|
updateInProgress = false;
|
|
465
785
|
if (diagnostics !== undefined) {
|
|
@@ -473,7 +793,17 @@ export function createGameSessionWithDriver<
|
|
|
473
793
|
// A transition requested during update commits after this update
|
|
474
794
|
// completes successfully. The update consumed one fixed step; the
|
|
475
795
|
// hard-cut frame publishes at the end of this presentation callback.
|
|
476
|
-
|
|
796
|
+
try {
|
|
797
|
+
commitTransition(intent, nextState, nextTick, true);
|
|
798
|
+
} catch (error) {
|
|
799
|
+
// Transition preparation/disposal failed: discard staged events.
|
|
800
|
+
stagedThisTick = [];
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
// Successful transition tick: staged events commit with source tick/scene.
|
|
804
|
+
if (stagedThisTick.length > 0) {
|
|
805
|
+
deliverEvents(stagedThisTick);
|
|
806
|
+
}
|
|
477
807
|
accumulatorMs = Math.max(0, accumulatorMs - fixedStepMs);
|
|
478
808
|
// F4: the transition consumed one fixed step; report it as such
|
|
479
809
|
// and never as a zero-step callback (zero-step fires only when
|
|
@@ -484,12 +814,24 @@ export function createGameSessionWithDriver<
|
|
|
484
814
|
break;
|
|
485
815
|
}
|
|
486
816
|
const snapshotStart = diagnostics === undefined ? 0 : now();
|
|
487
|
-
|
|
817
|
+
let rawSnapshot: unknown;
|
|
818
|
+
try {
|
|
819
|
+
rawSnapshot = activeScene.definition.snapshot({ state: nextState });
|
|
820
|
+
} catch (error) {
|
|
821
|
+
stagedThisTick = [];
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
488
824
|
if (diagnostics !== undefined) {
|
|
489
825
|
diagnostics.onSnapshot(now() - snapshotStart);
|
|
490
826
|
}
|
|
491
827
|
const freezeStart = diagnostics === undefined ? 0 : now();
|
|
492
|
-
|
|
828
|
+
let nextSnapshot: DeepReadonly<unknown>;
|
|
829
|
+
try {
|
|
830
|
+
nextSnapshot = deepFreeze(rawSnapshot, { scene: sceneNameForTick, tick: nextTick });
|
|
831
|
+
} catch (error) {
|
|
832
|
+
stagedThisTick = [];
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
493
835
|
if (diagnostics !== undefined) {
|
|
494
836
|
diagnostics.onDeepFreeze(now() - freezeStart);
|
|
495
837
|
}
|
|
@@ -501,6 +843,10 @@ export function createGameSessionWithDriver<
|
|
|
501
843
|
previousSnapshot = currentSnapshot;
|
|
502
844
|
currentSnapshot = nextSnapshot;
|
|
503
845
|
tick = nextTick;
|
|
846
|
+
// Successful tick: deliver staged events in tick order, then emission order.
|
|
847
|
+
if (stagedThisTick.length > 0) {
|
|
848
|
+
deliverEvents(stagedThisTick);
|
|
849
|
+
}
|
|
504
850
|
accumulatorMs = Math.max(0, accumulatorMs - fixedStepMs);
|
|
505
851
|
catchUpSteps += 1;
|
|
506
852
|
if (diagnostics !== undefined) {
|
|
@@ -513,8 +859,7 @@ export function createGameSessionWithDriver<
|
|
|
513
859
|
} catch (error) {
|
|
514
860
|
activeUpdateScope = undefined;
|
|
515
861
|
updateInProgress = false;
|
|
516
|
-
|
|
517
|
-
throw error;
|
|
862
|
+
pauseAndRethrow(error);
|
|
518
863
|
}
|
|
519
864
|
|
|
520
865
|
if (catchUpSteps === maxCatchUpSteps && accumulatorMs >= fixedStepMs) {
|
|
@@ -539,12 +884,12 @@ export function createGameSessionWithDriver<
|
|
|
539
884
|
commitOrPause();
|
|
540
885
|
}
|
|
541
886
|
if (status === 'running' && generation === activeGeneration) {
|
|
542
|
-
|
|
887
|
+
scheduleSuccessor(activeGeneration);
|
|
543
888
|
}
|
|
544
889
|
});
|
|
545
890
|
};
|
|
546
891
|
|
|
547
|
-
const session: GameSession<TScenes, TInput> = {
|
|
892
|
+
const session: GameSession<TScenes, TInput, TEventDefs> = {
|
|
548
893
|
get status() {
|
|
549
894
|
return status;
|
|
550
895
|
},
|
|
@@ -558,21 +903,44 @@ export function createGameSessionWithDriver<
|
|
|
558
903
|
if (status === 'running') {
|
|
559
904
|
return;
|
|
560
905
|
}
|
|
561
|
-
|
|
906
|
+
if (sceneFaulted) {
|
|
907
|
+
throw new GameSessionLifecycleError(
|
|
908
|
+
'The active scene failed disposal and is explicitly non-resumable: ' +
|
|
909
|
+
'replace it with setScene() or restartScene() before start(), or dispose the session.',
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const previous = status;
|
|
562
913
|
previousTimestampMs = undefined;
|
|
563
914
|
accumulatorMs = 0;
|
|
564
915
|
const activeGeneration = ++generation;
|
|
565
|
-
|
|
916
|
+
try {
|
|
917
|
+
schedule(activeGeneration);
|
|
918
|
+
} catch (error) {
|
|
919
|
+
// Do not publish `running` when scheduling could not be established.
|
|
920
|
+
status = previous;
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
setStatus('running');
|
|
566
924
|
},
|
|
567
925
|
pause() {
|
|
568
926
|
assertLive();
|
|
569
|
-
pauseInternal();
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
927
|
+
const pauseFailure = captureFailure(() => pauseInternal());
|
|
928
|
+
// The command must complete: a pending external transition is resolved
|
|
929
|
+
// at the pause boundary even when a status listener threw, so a later
|
|
930
|
+
// start() can never commit a stale transition on an unexpected frame.
|
|
931
|
+
const transitionFailure = captureFailure(() => {
|
|
932
|
+
if (pendingTransition !== undefined) {
|
|
933
|
+
const pending = pendingTransition;
|
|
934
|
+
pendingTransition = undefined;
|
|
935
|
+
commitTransition(pending, activeScene.state, undefined);
|
|
936
|
+
commitOrPause();
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
if (pauseFailure.failed && transitionFailure.failed) {
|
|
940
|
+
throw composeFailures(pauseFailure.value, transitionFailure.value);
|
|
575
941
|
}
|
|
942
|
+
throwFailure(pauseFailure);
|
|
943
|
+
throwFailure(transitionFailure);
|
|
576
944
|
},
|
|
577
945
|
setScene(name: keyof TScenes) {
|
|
578
946
|
assertLive();
|
|
@@ -617,14 +985,29 @@ export function createGameSessionWithDriver<
|
|
|
617
985
|
options.frameDriver.cancelFrame(frameHandle);
|
|
618
986
|
frameHandle = undefined;
|
|
619
987
|
}
|
|
620
|
-
status = 'disposed';
|
|
621
988
|
generation += 1;
|
|
622
989
|
previousTimestampMs = undefined;
|
|
623
990
|
accumulatorMs = 0;
|
|
624
991
|
pendingTransition = undefined;
|
|
625
992
|
inputBuffer.reset();
|
|
626
|
-
|
|
627
|
-
|
|
993
|
+
eventListeners.clear();
|
|
994
|
+
// Terminal cleanup is unconditional: `disposed` is delivered to the
|
|
995
|
+
// full listener snapshot (including the re-entrant queued pass), then
|
|
996
|
+
// every listener and the scene are released exactly once even when a
|
|
997
|
+
// status listener throws. The release flag is set BEFORE the
|
|
998
|
+
// notification so notifyStatus's finally performs the clear; for the
|
|
999
|
+
// re-entrant path the clear happens after the queued pass drains.
|
|
1000
|
+
releaseStatusListeners = true;
|
|
1001
|
+
const notificationFailure = captureFailure(() => setStatus('disposed'));
|
|
1002
|
+
const cleanupFailure = captureFailure(() => {
|
|
1003
|
+
listeners.clear();
|
|
1004
|
+
disposeSceneInstance(activeScene, activeScene.state);
|
|
1005
|
+
});
|
|
1006
|
+
if (notificationFailure.failed && cleanupFailure.failed) {
|
|
1007
|
+
throw composeFailures(notificationFailure.value, cleanupFailure.value);
|
|
1008
|
+
}
|
|
1009
|
+
throwFailure(notificationFailure);
|
|
1010
|
+
throwFailure(cleanupFailure);
|
|
628
1011
|
},
|
|
629
1012
|
getRenderFrame() {
|
|
630
1013
|
const alpha = commitFrame.hardCut
|
|
@@ -646,6 +1029,50 @@ export function createGameSessionWithDriver<
|
|
|
646
1029
|
},
|
|
647
1030
|
});
|
|
648
1031
|
},
|
|
1032
|
+
addStatusListener(listener) {
|
|
1033
|
+
assertLive();
|
|
1034
|
+
statusListeners.add(listener);
|
|
1035
|
+
diagnostics?.onStatusListenerCount?.(statusListeners.size);
|
|
1036
|
+
let removed = false;
|
|
1037
|
+
return Object.freeze({
|
|
1038
|
+
remove() {
|
|
1039
|
+
if (removed) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
removed = true;
|
|
1043
|
+
statusListeners.delete(listener);
|
|
1044
|
+
diagnostics?.onStatusListenerCount?.(statusListeners.size);
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
},
|
|
1048
|
+
addGameEventListener: ((name: string, listener: (event: AnyGameEventEnvelope) => void | Promise<void>) => {
|
|
1049
|
+
assertLive();
|
|
1050
|
+
if (!hasEvents || !allowedEventNames.has(name)) {
|
|
1051
|
+
throw new GameEventError(`Event "${String(name)}" is not declared by this game`);
|
|
1052
|
+
}
|
|
1053
|
+
let set = eventListeners.get(name);
|
|
1054
|
+
if (set === undefined) {
|
|
1055
|
+
set = new Set();
|
|
1056
|
+
eventListeners.set(name, set);
|
|
1057
|
+
}
|
|
1058
|
+
set.add(listener as (event: AnyGameEventEnvelope) => void | Promise<void>);
|
|
1059
|
+
let removed = false;
|
|
1060
|
+
return Object.freeze({
|
|
1061
|
+
remove() {
|
|
1062
|
+
if (removed) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
removed = true;
|
|
1066
|
+
const existing = eventListeners.get(name);
|
|
1067
|
+
if (existing) {
|
|
1068
|
+
existing.delete(listener as (event: AnyGameEventEnvelope) => void | Promise<void>);
|
|
1069
|
+
if (existing.size === 0) {
|
|
1070
|
+
eventListeners.delete(name);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
}) as unknown as GameSession<TScenes, TInput, TEventDefs>['addGameEventListener'],
|
|
649
1076
|
};
|
|
650
1077
|
|
|
651
1078
|
return Object.freeze(session);
|