lantern-engine 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/dist/assets/takeCleanerWorker-DTcI_Jqr.js +31 -0
  5. package/dist/assets/takeCleanerWorker-DTcI_Jqr.js.map +1 -0
  6. package/dist/constants-CjYVdFid.js +252 -0
  7. package/dist/constants-CjYVdFid.js.map +1 -0
  8. package/dist/lantern-engine.js +3473 -0
  9. package/dist/lantern-engine.js.map +1 -0
  10. package/dist/pixel.js +1404 -0
  11. package/dist/pixel.js.map +1 -0
  12. package/dist/types/anim/Animator.d.ts +50 -0
  13. package/dist/types/anim/Animator.d.ts.map +1 -0
  14. package/dist/types/anim/animationSystem.d.ts +13 -0
  15. package/dist/types/anim/animationSystem.d.ts.map +1 -0
  16. package/dist/types/assets/AssetLoader.d.ts +85 -0
  17. package/dist/types/assets/AssetLoader.d.ts.map +1 -0
  18. package/dist/types/audio/MusicPlayer.d.ts +57 -0
  19. package/dist/types/audio/MusicPlayer.d.ts.map +1 -0
  20. package/dist/types/audio/SoundEngine.d.ts +42 -0
  21. package/dist/types/audio/SoundEngine.d.ts.map +1 -0
  22. package/dist/types/audio/cleanTake.d.ts +3 -0
  23. package/dist/types/audio/cleanTake.d.ts.map +1 -0
  24. package/dist/types/audio/sampleBank.d.ts +27 -0
  25. package/dist/types/audio/sampleBank.d.ts.map +1 -0
  26. package/dist/types/audio/soundTable.d.ts +14 -0
  27. package/dist/types/audio/soundTable.d.ts.map +1 -0
  28. package/dist/types/audio/synth.d.ts +34 -0
  29. package/dist/types/audio/synth.d.ts.map +1 -0
  30. package/dist/types/audio/takeCleanerWorker.d.ts +2 -0
  31. package/dist/types/audio/takeCleanerWorker.d.ts.map +1 -0
  32. package/dist/types/core/BitmapLabel.d.ts +26 -0
  33. package/dist/types/core/BitmapLabel.d.ts.map +1 -0
  34. package/dist/types/core/Camera.d.ts +90 -0
  35. package/dist/types/core/Camera.d.ts.map +1 -0
  36. package/dist/types/core/Clock.d.ts +11 -0
  37. package/dist/types/core/Clock.d.ts.map +1 -0
  38. package/dist/types/core/Graphics.d.ts +37 -0
  39. package/dist/types/core/Graphics.d.ts.map +1 -0
  40. package/dist/types/core/Group.d.ts +15 -0
  41. package/dist/types/core/Group.d.ts.map +1 -0
  42. package/dist/types/core/Image.d.ts +25 -0
  43. package/dist/types/core/Image.d.ts.map +1 -0
  44. package/dist/types/core/ObjectFactory.d.ts +34 -0
  45. package/dist/types/core/ObjectFactory.d.ts.map +1 -0
  46. package/dist/types/core/Pointer.d.ts +27 -0
  47. package/dist/types/core/Pointer.d.ts.map +1 -0
  48. package/dist/types/core/Rectangle.d.ts +26 -0
  49. package/dist/types/core/Rectangle.d.ts.map +1 -0
  50. package/dist/types/core/Stage.d.ts +58 -0
  51. package/dist/types/core/Stage.d.ts.map +1 -0
  52. package/dist/types/core/Text.d.ts +22 -0
  53. package/dist/types/core/Text.d.ts.map +1 -0
  54. package/dist/types/core/TextureRegistry.d.ts +51 -0
  55. package/dist/types/core/TextureRegistry.d.ts.map +1 -0
  56. package/dist/types/core/Tweener.d.ts +53 -0
  57. package/dist/types/core/Tweener.d.ts.map +1 -0
  58. package/dist/types/core/atlas.d.ts +16 -0
  59. package/dist/types/core/atlas.d.ts.map +1 -0
  60. package/dist/types/core/atlasPlan.d.ts +34 -0
  61. package/dist/types/core/atlasPlan.d.ts.map +1 -0
  62. package/dist/types/core/eases.d.ts +3 -0
  63. package/dist/types/core/eases.d.ts.map +1 -0
  64. package/dist/types/core/gameObject.d.ts +42 -0
  65. package/dist/types/core/gameObject.d.ts.map +1 -0
  66. package/dist/types/core/pixiPatches.d.ts +9 -0
  67. package/dist/types/core/pixiPatches.d.ts.map +1 -0
  68. package/dist/types/core/types.d.ts +4 -0
  69. package/dist/types/core/types.d.ts.map +1 -0
  70. package/dist/types/ecs/SparseSet.d.ts +25 -0
  71. package/dist/types/ecs/SparseSet.d.ts.map +1 -0
  72. package/dist/types/ecs/World.d.ts +133 -0
  73. package/dist/types/ecs/World.d.ts.map +1 -0
  74. package/dist/types/ecs/fields.d.ts +34 -0
  75. package/dist/types/ecs/fields.d.ts.map +1 -0
  76. package/dist/types/engine/Engine.d.ts +80 -0
  77. package/dist/types/engine/Engine.d.ts.map +1 -0
  78. package/dist/types/engine/FixedLoop.d.ts +32 -0
  79. package/dist/types/engine/FixedLoop.d.ts.map +1 -0
  80. package/dist/types/engine/Scene.d.ts +37 -0
  81. package/dist/types/engine/Scene.d.ts.map +1 -0
  82. package/dist/types/engine/SceneManager.d.ts +31 -0
  83. package/dist/types/engine/SceneManager.d.ts.map +1 -0
  84. package/dist/types/events/EventBus.d.ts +20 -0
  85. package/dist/types/events/EventBus.d.ts.map +1 -0
  86. package/dist/types/fx/hitStop.d.ts +20 -0
  87. package/dist/types/fx/hitStop.d.ts.map +1 -0
  88. package/dist/types/fx/viewCull.d.ts +14 -0
  89. package/dist/types/fx/viewCull.d.ts.map +1 -0
  90. package/dist/types/index.d.ts +68 -0
  91. package/dist/types/index.d.ts.map +1 -0
  92. package/dist/types/input/Actions.d.ts +75 -0
  93. package/dist/types/input/Actions.d.ts.map +1 -0
  94. package/dist/types/input/Keyboard.d.ts +25 -0
  95. package/dist/types/input/Keyboard.d.ts.map +1 -0
  96. package/dist/types/input/aimAxis.d.ts +9 -0
  97. package/dist/types/input/aimAxis.d.ts.map +1 -0
  98. package/dist/types/input/longPress.d.ts +21 -0
  99. package/dist/types/input/longPress.d.ts.map +1 -0
  100. package/dist/types/input/stickAxis.d.ts +26 -0
  101. package/dist/types/input/stickAxis.d.ts.map +1 -0
  102. package/dist/types/input/touchDetect.d.ts +18 -0
  103. package/dist/types/input/touchDetect.d.ts.map +1 -0
  104. package/dist/types/light/LightLayer.d.ts +65 -0
  105. package/dist/types/light/LightLayer.d.ts.map +1 -0
  106. package/dist/types/net/Connection.d.ts +66 -0
  107. package/dist/types/net/Connection.d.ts.map +1 -0
  108. package/dist/types/net/InterpolationBuffer.d.ts +36 -0
  109. package/dist/types/net/InterpolationBuffer.d.ts.map +1 -0
  110. package/dist/types/net/PredictedMover.d.ts +43 -0
  111. package/dist/types/net/PredictedMover.d.ts.map +1 -0
  112. package/dist/types/net/Room.d.ts +69 -0
  113. package/dist/types/net/Room.d.ts.map +1 -0
  114. package/dist/types/net/RoomHost.d.ts +74 -0
  115. package/dist/types/net/RoomHost.d.ts.map +1 -0
  116. package/dist/types/net/cadence.d.ts +22 -0
  117. package/dist/types/net/cadence.d.ts.map +1 -0
  118. package/dist/types/net/constants.d.ts +24 -0
  119. package/dist/types/net/constants.d.ts.map +1 -0
  120. package/dist/types/net/movement.d.ts +50 -0
  121. package/dist/types/net/movement.d.ts.map +1 -0
  122. package/dist/types/net/protocol.d.ts +59 -0
  123. package/dist/types/net/protocol.d.ts.map +1 -0
  124. package/dist/types/net/serverClock.d.ts +14 -0
  125. package/dist/types/net/serverClock.d.ts.map +1 -0
  126. package/dist/types/net/snapshotCodec.d.ts +55 -0
  127. package/dist/types/net/snapshotCodec.d.ts.map +1 -0
  128. package/dist/types/pixel/Darkness.d.ts +78 -0
  129. package/dist/types/pixel/Darkness.d.ts.map +1 -0
  130. package/dist/types/pixel/Evaporation.d.ts +30 -0
  131. package/dist/types/pixel/Evaporation.d.ts.map +1 -0
  132. package/dist/types/pixel/PixelLayer.d.ts +85 -0
  133. package/dist/types/pixel/PixelLayer.d.ts.map +1 -0
  134. package/dist/types/pixel/index.d.ts +22 -0
  135. package/dist/types/pixel/index.d.ts.map +1 -0
  136. package/dist/types/pixel/lights.d.ts +34 -0
  137. package/dist/types/pixel/lights.d.ts.map +1 -0
  138. package/dist/types/pixel/palette.d.ts +54 -0
  139. package/dist/types/pixel/palette.d.ts.map +1 -0
  140. package/dist/types/pixel/pixel.d.ts +80 -0
  141. package/dist/types/pixel/pixel.d.ts.map +1 -0
  142. package/dist/types/pixel/pixelPaint.d.ts +43 -0
  143. package/dist/types/pixel/pixelPaint.d.ts.map +1 -0
  144. package/dist/types/pixel/pixelTone.d.ts +49 -0
  145. package/dist/types/pixel/pixelTone.d.ts.map +1 -0
  146. package/dist/types/pixel/propKit.d.ts +26 -0
  147. package/dist/types/pixel/propKit.d.ts.map +1 -0
  148. package/dist/types/pixel/ramp.d.ts +31 -0
  149. package/dist/types/pixel/ramp.d.ts.map +1 -0
  150. package/dist/types/pixel/selout.d.ts +3 -0
  151. package/dist/types/pixel/selout.d.ts.map +1 -0
  152. package/dist/types/pixel/spritePixels.d.ts +29 -0
  153. package/dist/types/pixel/spritePixels.d.ts.map +1 -0
  154. package/dist/types/platform/touch.d.ts +5 -0
  155. package/dist/types/platform/touch.d.ts.map +1 -0
  156. package/dist/types/rig/PosePlayer.d.ts +64 -0
  157. package/dist/types/rig/PosePlayer.d.ts.map +1 -0
  158. package/dist/types/rig/RigView.d.ts +68 -0
  159. package/dist/types/rig/RigView.d.ts.map +1 -0
  160. package/dist/types/rig/Skeleton.d.ts +14 -0
  161. package/dist/types/rig/Skeleton.d.ts.map +1 -0
  162. package/dist/types/rig/humanoid.d.ts +4 -0
  163. package/dist/types/rig/humanoid.d.ts.map +1 -0
  164. package/dist/types/rig/humanoidPoses.d.ts +117 -0
  165. package/dist/types/rig/humanoidPoses.d.ts.map +1 -0
  166. package/dist/types/rig/poseMath.d.ts +14 -0
  167. package/dist/types/rig/poseMath.d.ts.map +1 -0
  168. package/dist/types/rig/rigTypes.d.ts +82 -0
  169. package/dist/types/rig/rigTypes.d.ts.map +1 -0
  170. package/dist/types/settings.d.ts +25 -0
  171. package/dist/types/settings.d.ts.map +1 -0
  172. package/dist/types/tilemap/TilemapView.d.ts +42 -0
  173. package/dist/types/tilemap/TilemapView.d.ts.map +1 -0
  174. package/dist/types/tilemap/collision.d.ts +16 -0
  175. package/dist/types/tilemap/collision.d.ts.map +1 -0
  176. package/dist/types/tilemap/tiled.d.ts +104 -0
  177. package/dist/types/tilemap/tiled.d.ts.map +1 -0
  178. package/package.json +82 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,101 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are written here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
