1681-engine 0.5.8 → 0.5.10
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 +95 -0
- package/README.md +3 -0
- package/dist/1681-engine.js +526 -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/life/Birds.js +73 -0
- package/dist/ambient/life/Butterflies.js +43 -0
- package/dist/ambient/life/ChimneySmoke.js +50 -0
- package/dist/ambient/life/Drift.js +75 -0
- package/dist/ambient/life/Fireflies.js +52 -0
- package/dist/ambient/life/lifeMotion.js +49 -0
- 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 +177 -34
- package/dist/assets/Prepainter.js +61 -0
- 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/assets/workerPainter.js +39 -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/cjs/electron/electronLike.d.ts +55 -0
- package/dist/cjs/electron/electronLike.js +7 -0
- package/dist/cjs/electron/index.d.ts +14 -0
- package/dist/cjs/electron/index.js +20 -0
- package/dist/cjs/electron/lockdown.d.ts +33 -0
- package/dist/cjs/electron/lockdown.js +69 -0
- package/dist/cjs/electron/lockdownRules.d.ts +28 -0
- package/dist/cjs/electron/lockdownRules.js +61 -0
- package/dist/cjs/electron/sessionStore.d.ts +40 -0
- package/dist/cjs/electron/sessionStore.js +77 -0
- package/dist/cjs/electron/shellId.d.ts +22 -0
- package/dist/cjs/electron/shellId.js +31 -0
- package/dist/cjs/electron/webFiles.d.ts +13 -0
- package/dist/cjs/electron/webFiles.js +52 -0
- package/dist/cjs/electron/webPatcher.d.ts +80 -0
- package/dist/cjs/electron/webPatcher.js +216 -0
- package/dist/cjs/electron/webPointers.d.ts +19 -0
- package/dist/cjs/electron/webPointers.js +35 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/updates/index.d.ts +8 -0
- package/dist/cjs/updates/index.js +21 -0
- package/dist/cjs/updates/manifest.d.ts +73 -0
- package/dist/cjs/updates/manifest.js +67 -0
- package/dist/cjs/updates/pickUpdate.d.ts +42 -0
- package/dist/cjs/updates/pickUpdate.js +52 -0
- package/dist/cjs/updates/webPlan.d.ts +20 -0
- package/dist/cjs/updates/webPlan.js +34 -0
- 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/electron/lockdown.js +31 -0
- package/dist/electron/lockdownRules.js +42 -0
- package/dist/electron/sessionStore.js +48 -0
- package/dist/electron/shellId.js +18 -0
- package/dist/electron/webFiles.js +36 -0
- package/dist/electron/webPatcher.js +147 -0
- package/dist/electron/webPointers.js +25 -0
- package/dist/electron.js +19 -0
- 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/haptics.js +50 -0
- 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/ApiClient.js +96 -0
- 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/lifeArt.js +83 -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 +82 -71
- 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/ConfirmButton.js +24 -0
- 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 +1 -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 +7 -7
- package/dist/react/placement.js +0 -1
- package/dist/react/useFillSheet.js +0 -1
- package/dist/react/useFitToViewport.js +0 -1
- package/dist/react/useJsonSelector.js +13 -0
- 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 +73 -69
- 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 +57 -33
- package/dist/tilemap/autotile.js +103 -0
- package/dist/tilemap/collision.js +29 -19
- package/dist/tilemap/ldtk.js +21 -0
- package/dist/tilemap/ldtkParse.js +191 -0
- 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/ambient/life/Birds.d.ts +50 -0
- package/dist/types/ambient/life/Birds.d.ts.map +1 -0
- package/dist/types/ambient/life/Butterflies.d.ts +32 -0
- package/dist/types/ambient/life/Butterflies.d.ts.map +1 -0
- package/dist/types/ambient/life/ChimneySmoke.d.ts +37 -0
- package/dist/types/ambient/life/ChimneySmoke.d.ts.map +1 -0
- package/dist/types/ambient/life/Drift.d.ts +58 -0
- package/dist/types/ambient/life/Drift.d.ts.map +1 -0
- package/dist/types/ambient/life/Fireflies.d.ts +43 -0
- package/dist/types/ambient/life/Fireflies.d.ts.map +1 -0
- package/dist/types/ambient/life/lifeMotion.d.ts +63 -0
- package/dist/types/ambient/life/lifeMotion.d.ts.map +1 -0
- package/dist/types/assets/AssetLoader.d.ts +83 -8
- package/dist/types/assets/AssetLoader.d.ts.map +1 -1
- package/dist/types/assets/Prepainter.d.ts +64 -0
- package/dist/types/assets/Prepainter.d.ts.map +1 -0
- package/dist/types/assets/textureAtlas.d.ts +35 -0
- package/dist/types/assets/textureAtlas.d.ts.map +1 -0
- package/dist/types/assets/workerPainter.d.ts +21 -0
- package/dist/types/assets/workerPainter.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/electron/electronLike.d.ts +56 -0
- package/dist/types/electron/electronLike.d.ts.map +1 -0
- package/dist/types/electron/index.d.ts +15 -0
- package/dist/types/electron/index.d.ts.map +1 -0
- package/dist/types/electron/lockdown.d.ts +34 -0
- package/dist/types/electron/lockdown.d.ts.map +1 -0
- package/dist/types/electron/lockdownRules.d.ts +29 -0
- package/dist/types/electron/lockdownRules.d.ts.map +1 -0
- package/dist/types/electron/sessionStore.d.ts +41 -0
- package/dist/types/electron/sessionStore.d.ts.map +1 -0
- package/dist/types/electron/shellId.d.ts +23 -0
- package/dist/types/electron/shellId.d.ts.map +1 -0
- package/dist/types/electron/webFiles.d.ts +14 -0
- package/dist/types/electron/webFiles.d.ts.map +1 -0
- package/dist/types/electron/webPatcher.d.ts +81 -0
- package/dist/types/electron/webPatcher.d.ts.map +1 -0
- package/dist/types/electron/webPointers.d.ts +20 -0
- package/dist/types/electron/webPointers.d.ts.map +1 -0
- 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 +32 -4
- 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/haptics.d.ts +42 -0
- package/dist/types/input/haptics.d.ts.map +1 -0
- package/dist/types/input/rumble.d.ts +41 -0
- package/dist/types/input/rumble.d.ts.map +1 -0
- package/dist/types/net/ApiClient.d.ts +74 -0
- package/dist/types/net/ApiClient.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 +4 -1
- package/dist/types/pixel/index.d.ts.map +1 -1
- package/dist/types/pixel/lifeArt.d.ts +63 -0
- package/dist/types/pixel/lifeArt.d.ts.map +1 -0
- 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/react/ConfirmButton.d.ts +26 -0
- package/dist/types/react/ConfirmButton.d.ts.map +1 -0
- package/dist/types/react/baseCss.d.ts +1 -1
- package/dist/types/react/baseCss.d.ts.map +1 -1
- package/dist/types/react/index.d.ts +2 -0
- package/dist/types/react/index.d.ts.map +1 -1
- package/dist/types/react/labels.d.ts +2 -0
- package/dist/types/react/labels.d.ts.map +1 -1
- package/dist/types/react/useJsonSelector.d.ts +13 -0
- package/dist/types/react/useJsonSelector.d.ts.map +1 -0
- package/dist/types/tilemap/TilemapView.d.ts +17 -0
- package/dist/types/tilemap/TilemapView.d.ts.map +1 -1
- package/dist/types/tilemap/autotile.d.ts +95 -0
- package/dist/types/tilemap/autotile.d.ts.map +1 -0
- package/dist/types/tilemap/collision.d.ts +8 -0
- package/dist/types/tilemap/collision.d.ts.map +1 -1
- package/dist/types/tilemap/ldtk.d.ts +97 -0
- package/dist/types/tilemap/ldtk.d.ts.map +1 -0
- package/dist/types/tilemap/ldtkParse.d.ts +26 -0
- package/dist/types/tilemap/ldtkParse.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/types/updates/index.d.ts +9 -0
- package/dist/types/updates/index.d.ts.map +1 -0
- package/dist/types/updates/manifest.d.ts +74 -0
- package/dist/types/updates/manifest.d.ts.map +1 -0
- package/dist/types/updates/pickUpdate.d.ts +43 -0
- package/dist/types/updates/pickUpdate.d.ts.map +1 -0
- package/dist/types/updates/webPlan.d.ts +21 -0
- package/dist/types/updates/webPlan.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/dist/updates/manifest.js +35 -0
- package/dist/updates/pickUpdate.js +27 -0
- package/dist/updates/webPlan.js +14 -0
- package/dist/updates.js +15 -0
- package/package.json +31 -6
- 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":"DayClock.js","sources":["../../src/ambient/DayClock.ts"],"sourcesContent":["/**\n * A time of day read off a wall clock, so every client that agrees on the time (`serverNow`) stands in the same dusk\n * without a packet between them. Pure: no Pixi, no clock of its own — the caller hands in the time, and what draws\n * the night (a `LightLayer`, the pixel `NightShade`) or a HUD badge reads what this returns.\n *\n * ```ts\n * const clock = new DayClock({\n * periodMs: 2 * 3600_000, // a game day every two real hours\n * keys: [\n * { at: 0, color: 0x0a1030, alpha: 0.52 },\n * { at: 0.3, color: 0xfff0d8, alpha: 0 },\n * { at: 0.7, color: 0xffb070, alpha: 0 },\n * { at: 0.86, color: 0x0a1030, alpha: 0.52 },\n * ],\n * phases: [{ id: 'dawn', from: 0.2 }, { id: 'day', from: 0.3 }, { id: 'dusk', from: 0.7 }, { id: 'night', from: 0.8 }],\n * });\n * const day = clock.at(serverNow()); // { phase, id, color, alpha, night }\n * ```\n *\n * Phase 0 is midnight and 0.5 noon. Keys are the grade laid over the outdoor world at points of the day, mixed\n * between neighbours; a key at 0 and one at 1 with the same values make midnight seamless.\n */\n\n/** The world's grade at one point of the day: its colour (0xRRGGBB) and how strongly it is laid (0 by day). */\nexport interface DayKey {\n at: number;\n color: number;\n alpha: number;\n}\n\n/** A named part of the day starting at `from` (0..1); the last one runs on through midnight. */\nexport interface DayPhase<P extends string = string> {\n id: P;\n from: number;\n}\n\nexport interface DayClockOptions<P extends string = string> {\n /** Real milliseconds a whole game day lasts. */\n periodMs: number;\n /** Shifts the day against the wall clock (the same offset on every client keeps them together). */\n offsetMs?: number;\n /** At least one key. */\n keys: readonly DayKey[];\n /** Optional names for parts of the day; without them `id` is ''. */\n phases?: readonly DayPhase<P>[];\n}\n\nexport interface DayState<P extends string = string> {\n /** 0..1 through the game day: 0 midnight, 0.5 noon. */\n phase: number;\n /** The part of the day `phase` falls in. */\n id: P | '';\n /** The grade over the outdoor world, 0xRRGGBB. */\n color: number;\n /** How strongly the grade is laid (0 at full day). */\n alpha: number;\n /** 0 by day, 1 at the darkest key: how much lamps matter. */\n night: number;\n}\n\n/** `a` taken `t` of the way to `b`, per channel, 0xRRGGBB. */\nexport function mixRgb(a: number, b: number, t: number): number {\n const ch = (s: number) => Math.round(((a >> s) & 255) * (1 - t) + ((b >> s) & 255) * t);\n return (ch(16) << 16) | (ch(8) << 8) | ch(0);\n}\n\nexport class DayClock<P extends string = string> {\n private readonly periodMs: number;\n private readonly offsetMs: number;\n private readonly keys: DayKey[];\n private readonly phases: DayPhase<P>[];\n private readonly maxAlpha: number;\n /** A phase held still (a debug command, a cut scene at midnight); null follows the clock. */\n private held: number | null = null;\n\n constructor(options: DayClockOptions<P>) {\n if (!(options.periodMs > 0)) throw new Error('DayClock: periodMs must be above 0');\n if (!options.keys.length) throw new Error('DayClock: at least one key');\n this.periodMs = options.periodMs;\n this.offsetMs = options.offsetMs ?? 0;\n this.keys = [...options.keys].sort((a, b) => a.at - b.at);\n this.phases = [...(options.phases ?? [])].sort((a, b) => a.from - b.from);\n // a clock whose darkest key is barely dark still reaches night 1 there, and one with no dark never divides by 0\n this.maxAlpha = Math.max(0.01, ...this.keys.map((k) => k.alpha));\n }\n\n /** Holds the day at `phase` (0..1), or lets it run again with null. */\n hold(phase: number | null): void {\n this.held = phase === null ? null : ((phase % 1) + 1) % 1;\n }\n\n /** Where the day stands at wall time `ms` (0..1). */\n phaseAt(ms: number): number {\n if (this.held !== null) return this.held;\n const p = this.periodMs;\n return ((((ms + this.offsetMs) % p) + p) % p) / p;\n }\n\n at(ms: number): DayState<P> {\n const phase = this.phaseAt(ms);\n const keys = this.keys;\n let key = keys[0]!;\n let color = key.color;\n let alpha = key.alpha;\n // past the last key it holds; before the first it holds the first\n if (phase >= keys[keys.length - 1]!.at) {\n key = keys[keys.length - 1]!;\n color = key.color;\n alpha = key.alpha;\n }\n for (let i = 0; i < keys.length - 1; i++) {\n const a = keys[i]!;\n const b = keys[i + 1]!;\n if (phase < a.at || phase > b.at) continue;\n const t = b.at > a.at ? (phase - a.at) / (b.at - a.at) : 0;\n color = mixRgb(a.color, b.color, t);\n alpha = a.alpha + (b.alpha - a.alpha) * t;\n break;\n }\n // the stretch before the first `from` belongs to the last phase: night runs through midnight\n let id: P | '' = this.phases.length ? this.phases[this.phases.length - 1]!.id : '';\n for (const ph of this.phases) if (phase >= ph.from) id = ph.id;\n return { phase, id, color, alpha, night: Math.min(1, alpha / this.maxAlpha) };\n }\n}\n\n/** A day's phase as a 24-hour clock, \"HH:MM\" (phase 0 is midnight). */\nexport function clockText(phase: number): string {\n const minutes = Math.floor((((phase % 1) + 1) % 1) * 24 * 60);\n return `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}`;\n}\n\n/**\n * The night's colour pulled toward a place's own (a marsh mossy, a wood blue) as it deepens: `share` of the way at\n * full night, nothing by day.\n */\nexport function tintNight(day: Pick<DayState, 'color' | 'night'>, tint: number | undefined, share = 0.6): number {\n return tint === undefined ? day.color : mixRgb(day.color, tint, day.night * share);\n}\n"],"names":["mixRgb","a","b","ch","s","DayClock","options","k","phase","ms","p","keys","key","color","alpha","i","t","id","ph","clockText","minutes","tintNight","day","tint","share"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WeatherLayer.js","sources":["../../src/ambient/WeatherLayer.ts"],"sourcesContent":["import { Sprite, Texture } from 'pixi.js';\nimport type { ViewRect } from '../core/Camera';\nimport type { Image } from '../core/Image';\nimport type { Stage } from '../core/Stage';\nimport type { WeatherState } from './weather';\n\n/**\n * How one kind of weather moves:\n * - `fall`: streaks falling at a slant onto a ground line somewhere in view, half of them leaving a two-frame splash\n * (rain);\n * - `flake`: bits drifting down and swaying as they fall, each on its own phase (snow, ash, petals);\n * - `bank`: a few wide sprites drifting slowly sideways, laid over the bodies (fog, mist, smoke).\n */\nexport type WeatherMotion = 'fall' | 'flake' | 'bank';\n\nexport interface WeatherLook {\n motion: WeatherMotion;\n /** Particles at full strength; fewer show as the weather thins. Keep it in the low hundreds on a phone. */\n max: number;\n /** Texture keys, handed out in turn (repeat a key to make it commoner). */\n keys: readonly string[];\n /** `fall` only: the splash's two frames. Without them a drop simply vanishes where it lands. */\n splash?: readonly [string, string];\n /** World px a second, [lo, hi] each (defaults suit each motion). */\n vx?: readonly [number, number];\n vy?: readonly [number, number];\n /** `flake` only: how far (px) a flake sways either side as it falls (default 4). */\n sway?: number;\n /** A screen-wide tint the kind lays over everything (fog is mostly tint), at `tintAlpha` × strength. */\n tint?: number;\n tintAlpha?: number;\n}\n\nexport interface WeatherLayerOptions<K extends string> {\n looks: Readonly<Partial<Record<K, WeatherLook>>>;\n /** Depth of falling particles in the world layer: over the bodies, under the names (default 900_001). */\n depth?: number;\n /** Depth of fog banks (default 900_010). */\n bankDepth?: number;\n /** Depth of splashes: with the ground effects, so a body standing in the rain is not splashed over (default 0). */\n splashDepth?: number;\n /** Round positions to whole world pixels (default true): pixel art wants it, smooth art may not. */\n snap?: boolean;\n /** Where randomness comes from (default `Math.random`); the look of a shower need not be shared between clients. */\n random?: () => number;\n}\n\ninterface Drop {\n img: Image;\n key: string;\n x: number;\n y: number;\n vx: number;\n vy: number;\n /** `fall`: the ground line it lands on. `flake`: its sway's phase. */\n ground: number;\n /** `fall`: when its splash began (0 while it still falls). */\n splashAt: number;\n}\n\nconst SPLASH_MS = 80;\nconst DEFAULT_V: Record<WeatherMotion, { vx: readonly [number, number]; vy: readonly [number, number] }> = {\n fall: { vx: [-48, -38], vy: [250, 320] },\n flake: { vx: [4, 12], vy: [14, 30] },\n bank: { vx: [3, 8], vy: [0, 0] },\n};\n\n/**\n * The weather on screen: a capped pool of particles that lives only inside the camera's view, plus a light screen\n * tint for the kinds that change the air. Feed it a `WeatherState` each frame (from `weatherAt`, so it fades in and\n * out exactly when every other client's does); the pool is rebuilt only when the kind changes, and a thinning shower\n * hides particles rather than freeing them.\n *\n * The art is the game's: name texture keys per kind (the pixel module's `paintWeatherArt` paints a set in whole\n * pixels).\n */\nexport class WeatherLayer<K extends string> {\n private readonly drops: Drop[] = [];\n private kind: K | 'clear' = 'clear';\n private look: WeatherLook | undefined;\n private readonly tint = new Sprite({ texture: Texture.WHITE });\n private readonly depth: number;\n private readonly bankDepth: number;\n private readonly splashDepth: number;\n private readonly snap: boolean;\n private readonly random: () => number;\n\n constructor(\n private readonly stage: Stage,\n private readonly options: WeatherLayerOptions<K>,\n ) {\n this.depth = options.depth ?? 900_001;\n this.bankDepth = options.bankDepth ?? 900_010;\n this.splashDepth = options.splashDepth ?? 0;\n this.snap = options.snap ?? true;\n this.random = options.random ?? Math.random;\n this.tint.visible = false;\n stage.screen.addChild(this.tint);\n }\n\n /**\n * `now` in ms, `dt` the frame's ms, `view` the camera's world view. `share` (0..1) thins the pool on top of the\n * weather's own strength (a reduced-effects setting keeps half).\n */\n update(now: number, dt: number, view: ViewRect, weather: WeatherState<K>, share = 1): void {\n if (weather.kind !== this.kind) this.rebuild(weather.kind, view);\n const look = this.look;\n const tintAlpha = (look?.tintAlpha ?? 0) * weather.intensity;\n this.tint.visible = tintAlpha > 0.004;\n if (this.tint.visible) {\n this.tint.tint = look?.tint ?? 0xffffff;\n this.tint.alpha = tintAlpha;\n this.tint.width = this.stage.width;\n this.tint.height = this.stage.height;\n }\n if (!look) return;\n const active = Math.round(this.drops.length * weather.intensity * Math.max(0, Math.min(1, share)));\n const s = dt / 1000;\n for (let i = 0; i < this.drops.length; i++) {\n const d = this.drops[i]!;\n if (i >= active) {\n if (d.img.visible) d.img.setVisible(false);\n continue;\n }\n if (!d.img.visible) this.spawn(d, look, view, false);\n if (look.motion === 'fall') this.stepFall(d, look, now, s, view);\n else if (look.motion === 'bank') this.stepBank(d, look, s, view, weather.intensity);\n else this.stepFlake(d, look, now, s, view);\n }\n }\n\n private rebuild(kind: K | 'clear', view: ViewRect): void {\n for (const d of this.drops) d.img.destroy();\n this.drops.length = 0;\n this.kind = kind;\n this.look = kind === 'clear' ? undefined : this.options.looks[kind];\n const look = this.look;\n if (!look || !look.keys.length) return;\n for (let i = 0; i < look.max; i++) {\n const key = look.keys[i % look.keys.length]!;\n const img = this.stage.add.image(0, 0, key).setOrigin(0, 0).setVisible(false);\n const d: Drop = { img, key, x: 0, y: 0, vx: 0, vy: 0, ground: 0, splashAt: 0 };\n this.drops.push(d);\n // the first fill is spread over the whole view, so a shower starting does not arrive as one falling sheet\n this.spawn(d, look, view, true);\n img.setVisible(false);\n }\n }\n\n private between(r: readonly [number, number]): number {\n return r[0] + (r[1] - r[0]) * this.random();\n }\n\n private place(d: Drop): void {\n d.img.setPosition(this.snap ? Math.round(d.x) : d.x, this.snap ? Math.round(d.y) : d.y);\n }\n\n private spawn(d: Drop, look: WeatherLook, view: ViewRect, anywhere: boolean): void {\n const r = this.random;\n const v = DEFAULT_V[look.motion];\n const x = view.x - 30 + r() * (view.width + 60);\n d.vx = this.between(look.vx ?? v.vx);\n d.vy = this.between(look.vy ?? v.vy);\n if (look.motion === 'fall') {\n d.ground = view.y + r() * (view.height + 12);\n d.y = anywhere ? view.y - 10 + r() * (d.ground - view.y + 10) : d.ground - 30 - r() * view.height;\n d.x = x;\n d.splashAt = 0;\n d.img.setTexture(d.key).setDepth(this.depth).setAlpha(1);\n } else if (look.motion === 'bank') {\n d.x = anywhere ? x : view.x - d.img.width;\n d.y = view.y + r() * view.height;\n d.img.setDepth(this.bankDepth);\n } else {\n d.x = x;\n d.y = anywhere ? view.y + r() * view.height : view.y - 4 - r() * 20;\n d.ground = r() * Math.PI * 2;\n d.img.setDepth(this.depth);\n }\n d.img.setVisible(true);\n }\n\n private stepFall(d: Drop, look: WeatherLook, now: number, s: number, view: ViewRect): void {\n if (d.splashAt) {\n const t = now - d.splashAt;\n if (t > SPLASH_MS * 2) this.spawn(d, look, view, false);\n else if (t > SPLASH_MS && look.splash) d.img.setTexture(look.splash[1]);\n return;\n }\n d.x += d.vx * s;\n d.y += d.vy * s;\n if (d.y >= d.ground) {\n // every other drop splashes on the ground it hit; the rest vanish into it\n if (look.splash && this.random() < 0.5) {\n d.splashAt = now;\n d.img.setTexture(look.splash[0]).setDepth(this.splashDepth);\n d.x -= d.img.width / 2;\n d.y = d.ground - 1;\n this.place(d);\n } else this.spawn(d, look, view, false);\n return;\n }\n this.place(d);\n }\n\n private stepFlake(d: Drop, look: WeatherLook, now: number, s: number, view: ViewRect): void {\n d.x += d.vx * s;\n d.y += d.vy * s;\n if (d.y > view.bottom + 4 || d.x > view.right + 30 || d.x < view.x - 40) {\n this.spawn(d, look, view, false);\n return;\n }\n // the sway is added at draw time, so it never drifts the flake off its line\n const sway = Math.sin(now / 900 + d.ground) * (look.sway ?? 4);\n const x = d.x + sway;\n d.img.setPosition(this.snap ? Math.round(x) : x, this.snap ? Math.round(d.y) : d.y);\n }\n\n private stepBank(d: Drop, look: WeatherLook, s: number, view: ViewRect, intensity: number): void {\n d.x += d.vx * s;\n if (d.x > view.right + 10 || d.y < view.y - 20 || d.y > view.bottom + 10) this.spawn(d, look, view, d.x <= view.right + 10);\n d.img.setAlpha(0.35 + 0.5 * intensity);\n this.place(d);\n }\n\n destroy(): void {\n for (const d of this.drops) d.img.destroy();\n this.drops.length = 0;\n this.tint.destroy();\n }\n}\n"],"names":["SPLASH_MS","DEFAULT_V","WeatherLayer","stage","options","Sprite","Texture","now","dt","view","weather","share","look","tintAlpha","active","s","i","d","kind","key","img","r","anywhere","v","x","t","sway","intensity"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"grade.js","sources":["../../src/ambient/grade.ts"],"sourcesContent":["/**\n * The pure half of a colour grade: how strong a place's colour is laid at a point of the day, and the multiply colour\n * that leaves the same result. `ColorGrade` draws it.\n */\n\n/** A place's grade: its colour (0xRRGGBB) and how strongly it is laid by day and by night (0..1). */\nexport interface Grade {\n color: number;\n day: number;\n night: number;\n}\n\nexport interface GradeState {\n color: number;\n alpha: number;\n}\n\n/**\n * The grade at `night` (0 by day, 1 at full night, `DayClock`'s `night`), never past `max`: a grade is a light\n * tint, and one laid too strong washes every sprite toward one colour.\n */\nexport function gradeAt(grade: Grade, night: number, max = 0.14): GradeState {\n const n = Math.max(0, Math.min(1, night));\n return { color: grade.color, alpha: Math.max(0, Math.min(max, grade.day + (grade.night - grade.day) * n)) };\n}\n\n/** `color` taken `share` of the way from white: what a multiply sheet of that colour at that opacity leaves. */\nexport function mixToWhite(color: number, share: number): number {\n const s = Math.max(0, Math.min(1, share));\n const ch = (shift: number) => Math.round(255 - (255 - ((color >> shift) & 255)) * s) << shift;\n return ch(16) | ch(8) | ch(0);\n}\n"],"names":["gradeAt","grade","night","max","mixToWhite","color","share","s","ch","shift"],"mappings":"AAqBO,SAASA,EAAQC,GAAcC,GAAeC,IAAM,MAAkB;AAC3E,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGD,CAAK,CAAC;AACxC,SAAO,EAAE,OAAOD,EAAM,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK,IAAIE,GAAKF,EAAM,OAAOA,EAAM,QAAQA,EAAM,OAAO,CAAC,CAAC,EAAA;AAC1G;AAGO,SAASG,EAAWC,GAAeC,GAAuB;AAC/D,QAAMC,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGD,CAAK,CAAC,GAClCE,IAAK,CAACC,MAAkB,KAAK,MAAM,OAAO,OAAQJ,KAASI,IAAS,QAAQF,CAAC,KAAKE;AACxF,SAAOD,EAAG,EAAE,IAAIA,EAAG,CAAC,IAAIA,EAAG,CAAC;AAC9B;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"weather.js","sources":["../../src/ambient/weather.ts"],"sourcesContent":["/**\n * Weather rolled from wall time alone, so two players standing in the same place are rained on together without a\n * packet between them. Pure and deterministic: the same key, time and table give the same sky on every machine.\n *\n * Time is cut into slots; each slot rolls one entry of a weighted list from a hash of the key and the slot number,\n * and a change of kind fades out at the end of its slot and in at the start of the next. Places can share one sky:\n * a key listed in a `skies` group takes its kind from the group's roll (so the rain does not stop at a map's edge)\n * and only its strength from its own list. The kind `'clear'` means no weather and has no strength.\n *\n * ```ts\n * const TABLE: WeatherTable = {\n * slotMs: 12 * 60_000,\n * fadeMs: 40_000,\n * places: { meadow: [{ kind: 'clear', weight: 4 }, { kind: 'rain', weight: 3, intensity: [0.4, 1] }] },\n * };\n * const w = weatherAt('meadow', serverNow(), TABLE); // { kind: 'rain', intensity: 0.7 }\n * ```\n */\n\nexport interface WeatherEntry<K extends string = string> {\n kind: K | 'clear';\n weight: number;\n /** The strength range rolled for this kind, [lo, hi] in 0..1 (default [1, 1]). */\n intensity?: readonly [number, number];\n}\n\nexport interface WeatherSky<K extends string = string> {\n /** The place keys under this sky. */\n places: readonly string[];\n entries: readonly WeatherEntry<K>[];\n}\n\nexport interface WeatherTable<K extends string = string> {\n /** How long one roll of the sky lasts. */\n slotMs: number;\n /** How long a change of kind takes to fade out and in. */\n fadeMs: number;\n /** Each place's own list; a place with none (a cave) has no weather, even inside a sky. */\n places: Readonly<Record<string, readonly WeatherEntry<K>[]>>;\n /** Groups of neighbouring places that share one sky, by group id. */\n skies?: Readonly<Record<string, WeatherSky<K>>>;\n}\n\nexport interface WeatherState<K extends string = string> {\n kind: K | 'clear';\n /** 0..1, already faded round a change of kind; 0 when clear. */\n intensity: number;\n}\n\nconst CLEAR: WeatherState<never> = { kind: 'clear', intensity: 0 };\n\n/** FNV-1a over the text, then an avalanche: a stable number in [0, 1). */\nexport function hash01(text: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n return (h >>> 0) / 4294967296;\n}\n\n/** The sky group each place belongs to, worked out once per table. */\nconst SKY_OF = new WeakMap<object, Map<string, string>>();\n\nfunction skyOf(table: WeatherTable<string>, key: string): string | undefined {\n if (!table.skies) return undefined;\n let map = SKY_OF.get(table);\n if (!map) {\n map = new Map();\n for (const [id, sky] of Object.entries(table.skies)) for (const p of sky.places) map.set(p, id);\n SKY_OF.set(table, map);\n }\n return map.get(key);\n}\n\nfunction roll<K extends string>(seed: string, entries: readonly WeatherEntry<K>[], slot: number): WeatherState<K> {\n const total = entries.reduce((s, e) => s + e.weight, 0);\n if (total <= 0) return CLEAR;\n let r = hash01(`${seed}:${slot}`) * total;\n let pick = entries[entries.length - 1]!;\n for (const e of entries) {\n r -= e.weight;\n if (r < 0) {\n pick = e;\n break;\n }\n }\n if (pick.kind === 'clear') return CLEAR;\n const [lo, hi] = pick.intensity ?? [1, 1];\n return { kind: pick.kind, intensity: lo + (hi - lo) * hash01(`${seed}:${slot}:i`) };\n}\n\n/** The weather over place `key` at wall time `ms`. */\nexport function weatherAt<K extends string>(key: string, ms: number, table: WeatherTable<K>): WeatherState<K> {\n const own = table.places[key] ?? [];\n if (!own.length) return CLEAR;\n const group = skyOf(table, key);\n const seed = group === undefined ? key : `sky:${group}`;\n const entries = group === undefined ? own : table.skies![group]!.entries;\n if (!entries.length) return CLEAR;\n const slot = Math.floor(ms / table.slotMs);\n let now = roll(seed, entries, slot);\n if (now.kind === 'clear') return now;\n // a shared sky's kind at the strength this place feels it: its own range for that kind when its list has one\n if (group !== undefined) {\n const range = own.find((e) => e.kind === now.kind)?.intensity;\n if (range) now = { kind: now.kind, intensity: range[0] + (range[1] - range[0]) * hash01(`${seed}:${slot}:i`) };\n }\n const into = ms - slot * table.slotMs;\n const left = table.slotMs - into;\n let fade = 1;\n if (table.fadeMs > 0) {\n if (roll(seed, entries, slot - 1).kind !== now.kind) fade = Math.min(fade, into / table.fadeMs);\n if (roll(seed, entries, slot + 1).kind !== now.kind) fade = Math.min(fade, left / table.fadeMs);\n }\n return { kind: now.kind, intensity: now.intensity * Math.max(0, Math.min(1, fade)) };\n}\n"],"names":["CLEAR","hash01","text","h","i","SKY_OF","skyOf","table","key","map","id","sky","p","roll","seed","entries","slot","total","s","e","r","pick","lo","hi","weatherAt","ms","own","group","now","range","into","left","fade"],"mappings":"AAiDA,MAAMA,IAA6B,EAAE,MAAM,SAAS,WAAW,EAAA;AAGxD,SAASC,EAAOC,GAAsB;AAC3C,MAAIC,IAAI;AACR,WAASC,IAAI,GAAGA,IAAIF,EAAK,QAAQE;AAC/B,IAAAD,KAAKD,EAAK,WAAWE,CAAC,GACtBD,IAAI,KAAK,KAAKA,GAAG,QAAU;AAE7B,SAAAA,KAAKA,MAAM,IACXA,IAAI,KAAK,KAAKA,GAAG,UAAU,GAC3BA,KAAKA,MAAM,KACHA,MAAM,KAAK;AACrB;AAGA,MAAME,wBAAa,QAAA;AAEnB,SAASC,EAAMC,GAA6BC,GAAiC;AAC3E,MAAI,CAACD,EAAM,MAAO;AAClB,MAAIE,IAAMJ,EAAO,IAAIE,CAAK;AAC1B,MAAI,CAACE,GAAK;AACR,IAAAA,wBAAU,IAAA;AACV,eAAW,CAACC,GAAIC,CAAG,KAAK,OAAO,QAAQJ,EAAM,KAAK,EAAG,YAAWK,KAAKD,EAAI,OAAQ,CAAAF,EAAI,IAAIG,GAAGF,CAAE;AAC9F,IAAAL,EAAO,IAAIE,GAAOE,CAAG;AAAA,EACvB;AACA,SAAOA,EAAI,IAAID,CAAG;AACpB;AAEA,SAASK,EAAuBC,GAAcC,GAAqCC,GAA+B;AAChH,QAAMC,IAAQF,EAAQ,OAAO,CAACG,GAAGC,MAAMD,IAAIC,EAAE,QAAQ,CAAC;AACtD,MAAIF,KAAS,EAAG,QAAOjB;AACvB,MAAIoB,IAAInB,EAAO,GAAGa,CAAI,IAAIE,CAAI,EAAE,IAAIC,GAChCI,IAAON,EAAQA,EAAQ,SAAS,CAAC;AACrC,aAAW,KAAKA;AAEd,QADAK,KAAK,EAAE,QACHA,IAAI,GAAG;AACT,MAAAC,IAAO;AACP;AAAA,IACF;AAEF,MAAIA,EAAK,SAAS,QAAS,QAAOrB;AAClC,QAAM,CAACsB,GAAIC,CAAE,IAAIF,EAAK,aAAa,CAAC,GAAG,CAAC;AACxC,SAAO,EAAE,MAAMA,EAAK,MAAM,WAAWC,KAAMC,IAAKD,KAAMrB,EAAO,GAAGa,CAAI,IAAIE,CAAI,IAAI,EAAA;AAClF;AAGO,SAASQ,EAA4BhB,GAAaiB,GAAYlB,GAAyC;AAC5G,QAAMmB,IAAMnB,EAAM,OAAOC,CAAG,KAAK,CAAA;AACjC,MAAI,CAACkB,EAAI,OAAQ,QAAO1B;AACxB,QAAM2B,IAAQrB,EAAMC,GAAOC,CAAG,GACxBM,IAAOa,MAAU,SAAYnB,IAAM,OAAOmB,CAAK,IAC/CZ,IAAUY,MAAU,SAAYD,IAAMnB,EAAM,MAAOoB,CAAK,EAAG;AACjE,MAAI,CAACZ,EAAQ,OAAQ,QAAOf;AAC5B,QAAMgB,IAAO,KAAK,MAAMS,IAAKlB,EAAM,MAAM;AACzC,MAAIqB,IAAMf,EAAKC,GAAMC,GAASC,CAAI;AAClC,MAAIY,EAAI,SAAS,QAAS,QAAOA;AAEjC,MAAID,MAAU,QAAW;AACvB,UAAME,IAAQH,EAAI,KAAK,CAACP,MAAMA,EAAE,SAASS,EAAI,IAAI,GAAG;AACpD,IAAIC,UAAa,EAAE,MAAMD,EAAI,MAAM,WAAWC,EAAM,CAAC,KAAKA,EAAM,CAAC,IAAIA,EAAM,CAAC,KAAK5B,EAAO,GAAGa,CAAI,IAAIE,CAAI,IAAI,EAAA;AAAA,EAC7G;AACA,QAAMc,IAAOL,IAAKT,IAAOT,EAAM,QACzBwB,IAAOxB,EAAM,SAASuB;AAC5B,MAAIE,IAAO;AACX,SAAIzB,EAAM,SAAS,MACbM,EAAKC,GAAMC,GAASC,IAAO,CAAC,EAAE,SAASY,EAAI,aAAa,KAAK,IAAII,GAAMF,IAAOvB,EAAM,MAAM,IAC1FM,EAAKC,GAAMC,GAASC,IAAO,CAAC,EAAE,SAASY,EAAI,aAAa,KAAK,IAAII,GAAMD,IAAOxB,EAAM,MAAM,KAEzF,EAAE,MAAMqB,EAAI,MAAM,WAAWA,EAAI,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGI,CAAI,CAAC,EAAA;AACnF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AnimStateMachine.js","sources":["../../src/anim/AnimStateMachine.ts"],"sourcesContent":["import { Animator, type AnimationClip } from './Animator';\nimport {\n OP_EQ,\n OP_GE,\n OP_GT,\n OP_LE,\n OP_LT,\n OP_NE,\n PARAM_BOOL,\n PARAM_TRIGGER,\n compileParams,\n compileStates,\n compileTransitions,\n type CompiledParams,\n type CompiledStates,\n type CompiledTransitions,\n} from './animStateCompile';\nimport type { AnimParams, AnimStateMachineOptions } from './animStateTypes';\n\n/**\n * Chooses which clip an `Animator` plays from named states and the transitions between them, the way an animation\n * graph in a larger engine does: the game sets parameters (a bool, a number, a one-shot trigger) and the machine\n * moves between states when a transition's conditions hold, when a clip has played through, or from any state at\n * all by priority. Frame markers fire events (a footstep on frame 3) for the game to hang sounds and effects on.\n *\n * Pure and driven by `update(dt)`, so it runs under node with a fake clock. The definition is compiled into typed\n * arrays once; an update allocates nothing.\n *\n * ```ts\n * const sm = new AnimStateMachine(sheet.clips, {\n * initial: 'idle',\n * params: { speed: 0, grounded: true, jump: 'trigger' },\n * states: { idle: { clip: 'idle' }, run: { clip: 'run', markers: [{ frame: 2, name: 'step' }] }, jump: { clip: 'jump' } },\n * transitions: [\n * { from: 'idle', to: 'run', when: { speed: ['>', 0] } },\n * { from: 'run', to: 'idle', when: { speed: 0 } },\n * { from: '*', to: 'jump', when: { jump: true, grounded: true }, priority: 10 },\n * ],\n * });\n * sm.onEvent = (name) => name === 'step' && sound.play('step');\n * sm.set('speed', 3);\n * sm.update(dt); // seconds; then show sm.frame\n * ```\n */\nexport class AnimStateMachine<State extends string = string, Clip extends string = string, P extends AnimParams = AnimParams> {\n /** The animator it drives; read `frame` off it (or off the machine). */\n readonly animator: Animator<Clip>;\n /** Fired for every marker playback reaches, the state's first frame's on entering it. */\n onEvent: ((event: string, state: State, frame: number) => void) | null = null;\n /** Fired after each change of state. */\n onStateChange: ((from: State, to: State) => void) | null = null;\n\n private readonly params: CompiledParams;\n private readonly states: CompiledStates;\n private readonly tr: CompiledTransitions;\n private readonly keepTriggers: boolean;\n private stateIndex: number;\n /** Time played in this state, ms, scaled by speed as the animator scales it. */\n private time = 0;\n\n constructor(clips: Record<Clip, AnimationClip>, options: AnimStateMachineOptions<State, Clip, P>) {\n const opts = options as unknown as AnimStateMachineOptions<string, string, AnimParams>;\n this.params = compileParams(options.params ?? {});\n this.states = compileStates(opts, clips, this.params);\n this.tr = compileTransitions(opts, this.states, this.params);\n this.keepTriggers = options.keepTriggers === true;\n this.stateIndex = this.states.index.get(options.initial)!;\n this.animator = new Animator(clips, this.states.clips[this.stateIndex] as Clip);\n this.applySpeed();\n }\n\n /** The state playing. */\n get state(): State {\n return this.states.names[this.stateIndex] as State;\n }\n\n /** The frame to show now (a sheet frame index or name, as the clip lists it). */\n get frame(): string | number {\n return this.animator.frame;\n }\n\n /** How much of this state's clip has played: 0 at entry, 1 after one pass, 2 after two loops. */\n get normalizedTime(): number {\n return this.time / this.states.lengths[this.stateIndex]!;\n }\n\n /** Sets a bool or number parameter. */\n set(name: keyof P & string, value: boolean | number): this {\n const i = this.paramIndex(name);\n if (this.params.kinds[i] === PARAM_TRIGGER) throw new Error(`AnimStateMachine: \"${name}\" is a trigger; use trigger()`);\n this.params.values[i] = typeof value === 'boolean' ? (value ? 1 : 0) : value;\n return this;\n }\n\n /** A parameter's value: bools as booleans, triggers as whether they are set. */\n get(name: keyof P & string): boolean | number {\n const i = this.paramIndex(name);\n const v = this.params.values[i]!;\n return this.params.kinds[i] === PARAM_BOOL || this.params.kinds[i] === PARAM_TRIGGER ? v !== 0 : v;\n }\n\n /** Fires a trigger: the next update's transitions see it once. */\n trigger(name: keyof P & string): this {\n this.params.values[this.triggerIndex(name)] = 1;\n return this;\n }\n\n resetTrigger(name: keyof P & string): this {\n this.params.values[this.triggerIndex(name)] = 0;\n return this;\n }\n\n /** Goes to `state` now, whatever the transitions say (a respawn, a cut scene). */\n play(state: State): this {\n const i = this.states.index.get(state);\n if (i === undefined) throw new Error(`AnimStateMachine: no state \"${state}\"`);\n this.enter(i);\n return this;\n }\n\n /** Back to the initial values of every parameter (triggers cleared). */\n resetParams(): this {\n this.params.values.set(this.params.initial);\n return this;\n }\n\n /**\n * Advances the clip by `dt` seconds, fires the markers it passed, then takes at most one transition (so a chain of\n * them never runs through several states in one frame). `update(0)` only checks the transitions.\n */\n update(dt: number): void {\n const s = this.stateIndex;\n this.applySpeed();\n const a = this.animator;\n const before = a.frameIndex;\n const wasFinished = a.finished;\n a.update(dt);\n if (dt > 0 && a.speed > 0) {\n const delta = dt * 1000 * a.speed;\n this.time += delta;\n if (!wasFinished) this.firePassed(s, before, a.frameIndex, delta);\n }\n // a marker's listener may have changed state: the transitions are the state's now playing\n const t = this.pick(this.stateIndex);\n if (t >= 0) {\n this.consumeTriggers(t);\n this.enter(this.tr.to[t]!);\n }\n if (!this.keepTriggers) this.clearTriggers();\n }\n\n private pick(s: number): number {\n const cands = this.states.candidates[s]!;\n const tr = this.tr;\n const ended = this.animator.finished || this.time >= this.states.lengths[s]!;\n for (let k = 0; k < cands.length; k++) {\n const t = cands[k]!;\n if (tr.to[t] === s && tr.anyState[t] && !tr.self[t]) continue;\n if (tr.onEnd[t] && !ended) continue;\n if (tr.exitTime[t]! >= 0 && this.normalizedTime < tr.exitTime[t]!) continue;\n if (this.holds(t)) return t;\n }\n return -1;\n }\n\n private holds(t: number): boolean {\n const tr = this.tr;\n const values = this.params.values;\n for (let c = tr.condStart[t]!; c < tr.condEnd[t]!; c++) {\n const v = values[tr.condParam[c]!]!;\n const want = tr.condValue[c]!;\n let ok: boolean;\n switch (tr.condOp[c]) {\n case OP_EQ: ok = v === want; break;\n case OP_NE: ok = v !== want; break;\n case OP_GT: ok = v > want; break;\n case OP_GE: ok = v >= want; break;\n case OP_LT: ok = v < want; break;\n case OP_LE: ok = v <= want; break;\n default: ok = false;\n }\n if (!ok) return false;\n }\n return true;\n }\n\n private enter(i: number): void {\n const from = this.stateIndex;\n this.stateIndex = i;\n this.time = 0;\n this.animator.play(this.states.clips[i] as Clip, true);\n this.applySpeed();\n this.onStateChange?.(this.states.names[from] as State, this.states.names[i] as State);\n // a marker on the first frame (a jump's whoosh) belongs to entering the state\n this.fire(i, 0);\n }\n\n /**\n * Fires the markers of every frame after `from` up to `to`. The animator skips frames on a long step, so they are\n * walked rather than only the one landed on; a step long enough to loop the whole clip fires each frame once.\n */\n private firePassed(s: number, from: number, to: number, deltaMs: number): void {\n const n = this.states.frameCounts[s]!;\n let steps = (to - from + n) % n;\n if (steps === 0 && deltaMs >= this.states.lengths[s]!) steps = n;\n for (let k = 1; k <= steps; k++) {\n this.fire(s, (from + k) % n);\n if (this.stateIndex !== s) return; // a listener changed state: the rest belong to a clip no longer playing\n }\n }\n\n private fire(s: number, frame: number): void {\n const names = this.states.markers[s]![frame];\n if (!names || !this.onEvent) return;\n const state = this.states.names[s] as State;\n for (let i = 0; i < names.length; i++) this.onEvent(names[i]!, state, frame);\n }\n\n private applySpeed(): void {\n const s = this.stateIndex;\n const p = this.states.speedParams[s]!;\n this.animator.speed = this.states.speeds[s]! * (p >= 0 ? Math.abs(this.params.values[p]!) : 1);\n }\n\n private consumeTriggers(t: number): void {\n const tr = this.tr;\n for (let c = tr.condStart[t]!; c < tr.condEnd[t]!; c++) {\n if (this.params.kinds[tr.condParam[c]!] === PARAM_TRIGGER) this.params.values[tr.condParam[c]!] = 0;\n }\n }\n\n private clearTriggers(): void {\n const { kinds, values } = this.params;\n for (let i = 0; i < kinds.length; i++) if (kinds[i] === PARAM_TRIGGER) values[i] = 0;\n }\n\n private paramIndex(name: string): number {\n const i = this.params.index.get(name);\n if (i === undefined) throw new Error(`AnimStateMachine: no parameter \"${name}\"`);\n return i;\n }\n\n private triggerIndex(name: string): number {\n const i = this.paramIndex(name);\n if (this.params.kinds[i] !== PARAM_TRIGGER) throw new Error(`AnimStateMachine: \"${name}\" is not a trigger`);\n return i;\n }\n}\n"],"names":["AnimStateMachine","clips","options","opts","compileParams","compileStates","compileTransitions","Animator","name","value","i","PARAM_TRIGGER","v","PARAM_BOOL","state","dt","a","before","wasFinished","delta","t","s","cands","tr","ended","k","values","c","want","ok","OP_EQ","OP_NE","OP_GT","OP_GE","OP_LT","OP_LE","from","to","deltaMs","n","steps","frame","names","p","kinds"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Animator.js","sources":["../../src/anim/Animator.ts"],"sourcesContent":["/**\n * Frame animation of one sprite: which clip plays, which frame it is on. Pure state, advanced by time; a system copies\n * `frame` onto the sprite (`animationSystem`).\n *\n * ```ts\n * const clips = { idle: { frames: [0, 1], fps: 2 }, walk: { frames: [2, 3, 4, 5], fps: 10 }, die: { frames: [6, 7, 8], fps: 8, loop: false } };\n * const a = new Animator(clips, 'idle');\n * a.play('walk'); // switching to the clip already playing keeps its place\n * a.update(dt); // seconds\n * sprite.setFrame(a.frame);\n * ```\n */\nexport interface AnimationClip {\n /** Frame names or indices of the sprite sheet, in order. */\n frames: readonly (string | number)[];\n /** Frames per second (default 10). Pixel art usually wants 8–12. */\n fps?: number;\n /** Start over at the end (default true); otherwise hold the last frame and report it finished. */\n loop?: boolean;\n /** Per-frame lengths in ms, overriding `fps` (a held key pose, a quick smear). */\n durations?: readonly number[];\n}\n\nexport type AnimationClips = Record<string, AnimationClip>;\n\nexport class Animator<Name extends string = string> {\n private clip: AnimationClip;\n private index = 0;\n private elapsed = 0;\n private ended = false;\n private name: Name;\n /** Called when a clip that does not loop reaches its last frame. */\n onComplete: ((clip: Name) => void) | null = null;\n /** Playback speed multiplier (1 normal, 0 paused). */\n speed = 1;\n\n constructor(\n private readonly clips: Record<Name, AnimationClip>,\n initial: NoInfer<Name>,\n ) {\n const clip = clips[initial];\n if (!clip) throw new Error(`Animator: no clip \"${initial}\"`);\n if (clip.frames.length === 0) throw new Error(`Animator: clip \"${initial}\" has no frames`);\n this.clip = clip;\n this.name = initial;\n }\n\n /** The clip playing. */\n get current(): Name {\n return this.name;\n }\n\n /** The frame to show now. */\n get frame(): string | number {\n return this.clip.frames[this.index]!;\n }\n\n /** Which frame of the clip (0-based). */\n get frameIndex(): number {\n return this.index;\n }\n\n /** A non-looping clip has reached its last frame. */\n get finished(): boolean {\n return this.ended;\n }\n\n /** Plays `name`. The clip already playing keeps its place unless `restart`. */\n play(name: Name, restart = false): this {\n if (name === this.name && !restart) return this;\n const clip = this.clips[name];\n if (!clip) throw new Error(`Animator: no clip \"${name}\"`);\n if (clip.frames.length === 0) throw new Error(`Animator: clip \"${name}\" has no frames`);\n this.clip = clip;\n this.name = name;\n this.index = 0;\n this.elapsed = 0;\n this.ended = false;\n return this;\n }\n\n /** Advances by `dt` seconds; returns true when the frame changed. */\n update(dt: number): boolean {\n if (this.ended || !(dt > 0) || this.speed <= 0) return false;\n this.elapsed += dt * 1000 * this.speed;\n const before = this.index;\n const n = this.clip.frames.length;\n for (let len = this.lengthOf(this.index); this.elapsed >= len; len = this.lengthOf(this.index)) {\n this.elapsed -= len;\n if (this.index < n - 1) {\n this.index++;\n } else if (this.clip.loop !== false) {\n this.index = 0;\n } else {\n this.ended = true;\n this.elapsed = 0;\n this.onComplete?.(this.name);\n break;\n }\n }\n return this.index !== before;\n }\n\n private lengthOf(i: number): number {\n const ms = this.clip.durations?.[i] ?? 1000 / (this.clip.fps ?? 10);\n return Math.max(1, ms);\n }\n}\n"],"names":["Animator","clips","initial","clip","name","restart","dt","before","n","len","i","ms"],"mappings":"AAyBO,MAAMA,EAAuC;AAAA,EAWlD,YACmBC,GACjBC,GACA;AAFiB,SAAA,QAAAD;AAGjB,UAAME,IAAOF,EAAMC,CAAO;AAC1B,QAAI,CAACC,EAAM,OAAM,IAAI,MAAM,sBAAsBD,CAAO,GAAG;AAC3D,QAAIC,EAAK,OAAO,WAAW,SAAS,IAAI,MAAM,mBAAmBD,CAAO,iBAAiB;AACzF,SAAK,OAAOC,GACZ,KAAK,OAAOD;AAAA,EACd;AAAA,EARmB;AAAA,EAXX;AAAA,EACA,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA;AAAA,EAER,aAA4C;AAAA;AAAA,EAE5C,QAAQ;AAAA;AAAA,EAcR,IAAI,UAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,QAAyB;AAC3B,WAAO,KAAK,KAAK,OAAO,KAAK,KAAK;AAAA,EACpC;AAAA;AAAA,EAGA,IAAI,aAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAKE,GAAYC,IAAU,IAAa;AACtC,QAAID,MAAS,KAAK,QAAQ,CAACC,EAAS,QAAO;AAC3C,UAAMF,IAAO,KAAK,MAAMC,CAAI;AAC5B,QAAI,CAACD,EAAM,OAAM,IAAI,MAAM,sBAAsBC,CAAI,GAAG;AACxD,QAAID,EAAK,OAAO,WAAW,SAAS,IAAI,MAAM,mBAAmBC,CAAI,iBAAiB;AACtF,gBAAK,OAAOD,GACZ,KAAK,OAAOC,GACZ,KAAK,QAAQ,GACb,KAAK,UAAU,GACf,KAAK,QAAQ,IACN;AAAA,EACT;AAAA;AAAA,EAGA,OAAOE,GAAqB;AAC1B,QAAI,KAAK,SAAS,EAAEA,IAAK,MAAM,KAAK,SAAS,EAAG,QAAO;AACvD,SAAK,WAAWA,IAAK,MAAO,KAAK;AACjC,UAAMC,IAAS,KAAK,OACdC,IAAI,KAAK,KAAK,OAAO;AAC3B,aAASC,IAAM,KAAK,SAAS,KAAK,KAAK,GAAG,KAAK,WAAWA,GAAKA,IAAM,KAAK,SAAS,KAAK,KAAK;AAE3F,UADA,KAAK,WAAWA,GACZ,KAAK,QAAQD,IAAI;AACnB,aAAK;AAAA,eACI,KAAK,KAAK,SAAS;AAC5B,aAAK,QAAQ;AAAA,WACR;AACL,aAAK,QAAQ,IACb,KAAK,UAAU,GACf,KAAK,aAAa,KAAK,IAAI;AAC3B;AAAA,MACF;AAEF,WAAO,KAAK,UAAUD;AAAA,EACxB;AAAA,EAEQ,SAASG,GAAmB;AAClC,UAAMC,IAAK,KAAK,KAAK,YAAYD,CAAC,KAAK,OAAQ,KAAK,KAAK,OAAO;AAChE,WAAO,KAAK,IAAI,GAAGC,CAAE;AAAA,EACvB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animStateCompile.js","sources":["../../src/anim/animStateCompile.ts"],"sourcesContent":["import type { AnimationClip } from './Animator';\nimport type { AnimCondition, AnimParams, AnimStateMachineOptions, CompareOp } from './animStateTypes';\n\n/*\n * The state machine's definition turned once, at construction, into flat typed arrays, so an update walks numbers and\n * allocates nothing: parameter values by index, every transition's conditions in one run of arrays, and each state's\n * candidate transitions (its own and the any-state ones) already sorted by priority.\n */\n\nexport const PARAM_BOOL = 0;\nexport const PARAM_NUMBER = 1;\nexport const PARAM_TRIGGER = 2;\n\nexport const OP_EQ = 0;\nexport const OP_NE = 1;\nexport const OP_GT = 2;\nexport const OP_GE = 3;\nexport const OP_LT = 4;\nexport const OP_LE = 5;\n\nconst OPS: Record<CompareOp, number> = { '==': OP_EQ, '!=': OP_NE, '>': OP_GT, '>=': OP_GE, '<': OP_LT, '<=': OP_LE };\n\nexport interface CompiledParams {\n names: string[];\n index: Map<string, number>;\n kinds: Uint8Array;\n values: Float64Array;\n initial: Float64Array;\n}\n\nexport interface CompiledStates {\n names: string[];\n index: Map<string, number>;\n clips: string[];\n speeds: Float64Array;\n /** Index of the speed parameter, or -1. */\n speedParams: Int32Array;\n /** Total length of each state's clip in ms, at speed 1. */\n lengths: Float64Array;\n frameCounts: Int32Array;\n /** Per state, per clip frame: the event names fired there (undefined: none). */\n markers: (readonly (readonly string[] | undefined)[])[];\n /** Per state: transition indices to try, best first. */\n candidates: Int32Array[];\n}\n\nexport interface CompiledTransitions {\n to: Int32Array;\n anyState: Uint8Array;\n self: Uint8Array;\n onEnd: Uint8Array;\n /** -1: none. */\n exitTime: Float64Array;\n condStart: Int32Array;\n condEnd: Int32Array;\n condParam: Int32Array;\n condOp: Uint8Array;\n condValue: Float64Array;\n}\n\n/** The length of a clip in ms, frame by frame exactly as `Animator` times it. */\nexport function clipLength(clip: AnimationClip): number {\n let ms = 0;\n for (let i = 0; i < clip.frames.length; i++) ms += Math.max(1, clip.durations?.[i] ?? 1000 / (clip.fps ?? 10));\n return ms;\n}\n\nexport function compileParams(decl: AnimParams): CompiledParams {\n const names = Object.keys(decl);\n const kinds = new Uint8Array(names.length);\n const initial = new Float64Array(names.length);\n names.forEach((n, i) => {\n const v = decl[n];\n if (v === 'trigger') kinds[i] = PARAM_TRIGGER;\n else if (typeof v === 'boolean') {\n kinds[i] = PARAM_BOOL;\n initial[i] = v ? 1 : 0;\n } else if (typeof v === 'number' && Number.isFinite(v)) {\n kinds[i] = PARAM_NUMBER;\n initial[i] = v;\n } else throw new Error(`AnimStateMachine: parameter \"${n}\" must start as a boolean, a number or 'trigger'`);\n });\n return { names, index: new Map(names.map((n, i) => [n, i])), kinds, values: initial.slice(), initial };\n}\n\nexport function compileStates(\n options: AnimStateMachineOptions<string, string, AnimParams>,\n clips: Record<string, AnimationClip>,\n params: CompiledParams,\n): CompiledStates {\n const names = Object.keys(options.states);\n if (!names.includes(options.initial)) throw new Error(`AnimStateMachine: no state \"${options.initial}\"`);\n const n = names.length;\n const out: CompiledStates = {\n names,\n index: new Map(names.map((s, i) => [s, i])),\n clips: [],\n speeds: new Float64Array(n),\n speedParams: new Int32Array(n),\n lengths: new Float64Array(n),\n frameCounts: new Int32Array(n),\n markers: [],\n candidates: [],\n };\n names.forEach((s, i) => {\n const def = options.states[s]!;\n const clip = clips[def.clip];\n if (!clip) throw new Error(`AnimStateMachine: state \"${s}\" plays clip \"${def.clip}\", which does not exist`);\n if (clip.frames.length === 0) throw new Error(`AnimStateMachine: clip \"${def.clip}\" has no frames`);\n out.clips.push(def.clip);\n out.speeds[i] = def.speed ?? 1;\n out.speedParams[i] = def.speedParam === undefined ? -1 : paramIndex(params, def.speedParam, PARAM_NUMBER, `state \"${s}\"`);\n out.lengths[i] = clipLength(clip);\n out.frameCounts[i] = clip.frames.length;\n const byFrame: (string[] | undefined)[] = new Array(clip.frames.length).fill(undefined);\n for (const m of def.markers ?? []) {\n if (!(m.frame >= 0 && m.frame < clip.frames.length)) throw new Error(`AnimStateMachine: marker \"${m.name}\" of state \"${s}\" is past its clip`);\n (byFrame[m.frame] ??= []).push(m.name);\n }\n out.markers.push(byFrame);\n });\n return out;\n}\n\nexport function compileTransitions(\n options: AnimStateMachineOptions<string, string, AnimParams>,\n states: CompiledStates,\n params: CompiledParams,\n): CompiledTransitions {\n const list = options.transitions ?? [];\n const n = list.length;\n const condParam: number[] = [];\n const condOp: number[] = [];\n const condValue: number[] = [];\n const t: CompiledTransitions = {\n to: new Int32Array(n),\n anyState: new Uint8Array(n),\n self: new Uint8Array(n),\n onEnd: new Uint8Array(n),\n exitTime: new Float64Array(n),\n condStart: new Int32Array(n),\n condEnd: new Int32Array(n),\n condParam: new Int32Array(0),\n condOp: new Uint8Array(0),\n condValue: new Float64Array(0),\n };\n const sources: number[][] = states.names.map(() => []);\n const stateOf = (s: string, where: string) => {\n const i = states.index.get(s);\n if (i === undefined) throw new Error(`AnimStateMachine: ${where} names no state \"${s}\"`);\n return i;\n };\n list.forEach((tr, i) => {\n const where = `transition ${i} (→ ${tr.to})`;\n t.to[i] = stateOf(tr.to, where);\n t.anyState[i] = tr.from === '*' ? 1 : 0;\n t.self[i] = tr.self ? 1 : 0;\n t.onEnd[i] = tr.onEnd ? 1 : 0;\n t.exitTime[i] = tr.exitTime ?? -1;\n if (tr.from === '*') states.names.forEach((_s, si) => sources[si]!.push(i));\n else for (const s of typeof tr.from === 'string' ? [tr.from] : tr.from) sources[stateOf(s, where)]!.push(i);\n t.condStart[i] = condParam.length;\n for (const [name, cond] of Object.entries(tr.when ?? {}) as [string, AnimCondition][]) {\n const p = params.index.get(name);\n if (p === undefined) throw new Error(`AnimStateMachine: ${where} asks for parameter \"${name}\", which is not declared`);\n const [op, value] = conditionOf(params.kinds[p]!, cond, `${where}, parameter \"${name}\"`);\n condParam.push(p);\n condOp.push(op);\n condValue.push(value);\n }\n t.condEnd[i] = condParam.length;\n });\n t.condParam = Int32Array.from(condParam);\n t.condOp = Uint8Array.from(condOp);\n t.condValue = Float64Array.from(condValue);\n const priority = (i: number) => list[i]!.priority ?? 0;\n // a stable sort: equal priorities keep the order they were written in\n states.candidates = sources.map((ids) => Int32Array.from(ids.sort((a, b) => priority(b) - priority(a) || a - b)));\n return t;\n}\n\nfunction conditionOf(kind: number, cond: AnimCondition, where: string): [number, number] {\n if (Array.isArray(cond)) {\n const [op, value] = cond as readonly [CompareOp, number];\n if (kind !== PARAM_NUMBER) throw new Error(`AnimStateMachine: ${where} compares a parameter that is not a number`);\n if (!(op in OPS) || !Number.isFinite(value)) throw new Error(`AnimStateMachine: ${where} has a bad comparison`);\n return [OPS[op], value];\n }\n if (kind === PARAM_NUMBER) {\n if (typeof cond !== 'number') throw new Error(`AnimStateMachine: ${where} is a number; give a number or [op, number]`);\n return [OP_EQ, cond];\n }\n if (typeof cond !== 'boolean') throw new Error(`AnimStateMachine: ${where} is a ${kind === PARAM_BOOL ? 'bool' : 'trigger'}; give true or false`);\n return [OP_EQ, cond ? 1 : 0];\n}\n\nfunction paramIndex(params: CompiledParams, name: string, kind: number, where: string): number {\n const i = params.index.get(name);\n if (i === undefined || params.kinds[i] !== kind) throw new Error(`AnimStateMachine: ${where} names \"${name}\", which is not a number parameter`);\n return i;\n}\n"],"names":["PARAM_BOOL","PARAM_NUMBER","PARAM_TRIGGER","OP_EQ","OP_NE","OP_GT","OP_GE","OP_LT","OP_LE","OPS","clipLength","clip","ms","i","compileParams","decl","names","kinds","initial","n","v","compileStates","options","clips","params","out","s","def","paramIndex","byFrame","m","compileTransitions","states","list","condParam","condOp","condValue","t","sources","stateOf","where","tr","_s","si","name","cond","p","op","value","conditionOf","priority","ids","a","b","kind"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animStateSystem.js","sources":["../../src/anim/animStateSystem.ts"],"sourcesContent":["import { defineComponent, system, type ComponentType, type System } from '../ecs/World';\nimport { SpriteComponent } from './animationSystem';\nimport type { AnimStateMachine } from './AnimStateMachine';\n\n/** An entity's animation state machine. Give an entity this instead of `AnimationComponent`: the machine owns its animator. */\nexport const AnimStateComponent: ComponentType<AnimStateMachine> = defineComponent<AnimStateMachine>('AnimState');\n\n/**\n * Updates every `AnimStateMachine` each frame and shows its frame on the entity's sprite (only when it changed).\n * Add it after the systems that set the machines' parameters, so a change shows the same frame.\n */\nexport function animStateSystem(name = 'anim-state'): System {\n return system(name, 'frame', ({ world, dt }) => {\n world.each(SpriteComponent, AnimStateComponent, (_e, sprite, sm) => {\n sm.update(dt);\n const frame = sm.frame;\n if (sprite.frameName !== String(frame)) sprite.setFrame(frame);\n });\n });\n}\n"],"names":["AnimStateComponent","defineComponent","animStateSystem","name","system","world","dt","SpriteComponent","_e","sprite","sm","frame"],"mappings":";;AAKO,MAAMA,IAAsDC,EAAkC,WAAW;AAMzG,SAASC,EAAgBC,IAAO,cAAsB;AAC3D,SAAOC,EAAOD,GAAM,SAAS,CAAC,EAAE,OAAAE,GAAO,IAAAC,QAAS;AAC9C,IAAAD,EAAM,KAAKE,GAAiBP,GAAoB,CAACQ,GAAIC,GAAQC,MAAO;AAClE,MAAAA,EAAG,OAAOJ,CAAE;AACZ,YAAMK,IAAQD,EAAG;AACjB,MAAID,EAAO,cAAc,OAAOE,CAAK,KAAGF,EAAO,SAASE,CAAK;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACH;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"animationSystem.js","sources":["../../src/anim/animationSystem.ts"],"sourcesContent":["import type { Image } from '../core/Image';\nimport { defineComponent, system, type ComponentType, type System } from '../ecs/World';\nimport type { Animator } from './Animator';\n\n/** The engine's sprite component: the `Image` an entity is drawn as. */\nexport const SpriteComponent: ComponentType<Image> = defineComponent<Image>('Sprite');\n\n/** The engine's animation component: the `Animator` choosing the sprite's frame. */\nexport const AnimationComponent: ComponentType<Animator> = defineComponent<Animator>('Animation');\n\n/**\n * Advances every `Animator` each frame and shows its frame on the entity's sprite (only when it changed, so an idle\n * crowd costs no texture swaps).\n */\nexport function animationSystem(name = 'animation'): System {\n return system(name, 'frame', ({ world, dt }) => {\n world.each(SpriteComponent, AnimationComponent, (_e, sprite, anim) => {\n const changed = anim.update(dt);\n if (changed || sprite.frameName !== String(anim.frame)) sprite.setFrame(anim.frame);\n });\n });\n}\n"],"names":["SpriteComponent","defineComponent","AnimationComponent","animationSystem","name","system","world","dt","_e","sprite","anim"],"mappings":";AAKO,MAAMA,IAAwCC,EAAuB,QAAQ,GAGvEC,IAA8CD,EAA0B,WAAW;AAMzF,SAASE,EAAgBC,IAAO,aAAqB;AAC1D,SAAOC,EAAOD,GAAM,SAAS,CAAC,EAAE,OAAAE,GAAO,IAAAC,QAAS;AAC9C,IAAAD,EAAM,KAAKN,GAAiBE,GAAoB,CAACM,GAAIC,GAAQC,MAAS;AAEpE,OADgBA,EAAK,OAAOH,CAAE,KACfE,EAAO,cAAc,OAAOC,EAAK,KAAK,MAAGD,EAAO,SAASC,EAAK,KAAK;AAAA,IACpF,CAAC;AAAA,EACH,CAAC;AACH;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asepriteSlices.js","sources":["../../src/anim/asepriteSlices.ts"],"sourcesContent":["import type { AsepriteSheet, AsepriteSlice, AsepriteSliceKey } from './asepriteTypes';\n\n/** A slice by name, or undefined. */\nexport function sliceNamed(sheet: AsepriteSheet, name: string): AsepriteSlice | undefined {\n return sheet.slices.find((s) => s.name === name);\n}\n\n/**\n * The key of `slice` in effect on sprite frame `frame`: Aseprite keys a slice on the frame it changes and holds it\n * until the next key, so the last key at or before the frame. Before the first key the slice is not there (undefined).\n */\nexport function sliceAt(slice: AsepriteSlice, frame = 0): AsepriteSliceKey | undefined {\n let found: AsepriteSliceKey | undefined;\n for (const k of slice.keys) {\n if (k.frame > frame) break;\n found = k;\n }\n return found;\n}\n\n/**\n * The 9-slice borders of a key with a centre — how far in from each edge the stretchable middle starts — in the shape\n * Pixi's `NineSliceSprite` and `Texture.defaultBorders` take. Undefined for a key with no centre.\n */\nexport function sliceBorders(key: AsepriteSliceKey): { left: number; top: number; right: number; bottom: number } | undefined {\n const c = key.center;\n if (!c) return undefined;\n return { left: c.x, top: c.y, right: key.bounds.w - c.x - c.w, bottom: key.bounds.h - c.y - c.h };\n}\n"],"names":["sliceNamed","sheet","name","s","sliceAt","slice","frame","found","k","sliceBorders","key","c"],"mappings":"AAGO,SAASA,EAAWC,GAAsBC,GAAyC;AACxF,SAAOD,EAAM,OAAO,KAAK,CAACE,MAAMA,EAAE,SAASD,CAAI;AACjD;AAMO,SAASE,EAAQC,GAAsBC,IAAQ,GAAiC;AACrF,MAAIC;AACJ,aAAWC,KAAKH,EAAM,MAAM;AAC1B,QAAIG,EAAE,QAAQF,EAAO;AACrB,IAAAC,IAAQC;AAAA,EACV;AACA,SAAOD;AACT;AAMO,SAASE,EAAaC,GAAiG;AAC5H,QAAMC,IAAID,EAAI;AACd,MAAKC;AACL,WAAO,EAAE,MAAMA,EAAE,GAAG,KAAKA,EAAE,GAAG,OAAOD,EAAI,OAAO,IAAIC,EAAE,IAAIA,EAAE,GAAG,QAAQD,EAAI,OAAO,IAAIC,EAAE,IAAIA,EAAE,EAAA;AAChG;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"asepriteTags.js","sources":["../../src/anim/asepriteTags.ts"],"sourcesContent":["import type { AsepriteDirection } from './asepriteTypes';\n\nconst DIRECTIONS: readonly AsepriteDirection[] = ['forward', 'reverse', 'pingpong', 'pingpong_reverse'];\n\n/** Aseprite's direction name, or forward for anything unknown (older files write none). */\nexport function tagDirection(raw: string | undefined): AsepriteDirection {\n return DIRECTIONS.includes(raw as AsepriteDirection) ? (raw as AsepriteDirection) : 'forward';\n}\n\n/** Aseprite writes the repeat count as a string; absent, 0 or nonsense is forever. */\nexport function tagRepeat(raw: string | number | undefined): number {\n const n = typeof raw === 'number' ? raw : raw === undefined ? 0 : Number.parseInt(raw, 10);\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;\n}\n\n/**\n * The sheet frames a tag plays, in playing order, unrolled so the engine's `Animator` (which only ever plays a list\n * forward) shows exactly what Aseprite's own player shows.\n *\n * - forever (`repeat` 0): forward and reverse are one pass, looped; a ping-pong is one round trip without its two\n * ends doubled (a b c d c b, then a again), so a loop never stutters on the turn.\n * - `repeat` n: n passes, played once. A ping-pong counts each pass in one direction as one, as Aseprite does, and two\n * passes share their turning frame (repeat 2 of a b c is a b c b a).\n */\nexport function tagFrames(from: number, to: number, direction: AsepriteDirection, repeat: number): number[] {\n const lo = Math.min(from, to);\n const hi = Math.max(from, to);\n const up: number[] = [];\n for (let i = lo; i <= hi; i++) up.push(i);\n const down = up.slice().reverse();\n const backward = direction === 'reverse' || direction === 'pingpong_reverse';\n const first = backward ? down : up;\n const pingpong = direction === 'pingpong' || direction === 'pingpong_reverse';\n\n if (repeat === 0) {\n if (!pingpong || up.length <= 2) return first;\n const second = backward ? up : down;\n return first.concat(second.slice(1, -1));\n }\n if (!pingpong) {\n const out: number[] = [];\n for (let r = 0; r < repeat; r++) out.push(...first);\n return out;\n }\n const out = first.slice();\n for (let r = 1; r < repeat; r++) {\n const pass = r % 2 === 0 ? first : backward ? up : down;\n // the turning frame closes one pass and opens the next: shown once, as Aseprite shows it\n out.push(...(up.length > 1 ? pass.slice(1) : pass));\n }\n return out;\n}\n"],"names":["DIRECTIONS","tagDirection","raw","tagRepeat","n","tagFrames","from","to","direction","repeat","lo","hi","up","i","down","backward","first","pingpong","second","out","r","pass"],"mappings":"AAEA,MAAMA,IAA2C,CAAC,WAAW,WAAW,YAAY,kBAAkB;AAG/F,SAASC,EAAaC,GAA4C;AACvE,SAAOF,EAAW,SAASE,CAAwB,IAAKA,IAA4B;AACtF;AAGO,SAASC,EAAUD,GAA0C;AAClE,QAAME,IAAI,OAAOF,KAAQ,WAAWA,IAAMA,MAAQ,SAAY,IAAI,OAAO,SAASA,GAAK,EAAE;AACzF,SAAO,OAAO,SAASE,CAAC,KAAKA,IAAI,IAAI,KAAK,MAAMA,CAAC,IAAI;AACvD;AAWO,SAASC,EAAUC,GAAcC,GAAYC,GAA8BC,GAA0B;AAC1G,QAAMC,IAAK,KAAK,IAAIJ,GAAMC,CAAE,GACtBI,IAAK,KAAK,IAAIL,GAAMC,CAAE,GACtBK,IAAe,CAAA;AACrB,WAASC,IAAIH,GAAIG,KAAKF,GAAIE,IAAK,CAAAD,EAAG,KAAKC,CAAC;AACxC,QAAMC,IAAOF,EAAG,MAAA,EAAQ,QAAA,GAClBG,IAAWP,MAAc,aAAaA,MAAc,oBACpDQ,IAAQD,IAAWD,IAAOF,GAC1BK,IAAWT,MAAc,cAAcA,MAAc;AAE3D,MAAIC,MAAW,GAAG;AAChB,QAAI,CAACQ,KAAYL,EAAG,UAAU,EAAG,QAAOI;AACxC,UAAME,IAASH,IAAWH,IAAKE;AAC/B,WAAOE,EAAM,OAAOE,EAAO,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AACA,MAAI,CAACD,GAAU;AACb,UAAME,IAAgB,CAAA;AACtB,aAAS,IAAI,GAAG,IAAIV,GAAQ,IAAKU,CAAAA,EAAI,KAAK,GAAGH,CAAK;AAClD,WAAOG;AAAAA,EACT;AACA,QAAMA,IAAMH,EAAM,MAAA;AAClB,WAASI,IAAI,GAAGA,IAAIX,GAAQW,KAAK;AAC/B,UAAMC,IAAOD,IAAI,MAAM,IAAIJ,IAAQD,IAAWH,IAAKE;AAEnD,IAAAK,EAAI,KAAK,GAAIP,EAAG,SAAS,IAAIS,EAAK,MAAM,CAAC,IAAIA,CAAK;AAAA,EACpD;AACA,SAAOF;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parseAseprite.js","sources":["../../src/anim/parseAseprite.ts"],"sourcesContent":["import type { AnimationClip } from './Animator';\nimport { tagDirection, tagFrames, tagRepeat } from './asepriteTags';\nimport type {\n AsepriteCue,\n AsepriteFrame,\n AsepriteJson,\n AsepriteJsonFrame,\n AsepriteLayer,\n AsepriteSheet,\n AsepriteSlice,\n AsepriteTag,\n ClipMarker,\n} from './asepriteTypes';\n\n// Aseprite's default when a frame has no duration of its own (its \"Frame Duration\" default)\nconst DEFAULT_DURATION_MS = 100;\n\n/**\n * Reads Aseprite's exported sprite-sheet JSON — either frame layout, \"hash\" (frames keyed by file name) or \"array\" —\n * into frames, one `Animator` clip per tag, slices, layers and cel cues. Pure: no image is touched, so it runs under\n * node and in a worker; `addAsepriteTextures` / `loadAseprite` cut the sheet image by the frames it returns.\n *\n * ```ts\n * const sheet = parseAseprite(json);\n * const anim = new Animator(sheet.clips, 'idle'); // tag names are clip names; frames are sheet frame indices\n * ```\n */\nexport function parseAseprite(json: unknown): AsepriteSheet {\n if (!json || typeof json !== 'object') throw new Error('parseAseprite: not an Aseprite JSON object');\n const doc = json as AsepriteJson;\n if (!doc.frames || typeof doc.frames !== 'object') throw new Error('parseAseprite: no \"frames\"');\n const meta = doc.meta ?? {};\n\n const rawFrames: [string, AsepriteJsonFrame][] = Array.isArray(doc.frames)\n ? doc.frames.map((f, i) => [f.filename ?? String(i), f])\n : Object.entries(doc.frames);\n const frames = rawFrames.map(([name, f], index) => toFrame(name, f, index));\n if (frames.length === 0) throw new Error('parseAseprite: the sheet has no frames');\n\n const tags: AsepriteTag[] = (meta.frameTags ?? []).map((t) => {\n if (!(t.from >= 0 && t.to < frames.length && t.from <= t.to)) {\n throw new Error(`parseAseprite: tag \"${t.name}\" spans frames ${t.from}–${t.to} of ${frames.length}`);\n }\n const tag: AsepriteTag = { name: t.name, from: t.from, to: t.to, direction: tagDirection(t.direction), repeat: tagRepeat(t.repeat) };\n if (t.color !== undefined) tag.color = t.color;\n if (t.data !== undefined) tag.data = t.data;\n return tag;\n });\n\n const clips: Record<string, AnimationClip> = {};\n for (const tag of tags) {\n // Aseprite allows two tags of one name; the second must not silently replace the first\n let name = tag.name;\n for (let n = 2; name in clips; n++) name = `${tag.name}#${n}`;\n clips[name] = clipOf(frames, tagFrames(tag.from, tag.to, tag.direction, tag.repeat), tag.repeat === 0);\n }\n\n const all = clipOf(frames, frames.map((f) => f.index), true);\n const slices: AsepriteSlice[] = (meta.slices ?? []).map((s) => {\n const slice: AsepriteSlice = {\n name: s.name,\n keys: (s.keys ?? [])\n .map((k) => ({\n frame: k.frame,\n bounds: { ...k.bounds },\n ...(k.center ? { center: { ...k.center } } : {}),\n ...(k.pivot ? { pivot: { ...k.pivot } } : {}),\n }))\n .sort((a, b) => a.frame - b.frame),\n };\n if (s.color !== undefined) slice.color = s.color;\n if (s.data !== undefined) slice.data = s.data;\n return slice;\n });\n\n const layers: AsepriteLayer[] = [];\n const cues: AsepriteCue[] = [];\n for (const l of meta.layers ?? []) {\n const layer: AsepriteLayer = { name: l.name, opacity: l.opacity ?? 255, blendMode: l.blendMode ?? 'normal' };\n if (l.group !== undefined) layer.group = l.group;\n if (l.color !== undefined) layer.color = l.color;\n if (l.data !== undefined) layer.data = l.data;\n layers.push(layer);\n for (const c of l.cels ?? []) if (c.data) cues.push({ layer: l.name, frame: c.frame, data: c.data });\n }\n\n const size = meta.size ?? { w: 0, h: 0 };\n return { image: meta.image ?? '', width: size.w, height: size.h, frames, tags, clips, all, slices, layers, cues };\n}\n\nfunction toFrame(name: string, f: AsepriteJsonFrame, index: number): AsepriteFrame {\n if (!f || !f.frame) throw new Error(`parseAseprite: frame \"${name}\" has no rect`);\n // Aseprite never rotates frames; a packer that did would need its rect read sideways, which nothing here does\n if (f.rotated) throw new Error(`parseAseprite: frame \"${name}\" is rotated, which is not supported`);\n const { x, y, w, h } = f.frame;\n const src = f.sourceSize ?? { w, h };\n const off = f.trimmed && f.spriteSourceSize ? f.spriteSourceSize : { x: 0, y: 0 };\n return {\n index,\n name,\n rect: { x, y, w, h },\n duration: f.duration !== undefined && f.duration > 0 ? f.duration : DEFAULT_DURATION_MS,\n sourceW: src.w,\n sourceH: src.h,\n offsetX: off.x,\n offsetY: off.y,\n trimmed: f.trimmed === true,\n };\n}\n\nfunction clipOf(frames: readonly AsepriteFrame[], order: readonly number[], loop: boolean): AnimationClip {\n return { frames: order.slice(), durations: order.map((i) => frames[i]!.duration), loop };\n}\n\n/** The index of the frame Aseprite named `name`, or -1. */\nexport function frameNamed(sheet: AsepriteSheet, name: string): number {\n return sheet.frames.findIndex((f) => f.name === name);\n}\n\n/**\n * Markers for every clip from the sheet's cel cues: wherever a clip shows a sheet frame whose cel carries user data,\n * a marker of that data at that position of the clip. A ping-pong shows a frame twice a round trip, so it fires twice,\n * as a footstep should. `layer` keeps only the cues of one layer (a layer kept for marks, say \"events\").\n */\nexport function clipMarkers(sheet: AsepriteSheet, layer?: string): Record<string, ClipMarker[]> {\n const byFrame = new Map<number, string[]>();\n for (const c of sheet.cues) {\n if (layer !== undefined && c.layer !== layer) continue;\n const list = byFrame.get(c.frame) ?? [];\n list.push(c.data);\n byFrame.set(c.frame, list);\n }\n const out: Record<string, ClipMarker[]> = {};\n for (const [name, clip] of Object.entries(sheet.clips)) {\n const markers: ClipMarker[] = [];\n clip.frames.forEach((f, i) => {\n for (const data of byFrame.get(Number(f)) ?? []) markers.push({ frame: i, name: data });\n });\n if (markers.length) out[name] = markers;\n }\n return out;\n}\n"],"names":["DEFAULT_DURATION_MS","parseAseprite","json","doc","meta","frames","f","i","name","index","toFrame","tags","t","tag","tagDirection","tagRepeat","clips","n","clipOf","tagFrames","all","slices","s","slice","k","a","b","layers","cues","l","layer","c","size","x","y","w","h","src","off","order","loop","frameNamed","sheet","clipMarkers","byFrame","list","out","clip","markers","data"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AssetLoader.js","sources":["../../src/assets/AssetLoader.ts"],"sourcesContent":["import { parseTiled, type TileMap, type Tileset } from '../tilemap/tiled';\n\n/** Where loaded images go: the stage's `TextureRegistry` satisfies it. */\nexport interface TextureSink {\n addCanvas(key: string, canvas: HTMLCanvasElement): unknown;\n addSpriteSheet(key: string, canvas: HTMLCanvasElement, size: { frameWidth: number; frameHeight: number }): unknown;\n}\n\nexport interface AssetLoaderOptions {\n /** Prefix for relative URLs (default: none, so relative to the page). */\n baseUrl?: string;\n /** Requests running at once (default 6). */\n concurrency?: number;\n /** For tests and custom transports: defaults to the global `fetch`. */\n fetch?: (url: string) => Promise<{ ok: boolean; status: number; blob(): Promise<Blob>; json(): Promise<unknown>; text(): Promise<string> }>;\n /** Turns image bytes into a canvas; defaults to `createImageBitmap` drawn onto a new canvas. */\n toCanvas?: (blob: Blob) => Promise<HTMLCanvasElement>;\n}\n\nexport interface TilemapAssetOptions {\n /** The texture key a tileset's image is loaded under (default: the tileset's name). */\n textureKey?: (tileset: Tileset) => string;\n}\n\nexport interface LoadProgress {\n done: number;\n total: number;\n /** The asset that just finished. */\n key: string;\n}\n\ntype Job = { key: string; run: () => Promise<void> };\n\n/** `url` resolved against `base` the way a browser would (a bare file name sits beside its map). */\nexport function resolveUrl(url: string, base: string): string {\n if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('/')) return url;\n const dir = base.slice(0, base.lastIndexOf('/') + 1);\n const parts = (dir + url).split('/');\n const out: string[] = [];\n for (const p of parts) {\n if (p === '..' && out.length && out[out.length - 1] !== '..' && out[out.length - 1] !== '') out.pop();\n else if (p !== '.') out.push(p);\n }\n return out.join('/');\n}\n\nasync function defaultToCanvas(blob: Blob): Promise<HTMLCanvasElement> {\n const bitmap = await createImageBitmap(blob);\n const canvas = document.createElement('canvas');\n canvas.width = bitmap.width;\n canvas.height = bitmap.height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('AssetLoader: no 2D context (out of canvas memory?)');\n ctx.drawImage(bitmap, 0, 0);\n bitmap.close();\n return canvas;\n}\n\n/**\n * Loads a scene's assets before it starts: queue them, then `await load()`. Images become textures in the stage's\n * registry (frames of a sprite sheet named 0, 1, 2…); JSON, text and Tiled maps are kept here by key. A failed asset\n * fails the whole load with its URL in the message.\n *\n * ```ts\n * override async onEnter() {\n * const assets = new AssetLoader(this.engine.stage.textures);\n * assets.spritesheet('hero', 'art/hero.png', { frameWidth: 20, frameHeight: 24 }).tilemap('level', 'maps/level.tmj');\n * await assets.load(({ done, total }) => bar.set(done / total));\n * const map = assets.map('level');\n * }\n * ```\n */\nexport class AssetLoader {\n private readonly queue: Job[] = [];\n private readonly data = new Map<string, unknown>();\n private readonly maps = new Map<string, TileMap>();\n private readonly fetcher: NonNullable<AssetLoaderOptions['fetch']>;\n private readonly toCanvas: (blob: Blob) => Promise<HTMLCanvasElement>;\n private total = 0;\n private done = 0;\n\n constructor(\n private readonly textures: TextureSink,\n private readonly options: AssetLoaderOptions = {},\n ) {\n this.fetcher = options.fetch ?? ((url) => fetch(url));\n this.toCanvas = options.toCanvas ?? defaultToCanvas;\n }\n\n image(key: string, url: string): this {\n return this.add(key, async () => {\n this.textures.addCanvas(key, await this.toCanvas(await this.blob(url)));\n });\n }\n\n /** An image cut into equal frames named 0, 1, 2…, left to right, then row by row. */\n spritesheet(key: string, url: string, size: { frameWidth: number; frameHeight: number }): this {\n return this.add(key, async () => {\n this.textures.addSpriteSheet(key, await this.toCanvas(await this.blob(url)), size);\n });\n }\n\n json(key: string, url: string): this {\n return this.add(key, async () => {\n this.data.set(key, await (await this.request(url)).json());\n });\n }\n\n text(key: string, url: string): this {\n return this.add(key, async () => {\n this.data.set(key, await (await this.request(url)).text());\n });\n }\n\n /** A Tiled JSON map, then every tileset image it names (resolved beside the map file). */\n tilemap(key: string, url: string, options: TilemapAssetOptions = {}): this {\n return this.add(key, async () => {\n const map = parseTiled(await (await this.request(url)).json());\n this.maps.set(key, map);\n const textureKey = options.textureKey ?? ((ts: Tileset) => ts.name);\n for (const ts of map.tilesets) this.image(textureKey(ts), resolveUrl(ts.image, url));\n });\n }\n\n /** Runs everything queued; `onProgress` after each asset. Safe to call again after queueing more. */\n async load(onProgress?: (p: LoadProgress) => void): Promise<void> {\n const limit = Math.max(1, this.options.concurrency ?? 6);\n const worker = async (): Promise<void> => {\n for (let job = this.queue.shift(); job; job = this.queue.shift()) {\n await job.run();\n this.done++;\n onProgress?.({ done: this.done, total: this.total, key: job.key });\n }\n };\n // a finished tilemap queues its tilesets: keep draining until nothing is left\n while (this.queue.length) await Promise.all(Array.from({ length: limit }, worker));\n }\n\n /** A loaded JSON value. */\n getJson<T = unknown>(key: string): T {\n if (!this.data.has(key)) throw new Error(`AssetLoader: no JSON or text loaded as \"${key}\"`);\n return this.data.get(key) as T;\n }\n\n /** A loaded text file. */\n getText(key: string): string {\n return this.getJson<string>(key);\n }\n\n /** A loaded Tiled map. */\n map(key: string): TileMap {\n const m = this.maps.get(key);\n if (!m) throw new Error(`AssetLoader: no tilemap loaded as \"${key}\"`);\n return m;\n }\n\n private add(key: string, run: () => Promise<void>): this {\n this.queue.push({ key, run });\n this.total++;\n return this;\n }\n\n private async request(url: string) {\n const full = resolveUrl(url, this.options.baseUrl ?? '');\n let res;\n try {\n res = await this.fetcher(full);\n } catch (err) {\n throw new Error(`AssetLoader: could not fetch ${full}: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (!res.ok) throw new Error(`AssetLoader: ${full} answered ${res.status}`);\n return res;\n }\n\n private async blob(url: string): Promise<Blob> {\n return (await this.request(url)).blob();\n }\n}\n"],"names":["resolveUrl","url","base","parts","out","p","defaultToCanvas","blob","bitmap","canvas","ctx","AssetLoader","textures","options","key","size","map","parseTiled","textureKey","ts","onProgress","limit","worker","job","m","run","full","res","err"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loadAseprite.js","sources":["../../src/assets/loadAseprite.ts"],"sourcesContent":["import { Rectangle, Texture } from 'pixi.js';\nimport { parseAseprite } from '../anim/parseAseprite';\nimport type { AsepriteSheet } from '../anim/asepriteTypes';\nimport type { CanvasTexture } from '../core/TextureRegistry';\nimport { resolveUrl, type AssetLoaderOptions } from './AssetLoader';\n\n/** Where an Aseprite sheet's texture goes: the stage's `TextureRegistry` satisfies it. */\nexport interface AsepriteTextureSink {\n addCanvas(key: string, canvas: HTMLCanvasElement): CanvasTexture;\n}\n\n/** Fetch and decode hooks, as `AssetLoader` takes them (tests, custom transports, a base URL). */\nexport type AsepriteLoadOptions = Pick<AssetLoaderOptions, 'baseUrl' | 'fetch' | 'toCanvas'>;\n\n/**\n * Registers `canvas` (the sheet image) under `key` with one frame per sheet frame, named by its index (\"0\", \"1\"…), so\n * the clips `parseAseprite` returns play on an `Image` of that key as they are. A trimmed frame keeps its untrimmed\n * size and offset (Pixi's `orig` and `trim`), so a sprite anchored at its feet does not jump as the trim changes.\n */\nexport function addAsepriteTextures(textures: AsepriteTextureSink, key: string, canvas: HTMLCanvasElement, sheet: AsepriteSheet): CanvasTexture {\n const tex = textures.addCanvas(key, canvas);\n for (const f of sheet.frames) {\n const { x, y, w, h } = f.rect;\n if (!f.trimmed) {\n tex.add(String(f.index), 0, x, y, w, h);\n continue;\n }\n tex.frames.get(String(f.index))?.destroy();\n tex.frames.set(\n String(f.index),\n new Texture({\n source: tex.source,\n frame: new Rectangle(x, y, w, h),\n orig: new Rectangle(0, 0, f.sourceW, f.sourceH),\n trim: new Rectangle(f.offsetX, f.offsetY, w, h),\n }),\n );\n }\n return tex;\n}\n\n/**\n * Loads an Aseprite export — the JSON, then the sheet image it names (resolved beside the JSON) — registers its\n * frames under `key` and returns the parsed sheet (clips, slices, layers, cues).\n *\n * ```ts\n * const [hero] = await Promise.all([loadAseprite(stage.textures, 'hero', 'art/hero.json'), assets.load()]);\n * const sprite = stage.add.image(0, 0, 'hero', 0);\n * const anim = new Animator(hero.clips, 'idle');\n * ```\n */\nexport async function loadAseprite(textures: AsepriteTextureSink, key: string, jsonUrl: string, options: AsepriteLoadOptions = {}): Promise<AsepriteSheet> {\n const fetcher = options.fetch ?? ((url: string) => fetch(url));\n const get = async (url: string) => {\n let res;\n try {\n res = await fetcher(url);\n } catch (err) {\n throw new Error(`loadAseprite: could not fetch ${url}: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (!res.ok) throw new Error(`loadAseprite: ${url} answered ${res.status}`);\n return res;\n };\n const jsonFull = resolveUrl(jsonUrl, options.baseUrl ?? '');\n const sheet = parseAseprite(await (await get(jsonFull)).json());\n if (!sheet.image) throw new Error(`loadAseprite: ${jsonFull} names no sheet image (meta.image)`);\n const blob = await (await get(resolveUrl(sheet.image, jsonFull))).blob();\n const canvas = await (options.toCanvas ?? blobToCanvas)(blob);\n addAsepriteTextures(textures, key, canvas, sheet);\n return sheet;\n}\n\nasync function blobToCanvas(blob: Blob): Promise<HTMLCanvasElement> {\n const bitmap = await createImageBitmap(blob);\n const canvas = document.createElement('canvas');\n canvas.width = bitmap.width;\n canvas.height = bitmap.height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('loadAseprite: no 2D context (out of canvas memory?)');\n ctx.drawImage(bitmap, 0, 0);\n bitmap.close();\n return canvas;\n}\n"],"names":["addAsepriteTextures","textures","key","canvas","sheet","tex","f","x","y","w","h","Texture","Rectangle","loadAseprite","jsonUrl","options","fetcher","url","get","res","err","jsonFull","resolveUrl","parseAseprite","blob","blobToCanvas","bitmap","ctx"],"mappings":";;;AAmBO,SAASA,EAAoBC,GAA+BC,GAAaC,GAA2BC,GAAqC;AAC9I,QAAMC,IAAMJ,EAAS,UAAUC,GAAKC,CAAM;AAC1C,aAAWG,KAAKF,EAAM,QAAQ;AAC5B,UAAM,EAAE,GAAAG,GAAG,GAAAC,GAAG,GAAAC,GAAG,GAAAC,EAAA,IAAMJ,EAAE;AACzB,QAAI,CAACA,EAAE,SAAS;AACd,MAAAD,EAAI,IAAI,OAAOC,EAAE,KAAK,GAAG,GAAGC,GAAGC,GAAGC,GAAGC,CAAC;AACtC;AAAA,IACF;AACA,IAAAL,EAAI,OAAO,IAAI,OAAOC,EAAE,KAAK,CAAC,GAAG,QAAA,GACjCD,EAAI,OAAO;AAAA,MACT,OAAOC,EAAE,KAAK;AAAA,MACd,IAAIK,EAAQ;AAAA,QACV,QAAQN,EAAI;AAAA,QACZ,OAAO,IAAIO,EAAUL,GAAGC,GAAGC,GAAGC,CAAC;AAAA,QAC/B,MAAM,IAAIE,EAAU,GAAG,GAAGN,EAAE,SAASA,EAAE,OAAO;AAAA,QAC9C,MAAM,IAAIM,EAAUN,EAAE,SAASA,EAAE,SAASG,GAAGC,CAAC;AAAA,MAAA,CAC/C;AAAA,IAAA;AAAA,EAEL;AACA,SAAOL;AACT;AAYA,eAAsBQ,EAAaZ,GAA+BC,GAAaY,GAAiBC,IAA+B,CAAA,GAA4B;AACzJ,QAAMC,IAAUD,EAAQ,UAAU,CAACE,MAAgB,MAAMA,CAAG,IACtDC,IAAM,OAAOD,MAAgB;AACjC,QAAIE;AACJ,QAAI;AACF,MAAAA,IAAM,MAAMH,EAAQC,CAAG;AAAA,IACzB,SAASG,GAAK;AACZ,YAAM,IAAI,MAAM,iCAAiCH,CAAG,KAAKG,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC,EAAE;AAAA,IAC7G;AACA,QAAI,CAACD,EAAI,GAAI,OAAM,IAAI,MAAM,iBAAiBF,CAAG,aAAaE,EAAI,MAAM,EAAE;AAC1E,WAAOA;AAAA,EACT,GACME,IAAWC,EAAWR,GAASC,EAAQ,WAAW,EAAE,GACpDX,IAAQmB,EAAc,OAAO,MAAML,EAAIG,CAAQ,GAAG,MAAM;AAC9D,MAAI,CAACjB,EAAM,MAAO,OAAM,IAAI,MAAM,iBAAiBiB,CAAQ,oCAAoC;AAC/F,QAAMG,IAAO,OAAO,MAAMN,EAAII,EAAWlB,EAAM,OAAOiB,CAAQ,CAAC,GAAG,KAAA,GAC5DlB,IAAS,OAAOY,EAAQ,YAAYU,GAAcD,CAAI;AAC5D,SAAAxB,EAAoBC,GAAUC,GAAKC,GAAQC,CAAK,GACzCA;AACT;AAEA,eAAeqB,EAAaD,GAAwC;AAClE,QAAME,IAAS,MAAM,kBAAkBF,CAAI,GACrCrB,IAAS,SAAS,cAAc,QAAQ;AAC9C,EAAAA,EAAO,QAAQuB,EAAO,OACtBvB,EAAO,SAASuB,EAAO;AACvB,QAAMC,IAAMxB,EAAO,WAAW,IAAI;AAClC,MAAI,CAACwB,EAAK,OAAM,IAAI,MAAM,qDAAqD;AAC/E,SAAAA,EAAI,UAAUD,GAAQ,GAAG,CAAC,GAC1BA,EAAO,MAAA,GACAvB;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"takeCleanerWorker-DTcI_Jqr.js","sources":["../src/audio/cleanTake.ts","../src/audio/takeCleanerWorker.ts"],"sourcesContent":["/*\n * Cleans one recorded take (`SampleBank`): folded to mono, the silence an encoder or the model leaves at the start cut\n * (a swing heard 30 ms late reads as lag) and at the end trimmed, and brought to one loudness (`LOUDNESS`, RMS of what\n * is left, never past `PEAK`), so takes from different prompts sit at one level. Pure: run in `takeCleanerWorker.ts`,\n * because on the main thread the first blows of a fight froze the game 70–170 ms a take on a Mac and several times\n * that on a phone (profiled).\n */\n\n/** Below this share of the take's own peak a sample counts as silence when trimming (-40 dB). */\nconst FLOOR = 0.01;\n/** A take whose peak is under this is treated as empty (about -46 dBFS): the model returned near-silence. */\nconst EMPTY = 0.005;\n/** Target RMS of a cleaned take (-16 dBFS) and the peak it may not pass. */\nconst LOUDNESS = 0.16;\nconst PEAK = 0.95;\n/** Kept before the first loud sample, so the attack is not clipped into a click (s). */\nconst LEAD = 0.002;\n/** A short fade over the cut end, so a trimmed tail does not click (s). */\nconst TAIL_FADE = 0.01;\n\n/** The cleaned mono samples, or null for a take that is silence. */\nexport function cleanSamples(channels: Float32Array[], sampleRate: number): Float32Array | null {\n const n = channels[0]?.length ?? 0;\n const k0 = 1 / Math.max(1, channels.length);\n const mono = new Float32Array(n);\n for (const ch of channels) for (let i = 0; i < n; i++) mono[i]! += ch[i]! * k0;\n let top = 0;\n for (let i = 0; i < n; i++) {\n const a = Math.abs(mono[i]!);\n if (a > top) top = a;\n }\n if (top < EMPTY) return null;\n const floor = top * FLOOR;\n let start = 0;\n while (start < n && Math.abs(mono[start]!) < floor) start++;\n let end = n;\n while (end > start && Math.abs(mono[end - 1]!) < floor) end--;\n if (end <= start) return null;\n start = Math.max(0, start - Math.round(LEAD * sampleRate));\n const data = mono.slice(start, end);\n let power = 0;\n for (let i = 0; i < data.length; i++) power += data[i]! * data[i]!;\n const rms = Math.sqrt(power / data.length);\n const k = Math.min(LOUDNESS / Math.max(rms, 1e-6), PEAK / top);\n const fade = Math.min(data.length, Math.round(TAIL_FADE * sampleRate));\n for (let i = 0; i < data.length; i++) {\n const left = data.length - i;\n data[i]! *= k * (left < fade ? left / fade : 1);\n }\n return data;\n}\n","import { cleanSamples } from './cleanTake';\n\n/* Cleans recorded takes off the main thread (`SampleBank`); one message per take, answered with its id. */\nself.onmessage = (e: MessageEvent<{ id: number; channels: Float32Array[]; sampleRate: number }>) => {\n const { id, channels, sampleRate } = e.data;\n const data = cleanSamples(channels, sampleRate);\n self.postMessage({ id, data }, data ? { transfer: [data.buffer as ArrayBuffer] } : undefined);\n};\n"],"names":["cleanSamples","channels","sampleRate","n","k0","mono","ch","i","top","a","floor","start","end","data","power","rms","k","fade","left","e","id"],"mappings":"AAqBO,SAASA,EAAaC,GAA0BC,GAAyC;AAC9F,QAAMC,IAAIF,EAAS,CAAC,GAAG,UAAU,GAC3BG,IAAK,IAAI,KAAK,IAAI,GAAGH,EAAS,MAAM,GACpCI,IAAO,IAAI,aAAaF,CAAC;AAC/B,aAAWG,KAAML,EAAU,UAASM,IAAI,GAAGA,IAAIJ,GAAGI,IAAK,CAAAF,EAAKE,CAAC,KAAMD,EAAGC,CAAC,IAAKH;AAC5E,MAAII,IAAM;AACV,WAASD,IAAI,GAAGA,IAAIJ,GAAGI,KAAK;AAC1B,UAAME,IAAI,KAAK,IAAIJ,EAAKE,CAAC,CAAE;AAC3B,IAAIE,IAAID,MAAKA,IAAMC;AAAA,EACrB;AACA,MAAID,IAAM,KAAO,QAAO;AACxB,QAAME,IAAQF,IAAM;AACpB,MAAIG,IAAQ;AACZ,SAAOA,IAAQR,KAAK,KAAK,IAAIE,EAAKM,CAAK,CAAE,IAAID,IAAO,CAAAC;AACpD,MAAIC,IAAMT;AACV,SAAOS,IAAMD,KAAS,KAAK,IAAIN,EAAKO,IAAM,CAAC,CAAE,IAAIF,IAAO,CAAAE;AACxD,MAAIA,KAAOD,EAAO,QAAO;AACzB,EAAAA,IAAQ,KAAK,IAAI,GAAGA,IAAQ,KAAK,MAAM,OAAOT,CAAU,CAAC;AACzD,QAAMW,IAAOR,EAAK,MAAMM,GAAOC,CAAG;AAClC,MAAIE,IAAQ;AACZ,WAASP,IAAI,GAAGA,IAAIM,EAAK,QAAQN,IAAK,CAAAO,KAASD,EAAKN,CAAC,IAAKM,EAAKN,CAAC;AAChE,QAAMQ,IAAM,KAAK,KAAKD,IAAQD,EAAK,MAAM,GACnCG,IAAI,KAAK,IAAI,OAAW,KAAK,IAAID,GAAK,IAAI,GAAG,OAAOP,CAAG,GACvDS,IAAO,KAAK,IAAIJ,EAAK,QAAQ,KAAK,MAAM,OAAYX,CAAU,CAAC;AACrE,WAASK,IAAI,GAAGA,IAAIM,EAAK,QAAQN,KAAK;AACpC,UAAMW,IAAOL,EAAK,SAASN;AAC3B,IAAAM,EAAKN,CAAC,KAAMS,KAAKE,IAAOD,IAAOC,IAAOD,IAAO;AAAA,EAC/C;AACA,SAAOJ;AACT;AC/CA,KAAK,YAAY,CAACM,MAAkF;AAClG,QAAM,EAAE,IAAAC,GAAI,UAAAnB,GAAU,YAAAC,EAAA,IAAeiB,EAAE,MACjCN,IAAOb,EAAaC,GAAUC,CAAU;AAC9C,OAAK,YAAY,EAAE,IAAAkB,GAAI,MAAAP,EAAA,GAAQA,IAAO,EAAE,UAAU,CAACA,EAAK,MAAqB,EAAA,IAAM,MAAS;AAC9F;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Footsteps.js","sources":["../../src/audio/Footsteps.ts"],"sourcesContent":["import { sound } from './SoundEngine';\n\n/** What a surface sounds like underfoot: a step, and a beat of hooves (a mount) if it differs. */\nexport interface SurfaceSounds {\n step: string;\n hoof?: string;\n}\n\nexport interface FootstepOptions {\n /** Per surface name (`'grass'`, `'stone'`, `'wood'`…), the sounds a foot makes on it. */\n sounds: Record<string, SurfaceSounds>;\n /** The surface of anywhere `surfaceAt` does not name. */\n fallback: string;\n /** Weight of a step against other sounds (default 0.5). */\n gain?: number;\n /** Beyond this (world px) nobody else's steps are heard (default 200). */\n range?: number;\n}\n\n/**\n * Footsteps by the ground under the feet. The game says what a point is made of (`surfaceAt`: a tile's surface, a\n * Tiled property, a floor collider), and calls `step` on each beat of a walk or a gait; the sound is the surface's,\n * through `sound` at the feet, quieter and muffled with distance like every other sound, and dropped first when too\n * many sounds play at once.\n */\nexport class Footsteps {\n private surfaceAt: (x: number, y: number) => string | undefined = () => undefined;\n\n constructor(private readonly options: FootstepOptions) {}\n\n /** Where the surface of a world point comes from; `undefined` is the fallback surface. */\n setSurfaceAt(surfaceAt: (x: number, y: number) => string | undefined): void {\n this.surfaceAt = surfaceAt;\n }\n\n /** The surface at a world point. */\n surface(x: number, y: number): string {\n return this.surfaceAt(x, y) ?? this.options.fallback;\n }\n\n /** One step (or a beat of hooves when `mounted`) at the feet, world x/y; `local` is the player's own. */\n step(x: number, y: number, local: boolean, mounted = false): void {\n const o = this.options;\n const s = o.sounds[this.surface(x, y)] ?? o.sounds[o.fallback];\n if (!s) return;\n sound.play(mounted ? (s.hoof ?? s.step) : s.step, { x, y, local, gain: o.gain ?? 0.5, range: o.range ?? 200, minor: true });\n }\n}\n"],"names":["Footsteps","options","surfaceAt","x","y","local","mounted","o","s","sound"],"mappings":";AAyBO,MAAMA,EAAU;AAAA,EAGrB,YAA6BC,GAA0B;AAA1B,SAAA,UAAAA;AAAA,EAA2B;AAAA,EAA3B;AAAA,EAFrB,YAA0D,MAAA;AAAA;AAAA;AAAA,EAKlE,aAAaC,GAA+D;AAC1E,SAAK,YAAYA;AAAA,EACnB;AAAA;AAAA,EAGA,QAAQC,GAAWC,GAAmB;AACpC,WAAO,KAAK,UAAUD,GAAGC,CAAC,KAAK,KAAK,QAAQ;AAAA,EAC9C;AAAA;AAAA,EAGA,KAAKD,GAAWC,GAAWC,GAAgBC,IAAU,IAAa;AAChE,UAAMC,IAAI,KAAK,SACTC,IAAID,EAAE,OAAO,KAAK,QAAQJ,GAAGC,CAAC,CAAC,KAAKG,EAAE,OAAOA,EAAE,QAAQ;AAC7D,IAAKC,KACLC,EAAM,KAAKH,IAAWE,EAAE,QAAQA,EAAE,OAAQA,EAAE,MAAM,EAAE,GAAAL,GAAG,GAAAC,GAAG,OAAAC,GAAO,MAAME,EAAE,QAAQ,KAAK,OAAOA,EAAE,SAAS,KAAK,OAAO,GAAA,CAAM;AAAA,EAC5H;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MusicPlayer.js","sources":["../../src/audio/MusicPlayer.ts"],"sourcesContent":["import { engineSettings } from '../settings';\nimport { isTouchUi } from '../platform/touch';\nimport { ElementVoice } from './music/ElementVoice';\nimport { GraphVoice } from './music/GraphVoice';\nimport { MusicChannel, type PlayOptions } from './music/MusicChannel';\nimport { elementVolumeLocked, type MusicVoice } from './music/musicVoice';\n\nexport type { PlayOptions } from './music/MusicChannel';\n\n/**\n * How music reaches the speaker: `'element'` on the `<audio>` element's own volume (nothing in between; a file from\n * another origin plays as it is), `'graph'` through a Web Audio gain (the only way where that volume is read-only),\n * `'auto'` (default) the graph only where it has to be (iOS).\n */\nexport type MusicRoute = 'auto' | 'element' | 'graph';\n\nexport interface MusicOptions {\n route?: MusicRoute;\n /** Stop the music while the page is hidden (an app sent to the background). Default: on touch devices. */\n pauseInBackground?: boolean;\n}\n\n/**\n * Background music in layers: each named channel (`'music'` by default, `'ambience'`, any other) plays one looping\n * track at a time, crossfaded when it changes, all at `engineSettings.musicVolume`. Tracks stream through `<audio>`\n * elements (no decoding a whole song into memory).\n *\n * Browsers start sound only after a user gesture: a track played before one starts on the first tap or key. On iOS the\n * graph's context is woken inside that gesture too, and again after the app comes back from the background.\n *\n * ```ts\n * music.play('music/forest.ogg'); // the 'music' channel\n * music.channel('ambience').play('amb/wind.ogg', { volume: 0.5 }); // a second layer under it\n * music.play('music/boss.ogg', { fadeMs: 600 }); // crossfades the first only\n * music.stop(2000);\n * ```\n */\nexport class MusicPlayer {\n private readonly channels = new Map<string, MusicChannel>();\n private route: MusicRoute = 'auto';\n private graph: AudioContext | null = null;\n private graphLocked: boolean | null = null;\n private pauseInBackground: boolean | null = null;\n private installed = false;\n\n /** Picks the route and background behaviour; call before the first track for the route to apply to every track. */\n configure(options: MusicOptions): this {\n if (options.route) this.route = options.route;\n if (options.pauseInBackground !== undefined) this.pauseInBackground = options.pauseInBackground;\n return this;\n }\n\n /** The channel named `name`, made on first use. */\n channel(name = 'music'): MusicChannel {\n let ch = this.channels.get(name);\n if (!ch) {\n this.install();\n ch = new MusicChannel(() => engineSettings.getState().musicVolume, (el) => this.voiceFor(el));\n this.channels.set(name, ch);\n }\n return ch;\n }\n\n /** The url the `'music'` channel plays, if any. */\n get current(): string | null {\n return this.channels.get('music')?.current ?? null;\n }\n\n /** Plays `url` on the `'music'` channel. */\n play(url: string, options?: PlayOptions): void {\n this.channel().play(url, options);\n }\n\n /** Fades every channel out. */\n stop(fadeMs = 1200): void {\n for (const ch of this.channels.values()) ch.stop(fadeMs);\n }\n\n pause(): void {\n for (const ch of this.channels.values()) ch.pause();\n }\n\n resume(): void {\n this.wake();\n }\n\n destroy(): void {\n for (const ch of this.channels.values()) ch.dispose();\n this.channels.clear();\n void this.graph?.close();\n this.graph = null;\n }\n\n private voiceFor(el: HTMLAudioElement): MusicVoice {\n const graph = this.throughGraph() ? this.context() : null;\n return graph ? new GraphVoice(el, graph) : new ElementVoice(el);\n }\n\n private throughGraph(): boolean {\n if (this.route !== 'auto') return this.route === 'graph';\n return (this.graphLocked ??= elementVolumeLocked());\n }\n\n private context(): AudioContext | null {\n if (this.graph) return this.graph;\n const Ctor = (globalThis as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext }).AudioContext ?? (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctor) return null;\n this.graph = new Ctor();\n return this.graph;\n }\n\n /** Retries every refused start and follows the volume; the graph's context resumes inside the gesture that called it. */\n private wake(): void {\n if (this.graph && this.graph.state !== 'running') void this.graph.resume().catch(() => {});\n if (typeof document !== 'undefined' && document.hidden) return;\n for (const ch of this.channels.values()) ch.refresh();\n }\n\n private install(): void {\n if (this.installed || typeof window === 'undefined') return;\n this.installed = true;\n const wake = () => this.wake();\n // `touchend` is the gesture iOS accepts for starting media; the others are the desktop's\n for (const type of ['pointerdown', 'touchend', 'keydown'] as const) window.addEventListener(type, wake, { capture: true });\n engineSettings.subscribe(wake);\n document.addEventListener('visibilitychange', () => {\n if (!(this.pauseInBackground ?? isTouchUi())) return;\n if (document.hidden) this.pause();\n else wake();\n });\n }\n}\n\n/** A fade's value at `now`: linear from `from` to `to` over `ms`. */\nexport function fadeValue(f: { from: number; to: number; start: number; ms: number }, now: number): number {\n if (f.ms <= 0 || now >= f.start + f.ms) return f.to;\n if (now <= f.start) return f.from;\n return f.from + ((f.to - f.from) * (now - f.start)) / f.ms;\n}\n\n/** The shared music player. */\nexport const music = new MusicPlayer();\n"],"names":["MusicPlayer","options","name","ch","MusicChannel","engineSettings","el","url","fadeMs","graph","GraphVoice","ElementVoice","elementVolumeLocked","Ctor","wake","type","isTouchUi","music"],"mappings":";;;;;;AAqCO,MAAMA,EAAY;AAAA,EACN,+BAAe,IAAA;AAAA,EACxB,QAAoB;AAAA,EACpB,QAA6B;AAAA,EAC7B,cAA8B;AAAA,EAC9B,oBAAoC;AAAA,EACpC,YAAY;AAAA;AAAA,EAGpB,UAAUC,GAA6B;AACrC,WAAIA,EAAQ,UAAO,KAAK,QAAQA,EAAQ,QACpCA,EAAQ,sBAAsB,WAAW,KAAK,oBAAoBA,EAAQ,oBACvE;AAAA,EACT;AAAA;AAAA,EAGA,QAAQC,IAAO,SAAuB;AACpC,QAAIC,IAAK,KAAK,SAAS,IAAID,CAAI;AAC/B,WAAKC,MACH,KAAK,QAAA,GACLA,IAAK,IAAIC,EAAa,MAAMC,EAAe,SAAA,EAAW,aAAa,CAACC,MAAO,KAAK,SAASA,CAAE,CAAC,GAC5F,KAAK,SAAS,IAAIJ,GAAMC,CAAE,IAErBA;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,UAAyB;AAC3B,WAAO,KAAK,SAAS,IAAI,OAAO,GAAG,WAAW;AAAA,EAChD;AAAA;AAAA,EAGA,KAAKI,GAAaN,GAA6B;AAC7C,SAAK,QAAA,EAAU,KAAKM,GAAKN,CAAO;AAAA,EAClC;AAAA;AAAA,EAGA,KAAKO,IAAS,MAAY;AACxB,eAAWL,KAAM,KAAK,SAAS,SAAU,CAAAA,EAAG,KAAKK,CAAM;AAAA,EACzD;AAAA,EAEA,QAAc;AACZ,eAAWL,KAAM,KAAK,SAAS,OAAA,KAAa,MAAA;AAAA,EAC9C;AAAA,EAEA,SAAe;AACb,SAAK,KAAA;AAAA,EACP;AAAA,EAEA,UAAgB;AACd,eAAWA,KAAM,KAAK,SAAS,OAAA,KAAa,QAAA;AAC5C,SAAK,SAAS,MAAA,GACT,KAAK,OAAO,MAAA,GACjB,KAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,SAASG,GAAkC;AACjD,UAAMG,IAAQ,KAAK,aAAA,IAAiB,KAAK,YAAY;AACrD,WAAOA,IAAQ,IAAIC,EAAWJ,GAAIG,CAAK,IAAI,IAAIE,EAAaL,CAAE;AAAA,EAChE;AAAA,EAEQ,eAAwB;AAC9B,WAAI,KAAK,UAAU,SAAe,KAAK,UAAU,UACzC,KAAK,gBAAgBM,EAAA;AAAA,EAC/B;AAAA,EAEQ,UAA+B;AACrC,QAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,UAAMC,IAAQ,WAAgG,gBAAiB,WAA4D;AAC3L,WAAKA,KACL,KAAK,QAAQ,IAAIA,EAAA,GACV,KAAK,SAFM;AAAA,EAGpB;AAAA;AAAA,EAGQ,OAAa;AAEnB,QADI,KAAK,SAAS,KAAK,MAAM,UAAU,aAAgB,KAAK,MAAM,OAAA,EAAS,MAAM,MAAM;AAAA,IAAC,CAAC,GACrF,SAAO,WAAa,OAAe,SAAS;AAChD,iBAAWV,KAAM,KAAK,SAAS,OAAA,KAAa,QAAA;AAAA,EAC9C;AAAA,EAEQ,UAAgB;AACtB,QAAI,KAAK,aAAa,OAAO,SAAW,IAAa;AACrD,SAAK,YAAY;AACjB,UAAMW,IAAO,MAAM,KAAK,KAAA;AAExB,eAAWC,KAAQ,CAAC,eAAe,YAAY,SAAS,EAAY,QAAO,iBAAiBA,GAAMD,GAAM,EAAE,SAAS,IAAM;AACzH,IAAAT,EAAe,UAAUS,CAAI,GAC7B,SAAS,iBAAiB,oBAAoB,MAAM;AAClD,OAAM,KAAK,qBAAqBE,EAAA,OAC5B,SAAS,SAAQ,KAAK,MAAA,IACrBF,EAAA;AAAA,IACP,CAAC;AAAA,EACH;AACF;AAUO,MAAMG,IAAQ,IAAIjB,EAAA;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SoundEngine.js","sources":["../../src/audio/SoundEngine.ts"],"sourcesContent":["import { engineSettings } from '../settings';\nimport { hasTakes, SampleBank } from './sampleBank';\nimport { SOUNDS } from './soundTable';\nimport { renderRecipe, SYNTH_RATE } from './synth';\n\n/**\n * Plays sound effects through Web Audio, on a gain of their own (`engineSettings.sfxVolume`, beside the music's).\n * A sound is one of its recorded takes (`registerTakes`, `SampleBank`) when it has\n * any, else its `sounds.json` recipe rendered once into a buffer the first time it is asked for. A sound in the\n * world fades with its distance from the local player, pans with where it happened and loses its highs as it goes\n * (`MUFFLE`); the local player's own swings and blows play at full weight, everyone else's at `OTHERS`.\n *\n * Kept lean on purpose (an earlier, much larger set of effects was taken out for being too busy): one sound per\n * kind per `MIN_GAP_MS`, never more than `MAX_VOICES` at once, and nothing but combat.\n */\n\n/** Beyond this (px) a sound in the world is not heard at all. */\nconst HEARING = 480;\nconst MAX_VOICES = 10;\n/** Minor sounds (footsteps) start only while fewer than this are playing, so they never take a blow's voice. */\nconst MINOR_VOICES = 6;\n/** The same kind twice inside this (ms) is one sound: a cleave clipping six bodies lands once. */\nconst MIN_GAP_MS = 60;\n/** Weight of what other players, pets and monsters do, against the local player's own. */\nconst OTHERS = 0.45;\nconst DEFAULT_JITTER = 0.05;\n/** A sound cut short by `maxMs` fades out over this, so the cut does not click. */\nconst CUT_FADE_MS = 80;\n/** Low-pass cutoff (Hz) right beside the listener and at the edge of hearing: far blows sound dull, not just quiet. */\nconst MUFFLE: [number, number] = [18000, 1800];\n/**\n * The tone of the whole effects bus: the recorded takes came back bright (hiss, glassy ice, blade scrapes) and read\n * as shrill next to the music, so the highs are shelved down and the very top cut, for every sound at once.\n */\nconst TAME_SHELF = { freq: 3500, gainDb: -7 };\nconst TAME_TOP = 11000;\n\nexport interface SoundOptions {\n /** Where it happened in the world; absent for what happens to or is done by the local player. */\n x?: number;\n y?: number;\n /** Done by (or to) the local player: full weight. */\n local?: boolean;\n /** Extra weight (default 1). */\n gain?: number;\n /** How far away it can be heard (px), when nearer than `HEARING`. */\n range?: number;\n /** Skipped when the mix is busy (footsteps). */\n minor?: boolean;\n /** Cut off after this long (ms), faded over `CUT_FADE_MS`: a cast's sound ends with the cast. */\n maxMs?: number;\n}\n\nexport class SoundEngine {\n private ctx: AudioContext | null = null;\n private master: GainNode | null = null;\n private readonly buffers = new Map<string, AudioBuffer | null>();\n private readonly samples = new SampleBank();\n private readonly lastAt = new Map<string, number>();\n private voices = 0;\n /** Where the local player stands, set by the world scene; null off the world screens. */\n listener: () => { x: number; y: number } | null = () => null;\n private onRunning: (running: boolean) => void = () => {};\n\n constructor() {\n if (typeof window === 'undefined') return;\n // iOS lets a page start sound only inside a gesture, counts `touchend` (not `pointerdown`) as one, and leaves\n // the context 'interrupted' after the app comes back from the background: every touch retries until it runs\n const unlock = () => {\n const ctx = this.context();\n if (!ctx || ctx.state === 'running') return;\n void ctx.resume();\n // one silent sample started inside the gesture is what WebKit needs to open the output\n const blank = ctx.createBufferSource();\n blank.buffer = ctx.createBuffer(1, 1, 22050);\n blank.connect(ctx.destination);\n blank.start();\n };\n // listening only while the context is not running: once it runs a touch has nothing to do, and the listeners\n // come back the moment it stops (the interrupted iOS case)\n const types = ['pointerdown', 'pointerup', 'touchend', 'keydown'] as const;\n const listen = (on: boolean) => {\n for (const type of types) {\n if (on) window.addEventListener(type, unlock, { capture: true });\n else window.removeEventListener(type, unlock, { capture: true });\n }\n };\n this.onRunning = (running) => listen(!running);\n listen(true);\n // game sound, not media: it follows the ring/silent switch and mixes with the player's own music (iOS 17+)\n const session = (navigator as Navigator & { audioSession?: { type: string } }).audioSession;\n if (session) session.type = 'ambient';\n engineSettings.subscribe((s) => {\n if (this.ctx && this.master) this.master.gain.setTargetAtTime(this.level(s.sfxVolume), this.ctx.currentTime, 0.02);\n });\n }\n\n play(name: string | undefined, opts: SoundOptions = {}): void {\n if (!name || (!SOUNDS.recipes[name] && !hasTakes(name))) return;\n if (engineSettings.getState().sfxVolume <= 0) return;\n const ctx = this.ctx;\n if (!ctx || ctx.state !== 'running' || this.voices >= (opts.minor ? MINOR_VOICES : MAX_VOICES)) return;\n let gain = (opts.gain ?? 1) * (opts.local ? 1 : OTHERS);\n let pan = 0;\n let far = 0;\n if (opts.x !== undefined && opts.y !== undefined) {\n const me = this.listener();\n if (me) {\n const dx = opts.x - me.x;\n const d = Math.hypot(dx, opts.y - me.y);\n const hearing = Math.min(HEARING, opts.range ?? HEARING);\n if (d >= hearing) return;\n gain *= Math.pow(1 - d / hearing, 1.5);\n far = d / HEARING;\n pan = Math.max(-0.7, Math.min(0.7, dx / 320));\n }\n }\n if (gain < 0.03) return;\n const now = performance.now();\n if (now - (this.lastAt.get(name) ?? -1e9) < MIN_GAP_MS) return;\n const take = this.samples.take(ctx, name);\n const buffer = take ?? this.buffer(ctx, name);\n if (!buffer) return;\n if (take) gain *= SOUNDS.recipes[name]?.fileGain ?? 1;\n this.lastAt.set(name, now);\n const src = ctx.createBufferSource();\n src.buffer = buffer;\n const jitter = SOUNDS.recipes[name]?.jitter ?? DEFAULT_JITTER;\n src.playbackRate.value = 1 + (Math.random() * 2 - 1) * jitter;\n const g = ctx.createGain();\n g.gain.value = Math.min(1, gain);\n const p = ctx.createStereoPanner();\n p.pan.value = pan;\n const lp = ctx.createBiquadFilter();\n lp.type = 'lowpass';\n lp.frequency.value = MUFFLE[0] * Math.pow(MUFFLE[1] / MUFFLE[0], far);\n src.connect(lp).connect(g).connect(p).connect(this.master!);\n this.voices++;\n src.onended = () => {\n this.voices--;\n src.disconnect();\n lp.disconnect();\n g.disconnect();\n p.disconnect();\n };\n src.start();\n const lasts = (buffer.duration / src.playbackRate.value) * 1000;\n if (opts.maxMs !== undefined && lasts > opts.maxMs) {\n const end = ctx.currentTime + Math.max(CUT_FADE_MS, opts.maxMs) / 1000;\n g.gain.setValueAtTime(g.gain.value, end - CUT_FADE_MS / 1000);\n g.gain.linearRampToValueAtTime(0, end);\n src.stop(end);\n }\n }\n\n private level(volume: number): number {\n return Math.max(0, Math.min(1, volume)) * SOUNDS.master;\n }\n\n private context(): AudioContext | null {\n if (this.ctx) return this.ctx;\n if (typeof AudioContext === 'undefined') return null;\n this.ctx = new AudioContext();\n this.master = this.ctx.createGain();\n this.master.gain.value = this.level(engineSettings.getState().sfxVolume);\n const shelf = this.ctx.createBiquadFilter();\n shelf.type = 'highshelf';\n shelf.frequency.value = TAME_SHELF.freq;\n shelf.gain.value = TAME_SHELF.gainDb;\n const top = this.ctx.createBiquadFilter();\n top.type = 'lowpass';\n top.frequency.value = TAME_TOP;\n this.master.connect(shelf).connect(top).connect(this.ctx.destination);\n const ctx = this.ctx;\n const changed = () => {\n const running = ctx.state === 'running';\n this.onRunning(running);\n if (running) this.prerender(ctx);\n };\n ctx.addEventListener('statechange', changed);\n // a context allowed to run from the start changes no state\n if (ctx.state === 'running') queueMicrotask(changed);\n return ctx;\n }\n\n /**\n * Starts decoding every recorded take and renders every recipe not rendered yet, one per task, once the context\n * first runs — so the first swing of a fight does not pay for its sound in that frame.\n */\n private prerender(ctx: AudioContext): void {\n for (const name of this.samples.names()) this.samples.load(ctx, name);\n // a sound with recorded takes plays them; its recipe is only the fallback while they decode, not worth a 40 ms task\n const todo = Object.keys(SOUNDS.recipes).filter((name) => !this.buffers.has(name) && !hasTakes(name));\n const next = () => {\n const name = todo.shift();\n if (name === undefined) return;\n this.buffer(ctx, name);\n setTimeout(next, 0);\n };\n next();\n }\n\n /** Rendered on first use or by `prerender` (a few ms each), then kept. */\n private buffer(ctx: AudioContext, name: string): AudioBuffer | null {\n const cached = this.buffers.get(name);\n if (cached !== undefined) return cached;\n const recipe = SOUNDS.recipes[name];\n let buf: AudioBuffer | null = null;\n if (recipe) {\n const data = renderRecipe(recipe);\n buf = ctx.createBuffer(1, data.length, SYNTH_RATE);\n buf.getChannelData(0).set(data);\n }\n this.buffers.set(name, buf);\n return buf;\n }\n}\n\nexport const sound = new SoundEngine();\n"],"names":["HEARING","MAX_VOICES","MINOR_VOICES","MIN_GAP_MS","OTHERS","DEFAULT_JITTER","CUT_FADE_MS","MUFFLE","TAME_SHELF","TAME_TOP","SoundEngine","SampleBank","unlock","ctx","blank","types","listen","on","type","running","session","engineSettings","name","opts","SOUNDS","hasTakes","gain","pan","far","me","dx","hearing","now","take","buffer","src","jitter","g","p","lp","lasts","end","volume","shelf","top","changed","todo","next","cached","recipe","buf","data","renderRecipe","SYNTH_RATE","sound"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cleanTake.js","sources":["../../src/audio/cleanTake.ts"],"sourcesContent":["/*\n * Cleans one recorded take (`SampleBank`): folded to mono, the silence an encoder or the model leaves at the start cut\n * (a swing heard 30 ms late reads as lag) and at the end trimmed, and brought to one loudness (`LOUDNESS`, RMS of what\n * is left, never past `PEAK`), so takes from different prompts sit at one level. Pure: run in `takeCleanerWorker.ts`,\n * because on the main thread the first blows of a fight froze the game 70–170 ms a take on a Mac and several times\n * that on a phone (profiled).\n */\n\n/** Below this share of the take's own peak a sample counts as silence when trimming (-40 dB). */\nconst FLOOR = 0.01;\n/** A take whose peak is under this is treated as empty (about -46 dBFS): the model returned near-silence. */\nconst EMPTY = 0.005;\n/** Target RMS of a cleaned take (-16 dBFS) and the peak it may not pass. */\nconst LOUDNESS = 0.16;\nconst PEAK = 0.95;\n/** Kept before the first loud sample, so the attack is not clipped into a click (s). */\nconst LEAD = 0.002;\n/** A short fade over the cut end, so a trimmed tail does not click (s). */\nconst TAIL_FADE = 0.01;\n\n/** The cleaned mono samples, or null for a take that is silence. */\nexport function cleanSamples(channels: Float32Array[], sampleRate: number): Float32Array | null {\n const n = channels[0]?.length ?? 0;\n const k0 = 1 / Math.max(1, channels.length);\n const mono = new Float32Array(n);\n for (const ch of channels) for (let i = 0; i < n; i++) mono[i]! += ch[i]! * k0;\n let top = 0;\n for (let i = 0; i < n; i++) {\n const a = Math.abs(mono[i]!);\n if (a > top) top = a;\n }\n if (top < EMPTY) return null;\n const floor = top * FLOOR;\n let start = 0;\n while (start < n && Math.abs(mono[start]!) < floor) start++;\n let end = n;\n while (end > start && Math.abs(mono[end - 1]!) < floor) end--;\n if (end <= start) return null;\n start = Math.max(0, start - Math.round(LEAD * sampleRate));\n const data = mono.slice(start, end);\n let power = 0;\n for (let i = 0; i < data.length; i++) power += data[i]! * data[i]!;\n const rms = Math.sqrt(power / data.length);\n const k = Math.min(LOUDNESS / Math.max(rms, 1e-6), PEAK / top);\n const fade = Math.min(data.length, Math.round(TAIL_FADE * sampleRate));\n for (let i = 0; i < data.length; i++) {\n const left = data.length - i;\n data[i]! *= k * (left < fade ? left / fade : 1);\n }\n return data;\n}\n"],"names":["cleanSamples","channels","sampleRate","n","k0","mono","ch","i","top","a","floor","start","end","data","power","rms","k","fade","left"],"mappings":"AAqBO,SAASA,EAAaC,GAA0BC,GAAyC;AAC9F,QAAMC,IAAIF,EAAS,CAAC,GAAG,UAAU,GAC3BG,IAAK,IAAI,KAAK,IAAI,GAAGH,EAAS,MAAM,GACpCI,IAAO,IAAI,aAAaF,CAAC;AAC/B,aAAWG,KAAML,EAAU,UAASM,IAAI,GAAGA,IAAIJ,GAAGI,IAAK,CAAAF,EAAKE,CAAC,KAAMD,EAAGC,CAAC,IAAKH;AAC5E,MAAII,IAAM;AACV,WAASD,IAAI,GAAGA,IAAIJ,GAAGI,KAAK;AAC1B,UAAME,IAAI,KAAK,IAAIJ,EAAKE,CAAC,CAAE;AAC3B,IAAIE,IAAID,MAAKA,IAAMC;AAAA,EACrB;AACA,MAAID,IAAM,KAAO,QAAO;AACxB,QAAME,IAAQF,IAAM;AACpB,MAAIG,IAAQ;AACZ,SAAOA,IAAQR,KAAK,KAAK,IAAIE,EAAKM,CAAK,CAAE,IAAID,IAAO,CAAAC;AACpD,MAAIC,IAAMT;AACV,SAAOS,IAAMD,KAAS,KAAK,IAAIN,EAAKO,IAAM,CAAC,CAAE,IAAIF,IAAO,CAAAE;AACxD,MAAIA,KAAOD,EAAO,QAAO;AACzB,EAAAA,IAAQ,KAAK,IAAI,GAAGA,IAAQ,KAAK,MAAM,OAAOT,CAAU,CAAC;AACzD,QAAMW,IAAOR,EAAK,MAAMM,GAAOC,CAAG;AAClC,MAAIE,IAAQ;AACZ,WAASP,IAAI,GAAGA,IAAIM,EAAK,QAAQN,IAAK,CAAAO,KAASD,EAAKN,CAAC,IAAKM,EAAKN,CAAC;AAChE,QAAMQ,IAAM,KAAK,KAAKD,IAAQD,EAAK,MAAM,GACnCG,IAAI,KAAK,IAAI,OAAW,KAAK,IAAID,GAAK,IAAI,GAAG,OAAOP,CAAG,GACvDS,IAAO,KAAK,IAAIJ,EAAK,QAAQ,KAAK,MAAM,OAAYX,CAAU,CAAC;AACrE,WAASK,IAAI,GAAGA,IAAIM,EAAK,QAAQN,KAAK;AACpC,UAAMW,IAAOL,EAAK,SAASN;AAC3B,IAAAM,EAAKN,CAAC,KAAMS,KAAKE,IAAOD,IAAOC,IAAOD,IAAO;AAAA,EAC/C;AACA,SAAOJ;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ElementVoice.js","sources":["../../../src/audio/music/ElementVoice.ts"],"sourcesContent":["import { clamp01, type MusicVoice } from './musicVoice';\n\n/** A track on the `<audio>` element's own volume, faded frame by frame: nothing between the file and the speaker. */\nexport class ElementVoice implements MusicVoice {\n private fading: number | null = null;\n\n constructor(readonly el: HTMLAudioElement) {}\n\n level(): number {\n return this.el.volume;\n }\n\n set(level: number): void {\n this.stopFade();\n this.el.volume = clamp01(level);\n }\n\n fade(to: number, ms: number, done?: () => void): void {\n this.stopFade();\n const from = this.el.volume;\n const start = performance.now();\n const step = () => {\n const k = ms > 0 ? Math.min(1, (performance.now() - start) / ms) : 1;\n this.el.volume = clamp01(from + (to - from) * k);\n if (k < 1) this.fading = requestAnimationFrame(step);\n else {\n this.fading = null;\n done?.();\n }\n };\n this.fading = requestAnimationFrame(step);\n }\n\n get busy(): boolean {\n return this.fading !== null;\n }\n\n dispose(): void {\n this.stopFade();\n this.el.pause();\n this.el.removeAttribute('src');\n this.el.load();\n }\n\n private stopFade(): void {\n if (this.fading !== null) cancelAnimationFrame(this.fading);\n this.fading = null;\n }\n}\n"],"names":["ElementVoice","el","level","clamp01","to","ms","done","from","start","step","k"],"mappings":";AAGO,MAAMA,EAAmC;AAAA,EAG9C,YAAqBC,GAAsB;AAAtB,SAAA,KAAAA;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAFb,SAAwB;AAAA,EAIhC,QAAgB;AACd,WAAO,KAAK,GAAG;AAAA,EACjB;AAAA,EAEA,IAAIC,GAAqB;AACvB,SAAK,SAAA,GACL,KAAK,GAAG,SAASC,EAAQD,CAAK;AAAA,EAChC;AAAA,EAEA,KAAKE,GAAYC,GAAYC,GAAyB;AACpD,SAAK,SAAA;AACL,UAAMC,IAAO,KAAK,GAAG,QACfC,IAAQ,YAAY,IAAA,GACpBC,IAAO,MAAM;AACjB,YAAMC,IAAIL,IAAK,IAAI,KAAK,IAAI,IAAI,YAAY,IAAA,IAAQG,KAASH,CAAE,IAAI;AACnE,WAAK,GAAG,SAASF,EAAQI,KAAQH,IAAKG,KAAQG,CAAC,GAC3CA,IAAI,IAAG,KAAK,SAAS,sBAAsBD,CAAI,KAEjD,KAAK,SAAS,MACdH,IAAA;AAAA,IAEJ;AACA,SAAK,SAAS,sBAAsBG,CAAI;AAAA,EAC1C;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAA,GACL,KAAK,GAAG,MAAA,GACR,KAAK,GAAG,gBAAgB,KAAK,GAC7B,KAAK,GAAG,KAAA;AAAA,EACV;AAAA,EAEQ,WAAiB;AACvB,IAAI,KAAK,WAAW,QAAM,qBAAqB,KAAK,MAAM,GAC1D,KAAK,SAAS;AAAA,EAChB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GraphVoice.js","sources":["../../../src/audio/music/GraphVoice.ts"],"sourcesContent":["import { clamp01, type MusicVoice } from './musicVoice';\n\n/**\n * A track routed `<audio>` → MediaElementSource → gain → speakers, so its volume and crossfades work where the\n * element's own `volume` is read-only (iOS). The fade is scheduled on the audio clock (a ramp), so it runs even while\n * the page draws no frames. A cross-origin file without CORS headers plays silent this way: serve music from the\n * game's own origin, or keep `ElementVoice` where volume works.\n */\nexport class GraphVoice implements MusicVoice {\n private readonly gain: GainNode;\n private readonly source: MediaElementAudioSourceNode;\n private timer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n readonly el: HTMLAudioElement,\n private readonly graph: AudioContext,\n ) {\n this.source = graph.createMediaElementSource(el);\n this.gain = graph.createGain();\n this.gain.gain.value = 0;\n this.source.connect(this.gain).connect(graph.destination);\n }\n\n level(): number {\n return this.gain.gain.value;\n }\n\n set(level: number): void {\n this.stopFade();\n const g = this.gain.gain;\n g.cancelScheduledValues(this.graph.currentTime);\n g.setValueAtTime(clamp01(level), this.graph.currentTime);\n }\n\n fade(to: number, ms: number, done?: () => void): void {\n this.stopFade();\n const g = this.gain.gain;\n const now = this.graph.currentTime;\n g.cancelScheduledValues(now);\n g.setValueAtTime(g.value, now);\n g.linearRampToValueAtTime(clamp01(to), now + ms / 1000);\n this.timer = setTimeout(() => {\n this.timer = null;\n done?.();\n }, ms);\n }\n\n get busy(): boolean {\n return this.timer !== null;\n }\n\n dispose(): void {\n this.stopFade();\n this.el.pause();\n this.source.disconnect();\n this.gain.disconnect();\n }\n\n private stopFade(): void {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n }\n}\n"],"names":["GraphVoice","el","graph","level","g","clamp01","to","ms","done","now"],"mappings":";AAQO,MAAMA,EAAiC;AAAA,EAK5C,YACWC,GACQC,GACjB;AAFS,SAAA,KAAAD,GACQ,KAAA,QAAAC,GAEjB,KAAK,SAASA,EAAM,yBAAyBD,CAAE,GAC/C,KAAK,OAAOC,EAAM,WAAA,GAClB,KAAK,KAAK,KAAK,QAAQ,GACvB,KAAK,OAAO,QAAQ,KAAK,IAAI,EAAE,QAAQA,EAAM,WAAW;AAAA,EAC1D;AAAA,EAPW;AAAA,EACQ;AAAA,EANF;AAAA,EACA;AAAA,EACT,QAA8C;AAAA,EAYtD,QAAgB;AACd,WAAO,KAAK,KAAK,KAAK;AAAA,EACxB;AAAA,EAEA,IAAIC,GAAqB;AACvB,SAAK,SAAA;AACL,UAAMC,IAAI,KAAK,KAAK;AACpB,IAAAA,EAAE,sBAAsB,KAAK,MAAM,WAAW,GAC9CA,EAAE,eAAeC,EAAQF,CAAK,GAAG,KAAK,MAAM,WAAW;AAAA,EACzD;AAAA,EAEA,KAAKG,GAAYC,GAAYC,GAAyB;AACpD,SAAK,SAAA;AACL,UAAMJ,IAAI,KAAK,KAAK,MACdK,IAAM,KAAK,MAAM;AACvB,IAAAL,EAAE,sBAAsBK,CAAG,GAC3BL,EAAE,eAAeA,EAAE,OAAOK,CAAG,GAC7BL,EAAE,wBAAwBC,EAAQC,CAAE,GAAGG,IAAMF,IAAK,GAAI,GACtD,KAAK,QAAQ,WAAW,MAAM;AAC5B,WAAK,QAAQ,MACbC,IAAA;AAAA,IACF,GAAGD,CAAE;AAAA,EACP;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAA,GACL,KAAK,GAAG,MAAA,GACR,KAAK,OAAO,WAAA,GACZ,KAAK,KAAK,WAAA;AAAA,EACZ;AAAA,EAEQ,WAAiB;AACvB,IAAI,KAAK,SAAO,aAAa,KAAK,KAAK,GACvC,KAAK,QAAQ;AAAA,EACf;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MusicChannel.js","sources":["../../../src/audio/music/MusicChannel.ts"],"sourcesContent":["import type { MusicVoice } from './musicVoice';\n\nexport interface PlayOptions {\n /** Crossfade from what this channel plays now, ms (default 1200). */\n fadeMs?: number;\n /** Start over at the end (default true). */\n loop?: boolean;\n /** This track's own level, 0–1, under the music volume (default 1). */\n volume?: number;\n}\n\n/**\n * One layer of music (the score, a place's ambience…): one looping track at a time, crossfaded when it changes, at\n * its own level under the player's music volume.\n */\nexport class MusicChannel {\n private voice: MusicVoice | null = null;\n private url: string | null = null;\n private level = 1;\n\n constructor(\n private readonly volume: () => number,\n private readonly makeVoice: (el: HTMLAudioElement) => MusicVoice,\n ) {}\n\n /** The url of the track playing (or fading in), if any. */\n get current(): string | null {\n return this.url;\n }\n\n /** Plays `url`, crossfading from what plays now; the track already playing only takes the new `volume`. */\n play(url: string, options: PlayOptions = {}): void {\n const level = Math.min(1, Math.max(0, options.volume ?? 1));\n if (url === this.url) {\n if (level !== this.level) {\n this.level = level;\n this.refresh();\n }\n return;\n }\n if (typeof Audio === 'undefined') return;\n const fadeMs = Math.max(0, options.fadeMs ?? 1200);\n this.fadeOut(fadeMs);\n this.url = url;\n this.level = level;\n const el = new Audio(url);\n el.loop = options.loop !== false;\n el.preload = 'auto';\n const voice = this.makeVoice(el);\n voice.set(0);\n this.voice = voice;\n void el.play().catch(() => {\n // refused until the first gesture; the player's unlock retries it\n });\n voice.fade(this.target(), fadeMs);\n }\n\n /** Fades this channel out and stops it. */\n stop(fadeMs = 1200): void {\n this.fadeOut(Math.max(0, fadeMs));\n this.url = null;\n }\n\n /** Retries a start the browser refused, and follows the music volume. */\n refresh(): void {\n const v = this.voice;\n if (!v) return;\n if (v.el.paused) void v.el.play().catch(() => {});\n if (!v.busy) v.set(this.target());\n }\n\n /** Stops where it is (the app went to the background); `refresh` picks it up again. */\n pause(): void {\n this.voice?.el.pause();\n }\n\n dispose(): void {\n this.voice?.dispose();\n this.voice = null;\n this.url = null;\n }\n\n private target(): number {\n return Math.max(0, Math.min(1, this.volume() * this.level));\n }\n\n private fadeOut(ms: number): void {\n const old = this.voice;\n this.voice = null;\n if (old) old.fade(0, ms, () => old.dispose());\n }\n}\n"],"names":["MusicChannel","volume","makeVoice","url","options","level","fadeMs","el","voice","v","ms","old"],"mappings":"AAeO,MAAMA,EAAa;AAAA,EAKxB,YACmBC,GACAC,GACjB;AAFiB,SAAA,SAAAD,GACA,KAAA,YAAAC;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EANX,QAA2B;AAAA,EAC3B,MAAqB;AAAA,EACrB,QAAQ;AAAA;AAAA,EAQhB,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,KAAKC,GAAaC,IAAuB,IAAU;AACjD,UAAMC,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGD,EAAQ,UAAU,CAAC,CAAC;AAC1D,QAAID,MAAQ,KAAK,KAAK;AACpB,MAAIE,MAAU,KAAK,UACjB,KAAK,QAAQA,GACb,KAAK,QAAA;AAEP;AAAA,IACF;AACA,QAAI,OAAO,QAAU,IAAa;AAClC,UAAMC,IAAS,KAAK,IAAI,GAAGF,EAAQ,UAAU,IAAI;AACjD,SAAK,QAAQE,CAAM,GACnB,KAAK,MAAMH,GACX,KAAK,QAAQE;AACb,UAAME,IAAK,IAAI,MAAMJ,CAAG;AACxB,IAAAI,EAAG,OAAOH,EAAQ,SAAS,IAC3BG,EAAG,UAAU;AACb,UAAMC,IAAQ,KAAK,UAAUD,CAAE;AAC/B,IAAAC,EAAM,IAAI,CAAC,GACX,KAAK,QAAQA,GACRD,EAAG,OAAO,MAAM,MAAM;AAAA,IAE3B,CAAC,GACDC,EAAM,KAAK,KAAK,OAAA,GAAUF,CAAM;AAAA,EAClC;AAAA;AAAA,EAGA,KAAKA,IAAS,MAAY;AACxB,SAAK,QAAQ,KAAK,IAAI,GAAGA,CAAM,CAAC,GAChC,KAAK,MAAM;AAAA,EACb;AAAA;AAAA,EAGA,UAAgB;AACd,UAAMG,IAAI,KAAK;AACf,IAAKA,MACDA,EAAE,GAAG,UAAaA,EAAE,GAAG,OAAO,MAAM,MAAM;AAAA,IAAC,CAAC,GAC3CA,EAAE,UAAQ,IAAI,KAAK,QAAQ;AAAA,EAClC;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,GAAG,MAAA;AAAA,EACjB;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO,QAAA,GACZ,KAAK,QAAQ,MACb,KAAK,MAAM;AAAA,EACb;AAAA,EAEQ,SAAiB;AACvB,WAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,OAAA,IAAW,KAAK,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEQ,QAAQC,GAAkB;AAChC,UAAMC,IAAM,KAAK;AACjB,SAAK,QAAQ,MACTA,OAAS,KAAK,GAAGD,GAAI,MAAMC,EAAI,SAAS;AAAA,EAC9C;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"musicVoice.js","sources":["../../../src/audio/music/musicVoice.ts"],"sourcesContent":["/**\n * One looping track playing on one channel of `MusicPlayer`, with a volume that can be set and faded. Two ways to get\n * one: `ElementVoice` (the `<audio>` element's own volume) and `GraphVoice` (the element routed through a Web Audio\n * gain, for where an `<audio>`'s `volume` is read-only: iOS).\n */\nexport interface MusicVoice {\n readonly el: HTMLAudioElement;\n /** The volume now (0..1). */\n level(): number;\n /** Sets it at once, cancelling a fade in flight. */\n set(level: number): void;\n /** Fades to `level` over `ms`; `done` runs at the end (not if a later fade or `set` takes over). */\n fade(level: number, ms: number, done?: () => void): void;\n /** A fade is in flight: the channel leaves the volume setting alone until it ends. */\n readonly busy: boolean;\n /** Stops the sound and lets the element go. */\n dispose(): void;\n}\n\n/**\n * True where an `<audio>` ignores `volume` (iOS WebKit), so music has to go through Web Audio to be faded at all.\n * Newer iOS reads a set volume back while the speaker ignores it, so the probe alone misses it: an iPhone, iPod or an\n * iPad posing as a Mac is named first.\n */\nexport function elementVolumeLocked(): boolean {\n if (typeof Audio === 'undefined') return false;\n const nav = typeof navigator !== 'undefined' ? navigator : null;\n if (nav && (/iPhone|iPad|iPod/.test(nav.userAgent) || (/Macintosh/.test(nav.userAgent) && nav.maxTouchPoints > 1))) return true;\n const probe = new Audio();\n probe.volume = 0.5;\n return Math.abs(probe.volume - 0.5) > 0.01;\n}\n\nexport const clamp01 = (n: number): number => Math.max(0, Math.min(1, n));\n"],"names":["elementVolumeLocked","nav","probe","clamp01","n"],"mappings":"AAwBO,SAASA,IAA+B;AAC7C,MAAI,OAAO,QAAU,IAAa,QAAO;AACzC,QAAMC,IAAM,OAAO,YAAc,MAAc,YAAY;AAC3D,MAAIA,MAAQ,mBAAmB,KAAKA,EAAI,SAAS,KAAM,YAAY,KAAKA,EAAI,SAAS,KAAKA,EAAI,iBAAiB,GAAK,QAAO;AAC3H,QAAMC,IAAQ,IAAI,MAAA;AAClB,SAAAA,EAAM,SAAS,KACR,KAAK,IAAIA,EAAM,SAAS,GAAG,IAAI;AACxC;AAEO,MAAMC,IAAU,CAACC,MAAsB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGA,CAAC,CAAC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sampleBank.js","sources":["../../src/audio/sampleBank.ts"],"sourcesContent":["/**\n * The recorded takes of the sounds, registered by the game (`registerTakes`): `<sound>.mp3`, and `<sound>_2.mp3`…\n * when a sound has more than one. A\n * sound with takes plays one of them at random, never the same one twice running when it has more; a sound without\n * any — or whose takes have not decoded yet — falls back to its synth recipe in `SoundEngine`.\n *\n * Every take is cleaned once on decode (`cleanTake.ts`: mono, silence trimmed, one loudness), off the main thread\n * in `takeCleanerWorker.ts`, so `sounds.json`'s `fileGain` is the only mix knob. Measured on the first batch, peaks\n * alone left their loudness 20 dB apart.\n */\n\nimport { cleanSamples } from './cleanTake';\n\nconst TAKES: Record<string, string[]> = {};\n\n/**\n * Registers recorded takes by file path → url, e.g. a Vite `import.meta.glob('./sfx/*.mp3', { query: '?url',\n * import: 'default', eager: true })`. `hit.mp3`, `hit_2.mp3`… are takes of `hit`; sound names carry no digits.\n */\nexport function registerTakes(urls: Record<string, string>): void {\n for (const [path, url] of Object.entries(urls)) {\n const m = /(?:^|\\/)([a-z_]+?)(?:_\\d+)?\\.mp3$/.exec(path);\n if (m) (TAKES[m[1]!] ??= []).push(url);\n }\n}\n\n/** The sound has recorded takes in the build (decoded or not). */\nexport const hasTakes = (name: string): boolean => !!TAKES[name];\n\nexport class SampleBank {\n private readonly decoded = new Map<string, AudioBuffer[]>();\n private readonly loading = new Set<string>();\n private readonly last = new Map<string, number>();\n\n names(): string[] {\n return Object.keys(TAKES);\n }\n\n /** A decoded take, or null while none is ready (the caller then synthesises); starts loading on first ask. */\n take(ctx: AudioContext, name: string): AudioBuffer | null {\n const list = this.decoded.get(name);\n if (!list?.length) {\n this.load(ctx, name);\n return null;\n }\n let i = Math.floor(Math.random() * list.length);\n if (list.length > 1 && i === this.last.get(name)) i = (i + 1) % list.length;\n this.last.set(name, i);\n return list[i]!;\n }\n\n load(ctx: AudioContext, name: string): void {\n const urlsOf = TAKES[name];\n if (!urlsOf || this.loading.has(name)) return;\n this.loading.add(name);\n const list: AudioBuffer[] = [];\n this.decoded.set(name, list);\n for (const url of urlsOf) {\n fetch(url)\n .then((r) => r.arrayBuffer())\n .then((data) => ctx.decodeAudioData(data))\n .then((buf) => clean(buf))\n .then((data) => {\n if (!data) return;\n const out = ctx.createBuffer(1, data.length, ctx.sampleRate);\n out.copyToChannel(data as Float32Array<ArrayBuffer>, 0);\n list.push(out);\n })\n .catch(() => {});\n }\n }\n}\n\nlet worker: Worker | null | undefined;\nlet nextId = 0;\nconst waiting = new Map<number, (data: Float32Array | null) => void>();\n\n/** The cleaner worker, started on first use; null where workers cannot start (then takes are cleaned here). */\nfunction cleaner(): Worker | null {\n if (worker !== undefined) return worker;\n try {\n worker = new Worker(new URL('./takeCleanerWorker.ts', import.meta.url), { type: 'module' });\n worker.onmessage = (e: MessageEvent<{ id: number; data: Float32Array | null }>) => {\n waiting.get(e.data.id)?.(e.data.data);\n waiting.delete(e.data.id);\n };\n worker.onerror = () => {\n worker?.terminate();\n worker = null;\n for (const done of waiting.values()) done(null);\n waiting.clear();\n };\n } catch {\n worker = null;\n }\n return worker;\n}\n\n/** Takes handed over one per task: a fight's first blows ask for a dozen at once, and copying them all out in one\n * task still froze a frame. */\nlet turn: Promise<void> = Promise.resolve();\n\n/** A decoded take cleaned at the context's own rate (decodeAudioData resamples to it). */\nfunction clean(buf: AudioBuffer): Promise<Float32Array | null> {\n return new Promise((resolve) => {\n turn = turn\n .then(() => new Promise<void>((r) => setTimeout(r, 0)))\n .then(() => {\n send(buf).then(resolve, () => resolve(null));\n });\n });\n}\n\nfunction send(buf: AudioBuffer): Promise<Float32Array | null> {\n const channels = Array.from({ length: buf.numberOfChannels }, (_, c) => buf.getChannelData(c).slice());\n const w = cleaner();\n if (!w) return Promise.resolve(cleanSamples(channels, buf.sampleRate));\n const id = nextId++;\n return new Promise((resolve) => {\n waiting.set(id, resolve);\n w.postMessage({ id, channels, sampleRate: buf.sampleRate }, { transfer: channels.map((c) => c.buffer as ArrayBuffer) });\n });\n}\n"],"names":["TAKES","registerTakes","urls","path","url","m","hasTakes","name","SampleBank","ctx","list","i","urlsOf","r","data","buf","clean","out","worker","nextId","waiting","cleaner","done","turn","resolve","send","channels","_","c","w","cleanSamples","id"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"soundTable.js","sources":["../../src/audio/soundTable.ts"],"sourcesContent":["import type { SynthRecipe } from './synth';\n\n/**\n * The sounds the engine can play: synthesised recipes by name, plus the master share every sound plays at. Empty\n * until the game registers its own (`registerSounds`); the mapping from game happenings to sound names stays in\n * the game.\n */\nexport interface SoundTable {\n /** Share of the effects volume everything plays at: keeps full volume modest next to the music. */\n master: number;\n recipes: Record<string, SynthRecipe>;\n}\n\nexport const SOUNDS: SoundTable = { master: 1, recipes: {} };\n\nexport function registerSounds(table: Partial<SoundTable>): void {\n if (table.master !== undefined) SOUNDS.master = table.master;\n if (table.recipes) Object.assign(SOUNDS.recipes, table.recipes);\n}\n"],"names":["SOUNDS","registerSounds","table"],"mappings":"AAaO,MAAMA,IAAqB,EAAE,QAAQ,GAAG,SAAS,CAAA,EAAC;AAElD,SAASC,EAAeC,GAAkC;AAC/D,EAAIA,EAAM,WAAW,WAAWF,EAAO,SAASE,EAAM,SAClDA,EAAM,WAAS,OAAO,OAAOF,EAAO,SAASE,EAAM,OAAO;AAChE;"}
|
package/dist/audio/synth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"synth.js","sources":["../../src/audio/synth.ts"],"sourcesContent":["/**\n * The combat sounds are written, not recorded: a few oscillator or noise layers rendered straight into a buffer\n * (`sounds.json` → `recipes`). Lo-fi on purpose — 22 kHz mono, short and soft — so they sit with the pixel art\n * instead of pretending to be a recording.\n */\nexport const SYNTH_RATE = 22050;\n\nexport interface SynthLayer {\n /** Waveform; `noise` is white noise, or sample-and-hold noise at `f` Hz when `f` is given (crunchier). */\n w: 'sine' | 'square' | 'tri' | 'saw' | 'noise';\n /** Pitch at the start and at the end of the layer (Hz), swept exponentially. */\n f?: [number, number];\n /** Length (s). */\n t: number;\n /** Delay before the layer starts (s). */\n d?: number;\n /** Attack (s). */\n at?: number;\n g: number;\n /** Decay curve exponent: 1 is linear, higher falls away faster. */\n c?: number;\n duty?: number;\n /** Low- and high-pass cutoff at the start and the end (Hz). */\n lp?: [number, number];\n hp?: [number, number];\n}\n\nexport interface SynthRecipe {\n layers: SynthLayer[];\n /** Random pitch spread per play (share of 1); the engine's default when absent. */\n jitter?: number;\n /** Level of the sound's recorded takes (`registerTakes`), which are normalised on decode; default 1. */\n fileGain?: number;\n}\n\nconst WAVES: Record<Exclude<SynthLayer['w'], 'noise'>, (p: number, duty: number) => number> = {\n sine: (p) => Math.sin(p * Math.PI * 2),\n square: (p, duty) => (p < duty ? 1 : -1),\n tri: (p) => 1 - 4 * Math.abs(p - 0.5),\n saw: (p) => 2 * p - 1,\n};\n\nconst sweep = (a: number, b: number, k: number): number => (a > 0 && b > 0 ? a * Math.pow(b / a, k) : a + (b - a) * k);\n\n/** A one-pole filter is enough for a stand-in, and it cannot ring or blow up the way a swept biquad can. */\nfunction onePole(cut: number): number {\n return 1 - Math.exp((-2 * Math.PI * Math.min(cut, SYNTH_RATE * 0.45)) / SYNTH_RATE);\n}\n\nexport function renderRecipe(recipe: SynthRecipe): Float32Array {\n const total = Math.max(...recipe.layers.map((l) => (l.d ?? 0) + l.t));\n const out = new Float32Array(Math.ceil(total * SYNTH_RATE) + 1);\n let seed = 0x9e3779b9;\n const rand = () => {\n seed ^= seed << 13;\n seed ^= seed >>> 17;\n seed ^= seed << 5;\n return ((seed >>> 0) / 0xffffffff) * 2 - 1;\n };\n for (const l of recipe.layers) {\n const start = Math.round((l.d ?? 0) * SYNTH_RATE);\n const n = Math.round(l.t * SYNTH_RATE);\n const at = Math.max(1, Math.round((l.at ?? 0.004) * SYNTH_RATE));\n let phase = 0;\n let held = 0;\n let lpState = 0;\n let hpState = 0;\n for (let i = 0; i < n && start + i < out.length; i++) {\n const k = i / n;\n const freq = l.f ? sweep(l.f[0], l.f[1], k) : 0;\n phase += freq / SYNTH_RATE;\n const p = phase - Math.floor(phase);\n let s: number;\n if (l.w === 'noise') {\n // sample-and-hold: a new value each time the phase wraps, which is what makes it crunch\n if (!l.f || p < freq / SYNTH_RATE || i === 0) held = rand();\n s = l.f ? held : rand();\n } else s = WAVES[l.w](p, l.duty ?? 0.5);\n if (l.lp) {\n lpState += (s - lpState) * onePole(sweep(l.lp[0], l.lp[1], k));\n s = lpState;\n }\n if (l.hp) {\n hpState += (s - hpState) * onePole(sweep(l.hp[0], l.hp[1], k));\n s -= hpState;\n }\n const env = i < at ? i / at : Math.pow(1 - (i - at) / Math.max(1, n - at), l.c ?? 2);\n out[start + i]! += s * env * l.g;\n }\n }\n // a stand-in never clips: peaks are brought back under full scale\n let peak = 0;\n for (const v of out) peak = Math.max(peak, Math.abs(v));\n if (peak > 0.95) for (let i = 0; i < out.length; i++) out[i]! *= 0.95 / peak;\n return out;\n}\n"],"names":["SYNTH_RATE","WAVES","p","duty","sweep","a","b","k","onePole","cut","renderRecipe","recipe","total","l","out","seed","rand","start","n","at","phase","held","lpState","hpState","i","freq","s","env","peak","v"],"mappings":"AAKO,MAAMA,IAAa,OA8BpBC,IAAwF;AAAA,EAC5F,MAAM,CAACC,MAAM,KAAK,IAAIA,IAAI,KAAK,KAAK,CAAC;AAAA,EACrC,QAAQ,CAACA,GAAGC,MAAUD,IAAIC,IAAO,IAAI;AAAA,EACrC,KAAK,CAACD,MAAM,IAAI,IAAI,KAAK,IAAIA,IAAI,GAAG;AAAA,EACpC,KAAK,CAACA,MAAM,IAAIA,IAAI;AACtB,GAEME,IAAQ,CAACC,GAAWC,GAAWC,MAAuBF,IAAI,KAAKC,IAAI,IAAID,IAAI,KAAK,IAAIC,IAAID,GAAGE,CAAC,IAAIF,KAAKC,IAAID,KAAKE;AAGpH,SAASC,EAAQC,GAAqB;AACpC,SAAO,IAAI,KAAK,IAAK,KAAK,KAAK,KAAK,KAAK,IAAIA,GAAK,QAAa,IAAI,IAAK,KAAU;AACpF;AAEO,SAASC,EAAaC,GAAmC;AAC9D,QAAMC,IAAQ,KAAK,IAAI,GAAGD,EAAO,OAAO,IAAI,CAACE,OAAOA,EAAE,KAAK,KAAKA,EAAE,CAAC,CAAC,GAC9DC,IAAM,IAAI,aAAa,KAAK,KAAKF,IAAQ,KAAU,IAAI,CAAC;AAC9D,MAAIG,IAAO;AACX,QAAMC,IAAO,OACXD,KAAQA,KAAQ,IAChBA,KAAQA,MAAS,IACjBA,KAAQA,KAAQ,IACPA,MAAS,KAAK,aAAc,IAAI;AAE3C,aAAWF,KAAKF,EAAO,QAAQ;AAC7B,UAAMM,IAAQ,KAAK,OAAOJ,EAAE,KAAK,KAAK,KAAU,GAC1CK,IAAI,KAAK,MAAML,EAAE,IAAI,KAAU,GAC/BM,IAAK,KAAK,IAAI,GAAG,KAAK,OAAON,EAAE,MAAM,QAAS,KAAU,CAAC;AAC/D,QAAIO,IAAQ,GACRC,IAAO,GACPC,IAAU,GACVC,IAAU;AACd,aAASC,IAAI,GAAGA,IAAIN,KAAKD,IAAQO,IAAIV,EAAI,QAAQU,KAAK;AACpD,YAAMjB,IAAIiB,IAAIN,GACRO,IAAOZ,EAAE,IAAIT,EAAMS,EAAE,EAAE,CAAC,GAAGA,EAAE,EAAE,CAAC,GAAGN,CAAC,IAAI;AAC9C,MAAAa,KAASK,IAAO;AAChB,YAAMvB,IAAIkB,IAAQ,KAAK,MAAMA,CAAK;AAClC,UAAIM;AACJ,MAAIb,EAAE,MAAM,YAEN,CAACA,EAAE,KAAKX,IAAIuB,IAAO,SAAcD,MAAM,OAAGH,IAAOL,EAAA,IACrDU,IAAIb,EAAE,IAAIQ,IAAOL,EAAA,SACRf,EAAMY,EAAE,CAAC,EAAEX,GAAGW,EAAE,QAAQ,GAAG,GAClCA,EAAE,OACJS,MAAYI,IAAIJ,KAAWd,EAAQJ,EAAMS,EAAE,GAAG,CAAC,GAAGA,EAAE,GAAG,CAAC,GAAGN,CAAC,CAAC,GAC7DmB,IAAIJ,IAEFT,EAAE,OACJU,MAAYG,IAAIH,KAAWf,EAAQJ,EAAMS,EAAE,GAAG,CAAC,GAAGA,EAAE,GAAG,CAAC,GAAGN,CAAC,CAAC,GAC7DmB,KAAKH;AAEP,YAAMI,IAAMH,IAAIL,IAAKK,IAAIL,IAAK,KAAK,IAAI,KAAKK,IAAIL,KAAM,KAAK,IAAI,GAAGD,IAAIC,CAAE,GAAGN,EAAE,KAAK,CAAC;AACnF,MAAAC,EAAIG,IAAQO,CAAC,KAAME,IAAIC,IAAMd,EAAE;AAAA,IACjC;AAAA,EACF;AAEA,MAAIe,IAAO;AACX,aAAWC,KAAKf,EAAK,CAAAc,IAAO,KAAK,IAAIA,GAAM,KAAK,IAAIC,CAAC,CAAC;AACtD,MAAID,IAAO,KAAM,UAASJ,IAAI,GAAGA,IAAIV,EAAI,QAAQU,IAAK,CAAAV,EAAIU,CAAC,KAAM,OAAOI;AACxE,SAAOd;AACT;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BitmapLabel.js","sources":["../../src/core/BitmapLabel.ts"],"sourcesContent":["import { BitmapFont, BitmapText, DynamicBitmapFont } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\nconst INSTALLED = new Set<string>();\n\n/**\n * The glyphs baked into every label font: printable ASCII (space to `~`) plus whatever a game adds (`addLabelGlyphs`)\n * for the words its labels print, so no glyph is baked mid-frame onto a fresh page (a new canvas a page, and iOS\n * hands out none once its canvas memory is spent).\n */\nconst CHARS: (string | [string, string])[] = [[' ', '~']];\n\n/**\n * Adds letters every label font made **after this call** bakes ahead — the accented letters of a language the labels\n * print (German's `'äöüß'`, say), an arrow, a star. A font already installed keeps the glyphs it was made with, so\n * call it once at start-up, before the first label or `FloatingText.warm`.\n *\n * Without it, a label font holds ASCII only: a letter outside it is still drawn (Pixi bakes it when first printed),\n * but on that frame, onto a new glyph page — the stall and the canvas memory this module exists to avoid. For one\n * font's own extra letters, pass them to `installLabelFont` (or `Label.bitmap`, `FloatingText`'s `glyphs`) instead.\n */\nexport function addLabelGlyphs(letters: string): void {\n CHARS.push(letters);\n}\n/**\n * The side of one glyph page in CSS pixels; a page's canvas is this × the resolution. Pixi's default, 512, made every\n * label font a 2048² canvas at resolution 4 (16 MB each, ten of them at boot); the numbers' glyphs fill a few pages of\n * this size instead.\n */\nconst PAGE_SIZE = 128;\n\n/**\n * Rasterises one label font once and returns its name. Called ahead of time for the damage numbers: baking a font on\n * its first number froze the first frame that printed one by ~60 ms. `glyphs` are letters this font bakes besides\n * ASCII and `addLabelGlyphs`' (a font with its own is a font of its own: pass the same `glyphs` wherever it is used).\n */\nexport function installLabelFont(fontFamily: string, size: number, color: string, stroke: string, strokeWidth: number, resolution: number, glyphs = ''): string {\n const name = `label|${fontFamily}|${size}|${color}|${stroke}|${strokeWidth}|${resolution}${glyphs ? `|${glyphs}` : ''}`;\n if (!INSTALLED.has(name)) {\n // `install` takes no page size of its own: it reads the dynamic font's default, set for this call only\n const defaults = DynamicBitmapFont.defaultOptions;\n const was = defaults.textureSize;\n defaults.textureSize = PAGE_SIZE;\n try {\n BitmapFont.install({\n name,\n chars: glyphs ? [...CHARS, glyphs] : CHARS,\n resolution,\n style: { fontFamily, fontSize: size, fill: color, stroke: { color: stroke, width: strokeWidth, join: 'round' } },\n });\n } finally {\n defaults.textureSize = was;\n }\n INSTALLED.add(name);\n }\n return name;\n}\n\n/**\n * Text drawn from a bitmap font: the glyphs are rasterised once per font, so changing the string costs no canvas\n * redraw and no texture upload, and every label of one font batches with the others. For the floating numbers, which\n * a crowded fight throws dozens of a second — a plain `Text` repaints its canvas at resolution 4 and uploads it on\n * every change.\n */\nexport class BitmapLabel extends GameObject(BitmapText) {\n /**\n * `fontFamily` must already be loaded (`document.fonts.load`), or the glyphs bake in the fallback.\n * Styled like `worldText`: the same font, size, colour and outline, rasterised at the same resolution. `glyphs`:\n * letters beyond ASCII it prints (see `installLabelFont`).\n */\n constructor(x: number, y: number, text: string, fontFamily: string, size: number, color: string, stroke: string, strokeWidth: number, resolution: number, glyphs = '') {\n const name = installLabelFont(fontFamily, size, color, stroke, strokeWidth, resolution, glyphs);\n super({ text, style: { fontFamily: name, fontSize: size } });\n this.position.set(x, y);\n }\n\n setOrigin(x = 0.5, y = x): this {\n this.anchor.set(x, y);\n return this;\n }\n\n setText(text: string): this {\n this.text = text;\n return this;\n }\n}\n"],"names":["INSTALLED","CHARS","addLabelGlyphs","letters","PAGE_SIZE","installLabelFont","fontFamily","size","color","stroke","strokeWidth","resolution","glyphs","name","defaults","DynamicBitmapFont","was","BitmapFont","BitmapLabel","GameObject","BitmapText","x","y","text"],"mappings":";;AAGA,MAAMA,wBAAgB,IAAA,GAOhBC,IAAuC,CAAC,CAAC,KAAK,GAAG,CAAC;AAWjD,SAASC,EAAeC,GAAuB;AACpD,EAAAF,EAAM,KAAKE,CAAO;AACpB;AAMA,MAAMC,IAAY;AAOX,SAASC,EAAiBC,GAAoBC,GAAcC,GAAeC,GAAgBC,GAAqBC,GAAoBC,IAAS,IAAY;AAC9J,QAAMC,IAAO,SAASP,CAAU,IAAIC,CAAI,IAAIC,CAAK,IAAIC,CAAM,IAAIC,CAAW,IAAIC,CAAU,GAAGC,IAAS,IAAIA,CAAM,KAAK,EAAE;AACrH,MAAI,CAACZ,EAAU,IAAIa,CAAI,GAAG;AAExB,UAAMC,IAAWC,EAAkB,gBAC7BC,IAAMF,EAAS;AACrB,IAAAA,EAAS,cAAcV;AACvB,QAAI;AACF,MAAAa,EAAW,QAAQ;AAAA,QACjB,MAAAJ;AAAA,QACA,OAAOD,IAAS,CAAC,GAAGX,GAAOW,CAAM,IAAIX;AAAA,QACrC,YAAAU;AAAA,QACA,OAAO,EAAE,YAAAL,GAAY,UAAUC,GAAM,MAAMC,GAAO,QAAQ,EAAE,OAAOC,GAAQ,OAAOC,GAAa,MAAM,UAAQ;AAAA,MAAE,CAChH;AAAA,IACH,UAAA;AACE,MAAAI,EAAS,cAAcE;AAAA,IACzB;AACA,IAAAhB,EAAU,IAAIa,CAAI;AAAA,EACpB;AACA,SAAOA;AACT;AAQO,MAAMK,UAAoBC,EAAWC,CAAU,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtD,YAAYC,GAAWC,GAAWC,GAAcjB,GAAoBC,GAAcC,GAAeC,GAAgBC,GAAqBC,GAAoBC,IAAS,IAAI;AACrK,UAAMC,IAAOR,EAAiBC,GAAYC,GAAMC,GAAOC,GAAQC,GAAaC,GAAYC,CAAM;AAC9F,UAAM,EAAE,MAAAW,GAAM,OAAO,EAAE,YAAYV,GAAM,UAAUN,EAAA,GAAQ,GAC3D,KAAK,SAAS,IAAIc,GAAGC,CAAC;AAAA,EACxB;AAAA,EAEA,UAAUD,IAAI,KAAKC,IAAID,GAAS;AAC9B,gBAAK,OAAO,IAAIA,GAAGC,CAAC,GACb;AAAA,EACT;AAAA,EAEA,QAAQC,GAAoB;AAC1B,gBAAK,OAAOA,GACL;AAAA,EACT;AACF;"}
|
package/dist/core/Camera.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Camera.js","sources":["../../src/core/Camera.ts"],"sourcesContent":["import type { Container } from 'pixi.js';\nimport { Rectangle } from './Rectangle';\n\nexport interface TrackOptions {\n /**\n * How much of the way to the target the camera closes each 60th of a second (0–1, default 1: locked on). 0.1 is a\n * soft follow; the rate is the same at any frame rate.\n */\n lerp?: number;\n /** A box round the screen's centre (world px) the target may move in without the camera moving. */\n deadzoneWidth?: number;\n deadzoneHeight?: number;\n /** Where on the target to look (world px from its position), e.g. a little above a body's feet. */\n offsetX?: number;\n offsetY?: number;\n}\n\nexport interface ViewRect {\n x: number;\n y: number;\n width: number;\n height: number;\n right: number;\n bottom: number;\n}\n\n/**\n * Moves and zooms the world layer. `scrollX/scrollY` is the world point at the top-left of the screen, so\n * `round(scroll * zoom) / zoom` snaps the view to whole screen pixels.\n */\nexport class Camera {\n zoom = 1;\n /** The zoom `reset` goes back to: the engine sets it from its `zoom` option (default 1). */\n defaultZoom = 1;\n /** Backing pixels per screen pixel (the renderer's resolution, `zoomResolution`): what `snap` rounds to. */\n resolution = 1;\n /** Lands the view on whole backing pixels (pixel art); off, the world moves smoothly between them. */\n roundPixels = false;\n scrollX = 0;\n scrollY = 0;\n width = 0;\n height = 0;\n private bounds: { x: number; y: number; width: number; height: number } | null = null;\n private shakeUntil = 0;\n private shakeMs = 1;\n private shakeAngle: number | undefined = undefined;\n private shakeIntensity = 0;\n private flashRect: Rectangle | null = null;\n private flashStart = 0;\n private flashMs = 1;\n private flashPeak = 1;\n /** Screen-layer containers laid over the world with its exact transform, shake included (see `follow`). */\n private readonly followers = new Set<Container>();\n private target: { readonly x: number; readonly y: number } | null = null;\n private trackOpts: Required<TrackOptions> = { lerp: 1, deadzoneWidth: 0, deadzoneHeight: 0, offsetX: 0, offsetY: 0 };\n private lastApply = -1;\n\n constructor(\n private readonly world: Container,\n private readonly screen: Container,\n ) {}\n\n get worldView(): ViewRect {\n const width = this.width / this.zoom;\n const height = this.height / this.zoom;\n return { x: this.scrollX, y: this.scrollY, width, height, right: this.scrollX + width, bottom: this.scrollY + height };\n }\n\n setZoom(z: number): this {\n this.zoom = z;\n return this;\n }\n\n /** A world coordinate snapped to the nearest backing pixel: one screen pixel, or a half at ×1.5. */\n snap(v: number): number {\n const k = this.zoom * this.resolution;\n return Math.round(v * k) / k;\n }\n\n setBounds(x: number, y: number, width: number, height: number): this {\n this.bounds = { x, y, width, height };\n return this;\n }\n\n /** Lets the view go anywhere again. */\n clearBounds(): this {\n this.bounds = null;\n return this;\n }\n\n /**\n * Back to how the engine made it: no target, no bounds, `defaultZoom`, scrolled to 0, no shake or flash. The camera\n * is the engine's, shared by every scene, so a level's target and bounds would otherwise hold over the next scene\n * (a title screen kept following a hero that no longer existed). `SceneManager.replace` calls it between the old\n * scenes leaving and the new one entering; `push` and `pop` leave the camera alone, since the scene under a pause\n * menu still wants its view when it comes back. Layers added with `follow` are kept: they belong to tools that\n * outlive scenes (a debug overlay), not to a scene.\n */\n reset(): this {\n this.target = null;\n this.bounds = null;\n this.zoom = this.defaultZoom;\n this.scrollX = 0;\n this.scrollY = 0;\n this.shakeUntil = 0;\n this.shakeIntensity = 0;\n if (this.flashRect && !this.flashRect.destroyed) this.flashRect.setVisible(false);\n return this;\n }\n\n /** The tracked target, if any. */\n get tracking(): { readonly x: number; readonly y: number } | null {\n return this.target;\n }\n\n /** The bounds the view is kept inside, if any. */\n get boundsRect(): { readonly x: number; readonly y: number; readonly width: number; readonly height: number } | null {\n return this.bounds;\n }\n\n centerOn(x: number, y: number): this {\n this.scrollX = x - this.width / this.zoom / 2;\n this.scrollY = y - this.height / this.zoom / 2;\n this.clamp();\n return this;\n }\n\n setScroll(x: number, y: number): this {\n this.scrollX = x;\n this.scrollY = y;\n this.clamp();\n return this;\n }\n\n /**\n * Screen shake: `intensity` is a fraction of the screen size, like before. With an `angle` (the way a blow travelled)\n * the shake runs back and forth along it instead of at random, so a hit is felt coming from somewhere.\n */\n shake(ms: number, intensity: number, angle?: number): void {\n const now = performance.now();\n if (now < this.shakeUntil && intensity < this.shakeIntensity) return;\n this.shakeUntil = now + ms;\n this.shakeMs = ms;\n this.shakeIntensity = intensity;\n this.shakeAngle = angle;\n }\n\n /**\n * Keeps a container on the screen layer placed exactly over the world layer — zoom, scroll and this frame's\n * shake — for things drawn in world units above the screen-layer night (fireflies). Written in `apply`, after\n * the scene updated, so it never lags the world by a frame or misses a shake the world takes.\n */\n follow(layer: Container): void {\n this.followers.add(layer);\n }\n\n unfollow(layer: Container): void {\n this.followers.delete(layer);\n }\n\n /** Full-screen colour flash starting at `peak` alpha and fading out over `ms`. */\n flash(ms: number, r: number, g: number, b: number, _force = false, peak = 1): void {\n if (!this.flashRect || this.flashRect.destroyed) {\n this.flashRect = new Rectangle(1, 1, 0xffffff).setOrigin(0, 0);\n this.screen.addChild(this.flashRect);\n }\n this.flashRect.resize(this.width, this.height).setFillStyle((r << 16) | (g << 8) | b).setAlpha(peak).setVisible(true);\n this.flashStart = performance.now();\n this.flashMs = ms;\n this.flashPeak = peak;\n }\n\n /**\n * Keeps `target` (anything with `x` and `y`, read every frame: an entity's position, a sprite) in view, inside the\n * bounds if there are any. Replaces any target before.\n */\n track(target: { readonly x: number; readonly y: number }, options: TrackOptions = {}): this {\n this.target = target;\n this.trackOpts = { lerp: 1, deadzoneWidth: 0, deadzoneHeight: 0, offsetX: 0, offsetY: 0, ...options };\n if ((options.lerp ?? 1) >= 1) this.centerOn(target.x + this.trackOpts.offsetX, target.y + this.trackOpts.offsetY);\n return this;\n }\n\n untrack(): this {\n this.target = null;\n return this;\n }\n\n /** Moves the view toward the tracked target for a frame `dtMs` long. */\n private follow1(dtMs: number): void {\n const t = this.target;\n if (!t) return;\n const o = this.trackOpts;\n const vw = this.width / this.zoom;\n const vh = this.height / this.zoom;\n const cx = this.scrollX + vw / 2;\n const cy = this.scrollY + vh / 2;\n const tx = t.x + o.offsetX;\n const ty = t.y + o.offsetY;\n // only the part of the target's offset outside the deadzone moves the camera\n const hx = o.deadzoneWidth / 2;\n const hy = o.deadzoneHeight / 2;\n const dx = tx - cx > hx ? tx - cx - hx : tx - cx < -hx ? tx - cx + hx : 0;\n const dy = ty - cy > hy ? ty - cy - hy : ty - cy < -hy ? ty - cy + hy : 0;\n const k = o.lerp >= 1 ? 1 : 1 - Math.pow(1 - Math.max(0, o.lerp), dtMs / (1000 / 60));\n this.scrollX += dx * k;\n this.scrollY += dy * k;\n this.clamp();\n }\n\n /** Writes the camera into the world layer; called once per frame after the scene updated. */\n apply(now: number): void {\n const dtMs = this.lastApply < 0 ? 1000 / 60 : Math.min(100, now - this.lastApply);\n this.lastApply = now;\n this.follow1(dtMs);\n let ox = 0;\n let oy = 0;\n if (now < this.shakeUntil) {\n const k = (this.shakeUntil - now) / this.shakeMs;\n if (this.shakeAngle !== undefined) {\n // along the blow: thrown the way it went, then rocking back, a little less each beat\n const swing = Math.cos(((now - (this.shakeUntil - this.shakeMs)) / 28) * Math.PI) * this.shakeIntensity * this.width * k;\n ox = Math.round(Math.cos(this.shakeAngle) * swing);\n oy = Math.round(Math.sin(this.shakeAngle) * swing);\n } else {\n ox = Math.round((Math.random() * 2 - 1) * this.shakeIntensity * this.width * k);\n oy = Math.round((Math.random() * 2 - 1) * this.shakeIntensity * this.height * k);\n }\n }\n this.world.scale.set(this.zoom);\n const k = this.zoom * this.resolution;\n const sx = this.roundPixels ? Math.round(this.scrollX * k) / this.resolution : this.scrollX * this.zoom;\n const sy = this.roundPixels ? Math.round(this.scrollY * k) / this.resolution : this.scrollY * this.zoom;\n this.world.position.set(-sx + ox, -sy + oy);\n for (const f of this.followers) {\n f.scale.set(this.zoom);\n f.position.copyFrom(this.world.position);\n }\n if (this.flashRect?.visible) {\n const t = (now - this.flashStart) / this.flashMs;\n if (t >= 1) this.flashRect.setVisible(false);\n else this.flashRect.alpha = this.flashPeak * (1 - t);\n }\n }\n\n private clamp(): void {\n const b = this.bounds;\n if (!b) return;\n const vw = this.width / this.zoom;\n const vh = this.height / this.zoom;\n this.scrollX = vw >= b.width ? b.x + (b.width - vw) / 2 : Math.min(Math.max(this.scrollX, b.x), b.x + b.width - vw);\n this.scrollY = vh >= b.height ? b.y + (b.height - vh) / 2 : Math.min(Math.max(this.scrollY, b.y), b.y + b.height - vh);\n }\n}\n"],"names":["Camera","world","screen","width","height","z","v","k","x","y","ms","intensity","angle","now","layer","r","g","b","_force","peak","Rectangle","target","options","dtMs","t","o","vw","vh","cx","cy","tx","ty","hx","hy","dx","dy","ox","oy","swing","sx","sy","f"],"mappings":"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"}
|
package/dist/core/Clock.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Clock.js","sources":["../../src/core/Clock.ts"],"sourcesContent":["interface Timer {\n at: number;\n fn: () => void;\n removed: boolean;\n}\n\n/** Scene time and one-shot timers, advanced by the frame loop. */\nexport class Clock {\n now = performance.now();\n private timers: Timer[] = [];\n\n delayedCall(ms: number, fn: () => void): { remove: () => void } {\n const t: Timer = { at: this.now + ms, fn, removed: false };\n this.timers.push(t);\n return { remove: () => void (t.removed = true) };\n }\n\n update(now: number): void {\n this.now = now;\n const due = this.timers.filter((t) => !t.removed && t.at <= now);\n if (!due.length) return;\n this.timers = this.timers.filter((t) => !t.removed && t.at > now);\n for (const t of due) {\n // one timer that throws does not take the others due this frame with it\n try {\n t.fn();\n } catch (err) {\n console.error('[timer] failed', err);\n }\n }\n }\n\n clear(): void {\n this.timers = [];\n }\n}\n"],"names":["Clock","ms","fn","t","now","due","err"],"mappings":"AAOO,MAAMA,EAAM;AAAA,EACjB,MAAM,YAAY,IAAA;AAAA,EACV,SAAkB,CAAA;AAAA,EAE1B,YAAYC,GAAYC,GAAwC;AAC9D,UAAMC,IAAW,EAAE,IAAI,KAAK,MAAMF,GAAI,IAAAC,GAAI,SAAS,GAAA;AACnD,gBAAK,OAAO,KAAKC,CAAC,GACX,EAAE,QAAQ,MAAA;AAAM,MAAMA,EAAE,UAAU;AAAA,MAAA;AAAA,EAC3C;AAAA,EAEA,OAAOC,GAAmB;AACxB,SAAK,MAAMA;AACX,UAAMC,IAAM,KAAK,OAAO,OAAO,CAACF,MAAM,CAACA,EAAE,WAAWA,EAAE,MAAMC,CAAG;AAC/D,QAAKC,EAAI,QACT;AAAA,WAAK,SAAS,KAAK,OAAO,OAAO,CAACF,MAAM,CAACA,EAAE,WAAWA,EAAE,KAAKC,CAAG;AAChE,iBAAWD,KAAKE;AAEd,YAAI;AACF,UAAAF,EAAE,GAAA;AAAA,QACJ,SAASG,GAAK;AACZ,kBAAQ,MAAM,kBAAkBA,CAAG;AAAA,QACrC;AAAA;AAAA,EAEJ;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS,CAAA;AAAA,EAChB;AACF;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Graphics.js","sources":["../../src/core/Graphics.ts"],"sourcesContent":["import { Container, Graphics as PixiGraphics } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\nexport interface Vec2 {\n x: number;\n y: number;\n}\n\n/**\n * Immediate-mode vector drawing with a current fill and line style (fillStyle + fillRect), on top of Pixi's\n * shape-then-fill Graphics. Wrapped rather than subclassed: Pixi's Graphics owns `fillStyle` as a property.\n */\nexport class Graphics extends GameObject(Container) {\n private readonly g = new PixiGraphics();\n private fill = { color: 0xffffff, alpha: 1 };\n private line = { width: 1, color: 0xffffff, alpha: 1 };\n\n constructor() {\n super();\n this.addChild(this.g);\n }\n\n fillStyle(color: number, alpha = 1): this {\n this.fill = { color, alpha };\n return this;\n }\n\n lineStyle(width: number, color: number, alpha = 1): this {\n this.line = { width, color, alpha };\n return this;\n }\n\n clear(): this {\n this.g.clear();\n return this;\n }\n\n fillRect(x: number, y: number, w: number, h: number): this {\n this.g.rect(x, y, w, h).fill(this.fill);\n return this;\n }\n\n fillEllipse(x: number, y: number, w: number, h: number): this {\n this.g.ellipse(x, y, w / 2, h / 2).fill(this.fill);\n return this;\n }\n\n strokeEllipse(x: number, y: number, w: number, h: number): this {\n this.g.ellipse(x, y, w / 2, h / 2).stroke(this.line);\n return this;\n }\n\n fillCircle(x: number, y: number, r: number): this {\n this.g.circle(x, y, r).fill(this.fill);\n return this;\n }\n\n strokeCircle(x: number, y: number, r: number): this {\n this.g.circle(x, y, r).stroke(this.line);\n return this;\n }\n\n lineBetween(x1: number, y1: number, x2: number, y2: number): this {\n this.g.moveTo(x1, y1).lineTo(x2, y2).stroke(this.line);\n return this;\n }\n\n fillPoints(points: Vec2[], _closeShape = true): this {\n this.g.poly(points.flatMap((p) => [p.x, p.y]), true).fill(this.fill);\n return this;\n }\n\n strokePoints(points: Vec2[], closeShape = false): this {\n this.g.poly(points.flatMap((p) => [p.x, p.y]), closeShape).stroke(this.line);\n return this;\n }\n\n /** A pie slice from the centre, for cone telegraphs. */\n slice(x: number, y: number, r: number, start: number, end: number, anticlockwise = false): this {\n this.g.moveTo(x, y).arc(x, y, r, start, end, anticlockwise).closePath();\n return this;\n }\n\n beginPath(): this {\n this.g.beginPath();\n return this;\n }\n\n moveTo(x: number, y: number): this {\n this.g.moveTo(x, y);\n return this;\n }\n\n lineTo(x: number, y: number): this {\n this.g.lineTo(x, y);\n return this;\n }\n\n closePath(): this {\n this.g.closePath();\n return this;\n }\n\n fillPath(): this {\n this.g.fill(this.fill);\n return this;\n }\n\n strokePath(): this {\n this.g.stroke(this.line);\n return this;\n }\n}\n"],"names":["Graphics","GameObject","Container","PixiGraphics","color","alpha","width","x","y","w","h","r","x1","y1","x2","y2","points","_closeShape","p","closeShape","start","end","anticlockwise"],"mappings":"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"}
|
package/dist/core/Group.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Group.js","sources":["../../src/core/Group.ts"],"sourcesContent":["import { Container, type Container as PixiContainer } from 'pixi.js';\nimport { GameObject } from './gameObject';\n\n/** A container of world objects, drawn in list order (children do not sort by depth). */\nexport class Group extends GameObject(Container) {\n get list(): PixiContainer[] {\n return this.children;\n }\n\n add(child: PixiContainer | PixiContainer[]): this {\n for (const c of Array.isArray(child) ? child : [child]) this.addChild(c);\n return this;\n }\n\n addAt(child: PixiContainer, index: number): this {\n this.addChildAt(child, Math.min(index, this.children.length));\n return this;\n }\n\n remove(child: PixiContainer, destroy = false): this {\n if (child.parent === this) this.removeChild(child);\n if (destroy) child.destroy({ children: true, context: true });\n return this;\n }\n\n /** Puts `child` directly below `other` in the draw order. */\n moveBelow(child: PixiContainer, other: PixiContainer): this {\n if (child.parent !== this || other.parent !== this || child === other) return this;\n this.removeChild(child);\n this.addChildAt(child, this.getChildIndex(other));\n return this;\n }\n\n /** Puts `child` directly above `other` in the draw order. */\n moveAbove(child: PixiContainer, other: PixiContainer): this {\n if (child.parent !== this || other.parent !== this || child === other) return this;\n this.removeChild(child);\n this.addChildAt(child, this.getChildIndex(other) + 1);\n return this;\n }\n}\n"],"names":["Group","GameObject","Container","child","c","index","destroy","other"],"mappings":";;AAIO,MAAMA,UAAcC,EAAWC,CAAS,EAAE;AAAA,EAC/C,IAAI,OAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAIC,GAA8C;AAChD,eAAWC,KAAK,MAAM,QAAQD,CAAK,IAAIA,IAAQ,CAACA,CAAK,EAAG,MAAK,SAASC,CAAC;AACvE,WAAO;AAAA,EACT;AAAA,EAEA,MAAMD,GAAsBE,GAAqB;AAC/C,gBAAK,WAAWF,GAAO,KAAK,IAAIE,GAAO,KAAK,SAAS,MAAM,CAAC,GACrD;AAAA,EACT;AAAA,EAEA,OAAOF,GAAsBG,IAAU,IAAa;AAClD,WAAIH,EAAM,WAAW,QAAM,KAAK,YAAYA,CAAK,GAC7CG,OAAe,QAAQ,EAAE,UAAU,IAAM,SAAS,IAAM,GACrD;AAAA,EACT;AAAA;AAAA,EAGA,UAAUH,GAAsBI,GAA4B;AAC1D,WAAIJ,EAAM,WAAW,QAAQI,EAAM,WAAW,QAAQJ,MAAUI,IAAc,QAC9E,KAAK,YAAYJ,CAAK,GACtB,KAAK,WAAWA,GAAO,KAAK,cAAcI,CAAK,CAAC,GACzC;AAAA,EACT;AAAA;AAAA,EAGA,UAAUJ,GAAsBI,GAA4B;AAC1D,WAAIJ,EAAM,WAAW,QAAQI,EAAM,WAAW,QAAQJ,MAAUI,IAAc,QAC9E,KAAK,YAAYJ,CAAK,GACtB,KAAK,WAAWA,GAAO,KAAK,cAAcI,CAAK,IAAI,CAAC,GAC7C;AAAA,EACT;AACF;"}
|