1681-engine 0.5.8 → 0.5.9
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 +53 -0
- package/dist/1681-engine.js +463 -409
- package/dist/ambient/ColorGrade.js +0 -1
- package/dist/ambient/DayClock.js +0 -1
- package/dist/ambient/WeatherLayer.js +0 -1
- package/dist/ambient/grade.js +0 -1
- package/dist/ambient/weather.js +0 -1
- package/dist/anim/AnimStateMachine.js +0 -1
- package/dist/anim/Animator.js +0 -1
- package/dist/anim/animStateCompile.js +0 -1
- package/dist/anim/animStateSystem.js +0 -1
- package/dist/anim/animationSystem.js +0 -1
- package/dist/anim/asepriteSlices.js +0 -1
- package/dist/anim/asepriteTags.js +0 -1
- package/dist/anim/parseAseprite.js +0 -1
- package/dist/assets/AssetLoader.js +112 -14
- package/dist/assets/loadAseprite.js +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js +0 -1
- package/dist/assets/textureAtlas.js +58 -0
- package/dist/audio/Footsteps.js +0 -1
- package/dist/audio/MusicPlayer.js +0 -1
- package/dist/audio/SoundEngine.js +13 -7
- package/dist/audio/cleanTake.js +0 -1
- package/dist/audio/music/ElementVoice.js +0 -1
- package/dist/audio/music/GraphVoice.js +0 -1
- package/dist/audio/music/MusicChannel.js +0 -1
- package/dist/audio/music/musicVoice.js +0 -1
- package/dist/audio/sampleBank.js +0 -1
- package/dist/audio/soundTable.js +0 -1
- package/dist/audio/synth.js +0 -1
- package/dist/core/BitmapLabel.js +0 -1
- package/dist/core/Camera.js +1 -2
- package/dist/core/Clock.js +31 -21
- package/dist/core/Graphics.js +0 -1
- package/dist/core/Group.js +0 -1
- package/dist/core/Image.js +0 -1
- package/dist/core/ObjectFactory.js +0 -1
- package/dist/core/Pointer.js +46 -24
- package/dist/core/Rectangle.js +0 -1
- package/dist/core/Stage.js +5 -6
- package/dist/core/Text.js +0 -1
- package/dist/core/TextureRegistry.js +54 -24
- package/dist/core/Timeline.js +0 -1
- package/dist/core/TimelineBuilder.js +0 -1
- package/dist/core/TimelineChild.js +0 -1
- package/dist/core/Tweener.js +0 -1
- package/dist/core/atlas.js +0 -1
- package/dist/core/atlasPlan.js +0 -1
- package/dist/core/blendLayer.js +11 -0
- package/dist/core/eases.js +0 -1
- package/dist/core/gameObject.js +22 -20
- package/dist/core/pixelZoom.js +43 -0
- package/dist/core/pixiPatches.js +0 -1
- package/dist/core/runDue.js +28 -0
- package/dist/core/silhouette.js +49 -0
- package/dist/core/timelinePlace.js +0 -1
- package/dist/core/tweenProps.js +0 -1
- package/dist/debug/DebugOverlay.js +0 -1
- package/dist/debug/DrawCallCounter.js +15 -12
- package/dist/debug/ErrorLog.js +0 -1
- package/dist/debug/FrameStats.js +0 -1
- package/dist/debug/inspectWorld.js +0 -1
- package/dist/diagnostics/ErrorDeduper.js +0 -1
- package/dist/diagnostics/FrameSampler.js +0 -1
- package/dist/diagnostics/bootWatch.js +0 -1
- package/dist/diagnostics/captureErrors.js +0 -1
- package/dist/diagnostics/contextLoss.js +0 -1
- package/dist/diagnostics/frameStats.js +0 -1
- package/dist/diagnostics/gpuInfo.js +0 -1
- package/dist/diagnostics/renderGuard.js +0 -1
- package/dist/ecs/SparseSet.js +0 -1
- package/dist/ecs/World.js +0 -1
- package/dist/ecs/fields.js +0 -1
- package/dist/engine/Engine.js +42 -18
- package/dist/engine/FixedLoop.js +0 -1
- package/dist/engine/HiddenPause.js +27 -0
- package/dist/engine/Scene.js +0 -1
- package/dist/engine/SceneManager.js +0 -1
- package/dist/events/EventBus.js +0 -1
- package/dist/fx/Afterimages.js +82 -0
- package/dist/fx/FloatingText.js +0 -1
- package/dist/fx/hitStop.js +0 -1
- package/dist/fx/viewCull.js +0 -1
- package/dist/i18n/Translator.js +0 -1
- package/dist/i18n/extractStrings.js +0 -1
- package/dist/i18n/formatMessage.js +0 -1
- package/dist/input/Actions.js +29 -21
- package/dist/input/AimGesture.js +0 -1
- package/dist/input/Keyboard.js +0 -1
- package/dist/input/Keymap.js +0 -1
- package/dist/input/TargetLock.js +0 -1
- package/dist/input/aimAxis.js +0 -1
- package/dist/input/keyChord.js +0 -1
- package/dist/input/longPress.js +0 -1
- package/dist/input/rumble.js +42 -0
- package/dist/input/stickAxis.js +0 -1
- package/dist/input/touchDetect.js +0 -1
- package/dist/light/LightLayer.js +0 -1
- package/dist/nav/MinHeap.js +0 -1
- package/dist/nav/findPath.js +0 -1
- package/dist/nav/navGrid.js +0 -1
- package/dist/nav/nearestOpen.js +0 -1
- package/dist/nav/straightenPath.js +0 -1
- package/dist/net/Connection.js +39 -16
- package/dist/net/InterpolationBuffer.js +0 -1
- package/dist/net/PredictedMover.js +0 -1
- package/dist/net/Room.js +0 -1
- package/dist/net/RoomHost.js +0 -1
- package/dist/net/cadence.js +0 -1
- package/dist/net/constants.js +0 -1
- package/dist/net/lobby/LobbyClient.js +0 -1
- package/dist/net/lobby/LobbyHost.js +0 -1
- package/dist/net/lobby/LobbyRooms.js +0 -1
- package/dist/net/lobby/Matchmaker.js +0 -1
- package/dist/net/movement.js +40 -39
- package/dist/net/protocol.js +0 -1
- package/dist/net/reconnect.js +10 -0
- package/dist/net/serverClock.js +0 -1
- package/dist/net/snapshotCodec.js +0 -1
- package/dist/parallax/Parallax.js +0 -1
- package/dist/parallax/ParallaxLayer.js +0 -1
- package/dist/parallax/parallaxMath.js +0 -1
- package/dist/particles/Emitter.js +0 -1
- package/dist/particles/ParticleBudget.js +0 -1
- package/dist/particles/ParticleSim.js +0 -1
- package/dist/particles/particleCurves.js +0 -1
- package/dist/particles/particleSampling.js +0 -1
- package/dist/physics/Body.js +0 -1
- package/dist/physics/BroadPhase.js +0 -1
- package/dist/physics/Contact.js +0 -1
- package/dist/physics/ContactSolver.js +0 -1
- package/dist/physics/PhysicsWorld.js +0 -1
- package/dist/physics/SpatialHash.js +0 -1
- package/dist/physics/collide.js +0 -1
- package/dist/physics/collidePolygon.js +0 -1
- package/dist/physics/contactReport.js +0 -1
- package/dist/physics/drawColliders.js +0 -1
- package/dist/physics/physicsSystem.js +0 -1
- package/dist/physics/polygon.js +0 -1
- package/dist/physics/raycast.js +0 -1
- package/dist/physics/syncToBody.js +0 -1
- package/dist/physics/tileBodies.js +0 -1
- package/dist/physics/types.js +0 -1
- package/dist/pixel/Darkness.js +50 -47
- package/dist/pixel/DitherVignette.js +0 -1
- package/dist/pixel/Evaporation.js +0 -1
- package/dist/pixel/NightShade.js +0 -1
- package/dist/pixel/PixelLayer.js +0 -1
- package/dist/pixel/PixelMarks.js +99 -0
- package/dist/pixel/SwayProp.js +0 -1
- package/dist/pixel/aimPixels.js +67 -0
- package/dist/pixel/lights.js +0 -1
- package/dist/pixel/nineSlice.js +0 -1
- package/dist/pixel/paintLayer.js +0 -1
- package/dist/pixel/palette.js +0 -1
- package/dist/pixel/pixel.js +0 -1
- package/dist/pixel/pixelPaint.js +27 -24
- package/dist/pixel/pixelParticles.js +35 -0
- package/dist/pixel/pixelTone.js +0 -1
- package/dist/pixel/propKit.js +0 -1
- package/dist/pixel/ramp.js +0 -1
- package/dist/pixel/selout.js +0 -1
- package/dist/pixel/spritePixels.js +0 -1
- package/dist/pixel/swayRig.js +0 -1
- package/dist/pixel/vignetteMask.js +0 -1
- package/dist/pixel/weatherArt.js +0 -1
- package/dist/pixel.js +71 -65
- package/dist/platform/appResume.js +18 -0
- package/dist/platform/fullscreen.js +52 -0
- package/dist/platform/orientation.js +26 -0
- package/dist/platform/pointerLock.js +37 -0
- package/dist/platform/reducedMotion.js +23 -0
- package/dist/platform/timeoutSignal.js +14 -0
- package/dist/platform/touch.js +0 -1
- package/dist/postfx/BloomFilter.js +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js +0 -1
- package/dist/postfx/ColorAdjustFilter.js +0 -1
- package/dist/postfx/CrtFilter.js +0 -1
- package/dist/postfx/PixelateFilter.js +0 -1
- package/dist/postfx/PostFx.js +0 -1
- package/dist/postfx/ShockwaveFilter.js +0 -1
- package/dist/postfx/VignetteFilter.js +0 -1
- package/dist/postfx/fxAnimator.js +0 -1
- package/dist/postfx/fxShader.js +0 -1
- package/dist/postfx.js +0 -1
- package/dist/react/ChoiceDialog.js +0 -1
- package/dist/react/ConfirmDialog.js +0 -1
- package/dist/react/Glyph.js +0 -1
- package/dist/react/Modal.js +0 -1
- package/dist/react/Panel.js +0 -1
- package/dist/react/PixelImage.js +0 -1
- package/dist/react/Select.js +0 -1
- package/dist/react/Slider.js +0 -1
- package/dist/react/SplitPane.js +0 -1
- package/dist/react/TabStrip.js +0 -1
- package/dist/react/TitleTips.js +0 -1
- package/dist/react/Tooltip.js +0 -1
- package/dist/react/UiProvider.js +0 -1
- package/dist/react/baseCss.js +0 -1
- package/dist/react/choiceStore.js +0 -1
- package/dist/react/classNames.js +0 -1
- package/dist/react/confirmStore.js +0 -1
- package/dist/react/labels.js +0 -1
- package/dist/react/placement.js +0 -1
- package/dist/react/useFillSheet.js +0 -1
- package/dist/react/useFitToViewport.js +0 -1
- package/dist/react/usePicked.js +0 -1
- package/dist/react/useStore.js +0 -1
- package/dist/react/useViewport.js +0 -1
- package/dist/react.js +0 -1
- package/dist/rig/PosePlayer.js +0 -1
- package/dist/rig/RigView.js +0 -1
- package/dist/rig/Skeleton.js +0 -1
- package/dist/rig/humanoid.js +0 -1
- package/dist/rig/humanoid.json.js +0 -1
- package/dist/rig/humanoidPoses.js +0 -1
- package/dist/rig/poseMath.js +0 -1
- package/dist/rig/poses.json.js +0 -1
- package/dist/save/Autosaver.js +0 -1
- package/dist/save/MemoryBackend.js +0 -1
- package/dist/save/SaveStore.js +0 -1
- package/dist/save/WebStorageBackend.js +0 -1
- package/dist/save/checksum.js +0 -1
- package/dist/save/migrateSave.js +0 -1
- package/dist/settings.js +0 -1
- package/dist/tilemap/TilemapView.js +0 -1
- package/dist/tilemap/collision.js +0 -1
- package/dist/tilemap/tiled.js +0 -1
- package/dist/transitions/CrossfadeTransition.js +0 -1
- package/dist/transitions/DitherWipeTransition.js +0 -1
- package/dist/transitions/FadeTransition.js +0 -1
- package/dist/transitions/Letterbox.js +0 -1
- package/dist/transitions/ditherMask.js +0 -1
- package/dist/transitions/transitionTo.js +0 -1
- package/dist/transitions/tweenDone.js +0 -1
- package/dist/types/assets/AssetLoader.d.ts +63 -8
- package/dist/types/assets/AssetLoader.d.ts.map +1 -1
- package/dist/types/assets/textureAtlas.d.ts +35 -0
- package/dist/types/assets/textureAtlas.d.ts.map +1 -0
- package/dist/types/audio/SoundEngine.d.ts +5 -0
- package/dist/types/audio/SoundEngine.d.ts.map +1 -1
- package/dist/types/core/Camera.d.ts +1 -1
- package/dist/types/core/Camera.d.ts.map +1 -1
- package/dist/types/core/Clock.d.ts +14 -2
- package/dist/types/core/Clock.d.ts.map +1 -1
- package/dist/types/core/Pointer.d.ts +18 -0
- package/dist/types/core/Pointer.d.ts.map +1 -1
- package/dist/types/core/Stage.d.ts.map +1 -1
- package/dist/types/core/TextureRegistry.d.ts +39 -0
- package/dist/types/core/TextureRegistry.d.ts.map +1 -1
- package/dist/types/core/blendLayer.d.ts +23 -0
- package/dist/types/core/blendLayer.d.ts.map +1 -0
- package/dist/types/core/gameObject.d.ts.map +1 -1
- package/dist/types/core/pixelZoom.d.ts +45 -0
- package/dist/types/core/pixelZoom.d.ts.map +1 -0
- package/dist/types/core/runDue.d.ts +15 -0
- package/dist/types/core/runDue.d.ts.map +1 -0
- package/dist/types/core/silhouette.d.ts +16 -0
- package/dist/types/core/silhouette.d.ts.map +1 -0
- package/dist/types/debug/DrawCallCounter.d.ts.map +1 -1
- package/dist/types/engine/Engine.d.ts +16 -0
- package/dist/types/engine/Engine.d.ts.map +1 -1
- package/dist/types/engine/HiddenPause.d.ts +22 -0
- package/dist/types/engine/HiddenPause.d.ts.map +1 -0
- package/dist/types/fx/Afterimages.d.ts +62 -0
- package/dist/types/fx/Afterimages.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/input/Actions.d.ts +7 -1
- package/dist/types/input/Actions.d.ts.map +1 -1
- package/dist/types/input/rumble.d.ts +41 -0
- package/dist/types/input/rumble.d.ts.map +1 -0
- package/dist/types/net/Connection.d.ts +22 -0
- package/dist/types/net/Connection.d.ts.map +1 -1
- package/dist/types/net/constants.d.ts +13 -4
- package/dist/types/net/constants.d.ts.map +1 -1
- package/dist/types/net/movement.d.ts.map +1 -1
- package/dist/types/net/reconnect.d.ts +14 -0
- package/dist/types/net/reconnect.d.ts.map +1 -0
- package/dist/types/pixel/Darkness.d.ts +17 -5
- package/dist/types/pixel/Darkness.d.ts.map +1 -1
- package/dist/types/pixel/PixelMarks.d.ts +68 -0
- package/dist/types/pixel/PixelMarks.d.ts.map +1 -0
- package/dist/types/pixel/aimPixels.d.ts +23 -0
- package/dist/types/pixel/aimPixels.d.ts.map +1 -0
- package/dist/types/pixel/index.d.ts +3 -1
- package/dist/types/pixel/index.d.ts.map +1 -1
- package/dist/types/pixel/pixelPaint.d.ts +1 -0
- package/dist/types/pixel/pixelPaint.d.ts.map +1 -1
- package/dist/types/pixel/pixelParticles.d.ts +21 -0
- package/dist/types/pixel/pixelParticles.d.ts.map +1 -0
- package/dist/types/platform/appResume.d.ts +25 -0
- package/dist/types/platform/appResume.d.ts.map +1 -0
- package/dist/types/platform/fullscreen.d.ts +37 -0
- package/dist/types/platform/fullscreen.d.ts.map +1 -0
- package/dist/types/platform/orientation.d.ts +19 -0
- package/dist/types/platform/orientation.d.ts.map +1 -0
- package/dist/types/platform/pointerLock.d.ts +26 -0
- package/dist/types/platform/pointerLock.d.ts.map +1 -0
- package/dist/types/platform/reducedMotion.d.ts +20 -0
- package/dist/types/platform/reducedMotion.d.ts.map +1 -0
- package/dist/types/platform/timeoutSignal.d.ts +6 -0
- package/dist/types/platform/timeoutSignal.d.ts.map +1 -0
- package/dist/types/ui/index.d.ts +4 -0
- package/dist/types/ui/index.d.ts.map +1 -1
- package/dist/types/ui/rich/RichText.d.ts +48 -0
- package/dist/types/ui/rich/RichText.d.ts.map +1 -0
- package/dist/types/ui/rich/richFonts.d.ts +70 -0
- package/dist/types/ui/rich/richFonts.d.ts.map +1 -0
- package/dist/types/ui/rich/richLayout.d.ts +76 -0
- package/dist/types/ui/rich/richLayout.d.ts.map +1 -0
- package/dist/types/ui/rich/richMarkup.d.ts +48 -0
- package/dist/types/ui/rich/richMarkup.d.ts.map +1 -0
- package/dist/ui/Bar.js +0 -1
- package/dist/ui/Button.js +0 -1
- package/dist/ui/Dialog.js +0 -1
- package/dist/ui/FocusManager.js +0 -1
- package/dist/ui/Label.js +0 -1
- package/dist/ui/List.js +0 -1
- package/dist/ui/Panel.js +0 -1
- package/dist/ui/ScrollView.js +0 -1
- package/dist/ui/Slider.js +0 -1
- package/dist/ui/Stack.js +0 -1
- package/dist/ui/Toggle.js +0 -1
- package/dist/ui/UiHost.js +0 -1
- package/dist/ui/UiPointerGate.js +0 -1
- package/dist/ui/UiRoot.js +0 -1
- package/dist/ui/Widget.js +0 -1
- package/dist/ui/dom/DomOverlay.js +0 -1
- package/dist/ui/dom/DomWorldAnchors.js +0 -1
- package/dist/ui/dom/InputShield.js +0 -1
- package/dist/ui/dom/createStore.js +0 -1
- package/dist/ui/dom/shieldDecision.js +0 -1
- package/dist/ui/dom/worldToScreen.js +0 -1
- package/dist/ui/layout/anchor.js +0 -1
- package/dist/ui/layout/barDrain.js +0 -1
- package/dist/ui/layout/fitToViewport.js +0 -1
- package/dist/ui/layout/focusNav.js +0 -1
- package/dist/ui/layout/geometry.js +0 -1
- package/dist/ui/layout/listWidth.js +0 -1
- package/dist/ui/layout/placeTooltip.js +0 -1
- package/dist/ui/layout/scroll.js +0 -1
- package/dist/ui/layout/slider.js +0 -1
- package/dist/ui/layout/stack.js +0 -1
- package/dist/ui/layout/wrapRow.js +0 -1
- package/dist/ui/rich/RichText.js +87 -0
- package/dist/ui/rich/richFonts.js +105 -0
- package/dist/ui/rich/richLayout.js +132 -0
- package/dist/ui/rich/richMarkup.js +67 -0
- package/dist/ui.js +75 -65
- package/package.json +1 -4
- package/dist/1681-engine.js.map +0 -1
- package/dist/ambient/ColorGrade.js.map +0 -1
- package/dist/ambient/DayClock.js.map +0 -1
- package/dist/ambient/WeatherLayer.js.map +0 -1
- package/dist/ambient/grade.js.map +0 -1
- package/dist/ambient/weather.js.map +0 -1
- package/dist/anim/AnimStateMachine.js.map +0 -1
- package/dist/anim/Animator.js.map +0 -1
- package/dist/anim/animStateCompile.js.map +0 -1
- package/dist/anim/animStateSystem.js.map +0 -1
- package/dist/anim/animationSystem.js.map +0 -1
- package/dist/anim/asepriteSlices.js.map +0 -1
- package/dist/anim/asepriteTags.js.map +0 -1
- package/dist/anim/parseAseprite.js.map +0 -1
- package/dist/assets/AssetLoader.js.map +0 -1
- package/dist/assets/loadAseprite.js.map +0 -1
- package/dist/assets/takeCleanerWorker-DTcI_Jqr.js.map +0 -1
- package/dist/audio/Footsteps.js.map +0 -1
- package/dist/audio/MusicPlayer.js.map +0 -1
- package/dist/audio/SoundEngine.js.map +0 -1
- package/dist/audio/cleanTake.js.map +0 -1
- package/dist/audio/music/ElementVoice.js.map +0 -1
- package/dist/audio/music/GraphVoice.js.map +0 -1
- package/dist/audio/music/MusicChannel.js.map +0 -1
- package/dist/audio/music/musicVoice.js.map +0 -1
- package/dist/audio/sampleBank.js.map +0 -1
- package/dist/audio/soundTable.js.map +0 -1
- package/dist/audio/synth.js.map +0 -1
- package/dist/core/BitmapLabel.js.map +0 -1
- package/dist/core/Camera.js.map +0 -1
- package/dist/core/Clock.js.map +0 -1
- package/dist/core/Graphics.js.map +0 -1
- package/dist/core/Group.js.map +0 -1
- package/dist/core/Image.js.map +0 -1
- package/dist/core/ObjectFactory.js.map +0 -1
- package/dist/core/Pointer.js.map +0 -1
- package/dist/core/Rectangle.js.map +0 -1
- package/dist/core/Stage.js.map +0 -1
- package/dist/core/Text.js.map +0 -1
- package/dist/core/TextureRegistry.js.map +0 -1
- package/dist/core/Timeline.js.map +0 -1
- package/dist/core/TimelineBuilder.js.map +0 -1
- package/dist/core/TimelineChild.js.map +0 -1
- package/dist/core/Tweener.js.map +0 -1
- package/dist/core/atlas.js.map +0 -1
- package/dist/core/atlasPlan.js.map +0 -1
- package/dist/core/eases.js.map +0 -1
- package/dist/core/gameObject.js.map +0 -1
- package/dist/core/pixiPatches.js.map +0 -1
- package/dist/core/timelinePlace.js.map +0 -1
- package/dist/core/tweenProps.js.map +0 -1
- package/dist/debug/DebugOverlay.js.map +0 -1
- package/dist/debug/DrawCallCounter.js.map +0 -1
- package/dist/debug/ErrorLog.js.map +0 -1
- package/dist/debug/FrameStats.js.map +0 -1
- package/dist/debug/inspectWorld.js.map +0 -1
- package/dist/diagnostics/ErrorDeduper.js.map +0 -1
- package/dist/diagnostics/FrameSampler.js.map +0 -1
- package/dist/diagnostics/bootWatch.js.map +0 -1
- package/dist/diagnostics/captureErrors.js.map +0 -1
- package/dist/diagnostics/contextLoss.js.map +0 -1
- package/dist/diagnostics/frameStats.js.map +0 -1
- package/dist/diagnostics/gpuInfo.js.map +0 -1
- package/dist/diagnostics/renderGuard.js.map +0 -1
- package/dist/ecs/SparseSet.js.map +0 -1
- package/dist/ecs/World.js.map +0 -1
- package/dist/ecs/fields.js.map +0 -1
- package/dist/engine/Engine.js.map +0 -1
- package/dist/engine/FixedLoop.js.map +0 -1
- package/dist/engine/Scene.js.map +0 -1
- package/dist/engine/SceneManager.js.map +0 -1
- package/dist/events/EventBus.js.map +0 -1
- package/dist/fx/FloatingText.js.map +0 -1
- package/dist/fx/hitStop.js.map +0 -1
- package/dist/fx/viewCull.js.map +0 -1
- package/dist/i18n/Translator.js.map +0 -1
- package/dist/i18n/extractStrings.js.map +0 -1
- package/dist/i18n/formatMessage.js.map +0 -1
- package/dist/input/Actions.js.map +0 -1
- package/dist/input/AimGesture.js.map +0 -1
- package/dist/input/Keyboard.js.map +0 -1
- package/dist/input/Keymap.js.map +0 -1
- package/dist/input/TargetLock.js.map +0 -1
- package/dist/input/aimAxis.js.map +0 -1
- package/dist/input/keyChord.js.map +0 -1
- package/dist/input/longPress.js.map +0 -1
- package/dist/input/stickAxis.js.map +0 -1
- package/dist/input/touchDetect.js.map +0 -1
- package/dist/light/LightLayer.js.map +0 -1
- package/dist/nav/MinHeap.js.map +0 -1
- package/dist/nav/findPath.js.map +0 -1
- package/dist/nav/navGrid.js.map +0 -1
- package/dist/nav/nearestOpen.js.map +0 -1
- package/dist/nav/straightenPath.js.map +0 -1
- package/dist/net/Connection.js.map +0 -1
- package/dist/net/InterpolationBuffer.js.map +0 -1
- package/dist/net/PredictedMover.js.map +0 -1
- package/dist/net/Room.js.map +0 -1
- package/dist/net/RoomHost.js.map +0 -1
- package/dist/net/cadence.js.map +0 -1
- package/dist/net/constants.js.map +0 -1
- package/dist/net/lobby/LobbyClient.js.map +0 -1
- package/dist/net/lobby/LobbyHost.js.map +0 -1
- package/dist/net/lobby/LobbyRooms.js.map +0 -1
- package/dist/net/lobby/Matchmaker.js.map +0 -1
- package/dist/net/movement.js.map +0 -1
- package/dist/net/protocol.js.map +0 -1
- package/dist/net/serverClock.js.map +0 -1
- package/dist/net/snapshotCodec.js.map +0 -1
- package/dist/parallax/Parallax.js.map +0 -1
- package/dist/parallax/ParallaxLayer.js.map +0 -1
- package/dist/parallax/parallaxMath.js.map +0 -1
- package/dist/particles/Emitter.js.map +0 -1
- package/dist/particles/ParticleBudget.js.map +0 -1
- package/dist/particles/ParticleSim.js.map +0 -1
- package/dist/particles/particleCurves.js.map +0 -1
- package/dist/particles/particleSampling.js.map +0 -1
- package/dist/physics/Body.js.map +0 -1
- package/dist/physics/BroadPhase.js.map +0 -1
- package/dist/physics/Contact.js.map +0 -1
- package/dist/physics/ContactSolver.js.map +0 -1
- package/dist/physics/PhysicsWorld.js.map +0 -1
- package/dist/physics/SpatialHash.js.map +0 -1
- package/dist/physics/collide.js.map +0 -1
- package/dist/physics/collidePolygon.js.map +0 -1
- package/dist/physics/contactReport.js.map +0 -1
- package/dist/physics/drawColliders.js.map +0 -1
- package/dist/physics/physicsSystem.js.map +0 -1
- package/dist/physics/polygon.js.map +0 -1
- package/dist/physics/raycast.js.map +0 -1
- package/dist/physics/syncToBody.js.map +0 -1
- package/dist/physics/tileBodies.js.map +0 -1
- package/dist/physics/types.js.map +0 -1
- package/dist/pixel/Darkness.js.map +0 -1
- package/dist/pixel/DitherVignette.js.map +0 -1
- package/dist/pixel/Evaporation.js.map +0 -1
- package/dist/pixel/NightShade.js.map +0 -1
- package/dist/pixel/PixelLayer.js.map +0 -1
- package/dist/pixel/SwayProp.js.map +0 -1
- package/dist/pixel/lights.js.map +0 -1
- package/dist/pixel/nineSlice.js.map +0 -1
- package/dist/pixel/paintLayer.js.map +0 -1
- package/dist/pixel/palette.js.map +0 -1
- package/dist/pixel/pixel.js.map +0 -1
- package/dist/pixel/pixelPaint.js.map +0 -1
- package/dist/pixel/pixelTone.js.map +0 -1
- package/dist/pixel/propKit.js.map +0 -1
- package/dist/pixel/ramp.js.map +0 -1
- package/dist/pixel/selout.js.map +0 -1
- package/dist/pixel/spritePixels.js.map +0 -1
- package/dist/pixel/swayRig.js.map +0 -1
- package/dist/pixel/vignetteMask.js.map +0 -1
- package/dist/pixel/weatherArt.js.map +0 -1
- package/dist/pixel.js.map +0 -1
- package/dist/platform/touch.js.map +0 -1
- package/dist/postfx/BloomFilter.js.map +0 -1
- package/dist/postfx/ChromaticAberrationFilter.js.map +0 -1
- package/dist/postfx/ColorAdjustFilter.js.map +0 -1
- package/dist/postfx/CrtFilter.js.map +0 -1
- package/dist/postfx/PixelateFilter.js.map +0 -1
- package/dist/postfx/PostFx.js.map +0 -1
- package/dist/postfx/ShockwaveFilter.js.map +0 -1
- package/dist/postfx/VignetteFilter.js.map +0 -1
- package/dist/postfx/fxAnimator.js.map +0 -1
- package/dist/postfx/fxShader.js.map +0 -1
- package/dist/postfx.js.map +0 -1
- package/dist/react/ChoiceDialog.js.map +0 -1
- package/dist/react/ConfirmDialog.js.map +0 -1
- package/dist/react/Glyph.js.map +0 -1
- package/dist/react/Modal.js.map +0 -1
- package/dist/react/Panel.js.map +0 -1
- package/dist/react/PixelImage.js.map +0 -1
- package/dist/react/Select.js.map +0 -1
- package/dist/react/Slider.js.map +0 -1
- package/dist/react/SplitPane.js.map +0 -1
- package/dist/react/TabStrip.js.map +0 -1
- package/dist/react/TitleTips.js.map +0 -1
- package/dist/react/Tooltip.js.map +0 -1
- package/dist/react/UiProvider.js.map +0 -1
- package/dist/react/baseCss.js.map +0 -1
- package/dist/react/choiceStore.js.map +0 -1
- package/dist/react/classNames.js.map +0 -1
- package/dist/react/confirmStore.js.map +0 -1
- package/dist/react/labels.js.map +0 -1
- package/dist/react/placement.js.map +0 -1
- package/dist/react/useFillSheet.js.map +0 -1
- package/dist/react/useFitToViewport.js.map +0 -1
- package/dist/react/usePicked.js.map +0 -1
- package/dist/react/useStore.js.map +0 -1
- package/dist/react/useViewport.js.map +0 -1
- package/dist/react.js.map +0 -1
- package/dist/rig/PosePlayer.js.map +0 -1
- package/dist/rig/RigView.js.map +0 -1
- package/dist/rig/Skeleton.js.map +0 -1
- package/dist/rig/humanoid.js.map +0 -1
- package/dist/rig/humanoid.json.js.map +0 -1
- package/dist/rig/humanoidPoses.js.map +0 -1
- package/dist/rig/poseMath.js.map +0 -1
- package/dist/rig/poses.json.js.map +0 -1
- package/dist/save/Autosaver.js.map +0 -1
- package/dist/save/MemoryBackend.js.map +0 -1
- package/dist/save/SaveStore.js.map +0 -1
- package/dist/save/WebStorageBackend.js.map +0 -1
- package/dist/save/checksum.js.map +0 -1
- package/dist/save/migrateSave.js.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/tilemap/TilemapView.js.map +0 -1
- package/dist/tilemap/collision.js.map +0 -1
- package/dist/tilemap/tiled.js.map +0 -1
- package/dist/transitions/CrossfadeTransition.js.map +0 -1
- package/dist/transitions/DitherWipeTransition.js.map +0 -1
- package/dist/transitions/FadeTransition.js.map +0 -1
- package/dist/transitions/Letterbox.js.map +0 -1
- package/dist/transitions/ditherMask.js.map +0 -1
- package/dist/transitions/transitionTo.js.map +0 -1
- package/dist/transitions/tweenDone.js.map +0 -1
- package/dist/ui/Bar.js.map +0 -1
- package/dist/ui/Button.js.map +0 -1
- package/dist/ui/Dialog.js.map +0 -1
- package/dist/ui/FocusManager.js.map +0 -1
- package/dist/ui/Label.js.map +0 -1
- package/dist/ui/List.js.map +0 -1
- package/dist/ui/Panel.js.map +0 -1
- package/dist/ui/ScrollView.js.map +0 -1
- package/dist/ui/Slider.js.map +0 -1
- package/dist/ui/Stack.js.map +0 -1
- package/dist/ui/Toggle.js.map +0 -1
- package/dist/ui/UiHost.js.map +0 -1
- package/dist/ui/UiPointerGate.js.map +0 -1
- package/dist/ui/UiRoot.js.map +0 -1
- package/dist/ui/Widget.js.map +0 -1
- package/dist/ui/dom/DomOverlay.js.map +0 -1
- package/dist/ui/dom/DomWorldAnchors.js.map +0 -1
- package/dist/ui/dom/InputShield.js.map +0 -1
- package/dist/ui/dom/createStore.js.map +0 -1
- package/dist/ui/dom/shieldDecision.js.map +0 -1
- package/dist/ui/dom/worldToScreen.js.map +0 -1
- package/dist/ui/layout/anchor.js.map +0 -1
- package/dist/ui/layout/barDrain.js.map +0 -1
- package/dist/ui/layout/fitToViewport.js.map +0 -1
- package/dist/ui/layout/focusNav.js.map +0 -1
- package/dist/ui/layout/geometry.js.map +0 -1
- package/dist/ui/layout/listWidth.js.map +0 -1
- package/dist/ui/layout/placeTooltip.js.map +0 -1
- package/dist/ui/layout/scroll.js.map +0 -1
- package/dist/ui/layout/slider.js.map +0 -1
- package/dist/ui/layout/stack.js.map +0 -1
- package/dist/ui/layout/wrapRow.js.map +0 -1
- package/dist/ui.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LobbyHost.js","sources":["../../../src/net/lobby/LobbyHost.ts"],"sourcesContent":["import type { HostSocket } from '../RoomHost';\nimport { LobbyRooms, type LobbyRoom } from './LobbyRooms';\nimport type { LobbyClientMessage, LobbyErrorCode, LobbyServerMessage, MatchPlayer } from './lobbyProtocol';\nimport { Matchmaker, type FoundMatch, type MatchMode, type Ticket } from './Matchmaker';\n\n/** A match the lobby made (from the queue or a room's start), as `onStart` is given it. */\nexport interface LobbyMatch<D> {\n id: string;\n mode: string;\n players: (MatchPlayer & { data: D })[];\n /** The lobby room it was started from, for a room's own game. */\n room?: string;\n /** That room's settings. */\n settings?: unknown;\n}\n\nexport interface LobbyHostOptions<D> {\n /** Modes the queue fills (none: rooms only). */\n modes?: readonly MatchMode[];\n /** Turns a hello's data into the player's public data (validate it here); throw to refuse the player. */\n admit?: (data: unknown, id: number) => D;\n /** A player's rating in a mode (default 1000 for everyone). A party queues at its members' mean. */\n ratingOf?: (id: number, data: D, mode: string) => number;\n /**\n * Called when a match starts (everyone accepted, or a room's host started). Return what each player is told to go\n * to — a game server's address and a ticket, a room id on this server — as one value for all, or a function of\n * the player's id. Throw to cancel the start (everyone is told it was cancelled and requeued).\n */\n onStart?: (match: LobbyMatch<D>) => unknown;\n /** Time to accept a found match (default 15 s); 0 starts it at once. */\n readyCheckMs?: number;\n /** How long one who declined or let the check lapse is kept out of the queue (default 30 s). */\n declineLockMs?: number;\n /** Most players a room may hold (default 8), and rooms listed at once (default 50). */\n maxRoomPlayers?: number;\n listLimit?: number;\n /** Characters of a chat line (default 200) and of a room name (default 32). */\n chatMax?: number;\n nameMax?: number;\n /** Messages a player may send a second before they are refused (default 20); three times it closes the socket. */\n ratePerSecond?: number;\n /** How often the queue is searched (default 1000 ms). */\n matchEveryMs?: number;\n /** The game's own messages (anything the lobby does not handle). */\n onMessage?: (id: number, msg: { t: string; [k: string]: unknown }) => void;\n onJoin?: (id: number) => void;\n onLeave?: (id: number) => void;\n /** Clock and dice, for tests. */\n now?: () => number;\n random?: () => number;\n}\n\ninterface Member<D> {\n readonly id: number;\n data: D;\n /** The ticket it waits on alone (`p:<id>`), or its room's. */\n ticket: string | null;\n lockedUntil: number;\n match: string | null;\n rateWindow: number;\n rateCount: number;\n}\n\ninterface Pending {\n id: string;\n found: FoundMatch;\n players: MatchPlayer[];\n accepted: Set<number>;\n declined: Set<number>;\n expiresAt: number;\n}\n\n/**\n * The front room of an online game, transport-free like `RoomHost`: players say hello and browse rooms; a room has a\n * host, a code to share, ready flags, chat and the game's own settings; a player alone or a whole room queues for a\n * mode, the `Matchmaker` finds matches by rating, and each match goes through a ready check before `onStart` turns it\n * into a game. Someone who declines (or lets the check lapse) is locked out of the queue for a while; everyone else\n * goes back to their old place in it.\n *\n * ```ts\n * const lobby = new LobbyHost({\n * modes: [{ id: 'duel', teams: 2, teamSize: 1 }, { id: '2v2', teams: 2, teamSize: 2 }],\n * admit: (d) => ({ name: String((d as { name?: unknown })?.name ?? 'player').slice(0, 16) }),\n * ratingOf: (id) => ratings.get(id) ?? 1000,\n * onStart: (match) => ({ server: 'wss://eu1.example.com', ticket: issueTicket(match) }),\n * });\n * wss.on('connection', (ws) => {\n * const id = lobby.connect(ws);\n * ws.on('message', (data, binary) => lobby.message(id, binary ? '' : data.toString()));\n * ws.on('close', () => lobby.disconnect(id));\n * });\n * lobby.start();\n * ```\n */\nexport class LobbyHost<D = unknown> {\n readonly rooms: LobbyRooms;\n readonly matchmaker: Matchmaker;\n private readonly sockets = new Map<number, HostSocket>();\n private readonly members = new Map<number, Member<D>>();\n private readonly pending = new Map<string, Pending>();\n /** Rooms whose match started, by match id, so `finish` can open them again. */\n private readonly playing = new Map<string, string[]>();\n private nextId = 1;\n private nextMatch = 1;\n private listDirty = false;\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly now: () => number;\n\n constructor(private readonly options: LobbyHostOptions<D> = {}) {\n this.now = options.now ?? Date.now;\n this.rooms = new LobbyRooms(options.random);\n this.matchmaker = new Matchmaker(options.modes ?? []);\n }\n\n /** A socket connected; returns the id its messages and its close are reported under. */\n connect(socket: HostSocket): number {\n const id = this.nextId++;\n this.sockets.set(id, socket);\n return id;\n }\n\n /** Socket `id` closed: out of its room, the queue and any match being accepted. */\n disconnect(id: number): void {\n this.sockets.delete(id);\n const m = this.members.get(id);\n if (!m) return;\n this.leaveQueue(m, false);\n this.cancelPendingFor(id, 'left');\n this.leaveRoom(m);\n this.members.delete(id);\n this.options.onLeave?.(id);\n }\n\n /** A message arrived from socket `id`. */\n message(id: number, data: string | ArrayBuffer | Uint8Array): void {\n if (typeof data !== 'string') return;\n let msg: { t?: unknown; [k: string]: unknown };\n try {\n msg = JSON.parse(data);\n } catch {\n return;\n }\n if (!msg || typeof msg !== 'object' || typeof msg.t !== 'string') return;\n if (msg.t === 'hello') return this.hello(id, msg.data);\n if (msg.t === 'ping') {\n if (typeof msg.c === 'number' && Number.isFinite(msg.c)) this.send(id, { t: 'pong', c: msg.c, st: this.now() });\n return;\n }\n const m = this.members.get(id);\n if (!m || !this.allow(m)) return;\n const handled = this.handle(m, msg as LobbyClientMessage);\n if (!handled) this.options.onMessage?.(id, msg as { t: string; [k: string]: unknown });\n }\n\n /** A player's public data (what `admit` returned). */\n dataOf(id: number): D | undefined {\n return this.members.get(id)?.data;\n }\n\n /** Sends one of the game's own messages to a player. */\n send(id: number, msg: LobbyServerMessage | { t: string; [k: string]: unknown }): void {\n this.sockets.get(id)?.send(JSON.stringify(msg));\n }\n\n /** Searches the queue and runs the ready checks on a timer. */\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => this.update(), this.options.matchEveryMs ?? 1000);\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** One round: lapsed ready checks cancelled, the queue searched, the room list pushed if it changed. */\n update(now = this.now()): void {\n for (const p of [...this.pending.values()]) if (now >= p.expiresAt) this.cancel(p, 'timeout');\n for (const found of this.matchmaker.find(now)) this.found(found, now);\n if (this.listDirty) this.pushList();\n }\n\n /** A started match is over: its rooms open again, their members not ready. */\n finish(matchId: string): void {\n const rooms = this.playing.get(matchId);\n this.playing.delete(matchId);\n for (const r of rooms ?? []) {\n const room = this.rooms.get(r);\n if (!room) continue;\n room.playing = false;\n room.ready.clear();\n this.pushRoom(room);\n }\n this.listDirty = true;\n }\n\n // ── messages ─────────────────────────────────────────────────────────────────────────────────────────────────\n\n private hello(id: number, data: unknown): void {\n if (this.members.has(id)) return;\n let d: D;\n try {\n d = this.options.admit ? this.options.admit(data, id) : (data as D);\n } catch (err) {\n this.sockets.get(id)?.close(4003, err instanceof Error ? err.message : 'refused');\n return;\n }\n this.members.set(id, { id, data: d, ticket: null, lockedUntil: 0, match: null, rateWindow: 0, rateCount: 0 });\n this.send(id, { t: 'lobby/welcome', id, modes: (this.options.modes ?? []).map((m) => m.id) });\n this.send(id, { t: 'lobby/rooms', rooms: this.rooms.listed(this.options.listLimit ?? 50) });\n this.options.onJoin?.(id);\n }\n\n /** True when the lobby handled it (or refused it). */\n private handle(m: Member<D>, msg: LobbyClientMessage): boolean {\n switch (msg.t) {\n case 'lobby/list':\n this.send(m.id, { t: 'lobby/rooms', rooms: this.rooms.listed(this.options.listLimit ?? 50) });\n return true;\n case 'lobby/create':\n this.create(m, msg);\n return true;\n case 'lobby/join':\n this.join(m, msg);\n return true;\n case 'lobby/leave':\n if (!this.rooms.of(m.id)) this.error(m, 'not-in-room', 'You are not in a room');\n else {\n if (m.match) this.cancelPendingFor(m.id, 'left');\n this.leaveRoom(m);\n }\n return true;\n case 'lobby/ready':\n this.ready(m, msg.ready === true);\n return true;\n case 'lobby/kick':\n this.kick(m, msg.player);\n return true;\n case 'lobby/promote':\n this.promote(m, msg.player);\n return true;\n case 'lobby/settings': {\n const room = this.hostRoom(m);\n if (room && (room.queued || room.playing)) this.error(m, 'room-busy', 'Leave the queue first');\n else if (room) {\n room.settings = msg.settings;\n room.ready.clear(); // changed rules: everyone says again that they are ready\n this.pushRoom(room);\n }\n return true;\n }\n case 'lobby/chat': {\n const room = this.rooms.of(m.id);\n if (!room || typeof msg.text !== 'string') {\n this.error(m, 'not-in-room', 'You are not in a room');\n return true;\n }\n const text = msg.text.trim().slice(0, this.options.chatMax ?? 200);\n if (text) for (const id of room.members) this.send(id, { t: 'lobby/chat', from: m.id, text });\n return true;\n }\n case 'lobby/start':\n this.startRoom(m);\n return true;\n case 'queue/join':\n this.queue(m, msg.mode);\n return true;\n case 'queue/leave':\n this.leaveQueue(m, true);\n return true;\n case 'match/accept':\n this.answer(m, true);\n return true;\n case 'match/decline':\n this.answer(m, false);\n return true;\n default:\n return false;\n }\n }\n\n private create(m: Member<D>, msg: Extract<LobbyClientMessage, { t: 'lobby/create' }>): void {\n if (this.rooms.of(m.id)) return this.error(m, 'in-room', 'Leave your room first');\n if (m.ticket || m.match) return this.error(m, 'queued', 'Leave the queue first');\n const limit = this.options.maxRoomPlayers ?? 8;\n const max = Number.isInteger(msg.maxPlayers) ? Math.max(1, Math.min(limit, msg.maxPlayers!)) : limit;\n const modes = this.options.modes ?? [];\n const mode = typeof msg.mode === 'string' ? msg.mode.slice(0, 32) : (modes[0]?.id ?? 'custom');\n const name = typeof msg.name === 'string' && msg.name.trim() ? msg.name.trim().slice(0, this.options.nameMax ?? 32) : `Room ${this.rooms.count + 1}`;\n const room = this.rooms.create(m.id, { name, mode, maxPlayers: max, private: msg.private === true, settings: msg.settings ?? null });\n this.pushRoom(room);\n this.listDirty = true;\n }\n\n private join(m: Member<D>, msg: Extract<LobbyClientMessage, { t: 'lobby/join' }>): void {\n if (this.rooms.of(m.id)) return this.error(m, 'in-room', 'Leave your room first');\n if (m.ticket || m.match) return this.error(m, 'queued', 'Leave the queue first');\n const room = typeof msg.code === 'string' ? this.rooms.byInvite(msg.code) : typeof msg.room === 'string' ? this.rooms.get(msg.room) : undefined;\n // a private room is found only by its code\n if (!room || (room.private && typeof msg.code !== 'string')) return this.error(m, 'no-room', 'No such room');\n if (room.queued || room.playing) return this.error(m, 'room-busy', 'That room is busy');\n if (room.members.length >= room.maxPlayers) return this.error(m, 'room-full', 'That room is full');\n this.rooms.join(m.id, room);\n this.pushRoom(room);\n this.listDirty = true;\n }\n\n private ready(m: Member<D>, ready: boolean): void {\n const room = this.rooms.of(m.id);\n if (!room) return this.error(m, 'not-in-room', 'You are not in a room');\n if (room.queued || room.playing) return this.error(m, 'room-busy', 'Leave the queue first');\n if (ready) room.ready.add(m.id);\n else room.ready.delete(m.id);\n this.pushRoom(room);\n }\n\n private kick(m: Member<D>, player: unknown): void {\n const room = this.hostRoom(m);\n if (!room || typeof player !== 'number' || player === m.id || !room.members.includes(player)) return;\n const target = this.members.get(player);\n if (!target) return;\n if (room.queued) this.dequeueRoom(room);\n this.leaveRoom(target);\n this.send(player, { t: 'lobby/kicked' });\n }\n\n private promote(m: Member<D>, player: unknown): void {\n const room = this.hostRoom(m);\n if (!room || typeof player !== 'number' || !room.members.includes(player) || player === m.id) return;\n room.host = player;\n room.ready.delete(player);\n this.pushRoom(room);\n }\n\n /** The room `m` hosts, or an error to them. */\n private hostRoom(m: Member<D>): LobbyRoom | null {\n const room = this.rooms.of(m.id);\n if (!room) this.error(m, 'not-in-room', 'You are not in a room');\n else if (room.host !== m.id) this.error(m, 'not-host', 'Only the host can do that');\n else return room;\n return null;\n }\n\n private startRoom(m: Member<D>): void {\n const room = this.hostRoom(m);\n if (!room) return;\n if (room.queued || room.playing) return this.error(m, 'room-busy', 'That room is busy');\n if (!this.rooms.allReady(room)) return this.error(m, 'not-ready', 'Not everyone is ready');\n const id = `M${this.nextMatch++}`;\n const players: MatchPlayer[] = room.members.map((p) => ({ id: p, data: this.members.get(p)?.data, team: 0 }));\n room.playing = true;\n this.listDirty = true;\n if (!this.begin(id, room.mode, players, [room], room)) {\n room.playing = false;\n return;\n }\n this.pushRoom(room);\n }\n\n // ── the queue ────────────────────────────────────────────────────────────────────────────────────────────────\n\n private queue(m: Member<D>, modeId: unknown): void {\n const mode = typeof modeId === 'string' ? this.matchmaker.mode(modeId) : undefined;\n if (!mode) return this.error(m, 'no-mode', 'No such mode');\n if (m.match) return this.error(m, 'queued', 'A match is waiting for you');\n const room = this.rooms.of(m.id);\n const group = room ? room.members : [m.id];\n if (room) {\n if (room.host !== m.id) return this.error(m, 'not-host', 'Only the host can queue the room');\n if (room.queued || room.playing) return this.error(m, 'room-busy', 'The room is already queued');\n if (!this.rooms.allReady(room)) return this.error(m, 'not-ready', 'Not everyone is ready');\n } else if (m.ticket) return this.error(m, 'queued', 'You are already queued');\n if (group.length > mode.teamSize) return this.error(m, 'too-many', `A team holds ${mode.teamSize}`);\n const now = this.now();\n const locked = Math.max(...group.map((id) => this.members.get(id)?.lockedUntil ?? 0));\n if (locked > now) {\n this.send(m.id, { t: 'queue/locked', until: locked });\n return this.error(m, 'locked', 'Someone in your group declined a match recently');\n }\n const ticket: Ticket = { id: room ? `r:${room.id}` : `p:${m.id}`, mode: mode.id, members: [...group], rating: this.ratingOf(group, mode.id), since: now };\n this.matchmaker.add(ticket);\n if (room) {\n room.queued = true;\n this.pushRoom(room);\n this.listDirty = true;\n }\n for (const id of group) {\n const member = this.members.get(id);\n if (member) member.ticket = ticket.id;\n }\n this.pushQueue(ticket);\n }\n\n private ratingOf(group: readonly number[], mode: string): number {\n const of = this.options.ratingOf;\n if (!of) return 1000;\n let sum = 0;\n for (const id of group) sum += of(id, this.members.get(id)!.data, mode);\n return sum / group.length;\n }\n\n /** Takes `m` (or its whole room) out of the queue. */\n private leaveQueue(m: Member<D>, tell: boolean): void {\n if (!m.ticket) {\n if (tell) this.error(m, 'queued', 'You are not queued');\n return;\n }\n const room = this.rooms.of(m.id);\n if (room?.queued) return this.dequeueRoom(room);\n this.matchmaker.remove(m.ticket);\n m.ticket = null;\n if (tell) this.send(m.id, { t: 'queue/status', mode: null, since: 0, waiting: 0 });\n }\n\n private dequeueRoom(room: LobbyRoom): void {\n this.matchmaker.remove(`r:${room.id}`);\n room.queued = false;\n for (const id of room.members) {\n const member = this.members.get(id);\n if (member) member.ticket = null;\n this.send(id, { t: 'queue/status', mode: null, since: 0, waiting: 0 });\n }\n this.pushRoom(room);\n this.listDirty = true;\n }\n\n private pushQueue(t: Ticket): void {\n const waiting = this.matchmaker.waiting(t.mode).length - 1;\n for (const id of t.members) this.send(id, { t: 'queue/status', mode: t.mode, since: t.since, waiting });\n }\n\n // ── found matches and the ready check ───────────────────────────────────────────────────────────────────────\n\n private found(found: FoundMatch, now: number): void {\n const id = `M${this.nextMatch++}`;\n const players: MatchPlayer[] = found.teams.flatMap((team, i) => team.map((p) => ({ id: p, data: this.members.get(p)?.data, team: i })));\n for (const p of players) {\n const member = this.members.get(p.id);\n if (member) {\n member.ticket = null;\n member.match = id;\n }\n }\n const readyMs = this.options.readyCheckMs ?? 15_000;\n if (readyMs <= 0) {\n this.startFound(id, found, players);\n return;\n }\n const pending: Pending = { id, found, players, accepted: new Set(), declined: new Set(), expiresAt: now + readyMs };\n this.pending.set(id, pending);\n for (const p of players) this.send(p.id, { t: 'match/found', match: id, mode: found.mode, players, expiresAt: pending.expiresAt });\n }\n\n private answer(m: Member<D>, yes: boolean): void {\n const p = m.match ? this.pending.get(m.match) : undefined;\n if (!p) return;\n if (!yes) {\n p.declined.add(m.id);\n return this.cancel(p, 'declined');\n }\n p.accepted.add(m.id);\n const accepted = [...p.accepted];\n for (const pl of p.players) this.send(pl.id, { t: 'match/accepted', match: p.id, accepted });\n if (p.accepted.size === p.players.length) {\n this.pending.delete(p.id);\n this.startFound(p.id, p.found, p.players);\n }\n }\n\n private startFound(id: string, found: FoundMatch, players: MatchPlayer[]): void {\n const rooms = found.tickets.filter((t) => t.id.startsWith('r:')).map((t) => this.rooms.get(t.id.slice(2))).filter((r): r is LobbyRoom => !!r);\n for (const r of rooms) {\n r.queued = false;\n r.playing = true;\n }\n if (!this.begin(id, found.mode, players, rooms)) {\n for (const r of rooms) {\n r.playing = false;\n r.queued = true;\n }\n for (const t of found.tickets) this.requeue(t);\n return;\n }\n for (const r of rooms) this.pushRoom(r);\n this.listDirty = true;\n }\n\n /** Calls `onStart` and tells everyone where to go; false (nothing sent) when it threw. */\n private begin(id: string, mode: string, players: MatchPlayer[], rooms: LobbyRoom[], from?: LobbyRoom): boolean {\n let data: unknown = null;\n try {\n const match: LobbyMatch<D> = { id, mode, players: players as LobbyMatch<D>['players'] };\n if (from) {\n match.room = from.id;\n match.settings = from.settings;\n }\n data = this.options.onStart?.(match) ?? null;\n } catch (err) {\n for (const p of players) {\n const member = this.members.get(p.id);\n if (member) member.match = null;\n this.send(p.id, { t: 'match/cancelled', match: id, reason: 'left', requeued: !from });\n if (from) this.send(p.id, { t: 'lobby/error', code: 'bad-request', message: err instanceof Error ? err.message : 'could not start' });\n }\n return false;\n }\n if (rooms.length) this.playing.set(id, rooms.map((r) => r.id));\n for (const p of players) {\n const member = this.members.get(p.id);\n if (member) member.match = null;\n const mine = typeof data === 'function' ? (data as (id: number) => unknown)(p.id) : data;\n this.send(p.id, { t: 'match/start', match: id, mode, players, data: mine });\n }\n return true;\n }\n\n /** A ready check failed: whoever did not accept is locked out, every group that did goes back to its place. */\n private cancel(p: Pending, reason: 'declined' | 'timeout' | 'left'): void {\n this.pending.delete(p.id);\n const now = this.now();\n const lockMs = this.options.declineLockMs ?? 30_000;\n // on a decline only the decliners are to blame; on a lapse, everyone who had not accepted\n const blamed = new Set<number>(reason === 'timeout' ? p.players.map((pl) => pl.id).filter((id) => !p.accepted.has(id)) : p.declined);\n for (const t of p.found.tickets) {\n const guilty = t.members.some((id) => blamed.has(id) || !this.members.has(id));\n if (guilty) {\n for (const id of t.members) {\n const member = this.members.get(id);\n if (!member) continue;\n member.match = null;\n if (blamed.has(id)) member.lockedUntil = now + lockMs;\n if (blamed.has(id)) this.send(id, { t: 'queue/locked', until: member.lockedUntil });\n this.send(id, { t: 'match/cancelled', match: p.id, reason, requeued: false });\n }\n if (t.id.startsWith('r:')) {\n const room = this.rooms.get(t.id.slice(2));\n if (room) {\n room.queued = false;\n this.pushRoom(room);\n this.listDirty = true;\n }\n }\n continue;\n }\n this.requeue(t);\n for (const id of t.members) this.send(id, { t: 'match/cancelled', match: p.id, reason, requeued: true });\n }\n }\n\n /** Back into the queue at its old place (its old `since`). */\n private requeue(t: Ticket): void {\n this.matchmaker.add(t);\n for (const id of t.members) {\n const member = this.members.get(id);\n if (member) {\n member.match = null;\n member.ticket = t.id;\n }\n }\n this.pushQueue(t);\n }\n\n private cancelPendingFor(id: number, reason: 'left'): void {\n const m = this.members.get(id);\n const p = m?.match ? this.pending.get(m.match) : undefined;\n if (!p) return;\n p.declined.add(id);\n this.cancel(p, reason);\n }\n\n // ── rooms ────────────────────────────────────────────────────────────────────────────────────────────────────\n\n private leaveRoom(m: Member<D>): void {\n const room = this.rooms.of(m.id);\n if (!room) return;\n if (room.queued) this.dequeueRoom(room);\n const left = this.rooms.leave(m.id);\n this.send(m.id, { t: 'lobby/room', room: null });\n if (left && !left.closed) this.pushRoom(left.room);\n this.listDirty = true;\n }\n\n private pushRoom(room: LobbyRoom): void {\n if (!this.rooms.get(room.id)) return;\n const state = this.rooms.state(room, (id) => this.members.get(id)?.data);\n for (const id of room.members) this.send(id, { t: 'lobby/room', room: state });\n }\n\n /** The list, to everyone browsing (in no room). */\n private pushList(): void {\n this.listDirty = false;\n const rooms = this.rooms.listed(this.options.listLimit ?? 50);\n for (const id of this.members.keys()) if (!this.rooms.of(id)) this.send(id, { t: 'lobby/rooms', rooms });\n }\n\n private error(m: Member<D>, code: LobbyErrorCode, message: string): void {\n this.send(m.id, { t: 'lobby/error', code, message });\n }\n\n /** A per-second allowance; three times over closes the socket. */\n private allow(m: Member<D>): boolean {\n const now = this.now();\n if (now - m.rateWindow >= 1000) {\n m.rateWindow = now;\n m.rateCount = 0;\n }\n const limit = this.options.ratePerSecond ?? 20;\n if (++m.rateCount <= limit) return true;\n if (m.rateCount === limit + 1) this.error(m, 'rate', 'Slow down');\n if (m.rateCount > limit * 3) {\n this.sockets.get(m.id)?.close(4008, 'too many messages');\n this.disconnect(m.id);\n }\n return false;\n }\n}\n"],"names":["LobbyHost","options","LobbyRooms","Matchmaker","socket","id","m","data","msg","now","p","found","matchId","rooms","r","room","d","err","text","limit","max","modes","mode","name","ready","player","target","players","modeId","group","locked","ticket","member","of","sum","tell","waiting","team","i","readyMs","pending","yes","accepted","pl","t","from","match","mine","reason","lockMs","blamed","left","state","code","message"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LobbyRooms.js","sources":["../../../src/net/lobby/LobbyRooms.ts"],"sourcesContent":["import type { RoomMember, RoomState, RoomSummary } from './lobbyProtocol';\n\n/** A lobby room on the server. */\nexport interface LobbyRoom {\n readonly id: string;\n readonly code: string;\n name: string;\n mode: string;\n maxPlayers: number;\n private: boolean;\n host: number;\n /** In the order they came: the next host is the first left. */\n readonly members: number[];\n readonly ready: Set<number>;\n settings: unknown;\n /** In the matchmaking queue as one group. */\n queued: boolean;\n /** Its game is being played (or its match is being accepted): nobody joins until `LobbyHost.finish`. */\n playing: boolean;\n}\n\nconst CODE_LETTERS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';\n\n/**\n * The rooms of a lobby and who is in which, with no sockets: create, join by id or code, leave (the host's place\n * passing to the next member, an empty room closing), kick, ready. `LobbyHost` speaks for it.\n */\nexport class LobbyRooms {\n private readonly rooms = new Map<string, LobbyRoom>();\n private readonly byCode = new Map<string, LobbyRoom>();\n private readonly roomOf = new Map<number, LobbyRoom>();\n private nextId = 1;\n\n constructor(private readonly random: () => number = Math.random) {}\n\n get count(): number {\n return this.rooms.size;\n }\n\n get(id: string): LobbyRoom | undefined {\n return this.rooms.get(id);\n }\n\n byInvite(code: string): LobbyRoom | undefined {\n return this.byCode.get(code.toUpperCase());\n }\n\n of(player: number): LobbyRoom | undefined {\n return this.roomOf.get(player);\n }\n\n all(): IterableIterator<LobbyRoom> {\n return this.rooms.values();\n }\n\n create(host: number, o: { name: string; mode: string; maxPlayers: number; private: boolean; settings: unknown }): LobbyRoom {\n if (this.roomOf.has(host)) throw new Error('LobbyRooms: already in a room');\n let code = '';\n do code = Array.from({ length: 6 }, () => CODE_LETTERS[Math.floor(this.random() * CODE_LETTERS.length)]).join('');\n while (this.byCode.has(code));\n const room: LobbyRoom = { id: `R${this.nextId++}`, code, ...o, host, members: [host], ready: new Set(), queued: false, playing: false };\n this.rooms.set(room.id, room);\n this.byCode.set(code, room);\n this.roomOf.set(host, room);\n return room;\n }\n\n join(player: number, room: LobbyRoom): void {\n if (this.roomOf.has(player)) throw new Error('LobbyRooms: already in a room');\n room.members.push(player);\n this.roomOf.set(player, room);\n }\n\n /** Takes a player out of their room; returns the room (closed when it emptied, `closed` true). */\n leave(player: number): { room: LobbyRoom; closed: boolean } | null {\n const room = this.roomOf.get(player);\n if (!room) return null;\n this.roomOf.delete(player);\n const i = room.members.indexOf(player);\n if (i >= 0) room.members.splice(i, 1);\n room.ready.delete(player);\n if (room.members.length === 0) {\n this.rooms.delete(room.id);\n this.byCode.delete(room.code);\n return { room, closed: true };\n }\n if (room.host === player) {\n room.host = room.members[0]!;\n // a new host is not waiting on itself\n room.ready.delete(room.host);\n }\n return { room, closed: false };\n }\n\n /** Everyone but the host has said they are ready. */\n allReady(room: LobbyRoom): boolean {\n return room.members.every((m) => m === room.host || room.ready.has(m));\n }\n\n summary(room: LobbyRoom): RoomSummary {\n return { id: room.id, name: room.name, mode: room.mode, players: room.members.length, maxPlayers: room.maxPlayers, private: room.private, busy: room.queued || room.playing };\n }\n\n state(room: LobbyRoom, dataOf: (id: number) => unknown): RoomState {\n const members: RoomMember[] = room.members.map((id) => ({ id, data: dataOf(id), ready: id === room.host || room.ready.has(id) }));\n return { ...this.summary(room), code: room.code, host: room.host, members, settings: room.settings, queued: room.queued };\n }\n\n /** The rooms anyone may browse: public, open, not full; the fullest first, so players gather. */\n listed(limit: number): RoomSummary[] {\n return [...this.rooms.values()]\n .filter((r) => !r.private && !r.queued && !r.playing && r.members.length < r.maxPlayers)\n .sort((a, b) => b.members.length - a.members.length || (a.id < b.id ? -1 : 1))\n .slice(0, limit)\n .map((r) => this.summary(r));\n }\n}\n"],"names":["CODE_LETTERS","LobbyRooms","random","id","code","player","host","o","room","i","m","dataOf","members","limit","r","a","b"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Matchmaker.js","sources":["../../../src/net/lobby/Matchmaker.ts"],"sourcesContent":["/** A mode the matchmaker fills: `teams` sides of `teamSize` players. */\nexport interface MatchMode {\n id: string;\n teams: number;\n teamSize: number;\n /** Rating difference allowed at once (default 100), widened by `widenPerSecond` (default 10) up to `maxWindow` (default 600). */\n window?: number;\n widenPerSecond?: number;\n maxWindow?: number;\n}\n\n/** One group waiting together: a player alone, or a party that must not be split. */\nexport interface Ticket {\n id: string;\n mode: string;\n members: readonly number[];\n /** The group's rating (the game's own: Elo, a level, a power score). */\n rating: number;\n /** When it joined the queue (ms): the oldest waits least. */\n since: number;\n}\n\nexport interface FoundMatch {\n mode: string;\n tickets: Ticket[];\n /** Each team's player ids, in the order the teams are numbered. */\n teams: number[][];\n}\n\n/**\n * The matchmaking queue, pure: tickets in, matches out, no clock of its own and no sockets. Each `find(now)` takes\n * the oldest ticket of a mode as the anchor, gathers the groups within its rating window (and each within theirs,\n * so a newcomer is not thrown in with veterans because a veteran waited), and fills the teams from the closest\n * ratings first, never splitting a party; the teams are then balanced by rating. A window grows with the wait, so\n * anyone is matched in the end; a ticket nothing fits waits for the next call.\n */\nexport class Matchmaker {\n private readonly modes = new Map<string, MatchMode>();\n private readonly tickets = new Map<string, Ticket>();\n\n constructor(modes: readonly MatchMode[]) {\n for (const m of modes) {\n if (!(m.teams >= 1 && m.teamSize >= 1)) throw new Error(`Matchmaker: mode ${m.id} needs at least one team of one`);\n this.modes.set(m.id, m);\n }\n }\n\n get size(): number {\n return this.tickets.size;\n }\n\n mode(id: string): MatchMode | undefined {\n return this.modes.get(id);\n }\n\n /** Adds a ticket (replacing one of the same id); throws on an unknown mode or a group too big for a team. */\n add(ticket: Ticket): void {\n const m = this.modes.get(ticket.mode);\n if (!m) throw new Error(`Matchmaker: no mode ${ticket.mode}`);\n if (ticket.members.length < 1 || ticket.members.length > m.teamSize) throw new Error(`Matchmaker: a group of ${ticket.members.length} does not fit a team of ${m.teamSize}`);\n this.tickets.set(ticket.id, ticket);\n }\n\n remove(id: string): Ticket | undefined {\n const t = this.tickets.get(id);\n this.tickets.delete(id);\n return t;\n }\n\n get(id: string): Ticket | undefined {\n return this.tickets.get(id);\n }\n\n /** The tickets waiting for a mode, oldest first. */\n waiting(mode: string): Ticket[] {\n return [...this.tickets.values()].filter((t) => t.mode === mode).sort((a, b) => a.since - b.since);\n }\n\n /** How far a ticket's rating window reaches now. */\n windowOf(t: Ticket, now: number): number {\n const m = this.modes.get(t.mode)!;\n const waited = Math.max(0, now - t.since) / 1000;\n return Math.min(m.maxWindow ?? 600, (m.window ?? 100) + (m.widenPerSecond ?? 10) * waited);\n }\n\n /** Every match that can be made now; their tickets leave the queue. */\n find(now: number): FoundMatch[] {\n const out: FoundMatch[] = [];\n for (const mode of this.modes.values()) {\n const need = mode.teams * mode.teamSize;\n let progress = true;\n while (progress) {\n progress = false;\n const queue = this.waiting(mode.id);\n for (const anchor of queue) {\n const match = this.tryAnchor(anchor, queue, mode, need, now);\n if (!match) continue;\n for (const t of match.tickets) this.tickets.delete(t.id);\n out.push(match);\n progress = true;\n break;\n }\n }\n }\n return out;\n }\n\n private tryAnchor(anchor: Ticket, queue: Ticket[], mode: MatchMode, need: number, now: number): FoundMatch | null {\n const wa = this.windowOf(anchor, now);\n const near = queue\n .filter((t) => t !== anchor && Math.abs(t.rating - anchor.rating) <= Math.min(wa, this.windowOf(t, now)))\n .sort((a, b) => Math.abs(a.rating - anchor.rating) - Math.abs(b.rating - anchor.rating) || a.since - b.since);\n const picked = pickGroups([anchor], near, need);\n if (!picked) return null;\n const teams = balanceTeams(picked, mode.teams, mode.teamSize);\n if (!teams) return null;\n return { mode: mode.id, tickets: picked, teams };\n }\n}\n\n/** Groups from `pool` (closest first) added to `start` until they hold exactly `need` players; a small search, not greedy, so a party of three is not shut out by two singles taken first. */\nfunction pickGroups(start: Ticket[], pool: Ticket[], need: number): Ticket[] | null {\n const have = start.reduce((n, t) => n + t.members.length, 0);\n if (have === need) return start;\n if (have > need) return null;\n const chosen: Ticket[] = [];\n let budget = 2000;\n const search = (from: number, left: number): boolean => {\n if (left === 0) return true;\n if (--budget < 0) return false;\n for (let i = from; i < pool.length; i++) {\n const t = pool[i]!;\n if (t.members.length > left) continue;\n chosen.push(t);\n if (search(i + 1, left - t.members.length)) return true;\n chosen.pop();\n }\n return false;\n };\n return search(0, need - have) ? [...start, ...chosen] : null;\n}\n\n/**\n * Splits groups into `teams` of exactly `size` players, parties whole, ratings as even as it can: the largest groups\n * first, each into the team with room whose rating total is lowest. Returns null when the sizes cannot be packed.\n */\nexport function balanceTeams(groups: readonly Ticket[], teams: number, size: number): number[][] | null {\n const sorted = [...groups].sort((a, b) => b.members.length - a.members.length || b.rating - a.rating);\n const sides = Array.from({ length: teams }, () => ({ ids: [] as number[], weight: 0 }));\n const place = (i: number): boolean => {\n if (i === sorted.length) return true;\n const g = sorted[i]!;\n const order = [...sides.keys()].sort((a, b) => sides[a]!.weight - sides[b]!.weight || sides[a]!.ids.length - sides[b]!.ids.length);\n for (const s of order) {\n const side = sides[s]!;\n if (side.ids.length + g.members.length > size) continue;\n side.ids.push(...g.members);\n side.weight += g.rating * g.members.length;\n if (place(i + 1)) return true;\n side.ids.length -= g.members.length;\n side.weight -= g.rating * g.members.length;\n }\n return false;\n };\n return place(0) ? sides.map((s) => s.ids) : null;\n}\n"],"names":["Matchmaker","modes","m","id","ticket","mode","a","b","t","now","waited","out","need","progress","queue","anchor","match","wa","near","picked","pickGroups","teams","balanceTeams","start","pool","have","n","chosen","budget","search","from","left","i","groups","size","sorted","sides","place","g","order","s","side"],"mappings":"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"}
|
package/dist/net/movement.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"movement.js","sources":["../../src/net/movement.ts"],"sourcesContent":["import {\n BODY_HALF_HEIGHT,\n BODY_HALF_WIDTH,\n COLLISION_EPSILON,\n INPUT_FLAG_DASH,\n INPUT_STEP_DT,\n type InputStep,\n type MovementParams,\n} from './constants';\n\n/*\n * Deterministic player movement simulation.\n *\n * The server runs this exact algorithm (or a port of it) as the authority;\n * the client runs it for prediction and replays unacknowledged steps after every snapshot.\n * Keep the operation order identical on both sides so floating point results match bit for bit.\n */\n\n/** 1/sqrt(2), written as a literal so every port uses the exact same double. */\nexport const DIAGONAL = 0.7071067811865476;\n\nexport interface CollisionGrid {\n readonly width: number;\n readonly height: number;\n readonly tileSize: number;\n /** 1 = solid, row-major. */\n readonly solid: Uint8Array;\n /** 1 = a solid tile a shot flies over (`shotOverTiles`), row-major; absent when the ground was not given. */\n readonly shotOver?: Uint8Array;\n /** Half the moving body's collision box, px. */\n readonly bodyHalfWidth: number;\n readonly bodyHalfHeight: number;\n}\n\nexport interface CollisionGridOptions {\n /** The tile id of every cell, row-major; needed only for `shotOverTiles`. */\n ground?: readonly number[];\n /** Tile ids nothing walks but a shot flies over (water, lava…): solid for bodies, open for shots. */\n shotOverTiles?: Iterable<number>;\n /** Half the moving body's collision box, px (default `BODY_HALF_WIDTH` × `BODY_HALF_HEIGHT`, a 10×6 foot box). */\n bodyHalfWidth?: number;\n bodyHalfHeight?: number;\n}\n\nexport interface MoverState {\n x: number;\n y: number;\n /** Last non-zero movement direction (normalized), used for dashing while idle. */\n lastDx: number;\n lastDy: number;\n dashLeft: number;\n dashCd: number;\n dashDx: number;\n dashDy: number;\n}\n\n/** A grid from a string of '0'/'1' per cell (row-major, '1' solid). */\nexport function createCollisionGrid(width: number, height: number, tileSize: number, solid: string, options: CollisionGridOptions = {}): CollisionGrid {\n if (solid.length !== width * height) {\n throw new Error(`collision string length ${solid.length} != ${width}x${height}`);\n }\n const bytes = new Uint8Array(width * height);\n for (let i = 0; i < bytes.length; i++) bytes[i] = solid.charCodeAt(i) === 49 ? 1 : 0;\n const { ground } = options;\n const over = new Set(options.shotOverTiles ?? []);\n let shotOver: Uint8Array | undefined;\n if (ground && ground.length === bytes.length && over.size) {\n shotOver = new Uint8Array(bytes.length);\n for (let i = 0; i < bytes.length; i++) shotOver[i] = bytes[i] && over.has(ground[i]!) ? 1 : 0;\n }\n const bodyHalfWidth = options.bodyHalfWidth ?? BODY_HALF_WIDTH;\n const bodyHalfHeight = options.bodyHalfHeight ?? BODY_HALF_HEIGHT;\n return shotOver ? { width, height, tileSize, solid: bytes, shotOver, bodyHalfWidth, bodyHalfHeight } : { width, height, tileSize, solid: bytes, bodyHalfWidth, bodyHalfHeight };\n}\n\n/** Whether a shot in flight stops on this tile: solid, and not ground a shot flies over (`shotOverTiles`). */\nexport function blocksShot(grid: CollisionGrid, tx: number, ty: number): boolean {\n if (!isSolidTile(grid, tx, ty)) return false;\n return grid.shotOver?.[ty * grid.width + tx] !== 1;\n}\n\nexport function createMover(x: number, y: number): MoverState {\n return { x, y, lastDx: 1, lastDy: 0, dashLeft: 0, dashCd: 0, dashDx: 1, dashDy: 0 };\n}\n\nexport function isSolidTile(grid: CollisionGrid, tx: number, ty: number): boolean {\n if (tx < 0 || ty < 0 || tx >= grid.width || ty >= grid.height) return true;\n return grid.solid[ty * grid.width + tx] === 1;\n}\n\n/** True if the body box centered at (x, y) overlaps a solid tile. */\nexport function bodyCollides(grid: CollisionGrid, x: number, y: number): boolean {\n const ts = grid.tileSize;\n const x0 = Math.floor((x - grid.bodyHalfWidth) / ts);\n const x1 = Math.floor((x + grid.bodyHalfWidth) / ts);\n const y0 = Math.floor((y - grid.bodyHalfHeight) / ts);\n const y1 = Math.floor((y + grid.bodyHalfHeight) / ts);\n for (let ty = y0; ty <= y1; ty++) {\n for (let tx = x0; tx <= x1; tx++) {\n if (isSolidTile(grid, tx, ty)) return true;\n }\n }\n return false;\n}\n\n/** Moves along X by dx, stopping flush against the first solid tile column. */\nexport function moveX(grid: CollisionGrid, x: number, y: number, dx: number): number {\n if (dx === 0) return x;\n const ts = grid.tileSize;\n const nx = x + dx;\n const y0 = Math.floor((y - grid.bodyHalfHeight) / ts);\n const y1 = Math.floor((y + grid.bodyHalfHeight) / ts);\n if (dx > 0) {\n const tx = Math.floor((nx + grid.bodyHalfWidth) / ts);\n for (let ty = y0; ty <= y1; ty++) {\n if (isSolidTile(grid, tx, ty)) return tx * ts - grid.bodyHalfWidth - COLLISION_EPSILON;\n }\n } else {\n const tx = Math.floor((nx - grid.bodyHalfWidth) / ts);\n for (let ty = y0; ty <= y1; ty++) {\n if (isSolidTile(grid, tx, ty)) return (tx + 1) * ts + grid.bodyHalfWidth + COLLISION_EPSILON;\n }\n }\n return nx;\n}\n\n/** Moves along Y by dy, stopping flush against the first solid tile row. */\nexport function moveY(grid: CollisionGrid, x: number, y: number, dy: number): number {\n if (dy === 0) return y;\n const ts = grid.tileSize;\n const ny = y + dy;\n const x0 = Math.floor((x - grid.bodyHalfWidth) / ts);\n const x1 = Math.floor((x + grid.bodyHalfWidth) / ts);\n if (dy > 0) {\n const ty = Math.floor((ny + grid.bodyHalfHeight) / ts);\n for (let tx = x0; tx <= x1; tx++) {\n if (isSolidTile(grid, tx, ty)) return ty * ts - grid.bodyHalfHeight - COLLISION_EPSILON;\n }\n } else {\n const ty = Math.floor((ny - grid.bodyHalfHeight) / ts);\n for (let tx = x0; tx <= x1; tx++) {\n if (isSolidTile(grid, tx, ty)) return (ty + 1) * ts + grid.bodyHalfHeight + COLLISION_EPSILON;\n }\n }\n return ny;\n}\n\nfunction clampAxis(v: number): number {\n return v > 0 ? 1 : v < 0 ? -1 : 0;\n}\n\n/** Advances the mover by one fixed input step. Mutates `s`. */\nexport function stepMover(s: MoverState, step: InputStep, p: MovementParams, grid: CollisionGrid): void {\n if (s.dashCd > 0) s.dashCd--;\n\n // A dodge roll is committed: while it runs, input neither steers it nor aims the next one.\n const rolling = s.dashLeft > 0;\n let mx = p.canMove && !rolling ? clampAxis(step[0]) : 0;\n let my = p.canMove && !rolling ? clampAxis(step[1]) : 0;\n if (mx !== 0 && my !== 0) {\n mx = mx * DIAGONAL;\n my = my * DIAGONAL;\n }\n if (mx !== 0 || my !== 0) {\n s.lastDx = mx;\n s.lastDy = my;\n }\n\n if ((step[2] & INPUT_FLAG_DASH) !== 0 && p.canDash && p.canMove && s.dashLeft === 0 && s.dashCd === 0) {\n s.dashLeft = p.dashSteps;\n s.dashCd = p.dashCooldownSteps;\n s.dashDx = s.lastDx;\n s.dashDy = s.lastDy;\n }\n\n let vx: number;\n let vy: number;\n if (s.dashLeft > 0) {\n vx = s.dashDx * p.dashSpeed;\n vy = s.dashDy * p.dashSpeed;\n s.dashLeft--;\n } else {\n vx = mx * p.speed;\n vy = my * p.speed;\n }\n\n s.x = moveX(grid, s.x, s.y, vx * INPUT_STEP_DT);\n s.y = moveY(grid, s.x, s.y, vy * INPUT_STEP_DT);\n}\n"],"names":["DIAGONAL","createCollisionGrid","width","height","tileSize","solid","options","bytes","i","ground","over","shotOver","bodyHalfWidth","BODY_HALF_WIDTH","bodyHalfHeight","BODY_HALF_HEIGHT","blocksShot","grid","tx","ty","isSolidTile","createMover","x","y","bodyCollides","ts","x0","x1","y0","y1","moveX","dx","nx","COLLISION_EPSILON","moveY","dy","ny","clampAxis","v","stepMover","s","step","p","rolling","mx","my","INPUT_FLAG_DASH","vx","vy","INPUT_STEP_DT"],"mappings":";AAmBO,MAAMA,IAAW;AAsCjB,SAASC,EAAoBC,GAAeC,GAAgBC,GAAkBC,GAAeC,IAAgC,IAAmB;AACrJ,MAAID,EAAM,WAAWH,IAAQC;AAC3B,UAAM,IAAI,MAAM,2BAA2BE,EAAM,MAAM,OAAOH,CAAK,IAAIC,CAAM,EAAE;AAEjF,QAAMI,IAAQ,IAAI,WAAWL,IAAQC,CAAM;AAC3C,WAASK,IAAI,GAAGA,IAAID,EAAM,QAAQC,IAAK,CAAAD,EAAMC,CAAC,IAAIH,EAAM,WAAWG,CAAC,MAAM,KAAK,IAAI;AACnF,QAAM,EAAE,QAAAC,MAAWH,GACbI,IAAO,IAAI,IAAIJ,EAAQ,iBAAiB,CAAA,CAAE;AAChD,MAAIK;AACJ,MAAIF,KAAUA,EAAO,WAAWF,EAAM,UAAUG,EAAK,MAAM;AACzD,IAAAC,IAAW,IAAI,WAAWJ,EAAM,MAAM;AACtC,aAASC,IAAI,GAAGA,IAAID,EAAM,QAAQC,OAAcA,CAAC,IAAID,EAAMC,CAAC,KAAKE,EAAK,IAAID,EAAOD,CAAC,CAAE,IAAI,IAAI;AAAA,EAC9F;AACA,QAAMI,IAAgBN,EAAQ,iBAAiBO,GACzCC,IAAiBR,EAAQ,kBAAkBS;AACjD,SAAOJ,IAAW,EAAE,OAAAT,GAAO,QAAAC,GAAQ,UAAAC,GAAU,OAAOG,GAAO,UAAAI,GAAU,eAAAC,GAAe,gBAAAE,MAAmB,EAAE,OAAAZ,GAAO,QAAAC,GAAQ,UAAAC,GAAU,OAAOG,GAAO,eAAAK,GAAe,gBAAAE,EAAA;AACjK;AAGO,SAASE,EAAWC,GAAqBC,GAAYC,GAAqB;AAC/E,SAAKC,EAAYH,GAAMC,GAAIC,CAAE,IACtBF,EAAK,WAAWE,IAAKF,EAAK,QAAQC,CAAE,MAAM,IADV;AAEzC;AAEO,SAASG,EAAYC,GAAWC,GAAuB;AAC5D,SAAO,EAAE,GAAAD,GAAG,GAAAC,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAAA;AAClF;AAEO,SAASH,EAAYH,GAAqBC,GAAYC,GAAqB;AAChF,SAAID,IAAK,KAAKC,IAAK,KAAKD,KAAMD,EAAK,SAASE,KAAMF,EAAK,SAAe,KAC/DA,EAAK,MAAME,IAAKF,EAAK,QAAQC,CAAE,MAAM;AAC9C;AAGO,SAASM,EAAaP,GAAqBK,GAAWC,GAAoB;AAC/E,QAAME,IAAKR,EAAK,UACVS,IAAK,KAAK,OAAOJ,IAAIL,EAAK,iBAAiBQ,CAAE,GAC7CE,IAAK,KAAK,OAAOL,IAAIL,EAAK,iBAAiBQ,CAAE,GAC7CG,IAAK,KAAK,OAAOL,IAAIN,EAAK,kBAAkBQ,CAAE,GAC9CI,IAAK,KAAK,OAAON,IAAIN,EAAK,kBAAkBQ,CAAE;AACpD,WAASN,IAAKS,GAAIT,KAAMU,GAAIV;AAC1B,aAASD,IAAKQ,GAAIR,KAAMS,GAAIT;AAC1B,UAAIE,EAAYH,GAAMC,GAAIC,CAAE,EAAG,QAAO;AAG1C,SAAO;AACT;AAGO,SAASW,EAAMb,GAAqBK,GAAWC,GAAWQ,GAAoB;AACnF,MAAIA,MAAO,EAAG,QAAOT;AACrB,QAAMG,IAAKR,EAAK,UACVe,IAAKV,IAAIS,GACTH,IAAK,KAAK,OAAOL,IAAIN,EAAK,kBAAkBQ,CAAE,GAC9CI,IAAK,KAAK,OAAON,IAAIN,EAAK,kBAAkBQ,CAAE;AACpD,MAAIM,IAAK,GAAG;AACV,UAAMb,IAAK,KAAK,OAAOc,IAAKf,EAAK,iBAAiBQ,CAAE;AACpD,aAASN,IAAKS,GAAIT,KAAMU,GAAIV;AAC1B,UAAIC,EAAYH,GAAMC,GAAIC,CAAE,EAAG,QAAOD,IAAKO,IAAKR,EAAK,gBAAgBgB;AAAA,EAEzE,OAAO;AACL,UAAMf,IAAK,KAAK,OAAOc,IAAKf,EAAK,iBAAiBQ,CAAE;AACpD,aAASN,IAAKS,GAAIT,KAAMU,GAAIV;AAC1B,UAAIC,EAAYH,GAAMC,GAAIC,CAAE,WAAWD,IAAK,KAAKO,IAAKR,EAAK,gBAAgBgB;AAAA,EAE/E;AACA,SAAOD;AACT;AAGO,SAASE,EAAMjB,GAAqBK,GAAWC,GAAWY,GAAoB;AACnF,MAAIA,MAAO,EAAG,QAAOZ;AACrB,QAAME,IAAKR,EAAK,UACVmB,IAAKb,IAAIY,GACTT,IAAK,KAAK,OAAOJ,IAAIL,EAAK,iBAAiBQ,CAAE,GAC7CE,IAAK,KAAK,OAAOL,IAAIL,EAAK,iBAAiBQ,CAAE;AACnD,MAAIU,IAAK,GAAG;AACV,UAAMhB,IAAK,KAAK,OAAOiB,IAAKnB,EAAK,kBAAkBQ,CAAE;AACrD,aAASP,IAAKQ,GAAIR,KAAMS,GAAIT;AAC1B,UAAIE,EAAYH,GAAMC,GAAIC,CAAE,EAAG,QAAOA,IAAKM,IAAKR,EAAK,iBAAiBgB;AAAA,EAE1E,OAAO;AACL,UAAMd,IAAK,KAAK,OAAOiB,IAAKnB,EAAK,kBAAkBQ,CAAE;AACrD,aAASP,IAAKQ,GAAIR,KAAMS,GAAIT;AAC1B,UAAIE,EAAYH,GAAMC,GAAIC,CAAE,WAAWA,IAAK,KAAKM,IAAKR,EAAK,iBAAiBgB;AAAA,EAEhF;AACA,SAAOG;AACT;AAEA,SAASC,EAAUC,GAAmB;AACpC,SAAOA,IAAI,IAAI,IAAIA,IAAI,IAAI,KAAK;AAClC;AAGO,SAASC,EAAUC,GAAeC,GAAiBC,GAAmBzB,GAA2B;AACtG,EAAIuB,EAAE,SAAS,KAAGA,EAAE;AAGpB,QAAMG,IAAUH,EAAE,WAAW;AAC7B,MAAII,IAAKF,EAAE,WAAW,CAACC,IAAUN,EAAUI,EAAK,CAAC,CAAC,IAAI,GAClDI,IAAKH,EAAE,WAAW,CAACC,IAAUN,EAAUI,EAAK,CAAC,CAAC,IAAI;AACtD,EAAIG,MAAO,KAAKC,MAAO,MACrBD,IAAKA,IAAK5C,GACV6C,IAAKA,IAAK7C,KAER4C,MAAO,KAAKC,MAAO,OACrBL,EAAE,SAASI,GACXJ,EAAE,SAASK,KAGRJ,EAAK,CAAC,IAAIK,OAAqB,KAAKJ,EAAE,WAAWA,EAAE,WAAWF,EAAE,aAAa,KAAKA,EAAE,WAAW,MAClGA,EAAE,WAAWE,EAAE,WACfF,EAAE,SAASE,EAAE,mBACbF,EAAE,SAASA,EAAE,QACbA,EAAE,SAASA,EAAE;AAGf,MAAIO,GACAC;AACJ,EAAIR,EAAE,WAAW,KACfO,IAAKP,EAAE,SAASE,EAAE,WAClBM,IAAKR,EAAE,SAASE,EAAE,WAClBF,EAAE,eAEFO,IAAKH,IAAKF,EAAE,OACZM,IAAKH,IAAKH,EAAE,QAGdF,EAAE,IAAIV,EAAMb,GAAMuB,EAAE,GAAGA,EAAE,GAAGO,IAAKE,CAAa,GAC9CT,EAAE,IAAIN,EAAMjB,GAAMuB,EAAE,GAAGA,EAAE,GAAGQ,IAAKC,CAAa;AAChD;"}
|
package/dist/net/protocol.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sources":["../../src/net/protocol.ts"],"sourcesContent":["import type { EntityUpdate, InputStep } from './constants';\nimport type { MoverState } from './movement';\n\n/*\n * The engine's netcode messages. JSON, but for snapshots that carry body updates, which travel as one binary frame\n * (`encodeSnapshotFrame`). A game adds its own messages beside these: anything with a `t` the engine does not know\n * is handed to the game untouched.\n */\n\n/** Client → server. */\nexport type ClientMessage =\n /** Asks to join; `data` is the game's own (a name, a character). */\n | { t: 'hello'; data?: unknown }\n /** Input steps `seq`, `seq + 1`, … taken since the last send. */\n | { t: 'input'; seq: number; steps: InputStep[] }\n /** A ping carrying the client's own clock, answered by a pong. */\n | { t: 'ping'; c: number };\n\n/** Server → client. */\nexport type ServerMessage =\n | { t: 'welcome'; id: number; tick: number; stepRate: number; snapRate: number }\n | { t: 'pong'; c: number; st: number }\n | Snapshot;\n\n/** A body's whole mover, so a client can rewind to it exactly: x, y, lastDx, lastDy, dashLeft, dashCd, dashDx, dashDy. */\nexport type MoverWire = [number, number, number, number, number, number, number, number];\n\n/** A body coming into view: its id and the game's own description of it (what it looks like). */\nexport interface SpawnInfo {\n id: number;\n data?: unknown;\n}\n\nexport interface Snapshot {\n t: 'snap';\n /** The server's step count when it was taken. */\n tick: number;\n /** The server's wall time when it was taken (ms since the epoch), for interpolation. */\n time: number;\n /** The last input step of this client the server has applied (-1 before any). */\n ack: number;\n /** This client's own body, whole, as of `ack`. */\n me?: MoverWire;\n /** Bodies that came into view. */\n sp?: SpawnInfo[];\n /** Bodies that left view. */\n rm?: number[];\n /** Every body in view: [id, x, y, aim, anim, hp, flags]. */\n up?: EntityUpdate[];\n}\n\nexport function moverToWire(m: MoverState): MoverWire {\n return [m.x, m.y, m.lastDx, m.lastDy, m.dashLeft, m.dashCd, m.dashDx, m.dashDy];\n}\n\nexport function moverFromWire(w: MoverWire, into: MoverState): MoverState {\n [into.x, into.y, into.lastDx, into.lastDy, into.dashLeft, into.dashCd, into.dashDx, into.dashDy] = w;\n return into;\n}\n"],"names":["moverToWire","m","moverFromWire","w","into"],"mappings":"AAmDO,SAASA,EAAYC,GAA0B;AACpD,SAAO,CAACA,EAAE,GAAGA,EAAE,GAAGA,EAAE,QAAQA,EAAE,QAAQA,EAAE,UAAUA,EAAE,QAAQA,EAAE,QAAQA,EAAE,MAAM;AAChF;AAEO,SAASC,EAAcC,GAAcC,GAA8B;AACxE,UAACA,EAAK,GAAGA,EAAK,GAAGA,EAAK,QAAQA,EAAK,QAAQA,EAAK,UAAUA,EAAK,QAAQA,EAAK,QAAQA,EAAK,MAAM,IAAID,GAC5FC;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serverClock.js","sources":["../../src/net/serverClock.ts"],"sourcesContent":["/*\n * The server's clock, as this client best knows it. Everything the game reckons from wall time — the time of day,\n * the weather, timetables, daily resets — reads `serverNow()` instead\n * of `Date.now()`, so a phone whose clock is a few minutes off still sees the sky and the resets everyone else sees.\n *\n * The offset comes from timed exchanges: every pong (the server's time when it answered, against the ping's own\n * round trip) and any request that returns the server's time. The sample with the shortest round trip wins, since its\n * midpoint is the tightest guess; a sample older than a minute gives way to the next one so a drifting clock is\n * followed. Until the first one lands this device's own clock stands in (a game clock counting from the server's start must never\n * seed this). An offline game is its own server, so there the offset is zero.\n */\n\n/** A timed sample this old gives way to a new one, even a slower one. */\nconst STALE_MS = 60_000;\n/** A sample whose round trip took longer than this says too little about when the server answered: it is passed over. */\nexport const MAX_SAMPLE_RTT_MS = 2000;\n\nlet offset = 0;\nlet bestRtt = Infinity;\nlet takenAt = 0;\nlet timed = false;\n\n/** Server wall time now, in ms since the epoch. */\nexport function serverNow(): number {\n return Date.now() + offset;\n}\n\n/** The current estimate of server time minus this device's time, in ms. */\nexport function serverOffset(): number {\n return offset;\n}\n\n/**\n * A server timestamp `serverMs` taken halfway through an exchange that took `rttMs` to come back: the server's clock\n * then stood at `serverMs + rttMs / 2` of this device's now.\n */\nexport function noteServerTime(serverMs: number, rttMs: number, now = Date.now()): void {\n if (!Number.isFinite(serverMs) || !Number.isFinite(rttMs) || rttMs < 0 || rttMs > MAX_SAMPLE_RTT_MS) return;\n if (timed && rttMs > bestRtt && now - takenAt < STALE_MS) return;\n offset = serverMs + rttMs / 2 - now;\n bestRtt = rttMs;\n takenAt = now;\n timed = true;\n}\n\n/** Back to this device's own clock (offline, or leaving a server). */\nexport function resetServerClock(): void {\n offset = 0;\n bestRtt = Infinity;\n takenAt = 0;\n timed = false;\n}\n"],"names":["MAX_SAMPLE_RTT_MS","offset","bestRtt","takenAt","timed","serverNow","serverOffset","noteServerTime","serverMs","rttMs","now","resetServerClock"],"mappings":"AAeO,MAAMA,IAAoB;AAEjC,IAAIC,IAAS,GACTC,IAAU,OACVC,IAAU,GACVC,IAAQ;AAGL,SAASC,IAAoB;AAClC,SAAO,KAAK,QAAQJ;AACtB;AAGO,SAASK,IAAuB;AACrC,SAAOL;AACT;AAMO,SAASM,EAAeC,GAAkBC,GAAeC,IAAM,KAAK,OAAa;AACtF,EAAI,CAAC,OAAO,SAASF,CAAQ,KAAK,CAAC,OAAO,SAASC,CAAK,KAAKA,IAAQ,KAAKA,IAAQ,OAC9EL,KAASK,IAAQP,KAAWQ,IAAMP,IAAU,QAChDF,IAASO,IAAWC,IAAQ,IAAIC,GAChCR,IAAUO,GACVN,IAAUO,GACVN,IAAQ;AACV;AAGO,SAASO,IAAyB;AACvC,EAAAV,IAAS,GACTC,IAAU,OACVC,IAAU,GACVC,IAAQ;AACV;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotCodec.js","sources":["../../src/net/snapshotCodec.ts"],"sourcesContent":["import type { EntityUpdate } from './constants';\n\n/** Any snapshot whose `up` block travels as binary records; the rest is JSON. */\nexport interface SnapshotLike {\n up?: EntityUpdate[];\n}\n\n/**\n * Binary wire form of a snapshot's `up` block. A server in another language must write the same bytes.\n *\n * A snapshot that carries no updates is still one text frame of JSON. A snapshot that does is one **binary** WebSocket\n * frame, big-endian throughout:\n *\n * ```\n * u8 SNAP_FRAME_TAG\n * u32 L, the byte length of the JSON that follows\n * L UTF-8 JSON of the snapshot with `up` left out\n * n × UPDATE_RECORD_BYTES, one record per EntityUpdate, n = (frame length - 5 - L) / 16\n * ```\n *\n * One record (16 bytes), the `[id, x, y, aim, anim, hp, flags]` tuple:\n *\n * | off | type | field |\n * |-----|-------|------------------------------------------------------------------------------------|\n * | 0 | i32 | id |\n * | 4 | i16 | x in 1/UPDATE_POS_SCALE px, clamped to [-32768, 32767] (−4096 … 4095.875 px) |\n * | 6 | i16 | y, the same |\n * | 8 | u8 | aim in 1/UPDATE_AIM_STEPS of a turn, wrapped; read back in [-π, π) |\n * | 9 | u8 | anim (ANIM), clamped to 0..255 |\n * | 10 | i32 | hp (big bodies go past 65535) |\n * | 14 | u16 | flags (ENTITY_FLAG, nine bits used today), clamped to 0..65535 |\n *\n * Quantisation error: position ≤ 1/16 px, aim ≤ π/256 rad. The position range holds maps up to 4096 px a side.\n * Each record is written once per actor state version on the server and shared by every viewer.\n */\nexport const SNAP_FRAME_TAG = 1;\n/** Tag byte + the u32 JSON length. */\nexport const SNAP_FRAME_HEADER_BYTES = 5;\nexport const UPDATE_RECORD_BYTES = 16;\nexport const UPDATE_POS_SCALE = 8;\nexport const UPDATE_AIM_STEPS = 256;\n\nconst TURN = Math.PI * 2;\n\nfunction clampInt(v: number, lo: number, hi: number): number {\n if (!Number.isFinite(v)) return Number.isNaN(v) ? 0 : v > 0 ? hi : lo;\n const r = Math.round(v);\n return r < lo ? lo : r > hi ? hi : r;\n}\n\n/** World px → the i16 on the wire. */\nexport function quantizePos(v: number): number {\n return clampInt(v * UPDATE_POS_SCALE, -32768, 32767);\n}\n\n/** Radians → the u8 on the wire (wrapped, so any angle is accepted). */\nexport function quantizeAim(a: number): number {\n if (!Number.isFinite(a)) return 0;\n const s = Math.round((a * UPDATE_AIM_STEPS) / TURN);\n return ((s % UPDATE_AIM_STEPS) + UPDATE_AIM_STEPS) % UPDATE_AIM_STEPS;\n}\n\nexport function dequantizeAim(b: number): number {\n return ((b >= UPDATE_AIM_STEPS / 2 ? b - UPDATE_AIM_STEPS : b) * TURN) / UPDATE_AIM_STEPS;\n}\n\nexport function writeUpdate(view: DataView, off: number, u: EntityUpdate): void {\n view.setInt32(off, clampInt(u[0], -0x80000000, 0x7fffffff));\n view.setInt16(off + 4, quantizePos(u[1]));\n view.setInt16(off + 6, quantizePos(u[2]));\n view.setUint8(off + 8, quantizeAim(u[3]));\n view.setUint8(off + 9, clampInt(u[4], 0, 255));\n view.setInt32(off + 10, clampInt(u[5], -0x80000000, 0x7fffffff));\n view.setUint16(off + 14, clampInt(u[6], 0, 0xffff));\n}\n\nexport function readUpdate(view: DataView, off: number): EntityUpdate {\n return [\n view.getInt32(off),\n view.getInt16(off + 4) / UPDATE_POS_SCALE,\n view.getInt16(off + 6) / UPDATE_POS_SCALE,\n dequantizeAim(view.getUint8(off + 8)),\n view.getUint8(off + 9),\n view.getInt32(off + 10),\n view.getUint16(off + 14),\n ];\n}\n\nexport function encodeUpdates(ups: readonly EntityUpdate[]): Uint8Array {\n const out = new Uint8Array(ups.length * UPDATE_RECORD_BYTES);\n const view = new DataView(out.buffer);\n for (let i = 0; i < ups.length; i++) writeUpdate(view, i * UPDATE_RECORD_BYTES, ups[i]!);\n return out;\n}\n\nexport function decodeUpdates(bytes: Uint8Array): EntityUpdate[] {\n if (bytes.byteLength % UPDATE_RECORD_BYTES !== 0) throw new Error(`update block of ${bytes.byteLength} bytes`);\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const out: EntityUpdate[] = [];\n for (let off = 0; off < bytes.byteLength; off += UPDATE_RECORD_BYTES) out.push(readUpdate(view, off));\n return out;\n}\n\n/** The updates exactly as a client reads them off the wire: what a local simulation sends, so it quantises like the server. */\nexport function quantizeUpdates(ups: readonly EntityUpdate[]): EntityUpdate[] {\n // Same numbers as decodeUpdates(encodeUpdates(ups)) without the byte round trip (`+ 0` turns -0 into 0, as a read does).\n const out: EntityUpdate[] = new Array(ups.length);\n for (let i = 0; i < ups.length; i++) {\n const u = ups[i]!;\n out[i] = [\n clampInt(u[0], -0x80000000, 0x7fffffff) + 0,\n quantizePos(u[1]) / UPDATE_POS_SCALE + 0,\n quantizePos(u[2]) / UPDATE_POS_SCALE + 0,\n dequantizeAim(quantizeAim(u[3])),\n clampInt(u[4], 0, 255) + 0,\n clampInt(u[5], -0x80000000, 0x7fffffff) + 0,\n clampInt(u[6], 0, 0xffff) + 0,\n ];\n }\n return out;\n}\n\ninterface TextCodecs {\n TextEncoder: new () => { encode(s: string): Uint8Array };\n TextDecoder: new () => { decode(b: Uint8Array): string };\n}\n// the protocol builds without DOM types; browsers and Node both have these\nconst text = globalThis as unknown as TextCodecs;\nconst encoder = new text.TextEncoder();\nconst decoder = new text.TextDecoder();\n\n/** A snapshot as the server frames it: binary when it carries updates (see the layout above). */\nexport function encodeSnapshotFrame<S extends SnapshotLike>(snap: S): Uint8Array {\n const { up, ...rest } = snap;\n const json = encoder.encode(JSON.stringify(rest));\n const records = up?.length ? up.length : 0;\n const out = new Uint8Array(SNAP_FRAME_HEADER_BYTES + json.byteLength + records * UPDATE_RECORD_BYTES);\n const view = new DataView(out.buffer);\n view.setUint8(0, SNAP_FRAME_TAG);\n view.setUint32(1, json.byteLength);\n out.set(json, SNAP_FRAME_HEADER_BYTES);\n const base = SNAP_FRAME_HEADER_BYTES + json.byteLength;\n for (let i = 0; i < records; i++) writeUpdate(view, base + i * UPDATE_RECORD_BYTES, up![i]!);\n return out;\n}\n\n/** Reads a binary snapshot frame back into the same object a text frame parses to. */\nexport function decodeSnapshotFrame<S extends SnapshotLike>(frame: ArrayBuffer | Uint8Array): S {\n const bytes = frame instanceof Uint8Array ? frame : new Uint8Array(frame);\n if (bytes.byteLength < SNAP_FRAME_HEADER_BYTES) throw new Error('short snapshot frame');\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const tag = view.getUint8(0);\n if (tag !== SNAP_FRAME_TAG) throw new Error(`unknown binary frame tag ${tag}`);\n const len = view.getUint32(1);\n const base = SNAP_FRAME_HEADER_BYTES + len;\n if (base > bytes.byteLength) throw new Error('snapshot frame JSON overruns the frame');\n const snap = JSON.parse(decoder.decode(bytes.subarray(SNAP_FRAME_HEADER_BYTES, base))) as S;\n if (base < bytes.byteLength) snap.up = decodeUpdates(bytes.subarray(base));\n return snap;\n}\n"],"names":["SNAP_FRAME_TAG","SNAP_FRAME_HEADER_BYTES","UPDATE_RECORD_BYTES","UPDATE_POS_SCALE","UPDATE_AIM_STEPS","TURN","clampInt","v","lo","hi","r","quantizePos","quantizeAim","a","dequantizeAim","b","writeUpdate","view","off","u","readUpdate","encodeUpdates","ups","out","i","decodeUpdates","bytes","quantizeUpdates","text","encoder","decoder","encodeSnapshotFrame","snap","up","rest","json","records","base","decodeSnapshotFrame","frame","tag"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Parallax.js","sources":["../../src/parallax/Parallax.ts"],"sourcesContent":["import { Container } from 'pixi.js';\nimport { ParallaxLayer, type ParallaxCamera, type ParallaxLayerOptions } from './ParallaxLayer';\n\nexport interface ParallaxOptions {\n /**\n * Where the whole parallax sorts among the world layer's children (its `zIndex`, default -1000): below the level,\n * so every layer stays behind it. A foreground layer (factor above 1) belongs in a second `Parallax` with a depth\n * above the level's.\n */\n depth?: number;\n}\n\n/**\n * Layers of scenery behind (or before) the world, each scrolling at its own share of the camera's speed. Add `view`\n * to the world layer — a scene's `root` — and call `update(camera, dtMs)` once a frame after the camera moved, so\n * the layers sit where this frame's view is:\n *\n * ```ts\n * const sky = new Parallax();\n * sky.add({ texture: clouds, factorX: 0.1, repeatX: true, speedX: -6 });\n * sky.add({ display: hills, factorX: 0.5, factorY: 1 });\n * this.root.addChild(sky.view);\n * // each frame\n * sky.update(stage.cameras.main, dt * 1000);\n * ```\n */\nexport class Parallax {\n readonly view = new Container({ sortableChildren: true });\n private readonly list: ParallaxLayer[] = [];\n\n constructor(options: ParallaxOptions = {}) {\n this.view.zIndex = options.depth ?? -1000;\n }\n\n get layers(): readonly ParallaxLayer[] {\n return this.list;\n }\n\n /** Adds a layer (built here from options, or one made before); later layers draw in front unless a `depth` says. */\n add(layer: ParallaxLayer | ParallaxLayerOptions): ParallaxLayer {\n const made = layer instanceof ParallaxLayer ? layer : new ParallaxLayer(layer);\n if (made.depth === undefined) made.view.zIndex = this.list.length;\n this.list.push(made);\n this.view.addChild(made.view);\n return made;\n }\n\n /** Takes a layer out and destroys it. */\n remove(layer: ParallaxLayer): void {\n const i = this.list.indexOf(layer);\n if (i < 0) return;\n this.list.splice(i, 1);\n layer.destroy();\n }\n\n update(camera: ParallaxCamera, dtMs: number): void {\n for (let i = 0; i < this.list.length; i++) this.list[i]!.update(camera, dtMs);\n }\n\n destroy(): void {\n this.list.length = 0;\n this.view.destroy({ children: true });\n }\n}\n"],"names":["Parallax","Container","options","layer","made","ParallaxLayer","camera","dtMs","i"],"mappings":";;AA0BO,MAAMA,EAAS;AAAA,EACX,OAAO,IAAIC,EAAU,EAAE,kBAAkB,IAAM;AAAA,EACvC,OAAwB,CAAA;AAAA,EAEzC,YAAYC,IAA2B,IAAI;AACzC,SAAK,KAAK,SAASA,EAAQ,SAAS;AAAA,EACtC;AAAA,EAEA,IAAI,SAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAIC,GAA4D;AAC9D,UAAMC,IAAOD,aAAiBE,IAAgBF,IAAQ,IAAIE,EAAcF,CAAK;AAC7E,WAAIC,EAAK,UAAU,aAAgB,KAAK,SAAS,KAAK,KAAK,SAC3D,KAAK,KAAK,KAAKA,CAAI,GACnB,KAAK,KAAK,SAASA,EAAK,IAAI,GACrBA;AAAA,EACT;AAAA;AAAA,EAGA,OAAOD,GAA4B;AACjC,UAAM,IAAI,KAAK,KAAK,QAAQA,CAAK;AACjC,IAAI,IAAI,MACR,KAAK,KAAK,OAAO,GAAG,CAAC,GACrBA,EAAM,QAAA;AAAA,EACR;AAAA,EAEA,OAAOG,GAAwBC,GAAoB;AACjD,aAASC,IAAI,GAAGA,IAAI,KAAK,KAAK,QAAQA,IAAK,MAAK,KAAKA,CAAC,EAAG,OAAOF,GAAQC,CAAI;AAAA,EAC9E;AAAA,EAEA,UAAgB;AACd,SAAK,KAAK,SAAS,GACnB,KAAK,KAAK,QAAQ,EAAE,UAAU,IAAM;AAAA,EACtC;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ParallaxLayer.js","sources":["../../src/parallax/ParallaxLayer.ts"],"sourcesContent":["import { Container, Sprite, TilingSprite, type Texture } from 'pixi.js';\nimport { placeAxis, wrap, type AxisInput, type AxisPlacement } from './parallaxMath';\n\n/** What a parallax layer reads off the camera each frame; the engine's `Camera` has all of it. */\nexport interface ParallaxCamera {\n readonly scrollX: number;\n readonly scrollY: number;\n readonly width: number;\n readonly height: number;\n readonly zoom: number;\n /** Set on a pixel-perfect engine: a layer then lands on whole world pixels unless told otherwise. */\n readonly roundPixels?: boolean;\n}\n\nexport interface ParallaxLayerOptions {\n /**\n * A texture to draw, repeated along the axes `repeatX` / `repeatY` name (one tiling sprite, as wide as the view\n * whatever the level's length). Give either this or `display`.\n */\n texture?: Texture;\n /** Anything already drawn (a sprite, a `Graphics`, a container of them), drawn once and never repeated. */\n display?: Container;\n /** Share of the camera's horizontal travel the layer moves on screen: 0 fixed to the screen, 1 with the world (default 0.5). */\n factorX?: number;\n /** The same vertically (default: `factorX`). */\n factorY?: number;\n /** Repeat the texture left and right forever (a `texture` layer only). */\n repeatX?: boolean;\n /** Repeat the texture up and down forever (a `texture` layer only). */\n repeatY?: boolean;\n /** World px added to the layer's position, e.g. where on the level a row of hills stands. */\n offsetX?: number;\n offsetY?: number;\n /** Drift, world px a second, on top of the parallax (clouds). */\n speedX?: number;\n speedY?: number;\n /** Land on whole world pixels (default: the camera's `roundPixels`, i.e. a pixel-perfect engine). */\n snap?: boolean;\n /** Hide the layer while none of it is in view (default true). Repeated layers always fill the view. */\n cull?: boolean;\n /** Order among the parallax's layers: lower draws further back (default: the order they were added in). */\n depth?: number;\n}\n\n/**\n * One layer of a `Parallax`: a texture or a display object that scrolls at a share of the camera's speed, optionally\n * repeated and drifting. `view` goes into the world layer (the parallax adds it), so the camera's zoom and pixel\n * rounding apply to it like to any world object; the layer only moves it.\n */\nexport class ParallaxLayer {\n readonly view = new Container();\n factorX: number;\n factorY: number;\n offsetX: number;\n offsetY: number;\n speedX: number;\n speedY: number;\n snap: boolean | undefined;\n cull: boolean;\n /** The depth it was given, if any (a `Parallax` numbers the others in the order they were added). */\n readonly depth: number | undefined;\n private readonly repeatX: boolean;\n private readonly repeatY: boolean;\n private readonly tiling: TilingSprite | null = null;\n private readonly tileW: number = 0;\n private readonly tileH: number = 0;\n /** The content's local bounds, measured once: reading bounds every frame walks the whole display tree. */\n private boundsX = 0;\n private boundsY = 0;\n private boundsW = 0;\n private boundsH = 0;\n private driftX = 0;\n private driftY = 0;\n // scratch, so a frame allocates nothing\n private readonly input: AxisInput = { scroll: 0, view: 0, factor: 0, offset: 0, repeat: false, tile: 0, contentStart: 0, contentSize: 0, snap: false };\n private readonly px: AxisPlacement = { position: 0, size: 0, phase: 0, visible: true };\n private readonly py: AxisPlacement = { position: 0, size: 0, phase: 0, visible: true };\n\n constructor(options: ParallaxLayerOptions) {\n const { texture, display } = options;\n if (!texture === !display) throw new Error('ParallaxLayer: give exactly one of `texture` and `display`');\n this.factorX = options.factorX ?? 0.5;\n this.factorY = options.factorY ?? this.factorX;\n this.offsetX = options.offsetX ?? 0;\n this.offsetY = options.offsetY ?? 0;\n this.speedX = options.speedX ?? 0;\n this.speedY = options.speedY ?? 0;\n this.snap = options.snap;\n this.cull = options.cull ?? true;\n this.repeatX = texture !== undefined && options.repeatX === true;\n this.repeatY = texture !== undefined && options.repeatY === true;\n this.depth = options.depth;\n if (options.depth !== undefined) this.view.zIndex = options.depth;\n if (texture) {\n this.tileW = texture.width;\n this.tileH = texture.height;\n if (this.repeatX || this.repeatY) {\n this.tiling = new TilingSprite({ texture, width: texture.width, height: texture.height });\n this.view.addChild(this.tiling);\n } else this.view.addChild(new Sprite(texture));\n } else if (display) this.view.addChild(display);\n this.remeasure();\n }\n\n /** Measures the content again, after it was redrawn larger or smaller (culling reads the measured size). */\n remeasure(): void {\n if (this.tiling) {\n this.boundsX = 0;\n this.boundsY = 0;\n this.boundsW = this.tileW;\n this.boundsH = this.tileH;\n return;\n }\n const b = this.view.getLocalBounds();\n this.boundsX = b.x;\n this.boundsY = b.y;\n this.boundsW = b.width;\n this.boundsH = b.height;\n }\n\n /** Places the layer for this frame; `dtMs` advances the drift. */\n update(camera: ParallaxCamera, dtMs: number): void {\n const seconds = dtMs / 1000;\n this.driftX += this.speedX * seconds;\n this.driftY += this.speedY * seconds;\n // a repeating drift only matters within one tile: kept there, it never grows until the floats lose pixels\n if (this.repeatX) this.driftX = wrap(this.driftX, this.tileW);\n if (this.repeatY) this.driftY = wrap(this.driftY, this.tileH);\n const snap = this.snap ?? camera.roundPixels === true;\n const zoom = camera.zoom > 0 ? camera.zoom : 1;\n const a = this.input;\n a.snap = snap;\n a.scroll = camera.scrollX;\n a.view = camera.width / zoom;\n a.factor = this.factorX;\n a.offset = this.offsetX + this.driftX;\n a.repeat = this.repeatX;\n a.tile = this.tileW;\n a.contentStart = this.boundsX;\n a.contentSize = this.boundsW;\n const x = placeAxis(this.px, a);\n a.scroll = camera.scrollY;\n a.view = camera.height / zoom;\n a.factor = this.factorY;\n a.offset = this.offsetY + this.driftY;\n a.repeat = this.repeatY;\n a.tile = this.tileH;\n a.contentStart = this.boundsY;\n a.contentSize = this.boundsH;\n const y = placeAxis(this.py, a);\n this.view.position.set(x.position, y.position);\n const tiling = this.tiling;\n if (tiling) {\n const w = this.repeatX ? x.size : this.tileW;\n const h = this.repeatY ? y.size : this.tileH;\n // resized only when the view's size changed: a resize marks the sprite's geometry dirty for the renderer\n if (tiling.width !== w || tiling.height !== h) tiling.setSize(w, h);\n tiling.tilePosition.set(x.phase, y.phase);\n }\n // a camera that has no size yet (before the first resize) shows everything rather than nothing\n const sized = camera.width > 0 && camera.height > 0;\n this.view.visible = !this.cull || !sized || (x.visible && y.visible);\n }\n\n destroy(): void {\n this.view.destroy({ children: true });\n }\n}\n"],"names":["ParallaxLayer","Container","options","texture","display","TilingSprite","Sprite","b","camera","dtMs","seconds","wrap","snap","zoom","a","x","placeAxis","y","tiling","w","h","sized"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parallaxMath.js","sources":["../../src/parallax/parallaxMath.ts"],"sourcesContent":["/**\n * The arithmetic of parallax, apart from anything that draws so it can be tested under node. One axis at a time:\n * the same functions serve x and y.\n */\n\n/**\n * The world position a layer is drawn at so that, on screen, it moves `factor` times as far as the camera: 0 stays\n * fixed to the screen (a sky at infinity), 1 moves with the world, 0.5 at half speed, above 1 faster (a foreground).\n * The layer lives in the world layer, so the camera's zoom scales it like everything else.\n */\nexport function parallaxOrigin(scroll: number, factor: number, offset = 0): number {\n return scroll * (1 - factor) + offset;\n}\n\n/** `value` brought into [0, size): where a repeating pattern stands within one tile. 0 for an empty tile. */\nexport function wrap(value: number, size: number): number {\n if (!(size > 0)) return 0;\n const r = value % size;\n // `+ 0` turns the -0 a negative multiple leaves into 0, which a tile position compares equal to anyway\n return r < 0 ? r + size : r + 0;\n}\n\n/** Whether a span [start, start + size) overlaps the view [viewStart, viewStart + viewSize), `margin` px wider. */\nexport function spanVisible(start: number, size: number, viewStart: number, viewSize: number, margin = 0): boolean {\n return start < viewStart + viewSize + margin && start + size > viewStart - margin;\n}\n\n/** Where one axis of a layer goes this frame; written into a caller's object so a frame allocates nothing. */\nexport interface AxisPlacement {\n /** The layer's world position on this axis. */\n position: number;\n /** For a repeated axis, how long the tiled strip is (it covers the view); otherwise the content's own size. */\n size: number;\n /** For a repeated axis, the pattern's offset inside the strip (a tiling sprite's tile position), in [0, tile). */\n phase: number;\n /** For an axis that does not repeat, whether the content reaches into the view at all. */\n visible: boolean;\n}\n\nexport interface AxisInput {\n /** The camera's scroll on this axis (the world coordinate at the screen's edge). */\n scroll: number;\n /** How much of the world the view shows on this axis: the screen's size over the camera's zoom. */\n view: number;\n factor: number;\n /** Fixed offset plus whatever auto-scroll has added. */\n offset: number;\n repeat: boolean;\n /** One tile's size on this axis (repeated axes). */\n tile: number;\n /** Where the content's bounds begin from the layer's position, and how long they are (axes that do not repeat). */\n contentStart: number;\n contentSize: number;\n /** Land on whole world pixels. */\n snap: boolean;\n}\n\n/**\n * Places one axis. A repeated axis is a strip laid over the view, starting at the view's edge (floored to a whole\n * pixel when snapping), whose pattern is shifted so its tiles stand where the parallax puts them: the strip never\n * grows with the level, however long it is. An axis that does not repeat sits at its parallax origin and is visible\n * only while its content overlaps the view.\n */\nexport function placeAxis(out: AxisPlacement, a: AxisInput): AxisPlacement {\n let origin = parallaxOrigin(a.scroll, a.factor, a.offset);\n if (a.snap) origin = Math.round(origin);\n if (a.repeat && a.tile > 0) {\n const start = a.snap ? Math.floor(a.scroll) : a.scroll;\n out.position = start;\n // one pixel more than the view, for the part of a pixel the floor gave back\n out.size = Math.ceil(a.view) + 1;\n out.phase = wrap(origin - start, a.tile);\n out.visible = true;\n } else {\n out.position = origin;\n out.size = a.contentSize;\n out.phase = 0;\n out.visible = spanVisible(origin + a.contentStart, a.contentSize, a.scroll, a.view);\n }\n return out;\n}\n"],"names":["parallaxOrigin","scroll","factor","offset","wrap","value","size","r","spanVisible","start","viewStart","viewSize","margin","placeAxis","out","a","origin"],"mappings":"AAUO,SAASA,EAAeC,GAAgBC,GAAgBC,IAAS,GAAW;AACjF,SAAOF,KAAU,IAAIC,KAAUC;AACjC;AAGO,SAASC,EAAKC,GAAeC,GAAsB;AACxD,MAAI,EAAEA,IAAO,GAAI,QAAO;AACxB,QAAMC,IAAIF,IAAQC;AAElB,SAAOC,IAAI,IAAIA,IAAID,IAAOC,IAAI;AAChC;AAGO,SAASC,EAAYC,GAAeH,GAAcI,GAAmBC,GAAkBC,IAAS,GAAY;AACjH,SAAOH,IAAQC,IAAYC,IAAWC,KAAUH,IAAQH,IAAOI,IAAYE;AAC7E;AAsCO,SAASC,EAAUC,GAAoBC,GAA6B;AACzE,MAAIC,IAAShB,EAAee,EAAE,QAAQA,EAAE,QAAQA,EAAE,MAAM;AAExD,MADIA,EAAE,SAAMC,IAAS,KAAK,MAAMA,CAAM,IAClCD,EAAE,UAAUA,EAAE,OAAO,GAAG;AAC1B,UAAMN,IAAQM,EAAE,OAAO,KAAK,MAAMA,EAAE,MAAM,IAAIA,EAAE;AAChD,IAAAD,EAAI,WAAWL,GAEfK,EAAI,OAAO,KAAK,KAAKC,EAAE,IAAI,IAAI,GAC/BD,EAAI,QAAQV,EAAKY,IAASP,GAAOM,EAAE,IAAI,GACvCD,EAAI,UAAU;AAAA,EAChB;AACE,IAAAA,EAAI,WAAWE,GACfF,EAAI,OAAOC,EAAE,aACbD,EAAI,QAAQ,GACZA,EAAI,UAAUN,EAAYQ,IAASD,EAAE,cAAcA,EAAE,aAAaA,EAAE,QAAQA,EAAE,IAAI;AAEpF,SAAOD;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Emitter.js","sources":["../../src/particles/Emitter.ts"],"sourcesContent":["import { Particle, ParticleContainer, type IParticle, type Texture } from 'pixi.js';\nimport { engineSettings } from '../settings';\nimport { inView, type CameraLike } from '../fx/viewCull';\nimport { ParticleBudget, particleBudget } from './ParticleBudget';\nimport { ParticleSim } from './ParticleSim';\nimport { sampleColor, sampleCurve, type ColorCurve, type Curve } from './particleCurves';\nimport { dueParticles, sampleRange, scaleCount, spreadAngle, type Range } from './particleSampling';\n\n/** Where a new particle appears round the emitter: a disc, or a box centred on it. */\nexport type SpawnArea = { readonly radius: number } | { readonly width: number; readonly height: number };\n\nexport interface EmitterOptions {\n /** Every particle's texture. One emitter draws one texture (a frame of an atlas works: they share a source). */\n texture: Texture;\n /** Particles this emitter may hold at once (default 256). */\n capacity?: number;\n /** Particles a second while emitting (default 0: bursts only). */\n rate?: number;\n /** Seconds a particle lives (default 1). */\n lifetime?: Range;\n /** Starting speed, px/s (default [40, 80]). */\n speed?: Range;\n /** Direction of travel in radians (default -π/2, up the screen) and how far either way it may turn (default π). */\n angle?: number;\n spread?: number;\n /** Px/s² added to velocity (default none). */\n gravityX?: number;\n gravityY?: number;\n /** Share of speed lost per second (default 0). */\n drag?: number;\n /** Scale of the texture over life (default 1), and how much each particle's size may differ (0–1, default 0). */\n size?: Curve;\n sizeVariance?: number;\n /** Opacity over life (default fading from 1 to 0). */\n alpha?: Curve;\n /** Tint over life, `0xrrggbb` (default white). */\n tint?: ColorCurve;\n /** Starting rotation and spin, radians and radians/s (default 0). */\n rotation?: Range;\n spin?: Range;\n /** 'add' for glows, sparks and fire; 'normal' for smoke and debris (default 'normal'). */\n blend?: 'normal' | 'add';\n /** Where particles appear round the emitter (default its exact point). */\n area?: SpawnArea;\n /** World px beyond the screen within which the emitter still spawns and draws (default 64). */\n cullMargin?: number;\n /** Share of particles kept under reduced effects or the battery saver (default 0.35). */\n thinFactor?: number;\n /** The budget shared with other emitters (default the engine-wide `particleBudget`). */\n budget?: ParticleBudget;\n /** Random source (default `Math.random`); a seeded one makes an emitter replayable. */\n random?: () => number;\n}\n\n/**\n * A source of smooth sprite particles for any art style: sparks, smoke, dust, magic. Particles live in world\n * coordinates, so moving the emitter (`x`, `y`) trails new ones behind it instead of dragging the old ones along.\n *\n * Drawn by one Pixi `ParticleContainer` (`view`): add it to a scene's `root`, and call `update(dt, camera)` once a\n * frame. Particle objects are pooled and their colour is written straight into Pixi's packed field, so a steady\n * emitter allocates nothing per frame. Spawning asks the shared `ParticleBudget` first, and off-screen emitters\n * neither spawn nor draw.\n */\nexport class Emitter {\n readonly view: ParticleContainer;\n x = 0;\n y = 0;\n /** Continuous emission on or off (bursts work either way). */\n emitting = false;\n private readonly sim: ParticleSim;\n private readonly pool: IParticle[] = [];\n private readonly budget: ParticleBudget;\n private readonly random: () => number;\n private readonly opts: EmitterOptions;\n private carry = 0;\n private destroyed = false;\n /** Set by the last `update`: whether the emitter's point was near the screen. */\n private near = true;\n\n constructor(options: EmitterOptions) {\n this.opts = options;\n this.sim = new ParticleSim(options.capacity ?? 256);\n this.budget = options.budget ?? particleBudget;\n this.random = options.random ?? Math.random;\n this.view = new ParticleContainer({\n texture: options.texture,\n dynamicProperties: { position: true, rotation: true, vertex: true, color: true, uvs: false },\n });\n this.view.blendMode = options.blend === 'add' ? 'add' : 'normal';\n }\n\n /** Particles alive now. */\n get live(): number {\n return this.sim.count;\n }\n\n setPosition(x: number, y: number): this {\n this.x = x;\n this.y = y;\n return this;\n }\n\n start(): this {\n this.emitting = true;\n return this;\n }\n\n /** Stops emitting; the particles in the air live out their lives. */\n stop(): this {\n this.emitting = false;\n this.carry = 0;\n return this;\n }\n\n /** `count` particles at once, at the emitter or at (x, y). Thinned under reduced effects; skipped off screen. */\n burst(count: number, x = this.x, y = this.y): void {\n if (this.destroyed || !this.near) return;\n this.emit(scaleCount(count, this.thin(), this.random()), x, y);\n }\n\n /** Advances by `dt` seconds. Pass the camera to skip spawning and drawing while everything is off screen. */\n update(dt: number, camera?: CameraLike): void {\n if (this.destroyed) return;\n const margin = this.opts.cullMargin ?? 64;\n this.near = camera ? inView(camera, this.x, this.y, margin) : true;\n const rate = this.opts.rate ?? 0;\n if (this.emitting && rate > 0 && this.near) {\n const due = dueParticles(rate * this.thin(), dt, this.carry);\n const n = Math.floor(due);\n this.carry = due - n;\n if (n > 0) this.emit(n, this.x, this.y);\n } else this.carry = 0;\n const o = this.opts;\n const died = this.sim.step(dt, o.gravityX ?? 0, o.gravityY ?? 0, o.drag ?? 0);\n if (died > 0) this.budget.give(died);\n this.draw(camera, margin);\n }\n\n /** Every particle gone at once (a scene change, a cancelled effect). */\n clear(): void {\n this.budget.give(this.sim.clear());\n this.draw(undefined, 0);\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.clear();\n this.destroyed = true;\n this.pool.length = 0;\n this.view.destroy();\n }\n\n private thin(): number {\n const s = engineSettings.getState();\n return s.reducedFx || s.batterySaver ? (this.opts.thinFactor ?? 0.35) : 1;\n }\n\n private emit(n: number, x: number, y: number): void {\n const sim = this.sim;\n const granted = this.budget.take(Math.min(n, sim.capacity - sim.count));\n const o = this.opts;\n const r = this.random;\n const base = o.angle ?? -Math.PI / 2;\n const spread = o.spread ?? Math.PI;\n for (let k = 0; k < granted; k++) {\n let px = x;\n let py = y;\n const area = o.area;\n if (area && 'radius' in area) {\n // the square root spreads them evenly over the disc instead of crowding the centre\n const d = Math.sqrt(r()) * area.radius;\n const a = r() * Math.PI * 2;\n px += Math.cos(a) * d;\n py += Math.sin(a) * d;\n } else if (area) {\n px += (r() - 0.5) * area.width;\n py += (r() - 0.5) * area.height;\n }\n const a = spreadAngle(base, spread, r());\n const v = sampleRange(o.speed ?? [40, 80], r());\n sim.spawn(px, py, Math.cos(a) * v, Math.sin(a) * v, sampleRange(o.lifetime ?? 1, r()), sampleRange(o.rotation ?? 0, r()), sampleRange(o.spin ?? 0, r()), r());\n }\n }\n\n private draw(camera: CameraLike | undefined, margin: number): void {\n const sim = this.sim;\n const list = this.view.particleChildren;\n if (list.length !== sim.count) {\n while (list.length < sim.count) list.push(this.pool.pop() ?? new Particle({ texture: this.opts.texture, anchorX: 0.5, anchorY: 0.5 }));\n while (list.length > sim.count) this.pool.push(list.pop()!);\n this.view.update();\n }\n const o = this.opts;\n const size = o.size ?? 1;\n const alpha = o.alpha ?? FADE_OUT;\n const tint = o.tint ?? 0xffffff;\n const variance = o.sizeVariance ?? 0;\n let anyIn = sim.count === 0;\n for (let i = 0; i < sim.count; i++) {\n const p = list[i]!;\n const t = sim.progress(i);\n const x = sim.x[i]!;\n const y = sim.y[i]!;\n p.x = x;\n p.y = y;\n p.rotation = sim.rot[i]!;\n const s = sampleCurve(size, t) * (1 + (sim.seed[i]! * 2 - 1) * variance);\n p.scaleX = s;\n p.scaleY = s;\n const c = sampleColor(tint, t);\n const al = Math.min(1, Math.max(0, sampleCurve(alpha, t)));\n // Pixi's packed field (BGR plus alpha in the top byte), written directly: the tint setter parses a colour each time\n p.color = (((c & 0xff) << 16) | (c & 0xff00) | ((c >> 16) & 0xff)) + (((al * 255) | 0) << 24);\n if (!anyIn && (!camera || inView(camera, x, y, margin))) anyIn = true;\n }\n this.view.visible = anyIn && sim.count > 0;\n }\n}\n\nconst FADE_OUT: Curve = { start: 1, end: 0 };\n"],"names":["Emitter","options","ParticleSim","particleBudget","ParticleContainer","x","y","count","scaleCount","dt","camera","margin","inView","rate","due","dueParticles","n","o","died","s","engineSettings","sim","granted","r","base","spread","k","px","py","area","d","a","spreadAngle","v","sampleRange","list","Particle","size","alpha","FADE_OUT","tint","variance","anyIn","i","p","t","sampleCurve","c","sampleColor","al"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ParticleBudget.js","sources":["../../src/particles/ParticleBudget.ts"],"sourcesContent":["import { engineSettings } from '../settings';\n\n/**\n * Particles alive across every emitter that shares this budget. An emitter asks before it spawns and gives back as\n * its particles die, so a crowd of emitters at once costs no more than `limit`: ten explosions in one frame share the\n * budget instead of adding up to a frame the GPU cannot finish. Under reduced effects or the battery saver the limit\n * shrinks by `thinFactor`.\n */\nexport class ParticleBudget {\n private used = 0;\n\n constructor(\n /** Particles alive at once, for every emitter together. */\n public limit = 4000,\n /** The share of `limit` left under reduced effects or the battery saver. */\n public thinFactor = 0.35,\n ) {}\n\n /** The limit as the settings stand now. */\n get effectiveLimit(): number {\n const s = engineSettings.getState();\n return s.reducedFx || s.batterySaver ? Math.floor(this.limit * this.thinFactor) : this.limit;\n }\n\n get live(): number {\n return this.used;\n }\n\n /** Reserves up to `n` particles and returns how many were granted (0 when the budget is spent). */\n take(n: number): number {\n const granted = Math.max(0, Math.min(n, this.effectiveLimit - this.used));\n this.used += granted;\n return granted;\n }\n\n /** Returns `n` particles that died or were cleared. */\n give(n: number): void {\n this.used = Math.max(0, this.used - n);\n }\n}\n\n/** The budget every emitter shares unless it is handed its own. */\nexport const particleBudget = new ParticleBudget();\n"],"names":["ParticleBudget","limit","thinFactor","s","engineSettings","n","granted","particleBudget"],"mappings":";AAQO,MAAMA,EAAe;AAAA,EAG1B,YAESC,IAAQ,KAERC,IAAa,MACpB;AAHO,SAAA,QAAAD,GAEA,KAAA,aAAAC;AAAA,EACN;AAAA,EAHM;AAAA,EAEA;AAAA,EAND,OAAO;AAAA;AAAA,EAUf,IAAI,iBAAyB;AAC3B,UAAMC,IAAIC,EAAe,SAAA;AACzB,WAAOD,EAAE,aAAaA,EAAE,eAAe,KAAK,MAAM,KAAK,QAAQ,KAAK,UAAU,IAAI,KAAK;AAAA,EACzF;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAKE,GAAmB;AACtB,UAAMC,IAAU,KAAK,IAAI,GAAG,KAAK,IAAID,GAAG,KAAK,iBAAiB,KAAK,IAAI,CAAC;AACxE,gBAAK,QAAQC,GACNA;AAAA,EACT;AAAA;AAAA,EAGA,KAAKD,GAAiB;AACpB,SAAK,OAAO,KAAK,IAAI,GAAG,KAAK,OAAOA,CAAC;AAAA,EACvC;AACF;AAGO,MAAME,IAAiB,IAAIP,EAAA;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ParticleSim.js","sources":["../../src/particles/ParticleSim.ts"],"sourcesContent":["/**\n * The particles of one emitter as flat typed arrays (structure of arrays): position, velocity, age and lifetime,\n * rotation and spin, and a per-particle random draw the curves can use. Pure, no Pixi: the emitter draws what this\n * holds, and tests step it under node.\n *\n * Live particles are packed at indices `0 … count - 1`. A particle that dies is replaced by the last live one, so a\n * step never leaves holes and never allocates; the order of particles is therefore not stable, which drawing does\n * not need (every live index is written again each frame).\n */\nexport class ParticleSim {\n readonly x: Float32Array;\n readonly y: Float32Array;\n readonly vx: Float32Array;\n readonly vy: Float32Array;\n readonly age: Float32Array;\n readonly life: Float32Array;\n readonly rot: Float32Array;\n readonly spin: Float32Array;\n /** A random draw per particle (0–1), fixed for its life: size variance, flicker phase. */\n readonly seed: Float32Array;\n count = 0;\n\n constructor(readonly capacity: number) {\n this.x = new Float32Array(capacity);\n this.y = new Float32Array(capacity);\n this.vx = new Float32Array(capacity);\n this.vy = new Float32Array(capacity);\n this.age = new Float32Array(capacity);\n this.life = new Float32Array(capacity);\n this.rot = new Float32Array(capacity);\n this.spin = new Float32Array(capacity);\n this.seed = new Float32Array(capacity);\n }\n\n /** Adds a particle; returns its index, or -1 when the sim is full. `life` is in seconds. */\n spawn(x: number, y: number, vx: number, vy: number, life: number, rot: number, spin: number, seed: number): number {\n if (this.count >= this.capacity) return -1;\n const i = this.count++;\n this.x[i] = x;\n this.y[i] = y;\n this.vx[i] = vx;\n this.vy[i] = vy;\n this.age[i] = 0;\n this.life[i] = life > 0 ? life : 1e-3;\n this.rot[i] = rot;\n this.spin[i] = spin;\n this.seed[i] = seed;\n return i;\n }\n\n /**\n * Advances every particle by `dt` seconds: gravity (px/s²) added to velocity, then drag, then position. Drag is\n * the share of speed lost per second, applied as `exp(-drag × dt)` so it is the same at any frame rate. Returns how\n * many particles died this step.\n */\n step(dt: number, gravityX: number, gravityY: number, drag: number): number {\n const damp = drag > 0 ? Math.exp(-drag * dt) : 1;\n let died = 0;\n let i = 0;\n while (i < this.count) {\n const age = this.age[i]! + dt;\n if (age >= this.life[i]!) {\n this.removeAt(i);\n died++;\n continue; // the particle moved into `i` has not stepped yet\n }\n this.age[i] = age;\n const vx = (this.vx[i]! + gravityX * dt) * damp;\n const vy = (this.vy[i]! + gravityY * dt) * damp;\n this.vx[i] = vx;\n this.vy[i] = vy;\n this.x[i] = this.x[i]! + vx * dt;\n this.y[i] = this.y[i]! + vy * dt;\n this.rot[i] = this.rot[i]! + this.spin[i]! * dt;\n i++;\n }\n return died;\n }\n\n /** The share of its life particle `i` has lived (0–1). */\n progress(i: number): number {\n return this.age[i]! / this.life[i]!;\n }\n\n /** Every particle gone; returns how many there were. */\n clear(): number {\n const n = this.count;\n this.count = 0;\n return n;\n }\n\n private removeAt(i: number): void {\n const last = --this.count;\n if (i === last) return;\n this.x[i] = this.x[last]!;\n this.y[i] = this.y[last]!;\n this.vx[i] = this.vx[last]!;\n this.vy[i] = this.vy[last]!;\n this.age[i] = this.age[last]!;\n this.life[i] = this.life[last]!;\n this.rot[i] = this.rot[last]!;\n this.spin[i] = this.spin[last]!;\n this.seed[i] = this.seed[last]!;\n }\n}\n"],"names":["ParticleSim","capacity","x","y","vx","vy","life","rot","spin","seed","i","dt","gravityX","gravityY","drag","damp","died","age","n","last"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"particleCurves.js","sources":["../../src/particles/particleCurves.ts"],"sourcesContent":["/**\n * Values that change over a particle's life, sampled at `t` from 0 (born) to 1 (gone). A curve is one of:\n *\n * - a number: the same the whole life;\n * - `{ start, end }`: a straight line between the two;\n * - keyframes `[[t, value], …]` sorted by `t`: straight lines between them, held flat before the first and after\n * the last (a puff that swells fast then fades slowly is three keys, not a custom function).\n *\n * Pure and allocation-free: an emitter samples three curves per particle per frame.\n */\nexport type Curve = number | { readonly start: number; readonly end: number } | readonly (readonly [t: number, value: number])[];\n\n/** The same three shapes for colours (`0xrrggbb`), blended channel by channel. */\nexport type ColorCurve = Curve;\n\nexport function sampleCurve(curve: Curve, t: number): number {\n if (typeof curve === 'number') return curve;\n if (!isKeys(curve)) return curve.start + (curve.end - curve.start) * clamp01(t);\n const n = curve.length;\n if (n === 0) return 0;\n const first = curve[0]!;\n if (t <= first[0]) return first[1];\n for (let i = 1; i < n; i++) {\n const b = curve[i]!;\n if (t <= b[0]) {\n const a = curve[i - 1]!;\n const span = b[0] - a[0];\n return span <= 0 ? b[1] : a[1] + (b[1] - a[1]) * ((t - a[0]) / span);\n }\n }\n return curve[n - 1]![1];\n}\n\nexport function sampleColor(curve: ColorCurve, t: number): number {\n if (typeof curve === 'number') return curve;\n if (!isKeys(curve)) return lerpColor(curve.start, curve.end, clamp01(t));\n const n = curve.length;\n if (n === 0) return 0xffffff;\n const first = curve[0]!;\n if (t <= first[0]) return first[1];\n for (let i = 1; i < n; i++) {\n const b = curve[i]!;\n if (t <= b[0]) {\n const a = curve[i - 1]!;\n const span = b[0] - a[0];\n return span <= 0 ? b[1] : lerpColor(a[1], b[1], (t - a[0]) / span);\n }\n }\n return curve[n - 1]![1];\n}\n\n/** Two `0xrrggbb` colours mixed per channel, each channel rounded to a whole value. */\nexport function lerpColor(a: number, b: number, t: number): number {\n const k = clamp01(t);\n const r = Math.round(((a >> 16) & 0xff) + (((b >> 16) & 0xff) - ((a >> 16) & 0xff)) * k);\n const g = Math.round(((a >> 8) & 0xff) + (((b >> 8) & 0xff) - ((a >> 8) & 0xff)) * k);\n const bl = Math.round((a & 0xff) + ((b & 0xff) - (a & 0xff)) * k);\n return (r << 16) | (g << 8) | bl;\n}\n\nfunction isKeys(c: Exclude<Curve, number>): c is readonly (readonly [number, number])[] {\n return Array.isArray(c);\n}\n\nfunction clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n"],"names":["sampleCurve","curve","isKeys","clamp01","n","first","i","b","a","span","sampleColor","lerpColor","t","k","g","bl","c"],"mappings":"AAeO,SAASA,EAAYC,GAAc,GAAmB;AAC3D,MAAI,OAAOA,KAAU,SAAU,QAAOA;AACtC,MAAI,CAACC,EAAOD,CAAK,EAAG,QAAOA,EAAM,SAASA,EAAM,MAAMA,EAAM,SAASE,EAAQ,CAAC;AAC9E,QAAMC,IAAIH,EAAM;AAChB,MAAIG,MAAM,EAAG,QAAO;AACpB,QAAMC,IAAQJ,EAAM,CAAC;AACrB,MAAI,KAAKI,EAAM,CAAC,EAAG,QAAOA,EAAM,CAAC;AACjC,WAASC,IAAI,GAAGA,IAAIF,GAAGE,KAAK;AAC1B,UAAMC,IAAIN,EAAMK,CAAC;AACjB,QAAI,KAAKC,EAAE,CAAC,GAAG;AACb,YAAMC,IAAIP,EAAMK,IAAI,CAAC,GACfG,IAAOF,EAAE,CAAC,IAAIC,EAAE,CAAC;AACvB,aAAOC,KAAQ,IAAIF,EAAE,CAAC,IAAIC,EAAE,CAAC,KAAKD,EAAE,CAAC,IAAIC,EAAE,CAAC,OAAO,IAAIA,EAAE,CAAC,KAAKC;AAAA,IACjE;AAAA,EACF;AACA,SAAOR,EAAMG,IAAI,CAAC,EAAG,CAAC;AACxB;AAEO,SAASM,EAAYT,GAAmB,GAAmB;AAChE,MAAI,OAAOA,KAAU,SAAU,QAAOA;AACtC,MAAI,CAACC,EAAOD,CAAK,EAAG,QAAOU,EAAUV,EAAM,OAAOA,EAAM,KAAKE,EAAQ,CAAC,CAAC;AACvE,QAAMC,IAAIH,EAAM;AAChB,MAAIG,MAAM,EAAG,QAAO;AACpB,QAAMC,IAAQJ,EAAM,CAAC;AACrB,MAAI,KAAKI,EAAM,CAAC,EAAG,QAAOA,EAAM,CAAC;AACjC,WAASC,IAAI,GAAGA,IAAIF,GAAGE,KAAK;AAC1B,UAAMC,IAAIN,EAAMK,CAAC;AACjB,QAAI,KAAKC,EAAE,CAAC,GAAG;AACb,YAAMC,IAAIP,EAAMK,IAAI,CAAC,GACfG,IAAOF,EAAE,CAAC,IAAIC,EAAE,CAAC;AACvB,aAAOC,KAAQ,IAAIF,EAAE,CAAC,IAAII,EAAUH,EAAE,CAAC,GAAGD,EAAE,CAAC,IAAI,IAAIC,EAAE,CAAC,KAAKC,CAAI;AAAA,IACnE;AAAA,EACF;AACA,SAAOR,EAAMG,IAAI,CAAC,EAAG,CAAC;AACxB;AAGO,SAASO,EAAUH,GAAWD,GAAWK,GAAmB;AACjE,QAAMC,IAAIV,EAAQS,CAAC,GACb,IAAI,KAAK,OAAQJ,KAAK,KAAM,SAAWD,KAAK,KAAM,QAAUC,KAAK,KAAM,QAASK,CAAC,GACjFC,IAAI,KAAK,OAAQN,KAAK,IAAK,SAAWD,KAAK,IAAK,QAAUC,KAAK,IAAK,QAASK,CAAC,GAC9EE,IAAK,KAAK,OAAOP,IAAI,SAAUD,IAAI,QAASC,IAAI,QAASK,CAAC;AAChE,SAAQ,KAAK,KAAOC,KAAK,IAAKC;AAChC;AAEA,SAASb,EAAOc,GAAwE;AACtF,SAAO,MAAM,QAAQA,CAAC;AACxB;AAEA,SAASb,EAAQS,GAAmB;AAClC,SAAOA,IAAI,IAAI,IAAIA,IAAI,IAAI,IAAIA;AACjC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"particleSampling.js","sources":["../../src/particles/particleSampling.ts"],"sourcesContent":["/** A number picked at random between two (inclusive of the first), or a fixed one. */\nexport type Range = number | readonly [min: number, max: number];\n\n/** A value of `range` for one random draw `r` in [0, 1). */\nexport function sampleRange(range: Range, r: number): number {\n if (typeof range === 'number') return range;\n return range[0] + (range[1] - range[0]) * r;\n}\n\n/** `base` turned by up to `spread` either way: `r` 0 gives `base - spread`, 0.5 gives `base`. */\nexport function spreadAngle(base: number, spread: number, r: number): number {\n return base + (r * 2 - 1) * spread;\n}\n\n/**\n * Whole particles due after `dt` seconds at `rate` a second, given the fraction `carry` left over from before. Kept\n * as a fraction rather than rounded each frame, or 30 a second at 144 fps (0.21 a frame) would emit none at all.\n * Returns the count in the integer part and the new carry in the fraction: `n = Math.floor(v)`, `carry = v - n`.\n */\nexport function dueParticles(rate: number, dt: number, carry: number): number {\n if (!(rate > 0) || !(dt > 0)) return carry;\n return carry + rate * dt;\n}\n\n/**\n * `n` scaled by `factor` (reduced effects), rounded at random so a burst of 3 at 0.35 still gives one particle about\n * every time rather than never: the expected count stays `n × factor`.\n */\nexport function scaleCount(n: number, factor: number, r: number): number {\n if (factor >= 1) return n;\n const v = n * Math.max(0, factor);\n const whole = Math.floor(v);\n return whole + (r < v - whole ? 1 : 0);\n}\n\n/** A small seedable generator (mulberry32) for tests and replays; the emitter takes any `() => number`. */\nexport function seededRandom(seed: number): () => number {\n let s = seed >>> 0;\n return () => {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = s;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n"],"names":["sampleRange","range","r","spreadAngle","base","spread","dueParticles","rate","dt","carry","scaleCount","factor","v","whole","seededRandom","seed","s"],"mappings":"AAIO,SAASA,EAAYC,GAAcC,GAAmB;AAC3D,SAAI,OAAOD,KAAU,WAAiBA,IAC/BA,EAAM,CAAC,KAAKA,EAAM,CAAC,IAAIA,EAAM,CAAC,KAAKC;AAC5C;AAGO,SAASC,EAAYC,GAAcC,GAAgBH,GAAmB;AAC3E,SAAOE,KAAQF,IAAI,IAAI,KAAKG;AAC9B;AAOO,SAASC,EAAaC,GAAcC,GAAYC,GAAuB;AAC5E,SAAI,EAAEF,IAAO,MAAM,EAAEC,IAAK,KAAWC,IAC9BA,IAAQF,IAAOC;AACxB;AAMO,SAASE,EAAW,GAAWC,GAAgBT,GAAmB;AACvE,MAAIS,KAAU,EAAG,QAAO;AACxB,QAAMC,IAAI,IAAI,KAAK,IAAI,GAAGD,CAAM,GAC1BE,IAAQ,KAAK,MAAMD,CAAC;AAC1B,SAAOC,KAASX,IAAIU,IAAIC,IAAQ,IAAI;AACtC;AAGO,SAASC,EAAaC,GAA4B;AACvD,MAAIC,IAAID,MAAS;AACjB,SAAO,MAAM;AACX,IAAAC,IAAKA,IAAI,eAAgB;AACzB,QAAI,IAAIA;AACR,eAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC,GACnC,KAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE,KAC/B,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;"}
|
package/dist/physics/Body.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Body.js","sources":["../../src/physics/Body.ts"],"sourcesContent":["import { MAX_POLYGON_VERTICES, polygonData } from './polygon';\nimport type { BodyOptions, BodyType, Shape } from './types';\n\nlet nextBodyId = 1;\n\n/**\n * A rigid body: plain data and a few helpers, no drawing. Positions are the shape's centre in world pixels, y down;\n * velocities are px per second. Create bodies with `PhysicsWorld.add`, which also wakes them and puts them in the\n * broad phase.\n */\nexport class Body {\n readonly id = nextBodyId++;\n readonly type: BodyType;\n shape: Shape;\n x: number;\n y: number;\n /** Position at the start of the last world step: draw at `prev + (current - prev) × alpha`. */\n prevX: number;\n prevY: number;\n vx: number;\n vy: number;\n /** Radians, clockwise on screen. */\n angle: number;\n /** Angle at the start of the last world step, for drawing between steps like `prevX`. */\n prevAngle: number;\n /** Radians per second. */\n angularVelocity: number;\n /** Contacts and torques turn it (`BodyOptions.rotates`). */\n readonly rotates: boolean;\n /** Turning lost per second, as a share. */\n angularDamping: number;\n /** Torque gathered for the next step, cleared after it. */\n torque = 0;\n /** Moment of inertia about the centre (mass × px²); infinite for a body that does not rotate. */\n readonly inertia: number;\n readonly invInertia: number;\n /** Force gathered for the next step (px·mass/s²), cleared after it. */\n fx = 0;\n fy = 0;\n readonly mass: number;\n readonly invMass: number;\n restitution: number;\n friction: number;\n linearDamping: number;\n gravityScale: number;\n readonly sensor: boolean;\n layer: number;\n mask: number;\n userData: unknown;\n /** Box faces lying against more solid ground (`FACE_*` bits): never a contact normal, so seams do not snag. */\n internalFaces = 0;\n\n /** A sleeping body is not integrated and is treated as immovable until something wakes it. */\n awake = true;\n /** Seconds spent below the world's sleep speed. */\n restTime = 0;\n /** Standing on something: a solid contact whose normal points up at this body (refreshed every step). */\n grounded = false;\n /** What it stands on, while grounded. */\n ground: Body | null = null;\n groundNx = 0;\n groundNy = 0;\n\n // bounds, refreshed by `updateBounds`\n minX = 0;\n minY = 0;\n maxX = 0;\n maxY = 0;\n\n /** @internal cosine and sine of `angle`, refreshed by `updateBounds`. */\n cos = 1;\n sin = 0;\n /** @internal corners and outward face normals in world space (boxes and polygons), refreshed by `updateBounds`. */\n readonly wx = new Float64Array(MAX_POLYGON_VERTICES);\n readonly wy = new Float64Array(MAX_POLYGON_VERTICES);\n readonly wnx = new Float64Array(MAX_POLYGON_VERTICES);\n readonly wny = new Float64Array(MAX_POLYGON_VERTICES);\n /** @internal how many of the above are in use (0 for a circle). */\n corners = 0;\n\n /** @internal set by the world that holds it. */\n inWorld = false;\n /** @internal last ray that tested this body, so a ray crossing many cells tests it once. */\n rayStamp = 0;\n\n constructor(options: BodyOptions) {\n this.type = options.type ?? 'dynamic';\n this.shape = options.shape;\n this.x = this.prevX = options.x ?? 0;\n this.y = this.prevY = options.y ?? 0;\n this.vx = options.vx ?? 0;\n this.vy = options.vy ?? 0;\n this.angle = this.prevAngle = options.angle ?? 0;\n this.angularVelocity = options.angularVelocity ?? 0;\n this.angularDamping = options.angularDamping ?? 0.05;\n const mass = options.mass ?? 1;\n if (this.type === 'dynamic' && !(mass > 0)) throw new Error(`Body: a dynamic body needs a positive mass, got ${mass}`);\n this.mass = this.type === 'dynamic' ? mass : Infinity;\n this.invMass = this.type === 'dynamic' ? 1 / mass : 0;\n this.rotates = options.rotates === true && this.type !== 'static';\n const turns = this.rotates && this.type === 'dynamic';\n this.inertia = turns ? mass * inertiaPerMass(this.shape) : Infinity;\n this.invInertia = turns ? 1 / this.inertia : 0;\n this.restitution = options.restitution ?? 0;\n this.friction = options.friction ?? 0.5;\n this.linearDamping = options.linearDamping ?? 0;\n this.gravityScale = options.gravityScale ?? 1;\n this.sensor = options.sensor ?? false;\n this.layer = options.layer ?? 1;\n this.mask = options.mask ?? 0xffffffff;\n this.userData = options.userData;\n this.updateBounds();\n }\n\n get isDynamic(): boolean {\n return this.type === 'dynamic';\n }\n\n get isStatic(): boolean {\n return this.type === 'static';\n }\n\n /** Moves by itself this step: an awake dynamic body or any kinematic one. */\n get moving(): boolean {\n return this.type === 'kinematic' || (this.type === 'dynamic' && this.awake);\n }\n\n /** Inverse mass as the solver sees it: a sleeping body holds still like ground. */\n get solverInvMass(): number {\n return this.awake ? this.invMass : 0;\n }\n\n /** The smallest half size of the shape: how far it may move in one sub-step without passing through anything. */\n get minHalfExtent(): number {\n const s = this.shape;\n if (s.kind === 'circle') return s.radius;\n if (s.kind === 'box') return Math.min(s.halfWidth, s.halfHeight);\n return polygonData(s).inradius;\n }\n\n /** The furthest the shape reaches from the centre: how far a corner sweeps as the body turns. */\n get maxExtent(): number {\n const s = this.shape;\n if (s.kind === 'circle') return s.radius;\n if (s.kind === 'box') return Math.hypot(s.halfWidth, s.halfHeight);\n return polygonData(s).radius;\n }\n\n /** A box at no angle: the cheap axis-aligned tests apply to it. */\n get axisAligned(): boolean {\n return this.shape.kind === 'box' && this.angle === 0;\n }\n\n updateBounds(): void {\n const s = this.shape;\n if (s.kind === 'circle') {\n this.cos = Math.cos(this.angle);\n this.sin = Math.sin(this.angle);\n this.corners = 0;\n this.minX = this.x - s.radius;\n this.maxX = this.x + s.radius;\n this.minY = this.y - s.radius;\n this.maxY = this.y + s.radius;\n return;\n }\n if (s.kind === 'box' && this.angle === 0) {\n this.cos = 1;\n this.sin = 0;\n this.setBoxCorners(s.halfWidth, s.halfHeight);\n this.minX = this.x - s.halfWidth;\n this.maxX = this.x + s.halfWidth;\n this.minY = this.y - s.halfHeight;\n this.maxY = this.y + s.halfHeight;\n return;\n }\n const c = (this.cos = Math.cos(this.angle));\n const n = (this.sin = Math.sin(this.angle));\n if (s.kind === 'box') this.setBoxCorners(s.halfWidth, s.halfHeight);\n else {\n const d = polygonData(s);\n this.corners = d.vertices.length;\n for (let i = 0; i < d.vertices.length; i++) {\n this.wx[i] = d.vertices[i]!.x;\n this.wy[i] = d.vertices[i]!.y;\n this.wnx[i] = d.normals[i]!.x;\n this.wny[i] = d.normals[i]!.y;\n }\n }\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (let i = 0; i < this.corners; i++) {\n const lx = this.wx[i]!;\n const ly = this.wy[i]!;\n const x = this.x + c * lx - n * ly;\n const y = this.y + n * lx + c * ly;\n this.wx[i] = x;\n this.wy[i] = y;\n const nx = this.wnx[i]!;\n const ny = this.wny[i]!;\n this.wnx[i] = c * nx - n * ny;\n this.wny[i] = n * nx + c * ny;\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n this.minX = minX;\n this.maxX = maxX;\n this.minY = minY;\n this.maxY = maxY;\n }\n\n /** Box corners in local space (top, right, bottom, left faces), or in world space when the box sits at no angle. */\n private setBoxCorners(hw: number, hh: number): void {\n const ox = this.angle === 0 ? this.x : 0;\n const oy = this.angle === 0 ? this.y : 0;\n this.corners = 4;\n this.wx[0] = ox - hw;\n this.wy[0] = oy - hh;\n this.wx[1] = ox + hw;\n this.wy[1] = oy - hh;\n this.wx[2] = ox + hw;\n this.wy[2] = oy + hh;\n this.wx[3] = ox - hw;\n this.wy[3] = oy + hh;\n this.wnx[0] = 0;\n this.wny[0] = -1;\n this.wnx[1] = 1;\n this.wny[1] = 0;\n this.wnx[2] = 0;\n this.wny[2] = 1;\n this.wnx[3] = -1;\n this.wny[3] = 0;\n }\n\n wake(): void {\n if (this.type === 'static') return;\n this.awake = true;\n this.restTime = 0;\n }\n\n setVelocity(vx: number, vy: number): void {\n this.vx = vx;\n this.vy = vy;\n this.wake();\n }\n\n /**\n * An instant change of momentum (a jump, a hit): velocity changes by `impulse / mass`. Given a world point\n * (`px`, `py`) off the centre, a body that rotates is also set turning.\n */\n applyImpulse(ix: number, iy: number, px?: number, py?: number): void {\n if (this.type !== 'dynamic') return;\n this.vx += ix * this.invMass;\n this.vy += iy * this.invMass;\n if (px !== undefined && py !== undefined) this.angularVelocity += ((px - this.x) * iy - (py - this.y) * ix) * this.invInertia;\n this.wake();\n }\n\n /** A force held for the next step (thrust, wind); it is cleared once the step has used it. At a world point, it also turns the body. */\n applyForce(fx: number, fy: number, px?: number, py?: number): void {\n if (this.type !== 'dynamic') return;\n this.fx += fx;\n this.fy += fy;\n if (px !== undefined && py !== undefined) this.torque += (px - this.x) * fy - (py - this.y) * fx;\n this.wake();\n }\n\n /** A turning force held for the next step (mass × px² / s², clockwise on screen when positive). */\n applyTorque(torque: number): void {\n if (this.type !== 'dynamic' || !this.rotates) return;\n this.torque += torque;\n this.wake();\n }\n\n /** An instant change of spin: angular velocity changes by `impulse / inertia`. */\n applyAngularImpulse(impulse: number): void {\n if (this.type !== 'dynamic') return;\n this.angularVelocity += impulse * this.invInertia;\n this.wake();\n }\n\n /** Sets the spin (a kinematic body turns at it; a dynamic one that rotates starts at it). */\n setAngularVelocity(w: number): void {\n this.angularVelocity = w;\n this.wake();\n }\n\n /** Velocity of the body's material at a world point: its own plus what its spin adds there. */\n velocityAt(px: number, py: number): { x: number; y: number } {\n const w = this.angularVelocity;\n return { x: this.vx - w * (py - this.y), y: this.vy + w * (px - this.x) };\n }\n\n /**\n * Puts the body somewhere without sweeping it there; the drawn position jumps too (no interpolation across it).\n * A static body moved this way must be followed by `PhysicsWorld.staticsChanged()`.\n */\n setPosition(x: number, y: number, angle = this.angle): void {\n this.x = this.prevX = x;\n this.y = this.prevY = y;\n this.angle = this.prevAngle = angle;\n this.updateBounds();\n this.wake();\n }\n\n /** Turns the body to `angle` without sweeping it there (like `setPosition`). */\n setAngle(angle: number): void {\n this.setPosition(this.x, this.y, angle);\n }\n\n /** A world point in the body's own frame (before its turn). */\n toLocal(px: number, py: number): { x: number; y: number } {\n const dx = px - this.x;\n const dy = py - this.y;\n return { x: this.cos * dx + this.sin * dy, y: -this.sin * dx + this.cos * dy };\n }\n\n /** A point of the body's own frame in the world. */\n toWorld(lx: number, ly: number): { x: number; y: number } {\n return { x: this.x + this.cos * lx - this.sin * ly, y: this.y + this.sin * lx + this.cos * ly };\n }\n}\n\n/** Moment of inertia per unit of mass about the centre: a solid disc, a solid box, a solid polygon. */\nfunction inertiaPerMass(s: Shape): number {\n if (s.kind === 'circle') return (s.radius * s.radius) / 2;\n if (s.kind === 'box') return ((2 * s.halfWidth) ** 2 + (2 * s.halfHeight) ** 2) / 12;\n return polygonData(s).inertiaPerMass;\n}\n"],"names":["nextBodyId","Body","MAX_POLYGON_VERTICES","options","mass","turns","inertiaPerMass","s","polygonData","c","n","d","i","minX","minY","maxX","maxY","lx","ly","x","nx","ny","hw","hh","ox","oy","vx","vy","ix","iy","px","py","fx","fy","torque","impulse","w","y","angle","dx","dy"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BroadPhase.js","sources":["../../src/physics/BroadPhase.ts"],"sourcesContent":["import type { Body } from './Body';\nimport { SpatialHash } from './SpatialHash';\n\n/**\n * Two spatial hashes: static bodies, rebuilt only when they change (a level's ground is thousands of tiles merged\n * into a few hundred boxes and never moves), and everything else, rebuilt every sub-step. Pairs are found from the\n * moving side only: two sleeping or static bodies are never tested against each other.\n */\nexport class BroadPhase {\n readonly statics: SpatialHash;\n readonly movers: SpatialHash;\n private staticsDirty = true;\n\n constructor(\n cellSize: number,\n private readonly margin: number,\n ) {\n this.statics = new SpatialHash(cellSize);\n this.movers = new SpatialHash(cellSize);\n }\n\n markStaticsDirty(): void {\n this.staticsDirty = true;\n }\n\n /** Re-lists every body; statics only if they changed. Bounds must be current. */\n rebuild(bodies: readonly Body[]): void {\n if (this.staticsDirty) {\n this.statics.clear();\n for (const b of bodies) if (b.isStatic) this.statics.insert(b, this.margin);\n this.staticsDirty = false;\n }\n this.movers.clear();\n for (const b of bodies) if (!b.isStatic) this.movers.insert(b, this.margin);\n }\n\n /**\n * Calls `visit` once for every pair whose bounds (grown by the margin) overlap and in which at least one body\n * moves. A pair sharing several cells is visited only from the cell holding the corner of their overlap, which\n * needs no set of pairs seen.\n */\n pairs(bodies: readonly Body[], visit: (a: Body, b: Body) => void): void {\n const m = this.margin;\n const hash = this.movers;\n for (const a of bodies) {\n if (!a.moving || !a.inWorld) continue;\n const x0 = hash.cellOf(a.minX - m);\n const x1 = hash.cellOf(a.maxX + m);\n const y0 = hash.cellOf(a.minY - m);\n const y1 = hash.cellOf(a.maxY + m);\n for (let cy = y0; cy <= y1; cy++) {\n for (let cx = x0; cx <= x1; cx++) {\n const near = this.movers.at(cx, cy);\n if (near) {\n for (const b of near) {\n // two moving bodies meet from the lower id's side only\n if (b === a || (b.moving && b.id < a.id)) continue;\n if (this.owns(a, b, cx, cy)) visit(a, b);\n }\n }\n const ground = this.statics.at(cx, cy);\n if (ground) for (const b of ground) if (this.owns(a, b, cx, cy)) visit(a, b);\n }\n }\n }\n }\n\n /** The pair overlaps and (cx, cy) is the cell of its overlap's top-left corner. */\n private owns(a: Body, b: Body, cx: number, cy: number): boolean {\n const m = this.margin;\n if (a.maxX + m < b.minX - m || b.maxX + m < a.minX - m || a.maxY + m < b.minY - m || b.maxY + m < a.minY - m) return false;\n const hash = this.movers;\n return hash.cellOf(Math.max(a.minX, b.minX) - m) === cx && hash.cellOf(Math.max(a.minY, b.minY) - m) === cy;\n }\n}\n"],"names":["BroadPhase","cellSize","margin","SpatialHash","bodies","b","visit","m","hash","a","x0","x1","y0","y1","cy","cx","near","ground"],"mappings":";AAQO,MAAMA,EAAW;AAAA,EAKtB,YACEC,GACiBC,GACjB;AADiB,SAAA,SAAAA,GAEjB,KAAK,UAAU,IAAIC,EAAYF,CAAQ,GACvC,KAAK,SAAS,IAAIE,EAAYF,CAAQ;AAAA,EACxC;AAAA,EAJmB;AAAA,EANV;AAAA,EACA;AAAA,EACD,eAAe;AAAA,EAUvB,mBAAyB;AACvB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,QAAQG,GAA+B;AACrC,QAAI,KAAK,cAAc;AACrB,WAAK,QAAQ,MAAA;AACb,iBAAWC,KAAKD,EAAQ,CAAIC,EAAE,iBAAe,QAAQ,OAAOA,GAAG,KAAK,MAAM;AAC1E,WAAK,eAAe;AAAA,IACtB;AACA,SAAK,OAAO,MAAA;AACZ,eAAWA,KAAKD,EAAQ,CAAKC,EAAE,YAAU,KAAK,OAAO,OAAOA,GAAG,KAAK,MAAM;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAMD,GAAyBE,GAAyC;AACtE,UAAMC,IAAI,KAAK,QACTC,IAAO,KAAK;AAClB,eAAWC,KAAKL,GAAQ;AACtB,UAAI,CAACK,EAAE,UAAU,CAACA,EAAE,QAAS;AAC7B,YAAMC,IAAKF,EAAK,OAAOC,EAAE,OAAOF,CAAC,GAC3BI,IAAKH,EAAK,OAAOC,EAAE,OAAOF,CAAC,GAC3BK,IAAKJ,EAAK,OAAOC,EAAE,OAAOF,CAAC,GAC3BM,IAAKL,EAAK,OAAOC,EAAE,OAAOF,CAAC;AACjC,eAASO,IAAKF,GAAIE,KAAMD,GAAIC;AAC1B,iBAASC,IAAKL,GAAIK,KAAMJ,GAAII,KAAM;AAChC,gBAAMC,IAAO,KAAK,OAAO,GAAGD,GAAID,CAAE;AAClC,cAAIE;AACF,uBAAWX,KAAKW;AAEd,cAAIX,MAAMI,KAAMJ,EAAE,UAAUA,EAAE,KAAKI,EAAE,MACjC,KAAK,KAAKA,GAAGJ,GAAGU,GAAID,CAAE,KAAGR,EAAMG,GAAGJ,CAAC;AAG3C,gBAAMY,IAAS,KAAK,QAAQ,GAAGF,GAAID,CAAE;AACrC,cAAIG;AAAQ,uBAAWZ,KAAKY,EAAQ,CAAI,KAAK,KAAKR,GAAGJ,GAAGU,GAAID,CAAE,KAAGR,EAAMG,GAAGJ,CAAC;AAAA,QAC7E;AAAA,IAEJ;AAAA,EACF;AAAA;AAAA,EAGQ,KAAKI,GAASJ,GAASU,GAAYD,GAAqB;AAC9D,UAAMP,IAAI,KAAK;AACf,QAAIE,EAAE,OAAOF,IAAIF,EAAE,OAAOE,KAAKF,EAAE,OAAOE,IAAIE,EAAE,OAAOF,KAAKE,EAAE,OAAOF,IAAIF,EAAE,OAAOE,KAAKF,EAAE,OAAOE,IAAIE,EAAE,OAAOF,EAAG,QAAO;AACrH,UAAMC,IAAO,KAAK;AAClB,WAAOA,EAAK,OAAO,KAAK,IAAIC,EAAE,MAAMJ,EAAE,IAAI,IAAIE,CAAC,MAAMQ,KAAMP,EAAK,OAAO,KAAK,IAAIC,EAAE,MAAMJ,EAAE,IAAI,IAAIE,CAAC,MAAMO;AAAA,EAC3G;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Contact.js","sources":["../../src/physics/Contact.ts"],"sourcesContent":["import type { Body } from './Body';\nimport type { ContactInfo, Vec2 } from './types';\n\n/** One point of a contact and the solver's terms for it. */\nexport class ContactPoint {\n /** World position when measured, and the depth there (px, < 0 a gap). */\n x = 0;\n y = 0;\n depth = 0;\n /** Which corners or faces made it: matched from step to step to carry the impulses over. */\n id = 0;\n /** Accumulated normal and friction impulses. */\n jn = 0;\n jt = 0;\n /** Per sub-step solver terms. */\n massN = 0;\n massT = 0;\n target = 0;\n /** Offsets from each body's centre, refreshed every sub-step. */\n rax = 0;\n ray = 0;\n rbx = 0;\n rby = 0;\n /** The point in each body's own frame when measured, so position correction can track its depth as they move and turn. */\n lax = 0;\n lay = 0;\n lbx = 0;\n lby = 0;\n}\n\n/**\n * One touching pair, kept from step to step while they touch: the impulses it ended the last step with start the\n * next one (warm starting), which is what lets a stack settle in a few solver iterations instead of shivering.\n */\nexport class Contact implements ContactInfo {\n nx = 0;\n ny = 1;\n depth = 0;\n /** One or two points; only the first `count` are in use. */\n readonly pts: [ContactPoint, ContactPoint] = [new ContactPoint(), new ContactPoint()];\n count = 0;\n mu = 0;\n /** Solved this sub-step (false when the pre-solve hook turned it off). */\n enabled = true;\n /** The world step and sub-step that last found it. */\n seenStep = -1;\n seenSub = -1;\n /** Reported as touching (a `begin` went out and no `end` yet). */\n reported = false;\n\n constructor(\n readonly a: Body,\n readonly b: Body,\n readonly key: number,\n ) {}\n\n get sensor(): boolean {\n return this.a.sensor || this.b.sensor;\n }\n\n /** Where the two touch, world px. */\n get points(): Vec2[] {\n const out: Vec2[] = [];\n for (let i = 0; i < this.count; i++) out.push({ x: this.pts[i]!.x, y: this.pts[i]!.y });\n return out;\n }\n\n /** Accumulated normal impulse over the points: how hard the pair pressed together on the last sub-step. */\n get normalImpulse(): number {\n let j = 0;\n for (let i = 0; i < this.count; i++) j += this.pts[i]!.jn;\n return j;\n }\n\n /** The other body of the pair. */\n other(body: Body): Body {\n return body === this.a ? this.b : this.a;\n }\n\n /** A stable number for the pair, whichever order it is asked in. */\n static keyOf(a: Body, b: Body): number {\n const lo = a.id < b.id ? a.id : b.id;\n const hi = a.id < b.id ? b.id : a.id;\n return lo * 0x4000000 + hi;\n }\n}\n"],"names":["ContactPoint","Contact","a","b","key","out","i","j","body","lo","hi"],"mappings":"AAIO,MAAMA,EAAa;AAAA;AAAA,EAExB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,QAAQ;AAAA;AAAA,EAER,KAAK;AAAA;AAAA,EAEL,KAAK;AAAA,EACL,KAAK;AAAA;AAAA,EAEL,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAET,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAEN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAMO,MAAMC,EAA+B;AAAA,EAgB1C,YACWC,GACAC,GACAC,GACT;AAHS,SAAA,IAAAF,GACA,KAAA,IAAAC,GACA,KAAA,MAAAC;AAAA,EACR;AAAA,EAHQ;AAAA,EACA;AAAA,EACA;AAAA,EAlBX,KAAK;AAAA,EACL,KAAK;AAAA,EACL,QAAQ;AAAA;AAAA,EAEC,MAAoC,CAAC,IAAIJ,KAAgB,IAAIA,GAAc;AAAA,EACpF,QAAQ;AAAA,EACR,KAAK;AAAA;AAAA,EAEL,UAAU;AAAA;AAAA,EAEV,WAAW;AAAA,EACX,UAAU;AAAA;AAAA,EAEV,WAAW;AAAA,EAQX,IAAI,SAAkB;AACpB,WAAO,KAAK,EAAE,UAAU,KAAK,EAAE;AAAA,EACjC;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,UAAMK,IAAc,CAAA;AACpB,aAASC,IAAI,GAAGA,IAAI,KAAK,OAAOA,IAAK,CAAAD,EAAI,KAAK,EAAE,GAAG,KAAK,IAAIC,CAAC,EAAG,GAAG,GAAG,KAAK,IAAIA,CAAC,EAAG,GAAG;AACtF,WAAOD;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,gBAAwB;AAC1B,QAAIE,IAAI;AACR,aAASD,IAAI,GAAGA,IAAI,KAAK,OAAOA,IAAK,CAAAC,KAAK,KAAK,IAAID,CAAC,EAAG;AACvD,WAAOC;AAAA,EACT;AAAA;AAAA,EAGA,MAAMC,GAAkB;AACtB,WAAOA,MAAS,KAAK,IAAI,KAAK,IAAI,KAAK;AAAA,EACzC;AAAA;AAAA,EAGA,OAAO,MAAMN,GAASC,GAAiB;AACrC,UAAMM,IAAKP,EAAE,KAAKC,EAAE,KAAKD,EAAE,KAAKC,EAAE,IAC5BO,IAAKR,EAAE,KAAKC,EAAE,KAAKA,EAAE,KAAKD,EAAE;AAClC,WAAOO,IAAK,WAAYC;AAAA,EAC1B;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContactSolver.js","sources":["../../src/physics/ContactSolver.ts"],"sourcesContent":["import type { Body } from './Body';\nimport type { Contact, ContactPoint } from './Contact';\n\n/** Tuning the solver reads; `PhysicsWorld` owns the values. */\nexport interface SolverSettings {\n /** Approach speed (px/s) below which nothing bounces, so a resting body does not hop on gravity's own step. */\n restitutionThreshold: number;\n /** Overlap (px) left alone by position correction: a little keeps resting contacts alive and steady. */\n slop: number;\n /** Share of the remaining overlap removed per position iteration. */\n correction: number;\n /** The most one position iteration moves a pair apart (px), so a deep overlap is undone over a few steps, not flung. */\n maxCorrection: number;\n}\n\nconst cross = (ax: number, ay: number, bx: number, by: number): number => ax * by - ay * bx;\n\n/** Angular inverse mass as the solver sees it: a sleeping body holds still like ground. */\nconst solverInvInertia = (b: Body): number => (b.awake ? b.invInertia : 0);\n\n/**\n * Sequential impulses: each contact point is a normal constraint (no approach, plus bounce) and a friction constraint\n * clamped by its normal impulse (Coulomb), applied at the point so a body that rotates is turned by it. Penetration\n * is not fed back into the velocities (no Baumgarte energy): it is removed afterwards by moving positions and angles\n * only, tracked as the bodies move (a \"split impulse\" in its simplest form), so resting bodies do not bounce off their\n * own correction. Bodies that do not rotate have no inverse inertia, and every angular term drops out.\n */\nexport class ContactSolver {\n constructor(private readonly settings: SolverSettings) {}\n\n /** Mass, friction and the velocity target of every enabled contact point; then the impulses carried over from the last sub-step are applied (warm start). */\n prepare(contacts: readonly Contact[], dt: number): void {\n const { restitutionThreshold } = this.settings;\n for (const c of contacts) {\n if (!c.enabled) continue;\n const a = c.a;\n const b = c.b;\n const ia = a.solverInvMass;\n const ib = b.solverInvMass;\n const iia = solverInvInertia(a);\n const iib = solverInvInertia(b);\n c.mu = Math.sqrt(a.friction * b.friction);\n const nx = c.nx;\n const ny = c.ny;\n const tx = -ny;\n const ty = nx;\n const e = Math.max(a.restitution, b.restitution);\n for (let i = 0; i < c.count; i++) {\n const p = c.pts[i]!;\n p.rax = p.x - a.x;\n p.ray = p.y - a.y;\n p.rbx = p.x - b.x;\n p.rby = p.y - b.y;\n const rnA = cross(p.rax, p.ray, nx, ny);\n const rnB = cross(p.rbx, p.rby, nx, ny);\n const kn = ia + ib + iia * rnA * rnA + iib * rnB * rnB;\n p.massN = kn > 0 ? 1 / kn : 0;\n const rtA = cross(p.rax, p.ray, tx, ty);\n const rtB = cross(p.rbx, p.rby, tx, ty);\n const kt = ia + ib + iia * rtA * rtA + iib * rtB * rtB;\n p.massT = kt > 0 ? 1 / kt : 0;\n const vn = relX(a, b, p) * nx + relY(a, b, p) * ny;\n if (p.depth < 0) {\n // still a hair apart: let them close the gap this sub-step, no more (a speculative contact)\n p.target = p.depth / dt;\n } else {\n p.target = vn < -restitutionThreshold ? -e * vn : 0;\n }\n apply(a, b, p, p.jn * nx + p.jt * tx, p.jn * ny + p.jt * ty, ia, ib, iia, iib);\n }\n }\n }\n\n solveVelocities(contacts: readonly Contact[]): void {\n for (const c of contacts) {\n if (!c.enabled) continue;\n const a = c.a;\n const b = c.b;\n const ia = a.solverInvMass;\n const ib = b.solverInvMass;\n const iia = solverInvInertia(a);\n const iib = solverInvInertia(b);\n const nx = c.nx;\n const ny = c.ny;\n const tx = -ny;\n const ty = nx;\n for (let i = 0; i < c.count; i++) {\n const p = c.pts[i]!;\n if (p.massN === 0) continue;\n // friction first, bounded by the normal impulse of the last iteration\n const vt = relX(a, b, p) * tx + relY(a, b, p) * ty;\n const maxT = c.mu * p.jn;\n const jtNew = Math.max(-maxT, Math.min(maxT, p.jt - vt * p.massT));\n const dT = jtNew - p.jt;\n p.jt = jtNew;\n apply(a, b, p, dT * tx, dT * ty, ia, ib, iia, iib);\n\n const vn = relX(a, b, p) * nx + relY(a, b, p) * ny;\n const jnNew = Math.max(0, p.jn + (p.target - vn) * p.massN);\n const dn = jnNew - p.jn;\n p.jn = jnNew;\n apply(a, b, p, dn * nx, dn * ny, ia, ib, iia, iib);\n }\n }\n }\n\n /** Pushes overlapping pairs apart along their normals, by position (and angle) only. */\n solvePositions(contacts: readonly Contact[]): void {\n const { slop, correction, maxCorrection } = this.settings;\n for (const c of contacts) {\n if (!c.enabled) continue;\n const a = c.a;\n const b = c.b;\n const ia = a.solverInvMass;\n const ib = b.solverInvMass;\n const iia = solverInvInertia(a);\n const iib = solverInvInertia(b);\n const nx = c.nx;\n const ny = c.ny;\n for (let i = 0; i < c.count; i++) {\n const p = c.pts[i]!;\n // where the point's two anchors are now: their drift apart along the normal is what the depth has lost\n const ca = a.rotates ? Math.cos(a.angle) : a.cos;\n const sa = a.rotates ? Math.sin(a.angle) : a.sin;\n const cb = b.rotates ? Math.cos(b.angle) : b.cos;\n const sb = b.rotates ? Math.sin(b.angle) : b.sin;\n const rax = ca * p.lax - sa * p.lay;\n const ray = sa * p.lax + ca * p.lay;\n const rbx = cb * p.lbx - sb * p.lby;\n const rby = sb * p.lbx + cb * p.lby;\n const moved = (b.x + rbx - (a.x + rax)) * nx + (b.y + rby - (a.y + ray)) * ny;\n const depth = p.depth - moved;\n if (depth <= slop) continue;\n const rnA = cross(rax, ray, nx, ny);\n const rnB = cross(rbx, rby, nx, ny);\n const k = ia + ib + iia * rnA * rnA + iib * rnB * rnB;\n if (k === 0) continue;\n const push = Math.min(maxCorrection, correction * (depth - slop)) / k;\n const px = push * nx;\n const py = push * ny;\n a.x -= px * ia;\n a.y -= py * ia;\n b.x += px * ib;\n b.y += py * ib;\n if (iia > 0) a.angle -= iia * cross(rax, ray, px, py);\n if (iib > 0) b.angle += iib * cross(rbx, rby, px, py);\n }\n }\n }\n}\n\n/** Velocity of b's material at the point less a's. */\nfunction relX(a: Body, b: Body, p: ContactPoint): number {\n return b.vx - b.angularVelocity * p.rby - (a.vx - a.angularVelocity * p.ray);\n}\n\nfunction relY(a: Body, b: Body, p: ContactPoint): number {\n return b.vy + b.angularVelocity * p.rbx - (a.vy + a.angularVelocity * p.rax);\n}\n\n/** Impulse (px, py) on b at the point, its opposite on a. */\nfunction apply(a: Body, b: Body, p: ContactPoint, px: number, py: number, ia: number, ib: number, iia: number, iib: number): void {\n a.vx -= px * ia;\n a.vy -= py * ia;\n b.vx += px * ib;\n b.vy += py * ib;\n if (iia > 0) a.angularVelocity -= iia * cross(p.rax, p.ray, px, py);\n if (iib > 0) b.angularVelocity += iib * cross(p.rbx, p.rby, px, py);\n}\n"],"names":["cross","ax","ay","bx","by","solverInvInertia","b","ContactSolver","settings","contacts","dt","restitutionThreshold","ia","ib","iia","iib","nx","ny","tx","ty","e","i","p","rnA","rnB","kn","rtA","rtB","kt","vn","relX","relY","apply","c","a","vt","maxT","jtNew","dT","jnNew","dn","slop","correction","maxCorrection","ca","sa","cb","sb","rax","ray","rbx","rby","moved","depth","k","push","px","py"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PhysicsWorld.js","sources":["../../src/physics/PhysicsWorld.ts"],"sourcesContent":["import { EventBus } from '../events/EventBus';\nimport { Body } from './Body';\nimport { BroadPhase } from './BroadPhase';\nimport { collide, emptyManifold, type Manifold } from './collide';\nimport { Contact } from './Contact';\nimport { ContactSolver, type SolverSettings } from './ContactSolver';\nimport { info, setGround } from './contactReport';\nimport type { PhysicsWorldOptions } from './PhysicsWorldOptions';\nimport { castRay } from './raycast';\nimport type { BodyOptions, ContactInfo, PhysicsEvents, RayHit, RaycastOptions } from './types';\n\n/**\n * A rigid-body world for platformers and physics toys: dynamic, static and kinematic circles, boxes and convex\n * polygons, turned by contacts when made with `rotates: true`, stepped at a fixed dt (`physicsSystem` runs it as a\n * `fixed` system). Each step: forces and gravity (semi-implicit Euler), broad phase, narrow phase, sequential impulses\n * with warm starting, then positions, then overlap removed by moving positions. Fast bodies are handled by\n * sub-stepping, not swept tests.\n *\n * Not deterministic across machines: the solver's results depend on body order and floating point, which is fine for\n * a single player's world but not for one a server must replay (use `net/movement` for that).\n */\nexport class PhysicsWorld {\n gravityX: number;\n gravityY: number;\n readonly events = new EventBus<PhysicsEvents>();\n /** Called for each touching solid pair every sub-step; returning false skips its response this sub-step (one-way platforms). */\n preSolve: ((contact: Contact) => boolean) | null = null;\n private readonly list: Body[] = [];\n private readonly broad: BroadPhase;\n private readonly solver: ContactSolver;\n private readonly contacts = new Map<number, Contact>();\n private readonly active: Contact[] = [];\n private readonly manifold = emptyManifold();\n private readonly removals: Body[] = [];\n private readonly settings: SolverSettings;\n private readonly velocityIterations: number;\n private readonly positionIterations: number;\n private readonly maxSubsteps: number;\n private readonly maxSpeed: number;\n private readonly margin: number;\n private readonly groundNormal: number;\n private readonly allowSleep: boolean;\n private readonly sleepSpeed: number;\n private readonly sleepTime: number;\n private stepCount = 0;\n private subCount = 0;\n private stepping = false;\n\n constructor(options: PhysicsWorldOptions = {}) {\n this.gravityX = options.gravityX ?? 0;\n this.gravityY = options.gravityY ?? 980;\n this.margin = options.contactMargin ?? 0.5;\n this.settings = {\n restitutionThreshold: options.restitutionThreshold ?? 40,\n slop: options.slop ?? 0.05,\n correction: options.correction ?? 0.4,\n maxCorrection: options.maxCorrection ?? 4,\n };\n this.solver = new ContactSolver(this.settings);\n this.broad = new BroadPhase(options.cellSize ?? 64, this.margin);\n this.velocityIterations = options.velocityIterations ?? 8;\n this.positionIterations = options.positionIterations ?? 3;\n this.maxSubsteps = Math.max(1, options.maxSubsteps ?? 16);\n this.maxSpeed = options.maxSpeed ?? 4000;\n this.groundNormal = options.groundNormal ?? 0.7;\n this.allowSleep = options.allowSleep ?? false;\n this.sleepSpeed = options.sleepSpeed ?? 4;\n this.sleepTime = options.sleepTime ?? 0.5;\n }\n\n /** Adds a body (or makes one from options) and returns it. */\n add(body: Body | BodyOptions): Body {\n const b = body instanceof Body ? body : new Body(body);\n if (b.inWorld) return b;\n b.inWorld = true;\n b.updateBounds();\n b.wake();\n this.list.push(b);\n if (b.isStatic) this.broad.markStaticsDirty();\n return b;\n }\n\n addAll(bodies: Iterable<Body>): void {\n for (const b of bodies) this.add(b);\n }\n\n /** Takes a body out; its contacts end (with `end` events). During a step it waits until the step is over. */\n remove(body: Body): void {\n if (!body.inWorld) return;\n if (this.stepping) {\n this.removals.push(body);\n return;\n }\n body.inWorld = false;\n const i = this.list.indexOf(body);\n if (i >= 0) this.list.splice(i, 1);\n if (body.isStatic) this.broad.markStaticsDirty();\n body.grounded = false;\n body.ground = null;\n const ended: ContactInfo[] = [];\n for (const [key, c] of this.contacts) {\n if (c.a !== body && c.b !== body) continue;\n c.other(body).wake();\n if (c.reported) ended.push(info(c));\n this.contacts.delete(key);\n }\n for (const e of ended) this.events.emit('end', e);\n }\n\n /** Call after moving, resizing or retyping a static body by hand. */\n staticsChanged(): void {\n this.broad.markStaticsDirty();\n }\n\n get bodyCount(): number {\n return this.list.length;\n }\n\n /**\n * Every body in the world, in the order added: for tools (a debug drawing, an inspector) that would otherwise keep\n * a second list beside the world's. Read it, never hold it across a step: adding and removing change it in place.\n */\n get bodies(): readonly Body[] {\n return this.list;\n }\n\n /** Advances the world by `dt` seconds; call it with the fixed step (`FixedLoop.stepSeconds`). */\n step(dt: number): void {\n if (!(dt > 0) || !Number.isFinite(dt)) return;\n this.stepping = true;\n try {\n this.stepCount++;\n for (const b of this.list) {\n b.prevX = b.x;\n b.prevY = b.y;\n b.prevAngle = b.angle;\n }\n const n = this.substepsFor(dt);\n for (let i = 0; i < n; i++) this.substep(dt / n);\n for (const b of this.list) {\n b.fx = 0;\n b.fy = 0;\n b.torque = 0;\n }\n if (this.allowSleep) this.updateSleep(dt);\n } finally {\n this.stepping = false;\n }\n this.finishContacts();\n for (const b of this.removals.splice(0)) this.remove(b);\n }\n\n /** The first body on the segment from (x0, y0) to (x1, y1). Bodies the ray starts inside are skipped. */\n raycast(x0: number, y0: number, x1: number, y1: number, options?: RaycastOptions): RayHit | null {\n for (const b of this.list) if (!b.isStatic) b.updateBounds();\n this.broad.rebuild(this.list);\n return castRay([this.broad.statics, this.broad.movers], x0, y0, x1, y1, options);\n }\n\n /** The pairs touching `body` after the last step (sensors' overlaps included). */\n contactsOf(body: Body, out: ContactInfo[] = []): ContactInfo[] {\n out.length = 0;\n for (const c of this.contacts.values()) if (c.reported && (c.a === body || c.b === body)) out.push(c);\n return out;\n }\n\n /** Whether `body` stood on something at the end of the last step (also `body.grounded`). */\n isGrounded(body: Body): boolean {\n return body.grounded;\n }\n\n private substepsFor(dt: number): number {\n const gx = this.gravityX;\n const gy = this.gravityY;\n let worst = 0;\n for (const b of this.list) {\n if (!b.moving) continue;\n const g = b.isDynamic ? Math.hypot(gx, gy) * Math.abs(b.gravityScale) * dt : 0;\n // a turning body's corners sweep too\n const speed = Math.min(this.maxSpeed, Math.hypot(b.vx, b.vy) + g + Math.abs(b.angularVelocity) * b.maxExtent);\n worst = Math.max(worst, (speed * dt) / Math.max(0.5, b.minHalfExtent));\n }\n return Math.min(this.maxSubsteps, Math.max(1, Math.ceil(worst)));\n }\n\n private substep(h: number): void {\n this.subCount++;\n const max2 = this.maxSpeed * this.maxSpeed;\n for (const b of this.list) {\n if (!b.isDynamic || !b.awake) continue;\n b.vx += (this.gravityX * b.gravityScale + b.fx * b.invMass) * h;\n b.vy += (this.gravityY * b.gravityScale + b.fy * b.invMass) * h;\n if (b.linearDamping > 0) {\n const k = 1 / (1 + b.linearDamping * h);\n b.vx *= k;\n b.vy *= k;\n }\n const s2 = b.vx * b.vx + b.vy * b.vy;\n if (s2 > max2) {\n const k = this.maxSpeed / Math.sqrt(s2);\n b.vx *= k;\n b.vy *= k;\n }\n if (b.invInertia > 0) {\n b.angularVelocity += b.torque * b.invInertia * h;\n if (b.angularDamping > 0) b.angularVelocity /= 1 + b.angularDamping * h;\n // no corner faster than the speed limit\n const maxW = this.maxSpeed / Math.max(1, b.maxExtent);\n if (Math.abs(b.angularVelocity) > maxW) b.angularVelocity = Math.sign(b.angularVelocity) * maxW;\n }\n }\n this.broad.rebuild(this.list);\n this.active.length = 0;\n this.broad.pairs(this.list, this.visitPair);\n this.solver.prepare(this.active, h);\n for (let i = 0; i < this.velocityIterations; i++) this.solver.solveVelocities(this.active);\n for (const b of this.list) {\n if (!b.moving) continue;\n b.x += b.vx * h;\n b.y += b.vy * h;\n if (b.rotates) b.angle += b.angularVelocity * h;\n }\n for (let i = 0; i < this.positionIterations; i++) this.solver.solvePositions(this.active);\n for (const b of this.list) if (b.moving) b.updateBounds();\n }\n\n private readonly visitPair = (p: Body, q: Body): void => {\n if (!(p.layer & q.mask) || !(q.layer & p.mask)) return;\n const sensor = p.sensor || q.sensor;\n // two bodies nothing can push: only a sensor among them has anything to report\n if (p.invMass === 0 && q.invMass === 0 && !sensor) return;\n const a = p.id < q.id ? p : q;\n const b = a === p ? q : p;\n const m = this.manifold;\n if (!collide(a, b, sensor ? 0 : this.margin, m)) return;\n if (sensor && m.depth <= 0) return;\n const key = Contact.keyOf(a, b);\n let c = this.contacts.get(key);\n if (!c) {\n c = new Contact(a, b, key);\n this.contacts.set(key, c);\n }\n this.takeManifold(c, m, c.seenSub === this.subCount - 1);\n c.seenSub = this.subCount;\n c.seenStep = this.stepCount;\n if (sensor) {\n c.enabled = false;\n return;\n }\n if (a.awake !== b.awake) this.wakeOnImpact(a, b);\n c.enabled = this.preSolve ? this.preSolve(c) !== false : true;\n if (c.enabled) this.active.push(c);\n };\n\n /**\n * Copies the narrow phase's points into the contact, carrying each point's impulses over from the last sub-step\n * when the same feature touches again (`warm`: the pair touched on the last sub-step).\n */\n private takeManifold(c: Contact, m: Manifold, warm: boolean): void {\n const a = c.a;\n const b = c.b;\n let j0n = 0;\n let j0t = 0;\n let j1n = 0;\n let j1t = 0;\n let id0 = -1;\n let id1 = -1;\n if (warm) {\n if (c.count > 0) {\n id0 = c.pts[0].id;\n j0n = c.pts[0].jn;\n j0t = c.pts[0].jt;\n }\n if (c.count > 1) {\n id1 = c.pts[1].id;\n j1n = c.pts[1].jn;\n j1t = c.pts[1].jt;\n }\n }\n c.nx = m.nx;\n c.ny = m.ny;\n c.depth = m.depth;\n c.count = m.count;\n for (let i = 0; i < m.count; i++) {\n const p = c.pts[i]!;\n const id = m.id[i]!;\n p.x = m.px[i]!;\n p.y = m.py[i]!;\n p.depth = m.pd[i]!;\n p.id = id;\n p.jn = id === id0 ? j0n : id === id1 ? j1n : 0;\n p.jt = id === id0 ? j0t : id === id1 ? j1t : 0;\n const dax = p.x - a.x;\n const day = p.y - a.y;\n p.lax = a.cos * dax + a.sin * day;\n p.lay = -a.sin * dax + a.cos * day;\n const dbx = p.x - b.x;\n const dby = p.y - b.y;\n p.lbx = b.cos * dbx + b.sin * dby;\n p.lby = -b.sin * dbx + b.cos * dby;\n }\n }\n\n /** A sleeping body wakes when something meets it faster than the sleep speed (a kinematic platform always does). */\n private wakeOnImpact(a: Body, b: Body): void {\n const sleeper = a.awake ? b : a;\n const mover = a.awake ? a : b;\n if (sleeper.isStatic) return;\n const rv = Math.hypot(mover.vx - sleeper.vx, mover.vy - sleeper.vy);\n if (mover.type === 'kinematic' || rv > this.sleepSpeed) sleeper.wake();\n }\n\n private updateSleep(dt: number): void {\n const s2 = this.sleepSpeed * this.sleepSpeed;\n for (const b of this.list) {\n if (!b.isDynamic || !b.awake) continue;\n const spin = b.angularVelocity * b.maxExtent;\n if (b.vx * b.vx + b.vy * b.vy + spin * spin > s2 || b.fx !== 0 || b.fy !== 0 || b.torque !== 0) {\n b.restTime = 0;\n continue;\n }\n b.restTime += dt;\n if (b.restTime >= this.sleepTime) {\n b.awake = false;\n b.vx = 0;\n b.vy = 0;\n b.angularVelocity = 0;\n }\n }\n }\n\n /** Ends pairs no longer touching, starts new ones, refreshes `grounded`, then emits the events. */\n private finishContacts(): void {\n for (const b of this.list) {\n b.grounded = false;\n b.ground = null;\n }\n const begun: ContactInfo[] = [];\n const ended: ContactInfo[] = [];\n const cos = this.groundNormal;\n for (const [key, c] of this.contacts) {\n // a pair of bodies that are both at rest is not re-tested: it keeps touching as it was\n const touching = c.seenStep === this.stepCount || (c.reported && !c.a.moving && !c.b.moving);\n if (!touching) {\n if (c.reported) ended.push(info(c));\n this.contacts.delete(key);\n continue;\n }\n if (!c.reported) {\n c.reported = true;\n begun.push(info(c));\n }\n if (c.sensor || !c.enabled) continue;\n if (c.ny < -cos) setGround(c.b, c.a, c);\n else if (c.ny > cos) setGround(c.a, c.b, c);\n }\n for (const e of ended) this.events.emit('end', e);\n for (const e of begun) this.events.emit('begin', e);\n }\n}\n"],"names":["PhysicsWorld","EventBus","emptyManifold","options","ContactSolver","BroadPhase","body","b","Body","bodies","ended","key","c","info","e","dt","n","i","x0","y0","x1","y1","castRay","out","gx","gy","worst","g","speed","h","max2","k","s2","maxW","p","q","sensor","a","m","collide","Contact","warm","j0n","j0t","j1n","j1t","id0","id1","id","dax","day","dbx","dby","sleeper","mover","rv","spin","begun","cos","setGround"],"mappings":";;;;;;;;AAqBO,MAAMA,EAAa;AAAA,EACxB;AAAA,EACA;AAAA,EACS,SAAS,IAAIC,EAAA;AAAA;AAAA,EAEtB,WAAmD;AAAA,EAClC,OAAe,CAAA;AAAA,EACf;AAAA,EACA;AAAA,EACA,+BAAe,IAAA;AAAA,EACf,SAAoB,CAAA;AAAA,EACpB,WAAWC,EAAA;AAAA,EACX,WAAmB,CAAA;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EAEnB,YAAYC,IAA+B,IAAI;AAC7C,SAAK,WAAWA,EAAQ,YAAY,GACpC,KAAK,WAAWA,EAAQ,YAAY,KACpC,KAAK,SAASA,EAAQ,iBAAiB,KACvC,KAAK,WAAW;AAAA,MACd,sBAAsBA,EAAQ,wBAAwB;AAAA,MACtD,MAAMA,EAAQ,QAAQ;AAAA,MACtB,YAAYA,EAAQ,cAAc;AAAA,MAClC,eAAeA,EAAQ,iBAAiB;AAAA,IAAA,GAE1C,KAAK,SAAS,IAAIC,EAAc,KAAK,QAAQ,GAC7C,KAAK,QAAQ,IAAIC,EAAWF,EAAQ,YAAY,IAAI,KAAK,MAAM,GAC/D,KAAK,qBAAqBA,EAAQ,sBAAsB,GACxD,KAAK,qBAAqBA,EAAQ,sBAAsB,GACxD,KAAK,cAAc,KAAK,IAAI,GAAGA,EAAQ,eAAe,EAAE,GACxD,KAAK,WAAWA,EAAQ,YAAY,KACpC,KAAK,eAAeA,EAAQ,gBAAgB,KAC5C,KAAK,aAAaA,EAAQ,cAAc,IACxC,KAAK,aAAaA,EAAQ,cAAc,GACxC,KAAK,YAAYA,EAAQ,aAAa;AAAA,EACxC;AAAA;AAAA,EAGA,IAAIG,GAAgC;AAClC,UAAMC,IAAID,aAAgBE,IAAOF,IAAO,IAAIE,EAAKF,CAAI;AACrD,WAAIC,EAAE,YACNA,EAAE,UAAU,IACZA,EAAE,aAAA,GACFA,EAAE,KAAA,GACF,KAAK,KAAK,KAAKA,CAAC,GACZA,EAAE,YAAU,KAAK,MAAM,iBAAA,IACpBA;AAAA,EACT;AAAA,EAEA,OAAOE,GAA8B;AACnC,eAAWF,KAAKE,EAAQ,MAAK,IAAIF,CAAC;AAAA,EACpC;AAAA;AAAA,EAGA,OAAOD,GAAkB;AACvB,QAAI,CAACA,EAAK,QAAS;AACnB,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS,KAAKA,CAAI;AACvB;AAAA,IACF;AACA,IAAAA,EAAK,UAAU;AACf,UAAM,IAAI,KAAK,KAAK,QAAQA,CAAI;AAChC,IAAI,KAAK,KAAG,KAAK,KAAK,OAAO,GAAG,CAAC,GAC7BA,EAAK,YAAU,KAAK,MAAM,iBAAA,GAC9BA,EAAK,WAAW,IAChBA,EAAK,SAAS;AACd,UAAMI,IAAuB,CAAA;AAC7B,eAAW,CAACC,GAAKC,CAAC,KAAK,KAAK;AAC1B,MAAIA,EAAE,MAAMN,KAAQM,EAAE,MAAMN,MAC5BM,EAAE,MAAMN,CAAI,EAAE,KAAA,GACVM,EAAE,YAAUF,EAAM,KAAKG,EAAKD,CAAC,CAAC,GAClC,KAAK,SAAS,OAAOD,CAAG;AAE1B,eAAWG,KAAKJ,EAAO,MAAK,OAAO,KAAK,OAAOI,CAAC;AAAA,EAClD;AAAA;AAAA,EAGA,iBAAuB;AACrB,SAAK,MAAM,iBAAA;AAAA,EACb;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAKC,GAAkB;AACrB,QAAI,IAAEA,IAAK,MAAM,CAAC,OAAO,SAASA,CAAE,IACpC;AAAA,WAAK,WAAW;AAChB,UAAI;AACF,aAAK;AACL,mBAAWR,KAAK,KAAK;AACnB,UAAAA,EAAE,QAAQA,EAAE,GACZA,EAAE,QAAQA,EAAE,GACZA,EAAE,YAAYA,EAAE;AAElB,cAAMS,IAAI,KAAK,YAAYD,CAAE;AAC7B,iBAASE,IAAI,GAAGA,IAAID,GAAGC,IAAK,MAAK,QAAQF,IAAKC,CAAC;AAC/C,mBAAWT,KAAK,KAAK;AACnB,UAAAA,EAAE,KAAK,GACPA,EAAE,KAAK,GACPA,EAAE,SAAS;AAEb,QAAI,KAAK,cAAY,KAAK,YAAYQ,CAAE;AAAA,MAC1C,UAAA;AACE,aAAK,WAAW;AAAA,MAClB;AACA,WAAK,eAAA;AACL,iBAAWR,KAAK,KAAK,SAAS,OAAO,CAAC,EAAG,MAAK,OAAOA,CAAC;AAAA;AAAA,EACxD;AAAA;AAAA,EAGA,QAAQW,GAAYC,GAAYC,GAAYC,GAAYlB,GAAyC;AAC/F,eAAWI,KAAK,KAAK,KAAM,CAAKA,EAAE,cAAY,aAAA;AAC9C,gBAAK,MAAM,QAAQ,KAAK,IAAI,GACrBe,EAAQ,CAAC,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,GAAGJ,GAAIC,GAAIC,GAAIC,GAAIlB,CAAO;AAAA,EACjF;AAAA;AAAA,EAGA,WAAWG,GAAYiB,IAAqB,IAAmB;AAC7D,IAAAA,EAAI,SAAS;AACb,eAAWX,KAAK,KAAK,SAAS,UAAcA,EAAE,aAAaA,EAAE,MAAMN,KAAQM,EAAE,MAAMN,MAAOiB,EAAI,KAAKX,CAAC;AACpG,WAAOW;AAAA,EACT;AAAA;AAAA,EAGA,WAAWjB,GAAqB;AAC9B,WAAOA,EAAK;AAAA,EACd;AAAA,EAEQ,YAAYS,GAAoB;AACtC,UAAMS,IAAK,KAAK,UACVC,IAAK,KAAK;AAChB,QAAIC,IAAQ;AACZ,eAAWnB,KAAK,KAAK,MAAM;AACzB,UAAI,CAACA,EAAE,OAAQ;AACf,YAAMoB,IAAIpB,EAAE,YAAY,KAAK,MAAMiB,GAAIC,CAAE,IAAI,KAAK,IAAIlB,EAAE,YAAY,IAAIQ,IAAK,GAEvEa,IAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,MAAMrB,EAAE,IAAIA,EAAE,EAAE,IAAIoB,IAAI,KAAK,IAAIpB,EAAE,eAAe,IAAIA,EAAE,SAAS;AAC5G,MAAAmB,IAAQ,KAAK,IAAIA,GAAQE,IAAQb,IAAM,KAAK,IAAI,KAAKR,EAAE,aAAa,CAAC;AAAA,IACvE;AACA,WAAO,KAAK,IAAI,KAAK,aAAa,KAAK,IAAI,GAAG,KAAK,KAAKmB,CAAK,CAAC,CAAC;AAAA,EACjE;AAAA,EAEQ,QAAQG,GAAiB;AAC/B,SAAK;AACL,UAAMC,IAAO,KAAK,WAAW,KAAK;AAClC,eAAWvB,KAAK,KAAK,MAAM;AACzB,UAAI,CAACA,EAAE,aAAa,CAACA,EAAE,MAAO;AAG9B,UAFAA,EAAE,OAAO,KAAK,WAAWA,EAAE,eAAeA,EAAE,KAAKA,EAAE,WAAWsB,GAC9DtB,EAAE,OAAO,KAAK,WAAWA,EAAE,eAAeA,EAAE,KAAKA,EAAE,WAAWsB,GAC1DtB,EAAE,gBAAgB,GAAG;AACvB,cAAMwB,IAAI,KAAK,IAAIxB,EAAE,gBAAgBsB;AACrC,QAAAtB,EAAE,MAAMwB,GACRxB,EAAE,MAAMwB;AAAA,MACV;AACA,YAAMC,IAAKzB,EAAE,KAAKA,EAAE,KAAKA,EAAE,KAAKA,EAAE;AAClC,UAAIyB,IAAKF,GAAM;AACb,cAAMC,IAAI,KAAK,WAAW,KAAK,KAAKC,CAAE;AACtC,QAAAzB,EAAE,MAAMwB,GACRxB,EAAE,MAAMwB;AAAA,MACV;AACA,UAAIxB,EAAE,aAAa,GAAG;AACpB,QAAAA,EAAE,mBAAmBA,EAAE,SAASA,EAAE,aAAasB,GAC3CtB,EAAE,iBAAiB,QAAK,mBAAmB,IAAIA,EAAE,iBAAiBsB;AAEtE,cAAMI,IAAO,KAAK,WAAW,KAAK,IAAI,GAAG1B,EAAE,SAAS;AACpD,QAAI,KAAK,IAAIA,EAAE,eAAe,IAAI0B,MAAM1B,EAAE,kBAAkB,KAAK,KAAKA,EAAE,eAAe,IAAI0B;AAAA,MAC7F;AAAA,IACF;AACA,SAAK,MAAM,QAAQ,KAAK,IAAI,GAC5B,KAAK,OAAO,SAAS,GACrB,KAAK,MAAM,MAAM,KAAK,MAAM,KAAK,SAAS,GAC1C,KAAK,OAAO,QAAQ,KAAK,QAAQJ,CAAC;AAClC,aAASZ,IAAI,GAAGA,IAAI,KAAK,oBAAoBA,IAAK,MAAK,OAAO,gBAAgB,KAAK,MAAM;AACzF,eAAWV,KAAK,KAAK;AACnB,MAAKA,EAAE,WACPA,EAAE,KAAKA,EAAE,KAAKsB,GACdtB,EAAE,KAAKA,EAAE,KAAKsB,GACVtB,EAAE,YAASA,EAAE,SAASA,EAAE,kBAAkBsB;AAEhD,aAASZ,IAAI,GAAGA,IAAI,KAAK,oBAAoBA,IAAK,MAAK,OAAO,eAAe,KAAK,MAAM;AACxF,eAAWV,KAAK,KAAK,MAAUA,EAAE,YAAU,aAAA;AAAA,EAC7C;AAAA,EAEiB,YAAY,CAAC2B,GAASC,MAAkB;AACvD,QAAI,EAAED,EAAE,QAAQC,EAAE,SAAS,EAAEA,EAAE,QAAQD,EAAE,MAAO;AAChD,UAAME,IAASF,EAAE,UAAUC,EAAE;AAE7B,QAAID,EAAE,YAAY,KAAKC,EAAE,YAAY,KAAK,CAACC,EAAQ;AACnD,UAAMC,IAAIH,EAAE,KAAKC,EAAE,KAAKD,IAAIC,GACtB5B,IAAI8B,MAAMH,IAAIC,IAAID,GAClBI,IAAI,KAAK;AAEf,QADI,CAACC,EAAQF,GAAG9B,GAAG6B,IAAS,IAAI,KAAK,QAAQE,CAAC,KAC1CF,KAAUE,EAAE,SAAS,EAAG;AAC5B,UAAM3B,IAAM6B,EAAQ,MAAMH,GAAG9B,CAAC;AAC9B,QAAIK,IAAI,KAAK,SAAS,IAAID,CAAG;AAQ7B,QAPKC,MACHA,IAAI,IAAI4B,EAAQH,GAAG9B,GAAGI,CAAG,GACzB,KAAK,SAAS,IAAIA,GAAKC,CAAC,IAE1B,KAAK,aAAaA,GAAG0B,GAAG1B,EAAE,YAAY,KAAK,WAAW,CAAC,GACvDA,EAAE,UAAU,KAAK,UACjBA,EAAE,WAAW,KAAK,WACdwB,GAAQ;AACV,MAAAxB,EAAE,UAAU;AACZ;AAAA,IACF;AACA,IAAIyB,EAAE,UAAU9B,EAAE,SAAO,KAAK,aAAa8B,GAAG9B,CAAC,GAC/CK,EAAE,UAAU,KAAK,WAAW,KAAK,SAASA,CAAC,MAAM,KAAQ,IACrDA,EAAE,WAAS,KAAK,OAAO,KAAKA,CAAC;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAaA,GAAY0B,GAAaG,GAAqB;AACjE,UAAMJ,IAAIzB,EAAE,GACNL,IAAIK,EAAE;AACZ,QAAI8B,IAAM,GACNC,IAAM,GACNC,IAAM,GACNC,IAAM,GACNC,IAAM,IACNC,IAAM;AACV,IAAIN,MACE7B,EAAE,QAAQ,MACZkC,IAAMlC,EAAE,IAAI,CAAC,EAAE,IACf8B,IAAM9B,EAAE,IAAI,CAAC,EAAE,IACf+B,IAAM/B,EAAE,IAAI,CAAC,EAAE,KAEbA,EAAE,QAAQ,MACZmC,IAAMnC,EAAE,IAAI,CAAC,EAAE,IACfgC,IAAMhC,EAAE,IAAI,CAAC,EAAE,IACfiC,IAAMjC,EAAE,IAAI,CAAC,EAAE,MAGnBA,EAAE,KAAK0B,EAAE,IACT1B,EAAE,KAAK0B,EAAE,IACT1B,EAAE,QAAQ0B,EAAE,OACZ1B,EAAE,QAAQ0B,EAAE;AACZ,aAASrB,IAAI,GAAGA,IAAIqB,EAAE,OAAOrB,KAAK;AAChC,YAAMiB,IAAItB,EAAE,IAAIK,CAAC,GACX+B,IAAKV,EAAE,GAAGrB,CAAC;AACjB,MAAAiB,EAAE,IAAII,EAAE,GAAGrB,CAAC,GACZiB,EAAE,IAAII,EAAE,GAAGrB,CAAC,GACZiB,EAAE,QAAQI,EAAE,GAAGrB,CAAC,GAChBiB,EAAE,KAAKc,GACPd,EAAE,KAAKc,MAAOF,IAAMJ,IAAMM,MAAOD,IAAMH,IAAM,GAC7CV,EAAE,KAAKc,MAAOF,IAAMH,IAAMK,MAAOD,IAAMF,IAAM;AAC7C,YAAMI,IAAMf,EAAE,IAAIG,EAAE,GACda,IAAMhB,EAAE,IAAIG,EAAE;AACpB,MAAAH,EAAE,MAAMG,EAAE,MAAMY,IAAMZ,EAAE,MAAMa,GAC9BhB,EAAE,MAAM,CAACG,EAAE,MAAMY,IAAMZ,EAAE,MAAMa;AAC/B,YAAMC,IAAMjB,EAAE,IAAI3B,EAAE,GACd6C,IAAMlB,EAAE,IAAI3B,EAAE;AACpB,MAAA2B,EAAE,MAAM3B,EAAE,MAAM4C,IAAM5C,EAAE,MAAM6C,GAC9BlB,EAAE,MAAM,CAAC3B,EAAE,MAAM4C,IAAM5C,EAAE,MAAM6C;AAAA,IACjC;AAAA,EACF;AAAA;AAAA,EAGQ,aAAaf,GAAS9B,GAAe;AAC3C,UAAM8C,IAAUhB,EAAE,QAAQ9B,IAAI8B,GACxBiB,IAAQjB,EAAE,QAAQA,IAAI9B;AAC5B,QAAI8C,EAAQ,SAAU;AACtB,UAAME,IAAK,KAAK,MAAMD,EAAM,KAAKD,EAAQ,IAAIC,EAAM,KAAKD,EAAQ,EAAE;AAClE,KAAIC,EAAM,SAAS,eAAeC,IAAK,KAAK,iBAAoB,KAAA;AAAA,EAClE;AAAA,EAEQ,YAAYxC,GAAkB;AACpC,UAAMiB,IAAK,KAAK,aAAa,KAAK;AAClC,eAAWzB,KAAK,KAAK,MAAM;AACzB,UAAI,CAACA,EAAE,aAAa,CAACA,EAAE,MAAO;AAC9B,YAAMiD,IAAOjD,EAAE,kBAAkBA,EAAE;AACnC,UAAIA,EAAE,KAAKA,EAAE,KAAKA,EAAE,KAAKA,EAAE,KAAKiD,IAAOA,IAAOxB,KAAMzB,EAAE,OAAO,KAAKA,EAAE,OAAO,KAAKA,EAAE,WAAW,GAAG;AAC9F,QAAAA,EAAE,WAAW;AACb;AAAA,MACF;AACA,MAAAA,EAAE,YAAYQ,GACVR,EAAE,YAAY,KAAK,cACrBA,EAAE,QAAQ,IACVA,EAAE,KAAK,GACPA,EAAE,KAAK,GACPA,EAAE,kBAAkB;AAAA,IAExB;AAAA,EACF;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,eAAWA,KAAK,KAAK;AACnB,MAAAA,EAAE,WAAW,IACbA,EAAE,SAAS;AAEb,UAAMkD,IAAuB,CAAA,GACvB/C,IAAuB,CAAA,GACvBgD,IAAM,KAAK;AACjB,eAAW,CAAC/C,GAAKC,CAAC,KAAK,KAAK,UAAU;AAGpC,UAAI,EADaA,EAAE,aAAa,KAAK,aAAcA,EAAE,YAAY,CAACA,EAAE,EAAE,UAAU,CAACA,EAAE,EAAE,SACtE;AACb,QAAIA,EAAE,YAAUF,EAAM,KAAKG,EAAKD,CAAC,CAAC,GAClC,KAAK,SAAS,OAAOD,CAAG;AACxB;AAAA,MACF;AAKA,MAJKC,EAAE,aACLA,EAAE,WAAW,IACb6C,EAAM,KAAK5C,EAAKD,CAAC,CAAC,IAEhB,EAAAA,EAAE,UAAU,CAACA,EAAE,aACfA,EAAE,KAAK,CAAC8C,MAAe9C,EAAE,GAAGA,EAAE,GAAGA,CAAC,IAC7BA,EAAE,KAAK8C,KAAKC,EAAU/C,EAAE,GAAGA,EAAE,GAAGA,CAAC;AAAA,IAC5C;AACA,eAAWE,KAAKJ,EAAO,MAAK,OAAO,KAAK,OAAOI,CAAC;AAChD,eAAWA,KAAK2C,EAAO,MAAK,OAAO,KAAK,SAAS3C,CAAC;AAAA,EACpD;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SpatialHash.js","sources":["../../src/physics/SpatialHash.ts"],"sourcesContent":["import type { Body } from './Body';\n\n/**\n * Broad phase: a uniform grid hashed into a map, each cell listing the bodies whose bounds touch it. A body spans\n * as many cells as its bounds cover, so a long merged floor sits in a row of cells and is still one body.\n *\n * Cells are keyed by their coordinates packed into one number (16 bits each, wrapping): two cells 65,536 cells\n * apart share a list, which only costs a few extra narrow-phase tests, never a missed pair.\n */\nexport class SpatialHash {\n readonly cellSize: number;\n private readonly inv: number;\n private readonly cells = new Map<number, Body[]>();\n /** Lists emptied by `clear`, kept for reuse so a rebuild every step allocates nothing once warm. */\n private readonly spare: Body[][] = [];\n\n constructor(cellSize: number) {\n if (!(cellSize > 0)) throw new Error(`SpatialHash: cell size must be positive, got ${cellSize}`);\n this.cellSize = cellSize;\n this.inv = 1 / cellSize;\n }\n\n static key(cx: number, cy: number): number {\n return ((cx + 0x8000) & 0xffff) * 0x10000 + ((cy + 0x8000) & 0xffff);\n }\n\n cellOf(v: number): number {\n return Math.floor(v * this.inv);\n }\n\n clear(): void {\n for (const list of this.cells.values()) {\n list.length = 0;\n this.spare.push(list);\n }\n this.cells.clear();\n }\n\n insert(body: Body, margin = 0): void {\n const x0 = this.cellOf(body.minX - margin);\n const x1 = this.cellOf(body.maxX + margin);\n const y0 = this.cellOf(body.minY - margin);\n const y1 = this.cellOf(body.maxY + margin);\n for (let cy = y0; cy <= y1; cy++) {\n for (let cx = x0; cx <= x1; cx++) {\n const k = SpatialHash.key(cx, cy);\n let list = this.cells.get(k);\n if (!list) {\n list = this.spare.pop() ?? [];\n this.cells.set(k, list);\n }\n list.push(body);\n }\n }\n }\n\n /** The bodies listed in one cell (undefined when empty). Do not keep or edit the array. */\n at(cx: number, cy: number): readonly Body[] | undefined {\n return this.cells.get(SpatialHash.key(cx, cy));\n }\n}\n"],"names":["SpatialHash","cellSize","cx","cy","v","list","body","margin","x0","x1","y0","y1","k"],"mappings":"AASO,MAAMA,EAAY;AAAA,EACd;AAAA,EACQ;AAAA,EACA,4BAAY,IAAA;AAAA;AAAA,EAEZ,QAAkB,CAAA;AAAA,EAEnC,YAAYC,GAAkB;AAC5B,QAAI,EAAEA,IAAW,GAAI,OAAM,IAAI,MAAM,gDAAgDA,CAAQ,EAAE;AAC/F,SAAK,WAAWA,GAChB,KAAK,MAAM,IAAIA;AAAA,EACjB;AAAA,EAEA,OAAO,IAAIC,GAAYC,GAAoB;AACzC,YAASD,IAAK,QAAU,SAAU,SAAYC,IAAK,QAAU;AAAA,EAC/D;AAAA,EAEA,OAAOC,GAAmB;AACxB,WAAO,KAAK,MAAMA,IAAI,KAAK,GAAG;AAAA,EAChC;AAAA,EAEA,QAAc;AACZ,eAAWC,KAAQ,KAAK,MAAM,OAAA;AAC5B,MAAAA,EAAK,SAAS,GACd,KAAK,MAAM,KAAKA,CAAI;AAEtB,SAAK,MAAM,MAAA;AAAA,EACb;AAAA,EAEA,OAAOC,GAAYC,IAAS,GAAS;AACnC,UAAMC,IAAK,KAAK,OAAOF,EAAK,OAAOC,CAAM,GACnCE,IAAK,KAAK,OAAOH,EAAK,OAAOC,CAAM,GACnCG,IAAK,KAAK,OAAOJ,EAAK,OAAOC,CAAM,GACnCI,IAAK,KAAK,OAAOL,EAAK,OAAOC,CAAM;AACzC,aAASJ,IAAKO,GAAIP,KAAMQ,GAAIR;AAC1B,eAASD,IAAKM,GAAIN,KAAMO,GAAIP,KAAM;AAChC,cAAMU,IAAIZ,EAAY,IAAIE,GAAIC,CAAE;AAChC,YAAIE,IAAO,KAAK,MAAM,IAAIO,CAAC;AAC3B,QAAKP,MACHA,IAAO,KAAK,MAAM,IAAA,KAAS,CAAA,GAC3B,KAAK,MAAM,IAAIO,GAAGP,CAAI,IAExBA,EAAK,KAAKC,CAAI;AAAA,MAChB;AAAA,EAEJ;AAAA;AAAA,EAGA,GAAGJ,GAAYC,GAAyC;AACtD,WAAO,KAAK,MAAM,IAAIH,EAAY,IAAIE,GAAIC,CAAE,CAAC;AAAA,EAC/C;AACF;"}
|