+ and versions follow [Semantic Versioning](https://semver.org/); before 1.0 a minor version may break the API.
5
+
6
+ ## Unreleased
7
+
8
+ ## 0.4.0
9
+
10
+ ### Changed
11
+
12
+ - Node 22 or later (Node 20 reached its end of life; Pixi reads `navigator`, which Node 20 lacks, as it loads).
13
+
14
+ ### Added
15
+
16
+ - A documentation site (VitePress): home, guide, an API reference generated from the source by TypeDoc, the changelog,
17
+ and the examples running live; `npm run docs:dev` / `docs:build`, published by a GitHub Pages workflow.
18
+ - Examples `pixel-dungeon` (dithered torchlight, a carried light, evaporating slimes) and `ecs-stress` (tens of
19
+ thousands of bodies with the frame rate and step time).
20
+ - Types the public API refers to are exported too: `ComponentValues`, `ArrayOf`, `SettingsListener`, `Tween`,
21
+ `TweenConfig`, `TweenTarget`, `EaseFn`, `Vec2`, `AtlasPlan`, `AtlasPage`, `CameraLike`, `SoundEngine`,
22
+ `SoundOptions`, `TiledPropertyValue`; `PackedPixels` is exported as a class from the pixel module.
23
+
24
+ ## 0.3.0
25
+
26
+ ### Added
27
+
28
+ - Characters: `RigView` draws a rig from cut-out images or vector shapes (origin at the feet, mirrored by `facing`,
29
+ joints readable for held items); `PosePlayer` blends key poses with crossfades between clips; `HUMANOID_POSES` (72)
30
+ and `HUMANOID_CLIPS`.
31
+ - `LightLayer`: smooth darkness with soft lights, colour washes and flicker.
32
+ - `music` / `MusicPlayer`: streamed tracks, crossfaded, looping, at the music volume, iOS-safe.
33
+ - Multiplayer: the netcode protocol, `PredictedMover` (prediction and reconciliation), `InterpolationBuffer`, an
34
+ authoritative `Room` with input validation and flood limits, `RoomHost` for any Node WebSocket library, and a
35
+ client `Connection` with batching, clock sync and reconnection. Tested end to end with simulated latency and jitter,
36
+ and through real sockets in the example.
37
+ - Examples `night-garden` (smooth art, rig, lights) and `multiplayer` (`npm run example:server`).
38
+
39
+ ## 0.2.0
40
+
41
+ The engine is now **Lantern Engine** (`lantern-engine`), for 2D games in any art style; the pixel-art tools moved to
42
+ their own module, **Dither** (`lantern-engine/pixel`).
43
+
44
+ ### Breaking
45
+
46
+ - Pixel-art exports moved to `lantern-engine/pixel`: `PixelLayer`, `pixelPaint`, `pixelTone`, the colour and pixel-grid
47
+ helpers, `PALETTE`, the prop kit, `ramp`/`selectiveOutline`, `Darkness` and the transient lights, `Evaporation`,
48
+ `spritePixels`.
49
+ - `stage.add.pixels()` is gone: use `addPixelLayer(stage)` from the pixel module, or `stage.add.existing(obj)` for any
50
+ object made elsewhere.
51
+ - `EngineOptions.pixelArt` (default on) is now `pixelPerfect` (default off). Off, textures are smooth, antialiased and
52
+ drawn at the screen's density; on, they are sharp, at one backing pixel per CSS pixel, and the camera lands on
53
+ whole pixels (`Camera.roundPixels`).
54
+ - `createCollisionGrid`'s fifth argument is an options object (`ground`, `shotOverTiles`, `bodyHalfWidth`,
55
+ `bodyHalfHeight`); `setShotOverTiles` is gone.
56
+
57
+ ### Added
58
+
59
+ - `AssetLoader`: images, sprite sheets, JSON, text and Tiled maps with their tileset images, progress, a concurrency
60
+ limit, errors that name the URL.
61
+ - Tiled maps: `parseTiled` (CSV and base64 layers, groups, objects, properties, flips, tile animations; clear refusals
62
+ for what is not supported), `collisionFromTiled` (a layer or a tile property), `TilemapView` (layers drawn in culled
63
+ chunks, each at its own depth among the bodies).
64
+ - `Animator` (fps or per-frame durations, loop or finish, speed) and `animationSystem` with `SpriteComponent` and
65
+ `AnimationComponent`.
66
+ - `Actions`: named input bound to keys, gamepad buttons and sticks (with a dead zone), and virtual touch buttons; per-step
67
+ edges, analog values, rebinding.
68
+ - `Camera.track`: a soft follow that is the same at any frame rate, a deadzone, an offset, the bounds kept.
69
+ - Example `top-down` with a Tiled map, written by `examples/top-down/make-assets.mjs`.
70
+ - `world.each(A, B, fn)`: walks without allocating a row per entity.
71
+ - `defineFields(name, { x: 'f32', … })`: numeric components kept in typed-array columns (`world.columns`).
72
+ - `world.slots(A, B)`: the matching slot indices as a cached `Int32Array`, rebuilt only when membership changes.
73
+ With field components a step over 50,000 bodies takes 0.1 ms, 200 times the first release.
74
+ - `world.count(type)`, `entityIndex(e)`, `npm run bench`.
75
+
76
+ ### Changed
77
+
78
+ - `World` stores components in sparse sets instead of maps: a query over 50,000 bodies went from 21 ms to 2 ms a
79
+ step, and spawning and destroying makes a third of the garbage. Entity ids are recycled with a generation, so a
80
+ stale id never matches the entity that reuses its slot. Entities are walked last-added first.
81
+ - Removing a component while a walk is in progress is deferred like destroying an entity, so nothing is skipped.
82
+
83
+ ## 0.1.0 (as Dither Engine)
84
+
85
+ First release, split out of a shipped game's client.
86
+
87
+ ### Added
88
+
89
+ - `Engine`, `Scene`, `SceneManager` (replace, push, pop, async `onEnter`) and `FixedLoop` (fixed-step simulation with
90
+ interpolation and a cap on catch-up steps).
91
+ - `World` ECS: typed components, queries, `fixed` and `frame` systems, deferred destruction, error isolation.
92
+ - `EventBus` with `once`, `off`, `listenerCount` and listener error isolation.
93
+ - Rendering core: `Stage`, `Camera`, `PixelLayer`, display objects, tweens, timers, bitmap labels, texture atlas.
94
+ - Pixel effects: `pixelPaint`, `pixelTone`, `Evaporation`, hit-stop and recoil curves.
95
+ - Lighting: `Darkness` with dithered lamps and transient lights.
96
+ - Skeleton rig, pose blending, material ramps, selective outline and `HUMANOID_RIG`.
97
+ - Audio: `sound`, synthesized recipes (`registerSounds`), recorded takes (`registerTakes`).
98
+ - Input: `Keyboard`, stick maths, long press, touch detection.
99
+ - Networking: deterministic `stepMover` with a configurable body box, binary snapshot codec, cadence rules, server
100
+ clock.
101
+ - Example: `walk-room`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phadec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,114 @@
1
- # Temporary Holding Version
1
+ # Lantern Engine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/Phadec/lantern-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/Phadec/lantern-engine/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/Phadec/lantern-engine/actions/workflows/docs.yml/badge.svg)](https://phadec.github.io/lantern-engine/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ A 2D game engine for the web, built on [PixiJS 8](https://pixijs.com) and written in TypeScript, for games in any
8
+ art style: hand-drawn, HD, vector or pixel art. Pixel art gets a module of its own, **Dither**.
9
+
10
+ It grew out of a shipped online action RPG, so its pieces were made for a real game under real load: a fast ECS,
11
+ a fixed-step simulation a server can replay **bit for bit**, Tiled maps, input that works the same on keyboard,
12
+ gamepad and touch, and for pixel art, effects painted on the world's own pixel grid and dithered lighting.
13
+
14
+ ```ts
15
+ import { Engine, Scene, defineComponent, system } from 'lantern-engine';
16
+
17
+ const Position = defineComponent<{ x: number; y: number }>('Position');
18
+
19
+ class Level extends Scene {
20
+ override onEnter() {
21
+ this.world.spawn([Position, { x: 0, y: 0 }]);
22
+ this.world.addSystem(
23
+ system('drift', 'fixed', ({ world, dt }) => {
24
+ world.each(Position, (_e, p) => (p.x += 30 * dt));
25
+ }),
26
+ );
27
+ }
28
+ }
29
+
30
+ const engine = await Engine.create({ parent: document.body });
31
+ await engine.scenes.replace(new Level());
32
+ ```
33
+
34
+ > **Status: 0.x, early.** The API will change between minor versions until 1.0. See [CHANGELOG.md](CHANGELOG.md).
35
+
36
+ ## Two entry points
37
+
38
+ | Import | For | Holds |
39
+ |---|---|---|
40
+ | `lantern-engine` | every 2D game | engine, scenes, ECS, rendering core, assets, Tiled maps, animation, input, camera, rig maths, audio, networking |
41
+ | `lantern-engine/pixel` | pixel-art games | `PixelLayer`, `pixelPaint` shapes, colour ramps, dithered `Darkness` lighting, `Evaporation`, sprite pixel tools |
42
+
43
+ The core never imports the pixel module (a test holds it to that), so a game that does not use it does not ship it.
44
+ Start a pixel-art game with `Engine.create({ pixelPerfect: true })`: sharp textures and a camera that lands on whole
45
+ pixels. Without it, textures are smooth and drawn at the screen's own density.
46
+
47
+ ## Features
48
+
49
+ | Area | What you get |
50
+ |---|---|
51
+ | **Engine** | `Engine` (canvas, renderer, frame loop), a stack of `Scene`s (`replace`, `push`, `pop`, async loading), a fixed-step `FixedLoop` with interpolation and spiral-of-death protection, typed `EventBus`. |
52
+ | **ECS** | `World` on sparse sets: typed components, `query`, allocation-free `each`, numeric components in typed-array columns (`defineFields`) walked through a cached `slots()` list — a step over 50,000 bodies in 0.1 ms. Systems in `fixed` and `frame` phases, recycled ids with generations, error isolation. |
53
+ | **Assets** | `AssetLoader`: images, sprite sheets, JSON, text and Tiled maps with their tilesets, with progress and a concurrency limit. |
54
+ | **Tilemaps** | Tiled JSON maps (CSV or base64 layers, groups, objects, properties, flipped tiles), drawn in culled chunks by `TilemapView`, with collision from a layer or a tile property. |
55
+ | **Animation** | `Animator` clips (fps or per-frame durations, looping or finishing) and an animation system for sprites. |
56
+ | **Input** | `Actions`: named actions bound to keys, gamepad buttons and sticks, and on-screen touch buttons, with per-step edges and analog values. `Keyboard`, stick maths, long press, touch detection. |
57
+ | **Camera** | Zoom, bounds, shake, flash, and `track` with a soft follow that is the same at any frame rate, a deadzone and an offset. |
58
+ | **Rendering** | `Stage` with world and screen layers, display objects, tweens, timers, bitmap labels, a texture atlas that splits pages by the GPU's limit, guards that keep one bad frame from stopping the loop. |
59
+ | **Characters** | A 2D skeleton rig drawn by `RigView` from cut-out images or vector shapes, `PosePlayer` blending key poses with crossfades, and a built-in humanoid with 72 poses and ready clips (idle, walk, run, roll, slash, thrust, cast, throw, emotes). |
60
+ | **Light** | `LightLayer`: smooth darkness with soft radial lights, colour washes and flicker (the pixel module has its own dithered `Darkness`). |
61
+ | **Audio** | Web Audio `sound` engine (synthesized recipes, recorded takes cleaned in a worker, voice limiting, distance muffling, panning) and `music`: streamed tracks crossfaded and looped. iOS unlock handled. |
62
+ | **Multiplayer** | An authoritative `Room` and its socket host for Node, a client `Connection`, `PredictedMover` (prediction and reconciliation over deterministic movement: in tests and in the example the server never has to correct it), `InterpolationBuffer` for everyone else, binary snapshots, clock sync, input validation and flood limits, cadence rules. |
63
+ | **Pixel art** (`/pixel`) | `PixelLayer` (thousands of pixel rects in one pooled texture), `pixelPaint` shapes shaded from hue-shifted ramps and faded by ordered dither, `Darkness` with dithered lamps and transient lights, `Evaporation`, the selective outline. |
64
+
65
+ ## Install
66
+
67
+ ```sh
68
+ npm install lantern-engine pixi.js
69
+ ```
70
+
71
+ `pixi.js` (8.6 or later) is a peer dependency. The package is ESM only and ships its TypeScript types. On a server
72
+ (the multiplayer `Room`) it needs Node 22 or later.
73
+
74
+ The engine renders with **WebGL 2** by default. `Engine.create({ preference: 'webgpu' })` opts into WebGPU, which
75
+ is experimental here: it falls back to WebGL where the browser lacks it, but has not been profiled the way the WebGL
76
+ path has.
77
+
78
+ ## Documentation
79
+
80
+ **https://phadec.github.io/lantern-engine/** — the guide, the API reference and the examples running live.
81
+
82
+ The documentation site (guide, API reference generated from the source, live examples) is built with
83
+ `npm run docs:build` and published to GitHub Pages by `.github/workflows/docs.yml`; `npm run docs:dev` serves it
84
+ locally.
85
+
86
+ - [Getting started](docs/guide/getting-started.md): an engine, a scene, entities and systems, assets, a Tiled map, input,
87
+ the camera, a rigged character, light and music, and multiplayer, step by step.
88
+ - [Architecture](docs/guide/architecture.md): how the frame loop, scenes, ECS and rendering fit together, and the rules that keep it fast.
89
+ - [Examples](examples/), run with `npm run dev`:
90
+ - `top-down`: a Tiled map, asset loading, an animated hero, input actions on keyboard, gamepad and touch, camera follow.
91
+ - `night-garden`: smooth vector art, a rigged hero animated by blended poses, a lantern and fireflies in the dark.
92
+ - `pixel-dungeon`: the pixel module: dithered torchlight, a carried light, slimes that evaporate into their own pixels.
93
+ - `ecs-stress`: tens of thousands of bodies on typed-array components, with the frame rate and step time.
94
+ - `multiplayer`: `npm run example:server`, then two tabs: prediction, reconciliation and interpolation.
95
+ - `walk-room`: the smallest whole game, with pixel effects.
96
+
97
+ ## Develop
98
+
99
+ ```sh
100
+ npm install
101
+ npm run dev # examples at http://127.0.0.1:5180/
102
+ npm test # unit tests under node
103
+ npm run typecheck # sources and tests
104
+ npm run bench # ECS benchmark
105
+ npm run build # dist/: both ESM bundles and their .d.ts files
106
+ npm run check # typecheck, test and build, as CI runs it
107
+ npm run docs:dev # the documentation site, with the API reference and examples
108
+ ```
109
+
110
+ Contributions are welcome: read [CONTRIBUTING.md](CONTRIBUTING.md) first.
111
+
112
+ ## License
113
+
114
+ [MIT](LICENSE)
@@ -0,0 +1,31 @@
1
+ function m(l, h) {
2
+ const a = l[0]?.length ?? 0, f = 1 / Math.max(1, l.length), e = new Float32Array(a);
3
+ for (const t of l) for (let n = 0; n < a; n++) e[n] += t[n] * f;
4
+ let r = 0;
5
+ for (let t = 0; t < a; t++) {
6
+ const n = Math.abs(e[t]);
7
+ n > r && (r = n);
8
+ }
9
+ if (r < 5e-3) return null;
10
+ const i = r * 0.01;
11
+ let s = 0;
12
+ for (; s < a && Math.abs(e[s]) < i; ) s++;
13
+ let c = a;
14
+ for (; c > s && Math.abs(e[c - 1]) < i; ) c--;
15
+ if (c <= s) return null;
16
+ s = Math.max(0, s - Math.round(2e-3 * h));
17
+ const o = e.slice(s, c);
18
+ let M = 0;
19
+ for (let t = 0; t < o.length; t++) M += o[t] * o[t];
20
+ const d = Math.sqrt(M / o.length), g = Math.min(0.16 / Math.max(d, 1e-6), 0.95 / r), E = Math.min(o.length, Math.round(0.01 * h));
21
+ for (let t = 0; t < o.length; t++) {
22
+ const n = o.length - t;
23
+ o[t] *= g * (n < E ? n / E : 1);
24
+ }
25
+ return o;
26
+ }
27
+ self.onmessage = (l) => {
28
+ const { id: h, channels: a, sampleRate: f } = l.data, e = m(a, f);
29
+ self.postMessage({ id: h, data: e }, e ? { transfer: [e.buffer] } : void 0);
30
+ };
31
+ //# sourceMappingURL=takeCleanerWorker-DTcI_Jqr.js.map
@@ -0,0 +1 @@
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;"}
@@ -0,0 +1,252 @@
1
+ import { ColorMatrixFilter as y, Graphics as S, Container as x, BatchTextureArray as c } from "pixi.js";
2
+ const f = /* @__PURE__ */ new Map();
3
+ function T(s) {
4
+ let t = f.get(s);
5
+ if (t) return t;
6
+ const e = new y();
7
+ e.antialias = "off";
8
+ const i = (s >> 16 & 255) / 255, r = (s >> 8 & 255) / 255, l = (s & 255) / 255;
9
+ return e.matrix = [0, 0, 0, 0, i, 0, 0, 0, 0, r, 0, 0, 0, 0, l, 0, 0, 0, 1, 0], f.set(s, t = [e]), t;
10
+ }
11
+ function F(s) {
12
+ return class extends s {
13
+ scene;
14
+ sx = 1;
15
+ sy = 1;
16
+ fx = !1;
17
+ fy = !1;
18
+ /** The colour this object is filled with (`setTintFill`), or null. */
19
+ solidFillColor = null;
20
+ /** False once destroyed. */
21
+ get active() {
22
+ return !this.destroyed;
23
+ }
24
+ get depth() {
25
+ return this.zIndex;
26
+ }
27
+ set depth(t) {
28
+ this.zIndex = t;
29
+ }
30
+ get scaleX() {
31
+ return this.sx;
32
+ }
33
+ set scaleX(t) {
34
+ this.sx = t, this.applyScale();
35
+ }
36
+ get scaleY() {
37
+ return this.sy;
38
+ }
39
+ set scaleY(t) {
40
+ this.sy = t, this.applyScale();
41
+ }
42
+ get flipX() {
43
+ return this.fx;
44
+ }
45
+ set flipX(t) {
46
+ this.fx = t, this.applyScale();
47
+ }
48
+ get flipY() {
49
+ return this.fy;
50
+ }
51
+ set flipY(t) {
52
+ this.fy = t, this.applyScale();
53
+ }
54
+ applyScale() {
55
+ this.destroyed || this.scale.set(this.sx * (this.fx ? -1 : 1), this.sy * (this.fy ? -1 : 1));
56
+ }
57
+ setScale(t, e = t) {
58
+ return this.sx = t, this.sy = e, this.applyScale(), this;
59
+ }
60
+ setFlipX(t) {
61
+ return this.flipX = t, this;
62
+ }
63
+ setFlipY(t) {
64
+ return this.flipY = t, this;
65
+ }
66
+ setFlip(t, e) {
67
+ return this.fx = t, this.fy = e, this.applyScale(), this;
68
+ }
69
+ setDepth(t) {
70
+ return this.zIndex = t, this;
71
+ }
72
+ setAlpha(t = 1) {
73
+ return this.alpha = t, this;
74
+ }
75
+ setVisible(t) {
76
+ return this.visible = t, this;
77
+ }
78
+ setPosition(t = 0, e = t) {
79
+ return this.position.set(t, e), this;
80
+ }
81
+ setRotation(t = 0) {
82
+ return this.rotation = t, this;
83
+ }
84
+ setAngle(t = 0) {
85
+ return this.angle = t, this;
86
+ }
87
+ setBlendMode(t) {
88
+ return this.blendMode = t, this;
89
+ }
90
+ setTint(t = 16777215) {
91
+ return this.clearFill(), this.tint = t, this;
92
+ }
93
+ /** Paints every opaque pixel in one solid colour (hit flash, silhouettes). */
94
+ setTintFill(t) {
95
+ return this.tint = 16777215, this.solidFillColor === t ? this : (this.solidFillColor = t, this.filters = T(t), this);
96
+ }
97
+ clearTint() {
98
+ return this.clearFill(), this.tint = 16777215, this;
99
+ }
100
+ clearFill() {
101
+ this.solidFillColor !== null && (this.filters = null, this.solidFillColor = null);
102
+ }
103
+ /** 0 pins the object to the screen instead of the world (full-screen flashes). */
104
+ setScrollFactor(t) {
105
+ return t === 0 && this.scene.screen.addChild(this), this;
106
+ }
107
+ destroy() {
108
+ this.destroyed || (this.solidFillColor !== null && (this.filters = null), this.solidFillColor = null, super.destroy({ children: !0, context: !0 }));
109
+ }
110
+ };
111
+ }
112
+ class _ extends F(x) {
113
+ g = new S();
114
+ fill = { color: 16777215, alpha: 1 };
115
+ line = { width: 1, color: 16777215, alpha: 1 };
116
+ constructor() {
117
+ super(), this.addChild(this.g);
118
+ }
119
+ fillStyle(t, e = 1) {
120
+ return this.fill = { color: t, alpha: e }, this;
121
+ }
122
+ lineStyle(t, e, i = 1) {
123
+ return this.line = { width: t, color: e, alpha: i }, this;
124
+ }
125
+ clear() {
126
+ return this.g.clear(), this;
127
+ }
128
+ fillRect(t, e, i, r) {
129
+ return this.g.rect(t, e, i, r).fill(this.fill), this;
130
+ }
131
+ fillEllipse(t, e, i, r) {
132
+ return this.g.ellipse(t, e, i / 2, r / 2).fill(this.fill), this;
133
+ }
134
+ strokeEllipse(t, e, i, r) {
135
+ return this.g.ellipse(t, e, i / 2, r / 2).stroke(this.line), this;
136
+ }
137
+ fillCircle(t, e, i) {
138
+ return this.g.circle(t, e, i).fill(this.fill), this;
139
+ }
140
+ strokeCircle(t, e, i) {
141
+ return this.g.circle(t, e, i).stroke(this.line), this;
142
+ }
143
+ lineBetween(t, e, i, r) {
144
+ return this.g.moveTo(t, e).lineTo(i, r).stroke(this.line), this;
145
+ }
146
+ fillPoints(t, e = !0) {
147
+ return this.g.poly(t.flatMap((i) => [i.x, i.y]), !0).fill(this.fill), this;
148
+ }
149
+ strokePoints(t, e = !1) {
150
+ return this.g.poly(t.flatMap((i) => [i.x, i.y]), e).stroke(this.line), this;
151
+ }
152
+ /** A pie slice from the centre, for cone telegraphs. */
153
+ slice(t, e, i, r, l, n = !1) {
154
+ return this.g.moveTo(t, e).arc(t, e, i, r, l, n).closePath(), this;
155
+ }
156
+ beginPath() {
157
+ return this.g.beginPath(), this;
158
+ }
159
+ moveTo(t, e) {
160
+ return this.g.moveTo(t, e), this;
161
+ }
162
+ lineTo(t, e) {
163
+ return this.g.lineTo(t, e), this;
164
+ }
165
+ closePath() {
166
+ return this.g.closePath(), this;
167
+ }
168
+ fillPath() {
169
+ return this.g.fill(this.fill), this;
170
+ }
171
+ strokePath() {
172
+ return this.g.stroke(this.line), this;
173
+ }
174
+ }
175
+ function P(s) {
176
+ return s.override === "1" || s.override === "on" ? !0 : s.override === "0" || s.override === "off" ? !1 : s.native ? !0 : s.coarse && s.touchPoints > 0;
177
+ }
178
+ let o = null, p = !1;
179
+ function w(s) {
180
+ p = s, o = null;
181
+ }
182
+ function m() {
183
+ try {
184
+ const s = new URLSearchParams(window.location.search).get("touch");
185
+ return s !== null ? s : localStorage.getItem("engine.touch");
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ function I() {
191
+ return {
192
+ native: p,
193
+ coarse: typeof matchMedia == "function" && matchMedia("(pointer: coarse)").matches,
194
+ touchPoints: typeof navigator < "u" ? navigator.maxTouchPoints ?? 0 : 0,
195
+ override: m()
196
+ };
197
+ }
198
+ function L() {
199
+ return o === null && (o = typeof window < "u" && P(I())), o;
200
+ }
201
+ let d = !1;
202
+ function A() {
203
+ if (d) return !0;
204
+ const s = c.prototype, t = new c();
205
+ return !(typeof s.clear == "function" && typeof t.ids == "object" && Array.isArray(t.textures) && typeof t.count == "number") || !/ids\[[^\]]+\]\s*=\s*null/.test(String(s.clear)) ? !1 : (s.clear = function() {
206
+ const r = this.ids, l = this.textures;
207
+ for (let n = 0; n < this.count; n++) {
208
+ const u = l[n];
209
+ l[n] = null, u && delete r[u.uid];
210
+ }
211
+ this.count = 0;
212
+ }, d = !0, !0);
213
+ }
214
+ const g = {
215
+ reducedFx: !1,
216
+ batterySaver: !1,
217
+ combatLights: !0,
218
+ sfxVolume: 0.8,
219
+ musicVolume: 0.6
220
+ };
221
+ let h = { ...g };
222
+ const a = /* @__PURE__ */ new Set(), b = {
223
+ getState() {
224
+ return h;
225
+ },
226
+ set(s) {
227
+ h = { ...h, ...s };
228
+ for (const t of [...a]) t(h);
229
+ },
230
+ subscribe(s) {
231
+ return a.add(s), () => a.delete(s);
232
+ },
233
+ reset() {
234
+ b.set(g);
235
+ }
236
+ }, E = 16, C = 60, k = 1 / C, M = 1, Y = 5, D = 3, G = 0.01;
237
+ export {
238
+ Y as B,
239
+ G as C,
240
+ F as G,
241
+ M as I,
242
+ E as T,
243
+ _ as a,
244
+ D as b,
245
+ k as c,
246
+ C as d,
247
+ b as e,
248
+ L as i,
249
+ A as p,
250
+ w as s
251
+ };
252
+ //# sourceMappingURL=constants-CjYVdFid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants-CjYVdFid.js","sources":["../src/core/gameObject.ts","../src/core/Graphics.ts","../src/input/touchDetect.ts","../src/platform/touch.ts","../src/core/pixiPatches.ts","../src/settings.ts","../src/net/constants.ts"],"sourcesContent":["import { ColorMatrixFilter, type BLEND_MODES, type Container } from 'pixi.js';\nimport type { Stage } from './Stage';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Ctor<T> = new (...args: any[]) => T;\n\n/**\n * One solid-fill filter list per colour, shared by every object filled with it (a hit flash, a silhouette): a filter\n * made and destroyed on every hit of every body was garbage and shader state a blow. Kept for good —\n * a handful of colours are ever used.\n */\nconst FILLS = new Map<number, ColorMatrixFilter[]>();\n\nfunction fillFilters(color: number): ColorMatrixFilter[] {\n let list = FILLS.get(color);\n if (list) return list;\n const f = new ColorMatrixFilter();\n f.antialias = 'off';\n const r = ((color >> 16) & 0xff) / 255;\n const g = ((color >> 8) & 0xff) / 255;\n const b = (color & 0xff) / 255;\n f.matrix = [0, 0, 0, 0, r, 0, 0, 0, 0, g, 0, 0, 0, 0, b, 0, 0, 0, 1, 0];\n FILLS.set(color, (list = [f]));\n return list;\n}\n\n/** What `GameObject` adds to a Pixi display object. */\nexport interface GameObjectMembers {\n scene: Stage;\n /** False once destroyed. */\n readonly active: boolean;\n /** The draw order in the world layer (`zIndex`): bigger is in front. */\n depth: number;\n /** Scale without the flip: stays positive on a mirrored sprite. */\n scaleX: number;\n scaleY: number;\n flipX: boolean;\n flipY: boolean;\n setScale(x: number, y?: number): this;\n setFlipX(v: boolean): this;\n setFlipY(v: boolean): this;\n setFlip(x: boolean, y: boolean): this;\n setDepth(d: number): this;\n setAlpha(a?: number): this;\n setVisible(v: boolean): this;\n setPosition(x?: number, y?: number): this;\n setRotation(r?: number): this;\n setAngle(a?: number): this;\n setBlendMode(mode: BLEND_MODES): this;\n setTint(color?: number): this;\n /** Paints every opaque pixel in one solid colour (hit flash, silhouettes). */\n setTintFill(color: number): this;\n clearTint(): this;\n /** 0 pins the object to the screen instead of the world. */\n setScrollFactor(f: number): this;\n destroy(): void;\n}\n\n/**\n * The chainable setters the world code was written against (setDepth, setAlpha, setTintFill...), added onto a Pixi\n * display object. Scale is kept apart from flipping, so `scaleX` stays positive on a mirrored sprite as it did\n * before, and a tween on scale cannot undo a flip.\n */\nexport function GameObject<TBase extends Ctor<Container>>(Base: TBase): TBase & Ctor<GameObjectMembers> {\n return class extends Base {\n scene!: Stage;\n private sx = 1;\n private sy = 1;\n private fx = false;\n private fy = false;\n /** The colour this object is filled with (`setTintFill`), or null. */\n private solidFillColor: number | null = null;\n\n /** False once destroyed. */\n get active(): boolean {\n return !this.destroyed;\n }\n\n get depth(): number {\n return this.zIndex;\n }\n\n set depth(d: number) {\n this.zIndex = d;\n }\n\n get scaleX(): number {\n return this.sx;\n }\n\n set scaleX(v: number) {\n this.sx = v;\n this.applyScale();\n }\n\n get scaleY(): number {\n return this.sy;\n }\n\n set scaleY(v: number) {\n this.sy = v;\n this.applyScale();\n }\n\n get flipX(): boolean {\n return this.fx;\n }\n\n set flipX(v: boolean) {\n this.fx = v;\n this.applyScale();\n }\n\n get flipY(): boolean {\n return this.fy;\n }\n\n set flipY(v: boolean) {\n this.fy = v;\n this.applyScale();\n }\n\n private applyScale(): void {\n if (!this.destroyed) this.scale.set(this.sx * (this.fx ? -1 : 1), this.sy * (this.fy ? -1 : 1));\n }\n\n setScale(x: number, y = x): this {\n this.sx = x;\n this.sy = y;\n this.applyScale();\n return this;\n }\n\n setFlipX(v: boolean): this {\n this.flipX = v;\n return this;\n }\n\n setFlipY(v: boolean): this {\n this.flipY = v;\n return this;\n }\n\n setFlip(x: boolean, y: boolean): this {\n this.fx = x;\n this.fy = y;\n this.applyScale();\n return this;\n }\n\n setDepth(d: number): this {\n this.zIndex = d;\n return this;\n }\n\n setAlpha(a = 1): this {\n this.alpha = a;\n return this;\n }\n\n setVisible(v: boolean): this {\n this.visible = v;\n return this;\n }\n\n setPosition(x = 0, y = x): this {\n this.position.set(x, y);\n return this;\n }\n\n setRotation(r = 0): this {\n this.rotation = r;\n return this;\n }\n\n setAngle(a = 0): this {\n this.angle = a;\n return this;\n }\n\n setBlendMode(mode: BLEND_MODES): this {\n this.blendMode = mode;\n return this;\n }\n\n setTint(color = 0xffffff): this {\n this.clearFill();\n this.tint = color;\n return this;\n }\n\n /** Paints every opaque pixel in one solid colour (hit flash, silhouettes). */\n setTintFill(color: number): this {\n this.tint = 0xffffff;\n if (this.solidFillColor === color) return this;\n this.solidFillColor = color;\n this.filters = fillFilters(color);\n return this;\n }\n\n clearTint(): this {\n this.clearFill();\n this.tint = 0xffffff;\n return this;\n }\n\n private clearFill(): void {\n if (this.solidFillColor === null) return;\n this.filters = null;\n this.solidFillColor = null;\n }\n\n /** 0 pins the object to the screen instead of the world (full-screen flashes). */\n setScrollFactor(f: number): this {\n if (f === 0) this.scene.screen.addChild(this);\n return this;\n }\n\n override destroy(): void {\n if (this.destroyed) return;\n // the fill filters are shared (`FILLS`): let go of them, never destroy them\n if (this.solidFillColor !== null) this.filters = null;\n this.solidFillColor = null;\n // `context`: a Pixi Graphics destroyed with options keeps its GraphicsContext — and that context's GPU buffers —\n // until Pixi's own GC sweeps it a minute or more later. Every effect layer is a Graphics, so a crowded fight left\n // hundreds a second on the GPU, and a phone that ran out dropped the WebGL context (the world rebuilt, seconds\n // frozen, the socket timed out).\n super.destroy({ children: true, context: true });\n }\n };\n}\n","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","/** What decides whether the touch controls are shown; read off the browser by `touchDevice.ts`. */\nexport interface TouchEnv {\n /** Running inside the native shell (Capacitor iOS). */\n native: boolean;\n /** `(pointer: coarse)` matches: the **primary** pointer is a finger. */\n coarse: boolean;\n /** `navigator.maxTouchPoints`. */\n touchPoints: number;\n /** `?touch=1|0` in the URL or `localStorage['engine.touch']`, for trying the controls on a desktop browser. */\n override: string | null;\n}\n\n/**\n * Touch controls only where a finger is the main way in: the native app always, a browser whose primary pointer\n * is coarse and that reports touch points (a phone or tablet), never a desktop — a laptop with a touch screen\n * still has a fine primary pointer and keeps its mouse and keyboard.\n */\nexport function detectTouch(env: TouchEnv): boolean {\n if (env.override === '1' || env.override === 'on') return true;\n if (env.override === '0' || env.override === 'off') return false;\n if (env.native) return true;\n return env.coarse && env.touchPoints > 0;\n}\n","import { detectTouch, type TouchEnv } from '../input/touchDetect';\n\nlet cached: boolean | null = null;\nlet nativeShell = false;\n\n/** A native shell (Capacitor, Electron touch kiosk…) tells the engine it is one; browsers leave it false. */\nexport function setNativeShell(native: boolean): void {\n nativeShell = native;\n cached = null;\n}\n\nfunction readOverride(): string | null {\n try {\n const fromUrl = new URLSearchParams(window.location.search).get('touch');\n if (fromUrl !== null) return fromUrl;\n return localStorage.getItem('engine.touch');\n } catch {\n return null;\n }\n}\n\nfunction readEnv(): TouchEnv {\n return {\n native: nativeShell,\n coarse: typeof matchMedia === 'function' && matchMedia('(pointer: coarse)').matches,\n touchPoints: typeof navigator !== 'undefined' ? navigator.maxTouchPoints ?? 0 : 0,\n override: readOverride(),\n };\n}\n\n/** True when the game is played with fingers (decided once per launch, `detectTouch`). */\nexport function isTouchUi(): boolean {\n if (cached === null) cached = typeof window !== 'undefined' && detectTouch(readEnv());\n return cached;\n}\n","import { BatchTextureArray } from 'pixi.js';\n\nlet patched = false;\n\n/**\n * Pixi 8's `BatchTextureArray.clear` sets `ids[source.uid] = null` instead of deleting the key, and its batches are\n * pooled, so every pooled batch keeps one key for every texture source ever drawn through it — about 70% of what a\n * fight's heap grew by in an hour (Pixi 8.21.0). The replacement deletes the key, which reads the\n * same to Pixi (`checkAndUpdateTexture` treats null and a missing key alike). Applied once, and only while `clear`\n * still has the shape it was written against, so a Pixi that fixed or changed it is left alone.\n */\nexport function patchPixi(): boolean {\n if (patched) return true;\n const proto = BatchTextureArray.prototype as unknown as { clear: () => void };\n const probe = new BatchTextureArray() as unknown as { ids: unknown; textures: unknown; count: unknown };\n const shaped = typeof proto.clear === 'function' && typeof probe.ids === 'object' && Array.isArray(probe.textures) && typeof probe.count === 'number';\n if (!shaped || !/ids\\[[^\\]]+\\]\\s*=\\s*null/.test(String(proto.clear))) return false;\n proto.clear = function clear(this: { ids: Record<number, number | null>; textures: ({ uid: number } | null)[]; count: number }): void {\n const ids = this.ids;\n const textures = this.textures;\n for (let i = 0; i < this.count; i++) {\n const t = textures[i];\n textures[i] = null;\n if (t) delete ids[t.uid];\n }\n this.count = 0;\n };\n patched = true;\n return true;\n}\n","/**\n * The few player settings the engine itself reads. A game keeps its own settings store and mirrors these fields\n * into this one (`engineSettings.set`) whenever they change; the engine never reads the game's store.\n */\nexport interface EngineSettings {\n /** Fewer particles and lighter effects. */\n reducedFx: boolean;\n /** Turns off everything that only costs battery: transient lights, heavy particles. */\n batterySaver: boolean;\n /** Short-lived lights a fight throws into the dark (`pixel/lights.ts`). */\n combatLights: boolean;\n /** 0–1, the combat sounds' volume (`audio/SoundEngine.ts`). */\n sfxVolume: number;\n /** 0–1, the music's volume. */\n musicVolume: number;\n}\n\nexport type SettingsListener = (s: EngineSettings) => void;\n\nconst DEFAULTS: EngineSettings = {\n reducedFx: false,\n batterySaver: false,\n combatLights: true,\n sfxVolume: 0.8,\n musicVolume: 0.6,\n};\n\nlet state: EngineSettings = { ...DEFAULTS };\nconst listeners = new Set<SettingsListener>();\n\n/** Same shape as the zustand stores the game used (`getState`, `subscribe`), so moved code reads it unchanged. */\nexport const engineSettings = {\n getState(): EngineSettings {\n return state;\n },\n set(patch: Partial<EngineSettings>): void {\n state = { ...state, ...patch };\n for (const l of [...listeners]) l(state);\n },\n subscribe(listener: SettingsListener): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n reset(): void {\n engineSettings.set(DEFAULTS);\n },\n};\n","/** World and movement constants. A game that needs other numbers forks these; client and server must agree. */\n\nexport const TILE_SIZE = 16;\n\n/** Fixed input steps a second; movement is simulated per step on both sides. */\nexport const INPUT_STEP_RATE = 60;\nexport const INPUT_STEP_DT = 1 / INPUT_STEP_RATE;\n\n/** Bit of an input step's flags that asks for a dodge roll. */\nexport const INPUT_FLAG_DASH = 1;\n\n/** The body's collision box, half extents (px). */\nexport const BODY_HALF_WIDTH = 5;\nexport const BODY_HALF_HEIGHT = 3;\nexport const COLLISION_EPSILON = 0.01;\n\n/** One fixed input step: [axisX, axisY, flags]. */\nexport type InputStep = [number, number, number];\n\nexport interface MovementParams {\n speed: number;\n dashSpeed: number;\n dashSteps: number;\n dashCooldownSteps: number;\n canMove: boolean;\n canDash: boolean;\n}\n\n/** One body's state in a snapshot: [id, x, y, aim, anim, hp, flags]. */\nexport type EntityUpdate = [number, number, number, number, number, number, number];\n"],"names":["FILLS","fillFilters","color","list","f","ColorMatrixFilter","r","g","b","GameObject","Base","d","v","x","y","a","mode","Graphics","Container","PixiGraphics","alpha","width","w","h","x1","y1","x2","y2","points","_closeShape","p","closeShape","start","end","anticlockwise","detectTouch","env","cached","nativeShell","setNativeShell","native","readOverride","fromUrl","readEnv","isTouchUi","patched","patchPixi","proto","BatchTextureArray","probe","ids","textures","i","t","DEFAULTS","state","listeners","engineSettings","patch","l","listener","TILE_SIZE","INPUT_STEP_RATE","INPUT_STEP_DT","INPUT_FLAG_DASH","BODY_HALF_WIDTH","BODY_HALF_HEIGHT","COLLISION_EPSILON"],"mappings":"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"}