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
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
|
|
2
|
+
import type {
|
|
3
|
+
PlatformerContact2D,
|
|
4
|
+
PlatformerMoveResult2D,
|
|
5
|
+
TileCell2D,
|
|
6
|
+
TileMap2D,
|
|
7
|
+
} from './types';
|
|
8
|
+
import { cellsInAabb, cellsInSweptBounds } from './queries';
|
|
9
|
+
import { tileError } from './errors';
|
|
10
|
+
import type { Aabb2D, Vector2D } from '../geometry/types';
|
|
11
|
+
import { assertValidAabb2D, assertValidVector2D } from '../geometry/validation';
|
|
12
|
+
|
|
13
|
+
/** Frozen v1 movement semantics (T16.0). */
|
|
14
|
+
export const PLATFORMER_MAX_ITERATIONS = 4;
|
|
15
|
+
export const PLATFORMER_SKIN = 1e-6;
|
|
16
|
+
/** Previous-bottom must be at or above the platform top by this tolerance. */
|
|
17
|
+
export const ONE_WAY_TOLERANCE = 1e-4;
|
|
18
|
+
|
|
19
|
+
export interface PlatformerMoveOptions2D {
|
|
20
|
+
/** Explicit per-call drop-through intent; the helper owns no timer. */
|
|
21
|
+
readonly dropThroughOneWay?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Snap downward onto floor tops within this many world units when the
|
|
24
|
+
* body ends the step falling or resting and no floor contact was resolved.
|
|
25
|
+
* Default 0 (disabled).
|
|
26
|
+
*/
|
|
27
|
+
readonly floorSnapDistance?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isSolid(kind: string | undefined): boolean {
|
|
31
|
+
return kind === 'solid';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isOneWay(kind: string | undefined): boolean {
|
|
35
|
+
return kind === 'one-way-up';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function overlaps(a: Aabb2D, b: Aabb2D): boolean {
|
|
39
|
+
return (
|
|
40
|
+
a.x < b.x + b.width && a.x + a.width > b.x &&
|
|
41
|
+
a.y < b.y + b.height && a.y + a.height > b.y
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Pure fixed-step AABB platformer movement (T16.3, T16-F5).
|
|
47
|
+
*
|
|
48
|
+
* Frozen semantics:
|
|
49
|
+
* - Axis order X then Y.
|
|
50
|
+
* - Solid tiles block both axes; one-way tiles block descent only when the
|
|
51
|
+
* previous bottom was at/above the platform top (ONE_WAY_TOLERANCE) and
|
|
52
|
+
* `dropThroughOneWay` is not set.
|
|
53
|
+
* - Starting overlap resolves along the least-penetration face preferring UP
|
|
54
|
+
* on ties (up = negative y in y-down space), up to PLATFORMER_MAX_ITERATIONS;
|
|
55
|
+
* unresolved spawn overlap throws deterministically. The reported contact is
|
|
56
|
+
* always the tile that supplied the winning face.
|
|
57
|
+
* - Each axis resolves against the NEAREST blocking plane and reports contacts
|
|
58
|
+
* ONLY for tiles touching that winning plane — farther crossed rows/columns
|
|
59
|
+
* are never reported.
|
|
60
|
+
* - Floor snap pulls a falling/resting body down onto tops within
|
|
61
|
+
* `floorSnapDistance`.
|
|
62
|
+
* - Candidate order is layer order then row-major cell order; equal physical
|
|
63
|
+
* candidates preserve that deterministic order.
|
|
64
|
+
*
|
|
65
|
+
* Displacement contract (GS-TILE-03): `displacement` reports requested-motion
|
|
66
|
+
* resolution only — starting-overlap recovery moves the body and reports its
|
|
67
|
+
* contact, but never contributes to displacement. The caller's body object is
|
|
68
|
+
* never mutated or frozen; every returned body/contact/normal is an owned
|
|
69
|
+
* frozen value.
|
|
70
|
+
*/
|
|
71
|
+
export function movePlatformerBody2D(options: {
|
|
72
|
+
readonly body: Aabb2D;
|
|
73
|
+
readonly velocity: Vector2D;
|
|
74
|
+
readonly deltaSeconds: number;
|
|
75
|
+
readonly map: TileMap2D;
|
|
76
|
+
/** Layer ids that contribute collision; others render only. */
|
|
77
|
+
readonly collisionLayers: readonly string[];
|
|
78
|
+
} & PlatformerMoveOptions2D): PlatformerMoveResult2D {
|
|
79
|
+
const { map, deltaSeconds, collisionLayers } = options;
|
|
80
|
+
// GS-TILE-03: validate author inputs once at the public boundary, and
|
|
81
|
+
// copy the body — the caller's object is never mutated or frozen.
|
|
82
|
+
assertValidAabb2D(options.body, 'body');
|
|
83
|
+
assertValidVector2D(options.velocity, 'velocity');
|
|
84
|
+
let body: Aabb2D = { ...options.body };
|
|
85
|
+
let vx = options.velocity.x;
|
|
86
|
+
let vy = options.velocity.y;
|
|
87
|
+
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
|
|
88
|
+
throw tileError('deltaSeconds', `must be a finite number >= 0; got ${String(deltaSeconds)}`);
|
|
89
|
+
}
|
|
90
|
+
const dropThrough = options.dropThroughOneWay ?? false;
|
|
91
|
+
if (typeof dropThrough !== 'boolean') {
|
|
92
|
+
throw tileError('dropThroughOneWay', `must be a boolean or omitted; got ${String(options.dropThroughOneWay)}`);
|
|
93
|
+
}
|
|
94
|
+
const snapDistance = options.floorSnapDistance ?? 0;
|
|
95
|
+
if (typeof snapDistance !== 'number' || !Number.isFinite(snapDistance) || snapDistance < 0) {
|
|
96
|
+
throw tileError('floorSnapDistance', `must be a finite number >= 0 or omitted; got ${String(options.floorSnapDistance)}`);
|
|
97
|
+
}
|
|
98
|
+
// GS-TILE-02: layer selection is explicit and exact — unknown ids fail
|
|
99
|
+
// here instead of silently colliding with nothing (a typo would otherwise
|
|
100
|
+
// drop the player through the world). Duplicates are harmless: queries
|
|
101
|
+
// iterate map layers, so an id can never report twice.
|
|
102
|
+
if (!Array.isArray(collisionLayers)) {
|
|
103
|
+
throw tileError('collisionLayers', `must be an array of layer ids; got ${String(collisionLayers)}`);
|
|
104
|
+
}
|
|
105
|
+
for (const layerId of collisionLayers) {
|
|
106
|
+
if (typeof layerId !== 'string' || map.layerById[layerId] === undefined) {
|
|
107
|
+
throw tileError('collisionLayers', `unknown collision layer ${JSON.stringify(layerId)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const intendedX = vx * deltaSeconds;
|
|
111
|
+
const intendedY = vy * deltaSeconds;
|
|
112
|
+
|
|
113
|
+
const contacts: PlatformerContact2D[] = [];
|
|
114
|
+
let appliedX = 0;
|
|
115
|
+
let appliedY = 0;
|
|
116
|
+
|
|
117
|
+
/** Report one owned frozen contact (GS-TILE-03: nested values immutable). */
|
|
118
|
+
const report = (cell: TileCell2D, normal: { x: number; y: number }): void => {
|
|
119
|
+
contacts.push(
|
|
120
|
+
Object.freeze({ cell, normal: Object.freeze({ ...normal }) }),
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// --- Starting-overlap recovery ---
|
|
125
|
+
// Track the winning tile TOGETHER with its face so the reported contact
|
|
126
|
+
// always names the tile that supplied the resolved plane (T16-F5).
|
|
127
|
+
for (let iter = 0; iter < PLATFORMER_MAX_ITERATIONS; iter++) {
|
|
128
|
+
const overlapping = cellsInAabb(map, body, collisionLayers)
|
|
129
|
+
.filter((c) => isSolid(c.collision) && overlaps(body, c.aabb));
|
|
130
|
+
if (overlapping.length === 0) break;
|
|
131
|
+
// Least-penetration FACE wins; ties prefer pushing UP (negative y).
|
|
132
|
+
let bestAxis: 'x' | 'y' = 'y';
|
|
133
|
+
let bestSign = -1;
|
|
134
|
+
let bestDepth = Number.POSITIVE_INFINITY;
|
|
135
|
+
let bestCell: TileCell2D | undefined;
|
|
136
|
+
const consider = (axis: 'x' | 'y', sign: number, depth: number, cell: TileCell2D): void => {
|
|
137
|
+
// Deterministic tie rule: prefer y over x, and prefer negative-y (up)
|
|
138
|
+
// over positive-y. Otherwise first-evaluated (row-major) wins.
|
|
139
|
+
const better =
|
|
140
|
+
depth < bestDepth ||
|
|
141
|
+
(depth === bestDepth &&
|
|
142
|
+
(axis === 'y' ? bestAxis !== 'y' || sign === -1 : false));
|
|
143
|
+
if (better) {
|
|
144
|
+
bestAxis = axis;
|
|
145
|
+
bestSign = sign;
|
|
146
|
+
bestDepth = depth;
|
|
147
|
+
bestCell = cell;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
for (const c of overlapping) {
|
|
151
|
+
const pushLeft = (c.aabb.x - body.width) - body.x;
|
|
152
|
+
const pushRight = (c.aabb.x + c.aabb.width) - body.x;
|
|
153
|
+
const pushUp = c.aabb.y - body.height - body.y;
|
|
154
|
+
const pushDown = (c.aabb.y + c.aabb.height) - body.y;
|
|
155
|
+
consider('x', -1, Math.abs(pushLeft), c);
|
|
156
|
+
consider('x', 1, Math.abs(pushRight), c);
|
|
157
|
+
consider('y', -1, Math.abs(pushUp), c);
|
|
158
|
+
consider('y', 1, Math.abs(pushDown), c);
|
|
159
|
+
}
|
|
160
|
+
if (bestCell === undefined) break;
|
|
161
|
+
// Closure mutation defeats TS narrowing here; assert the union.
|
|
162
|
+
const axis = bestAxis as 'x' | 'y';
|
|
163
|
+
body =
|
|
164
|
+
axis === 'x'
|
|
165
|
+
? { ...body, x: body.x + bestSign * bestDepth }
|
|
166
|
+
: { ...body, y: body.y + bestSign * bestDepth };
|
|
167
|
+
// Negative-y push lands the body ON a tile top (floor); positive-y hits
|
|
168
|
+
// a ceiling. The contact names the winning tile.
|
|
169
|
+
if (axis === 'y') {
|
|
170
|
+
report(bestCell, { x: 0, y: bestSign });
|
|
171
|
+
vy = 0;
|
|
172
|
+
} else {
|
|
173
|
+
report(bestCell, { x: bestSign, y: 0 });
|
|
174
|
+
vx = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Final check: unresolved spawn overlap is a deterministic error.
|
|
178
|
+
const stillOverlapping = cellsInAabb(map, body, collisionLayers).some(
|
|
179
|
+
(c) => isSolid(c.collision) && overlaps(body, c.aabb),
|
|
180
|
+
);
|
|
181
|
+
if (stillOverlapping) {
|
|
182
|
+
throw tileError('body', 'starting overlap could not be resolved within PLATFORMER_MAX_ITERATIONS');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const prevBottom = body.y + body.height;
|
|
186
|
+
// --- X axis sweep ---
|
|
187
|
+
{
|
|
188
|
+
const dx = intendedX;
|
|
189
|
+
if (dx !== 0) {
|
|
190
|
+
const newX = body.x + dx;
|
|
191
|
+
let clamped = newX;
|
|
192
|
+
const yOverlap = (aabb: Aabb2D): boolean =>
|
|
193
|
+
body.y < aabb.y + aabb.height && body.y + body.height > aabb.y;
|
|
194
|
+
const solids = cellsInSweptBounds(map, body, { x: dx, y: 0 }, collisionLayers)
|
|
195
|
+
.filter((c) => isSolid(c.collision));
|
|
196
|
+
// Phase 1: resolve against the NEAREST blocking plane only.
|
|
197
|
+
if (dx > 0) {
|
|
198
|
+
let planeX = Number.POSITIVE_INFINITY;
|
|
199
|
+
for (const c of solids) {
|
|
200
|
+
if (!yOverlap(c.aabb)) continue;
|
|
201
|
+
if (body.x + body.width > c.aabb.x + PLATFORMER_SKIN) continue;
|
|
202
|
+
if (c.aabb.x < planeX) planeX = c.aabb.x;
|
|
203
|
+
}
|
|
204
|
+
if (planeX !== Number.POSITIVE_INFINITY) {
|
|
205
|
+
clamped = Math.min(clamped, planeX - body.width);
|
|
206
|
+
// Phase 2: emit contacts ONLY for tiles touching the winning plane.
|
|
207
|
+
for (const c of solids) {
|
|
208
|
+
if (!yOverlap(c.aabb)) continue;
|
|
209
|
+
if (Math.abs(c.aabb.x - planeX) > PLATFORMER_SKIN) continue;
|
|
210
|
+
report(c, { x: -1, y: 0 });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
let planeX = Number.NEGATIVE_INFINITY;
|
|
215
|
+
for (const c of solids) {
|
|
216
|
+
if (!yOverlap(c.aabb)) continue;
|
|
217
|
+
if (body.x < c.aabb.x + c.aabb.width - PLATFORMER_SKIN) continue;
|
|
218
|
+
if (c.aabb.x + c.aabb.width > planeX) planeX = c.aabb.x + c.aabb.width;
|
|
219
|
+
}
|
|
220
|
+
if (planeX !== Number.NEGATIVE_INFINITY) {
|
|
221
|
+
clamped = Math.max(clamped, planeX);
|
|
222
|
+
for (const c of solids) {
|
|
223
|
+
if (!yOverlap(c.aabb)) continue;
|
|
224
|
+
if (Math.abs(c.aabb.x + c.aabb.width - planeX) > PLATFORMER_SKIN) continue;
|
|
225
|
+
report(c, { x: 1, y: 0 });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (clamped !== newX) vx = 0;
|
|
230
|
+
appliedX = clamped - body.x;
|
|
231
|
+
body = { ...body, x: clamped };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// --- Y axis sweep ---
|
|
235
|
+
{
|
|
236
|
+
const dy = intendedY;
|
|
237
|
+
if (dy !== 0) {
|
|
238
|
+
const newY = body.y + dy;
|
|
239
|
+
let clamped = newY;
|
|
240
|
+
const prevBottomY = body.y + body.height;
|
|
241
|
+
const prevTop = body.y;
|
|
242
|
+
const xOverlap = (aabb: Aabb2D): boolean =>
|
|
243
|
+
body.x < aabb.x + aabb.width && body.x + body.width > aabb.x;
|
|
244
|
+
const candidates = cellsInSweptBounds(map, body, { x: 0, y: dy }, collisionLayers);
|
|
245
|
+
interface FloorCandidate { cell: TileCell2D }
|
|
246
|
+
const floors: FloorCandidate[] = [];
|
|
247
|
+
const ceilings: TileCell2D[] = [];
|
|
248
|
+
for (const c of candidates) {
|
|
249
|
+
const solid = isSolid(c.collision);
|
|
250
|
+
const oneWay = isOneWay(c.collision);
|
|
251
|
+
if (!solid && !oneWay) continue;
|
|
252
|
+
if (!xOverlap(c.aabb)) continue;
|
|
253
|
+
if (dy > 0) {
|
|
254
|
+
// Descending: floor candidate.
|
|
255
|
+
if (oneWay && (dropThrough || prevBottomY > c.aabb.y + ONE_WAY_TOLERANCE)) continue;
|
|
256
|
+
// Swept crossing: started above/on the top and reached past it.
|
|
257
|
+
if (prevBottomY <= c.aabb.y + PLATFORMER_SKIN && newY + body.height >= c.aabb.y) {
|
|
258
|
+
floors.push({ cell: c });
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
// Ascending: only solids block ceilings.
|
|
262
|
+
if (!solid) continue;
|
|
263
|
+
if (
|
|
264
|
+
prevTop >= c.aabb.y + c.aabb.height - PLATFORMER_SKIN &&
|
|
265
|
+
newY <= c.aabb.y + c.aabb.height
|
|
266
|
+
) {
|
|
267
|
+
ceilings.push(c);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (dy > 0 && floors.length > 0) {
|
|
272
|
+
// Nearest plane wins; report only tiles touching it.
|
|
273
|
+
let planeY = Number.POSITIVE_INFINITY;
|
|
274
|
+
for (const f of floors) {
|
|
275
|
+
if (f.cell.aabb.y < planeY) planeY = f.cell.aabb.y;
|
|
276
|
+
}
|
|
277
|
+
clamped = Math.min(clamped, planeY - body.height);
|
|
278
|
+
for (const f of floors) {
|
|
279
|
+
if (f.cell.aabb.y !== planeY) continue;
|
|
280
|
+
report(f.cell, { x: 0, y: -1 });
|
|
281
|
+
}
|
|
282
|
+
} else if (dy < 0 && ceilings.length > 0) {
|
|
283
|
+
let planeBottom = Number.NEGATIVE_INFINITY;
|
|
284
|
+
for (const c of ceilings) {
|
|
285
|
+
if (c.aabb.y + c.aabb.height > planeBottom) planeBottom = c.aabb.y + c.aabb.height;
|
|
286
|
+
}
|
|
287
|
+
clamped = Math.max(clamped, planeBottom);
|
|
288
|
+
for (const c of ceilings) {
|
|
289
|
+
if (Math.abs(c.aabb.y + c.aabb.height - planeBottom) > PLATFORMER_SKIN) continue;
|
|
290
|
+
report(c, { x: 0, y: 1 });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (clamped !== newY) vy = 0;
|
|
294
|
+
appliedY = clamped - body.y;
|
|
295
|
+
body = { ...body, y: clamped };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// --- Floor snap ---
|
|
300
|
+
// GS-TILE-03: drop-through suppresses one-way floors without disabling
|
|
301
|
+
// snap onto solid floors.
|
|
302
|
+
{
|
|
303
|
+
const hasFloor = contacts.some((c) => c.normal.y === -1);
|
|
304
|
+
if (!hasFloor && vy >= 0 && snapDistance > 0) {
|
|
305
|
+
const probeBottom = body.y + body.height + snapDistance;
|
|
306
|
+
const probe: Aabb2D = { x: body.x, y: body.y + body.height, width: body.width, height: snapDistance };
|
|
307
|
+
const near = cellsInAabb(map, probe, collisionLayers);
|
|
308
|
+
let bestTop = Number.POSITIVE_INFINITY;
|
|
309
|
+
let bestCell: TileCell2D | undefined;
|
|
310
|
+
for (const c of near) {
|
|
311
|
+
const top = c.aabb.y;
|
|
312
|
+
if (top < body.y + body.height - PLATFORMER_SKIN) continue;
|
|
313
|
+
if (top > probeBottom) continue;
|
|
314
|
+
const ok = isSolid(c.collision) ||
|
|
315
|
+
(!dropThrough && isOneWay(c.collision) && prevBottom <= top + ONE_WAY_TOLERANCE);
|
|
316
|
+
if (!ok) continue;
|
|
317
|
+
if (top < bestTop) {
|
|
318
|
+
bestTop = top;
|
|
319
|
+
bestCell = c;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (bestCell !== undefined && bestTop < Number.POSITIVE_INFINITY) {
|
|
323
|
+
const snapDelta = bestTop - body.height - body.y;
|
|
324
|
+
body = { ...body, y: bestTop - body.height };
|
|
325
|
+
report(bestCell, { x: 0, y: -1 });
|
|
326
|
+
vy = 0;
|
|
327
|
+
appliedY += snapDelta;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const classified = classify(contacts);
|
|
333
|
+
return Object.freeze({
|
|
334
|
+
body: Object.freeze(body),
|
|
335
|
+
velocity: Object.freeze({ x: vx, y: vy }),
|
|
336
|
+
displacement: Object.freeze({ x: appliedX, y: appliedY }),
|
|
337
|
+
remainingDisplacement: Object.freeze({
|
|
338
|
+
x: intendedX - appliedX,
|
|
339
|
+
y: intendedY - appliedY,
|
|
340
|
+
}),
|
|
341
|
+
contacts: Object.freeze({
|
|
342
|
+
floor: classified.floor,
|
|
343
|
+
ceiling: classified.ceiling,
|
|
344
|
+
leftWall: classified.leftWall,
|
|
345
|
+
rightWall: classified.rightWall,
|
|
346
|
+
all: Object.freeze(contacts),
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function classify(contacts: PlatformerContact2D[]): {
|
|
352
|
+
floor: PlatformerContact2D | undefined;
|
|
353
|
+
ceiling: PlatformerContact2D | undefined;
|
|
354
|
+
leftWall: PlatformerContact2D | undefined;
|
|
355
|
+
rightWall: PlatformerContact2D | undefined;
|
|
356
|
+
} {
|
|
357
|
+
let floor: PlatformerContact2D | undefined;
|
|
358
|
+
let ceiling: PlatformerContact2D | undefined;
|
|
359
|
+
let leftWall: PlatformerContact2D | undefined;
|
|
360
|
+
let rightWall: PlatformerContact2D | undefined;
|
|
361
|
+
for (const c of contacts) {
|
|
362
|
+
if (c.normal.y === -1 && floor === undefined) floor = c;
|
|
363
|
+
else if (c.normal.y === 1 && ceiling === undefined) ceiling = c;
|
|
364
|
+
else if (c.normal.x === 1 && leftWall === undefined) leftWall = c;
|
|
365
|
+
else if (c.normal.x === -1 && rightWall === undefined) rightWall = c;
|
|
366
|
+
}
|
|
367
|
+
return { floor, ceiling, leftWall, rightWall };
|
|
368
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { Aabb2D, Point2D } from '../geometry/types';
|
|
2
|
+
import type { TileCell2D, TileMap2D } from './types';
|
|
3
|
+
import { forEachCellInSpan, makeCell } from './definitions';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bounded cell queries (T16.2, T16-F6).
|
|
7
|
+
*
|
|
8
|
+
* Deterministic order: layer order (map declaration), then row-major within
|
|
9
|
+
* a layer. Spans are clamped to the finite map before iteration; queries
|
|
10
|
+
* outside the map return an empty frozen array. All reads flow through the
|
|
11
|
+
* private chunk index — each overlapped 16x16 chunk region is fetched once.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function clampSpan(
|
|
15
|
+
layer: { width: number; height: number },
|
|
16
|
+
minCx: number, minCy: number, maxCx: number, maxCy: number,
|
|
17
|
+
): { x0: number; y0: number; x1: number; y1: number } | undefined {
|
|
18
|
+
const x0 = Math.max(0, minCx);
|
|
19
|
+
const y0 = Math.max(0, minCy);
|
|
20
|
+
const x1 = Math.min(layer.width - 1, maxCx);
|
|
21
|
+
const y1 = Math.min(layer.height - 1, maxCy);
|
|
22
|
+
if (x0 > x1 || y0 > y1) return undefined;
|
|
23
|
+
return { x0, y0, x1, y1 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function collectSpan(
|
|
27
|
+
map: TileMap2D,
|
|
28
|
+
layerId: string,
|
|
29
|
+
span: { x0: number; y0: number; x1: number; y1: number },
|
|
30
|
+
out: TileCell2D[],
|
|
31
|
+
): void {
|
|
32
|
+
forEachCellInSpan(map, layerId, span.x0, span.y0, span.x1, span.y1, (cx, cy, id) => {
|
|
33
|
+
out.push(makeCell(map, layerId, cx, cy, id));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** All non-empty cells in the given layers overlapping the point. */
|
|
38
|
+
export function cellsAtPoint(
|
|
39
|
+
map: TileMap2D,
|
|
40
|
+
point: Point2D,
|
|
41
|
+
layerIds?: readonly string[],
|
|
42
|
+
): readonly TileCell2D[] {
|
|
43
|
+
const out: TileCell2D[] = [];
|
|
44
|
+
const cx = Math.floor((point.x - map.origin.x) / map.cellSize.width);
|
|
45
|
+
const cy = Math.floor((point.y - map.origin.y) / map.cellSize.height);
|
|
46
|
+
for (const layer of map.layers) {
|
|
47
|
+
if (layerIds !== undefined && !layerIds.includes(layer.id)) continue;
|
|
48
|
+
const span = clampSpan(layer, cx, cy, cx, cy);
|
|
49
|
+
if (span === undefined) continue;
|
|
50
|
+
collectSpan(map, layer.id, span, out);
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze(out);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** All non-empty cells whose AABBs intersect the query AABB. */
|
|
56
|
+
export function cellsInAabb(
|
|
57
|
+
map: TileMap2D,
|
|
58
|
+
aabb: Aabb2D,
|
|
59
|
+
layerIds?: readonly string[],
|
|
60
|
+
): readonly TileCell2D[] {
|
|
61
|
+
const out: TileCell2D[] = [];
|
|
62
|
+
const minCx = Math.floor((aabb.x - map.origin.x) / map.cellSize.width);
|
|
63
|
+
const minCy = Math.floor((aabb.y - map.origin.y) / map.cellSize.height);
|
|
64
|
+
// GS-TILE-05: half-open tile areas, exact arithmetic — no shared epsilon.
|
|
65
|
+
// The maximum corner selects ceil(max/cell) - 1, so exact boundaries
|
|
66
|
+
// exclude the next cell and tiny positive extents still select their
|
|
67
|
+
// own. A zero/negative extent is a point query for the cell containing
|
|
68
|
+
// the minimum corner (documented divergence from inclusive contact).
|
|
69
|
+
const maxCx = maxCellIndex(aabb.x, aabb.width, map.origin.x, map.cellSize.width);
|
|
70
|
+
const maxCy = maxCellIndex(aabb.y, aabb.height, map.origin.y, map.cellSize.height);
|
|
71
|
+
for (const layer of map.layers) {
|
|
72
|
+
if (layerIds !== undefined && !layerIds.includes(layer.id)) continue;
|
|
73
|
+
const span = clampSpan(layer, minCx, minCy, maxCx, maxCy);
|
|
74
|
+
if (span === undefined) continue;
|
|
75
|
+
collectSpan(map, layer.id, span, out);
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze(out);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Last cell index covered by the half-open span [min, min + extent).
|
|
82
|
+
* Exact boundaries exclude the next cell; zero/negative extents fall back
|
|
83
|
+
* to the cell containing the minimum corner (point-query semantics).
|
|
84
|
+
*/
|
|
85
|
+
function maxCellIndex(min: number, extent: number, origin: number, cell: number): number {
|
|
86
|
+
if (!(min + extent > min)) {
|
|
87
|
+
return Math.floor((min - origin) / cell);
|
|
88
|
+
}
|
|
89
|
+
return Math.ceil((min + extent - origin) / cell) - 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Cells overlapped by the swept bounds of a body moving by `displacement`.
|
|
94
|
+
* The swept bounds are the union AABB of start and end positions — bounded,
|
|
95
|
+
* never unbounded even for very large displacements (clamped to the map).
|
|
96
|
+
*/
|
|
97
|
+
export function cellsInSweptBounds(
|
|
98
|
+
map: TileMap2D,
|
|
99
|
+
body: Aabb2D,
|
|
100
|
+
displacement: { x: number; y: number },
|
|
101
|
+
layerIds?: readonly string[],
|
|
102
|
+
): readonly TileCell2D[] {
|
|
103
|
+
const minX = Math.min(body.x, body.x + displacement.x);
|
|
104
|
+
const minY = Math.min(body.y, body.y + displacement.y);
|
|
105
|
+
return cellsInAabb(
|
|
106
|
+
map,
|
|
107
|
+
{ x: minX, y: minY, width: body.width + Math.abs(displacement.x), height: body.height + Math.abs(displacement.y) },
|
|
108
|
+
layerIds,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Non-empty cells inside the visible region (camera bounds + overscan).
|
|
114
|
+
* Same deterministic ordering as `cellsInAabb`.
|
|
115
|
+
*/
|
|
116
|
+
export function visibleCells(
|
|
117
|
+
map: TileMap2D,
|
|
118
|
+
visibleBounds: Aabb2D,
|
|
119
|
+
overscanCells: number,
|
|
120
|
+
layerIds?: readonly string[],
|
|
121
|
+
): readonly TileCell2D[] {
|
|
122
|
+
const out: TileCell2D[] = [];
|
|
123
|
+
const minCx = Math.floor((visibleBounds.x - map.origin.x) / map.cellSize.width) - overscanCells;
|
|
124
|
+
const minCy = Math.floor((visibleBounds.y - map.origin.y) / map.cellSize.height) - overscanCells;
|
|
125
|
+
const maxCx =
|
|
126
|
+
Math.ceil((visibleBounds.x + visibleBounds.width - map.origin.x) / map.cellSize.width) - 1 + overscanCells;
|
|
127
|
+
const maxCy =
|
|
128
|
+
Math.ceil((visibleBounds.y + visibleBounds.height - map.origin.y) / map.cellSize.height) - 1 + overscanCells;
|
|
129
|
+
for (const layer of map.layers) {
|
|
130
|
+
if (layerIds !== undefined && !layerIds.includes(layer.id)) continue;
|
|
131
|
+
const span = clampSpan(layer, minCx, minCy, maxCx, maxCy);
|
|
132
|
+
if (span === undefined) continue;
|
|
133
|
+
collectSpan(map, layer.id, span, out);
|
|
134
|
+
}
|
|
135
|
+
return Object.freeze(out);
|
|
136
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { tileError } from './errors';
|
|
2
|
+
import type { TileMapInput2D, TileSet2D } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Narrow Tiled JSON adapter (T16.5, hardened per T16-F6).
|
|
6
|
+
*
|
|
7
|
+
* Accepts ONLY the finite orthogonal subset used by the reference level:
|
|
8
|
+
* - `orientation: "orthogonal"`, `infinite: false`
|
|
9
|
+
* - `layers` of `type: "tilelayer"` with inline numeric `data` (no base64,
|
|
10
|
+
* no compression, no chunks)
|
|
11
|
+
* - EVERY other layer type (objectgroup, group, imagelayer, ...) is rejected
|
|
12
|
+
* with its exact source path — nothing silently disappears
|
|
13
|
+
* - Nonzero `offsetx`/`offsety` are rejected (v1 has no layer offsets)
|
|
14
|
+
* - No per-tile flips (high GID bits) — rejected with a source path
|
|
15
|
+
*
|
|
16
|
+
* The root value, options object, and every layer entry are validated BEFORE
|
|
17
|
+
* any property access, so malformed input always surfaces as a structured
|
|
18
|
+
* TileMapError instead of a TypeError. Raw Tiled JSON never enters
|
|
19
|
+
* fixed-step or rendering hot paths; the output is a normalized Gamekit map
|
|
20
|
+
* input.
|
|
21
|
+
*/
|
|
22
|
+
export interface TiledAdapterOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Map Tiled global tile ids to Gamekit tile names. The referenced names
|
|
25
|
+
* must exist on the provided tileset.
|
|
26
|
+
*/
|
|
27
|
+
readonly gidToTileName: Readonly<Record<number, string>>;
|
|
28
|
+
/** Layers to convert, in order. Defaults to every tilelayer. */
|
|
29
|
+
readonly layerIds?: readonly string[];
|
|
30
|
+
/** Layers that contribute collision; unlisted layers stay decorative. */
|
|
31
|
+
readonly collidableLayers?: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function reject(cond: boolean, path: string, message: string): void {
|
|
35
|
+
if (cond) throw tileError(path, message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertPlainObject(value: unknown, path: string): asserts value is Record<string, unknown> {
|
|
39
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
40
|
+
throw tileError(path, `expected an object; got ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseTiledMap2D(
|
|
45
|
+
json: unknown,
|
|
46
|
+
tileset: TileSet2D,
|
|
47
|
+
options: TiledAdapterOptions,
|
|
48
|
+
): TileMapInput2D {
|
|
49
|
+
// Validate inputs BEFORE any property access (T16-F6).
|
|
50
|
+
assertPlainObject(json, 'root');
|
|
51
|
+
assertPlainObject(options as unknown, 'options');
|
|
52
|
+
const gidMap = options.gidToTileName as Record<string, string> | null;
|
|
53
|
+
if (gidMap === null || typeof gidMap !== 'object' || Array.isArray(gidMap)) {
|
|
54
|
+
throw tileError('options.gidToTileName', 'must be an object mapping gids to tile names');
|
|
55
|
+
}
|
|
56
|
+
if (options.layerIds !== undefined) {
|
|
57
|
+
if (!Array.isArray(options.layerIds)) throw tileError('options.layerIds', 'must be an array of layer names');
|
|
58
|
+
for (let i = 0; i < options.layerIds.length; i++) {
|
|
59
|
+
if (typeof options.layerIds[i] !== 'string') {
|
|
60
|
+
throw tileError(`options.layerIds[${i}]`, 'must be a string');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (options.collidableLayers !== undefined) {
|
|
65
|
+
if (!Array.isArray(options.collidableLayers)) throw tileError('options.collidableLayers', 'must be an array of layer names');
|
|
66
|
+
for (let i = 0; i < options.collidableLayers.length; i++) {
|
|
67
|
+
if (typeof options.collidableLayers[i] !== 'string') {
|
|
68
|
+
throw tileError(`options.collidableLayers[${i}]`, 'must be a string');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (tileset === undefined || tileset.idOfName === undefined) {
|
|
73
|
+
throw tileError('tileset', 'must be a value produced by defineTileSet2D');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const root = json as Record<string, unknown>;
|
|
77
|
+
reject(root.orientation !== 'orthogonal', 'root.orientation', `only "orthogonal" is supported; got ${JSON.stringify(root.orientation)}`);
|
|
78
|
+
reject(root.infinite === true, 'root.infinite', 'infinite maps are not supported in v1');
|
|
79
|
+
const width = root.width;
|
|
80
|
+
const height = root.height;
|
|
81
|
+
reject(typeof width !== 'number' || !Number.isSafeInteger(width) || width <= 0, 'root.width', 'must be a positive integer');
|
|
82
|
+
reject(typeof height !== 'number' || !Number.isSafeInteger(height) || height <= 0, 'root.height', 'must be a positive integer');
|
|
83
|
+
const tw = root.tilewidth;
|
|
84
|
+
const th = root.tileheight;
|
|
85
|
+
reject(typeof tw !== 'number' || !Number.isFinite(tw) || tw <= 0, 'root.tilewidth', 'must be finite and > 0');
|
|
86
|
+
reject(typeof th !== 'number' || !Number.isFinite(th) || th <= 0, 'root.tileheight', 'must be finite and > 0');
|
|
87
|
+
|
|
88
|
+
reject(!Array.isArray(root.layers), 'root.layers', 'must be an array of layer objects');
|
|
89
|
+
const layersRaw = root.layers as unknown[];
|
|
90
|
+
const wanted = options.layerIds;
|
|
91
|
+
const collidableLayers = options.collidableLayers;
|
|
92
|
+
|
|
93
|
+
const layers: TileMapInput2D['layers'] = [];
|
|
94
|
+
layersRaw.forEach((rawLayerUnknown, index) => {
|
|
95
|
+
assertPlainObject(rawLayerUnknown, `layers[${index}]`);
|
|
96
|
+
const layer = rawLayerUnknown as Record<string, unknown>;
|
|
97
|
+
const rawType = layer.type;
|
|
98
|
+
reject(
|
|
99
|
+
typeof rawType !== 'string',
|
|
100
|
+
`layers[${index}].type`,
|
|
101
|
+
`must be a string layer type; got ${JSON.stringify(rawType)}`,
|
|
102
|
+
);
|
|
103
|
+
// T16-F6: tilelayer is the ONLY accepted type — every other layer type
|
|
104
|
+
// (objectgroup, group, imagelayer, unknown future types) is rejected
|
|
105
|
+
// with its exact source path instead of being silently skipped.
|
|
106
|
+
reject(
|
|
107
|
+
rawType !== 'tilelayer',
|
|
108
|
+
`layers[${index}](${JSON.stringify(String(layer.name ?? ''))}).type`,
|
|
109
|
+
`"${String(rawType)}" layers are not supported in v1`,
|
|
110
|
+
);
|
|
111
|
+
const name = typeof layer.name === 'string' && layer.name.length > 0 ? layer.name : `layer${index}`;
|
|
112
|
+
if (wanted !== undefined && !wanted.includes(name)) return;
|
|
113
|
+
const p = `layers[${index}](${JSON.stringify(name)})`;
|
|
114
|
+
// T16-F6: nonzero layer offsets change cell placement — rejected, not ignored.
|
|
115
|
+
reject(layer.offsetx !== undefined && layer.offsetx !== 0, `${p}.offsetx`, `nonzero layer offsets are not supported in v1; got ${JSON.stringify(layer.offsetx)}`);
|
|
116
|
+
reject(layer.offsety !== undefined && layer.offsety !== 0, `${p}.offsety`, `nonzero layer offsets are not supported in v1; got ${JSON.stringify(layer.offsety)}`);
|
|
117
|
+
reject(layer.encoding !== undefined && layer.encoding !== 'csv', `${p}.encoding`, `only inline CSV-free numeric arrays are supported; got ${JSON.stringify(layer.encoding)}`);
|
|
118
|
+
reject(layer.compression !== undefined, `${p}.compression`, 'compressed layers are not supported in v1');
|
|
119
|
+
reject(layer.chunks !== undefined, `${p}.chunks`, 'chunked (infinite) layers are not supported in v1');
|
|
120
|
+
reject(!Array.isArray(layer.data), `${p}.data`, 'must be an inline array of gids');
|
|
121
|
+
const layerWidth = layer.width;
|
|
122
|
+
const layerHeight = layer.height;
|
|
123
|
+
reject(layerWidth !== width, `${p}.width`, `must equal map width ${String(width)}`);
|
|
124
|
+
reject(layerHeight !== height, `${p}.height`, `must equal map height ${String(height)}`);
|
|
125
|
+
|
|
126
|
+
const gidData = layer.data as unknown[];
|
|
127
|
+
reject(gidData.length !== (width as number) * (height as number), `${p}.data`, `length must equal width*height (${String(width)}*${String(height)})`);
|
|
128
|
+
|
|
129
|
+
// Convert gids -> Gamekit ids. Flip bits unsupported.
|
|
130
|
+
const out: number[] = new Array(gidData.length);
|
|
131
|
+
for (let i = 0; i < gidData.length; i++) {
|
|
132
|
+
const rawUnknown = gidData[i];
|
|
133
|
+
const raw = typeof rawUnknown === 'number' ? rawUnknown : -1;
|
|
134
|
+
reject(!Number.isSafeInteger(raw) || raw < 0, `${p}.data[${i}]`, `must be a non-negative safe integer; got ${JSON.stringify(rawUnknown)}`);
|
|
135
|
+
if (raw === 0) {
|
|
136
|
+
out[i] = 0;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const FLIP_BITS = 0x80000000 | 0x40000000 | 0x20000000;
|
|
140
|
+
reject((raw & FLIP_BITS) !== 0, `${p}.data[${i}]`, 'per-tile flip/rotation flags are not supported in v1');
|
|
141
|
+
const mapped = gidMap[raw];
|
|
142
|
+
reject(mapped === undefined, `${p}.data[${i}]`, `gid ${raw} has no mapping in gidToTileName`);
|
|
143
|
+
const gamekitId = tileset.idOfName[mapped as string] ?? -1;
|
|
144
|
+
reject(gamekitId === -1, `${p}.data[${i}]`, `mapped tile name ${JSON.stringify(mapped)} is missing from the bound tileset`);
|
|
145
|
+
out[i] = gamekitId;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
layers.push({
|
|
149
|
+
id: name,
|
|
150
|
+
width: width as number,
|
|
151
|
+
height: height as number,
|
|
152
|
+
data: out,
|
|
153
|
+
collidable: collidableLayers ? collidableLayers.includes(name) : true,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
reject(layers.length === 0, 'layers', 'no tilelayers matched; at least one is required');
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
cellSize: { width: tw as number, height: th as number },
|
|
161
|
+
tileset,
|
|
162
|
+
layers,
|
|
163
|
+
origin: { x: 0, y: 0 },
|
|
164
|
+
};
|
|
165
|
+
}
|