@thunderclap-engine/engine 0.0.0-stage → 1.1.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.
- package/CHANGELOG.md +390 -0
- package/LICENSE +21 -0
- package/README.md +333 -2
- package/dist/analytics/analytics.d.ts +80 -0
- package/dist/analytics/analytics.js +129 -0
- package/dist/analytics/index.d.ts +1 -0
- package/dist/analytics/index.js +1 -0
- package/dist/anim/aim.d.ts +43 -0
- package/dist/anim/aim.js +157 -0
- package/dist/anim/clip.d.ts +98 -0
- package/dist/anim/clip.js +282 -0
- package/dist/anim/compress.d.ts +65 -0
- package/dist/anim/compress.js +339 -0
- package/dist/anim/foot-ik.d.ts +74 -0
- package/dist/anim/foot-ik.js +241 -0
- package/dist/anim/graph.d.ts +153 -0
- package/dist/anim/graph.js +532 -0
- package/dist/anim/ik.d.ts +105 -0
- package/dist/anim/ik.js +435 -0
- package/dist/anim/index.d.ts +36 -0
- package/dist/anim/index.js +21 -0
- package/dist/anim/match.d.ts +66 -0
- package/dist/anim/match.js +153 -0
- package/dist/anim/mixer.d.ts +113 -0
- package/dist/anim/mixer.js +314 -0
- package/dist/anim/morph.d.ts +41 -0
- package/dist/anim/morph.js +94 -0
- package/dist/anim/pose.d.ts +74 -0
- package/dist/anim/pose.js +254 -0
- package/dist/anim/quat.d.ts +44 -0
- package/dist/anim/quat.js +187 -0
- package/dist/anim/retarget.d.ts +97 -0
- package/dist/anim/retarget.js +366 -0
- package/dist/anim/root-motion.d.ts +44 -0
- package/dist/anim/root-motion.js +224 -0
- package/dist/anim/skin-pose.d.ts +39 -0
- package/dist/anim/skin-pose.js +53 -0
- package/dist/anim/socket.d.ts +47 -0
- package/dist/anim/socket.js +84 -0
- package/dist/anim/spring-chain.d.ts +73 -0
- package/dist/anim/spring-chain.js +294 -0
- package/dist/assets/atlas.d.ts +90 -0
- package/dist/assets/atlas.js +212 -0
- package/dist/assets/baked.d.ts +33 -0
- package/dist/assets/baked.js +38 -0
- package/dist/assets/bundle.d.ts +88 -0
- package/dist/assets/bundle.js +464 -0
- package/dist/assets/cache.d.ts +56 -0
- package/dist/assets/cache.js +123 -0
- package/dist/assets/gltf-load.d.ts +252 -0
- package/dist/assets/gltf-load.js +661 -0
- package/dist/assets/gltf-skin.d.ts +36 -0
- package/dist/assets/gltf-skin.js +109 -0
- package/dist/assets/gltf.d.ts +706 -0
- package/dist/assets/gltf.js +1376 -0
- package/dist/assets/index.d.ts +20 -0
- package/dist/assets/index.js +10 -0
- package/dist/assets/ktx2.d.ts +71 -0
- package/dist/assets/ktx2.js +213 -0
- package/dist/assets/manifest.d.ts +79 -0
- package/dist/assets/manifest.js +7 -0
- package/dist/assets/meshopt.d.ts +23 -0
- package/dist/assets/meshopt.js +403 -0
- package/dist/audio/audio.d.ts +733 -0
- package/dist/audio/audio.js +2013 -0
- package/dist/audio/dsp.d.ts +61 -0
- package/dist/audio/dsp.js +159 -0
- package/dist/audio/duck.d.ts +44 -0
- package/dist/audio/duck.js +101 -0
- package/dist/audio/envelope.d.ts +53 -0
- package/dist/audio/envelope.js +106 -0
- package/dist/audio/index.d.ts +13 -0
- package/dist/audio/index.js +14 -0
- package/dist/audio/music.d.ts +113 -0
- package/dist/audio/music.js +548 -0
- package/dist/audio/occlusion.d.ts +55 -0
- package/dist/audio/occlusion.js +61 -0
- package/dist/audio/persist.d.ts +23 -0
- package/dist/audio/persist.js +27 -0
- package/dist/audio/recipes.d.ts +55 -0
- package/dist/audio/recipes.js +227 -0
- package/dist/audio/send.d.ts +41 -0
- package/dist/audio/send.js +118 -0
- package/dist/audio/sprites.d.ts +59 -0
- package/dist/audio/sprites.js +43 -0
- package/dist/audio/synth.d.ts +103 -0
- package/dist/audio/synth.js +197 -0
- package/dist/audio/variations.d.ts +25 -0
- package/dist/audio/variations.js +55 -0
- package/dist/audio/zones.d.ts +100 -0
- package/dist/audio/zones.js +167 -0
- package/dist/core/detached-surface.d.ts +44 -0
- package/dist/core/detached-surface.js +104 -0
- package/dist/core/events.d.ts +24 -0
- package/dist/core/events.js +43 -0
- package/dist/core/game.d.ts +409 -0
- package/dist/core/game.js +530 -0
- package/dist/core/hash.d.ts +24 -0
- package/dist/core/hash.js +49 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +9 -0
- package/dist/core/loop.d.ts +113 -0
- package/dist/core/loop.js +185 -0
- package/dist/core/math.d.ts +58 -0
- package/dist/core/math.js +210 -0
- package/dist/core/noise.d.ts +40 -0
- package/dist/core/noise.js +247 -0
- package/dist/core/pool.d.ts +42 -0
- package/dist/core/pool.js +85 -0
- package/dist/core/random.d.ts +61 -0
- package/dist/core/random.js +199 -0
- package/dist/core/scene.d.ts +79 -0
- package/dist/core/scene.js +195 -0
- package/dist/core/timer.d.ts +66 -0
- package/dist/core/timer.js +286 -0
- package/dist/fx/billboard-wgsl.d.ts +29 -0
- package/dist/fx/billboard-wgsl.js +158 -0
- package/dist/fx/billboard.d.ts +249 -0
- package/dist/fx/billboard.js +702 -0
- package/dist/fx/debug-draw.d.ts +127 -0
- package/dist/fx/debug-draw.js +523 -0
- package/dist/fx/debug-wgsl.d.ts +7 -0
- package/dist/fx/debug-wgsl.js +32 -0
- package/dist/fx/decals-wgsl.d.ts +15 -0
- package/dist/fx/decals-wgsl.js +173 -0
- package/dist/fx/decals.d.ts +216 -0
- package/dist/fx/decals.js +607 -0
- package/dist/fx/effect.d.ts +129 -0
- package/dist/fx/effect.js +349 -0
- package/dist/fx/effects/hit-spark.json +48 -0
- package/dist/fx/effects/index.d.ts +8 -0
- package/dist/fx/effects/index.js +9 -0
- package/dist/fx/effects/magic-aura.json +47 -0
- package/dist/fx/effects/smoke-puff.json +34 -0
- package/dist/fx/emit-surface.d.ts +40 -0
- package/dist/fx/emit-surface.js +124 -0
- package/dist/fx/flicker.d.ts +30 -0
- package/dist/fx/flicker.js +42 -0
- package/dist/fx/index.d.ts +31 -0
- package/dist/fx/index.js +20 -0
- package/dist/fx/particles-wgsl.d.ts +57 -0
- package/dist/fx/particles-wgsl.js +828 -0
- package/dist/fx/particles.d.ts +566 -0
- package/dist/fx/particles.js +1533 -0
- package/dist/fx/random.d.ts +15 -0
- package/dist/fx/random.js +20 -0
- package/dist/fx/sky-wgsl.d.ts +23 -0
- package/dist/fx/sky-wgsl.js +127 -0
- package/dist/fx/sky.d.ts +96 -0
- package/dist/fx/sky.js +175 -0
- package/dist/fx/text-atlas.d.ts +143 -0
- package/dist/fx/text-atlas.js +327 -0
- package/dist/fx/trail-wgsl.d.ts +11 -0
- package/dist/fx/trail-wgsl.js +42 -0
- package/dist/fx/trail.d.ts +194 -0
- package/dist/fx/trail.js +555 -0
- package/dist/fx/water-wgsl.d.ts +34 -0
- package/dist/fx/water-wgsl.js +197 -0
- package/dist/fx/water.d.ts +176 -0
- package/dist/fx/water.js +371 -0
- package/dist/geom/collide/bvh.d.ts +151 -0
- package/dist/geom/collide/bvh.js +493 -0
- package/dist/geom/collide/debug.d.ts +14 -0
- package/dist/geom/collide/debug.js +74 -0
- package/dist/geom/collide/grid.d.ts +26 -0
- package/dist/geom/collide/grid.js +91 -0
- package/dist/geom/collide/index.d.ts +8 -0
- package/dist/geom/collide/index.js +8 -0
- package/dist/geom/collide/queries.d.ts +70 -0
- package/dist/geom/collide/queries.js +583 -0
- package/dist/geom/collide/serialize.d.ts +14 -0
- package/dist/geom/collide/serialize.js +145 -0
- package/dist/geom/collide/shapes.d.ts +161 -0
- package/dist/geom/collide/shapes.js +686 -0
- package/dist/geom/collide/slide.d.ts +66 -0
- package/dist/geom/collide/slide.js +375 -0
- package/dist/geom/collide/tile-slide.d.ts +88 -0
- package/dist/geom/collide/tile-slide.js +255 -0
- package/dist/geom/geometry.d.ts +95 -0
- package/dist/geom/geometry.js +322 -0
- package/dist/geom/heightfield.d.ts +72 -0
- package/dist/geom/heightfield.js +295 -0
- package/dist/geom/hull.d.ts +27 -0
- package/dist/geom/hull.js +279 -0
- package/dist/geom/index.d.ts +35 -0
- package/dist/geom/index.js +23 -0
- package/dist/geom/iso.d.ts +74 -0
- package/dist/geom/iso.js +89 -0
- package/dist/geom/lod.d.ts +29 -0
- package/dist/geom/lod.js +27 -0
- package/dist/geom/ops.d.ts +47 -0
- package/dist/geom/ops.js +285 -0
- package/dist/geom/primitives.d.ts +97 -0
- package/dist/geom/primitives.js +359 -0
- package/dist/geom/shapes.d.ts +43 -0
- package/dist/geom/shapes.js +88 -0
- package/dist/geom/simplify.d.ts +38 -0
- package/dist/geom/simplify.js +519 -0
- package/dist/geom/split.d.ts +37 -0
- package/dist/geom/split.js +413 -0
- package/dist/geom/sweep.d.ts +83 -0
- package/dist/geom/sweep.js +534 -0
- package/dist/geom/tangents.d.ts +16 -0
- package/dist/geom/tangents.js +124 -0
- package/dist/geom/triangulate.d.ts +19 -0
- package/dist/geom/triangulate.js +147 -0
- package/dist/host/default.d.ts +10 -0
- package/dist/host/default.js +10 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.js +3 -0
- package/dist/host/types.d.ts +144 -0
- package/dist/host/types.js +1 -0
- package/dist/host/web-links.d.ts +38 -0
- package/dist/host/web-links.js +110 -0
- package/dist/host/web.d.ts +30 -0
- package/dist/host/web.js +216 -0
- package/dist/host/xr.d.ts +24 -0
- package/dist/host/xr.js +59 -0
- package/dist/i18n/i18n.d.ts +59 -0
- package/dist/i18n/i18n.js +241 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/message.d.ts +72 -0
- package/dist/i18n/message.js +369 -0
- package/dist/i18n/negotiate.d.ts +17 -0
- package/dist/i18n/negotiate.js +58 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/input/bindings.d.ts +31 -0
- package/dist/input/bindings.js +141 -0
- package/dist/input/clock.d.ts +44 -0
- package/dist/input/clock.js +49 -0
- package/dist/input/follow.d.ts +133 -0
- package/dist/input/follow.js +218 -0
- package/dist/input/gestures.d.ts +281 -0
- package/dist/input/gestures.js +662 -0
- package/dist/input/glyphs.d.ts +35 -0
- package/dist/input/glyphs.js +216 -0
- package/dist/input/gyro.d.ts +85 -0
- package/dist/input/gyro.js +140 -0
- package/dist/input/index.d.ts +19 -0
- package/dist/input/index.js +16 -0
- package/dist/input/input.d.ts +543 -0
- package/dist/input/input.js +1492 -0
- package/dist/input/look.d.ts +327 -0
- package/dist/input/look.js +681 -0
- package/dist/input/magnify.d.ts +177 -0
- package/dist/input/magnify.js +131 -0
- package/dist/input/pointer-dispatch.d.ts +138 -0
- package/dist/input/pointer-dispatch.js +180 -0
- package/dist/input/pointer-router.d.ts +204 -0
- package/dist/input/pointer-router.js +533 -0
- package/dist/input/pointer-sources.d.ts +164 -0
- package/dist/input/pointer-sources.js +306 -0
- package/dist/input/rays.d.ts +138 -0
- package/dist/input/rays.js +615 -0
- package/dist/input/record.d.ts +209 -0
- package/dist/input/record.js +601 -0
- package/dist/input/touch-zones.d.ts +147 -0
- package/dist/input/touch-zones.js +300 -0
- package/dist/input/types.d.ts +158 -0
- package/dist/input/types.js +1 -0
- package/dist/input/xr-source.d.ts +53 -0
- package/dist/input/xr-source.js +164 -0
- package/dist/nav/astar.d.ts +45 -0
- package/dist/nav/astar.js +182 -0
- package/dist/nav/corridor.d.ts +40 -0
- package/dist/nav/corridor.js +95 -0
- package/dist/nav/crowd.d.ts +94 -0
- package/dist/nav/crowd.js +624 -0
- package/dist/nav/flow.d.ts +61 -0
- package/dist/nav/flow.js +193 -0
- package/dist/nav/grid.d.ts +108 -0
- package/dist/nav/grid.js +210 -0
- package/dist/nav/heap.d.ts +22 -0
- package/dist/nav/heap.js +92 -0
- package/dist/nav/index.d.ts +37 -0
- package/dist/nav/index.js +19 -0
- package/dist/nav/mesh/areas.d.ts +44 -0
- package/dist/nav/mesh/areas.js +98 -0
- package/dist/nav/mesh/bake-options.d.ts +33 -0
- package/dist/nav/mesh/bake-options.js +76 -0
- package/dist/nav/mesh/contour.d.ts +22 -0
- package/dist/nav/mesh/contour.js +323 -0
- package/dist/nav/mesh/flow3.d.ts +35 -0
- package/dist/nav/mesh/flow3.js +229 -0
- package/dist/nav/mesh/links.d.ts +38 -0
- package/dist/nav/mesh/links.js +72 -0
- package/dist/nav/mesh/mesh-path.d.ts +152 -0
- package/dist/nav/mesh/mesh-path.js +643 -0
- package/dist/nav/mesh/mesh-reach.d.ts +78 -0
- package/dist/nav/mesh/mesh-reach.js +450 -0
- package/dist/nav/mesh/navmesh.d.ts +117 -0
- package/dist/nav/mesh/navmesh.js +647 -0
- package/dist/nav/mesh/polymesh.d.ts +24 -0
- package/dist/nav/mesh/polymesh.js +384 -0
- package/dist/nav/mesh/regions.d.ts +12 -0
- package/dist/nav/mesh/regions.js +367 -0
- package/dist/nav/mesh/serialize.d.ts +13 -0
- package/dist/nav/mesh/serialize.js +197 -0
- package/dist/nav/mesh/voxelize.d.ts +111 -0
- package/dist/nav/mesh/voxelize.js +552 -0
- package/dist/nav/obstacles.d.ts +75 -0
- package/dist/nav/obstacles.js +294 -0
- package/dist/nav/path.d.ts +59 -0
- package/dist/nav/path.js +177 -0
- package/dist/nav/query-queue.d.ts +46 -0
- package/dist/nav/query-queue.js +308 -0
- package/dist/nav/reach.d.ts +53 -0
- package/dist/nav/reach.js +148 -0
- package/dist/nav/steer.d.ts +36 -0
- package/dist/nav/steer.js +78 -0
- package/dist/platform/boot.d.ts +28 -0
- package/dist/platform/boot.js +43 -0
- package/dist/platform/device-mode.d.ts +40 -0
- package/dist/platform/device-mode.js +104 -0
- package/dist/platform/device.d.ts +31 -0
- package/dist/platform/device.js +91 -0
- package/dist/platform/environment.d.ts +42 -0
- package/dist/platform/environment.js +82 -0
- package/dist/platform/gpu.d.ts +40 -0
- package/dist/platform/gpu.js +57 -0
- package/dist/platform/index.d.ts +18 -0
- package/dist/platform/index.js +10 -0
- package/dist/platform/letterbox.d.ts +43 -0
- package/dist/platform/letterbox.js +57 -0
- package/dist/platform/links.d.ts +20 -0
- package/dist/platform/links.js +43 -0
- package/dist/platform/screens.d.ts +36 -0
- package/dist/platform/screens.js +138 -0
- package/dist/platform/surface.d.ts +72 -0
- package/dist/platform/surface.js +157 -0
- package/dist/platform/xr-fake.d.ts +168 -0
- package/dist/platform/xr-fake.js +434 -0
- package/dist/platform/xr-surface.d.ts +120 -0
- package/dist/platform/xr-surface.js +214 -0
- package/dist/platform/xr-types.d.ts +262 -0
- package/dist/post/ao.d.ts +12 -0
- package/dist/post/ao.js +136 -0
- package/dist/post/blend.d.ts +27 -0
- package/dist/post/blend.js +129 -0
- package/dist/post/depth-pass.d.ts +43 -0
- package/dist/post/depth-pass.js +118 -0
- package/dist/post/dof.d.ts +1 -0
- package/dist/post/dof.js +66 -0
- package/dist/post/exposure.d.ts +80 -0
- package/dist/post/exposure.js +322 -0
- package/dist/post/index.d.ts +12 -0
- package/dist/post/index.js +11 -0
- package/dist/post/lens-dirt.d.ts +13 -0
- package/dist/post/lens-dirt.js +91 -0
- package/dist/post/lut.d.ts +19 -0
- package/dist/post/lut.js +50 -0
- package/dist/post/motion-blur.d.ts +9 -0
- package/dist/post/motion-blur.js +78 -0
- package/dist/post/post-stack.d.ts +302 -0
- package/dist/post/post-stack.js +1137 -0
- package/dist/post/settings.d.ts +485 -0
- package/dist/post/settings.js +416 -0
- package/dist/post/shaders.d.ts +25 -0
- package/dist/post/shaders.js +410 -0
- package/dist/post/ssr.d.ts +16 -0
- package/dist/post/ssr.js +155 -0
- package/dist/post/taa.d.ts +74 -0
- package/dist/post/taa.js +235 -0
- package/dist/post/white-balance.d.ts +25 -0
- package/dist/post/white-balance.js +98 -0
- package/dist/render/bindings.d.ts +98 -0
- package/dist/render/bindings.js +281 -0
- package/dist/render/blend.d.ts +13 -0
- package/dist/render/blend.js +26 -0
- package/dist/render/camera-2d.d.ts +72 -0
- package/dist/render/camera-2d.js +92 -0
- package/dist/render/camera.d.ts +302 -0
- package/dist/render/camera.js +671 -0
- package/dist/render/color.d.ts +16 -0
- package/dist/render/color.js +41 -0
- package/dist/render/compressed.d.ts +104 -0
- package/dist/render/compressed.js +248 -0
- package/dist/render/diagnostics.d.ts +15 -0
- package/dist/render/diagnostics.js +43 -0
- package/dist/render/dynamic-resolution.d.ts +70 -0
- package/dist/render/dynamic-resolution.js +126 -0
- package/dist/render/fake-gpu.d.ts +204 -0
- package/dist/render/fake-gpu.js +426 -0
- package/dist/render/flipbook.d.ts +56 -0
- package/dist/render/flipbook.js +87 -0
- package/dist/render/frame.d.ts +278 -0
- package/dist/render/frame.js +174 -0
- package/dist/render/fullscreen.d.ts +54 -0
- package/dist/render/fullscreen.js +91 -0
- package/dist/render/gpu-timer.d.ts +73 -0
- package/dist/render/gpu-timer.js +186 -0
- package/dist/render/index.d.ts +53 -0
- package/dist/render/index.js +29 -0
- package/dist/render/instance-list.d.ts +185 -0
- package/dist/render/instance-list.js +547 -0
- package/dist/render/lights.d.ts +181 -0
- package/dist/render/lights.js +233 -0
- package/dist/render/lod.d.ts +54 -0
- package/dist/render/lod.js +52 -0
- package/dist/render/material.d.ts +165 -0
- package/dist/render/material.js +292 -0
- package/dist/render/mesh.d.ts +284 -0
- package/dist/render/mesh.js +472 -0
- package/dist/render/mipmap.d.ts +16 -0
- package/dist/render/mipmap.js +121 -0
- package/dist/render/parallax.d.ts +50 -0
- package/dist/render/parallax.js +47 -0
- package/dist/render/pbr-wgsl.d.ts +31 -0
- package/dist/render/pbr-wgsl.js +119 -0
- package/dist/render/ping-pong.d.ts +42 -0
- package/dist/render/ping-pong.js +74 -0
- package/dist/render/renderer.d.ts +741 -0
- package/dist/render/renderer.js +2486 -0
- package/dist/render/scene-pipelines.d.ts +44 -0
- package/dist/render/scene-pipelines.js +92 -0
- package/dist/render/scene-target.d.ts +170 -0
- package/dist/render/scene-target.js +450 -0
- package/dist/render/shadow.d.ts +74 -0
- package/dist/render/shadow.js +208 -0
- package/dist/render/skin.d.ts +112 -0
- package/dist/render/skin.js +240 -0
- package/dist/render/snapshot.d.ts +36 -0
- package/dist/render/snapshot.js +34 -0
- package/dist/render/standard.d.ts +162 -0
- package/dist/render/standard.js +263 -0
- package/dist/render/texture.d.ts +142 -0
- package/dist/render/texture.js +228 -0
- package/dist/render/tile-layer.d.ts +110 -0
- package/dist/render/tile-layer.js +304 -0
- package/dist/render/transform.d.ts +115 -0
- package/dist/render/transform.js +318 -0
- package/dist/render/wgsl.d.ts +131 -0
- package/dist/render/wgsl.js +528 -0
- package/dist/save/codec.d.ts +49 -0
- package/dist/save/codec.js +169 -0
- package/dist/save/index.d.ts +8 -0
- package/dist/save/index.js +8 -0
- package/dist/save/journal.d.ts +24 -0
- package/dist/save/journal.js +102 -0
- package/dist/save/migrate.d.ts +15 -0
- package/dist/save/migrate.js +37 -0
- package/dist/save/schema.d.ts +72 -0
- package/dist/save/schema.js +154 -0
- package/dist/save/settings.d.ts +28 -0
- package/dist/save/settings.js +59 -0
- package/dist/save/slots.d.ts +56 -0
- package/dist/save/slots.js +200 -0
- package/dist/save/storage.d.ts +35 -0
- package/dist/save/storage.js +62 -0
- package/dist/save/store.d.ts +114 -0
- package/dist/save/store.js +269 -0
- package/dist/testing/fake-host.d.ts +103 -0
- package/dist/testing/fake-host.js +163 -0
- package/dist/testing/fixed-clock.d.ts +25 -0
- package/dist/testing/fixed-clock.js +30 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/test-game.d.ts +76 -0
- package/dist/testing/test-game.js +92 -0
- package/dist/tween/ease.d.ts +77 -0
- package/dist/tween/ease.js +192 -0
- package/dist/tween/index.d.ts +8 -0
- package/dist/tween/index.js +7 -0
- package/dist/tween/timeline.d.ts +78 -0
- package/dist/tween/timeline.js +269 -0
- package/dist/tween/tween.d.ts +168 -0
- package/dist/tween/tween.js +320 -0
- package/dist/ui/anchor.d.ts +206 -0
- package/dist/ui/anchor.js +307 -0
- package/dist/ui/caption.d.ts +122 -0
- package/dist/ui/caption.js +286 -0
- package/dist/ui/cursor.d.ts +105 -0
- package/dist/ui/cursor.js +261 -0
- package/dist/ui/dom.d.ts +30 -0
- package/dist/ui/dom.js +92 -0
- package/dist/ui/floats.d.ts +86 -0
- package/dist/ui/floats.js +260 -0
- package/dist/ui/hud.d.ts +198 -0
- package/dist/ui/hud.js +431 -0
- package/dist/ui/index.d.ts +37 -0
- package/dist/ui/index.js +34 -0
- package/dist/ui/inventory-card.d.ts +52 -0
- package/dist/ui/inventory-card.js +173 -0
- package/dist/ui/inventory-context.d.ts +29 -0
- package/dist/ui/inventory-context.js +125 -0
- package/dist/ui/inventory-drag.d.ts +63 -0
- package/dist/ui/inventory-drag.js +394 -0
- package/dist/ui/inventory-grid.d.ts +66 -0
- package/dist/ui/inventory-grid.js +625 -0
- package/dist/ui/inventory-radial.d.ts +39 -0
- package/dist/ui/inventory-radial.js +168 -0
- package/dist/ui/inventory.d.ts +128 -0
- package/dist/ui/inventory.js +154 -0
- package/dist/ui/layout.d.ts +356 -0
- package/dist/ui/layout.js +856 -0
- package/dist/ui/link.d.ts +64 -0
- package/dist/ui/link.js +155 -0
- package/dist/ui/loading.d.ts +40 -0
- package/dist/ui/loading.js +157 -0
- package/dist/ui/loupe.d.ts +74 -0
- package/dist/ui/loupe.js +164 -0
- package/dist/ui/menu-nav.d.ts +33 -0
- package/dist/ui/menu-nav.js +28 -0
- package/dist/ui/minimap-canvas.d.ts +145 -0
- package/dist/ui/minimap-canvas.js +460 -0
- package/dist/ui/notify.d.ts +40 -0
- package/dist/ui/notify.js +141 -0
- package/dist/ui/orb.d.ts +53 -0
- package/dist/ui/orb.js +304 -0
- package/dist/ui/overlay.d.ts +213 -0
- package/dist/ui/overlay.js +394 -0
- package/dist/ui/padnav.d.ts +53 -0
- package/dist/ui/padnav.js +146 -0
- package/dist/ui/perf.d.ts +130 -0
- package/dist/ui/perf.js +329 -0
- package/dist/ui/rotate-card.d.ts +54 -0
- package/dist/ui/rotate-card.js +97 -0
- package/dist/ui/stack.d.ts +128 -0
- package/dist/ui/stack.js +312 -0
- package/dist/ui/styles.d.ts +13 -0
- package/dist/ui/styles.js +200 -0
- package/dist/ui/text-scale.d.ts +67 -0
- package/dist/ui/text-scale.js +102 -0
- package/dist/ui/theme.d.ts +70 -0
- package/dist/ui/theme.js +98 -0
- package/dist/ui/toast.d.ts +23 -0
- package/dist/ui/toast.js +46 -0
- package/dist/ui/tooltip.d.ts +89 -0
- package/dist/ui/tooltip.js +333 -0
- package/dist/ui/widgets-2.d.ts +134 -0
- package/dist/ui/widgets-2.js +399 -0
- package/dist/ui/widgets-3.d.ts +91 -0
- package/dist/ui/widgets-3.js +212 -0
- package/dist/ui/widgets.d.ts +34 -0
- package/dist/ui/widgets.js +50 -0
- package/dist/ui/world-panel/index.d.ts +5 -0
- package/dist/ui/world-panel/index.js +5 -0
- package/dist/ui/world-panel/layout.d.ts +38 -0
- package/dist/ui/world-panel/layout.js +68 -0
- package/dist/ui/world-panel/paint.d.ts +96 -0
- package/dist/ui/world-panel/paint.js +136 -0
- package/dist/ui/world-panel/panel.d.ts +235 -0
- package/dist/ui/world-panel/panel.js +762 -0
- package/dist/ui/world-panel/wgsl.d.ts +10 -0
- package/dist/ui/world-panel/wgsl.js +52 -0
- package/dist/ui/world-panel/widgets.d.ts +207 -0
- package/dist/ui/world-panel/widgets.js +569 -0
- package/dist/voxel/chunks.d.ts +77 -0
- package/dist/voxel/chunks.js +149 -0
- package/dist/voxel/grid.d.ts +84 -0
- package/dist/voxel/grid.js +223 -0
- package/dist/voxel/index.d.ts +22 -0
- package/dist/voxel/index.js +7 -0
- package/dist/voxel/mesher-worker.d.ts +14 -0
- package/dist/voxel/mesher-worker.js +47 -0
- package/dist/voxel/mesher.d.ts +73 -0
- package/dist/voxel/mesher.js +306 -0
- package/dist/voxel/ray.d.ts +32 -0
- package/dist/voxel/ray.js +73 -0
- package/dist/voxel/wgsl.d.ts +16 -0
- package/dist/voxel/wgsl.js +28 -0
- package/dist/voxel/worker-client.d.ts +79 -0
- package/dist/voxel/worker-client.js +122 -0
- package/package.json +177 -3
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Save envelope, the typed-array JSON codec and the export/import string codec.
|
|
3
|
+
*
|
|
4
|
+
* Stored value: JSON `{ "v": <version>, "t": <savedAt ms>, "d": <data> }`.
|
|
5
|
+
* Export string: `TC1.<game>.<checksum>.<base64url(envelope JSON)>`. The checksum
|
|
6
|
+
* (FNV-1a, hex) catches truncated copy-paste; it is not tamper protection.
|
|
7
|
+
*
|
|
8
|
+
* Typed arrays inside the data are stored as `{ "$b": <base64url bytes>, "$t"?: <kind> }`
|
|
9
|
+
* (`$t` omitted for `Uint8Array`) by `saveReplacer` and turned back by `saveReviver`, so a
|
|
10
|
+
* tilemap or a voxel chunk never goes through a JSON number array. Bytes are in platform byte
|
|
11
|
+
* order (little-endian everywhere WebGPU runs). A plain object of the player's data that has
|
|
12
|
+
* its own `$b` key is escaped as `$$b` (one more `$` per level) and unescaped on load.
|
|
13
|
+
*/
|
|
14
|
+
export function isEnvelope(x) {
|
|
15
|
+
if (typeof x !== 'object' || x === null)
|
|
16
|
+
return false;
|
|
17
|
+
const e = x;
|
|
18
|
+
return typeof e.v === 'number' && Number.isInteger(e.v) && typeof e.t === 'number' && 'd' in e;
|
|
19
|
+
}
|
|
20
|
+
/** 32-bit FNV-1a over UTF-16 code units, as 8 hex chars. */
|
|
21
|
+
export function fnv1a(text) {
|
|
22
|
+
let h = 0x811c9dc5;
|
|
23
|
+
for (let i = 0; i < text.length; i++) {
|
|
24
|
+
h ^= text.charCodeAt(i);
|
|
25
|
+
h = Math.imul(h, 0x01000193);
|
|
26
|
+
}
|
|
27
|
+
return (h >>> 0).toString(16).padStart(8, '0');
|
|
28
|
+
}
|
|
29
|
+
// ---- base64url over bytes ----
|
|
30
|
+
const CHUNK = 0x2000;
|
|
31
|
+
function bytesToBase64Url(bytes) {
|
|
32
|
+
let bin = '';
|
|
33
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
34
|
+
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
|
|
35
|
+
}
|
|
36
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
37
|
+
}
|
|
38
|
+
function base64UrlToBytes(b64) {
|
|
39
|
+
const std = b64.replace(/-/g, '+').replace(/_/g, '/');
|
|
40
|
+
const bin = atob(std + '='.repeat((4 - (std.length % 4)) % 4));
|
|
41
|
+
const out = new Uint8Array(new ArrayBuffer(bin.length));
|
|
42
|
+
for (let i = 0; i < bin.length; i++)
|
|
43
|
+
out[i] = bin.charCodeAt(i);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function toBase64Url(text) {
|
|
47
|
+
return bytesToBase64Url(new TextEncoder().encode(text));
|
|
48
|
+
}
|
|
49
|
+
function fromBase64Url(b64) {
|
|
50
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(base64UrlToBytes(b64));
|
|
51
|
+
}
|
|
52
|
+
/** Typed-array kind → constructor. Internal: `schema.ts` and the tests import it from here. */
|
|
53
|
+
export const TYPED_KIND_CTORS = {
|
|
54
|
+
u8: Uint8Array,
|
|
55
|
+
u16: Uint16Array,
|
|
56
|
+
u32: Uint32Array,
|
|
57
|
+
i8: Int8Array,
|
|
58
|
+
i16: Int16Array,
|
|
59
|
+
i32: Int32Array,
|
|
60
|
+
f32: Float32Array,
|
|
61
|
+
f64: Float64Array,
|
|
62
|
+
};
|
|
63
|
+
const KIND_OF = new Map(Object.keys(TYPED_KIND_CTORS).map((k) => [TYPED_KIND_CTORS[k], k]));
|
|
64
|
+
/** The tag kind for `value` if it is one of the eight supported typed arrays, else null. */
|
|
65
|
+
export function typedKindOf(value) {
|
|
66
|
+
if (!ArrayBuffer.isView(value))
|
|
67
|
+
return null;
|
|
68
|
+
return KIND_OF.get(value.constructor) ?? null;
|
|
69
|
+
}
|
|
70
|
+
const ESCAPED_KEY = /^\$+b$/;
|
|
71
|
+
function isPlainObject(v) {
|
|
72
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
73
|
+
return false;
|
|
74
|
+
const proto = Object.getPrototypeOf(v);
|
|
75
|
+
return proto === Object.prototype || proto === null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `JSON.stringify` replacer: typed arrays become `{ $b, $t? }`; a plain object with a key
|
|
79
|
+
* matching `/^\$+b$/` gets that key prefixed with one more `$`.
|
|
80
|
+
*/
|
|
81
|
+
export function saveReplacer(_key, value) {
|
|
82
|
+
const kind = typedKindOf(value);
|
|
83
|
+
if (kind !== null) {
|
|
84
|
+
const view = value;
|
|
85
|
+
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
86
|
+
const b = bytesToBase64Url(bytes);
|
|
87
|
+
return kind === 'u8' ? { $b: b } : { $b: b, $t: kind };
|
|
88
|
+
}
|
|
89
|
+
if (isPlainObject(value)) {
|
|
90
|
+
const keys = Object.keys(value);
|
|
91
|
+
if (keys.some((k) => ESCAPED_KEY.test(k))) {
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const k of keys)
|
|
94
|
+
out[ESCAPED_KEY.test(k) ? `$${k}` : k] = value[k];
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function isTag(v) {
|
|
101
|
+
if (typeof v.$b !== 'string')
|
|
102
|
+
return false;
|
|
103
|
+
const keys = Object.keys(v);
|
|
104
|
+
if (keys.length === 1)
|
|
105
|
+
return true;
|
|
106
|
+
return keys.length === 2 && typeof v.$t === 'string' && v.$t in TYPED_KIND_CTORS;
|
|
107
|
+
}
|
|
108
|
+
/** `JSON.parse` reviver undoing `saveReplacer`. Bad tags (damaged base64) are left as objects. */
|
|
109
|
+
export function saveReviver(_key, value) {
|
|
110
|
+
if (!isPlainObject(value))
|
|
111
|
+
return value;
|
|
112
|
+
if (isTag(value)) {
|
|
113
|
+
let bytes;
|
|
114
|
+
try {
|
|
115
|
+
bytes = base64UrlToBytes(value.$b);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
const kind = value.$t ?? 'u8';
|
|
121
|
+
const Ctor = TYPED_KIND_CTORS[kind];
|
|
122
|
+
if (bytes.byteLength % Ctor.BYTES_PER_ELEMENT !== 0)
|
|
123
|
+
return value;
|
|
124
|
+
if (kind === 'u8')
|
|
125
|
+
return bytes;
|
|
126
|
+
return new Ctor(bytes.buffer, 0, bytes.byteLength / Ctor.BYTES_PER_ELEMENT);
|
|
127
|
+
}
|
|
128
|
+
const keys = Object.keys(value);
|
|
129
|
+
if (keys.some((k) => k.length > 2 && ESCAPED_KEY.test(k))) {
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const k of keys)
|
|
132
|
+
out[k.length > 2 && ESCAPED_KEY.test(k) ? k.slice(1) : k] = value[k];
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
// ---- export string ----
|
|
138
|
+
const PREFIX = 'TC1';
|
|
139
|
+
export function encodeExport(game, env) {
|
|
140
|
+
const json = JSON.stringify(env, saveReplacer);
|
|
141
|
+
return `${PREFIX}.${game}.${fnv1a(json)}.${toBase64Url(json)}`;
|
|
142
|
+
}
|
|
143
|
+
export function decodeExport(game, text) {
|
|
144
|
+
const parts = text.trim().split('.');
|
|
145
|
+
if (parts.length !== 4 || parts[0] !== PREFIX)
|
|
146
|
+
return { ok: false, error: 'not a save string' };
|
|
147
|
+
const [, g, sum, body] = parts;
|
|
148
|
+
if (g !== game)
|
|
149
|
+
return { ok: false, error: `save belongs to "${g}", not "${game}"` };
|
|
150
|
+
let json;
|
|
151
|
+
try {
|
|
152
|
+
json = fromBase64Url(body);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { ok: false, error: 'save string is damaged (bad encoding)' };
|
|
156
|
+
}
|
|
157
|
+
if (fnv1a(json) !== sum)
|
|
158
|
+
return { ok: false, error: 'save string is damaged (checksum)' };
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(json, saveReviver);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { ok: false, error: 'save string is damaged (bad JSON)' };
|
|
165
|
+
}
|
|
166
|
+
if (!isEnvelope(parsed))
|
|
167
|
+
return { ok: false, error: 'save string has no version envelope' };
|
|
168
|
+
return { ok: true, envelope: parsed };
|
|
169
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { decodeExport, encodeExport, fnv1a, saveReplacer, saveReviver, type TypedArrayOf, type TypedKind, typedKindOf, } from './codec.js';
|
|
2
|
+
export { createJournal, type Journal, type JournalOptions } from './journal.js';
|
|
3
|
+
export { assertMigrationChain, type Migration, MigrationError, type Migrations, runMigrations, } from './migrate.js';
|
|
4
|
+
export { type Infer, type NumberOptions, type Schema, s } from './schema.js';
|
|
5
|
+
export { type BindTarget, createSettingsStore, type SettingsStore, type SettingsStoreOptions, } from './settings.js';
|
|
6
|
+
export { createSlotManager, type SlotInfo, type SlotManager, type SlotManagerOptions, slotName, } from './slots.js';
|
|
7
|
+
export { isQuotaError, MemoryStorage, probeStorage, type StorageLike } from './storage.js';
|
|
8
|
+
export { createSaveStore, type ImportResult, type LoadResult, listSlots, type SaveStatus, type SaveStore, type SaveStoreOptions, type SlotMeta, type StoredMeta, saveKey, } from './store.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { decodeExport, encodeExport, fnv1a, saveReplacer, saveReviver, typedKindOf, } from './codec.js';
|
|
2
|
+
export { createJournal } from './journal.js';
|
|
3
|
+
export { assertMigrationChain, MigrationError, runMigrations, } from './migrate.js';
|
|
4
|
+
export { s } from './schema.js';
|
|
5
|
+
export { createSettingsStore, } from './settings.js';
|
|
6
|
+
export { createSlotManager, slotName, } from './slots.js';
|
|
7
|
+
export { isQuotaError, MemoryStorage, probeStorage } from './storage.js';
|
|
8
|
+
export { createSaveStore, listSlots, saveKey, } from './store.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SaveStore } from './store.js';
|
|
2
|
+
export interface JournalOptions {
|
|
3
|
+
/** Most undo steps kept. Default 50. */
|
|
4
|
+
limit?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Journal {
|
|
7
|
+
/** Snapshot the current data. Identical to the last snapshot: no-op. Clears redo. */
|
|
8
|
+
push(label?: string): void;
|
|
9
|
+
/** Restore the previous snapshot (the current data becomes the redo step). False when none. */
|
|
10
|
+
undo(): boolean;
|
|
11
|
+
/** Restore the next snapshot after an undo. False when none. */
|
|
12
|
+
redo(): boolean;
|
|
13
|
+
readonly canUndo: boolean;
|
|
14
|
+
readonly canRedo: boolean;
|
|
15
|
+
/** Undo steps available. */
|
|
16
|
+
readonly length: number;
|
|
17
|
+
/** The label of the step `undo` would restore, if `push` gave one. */
|
|
18
|
+
readonly undoLabel: string | undefined;
|
|
19
|
+
/** The label of the step `redo` would restore. */
|
|
20
|
+
readonly redoLabel: string | undefined;
|
|
21
|
+
/** Drop every snapshot. */
|
|
22
|
+
clear(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function createJournal<T>(store: SaveStore<T>, opts?: JournalOptions): Journal;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An undo journal over a save store: snapshots of the store's validated data, restored through
|
|
3
|
+
* `store.set`. Call `push()` before a change (a checkpoint) or after one (a commit); both work.
|
|
4
|
+
*/
|
|
5
|
+
import { saveReplacer } from './codec.js';
|
|
6
|
+
export function createJournal(store, opts = {}) {
|
|
7
|
+
const limit = opts.limit ?? 50;
|
|
8
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
9
|
+
throw new Error('journal limit must be ≥ 1');
|
|
10
|
+
const past = [];
|
|
11
|
+
const future = [];
|
|
12
|
+
// The JSON text of the current data, cached by identity (`set` replaces the reference).
|
|
13
|
+
let cachedRef;
|
|
14
|
+
let cachedText = '';
|
|
15
|
+
const currentText = () => {
|
|
16
|
+
const d = store.get();
|
|
17
|
+
if (d !== cachedRef) {
|
|
18
|
+
cachedRef = d;
|
|
19
|
+
cachedText = JSON.stringify(d, saveReplacer);
|
|
20
|
+
}
|
|
21
|
+
return cachedText;
|
|
22
|
+
};
|
|
23
|
+
const snapshot = (label) => {
|
|
24
|
+
const text = currentText();
|
|
25
|
+
const snap = { data: structuredClone(store.get()), text };
|
|
26
|
+
if (label !== undefined)
|
|
27
|
+
snap.label = label;
|
|
28
|
+
return snap;
|
|
29
|
+
};
|
|
30
|
+
const top = (stack) => stack[stack.length - 1];
|
|
31
|
+
/** Steps in `stack` not equal to the current data (the top may be a commit of it). */
|
|
32
|
+
const steps = (stack) => stack.length - (stack.length && top(stack).text === currentText() ? 1 : 0);
|
|
33
|
+
const trim = () => {
|
|
34
|
+
while (past.length > limit)
|
|
35
|
+
past.shift();
|
|
36
|
+
};
|
|
37
|
+
/** Move from one stack to the other, with the current data as the return step. */
|
|
38
|
+
const step = (from, to) => {
|
|
39
|
+
const cur = snapshot();
|
|
40
|
+
while (from.length && top(from).text === cur.text) {
|
|
41
|
+
// A commit of the current data: carry its label to the return step.
|
|
42
|
+
cur.label = from.pop().label;
|
|
43
|
+
}
|
|
44
|
+
const target = from.pop();
|
|
45
|
+
if (!target)
|
|
46
|
+
return false;
|
|
47
|
+
const toTop = top(to);
|
|
48
|
+
if (toTop && toTop.text === cur.text)
|
|
49
|
+
toTop.label ??= cur.label;
|
|
50
|
+
else
|
|
51
|
+
to.push(cur);
|
|
52
|
+
store.set(structuredClone(target.data));
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
push(label) {
|
|
57
|
+
const snap = snapshot(label);
|
|
58
|
+
future.length = 0;
|
|
59
|
+
const last = top(past);
|
|
60
|
+
if (last && last.text === snap.text) {
|
|
61
|
+
if (label !== undefined)
|
|
62
|
+
last.label = label;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
past.push(snap);
|
|
66
|
+
trim();
|
|
67
|
+
},
|
|
68
|
+
undo: () => step(past, future),
|
|
69
|
+
redo() {
|
|
70
|
+
const ok = step(future, past);
|
|
71
|
+
trim();
|
|
72
|
+
return ok;
|
|
73
|
+
},
|
|
74
|
+
get canUndo() {
|
|
75
|
+
return steps(past) > 0;
|
|
76
|
+
},
|
|
77
|
+
get canRedo() {
|
|
78
|
+
return steps(future) > 0;
|
|
79
|
+
},
|
|
80
|
+
get length() {
|
|
81
|
+
return steps(past);
|
|
82
|
+
},
|
|
83
|
+
get undoLabel() {
|
|
84
|
+
const t = currentText();
|
|
85
|
+
for (let i = past.length - 1; i >= 0; i--)
|
|
86
|
+
if (past[i].text !== t)
|
|
87
|
+
return past[i].label;
|
|
88
|
+
return undefined;
|
|
89
|
+
},
|
|
90
|
+
get redoLabel() {
|
|
91
|
+
const t = currentText();
|
|
92
|
+
for (let i = future.length - 1; i >= 0; i--)
|
|
93
|
+
if (future[i].text !== t)
|
|
94
|
+
return future[i].label;
|
|
95
|
+
return undefined;
|
|
96
|
+
},
|
|
97
|
+
clear() {
|
|
98
|
+
past.length = 0;
|
|
99
|
+
future.length = 0;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrations chain. `migrations[n]` upgrades data from version `n` to `n + 1`.
|
|
3
|
+
* Versions are positive integers; the first shipped version is 1.
|
|
4
|
+
*/
|
|
5
|
+
export type Migration = (data: any) => unknown;
|
|
6
|
+
export type Migrations = Readonly<Record<number, Migration>>;
|
|
7
|
+
export declare class MigrationError extends Error {
|
|
8
|
+
readonly fromVersion: number;
|
|
9
|
+
readonly toVersion: number;
|
|
10
|
+
constructor(message: string, fromVersion: number, toVersion: number);
|
|
11
|
+
}
|
|
12
|
+
/** Throws `MigrationError` if a step is missing, the data is from the future, or a step throws. */
|
|
13
|
+
export declare function runMigrations(data: unknown, fromVersion: number, toVersion: number, migrations: Migrations): unknown;
|
|
14
|
+
/** Throws if the chain `1 → version` has gaps. Call once at startup or in a test. */
|
|
15
|
+
export declare function assertMigrationChain(version: number, migrations: Migrations): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class MigrationError extends Error {
|
|
2
|
+
fromVersion;
|
|
3
|
+
toVersion;
|
|
4
|
+
constructor(message, fromVersion, toVersion) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.fromVersion = fromVersion;
|
|
7
|
+
this.toVersion = toVersion;
|
|
8
|
+
this.name = 'MigrationError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Throws `MigrationError` if a step is missing, the data is from the future, or a step throws. */
|
|
12
|
+
export function runMigrations(data, fromVersion, toVersion, migrations) {
|
|
13
|
+
if (!Number.isInteger(fromVersion) || fromVersion < 1)
|
|
14
|
+
throw new MigrationError(`invalid save version ${fromVersion}`, fromVersion, toVersion);
|
|
15
|
+
if (fromVersion > toVersion)
|
|
16
|
+
throw new MigrationError(`save version ${fromVersion} is newer than this build (${toVersion})`, fromVersion, toVersion);
|
|
17
|
+
let current = data;
|
|
18
|
+
for (let v = fromVersion; v < toVersion; v++) {
|
|
19
|
+
const step = migrations[v];
|
|
20
|
+
if (!step)
|
|
21
|
+
throw new MigrationError(`no migration from v${v} to v${v + 1}`, v, toVersion);
|
|
22
|
+
try {
|
|
23
|
+
current = step(current);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new MigrationError(`migration v${v} → v${v + 1} failed: ${err instanceof Error ? err.message : String(err)}`, v, toVersion);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
/** Throws if the chain `1 → version` has gaps. Call once at startup or in a test. */
|
|
32
|
+
export function assertMigrationChain(version, migrations) {
|
|
33
|
+
for (let v = 1; v < version; v++) {
|
|
34
|
+
if (typeof migrations[v] !== 'function')
|
|
35
|
+
throw new MigrationError(`migration chain gap: v${v} → v${v + 1}`, v, version);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny runtime schema guard for save data. Not a general validation library:
|
|
3
|
+
* just enough to reject corrupt or hand-edited saves before the game sees them.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const Settings = s.object({ volume: s.number({ min: 0, max: 1 }), name: s.optional(s.string()) });
|
|
7
|
+
* type Settings = Infer<typeof Settings>;
|
|
8
|
+
* Settings.validate(json); // null when valid, else "$.volume: expected number ≤ 1"
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { type TypedArrayOf, type TypedKind } from './codec.js';
|
|
12
|
+
export interface Schema<T> {
|
|
13
|
+
/** Returns `null` if `value` matches, else a message naming the first failing path. */
|
|
14
|
+
validate(value: unknown, path?: string): string | null;
|
|
15
|
+
/** Type guard form of `validate`. */
|
|
16
|
+
is(value: unknown): value is T;
|
|
17
|
+
/** Phantom field carrying the inferred type. Never set at runtime. */
|
|
18
|
+
readonly _type?: T;
|
|
19
|
+
}
|
|
20
|
+
export type Infer<S> = S extends Schema<infer T> ? T : never;
|
|
21
|
+
/** `s.number(opts)`: an inclusive range and an integer check. */
|
|
22
|
+
export interface NumberOptions {
|
|
23
|
+
min?: number;
|
|
24
|
+
max?: number;
|
|
25
|
+
int?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Optional-key marker used by `s.object`. */
|
|
28
|
+
declare const OPTIONAL: unique symbol;
|
|
29
|
+
type OptionalSchema<T> = Schema<T | undefined> & {
|
|
30
|
+
[OPTIONAL]: true;
|
|
31
|
+
};
|
|
32
|
+
type Shape = Record<string, Schema<unknown>>;
|
|
33
|
+
type OptionalKeys<S extends Shape> = {
|
|
34
|
+
[K in keyof S]: S[K] extends {
|
|
35
|
+
[OPTIONAL]: true;
|
|
36
|
+
} ? K : never;
|
|
37
|
+
}[keyof S];
|
|
38
|
+
type ObjectOf<S extends Shape> = {
|
|
39
|
+
[K in Exclude<keyof S, OptionalKeys<S>>]: Infer<S[K]>;
|
|
40
|
+
} & {
|
|
41
|
+
[K in OptionalKeys<S>]?: Infer<S[K]>;
|
|
42
|
+
};
|
|
43
|
+
export declare const s: {
|
|
44
|
+
unknown(): Schema<unknown>;
|
|
45
|
+
boolean(): Schema<boolean>;
|
|
46
|
+
string(opts?: {
|
|
47
|
+
maxLength?: number;
|
|
48
|
+
}): Schema<string>;
|
|
49
|
+
number(opts?: NumberOptions): Schema<number>;
|
|
50
|
+
literal<const L extends string | number | boolean | null>(...values: L[]): Schema<L>;
|
|
51
|
+
array<T>(item: Schema<T>, opts?: {
|
|
52
|
+
maxLength?: number;
|
|
53
|
+
}): Schema<T[]>;
|
|
54
|
+
/** String-keyed map with uniform values. */
|
|
55
|
+
record<T>(value: Schema<T>): Schema<Record<string, T>>;
|
|
56
|
+
/** Fixed-shape object. Extra keys are allowed (forward compatibility) unless `strict`. */
|
|
57
|
+
object<S extends Shape>(shape: S, opts?: {
|
|
58
|
+
strict?: boolean;
|
|
59
|
+
}): Schema<ObjectOf<S>>;
|
|
60
|
+
optional<T>(inner: Schema<T>): OptionalSchema<T>;
|
|
61
|
+
nullable<T>(inner: Schema<T>): Schema<T | null>;
|
|
62
|
+
/** A `Uint8Array` (stored as base64url, not a number array). */
|
|
63
|
+
bytes(opts?: {
|
|
64
|
+
maxLength?: number;
|
|
65
|
+
}): Schema<Uint8Array>;
|
|
66
|
+
/** One of the eight typed arrays by kind, e.g. `s.typed('f32')` for a `Float32Array`. */
|
|
67
|
+
typed<K extends TypedKind>(kind: K, opts?: {
|
|
68
|
+
maxLength?: number;
|
|
69
|
+
}): Schema<TypedArrayOf<K>>;
|
|
70
|
+
union<T extends Schema<unknown>[]>(...options: T): Schema<Infer<T[number]>>;
|
|
71
|
+
};
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tiny runtime schema guard for save data. Not a general validation library:
|
|
3
|
+
* just enough to reject corrupt or hand-edited saves before the game sees them.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const Settings = s.object({ volume: s.number({ min: 0, max: 1 }), name: s.optional(s.string()) });
|
|
7
|
+
* type Settings = Infer<typeof Settings>;
|
|
8
|
+
* Settings.validate(json); // null when valid, else "$.volume: expected number ≤ 1"
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { TYPED_KIND_CTORS } from './codec.js';
|
|
12
|
+
function make(validate) {
|
|
13
|
+
return {
|
|
14
|
+
validate: (value, path = '$') => validate(value, path),
|
|
15
|
+
is: (value) => validate(value, '$') === null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function typeName(v) {
|
|
19
|
+
if (v === null)
|
|
20
|
+
return 'null';
|
|
21
|
+
if (Array.isArray(v))
|
|
22
|
+
return 'array';
|
|
23
|
+
if (ArrayBuffer.isView(v))
|
|
24
|
+
return v.constructor.name;
|
|
25
|
+
return typeof v;
|
|
26
|
+
}
|
|
27
|
+
/** Optional-key marker used by `s.object`. */
|
|
28
|
+
const OPTIONAL = Symbol('optional');
|
|
29
|
+
export const s = {
|
|
30
|
+
unknown() {
|
|
31
|
+
return make(() => null);
|
|
32
|
+
},
|
|
33
|
+
boolean() {
|
|
34
|
+
return make((v, p) => typeof v === 'boolean' ? null : `${p}: expected boolean, got ${typeName(v)}`);
|
|
35
|
+
},
|
|
36
|
+
string(opts = {}) {
|
|
37
|
+
return make((v, p) => {
|
|
38
|
+
if (typeof v !== 'string')
|
|
39
|
+
return `${p}: expected string, got ${typeName(v)}`;
|
|
40
|
+
if (opts.maxLength !== undefined && v.length > opts.maxLength)
|
|
41
|
+
return `${p}: string longer than ${opts.maxLength}`;
|
|
42
|
+
return null;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
number(opts = {}) {
|
|
46
|
+
return make((v, p) => {
|
|
47
|
+
if (typeof v !== 'number' || !Number.isFinite(v))
|
|
48
|
+
return `${p}: expected finite number, got ${typeName(v)}`;
|
|
49
|
+
if (opts.int && !Number.isInteger(v))
|
|
50
|
+
return `${p}: expected integer`;
|
|
51
|
+
if (opts.min !== undefined && v < opts.min)
|
|
52
|
+
return `${p}: expected number ≥ ${opts.min}`;
|
|
53
|
+
if (opts.max !== undefined && v > opts.max)
|
|
54
|
+
return `${p}: expected number ≤ ${opts.max}`;
|
|
55
|
+
return null;
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
literal(...values) {
|
|
59
|
+
return make((v, p) => values.includes(v) ? null : `${p}: expected one of ${JSON.stringify(values)}`);
|
|
60
|
+
},
|
|
61
|
+
array(item, opts = {}) {
|
|
62
|
+
return make((v, p) => {
|
|
63
|
+
if (!Array.isArray(v))
|
|
64
|
+
return `${p}: expected array, got ${typeName(v)}`;
|
|
65
|
+
if (opts.maxLength !== undefined && v.length > opts.maxLength)
|
|
66
|
+
return `${p}: array longer than ${opts.maxLength}`;
|
|
67
|
+
for (let i = 0; i < v.length; i++) {
|
|
68
|
+
const err = item.validate(v[i], `${p}[${i}]`);
|
|
69
|
+
if (err)
|
|
70
|
+
return err;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
/** String-keyed map with uniform values. */
|
|
76
|
+
record(value) {
|
|
77
|
+
return make((v, p) => {
|
|
78
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
79
|
+
return `${p}: expected object, got ${typeName(v)}`;
|
|
80
|
+
for (const [k, val] of Object.entries(v)) {
|
|
81
|
+
const err = value.validate(val, `${p}.${k}`);
|
|
82
|
+
if (err)
|
|
83
|
+
return err;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
/** Fixed-shape object. Extra keys are allowed (forward compatibility) unless `strict`. */
|
|
89
|
+
object(shape, opts = {}) {
|
|
90
|
+
return make((v, p) => {
|
|
91
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
92
|
+
return `${p}: expected object, got ${typeName(v)}`;
|
|
93
|
+
const obj = v;
|
|
94
|
+
for (const [k, schema] of Object.entries(shape)) {
|
|
95
|
+
const optional = OPTIONAL in schema;
|
|
96
|
+
if (!(k in obj) || obj[k] === undefined) {
|
|
97
|
+
if (optional)
|
|
98
|
+
continue;
|
|
99
|
+
return `${p}.${k}: missing`;
|
|
100
|
+
}
|
|
101
|
+
const err = schema.validate(obj[k], `${p}.${k}`);
|
|
102
|
+
if (err)
|
|
103
|
+
return err;
|
|
104
|
+
}
|
|
105
|
+
if (opts.strict) {
|
|
106
|
+
for (const k of Object.keys(obj))
|
|
107
|
+
if (!(k in shape))
|
|
108
|
+
return `${p}.${k}: unexpected key`;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
optional(inner) {
|
|
114
|
+
const base = make((v, p) => (v === undefined ? null : inner.validate(v, p)));
|
|
115
|
+
return Object.assign(base, { [OPTIONAL]: true });
|
|
116
|
+
},
|
|
117
|
+
nullable(inner) {
|
|
118
|
+
return make((v, p) => (v === null ? null : inner.validate(v, p)));
|
|
119
|
+
},
|
|
120
|
+
/** A `Uint8Array` (stored as base64url, not a number array). */
|
|
121
|
+
bytes(opts = {}) {
|
|
122
|
+
return make((v, p) => {
|
|
123
|
+
if (!(v instanceof Uint8Array))
|
|
124
|
+
return `${p}: expected Uint8Array, got ${typeName(v)}`;
|
|
125
|
+
if (opts.maxLength !== undefined && v.length > opts.maxLength)
|
|
126
|
+
return `${p}: bytes longer than ${opts.maxLength}`;
|
|
127
|
+
return null;
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
/** One of the eight typed arrays by kind, e.g. `s.typed('f32')` for a `Float32Array`. */
|
|
131
|
+
typed(kind, opts = {}) {
|
|
132
|
+
const Ctor = TYPED_KIND_CTORS[kind];
|
|
133
|
+
return make((v, p) => {
|
|
134
|
+
if (!(v instanceof Ctor))
|
|
135
|
+
return `${p}: expected ${Ctor.name}, got ${typeName(v)}`;
|
|
136
|
+
const len = v.byteLength / Ctor.BYTES_PER_ELEMENT;
|
|
137
|
+
if (opts.maxLength !== undefined && len > opts.maxLength)
|
|
138
|
+
return `${p}: typed array longer than ${opts.maxLength}`;
|
|
139
|
+
return null;
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
union(...options) {
|
|
143
|
+
return make((v, p) => {
|
|
144
|
+
const errors = [];
|
|
145
|
+
for (const o of options) {
|
|
146
|
+
const err = o.validate(v, p);
|
|
147
|
+
if (err === null)
|
|
148
|
+
return null;
|
|
149
|
+
errors.push(err);
|
|
150
|
+
}
|
|
151
|
+
return errors.join(' | ') || `${p}: no union options`;
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A settings store: `createSaveStore` in the `settings` slot, written synchronously, kept apart
|
|
3
|
+
* from progress, with `bind` for wiring menu controls.
|
|
4
|
+
*/
|
|
5
|
+
import { type SaveStore, type SaveStoreOptions } from './store.js';
|
|
6
|
+
export interface SettingsStoreOptions<T> extends Omit<SaveStoreOptions<T>, 'slot' | 'debounceMs' | 'version' | 'meta'> {
|
|
7
|
+
/** Default `settings`. */
|
|
8
|
+
slot?: string;
|
|
9
|
+
/** Default 1. */
|
|
10
|
+
version?: number;
|
|
11
|
+
}
|
|
12
|
+
/** A control `bind` can drive: an `<input>`, `<select>`, or any `{ value }` object (tests). */
|
|
13
|
+
export type BindTarget = HTMLInputElement | HTMLSelectElement | {
|
|
14
|
+
value: string;
|
|
15
|
+
checked?: boolean;
|
|
16
|
+
type?: string;
|
|
17
|
+
addEventListener?: (type: string, fn: () => void) => void;
|
|
18
|
+
removeEventListener?: (type: string, fn: () => void) => void;
|
|
19
|
+
};
|
|
20
|
+
export interface SettingsStore<T> extends SaveStore<T> {
|
|
21
|
+
/**
|
|
22
|
+
* Sets the control from `get()[key]`, writes the control's value to the store on `input` and
|
|
23
|
+
* `change` (coerced to the key's current type: number, boolean or string; a checkbox uses
|
|
24
|
+
* `checked`), and updates the control when the store changes elsewhere. Returns unbind.
|
|
25
|
+
*/
|
|
26
|
+
bind<K extends keyof T & string>(input: BindTarget, key: K): () => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function createSettingsStore<T>(opts: SettingsStoreOptions<T>): SettingsStore<T>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A settings store: `createSaveStore` in the `settings` slot, written synchronously, kept apart
|
|
3
|
+
* from progress, with `bind` for wiring menu controls.
|
|
4
|
+
*/
|
|
5
|
+
import { createSaveStore } from './store.js';
|
|
6
|
+
export function createSettingsStore(opts) {
|
|
7
|
+
const store = createSaveStore({
|
|
8
|
+
...opts,
|
|
9
|
+
slot: opts.slot ?? 'settings',
|
|
10
|
+
version: opts.version ?? 1,
|
|
11
|
+
debounceMs: 0,
|
|
12
|
+
});
|
|
13
|
+
const bind = (input, key) => {
|
|
14
|
+
const checkbox = input.type === 'checkbox';
|
|
15
|
+
const read = () => {
|
|
16
|
+
const current = store.get()[key];
|
|
17
|
+
if (typeof current === 'boolean')
|
|
18
|
+
return checkbox ? !!input.checked : input.value === 'true';
|
|
19
|
+
if (typeof current === 'number') {
|
|
20
|
+
const n = Number(input.value);
|
|
21
|
+
return Number.isFinite(n) ? n : current;
|
|
22
|
+
}
|
|
23
|
+
return input.value;
|
|
24
|
+
};
|
|
25
|
+
const show = () => {
|
|
26
|
+
const v = store.get()[key];
|
|
27
|
+
if (checkbox)
|
|
28
|
+
input.checked = !!v;
|
|
29
|
+
else
|
|
30
|
+
input.value = String(v);
|
|
31
|
+
};
|
|
32
|
+
let writing = false;
|
|
33
|
+
const onInput = () => {
|
|
34
|
+
const next = read();
|
|
35
|
+
if (Object.is(next, store.get()[key]))
|
|
36
|
+
return;
|
|
37
|
+
writing = true;
|
|
38
|
+
const ok = store.update((d) => {
|
|
39
|
+
d[key] = next;
|
|
40
|
+
});
|
|
41
|
+
writing = false;
|
|
42
|
+
if (!ok)
|
|
43
|
+
show();
|
|
44
|
+
};
|
|
45
|
+
show();
|
|
46
|
+
input.addEventListener?.('input', onInput);
|
|
47
|
+
input.addEventListener?.('change', onInput);
|
|
48
|
+
const unsubscribe = store.subscribe(() => {
|
|
49
|
+
if (!writing)
|
|
50
|
+
show();
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
input.removeEventListener?.('input', onInput);
|
|
54
|
+
input.removeEventListener?.('change', onInput);
|
|
55
|
+
unsubscribe();
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
return Object.assign(store, { bind });
|
|
59
|
+
}
|