cubeforge 0.8.1 → 0.8.3

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/dist/index.js CHANGED
@@ -2963,6 +2963,7 @@ function getSamplingKey(sampling) {
2963
2963
  return `${sampling.min}|${sampling.mag}`;
2964
2964
  }
2965
2965
  function getTextureKey(sprite) {
2966
+ if (sprite.dynamicSrc) return sprite.dynamicSrc;
2966
2967
  const src = sprite.image?.src || sprite.src;
2967
2968
  const samplingKey = getSamplingKey(sprite.sampling);
2968
2969
  const suffix = samplingKey ? `:s=${samplingKey}` : "";
@@ -3159,6 +3160,15 @@ var RenderSystem = class {
3159
3160
  // FPS tracking
3160
3161
  frameTimes = [];
3161
3162
  lastTimestamp = 0;
3163
+ // ── Dynamic canvas textures (texSubImage2D optimization) ─────────────────
3164
+ _dynamicCanvases = /* @__PURE__ */ new Map();
3165
+ // ── Idle frame skip ──────────────────────────────────────────────────────
3166
+ _idleSkip = false;
3167
+ _prevSceneHash = -1;
3168
+ _idleFBO = null;
3169
+ _idleTex = null;
3170
+ _idleFBOW = 0;
3171
+ _idleFBOH = 0;
3162
3172
  // ── Post-process ─────────────────────────────────────────────────────────
3163
3173
  _ppOptions = {};
3164
3174
  // Lazily created programs (null until first PP use)
@@ -3210,6 +3220,55 @@ var RenderSystem = class {
3210
3220
  setPostProcessOptions(opts) {
3211
3221
  this._ppOptions = opts;
3212
3222
  }
3223
+ /**
3224
+ * Enable idle frame skip. When enabled the renderer computes a hash of all
3225
+ * visible entity positions, frame indices, and camera state each frame. If
3226
+ * the hash matches the previous frame the GPU draw calls are skipped entirely
3227
+ * and the cached scene is blitted to screen instead.
3228
+ *
3229
+ * Best for scenes that are frequently static (pause menus, cutscenes, HUDs).
3230
+ * Animations and active particles automatically disable the skip for that frame.
3231
+ */
3232
+ setIdleFrameSkip(enabled) {
3233
+ this._idleSkip = enabled;
3234
+ }
3235
+ /**
3236
+ * Register an HTMLCanvasElement as a GPU texture that the renderer can sample.
3237
+ * The canvas is uploaded once on registration. Call {@link markDynamicCanvasDirty}
3238
+ * to schedule a re-upload before the next frame.
3239
+ *
3240
+ * Use the returned `id` as the `dynamicSrc` on a `<Sprite>` component.
3241
+ */
3242
+ registerDynamicCanvas(id, canvas) {
3243
+ const { gl } = this;
3244
+ const tex = gl.createTexture();
3245
+ gl.bindTexture(gl.TEXTURE_2D, tex);
3246
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
3247
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3248
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
3249
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3250
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3251
+ this._dynamicCanvases.set(id, { canvas, tex, dirty: false });
3252
+ this.textures.set(id, tex);
3253
+ this.touchTexture(id);
3254
+ }
3255
+ /**
3256
+ * Mark a dynamic canvas as modified so it will be re-uploaded to the GPU
3257
+ * (via `texSubImage2D`) at the start of the next rendered frame.
3258
+ * Only call this after actually drawing new content to the canvas.
3259
+ */
3260
+ markDynamicCanvasDirty(id) {
3261
+ const entry = this._dynamicCanvases.get(id);
3262
+ if (entry) entry.dirty = true;
3263
+ }
3264
+ /** Remove a registered dynamic canvas and free its GPU texture. */
3265
+ unregisterDynamicCanvas(id) {
3266
+ const entry = this._dynamicCanvases.get(id);
3267
+ if (!entry) return;
3268
+ this.gl.deleteTexture(entry.tex);
3269
+ this._dynamicCanvases.delete(id);
3270
+ this.textures.delete(id);
3271
+ }
3213
3272
  get _anyPPEnabled() {
3214
3273
  const o = this._ppOptions;
3215
3274
  return (o.bloom?.enabled ?? false) || (o.vignette?.enabled ?? false) || (o.chromaticAberration?.enabled ?? false) || (o.scanlines?.enabled ?? false);
@@ -3532,6 +3591,71 @@ var RenderSystem = class {
3532
3591
  break;
3533
3592
  }
3534
3593
  }
3594
+ // ── Scene hash for idle frame skip ───────────────────────────────────────
3595
+ _computeSceneHash(world, camX, camY, zoom, shakeX, shakeY) {
3596
+ let h = 2166136261;
3597
+ const mix = (n) => {
3598
+ const i = n | 0;
3599
+ h = Math.imul(h ^ i & 255, 16777619);
3600
+ h = Math.imul(h ^ i >> 8 & 255, 16777619);
3601
+ h = Math.imul(h ^ i >> 16 & 255, 16777619);
3602
+ h = Math.imul(h ^ i >> 24 & 255, 16777619);
3603
+ };
3604
+ mix(camX * 100);
3605
+ mix(camY * 100);
3606
+ mix(zoom * 1e3);
3607
+ mix(shakeX * 1e3);
3608
+ mix(shakeY * 1e3);
3609
+ for (const id of world.query("Transform", "Sprite")) {
3610
+ const t = world.getComponent(id, "Transform");
3611
+ const s2 = world.getComponent(id, "Sprite");
3612
+ mix(id);
3613
+ mix(t.x * 100);
3614
+ mix(t.y * 100);
3615
+ mix(t.rotation * 1e3);
3616
+ mix(t.scaleX * 1e3);
3617
+ mix(t.scaleY * 1e3);
3618
+ mix(s2.frameIndex);
3619
+ mix(s2.visible ? 1 : 0);
3620
+ mix((s2.opacity ?? 1) * 255);
3621
+ }
3622
+ for (const id of world.query("AnimationState")) {
3623
+ const anim = world.getComponent(id, "AnimationState");
3624
+ if (anim.playing) return -1;
3625
+ }
3626
+ for (const id of world.query("ParticlePool")) {
3627
+ const pool = world.getComponent(id, "ParticlePool");
3628
+ if (pool.active || pool.particles.length > 0) return -1;
3629
+ }
3630
+ for (const id of world.query("SquashStretch")) {
3631
+ const ss = world.getComponent(id, "SquashStretch");
3632
+ mix(ss.currentScaleX * 1e3);
3633
+ mix(ss.currentScaleY * 1e3);
3634
+ }
3635
+ return h;
3636
+ }
3637
+ _ensureIdleFBO(w, h) {
3638
+ if (this._idleFBOW === w && this._idleFBOH === h && this._idleFBO) return;
3639
+ const { gl } = this;
3640
+ if (this._idleFBO) gl.deleteFramebuffer(this._idleFBO);
3641
+ if (this._idleTex) gl.deleteTexture(this._idleTex);
3642
+ const tex = gl.createTexture();
3643
+ gl.bindTexture(gl.TEXTURE_2D, tex);
3644
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
3645
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3646
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
3647
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3648
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3649
+ const fbo = gl.createFramebuffer();
3650
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
3651
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
3652
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3653
+ gl.bindTexture(gl.TEXTURE_2D, null);
3654
+ this._idleFBO = fbo;
3655
+ this._idleTex = tex;
3656
+ this._idleFBOW = w;
3657
+ this._idleFBOH = h;
3658
+ }
3535
3659
  // ── Instanced draw call ────────────────────────────────────────────────────
3536
3660
  flush(count, textureKey, sampling, blendMode, shapeSpriteRef) {
3537
3661
  if (count === 0) return;
@@ -3752,6 +3876,24 @@ var RenderSystem = class {
3752
3876
  ss.currentScaleX += (tScX - ss.currentScaleX) * ss.recovery * dt;
3753
3877
  ss.currentScaleY += (tScY - ss.currentScaleY) * ss.recovery * dt;
3754
3878
  }
3879
+ for (const entry of this._dynamicCanvases.values()) {
3880
+ if (!entry.dirty) continue;
3881
+ gl.bindTexture(gl.TEXTURE_2D, entry.tex);
3882
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, entry.canvas);
3883
+ entry.dirty = false;
3884
+ }
3885
+ if (this._idleSkip) {
3886
+ this._ensureIdleFBO(W, H);
3887
+ const hash = this._computeSceneHash(world, camX, camY, zoom, shakeX, shakeY);
3888
+ if (hash !== -1 && hash === this._prevSceneHash) {
3889
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._idleFBO);
3890
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
3891
+ gl.blitFramebuffer(0, 0, W, H, 0, 0, W, H, gl.COLOR_BUFFER_BIT, gl.NEAREST);
3892
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3893
+ return;
3894
+ }
3895
+ this._prevSceneHash = hash;
3896
+ }
3755
3897
  const ppEnabled = this._anyPPEnabled;
3756
3898
  if (ppEnabled) {
3757
3899
  this._ensurePPPrograms();
@@ -3897,7 +4039,7 @@ var RenderSystem = class {
3897
4039
  const ss = world.getComponent(id, "SquashStretch");
3898
4040
  const scaleXMod = ss ? ss.currentScaleX : 1;
3899
4041
  const scaleYMod = ss ? ss.currentScaleY : 1;
3900
- const hasTexture = sprite.image && sprite.image.complete && sprite.image.naturalWidth > 0;
4042
+ const hasTexture = sprite.image && sprite.image.complete && sprite.image.naturalWidth > 0 || sprite.dynamicSrc !== void 0 && this._dynamicCanvases.has(sprite.dynamicSrc);
3901
4043
  const opacity = sprite.opacity ?? 1;
3902
4044
  let [r, g, b, a] = hasTexture ? [1, 1, 1, 1] : parseCSSColor(sprite.color);
3903
4045
  a *= opacity;
@@ -4368,6 +4510,12 @@ var RenderSystem = class {
4368
4510
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
4369
4511
  this._applyPostProcess(W, H);
4370
4512
  }
4513
+ if (this._idleSkip && this._idleFBO) {
4514
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
4515
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._idleFBO);
4516
+ gl.blitFramebuffer(0, 0, W, H, 0, 0, W, H, gl.COLOR_BUFFER_BIT, gl.NEAREST);
4517
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
4518
+ }
4371
4519
  const now = performance.now();
4372
4520
  if (this.lastTimestamp > 0) {
4373
4521
  this.frameTimes.push(now - this.lastTimestamp);
@@ -7237,6 +7385,7 @@ function Sprite({
7237
7385
  height,
7238
7386
  color = "#ffffff",
7239
7387
  src,
7388
+ dynamicSrc,
7240
7389
  offsetX = 0,
7241
7390
  offsetY = 0,
7242
7391
  zIndex = 0,
@@ -7288,6 +7437,7 @@ function Sprite({
7288
7437
  height,
7289
7438
  color,
7290
7439
  src,
7440
+ dynamicSrc,
7291
7441
  offsetX,
7292
7442
  offsetY,
7293
7443
  zIndex,
@@ -7369,6 +7519,7 @@ function Sprite({
7369
7519
  const comp = engine.ecs.getComponent(entityId, "Sprite");
7370
7520
  if (!comp) return;
7371
7521
  comp.color = color;
7522
+ comp.dynamicSrc = dynamicSrc;
7372
7523
  comp.visible = visible;
7373
7524
  comp.flipX = flipX;
7374
7525
  comp.flipY = flipY;
@@ -7388,6 +7539,7 @@ function Sprite({
7388
7539
  comp.starInnerRadius = starInnerRadius;
7389
7540
  }, [
7390
7541
  color,
7542
+ dynamicSrc,
7391
7543
  visible,
7392
7544
  flipX,
7393
7545
  flipY,
@@ -10289,11 +10441,51 @@ function useWebGLPostProcess(opts) {
10289
10441
  }, [engine, optsKey]);
10290
10442
  }
10291
10443
 
10444
+ // src/hooks/useDynamicCanvas.ts
10445
+ import { useCallback as useCallback7, useEffect as useEffect59, useMemo as useMemo10, useRef as useRef23 } from "react";
10446
+ function useDynamicCanvas(width, height) {
10447
+ const engine = useGame();
10448
+ const idRef = useRef23(`__dynamic__:${Math.random().toString(36).slice(2)}`);
10449
+ const id = idRef.current;
10450
+ const canvas = useMemo10(() => {
10451
+ const c = document.createElement("canvas");
10452
+ c.width = width;
10453
+ c.height = height;
10454
+ return c;
10455
+ }, [width, height]);
10456
+ const ctx = useMemo10(() => canvas.getContext("2d"), [canvas]);
10457
+ useEffect59(() => {
10458
+ const rs = engine.activeRenderSystem;
10459
+ rs.registerDynamicCanvas?.(id, canvas);
10460
+ return () => {
10461
+ rs.unregisterDynamicCanvas?.(id);
10462
+ };
10463
+ }, [engine, id, canvas]);
10464
+ const markDirty = useCallback7(() => {
10465
+ const rs = engine.activeRenderSystem;
10466
+ rs.markDynamicCanvasDirty?.(id);
10467
+ }, [engine, id]);
10468
+ return useMemo10(() => ({ id, canvas, ctx, markDirty }), [id, canvas, ctx, markDirty]);
10469
+ }
10470
+
10471
+ // src/hooks/useIdleFrameSkip.ts
10472
+ import { useEffect as useEffect60 } from "react";
10473
+ function useIdleFrameSkip(enabled = true) {
10474
+ const engine = useGame();
10475
+ useEffect60(() => {
10476
+ const rs = engine.activeRenderSystem;
10477
+ rs.setIdleFrameSkip?.(enabled);
10478
+ return () => {
10479
+ rs.setIdleFrameSkip?.(false);
10480
+ };
10481
+ }, [engine, enabled]);
10482
+ }
10483
+
10292
10484
  // src/hooks/useAudioListener.ts
10293
- import { useEffect as useEffect59, useContext as useContext49 } from "react";
10485
+ import { useEffect as useEffect61, useContext as useContext49 } from "react";
10294
10486
  function useAudioListener() {
10295
10487
  const engine = useContext49(EngineContext);
10296
- useEffect59(() => {
10488
+ useEffect61(() => {
10297
10489
  const eid = engine.ecs.createEntity();
10298
10490
  engine.ecs.addComponent(
10299
10491
  eid,
@@ -10337,13 +10529,13 @@ function useTouch() {
10337
10529
  }
10338
10530
 
10339
10531
  // src/hooks/useGestures.ts
10340
- import { useEffect as useEffect60, useRef as useRef23 } from "react";
10532
+ import { useEffect as useEffect62, useRef as useRef24 } from "react";
10341
10533
  function useGestures(handlers, opts = {}) {
10342
- const handlersRef = useRef23(handlers);
10534
+ const handlersRef = useRef24(handlers);
10343
10535
  handlersRef.current = handlers;
10344
- const optsRef = useRef23(opts);
10536
+ const optsRef = useRef24(opts);
10345
10537
  optsRef.current = opts;
10346
- useEffect60(() => {
10538
+ useEffect62(() => {
10347
10539
  const target = optsRef.current.target ?? window;
10348
10540
  let starts = [];
10349
10541
  let longPressTimer = null;
@@ -10503,14 +10695,14 @@ var touchHapticsControls = {
10503
10695
  };
10504
10696
 
10505
10697
  // src/hooks/useTimer.ts
10506
- import { useRef as useRef24, useCallback as useCallback7, useEffect as useEffect61, useContext as useContext51 } from "react";
10698
+ import { useRef as useRef25, useCallback as useCallback8, useEffect as useEffect63, useContext as useContext51 } from "react";
10507
10699
  function useTimer(duration, onComplete, opts) {
10508
10700
  const engine = useContext51(EngineContext);
10509
- const onCompleteRef = useRef24(onComplete);
10701
+ const onCompleteRef = useRef25(onComplete);
10510
10702
  onCompleteRef.current = onComplete;
10511
- const loopRef = useRef24(opts?.loop ?? false);
10703
+ const loopRef = useRef25(opts?.loop ?? false);
10512
10704
  loopRef.current = opts?.loop ?? false;
10513
- const timerRef = useRef24(null);
10705
+ const timerRef = useRef25(null);
10514
10706
  if (!timerRef.current) {
10515
10707
  timerRef.current = createTimer(
10516
10708
  duration,
@@ -10523,7 +10715,7 @@ function useTimer(duration, onComplete, opts) {
10523
10715
  opts?.autoStart ?? false
10524
10716
  );
10525
10717
  }
10526
- useEffect61(() => {
10718
+ useEffect63(() => {
10527
10719
  const eid = engine.ecs.createEntity();
10528
10720
  engine.ecs.addComponent(
10529
10721
  eid,
@@ -10535,13 +10727,13 @@ function useTimer(duration, onComplete, opts) {
10535
10727
  if (engine.ecs.hasEntity(eid)) engine.ecs.destroyEntity(eid);
10536
10728
  };
10537
10729
  }, [engine.ecs]);
10538
- const start2 = useCallback7(() => {
10730
+ const start2 = useCallback8(() => {
10539
10731
  timerRef.current.start();
10540
10732
  }, []);
10541
- const stop = useCallback7(() => {
10733
+ const stop = useCallback8(() => {
10542
10734
  timerRef.current.stop();
10543
10735
  }, []);
10544
- const reset = useCallback7(() => {
10736
+ const reset = useCallback8(() => {
10545
10737
  timerRef.current.reset();
10546
10738
  }, []);
10547
10739
  return {
@@ -10564,15 +10756,15 @@ function useTimer(duration, onComplete, opts) {
10564
10756
  }
10565
10757
 
10566
10758
  // src/hooks/useCoroutine.ts
10567
- import { useRef as useRef25, useEffect as useEffect62, useContext as useContext52 } from "react";
10759
+ import { useRef as useRef26, useEffect as useEffect64, useContext as useContext52 } from "react";
10568
10760
  var wait = (seconds) => ({ type: "wait", seconds });
10569
10761
  var waitFrames = (frames) => ({ type: "waitFrames", frames });
10570
10762
  var waitUntil = (condition) => ({ type: "waitUntil", condition });
10571
10763
  var nextCoroutineId = 1;
10572
10764
  function useCoroutine() {
10573
10765
  const engine = useContext52(EngineContext);
10574
- const coroutinesRef = useRef25(/* @__PURE__ */ new Map());
10575
- useEffect62(() => {
10766
+ const coroutinesRef = useRef26(/* @__PURE__ */ new Map());
10767
+ useEffect64(() => {
10576
10768
  const eid = engine.ecs.createEntity();
10577
10769
  engine.ecs.addComponent(
10578
10770
  eid,
@@ -10637,33 +10829,33 @@ function useCoroutine() {
10637
10829
  return coroutinesRef.current.size;
10638
10830
  }
10639
10831
  };
10640
- const controlsRef = useRef25(controls);
10832
+ const controlsRef = useRef26(controls);
10641
10833
  return controlsRef.current;
10642
10834
  }
10643
10835
 
10644
10836
  // src/hooks/useSceneManager.ts
10645
- import { useState as useState13, useCallback as useCallback8, useRef as useRef26 } from "react";
10837
+ import { useState as useState13, useCallback as useCallback9, useRef as useRef27 } from "react";
10646
10838
  function useSceneManager(initialScene) {
10647
10839
  const [stack, setStack] = useState13([initialScene]);
10648
- const stackRef = useRef26(stack);
10840
+ const stackRef = useRef27(stack);
10649
10841
  stackRef.current = stack;
10650
- const push = useCallback8((scene) => {
10842
+ const push = useCallback9((scene) => {
10651
10843
  setStack((prev) => [...prev, scene]);
10652
10844
  }, []);
10653
- const pop = useCallback8(() => {
10845
+ const pop = useCallback9(() => {
10654
10846
  const prev = stackRef.current;
10655
10847
  if (prev.length <= 1) return void 0;
10656
10848
  const popped = prev[prev.length - 1];
10657
10849
  setStack(prev.slice(0, -1));
10658
10850
  return popped;
10659
10851
  }, []);
10660
- const replace = useCallback8((scene) => {
10852
+ const replace = useCallback9((scene) => {
10661
10853
  setStack((prev) => [...prev.slice(0, -1), scene]);
10662
10854
  }, []);
10663
- const reset = useCallback8((scene) => {
10855
+ const reset = useCallback9((scene) => {
10664
10856
  setStack([scene]);
10665
10857
  }, []);
10666
- const has = useCallback8(
10858
+ const has = useCallback9(
10667
10859
  (scene) => {
10668
10860
  return stack.includes(scene);
10669
10861
  },
@@ -10681,7 +10873,7 @@ function useSceneManager(initialScene) {
10681
10873
  }
10682
10874
 
10683
10875
  // src/hooks/useSceneTransition.ts
10684
- import { useState as useState14, useCallback as useCallback9, useRef as useRef27, useEffect as useEffect63 } from "react";
10876
+ import { useState as useState14, useCallback as useCallback10, useRef as useRef28, useEffect as useEffect65 } from "react";
10685
10877
  function useSceneTransition(initialScene, defaultTransition) {
10686
10878
  const [stack, setStack] = useState14([initialScene]);
10687
10879
  const [transState, setTransState] = useState14({
@@ -10690,11 +10882,11 @@ function useSceneTransition(initialScene, defaultTransition) {
10690
10882
  effect: null,
10691
10883
  pendingAction: null
10692
10884
  });
10693
- const stackRef = useRef27(stack);
10885
+ const stackRef = useRef28(stack);
10694
10886
  stackRef.current = stack;
10695
- const transRef = useRef27(transState);
10887
+ const transRef = useRef28(transState);
10696
10888
  transRef.current = transState;
10697
- useEffect63(() => {
10889
+ useEffect65(() => {
10698
10890
  if (transState.phase === "idle") return;
10699
10891
  const effect = transState.effect;
10700
10892
  if (!effect || effect.type === "instant") return;
@@ -10723,7 +10915,7 @@ function useSceneTransition(initialScene, defaultTransition) {
10723
10915
  rafId2 = requestAnimationFrame(animate);
10724
10916
  return () => cancelAnimationFrame(rafId2);
10725
10917
  }, [transState.phase, transState.effect]);
10726
- const startTransition = useCallback9(
10918
+ const startTransition = useCallback10(
10727
10919
  (effect, action) => {
10728
10920
  const eff = effect ?? defaultTransition ?? { type: "instant" };
10729
10921
  if (eff.type === "instant" || isReducedMotionPreferred()) {
@@ -10739,13 +10931,13 @@ function useSceneTransition(initialScene, defaultTransition) {
10739
10931
  },
10740
10932
  [defaultTransition]
10741
10933
  );
10742
- const push = useCallback9(
10934
+ const push = useCallback10(
10743
10935
  (scene, transition) => {
10744
10936
  startTransition(transition, () => setStack((prev) => [...prev, scene]));
10745
10937
  },
10746
10938
  [startTransition]
10747
10939
  );
10748
- const pop = useCallback9(
10940
+ const pop = useCallback10(
10749
10941
  (transition) => {
10750
10942
  const prev = stackRef.current;
10751
10943
  if (prev.length <= 1) return void 0;
@@ -10755,13 +10947,13 @@ function useSceneTransition(initialScene, defaultTransition) {
10755
10947
  },
10756
10948
  [startTransition]
10757
10949
  );
10758
- const replace = useCallback9(
10950
+ const replace = useCallback10(
10759
10951
  (scene, transition) => {
10760
10952
  startTransition(transition, () => setStack((prev) => [...prev.slice(0, -1), scene]));
10761
10953
  },
10762
10954
  [startTransition]
10763
10955
  );
10764
- const reset = useCallback9(
10956
+ const reset = useCallback10(
10765
10957
  (scene, transition) => {
10766
10958
  startTransition(transition, () => setStack([scene]));
10767
10959
  },
@@ -10862,10 +11054,10 @@ function SceneTransitionOverlay({ controls }) {
10862
11054
  }
10863
11055
 
10864
11056
  // src/hooks/useHitstop.ts
10865
- import { useContext as useContext53, useCallback as useCallback10 } from "react";
11057
+ import { useContext as useContext53, useCallback as useCallback11 } from "react";
10866
11058
  function useHitstop() {
10867
11059
  const engine = useContext53(EngineContext);
10868
- const freeze = useCallback10(
11060
+ const freeze = useCallback11(
10869
11061
  (seconds) => {
10870
11062
  engine.loop.hitPause(seconds);
10871
11063
  },
@@ -10875,16 +11067,16 @@ function useHitstop() {
10875
11067
  }
10876
11068
 
10877
11069
  // src/hooks/useInputBuffer.ts
10878
- import { useMemo as useMemo10 } from "react";
11070
+ import { useMemo as useMemo11 } from "react";
10879
11071
  function useInputBuffer(opts) {
10880
- return useMemo10(() => new InputBuffer(opts), []);
11072
+ return useMemo11(() => new InputBuffer(opts), []);
10881
11073
  }
10882
11074
 
10883
11075
  // src/hooks/useComboDetector.ts
10884
- import { useMemo as useMemo11, useRef as useRef28 } from "react";
11076
+ import { useMemo as useMemo12, useRef as useRef29 } from "react";
10885
11077
  function useComboDetector(combos) {
10886
- const lastComboRef = useRef28(null);
10887
- const result = useMemo11(() => {
11078
+ const lastComboRef = useRef29(null);
11079
+ const result = useMemo12(() => {
10888
11080
  const detector = new ComboDetector({ combos });
10889
11081
  const api = {
10890
11082
  feed(action) {
@@ -10906,11 +11098,11 @@ function useComboDetector(combos) {
10906
11098
  }
10907
11099
 
10908
11100
  // src/hooks/useParent.ts
10909
- import { useEffect as useEffect64, useContext as useContext54 } from "react";
11101
+ import { useEffect as useEffect66, useContext as useContext54 } from "react";
10910
11102
  function useParent(childEntityId, parentEntityId) {
10911
11103
  const engine = useContext54(EngineContext);
10912
11104
  if (!engine) throw new Error("useParent must be used inside <Game>");
10913
- useEffect64(() => {
11105
+ useEffect66(() => {
10914
11106
  setParent(engine.ecs, childEntityId, parentEntityId);
10915
11107
  return () => {
10916
11108
  removeParent(engine.ecs, childEntityId);
@@ -10919,7 +11111,7 @@ function useParent(childEntityId, parentEntityId) {
10919
11111
  }
10920
11112
 
10921
11113
  // src/hooks/useAccessibility.ts
10922
- import { useCallback as useCallback11, useSyncExternalStore } from "react";
11114
+ import { useCallback as useCallback12, useSyncExternalStore } from "react";
10923
11115
  var _version = 0;
10924
11116
  var _listeners = /* @__PURE__ */ new Set();
10925
11117
  function subscribe(cb) {
@@ -10931,12 +11123,12 @@ function getSnapshot() {
10931
11123
  }
10932
11124
  function useAccessibility() {
10933
11125
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
10934
- const setOptions = useCallback11((opts) => {
11126
+ const setOptions = useCallback12((opts) => {
10935
11127
  setAccessibilityOptions(opts);
10936
11128
  _version++;
10937
11129
  for (const cb of _listeners) cb();
10938
11130
  }, []);
10939
- const announce = useCallback11((text, priority) => {
11131
+ const announce = useCallback12((text, priority) => {
10940
11132
  announceToScreenReader(text, priority);
10941
11133
  }, []);
10942
11134
  return {
@@ -10947,18 +11139,18 @@ function useAccessibility() {
10947
11139
  }
10948
11140
 
10949
11141
  // src/hooks/useHMR.ts
10950
- import { useEffect as useEffect65, useRef as useRef29, useMemo as useMemo12 } from "react";
11142
+ import { useEffect as useEffect67, useRef as useRef30, useMemo as useMemo13 } from "react";
10951
11143
  var idCounter = 0;
10952
11144
  function useHMR(hmrKey) {
10953
- const idRef = useRef29(null);
11145
+ const idRef = useRef30(null);
10954
11146
  if (idRef.current === null) {
10955
11147
  idRef.current = hmrKey ?? `__hmr_${idCounter++}`;
10956
11148
  }
10957
11149
  const key = idRef.current;
10958
11150
  const isHotReload = hmrLoadState(key) !== void 0;
10959
- const disposeRef = useRef29(null);
10960
- const acceptRef = useRef29(null);
10961
- useEffect65(() => {
11151
+ const disposeRef = useRef30(null);
11152
+ const acceptRef = useRef30(null);
11153
+ useEffect67(() => {
10962
11154
  const hot = import.meta.hot;
10963
11155
  if (!hot) return;
10964
11156
  hot.dispose(() => {
@@ -10972,7 +11164,7 @@ function useHMR(hmrKey) {
10972
11164
  acceptRef.current(prev);
10973
11165
  }
10974
11166
  }, [key]);
10975
- return useMemo12(
11167
+ return useMemo13(
10976
11168
  () => ({
10977
11169
  onDispose(handler) {
10978
11170
  disposeRef.current = handler;
@@ -10991,11 +11183,11 @@ function useHMR(hmrKey) {
10991
11183
  }
10992
11184
 
10993
11185
  // src/hooks/useSquashStretch.ts
10994
- import { useCallback as useCallback12, useContext as useContext55 } from "react";
11186
+ import { useCallback as useCallback13, useContext as useContext55 } from "react";
10995
11187
  function useSquashStretch() {
10996
11188
  const engine = useContext55(EngineContext);
10997
11189
  const entityId = useContext55(EntityContext);
10998
- const trigger = useCallback12(
11190
+ const trigger = useCallback13(
10999
11191
  (scaleX, scaleY) => {
11000
11192
  const ss = engine.ecs.getComponent(entityId, "SquashStretch");
11001
11193
  if (!ss) return;
@@ -11008,16 +11200,16 @@ function useSquashStretch() {
11008
11200
  }
11009
11201
 
11010
11202
  // src/hooks/useHistory.ts
11011
- import { useCallback as useCallback13, useContext as useContext56, useEffect as useEffect66, useRef as useRef30, useState as useState15 } from "react";
11203
+ import { useCallback as useCallback14, useContext as useContext56, useEffect as useEffect68, useRef as useRef31, useState as useState15 } from "react";
11012
11204
  function useHistory(options) {
11013
11205
  const engine = useContext56(EngineContext);
11014
11206
  const capacity = options?.capacity ?? 50;
11015
11207
  const bindKeys = options?.bindKeyboardShortcuts ?? false;
11016
- const stackRef = useRef30([]);
11017
- const indexRef = useRef30(-1);
11208
+ const stackRef = useRef31([]);
11209
+ const indexRef = useRef31(-1);
11018
11210
  const [version, setVersion] = useState15(0);
11019
- const bump = useCallback13(() => setVersion((v) => v + 1), []);
11020
- const push = useCallback13(() => {
11211
+ const bump = useCallback14(() => setVersion((v) => v + 1), []);
11212
+ const push = useCallback14(() => {
11021
11213
  const snap = engine.ecs.getSnapshot();
11022
11214
  const stack = stackRef.current;
11023
11215
  if (indexRef.current < stack.length - 1) {
@@ -11028,14 +11220,14 @@ function useHistory(options) {
11028
11220
  indexRef.current = stack.length - 1;
11029
11221
  bump();
11030
11222
  }, [engine, capacity, bump]);
11031
- const undo = useCallback13(() => {
11223
+ const undo = useCallback14(() => {
11032
11224
  if (indexRef.current <= 0) return;
11033
11225
  indexRef.current -= 1;
11034
11226
  engine.ecs.restoreSnapshot(stackRef.current[indexRef.current]);
11035
11227
  engine.loop.markDirty();
11036
11228
  bump();
11037
11229
  }, [engine, bump]);
11038
- const redo = useCallback13(() => {
11230
+ const redo = useCallback14(() => {
11039
11231
  const stack = stackRef.current;
11040
11232
  if (indexRef.current >= stack.length - 1) return;
11041
11233
  indexRef.current += 1;
@@ -11043,12 +11235,12 @@ function useHistory(options) {
11043
11235
  engine.loop.markDirty();
11044
11236
  bump();
11045
11237
  }, [engine, bump]);
11046
- const clear = useCallback13(() => {
11238
+ const clear = useCallback14(() => {
11047
11239
  stackRef.current = [];
11048
11240
  indexRef.current = -1;
11049
11241
  bump();
11050
11242
  }, [bump]);
11051
- useEffect66(() => {
11243
+ useEffect68(() => {
11052
11244
  if (!bindKeys) return;
11053
11245
  const onKey = (e) => {
11054
11246
  const mod = e.ctrlKey || e.metaKey;
@@ -11073,13 +11265,13 @@ function useHistory(options) {
11073
11265
  }
11074
11266
 
11075
11267
  // src/hooks/useSelection.tsx
11076
- import { createContext as createContext2, useCallback as useCallback14, useContext as useContext57, useEffect as useEffect67, useMemo as useMemo13, useState as useState16 } from "react";
11268
+ import { createContext as createContext2, useCallback as useCallback15, useContext as useContext57, useEffect as useEffect69, useMemo as useMemo14, useState as useState16 } from "react";
11077
11269
  import { jsx as jsx15 } from "react/jsx-runtime";
11078
11270
  var SelectionContext = createContext2(null);
11079
11271
  function Selection({ initial, onChange, children }) {
11080
11272
  const engine = useContext57(EngineContext);
11081
11273
  const [selected, setSelected] = useState16(initial ?? []);
11082
- useEffect67(() => {
11274
+ useEffect69(() => {
11083
11275
  if (!engine) return;
11084
11276
  return engine.ecs.onDestroyEntity((destroyedId) => {
11085
11277
  setSelected((prev) => {
@@ -11090,14 +11282,14 @@ function Selection({ initial, onChange, children }) {
11090
11282
  });
11091
11283
  });
11092
11284
  }, [engine, onChange]);
11093
- const commit = useCallback14(
11285
+ const commit = useCallback15(
11094
11286
  (next) => {
11095
11287
  setSelected(next);
11096
11288
  onChange?.(next);
11097
11289
  },
11098
11290
  [onChange]
11099
11291
  );
11100
- const select = useCallback14(
11292
+ const select = useCallback15(
11101
11293
  (id, opts) => {
11102
11294
  if (opts?.additive) {
11103
11295
  setSelected((prev) => {
@@ -11112,7 +11304,7 @@ function Selection({ initial, onChange, children }) {
11112
11304
  },
11113
11305
  [commit, onChange]
11114
11306
  );
11115
- const deselect = useCallback14(
11307
+ const deselect = useCallback15(
11116
11308
  (id) => {
11117
11309
  setSelected((prev) => {
11118
11310
  if (!prev.includes(id)) return prev;
@@ -11123,7 +11315,7 @@ function Selection({ initial, onChange, children }) {
11123
11315
  },
11124
11316
  [onChange]
11125
11317
  );
11126
- const toggle = useCallback14(
11318
+ const toggle = useCallback15(
11127
11319
  (id) => {
11128
11320
  setSelected((prev) => {
11129
11321
  const next = prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id];
@@ -11133,9 +11325,9 @@ function Selection({ initial, onChange, children }) {
11133
11325
  },
11134
11326
  [onChange]
11135
11327
  );
11136
- const clear = useCallback14(() => commit([]), [commit]);
11137
- const isSelected = useCallback14((id) => selected.includes(id), [selected]);
11138
- const value = useMemo13(
11328
+ const clear = useCallback15(() => commit([]), [commit]);
11329
+ const isSelected = useCallback15((id) => selected.includes(id), [selected]);
11330
+ const value = useMemo14(
11139
11331
  () => ({ selected, select, deselect, toggle, clear, isSelected }),
11140
11332
  [selected, select, deselect, toggle, clear, isSelected]
11141
11333
  );
@@ -11148,10 +11340,10 @@ function useSelection() {
11148
11340
  }
11149
11341
 
11150
11342
  // src/components/TransformHandles.tsx
11151
- import { useContext as useContext58, useEffect as useEffect69, useRef as useRef31 } from "react";
11343
+ import { useContext as useContext58, useEffect as useEffect71, useRef as useRef32 } from "react";
11152
11344
 
11153
11345
  // src/hooks/useOverlayTick.ts
11154
- import { useEffect as useEffect68 } from "react";
11346
+ import { useEffect as useEffect70 } from "react";
11155
11347
  var callbacks = /* @__PURE__ */ new Set();
11156
11348
  var rafId = 0;
11157
11349
  function tick() {
@@ -11174,7 +11366,7 @@ function register(cb) {
11174
11366
  };
11175
11367
  }
11176
11368
  function useOverlayTick(callback, deps = []) {
11177
- useEffect68(() => {
11369
+ useEffect70(() => {
11178
11370
  return register(callback);
11179
11371
  }, deps);
11180
11372
  }
@@ -11190,8 +11382,8 @@ function TransformHandles({
11190
11382
  }) {
11191
11383
  const engine = useContext58(EngineContext);
11192
11384
  const selection = useSelection();
11193
- const overlayRef = useRef31(null);
11194
- const dragRef = useRef31(null);
11385
+ const overlayRef = useRef32(null);
11386
+ const dragRef = useRef32(null);
11195
11387
  useOverlayTick(() => {
11196
11388
  if (!engine) return;
11197
11389
  const overlay = overlayRef.current;
@@ -11226,7 +11418,7 @@ function TransformHandles({
11226
11418
  node.style.transform = `translate(-50%, -50%) rotate(${t.rotation}rad)`;
11227
11419
  }
11228
11420
  }, [engine, selection.selected]);
11229
- useEffect69(() => {
11421
+ useEffect71(() => {
11230
11422
  if (!engine) return;
11231
11423
  const onMove = (e) => {
11232
11424
  const drag = dragRef.current;
@@ -11450,7 +11642,7 @@ function worldToScreenCss(engine, wx, wy) {
11450
11642
  }
11451
11643
 
11452
11644
  // src/hooks/useSnap.ts
11453
- import { useCallback as useCallback15, useContext as useContext59 } from "react";
11645
+ import { useCallback as useCallback16, useContext as useContext59 } from "react";
11454
11646
  function useSnap(options) {
11455
11647
  const engine = useContext59(EngineContext);
11456
11648
  const gridX = typeof options?.grid === "number" ? options.grid : options?.grid?.x ?? 0;
@@ -11458,7 +11650,7 @@ function useSnap(options) {
11458
11650
  const snapToEntities = options?.snapToEntities ?? false;
11459
11651
  const threshold = options?.threshold ?? 8;
11460
11652
  const exclude = options?.exclude;
11461
- const snapToGrid = useCallback15(
11653
+ const snapToGrid = useCallback16(
11462
11654
  (x, y) => {
11463
11655
  const sx = gridX > 0 ? Math.round(x / gridX) * gridX : x;
11464
11656
  const sy = gridY > 0 ? Math.round(y / gridY) * gridY : y;
@@ -11466,7 +11658,7 @@ function useSnap(options) {
11466
11658
  },
11467
11659
  [gridX, gridY]
11468
11660
  );
11469
- const snap = useCallback15(
11661
+ const snap = useCallback16(
11470
11662
  (x, y) => {
11471
11663
  let outX = x;
11472
11664
  let outY = y;
@@ -11541,7 +11733,7 @@ function getBounds(ecs, id) {
11541
11733
  }
11542
11734
 
11543
11735
  // src/components/EditableText.tsx
11544
- import { useContext as useContext60, useEffect as useEffect70, useRef as useRef32 } from "react";
11736
+ import { useContext as useContext60, useEffect as useEffect72, useRef as useRef33 } from "react";
11545
11737
  import { jsx as jsx17 } from "react/jsx-runtime";
11546
11738
  function EditableText({
11547
11739
  value,
@@ -11565,8 +11757,8 @@ function EditableText({
11565
11757
  }) {
11566
11758
  const engine = useContext60(EngineContext);
11567
11759
  const entityId = useContext60(EntityContext);
11568
- const containerRef = useRef32(null);
11569
- const inputRef = useRef32(null);
11760
+ const containerRef = useRef33(null);
11761
+ const inputRef = useRef33(null);
11570
11762
  useOverlayTick(() => {
11571
11763
  if (!engine || entityId === null || entityId === void 0) return;
11572
11764
  const container = containerRef.current;
@@ -11593,7 +11785,7 @@ function EditableText({
11593
11785
  container.style.setProperty("--cubeforge-canvas-left", `${rect.left + window.scrollX}px`);
11594
11786
  container.style.setProperty("--cubeforge-canvas-top", `${rect.top + window.scrollY}px`);
11595
11787
  }, [engine, entityId, width, height, fontSize]);
11596
- useEffect70(() => {
11788
+ useEffect72(() => {
11597
11789
  if (autoFocus) inputRef.current?.focus();
11598
11790
  }, [autoFocus]);
11599
11791
  if (!engine) return null;
@@ -11724,7 +11916,7 @@ function copyRegion(source, region, scale) {
11724
11916
  }
11725
11917
 
11726
11918
  // src/components/A11yNode.tsx
11727
- import { useContext as useContext61, useRef as useRef33 } from "react";
11919
+ import { useContext as useContext61, useRef as useRef34 } from "react";
11728
11920
  import { jsx as jsx18 } from "react/jsx-runtime";
11729
11921
  function A11yNode({
11730
11922
  label,
@@ -11740,7 +11932,7 @@ function A11yNode({
11740
11932
  }) {
11741
11933
  const engine = useContext61(EngineContext);
11742
11934
  const entityId = useContext61(EntityContext);
11743
- const nodeRef = useRef33(null);
11935
+ const nodeRef = useRef34(null);
11744
11936
  useOverlayTick(() => {
11745
11937
  if (!engine || entityId === null || entityId === void 0) return;
11746
11938
  const node = nodeRef.current;
@@ -11829,7 +12021,7 @@ function worldToScreenCss3(engine, wx, wy) {
11829
12021
  }
11830
12022
 
11831
12023
  // src/components/VectorPath.tsx
11832
- import { useContext as useContext62, useRef as useRef34 } from "react";
12024
+ import { useContext as useContext62, useRef as useRef35 } from "react";
11833
12025
  import { jsx as jsx19 } from "react/jsx-runtime";
11834
12026
  function VectorPath({
11835
12027
  d,
@@ -11845,7 +12037,7 @@ function VectorPath({
11845
12037
  }) {
11846
12038
  const engine = useContext62(EngineContext);
11847
12039
  const entityId = useContext62(EntityContext);
11848
- const svgRef = useRef34(null);
12040
+ const svgRef = useRef35(null);
11849
12041
  useOverlayTick(() => {
11850
12042
  if (!engine || entityId === null || entityId === void 0) return;
11851
12043
  const svg = svgRef.current;
@@ -11906,12 +12098,12 @@ function worldToScreenCss4(engine, wx, wy) {
11906
12098
  }
11907
12099
 
11908
12100
  // src/hooks/useGrid.ts
11909
- import { useCallback as useCallback16, useContext as useContext63, useMemo as useMemo14, useRef as useRef35, useState as useState17 } from "react";
12101
+ import { useCallback as useCallback17, useContext as useContext63, useMemo as useMemo15, useRef as useRef36, useState as useState17 } from "react";
11910
12102
  function useGrid({ width, height, fill }) {
11911
12103
  const engine = useContext63(EngineContext);
11912
12104
  const [version, setVersion] = useState17(0);
11913
- const bump = useCallback16(() => setVersion((v) => v + 1), []);
11914
- const dataRef = useRef35(null);
12105
+ const bump = useCallback17(() => setVersion((v) => v + 1), []);
12106
+ const dataRef = useRef36(null);
11915
12107
  if (dataRef.current === null) {
11916
12108
  const arr = new Array(width * height);
11917
12109
  if (typeof fill === "function") {
@@ -11926,19 +12118,19 @@ function useGrid({ width, height, fill }) {
11926
12118
  }
11927
12119
  dataRef.current = arr;
11928
12120
  }
11929
- const markChanged = useCallback16(() => {
12121
+ const markChanged = useCallback17(() => {
11930
12122
  engine?.loop.markDirty();
11931
12123
  bump();
11932
12124
  }, [engine, bump]);
11933
- const inBounds = useCallback16((x, y) => x >= 0 && y >= 0 && x < width && y < height, [width, height]);
11934
- const get = useCallback16(
12125
+ const inBounds = useCallback17((x, y) => x >= 0 && y >= 0 && x < width && y < height, [width, height]);
12126
+ const get = useCallback17(
11935
12127
  (x, y) => {
11936
12128
  if (!inBounds(x, y)) return void 0;
11937
12129
  return dataRef.current[y * width + x];
11938
12130
  },
11939
12131
  [width, inBounds]
11940
12132
  );
11941
- const set = useCallback16(
12133
+ const set = useCallback17(
11942
12134
  (x, y, value) => {
11943
12135
  if (!inBounds(x, y)) return;
11944
12136
  const data = dataRef.current;
@@ -11949,7 +12141,7 @@ function useGrid({ width, height, fill }) {
11949
12141
  },
11950
12142
  [width, inBounds, markChanged]
11951
12143
  );
11952
- const swap = useCallback16(
12144
+ const swap = useCallback17(
11953
12145
  (ax, ay, bx, by) => {
11954
12146
  if (!inBounds(ax, ay) || !inBounds(bx, by)) return;
11955
12147
  const data = dataRef.current;
@@ -11963,7 +12155,7 @@ function useGrid({ width, height, fill }) {
11963
12155
  },
11964
12156
  [width, inBounds, markChanged]
11965
12157
  );
11966
- const forEach = useCallback16(
12158
+ const forEach = useCallback17(
11967
12159
  (cb) => {
11968
12160
  const data = dataRef.current;
11969
12161
  for (let y = 0; y < height; y++) {
@@ -11974,7 +12166,7 @@ function useGrid({ width, height, fill }) {
11974
12166
  },
11975
12167
  [width, height]
11976
12168
  );
11977
- const find = useCallback16(
12169
+ const find = useCallback17(
11978
12170
  (pred) => {
11979
12171
  const data = dataRef.current;
11980
12172
  for (let y = 0; y < height; y++) {
@@ -11987,7 +12179,7 @@ function useGrid({ width, height, fill }) {
11987
12179
  },
11988
12180
  [width, height]
11989
12181
  );
11990
- const count = useCallback16(
12182
+ const count = useCallback17(
11991
12183
  (pred) => {
11992
12184
  const data = dataRef.current;
11993
12185
  let n = 0;
@@ -12000,7 +12192,7 @@ function useGrid({ width, height, fill }) {
12000
12192
  },
12001
12193
  [width, height]
12002
12194
  );
12003
- const neighbors = useCallback16(
12195
+ const neighbors = useCallback17(
12004
12196
  (x, y, diagonal = false) => {
12005
12197
  const data = dataRef.current;
12006
12198
  const result = [];
@@ -12028,7 +12220,7 @@ function useGrid({ width, height, fill }) {
12028
12220
  },
12029
12221
  [width, inBounds]
12030
12222
  );
12031
- const fillAll = useCallback16(
12223
+ const fillAll = useCallback17(
12032
12224
  (value) => {
12033
12225
  const data = dataRef.current;
12034
12226
  if (typeof value === "function") {
@@ -12045,7 +12237,7 @@ function useGrid({ width, height, fill }) {
12045
12237
  },
12046
12238
  [width, height, markChanged]
12047
12239
  );
12048
- const toArray = useCallback16(() => {
12240
+ const toArray = useCallback17(() => {
12049
12241
  const data = dataRef.current;
12050
12242
  const out = new Array(height);
12051
12243
  for (let y = 0; y < height; y++) {
@@ -12055,7 +12247,7 @@ function useGrid({ width, height, fill }) {
12055
12247
  }
12056
12248
  return out;
12057
12249
  }, [width, height]);
12058
- const fromArray = useCallback16(
12250
+ const fromArray = useCallback17(
12059
12251
  (arr) => {
12060
12252
  const data = dataRef.current;
12061
12253
  for (let y = 0; y < height; y++) {
@@ -12070,7 +12262,7 @@ function useGrid({ width, height, fill }) {
12070
12262
  [width, height, markChanged]
12071
12263
  );
12072
12264
  void version;
12073
- return useMemo14(
12265
+ return useMemo15(
12074
12266
  () => ({
12075
12267
  width,
12076
12268
  height,
@@ -12091,7 +12283,7 @@ function useGrid({ width, height, fill }) {
12091
12283
  }
12092
12284
 
12093
12285
  // src/hooks/useTurnSystem.ts
12094
- import { useCallback as useCallback17, useContext as useContext64, useEffect as useEffect71, useMemo as useMemo15, useRef as useRef36, useState as useState18 } from "react";
12286
+ import { useCallback as useCallback18, useContext as useContext64, useEffect as useEffect73, useMemo as useMemo16, useRef as useRef37, useState as useState18 } from "react";
12095
12287
  function useTurnSystem({
12096
12288
  players,
12097
12289
  initialIndex = 0,
@@ -12104,11 +12296,11 @@ function useTurnSystem({
12104
12296
  const [activeIndex, setActiveIndex] = useState18(initialIndex % players.length);
12105
12297
  const [turn, setTurn] = useState18(0);
12106
12298
  const [isPending, setIsPending] = useState18(false);
12107
- const pendingRafId = useRef36(null);
12108
- const pendingRemaining = useRef36(0);
12109
- const pendingTarget = useRef36(null);
12110
- const pendingLastTime = useRef36(0);
12111
- const clearPending = useCallback17(() => {
12299
+ const pendingRafId = useRef37(null);
12300
+ const pendingRemaining = useRef37(0);
12301
+ const pendingTarget = useRef37(null);
12302
+ const pendingLastTime = useRef37(0);
12303
+ const clearPending = useCallback18(() => {
12112
12304
  if (pendingRafId.current !== null) {
12113
12305
  cancelAnimationFrame(pendingRafId.current);
12114
12306
  pendingRafId.current = null;
@@ -12116,13 +12308,13 @@ function useTurnSystem({
12116
12308
  pendingTarget.current = null;
12117
12309
  setIsPending(false);
12118
12310
  }, []);
12119
- const startedOnce = useRef36(false);
12120
- useEffect71(() => {
12311
+ const startedOnce = useRef37(false);
12312
+ useEffect73(() => {
12121
12313
  if (startedOnce.current) return;
12122
12314
  startedOnce.current = true;
12123
12315
  onTurnStart?.({ player: players[activeIndex], index: activeIndex, turn: 0 });
12124
12316
  }, []);
12125
- const applyChange = useCallback17(
12317
+ const applyChange = useCallback18(
12126
12318
  (nextIndex) => {
12127
12319
  const curr = players[activeIndex];
12128
12320
  onTurnEnd?.({ player: curr, index: activeIndex, turn });
@@ -12135,7 +12327,7 @@ function useTurnSystem({
12135
12327
  },
12136
12328
  [players, activeIndex, turn, onTurnStart, onTurnEnd, engine]
12137
12329
  );
12138
- const scheduleChange = useCallback17(
12330
+ const scheduleChange = useCallback18(
12139
12331
  (nextIndex) => {
12140
12332
  clearPending();
12141
12333
  if (aiDelay <= 0) {
@@ -12167,9 +12359,9 @@ function useTurnSystem({
12167
12359
  },
12168
12360
  [aiDelay, applyChange, clearPending, engine]
12169
12361
  );
12170
- const nextTurn = useCallback17(() => scheduleChange(activeIndex + 1), [scheduleChange, activeIndex]);
12171
- const prevTurn = useCallback17(() => scheduleChange(activeIndex - 1), [scheduleChange, activeIndex]);
12172
- const skipTo = useCallback17(
12362
+ const nextTurn = useCallback18(() => scheduleChange(activeIndex + 1), [scheduleChange, activeIndex]);
12363
+ const prevTurn = useCallback18(() => scheduleChange(activeIndex - 1), [scheduleChange, activeIndex]);
12364
+ const skipTo = useCallback18(
12173
12365
  (target) => {
12174
12366
  let idx;
12175
12367
  if (typeof target === "number") {
@@ -12182,19 +12374,19 @@ function useTurnSystem({
12182
12374
  },
12183
12375
  [players, scheduleChange]
12184
12376
  );
12185
- const reset = useCallback17(() => {
12377
+ const reset = useCallback18(() => {
12186
12378
  clearPending();
12187
12379
  setActiveIndex(initialIndex % players.length);
12188
12380
  setTurn(0);
12189
12381
  engine?.loop.markDirty();
12190
12382
  }, [players.length, initialIndex, clearPending, engine]);
12191
- useEffect71(
12383
+ useEffect73(
12192
12384
  () => () => {
12193
12385
  if (pendingRafId.current !== null) cancelAnimationFrame(pendingRafId.current);
12194
12386
  },
12195
12387
  []
12196
12388
  );
12197
- return useMemo15(
12389
+ return useMemo16(
12198
12390
  () => ({
12199
12391
  activePlayer: players[activeIndex],
12200
12392
  activeIndex,
@@ -12210,13 +12402,13 @@ function useTurnSystem({
12210
12402
  }
12211
12403
 
12212
12404
  // src/hooks/useHoverable.ts
12213
- import { useContext as useContext65, useEffect as useEffect72, useState as useState19 } from "react";
12405
+ import { useContext as useContext65, useEffect as useEffect74, useState as useState19 } from "react";
12214
12406
  function useHoverable(options) {
12215
12407
  const engine = useContext65(EngineContext);
12216
12408
  const ctxEntityId = useContext65(EntityContext);
12217
12409
  const entityId = options?.entityId ?? ctxEntityId;
12218
12410
  const [isHovered, setIsHovered] = useState19(false);
12219
- useEffect72(() => {
12411
+ useEffect74(() => {
12220
12412
  if (!engine || entityId === null || entityId === void 0 || options?.disabled) return;
12221
12413
  const canvas = engine.canvas;
12222
12414
  let hovered = false;
@@ -12293,7 +12485,7 @@ function screenToWorld(engine, cssX, cssY) {
12293
12485
  }
12294
12486
 
12295
12487
  // src/hooks/useDragDrop.ts
12296
- import { useContext as useContext66, useEffect as useEffect73, useRef as useRef37, useState as useState20 } from "react";
12488
+ import { useContext as useContext66, useEffect as useEffect75, useRef as useRef38, useState as useState20 } from "react";
12297
12489
  var dragStateByEngine = /* @__PURE__ */ new WeakMap();
12298
12490
  function getActiveDrag(engine) {
12299
12491
  return dragStateByEngine.get(engine) ?? null;
@@ -12318,9 +12510,9 @@ function useDraggable(options) {
12318
12510
  const entityId = options?.entityId ?? ctxEntityId;
12319
12511
  const [isDragging, setIsDragging] = useState20(false);
12320
12512
  const [position, setPosition] = useState20({ x: 0, y: 0 });
12321
- const optsRef = useRef37(options);
12513
+ const optsRef = useRef38(options);
12322
12514
  optsRef.current = options;
12323
- useEffect73(() => {
12515
+ useEffect75(() => {
12324
12516
  if (!engine || entityId === null || entityId === void 0 || options?.disabled) return;
12325
12517
  const canvas = engine.canvas;
12326
12518
  let dragging = false;
@@ -12430,9 +12622,9 @@ function useDroppable(options) {
12430
12622
  const entityId = options?.entityId ?? ctxEntityId;
12431
12623
  const [isOver, setIsOver] = useState20(false);
12432
12624
  const [hoveredBy, setHoveredBy] = useState20(null);
12433
- const optsRef = useRef37(options);
12625
+ const optsRef = useRef38(options);
12434
12626
  optsRef.current = options;
12435
- useEffect73(() => {
12627
+ useEffect75(() => {
12436
12628
  if (!engine || entityId === null || entityId === void 0 || options?.disabled) return;
12437
12629
  let currentlyOver = false;
12438
12630
  const check = () => {
@@ -12534,7 +12726,7 @@ function findDroppableUnder(engine, drag) {
12534
12726
  }
12535
12727
 
12536
12728
  // src/hooks/useKeyboardFocus.ts
12537
- import { useContext as useContext67, useEffect as useEffect74, useRef as useRef38, useState as useState21 } from "react";
12729
+ import { useContext as useContext67, useEffect as useEffect76, useRef as useRef39, useState as useState21 } from "react";
12538
12730
  var registry = /* @__PURE__ */ new Map();
12539
12731
  var focusedId = null;
12540
12732
  var subscribers = /* @__PURE__ */ new Set();
@@ -12551,9 +12743,9 @@ function setFocused(id) {
12551
12743
  }
12552
12744
  function useFocusable(options) {
12553
12745
  const entityIdCtx = useContext67(EntityContext);
12554
- const optsRef = useRef38(options);
12746
+ const optsRef = useRef39(options);
12555
12747
  optsRef.current = options;
12556
- useEffect74(() => {
12748
+ useEffect76(() => {
12557
12749
  if (entityIdCtx === null || entityIdCtx === void 0) return;
12558
12750
  const id = entityIdCtx;
12559
12751
  const entry = {
@@ -12573,14 +12765,14 @@ function useFocusable(options) {
12573
12765
  function useKeyboardFocus() {
12574
12766
  const engine = useContext67(EngineContext);
12575
12767
  const [focused, setFocusedState] = useState21(focusedId);
12576
- useEffect74(() => {
12768
+ useEffect76(() => {
12577
12769
  const cb = () => setFocusedState(focusedId);
12578
12770
  subscribers.add(cb);
12579
12771
  return () => {
12580
12772
  subscribers.delete(cb);
12581
12773
  };
12582
12774
  }, []);
12583
- useEffect74(() => {
12775
+ useEffect76(() => {
12584
12776
  if (!engine) return;
12585
12777
  const onKey = (e) => {
12586
12778
  const target = e.target;
@@ -12706,7 +12898,7 @@ function subscribeFocus(cb) {
12706
12898
  }
12707
12899
 
12708
12900
  // src/components/FocusRing.tsx
12709
- import { useContext as useContext68, useEffect as useEffect75, useRef as useRef39, useState as useState22 } from "react";
12901
+ import { useContext as useContext68, useEffect as useEffect77, useRef as useRef40, useState as useState22 } from "react";
12710
12902
  import { Fragment as Fragment9, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
12711
12903
  function FocusRing({
12712
12904
  color = "#4fc3f7",
@@ -12716,14 +12908,14 @@ function FocusRing({
12716
12908
  pulse = true
12717
12909
  }) {
12718
12910
  const engine = useContext68(EngineContext);
12719
- const ringRef = useRef39(null);
12911
+ const ringRef = useRef40(null);
12720
12912
  const [focused, setFocused2] = useState22(getFocusedEntityId());
12721
12913
  const [reducedMotion, setReducedMotion] = useState22(false);
12722
- useEffect75(() => {
12914
+ useEffect77(() => {
12723
12915
  const unsub = subscribeFocus(() => setFocused2(getFocusedEntityId()));
12724
12916
  return unsub;
12725
12917
  }, []);
12726
- useEffect75(() => {
12918
+ useEffect77(() => {
12727
12919
  if (typeof window === "undefined" || !window.matchMedia) return;
12728
12920
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
12729
12921
  setReducedMotion(mq.matches);
@@ -12860,10 +13052,10 @@ function listSavedScenes(prefix = "") {
12860
13052
  }
12861
13053
 
12862
13054
  // ../gameplay/src/hooks/useAnimationController.ts
12863
- import { useState as useState23, useCallback as useCallback18 } from "react";
13055
+ import { useState as useState23, useCallback as useCallback19 } from "react";
12864
13056
  function useAnimationController(states, initial) {
12865
13057
  const [stateName, setStateName] = useState23(initial);
12866
- const setState = useCallback18((next) => {
13058
+ const setState = useCallback19((next) => {
12867
13059
  setStateName((prev) => prev === next ? prev : next);
12868
13060
  }, []);
12869
13061
  const clip = states[stateName] ?? states[initial];
@@ -12885,39 +13077,39 @@ function useAnimationController(states, initial) {
12885
13077
  }
12886
13078
 
12887
13079
  // ../gameplay/src/hooks/useAISteering.ts
12888
- import { useCallback as useCallback19 } from "react";
13080
+ import { useCallback as useCallback20 } from "react";
12889
13081
  function useAISteering() {
12890
- const seek$ = useCallback19((pos, target, speed) => seek(pos, target, speed), []);
12891
- const flee$ = useCallback19((pos, threat, speed) => flee(pos, threat, speed), []);
12892
- const arrive$ = useCallback19(
13082
+ const seek$ = useCallback20((pos, target, speed) => seek(pos, target, speed), []);
13083
+ const flee$ = useCallback20((pos, threat, speed) => flee(pos, threat, speed), []);
13084
+ const arrive$ = useCallback20(
12893
13085
  (pos, target, speed, slowRadius) => arrive(pos, target, speed, slowRadius),
12894
13086
  []
12895
13087
  );
12896
- const patrol$ = useCallback19(
13088
+ const patrol$ = useCallback20(
12897
13089
  (pos, waypoints, speed, currentIdx, arriveThreshold) => patrol(pos, waypoints, speed, currentIdx, arriveThreshold),
12898
13090
  []
12899
13091
  );
12900
- const wander$ = useCallback19(
13092
+ const wander$ = useCallback20(
12901
13093
  (pos, angle, speed, jitter) => wander(pos, angle, speed, jitter),
12902
13094
  []
12903
13095
  );
12904
- const pursuit$ = useCallback19(
13096
+ const pursuit$ = useCallback20(
12905
13097
  (pos, targetPos, targetVel, speed, lookAhead) => pursuit(pos, targetPos, targetVel, speed, lookAhead),
12906
13098
  []
12907
13099
  );
12908
- const evade$ = useCallback19(
13100
+ const evade$ = useCallback20(
12909
13101
  (pos, threatPos, threatVel, speed, lookAhead) => evade(pos, threatPos, threatVel, speed, lookAhead),
12910
13102
  []
12911
13103
  );
12912
- const separation$ = useCallback19(
13104
+ const separation$ = useCallback20(
12913
13105
  (pos, neighbors, speed, radius) => separation(pos, neighbors, speed, radius),
12914
13106
  []
12915
13107
  );
12916
- const cohesion$ = useCallback19(
13108
+ const cohesion$ = useCallback20(
12917
13109
  (pos, neighbors, speed) => cohesion(pos, neighbors, speed),
12918
13110
  []
12919
13111
  );
12920
- const alignment$ = useCallback19(
13112
+ const alignment$ = useCallback20(
12921
13113
  (neighborVelocities, speed) => alignment(neighborVelocities, speed),
12922
13114
  []
12923
13115
  );
@@ -12948,11 +13140,11 @@ function useDamageZone(damage, opts = {}) {
12948
13140
  }
12949
13141
 
12950
13142
  // ../gameplay/src/hooks/useDropThrough.ts
12951
- import { useContext as useContext70, useCallback as useCallback20 } from "react";
13143
+ import { useContext as useContext70, useCallback as useCallback21 } from "react";
12952
13144
  function useDropThrough(frames = 8) {
12953
13145
  const engine = useContext70(EngineContext);
12954
13146
  const entityId = useContext70(EntityContext);
12955
- const dropThrough = useCallback20(() => {
13147
+ const dropThrough = useCallback21(() => {
12956
13148
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
12957
13149
  if (rb) rb.dropThrough = frames;
12958
13150
  }, [engine.ecs, entityId, frames]);
@@ -12960,17 +13152,17 @@ function useDropThrough(frames = 8) {
12960
13152
  }
12961
13153
 
12962
13154
  // ../gameplay/src/hooks/useGameStateMachine.ts
12963
- import { useState as useState24, useRef as useRef40, useCallback as useCallback21, useEffect as useEffect76, useContext as useContext71 } from "react";
13155
+ import { useState as useState24, useRef as useRef41, useCallback as useCallback22, useEffect as useEffect78, useContext as useContext71 } from "react";
12964
13156
  function useGameStateMachine(states, initial) {
12965
13157
  const engine = useContext71(EngineContext);
12966
13158
  const [state, setState] = useState24(initial);
12967
- const stateRef = useRef40(initial);
12968
- const statesRef = useRef40(states);
13159
+ const stateRef = useRef41(initial);
13160
+ const statesRef = useRef41(states);
12969
13161
  statesRef.current = states;
12970
- useEffect76(() => {
13162
+ useEffect78(() => {
12971
13163
  statesRef.current[initial]?.onEnter?.();
12972
13164
  }, []);
12973
- const transition = useCallback21((to) => {
13165
+ const transition = useCallback22((to) => {
12974
13166
  const current = stateRef.current;
12975
13167
  if (current === to) return;
12976
13168
  statesRef.current[current]?.onExit?.();
@@ -12978,7 +13170,7 @@ function useGameStateMachine(states, initial) {
12978
13170
  setState(to);
12979
13171
  statesRef.current[to]?.onEnter?.();
12980
13172
  }, []);
12981
- useEffect76(() => {
13173
+ useEffect78(() => {
12982
13174
  const eid = engine.ecs.createEntity();
12983
13175
  engine.ecs.addComponent(
12984
13176
  eid,
@@ -12994,27 +13186,27 @@ function useGameStateMachine(states, initial) {
12994
13186
  }
12995
13187
 
12996
13188
  // ../gameplay/src/hooks/useHealth.ts
12997
- import { useRef as useRef41, useEffect as useEffect77, useContext as useContext72, useCallback as useCallback22 } from "react";
13189
+ import { useRef as useRef42, useEffect as useEffect79, useContext as useContext72, useCallback as useCallback23 } from "react";
12998
13190
  function useHealth(maxHp, opts = {}) {
12999
13191
  const engine = useContext72(EngineContext);
13000
13192
  const entityId = useContext72(EntityContext);
13001
- const hpRef = useRef41(maxHp);
13002
- const invincibleRef = useRef41(false);
13193
+ const hpRef = useRef42(maxHp);
13194
+ const invincibleRef = useRef42(false);
13003
13195
  const iFrameDuration = opts.iFrames ?? 1;
13004
- const onDeathRef = useRef41(opts.onDeath);
13005
- const onDamageRef = useRef41(opts.onDamage);
13006
- useEffect77(() => {
13196
+ const onDeathRef = useRef42(opts.onDeath);
13197
+ const onDamageRef = useRef42(opts.onDamage);
13198
+ useEffect79(() => {
13007
13199
  onDeathRef.current = opts.onDeath;
13008
13200
  });
13009
- useEffect77(() => {
13201
+ useEffect79(() => {
13010
13202
  onDamageRef.current = opts.onDamage;
13011
13203
  });
13012
- const timerRef = useRef41(
13204
+ const timerRef = useRef42(
13013
13205
  createTimer(iFrameDuration, () => {
13014
13206
  invincibleRef.current = false;
13015
13207
  })
13016
13208
  );
13017
- const takeDamage = useCallback22(
13209
+ const takeDamage = useCallback23(
13018
13210
  (amount = 1) => {
13019
13211
  if (invincibleRef.current || hpRef.current <= 0) return;
13020
13212
  hpRef.current = Math.max(0, hpRef.current - amount);
@@ -13027,28 +13219,28 @@ function useHealth(maxHp, opts = {}) {
13027
13219
  },
13028
13220
  [iFrameDuration]
13029
13221
  );
13030
- const takeDamageRef = useRef41(takeDamage);
13031
- useEffect77(() => {
13222
+ const takeDamageRef = useRef42(takeDamage);
13223
+ useEffect79(() => {
13032
13224
  takeDamageRef.current = takeDamage;
13033
13225
  }, [takeDamage]);
13034
- useEffect77(() => {
13226
+ useEffect79(() => {
13035
13227
  return engine.events.on(`damage:${entityId}`, ({ amount }) => {
13036
13228
  takeDamageRef.current(amount);
13037
13229
  });
13038
13230
  }, [engine.events, entityId]);
13039
- const heal = useCallback22(
13231
+ const heal = useCallback23(
13040
13232
  (amount) => {
13041
13233
  hpRef.current = Math.min(maxHp, hpRef.current + amount);
13042
13234
  },
13043
13235
  [maxHp]
13044
13236
  );
13045
- const setHp = useCallback22(
13237
+ const setHp = useCallback23(
13046
13238
  (hp) => {
13047
13239
  hpRef.current = Math.min(maxHp, Math.max(0, hp));
13048
13240
  },
13049
13241
  [maxHp]
13050
13242
  );
13051
- const update = useCallback22((dt) => {
13243
+ const update = useCallback23((dt) => {
13052
13244
  timerRef.current.update(dt);
13053
13245
  }, []);
13054
13246
  return {
@@ -13072,11 +13264,11 @@ function useHealth(maxHp, opts = {}) {
13072
13264
  }
13073
13265
 
13074
13266
  // ../gameplay/src/hooks/useKinematicBody.ts
13075
- import { useContext as useContext73, useCallback as useCallback23 } from "react";
13267
+ import { useContext as useContext73, useCallback as useCallback24 } from "react";
13076
13268
  function useKinematicBody() {
13077
13269
  const engine = useContext73(EngineContext);
13078
13270
  const entityId = useContext73(EntityContext);
13079
- const moveAndCollide = useCallback23(
13271
+ const moveAndCollide = useCallback24(
13080
13272
  (dx, dy) => {
13081
13273
  const transform = engine.ecs.getComponent(entityId, "Transform");
13082
13274
  if (!transform) return { dx: 0, dy: 0 };
@@ -13112,7 +13304,7 @@ function useKinematicBody() {
13112
13304
  },
13113
13305
  [engine.ecs, entityId]
13114
13306
  );
13115
- const setVelocity = useCallback23(
13307
+ const setVelocity = useCallback24(
13116
13308
  (vx, vy) => {
13117
13309
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
13118
13310
  if (rb) {
@@ -13122,7 +13314,7 @@ function useKinematicBody() {
13122
13314
  },
13123
13315
  [engine.ecs, entityId]
13124
13316
  );
13125
- const setAngularVelocity = useCallback23(
13317
+ const setAngularVelocity = useCallback24(
13126
13318
  (w) => {
13127
13319
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
13128
13320
  if (rb) rb.angularVelocity = w;
@@ -13133,12 +13325,12 @@ function useKinematicBody() {
13133
13325
  }
13134
13326
 
13135
13327
  // ../gameplay/src/hooks/useLevelTransition.ts
13136
- import { useState as useState25, useRef as useRef42, useCallback as useCallback24 } from "react";
13328
+ import { useState as useState25, useRef as useRef43, useCallback as useCallback25 } from "react";
13137
13329
  function useLevelTransition(initial) {
13138
13330
  const [currentLevel, setCurrentLevel] = useState25(initial);
13139
13331
  const [isTransitioning, setIsTransitioning] = useState25(false);
13140
- const overlayRef = useRef42(null);
13141
- const transitionTo = useCallback24((level, opts = {}) => {
13332
+ const overlayRef = useRef43(null);
13333
+ const transitionTo = useCallback25((level, opts = {}) => {
13142
13334
  const { duration = 0.4, type = "fade" } = opts;
13143
13335
  if (type === "instant") {
13144
13336
  setCurrentLevel(level);
@@ -13175,7 +13367,7 @@ function useLevelTransition(initial) {
13175
13367
  }
13176
13368
 
13177
13369
  // ../gameplay/src/hooks/usePlatformerController.ts
13178
- import { useContext as useContext74, useEffect as useEffect78 } from "react";
13370
+ import { useContext as useContext74, useEffect as useEffect80 } from "react";
13179
13371
  function normalizeKeys(val, defaults) {
13180
13372
  if (!val) return defaults;
13181
13373
  return Array.isArray(val) ? val : [val];
@@ -13199,7 +13391,7 @@ function usePlatformerController(entityId, opts = {}) {
13199
13391
  const leftKeys = normalizeKeys(bindings?.left, ["ArrowLeft", "KeyA", "a"]);
13200
13392
  const rightKeys = normalizeKeys(bindings?.right, ["ArrowRight", "KeyD", "d"]);
13201
13393
  const jumpKeys = normalizeKeys(bindings?.jump, ["Space", "ArrowUp", "KeyW", "w"]);
13202
- useEffect78(() => {
13394
+ useEffect80(() => {
13203
13395
  const state = {
13204
13396
  coyoteTimer: 0,
13205
13397
  jumpBuffer: 0,
@@ -13263,22 +13455,22 @@ function usePlatformerController(entityId, opts = {}) {
13263
13455
  }
13264
13456
 
13265
13457
  // ../gameplay/src/hooks/usePathfinding.ts
13266
- import { useCallback as useCallback25 } from "react";
13458
+ import { useCallback as useCallback26 } from "react";
13267
13459
  function usePathfinding() {
13268
- const createGrid$ = useCallback25(
13460
+ const createGrid$ = useCallback26(
13269
13461
  (cols, rows, cellSize) => createNavGrid(cols, rows, cellSize),
13270
13462
  []
13271
13463
  );
13272
- const setWalkable$ = useCallback25(
13464
+ const setWalkable$ = useCallback26(
13273
13465
  (grid, col, row, walkable) => setWalkable(grid, col, row, walkable),
13274
13466
  []
13275
13467
  );
13276
- const findPath$ = useCallback25((grid, start2, goal) => findPath(grid, start2, goal), []);
13468
+ const findPath$ = useCallback26((grid, start2, goal) => findPath(grid, start2, goal), []);
13277
13469
  return { createGrid: createGrid$, setWalkable: setWalkable$, findPath: findPath$ };
13278
13470
  }
13279
13471
 
13280
13472
  // ../gameplay/src/hooks/usePersistedBindings.ts
13281
- import { useState as useState26, useCallback as useCallback26, useMemo as useMemo16, useContext as useContext75 } from "react";
13473
+ import { useState as useState26, useCallback as useCallback27, useMemo as useMemo17, useContext as useContext75 } from "react";
13282
13474
  function usePersistedBindings(storageKey, defaults) {
13283
13475
  const engine = useContext75(EngineContext);
13284
13476
  const input = engine.input;
@@ -13290,7 +13482,7 @@ function usePersistedBindings(storageKey, defaults) {
13290
13482
  }
13291
13483
  return defaults;
13292
13484
  });
13293
- const normalized = useMemo16(() => {
13485
+ const normalized = useMemo17(() => {
13294
13486
  const out = {};
13295
13487
  for (const [action, keys] of Object.entries(bindings)) {
13296
13488
  if (typeof keys === "string") out[action] = [keys];
@@ -13298,7 +13490,7 @@ function usePersistedBindings(storageKey, defaults) {
13298
13490
  }
13299
13491
  return out;
13300
13492
  }, [bindings]);
13301
- const rebind = useCallback26(
13493
+ const rebind = useCallback27(
13302
13494
  (action, keys) => {
13303
13495
  setBindings((prev) => {
13304
13496
  const next = { ...prev, [action]: Array.isArray(keys) ? keys : [keys] };
@@ -13311,14 +13503,14 @@ function usePersistedBindings(storageKey, defaults) {
13311
13503
  },
13312
13504
  [storageKey]
13313
13505
  );
13314
- const reset = useCallback26(() => {
13506
+ const reset = useCallback27(() => {
13315
13507
  try {
13316
13508
  localStorage.removeItem(storageKey);
13317
13509
  } catch {
13318
13510
  }
13319
13511
  setBindings(defaults);
13320
13512
  }, [storageKey]);
13321
- return useMemo16(
13513
+ return useMemo17(
13322
13514
  () => ({
13323
13515
  bindings,
13324
13516
  rebind,
@@ -13332,21 +13524,21 @@ function usePersistedBindings(storageKey, defaults) {
13332
13524
  }
13333
13525
 
13334
13526
  // ../gameplay/src/hooks/useRestart.ts
13335
- import { useState as useState27, useCallback as useCallback27 } from "react";
13527
+ import { useState as useState27, useCallback as useCallback28 } from "react";
13336
13528
  function useRestart() {
13337
13529
  const [restartKey, setRestartKey] = useState27(0);
13338
- const restart = useCallback27(() => {
13530
+ const restart = useCallback28(() => {
13339
13531
  setRestartKey((k) => k + 1);
13340
13532
  }, []);
13341
13533
  return { restartKey, restart };
13342
13534
  }
13343
13535
 
13344
13536
  // ../gameplay/src/hooks/useSave.ts
13345
- import { useCallback as useCallback28, useRef as useRef43 } from "react";
13537
+ import { useCallback as useCallback29, useRef as useRef44 } from "react";
13346
13538
  function useSave(key, defaultValue, opts = {}) {
13347
13539
  const version = opts.version ?? 1;
13348
- const dataRef = useRef43(defaultValue);
13349
- const save = useCallback28(
13540
+ const dataRef = useRef44(defaultValue);
13541
+ const save = useCallback29(
13350
13542
  (value) => {
13351
13543
  dataRef.current = value;
13352
13544
  const slot = { version, data: value };
@@ -13358,7 +13550,7 @@ function useSave(key, defaultValue, opts = {}) {
13358
13550
  },
13359
13551
  [key, version]
13360
13552
  );
13361
- const load = useCallback28(() => {
13553
+ const load = useCallback29(() => {
13362
13554
  try {
13363
13555
  const raw = localStorage.getItem(key);
13364
13556
  if (!raw) return defaultValue;
@@ -13378,11 +13570,11 @@ function useSave(key, defaultValue, opts = {}) {
13378
13570
  return defaultValue;
13379
13571
  }
13380
13572
  }, [key, version]);
13381
- const clear = useCallback28(() => {
13573
+ const clear = useCallback29(() => {
13382
13574
  localStorage.removeItem(key);
13383
13575
  dataRef.current = defaultValue;
13384
13576
  }, [key]);
13385
- const exists = useCallback28(() => {
13577
+ const exists = useCallback29(() => {
13386
13578
  return localStorage.getItem(key) !== null;
13387
13579
  }, [key]);
13388
13580
  return {
@@ -13397,7 +13589,7 @@ function useSave(key, defaultValue, opts = {}) {
13397
13589
  }
13398
13590
 
13399
13591
  // ../gameplay/src/hooks/useIDBSave.ts
13400
- import { useCallback as useCallback29, useRef as useRef44 } from "react";
13592
+ import { useCallback as useCallback30, useRef as useRef45 } from "react";
13401
13593
  var DB_NAME = "cubeforge-saves";
13402
13594
  var STORE_NAME = "saves";
13403
13595
  function openDB() {
@@ -13415,12 +13607,12 @@ function openDB() {
13415
13607
  }
13416
13608
  function useIDBSave(key, defaultValue, opts = {}) {
13417
13609
  const version = opts.version ?? 1;
13418
- const dbRef = useRef44(null);
13419
- const getDB = useCallback29(() => {
13610
+ const dbRef = useRef45(null);
13611
+ const getDB = useCallback30(() => {
13420
13612
  if (!dbRef.current) dbRef.current = openDB();
13421
13613
  return dbRef.current;
13422
13614
  }, []);
13423
- const save = useCallback29(
13615
+ const save = useCallback30(
13424
13616
  async (value) => {
13425
13617
  const db = await getDB();
13426
13618
  const slot = { version, data: value };
@@ -13433,7 +13625,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
13433
13625
  },
13434
13626
  [getDB, key, version]
13435
13627
  );
13436
- const load = useCallback29(async () => {
13628
+ const load = useCallback30(async () => {
13437
13629
  const db = await getDB();
13438
13630
  return new Promise((resolve, reject) => {
13439
13631
  const tx = db.transaction(STORE_NAME, "readonly");
@@ -13459,7 +13651,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
13459
13651
  req.onerror = () => reject(req.error);
13460
13652
  });
13461
13653
  }, [getDB, key, version]);
13462
- const clear = useCallback29(async () => {
13654
+ const clear = useCallback30(async () => {
13463
13655
  const db = await getDB();
13464
13656
  return new Promise((resolve, reject) => {
13465
13657
  const tx = db.transaction(STORE_NAME, "readwrite");
@@ -13468,7 +13660,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
13468
13660
  req.onerror = () => reject(req.error);
13469
13661
  });
13470
13662
  }, [getDB, key]);
13471
- const exists = useCallback29(async () => {
13663
+ const exists = useCallback30(async () => {
13472
13664
  const db = await getDB();
13473
13665
  return new Promise((resolve, reject) => {
13474
13666
  const tx = db.transaction(STORE_NAME, "readonly");
@@ -13481,7 +13673,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
13481
13673
  }
13482
13674
 
13483
13675
  // ../gameplay/src/hooks/useSaveSlots.ts
13484
- import { useCallback as useCallback30, useState as useState28 } from "react";
13676
+ import { useCallback as useCallback31, useState as useState28 } from "react";
13485
13677
  function useSaveSlots(namespace, opts = {}) {
13486
13678
  const version = opts.version ?? 1;
13487
13679
  const indexKey = `cubeforge-saves:${namespace}:__index`;
@@ -13535,7 +13727,7 @@ function useSaveSlots(namespace, opts = {}) {
13535
13727
  const metas = readIndex().map((id) => readSlot(id)?.meta).filter((m) => m !== void 0).sort((a, b) => a.savedAt < b.savedAt ? 1 : -1);
13536
13728
  setSlots(metas);
13537
13729
  }
13538
- const save = useCallback30(
13730
+ const save = useCallback31(
13539
13731
  (id, data, label, thumbnail) => {
13540
13732
  const meta = {
13541
13733
  id,
@@ -13550,7 +13742,7 @@ function useSaveSlots(namespace, opts = {}) {
13550
13742
  // eslint-disable-next-line react-hooks/exhaustive-deps
13551
13743
  [namespace, version]
13552
13744
  );
13553
- const load = useCallback30(
13745
+ const load = useCallback31(
13554
13746
  (id) => {
13555
13747
  const entry = readSlot(id);
13556
13748
  if (!entry) return null;
@@ -13566,7 +13758,7 @@ function useSaveSlots(namespace, opts = {}) {
13566
13758
  // eslint-disable-next-line react-hooks/exhaustive-deps
13567
13759
  [namespace, version]
13568
13760
  );
13569
- const deleteFn = useCallback30(
13761
+ const deleteFn = useCallback31(
13570
13762
  (id) => {
13571
13763
  try {
13572
13764
  localStorage.removeItem(slotKey(id));
@@ -13578,7 +13770,7 @@ function useSaveSlots(namespace, opts = {}) {
13578
13770
  // eslint-disable-next-line react-hooks/exhaustive-deps
13579
13771
  [namespace]
13580
13772
  );
13581
- const copy = useCallback30(
13773
+ const copy = useCallback31(
13582
13774
  (fromId, toId) => {
13583
13775
  const entry = readSlot(fromId);
13584
13776
  if (!entry) return false;
@@ -13591,7 +13783,7 @@ function useSaveSlots(namespace, opts = {}) {
13591
13783
  // eslint-disable-next-line react-hooks/exhaustive-deps
13592
13784
  [namespace, version]
13593
13785
  );
13594
- const rename = useCallback30(
13786
+ const rename = useCallback31(
13595
13787
  (id, label) => {
13596
13788
  const entry = readSlot(id);
13597
13789
  if (!entry) return;
@@ -13601,10 +13793,10 @@ function useSaveSlots(namespace, opts = {}) {
13601
13793
  // eslint-disable-next-line react-hooks/exhaustive-deps
13602
13794
  [namespace]
13603
13795
  );
13604
- const listSlots = useCallback30(() => {
13796
+ const listSlots = useCallback31(() => {
13605
13797
  return readIndex().map((id) => readSlot(id)?.meta).filter((m) => m !== void 0).sort((a, b) => a.savedAt < b.savedAt ? 1 : -1);
13606
13798
  }, [namespace]);
13607
- const exists = useCallback30(
13799
+ const exists = useCallback31(
13608
13800
  (id) => {
13609
13801
  return readSlot(id) !== null;
13610
13802
  },
@@ -13626,7 +13818,7 @@ function useSaveSlots(namespace, opts = {}) {
13626
13818
  }
13627
13819
 
13628
13820
  // ../gameplay/src/hooks/useTopDownMovement.ts
13629
- import { useContext as useContext76, useEffect as useEffect79 } from "react";
13821
+ import { useContext as useContext76, useEffect as useEffect81 } from "react";
13630
13822
  function moveToward(current, target, maxDelta) {
13631
13823
  const diff = target - current;
13632
13824
  if (Math.abs(diff) <= maxDelta) return target;
@@ -13635,7 +13827,7 @@ function moveToward(current, target, maxDelta) {
13635
13827
  function useTopDownMovement(entityId, opts = {}) {
13636
13828
  const engine = useContext76(EngineContext);
13637
13829
  const { speed = 200, normalizeDiagonal = true, acceleration = Infinity, deceleration = acceleration } = opts;
13638
- useEffect79(() => {
13830
+ useEffect81(() => {
13639
13831
  const updateFn = (id, world, input, dt) => {
13640
13832
  if (!world.hasEntity(id)) return;
13641
13833
  const rb = world.getComponent(id, "RigidBody");
@@ -13675,18 +13867,18 @@ function useTopDownMovement(entityId, opts = {}) {
13675
13867
  }
13676
13868
 
13677
13869
  // ../gameplay/src/hooks/useDialogue.ts
13678
- import { useState as useState29, useCallback as useCallback31, useRef as useRef45 } from "react";
13870
+ import { useState as useState29, useCallback as useCallback32, useRef as useRef46 } from "react";
13679
13871
  function useDialogue() {
13680
13872
  const [active, setActive] = useState29(false);
13681
13873
  const [currentId, setCurrentId] = useState29(null);
13682
- const scriptRef = useRef45(null);
13683
- const start2 = useCallback31((script, startId) => {
13874
+ const scriptRef = useRef46(null);
13875
+ const start2 = useCallback32((script, startId) => {
13684
13876
  scriptRef.current = script;
13685
13877
  const id = startId ?? Object.keys(script)[0];
13686
13878
  setCurrentId(id);
13687
13879
  setActive(true);
13688
13880
  }, []);
13689
- const advance = useCallback31(
13881
+ const advance = useCallback32(
13690
13882
  (choiceIndex) => {
13691
13883
  if (!scriptRef.current || !currentId) return;
13692
13884
  const line = scriptRef.current[currentId];
@@ -13716,7 +13908,7 @@ function useDialogue() {
13716
13908
  },
13717
13909
  [currentId]
13718
13910
  );
13719
- const close = useCallback31(() => {
13911
+ const close = useCallback32(() => {
13720
13912
  setActive(false);
13721
13913
  setCurrentId(null);
13722
13914
  scriptRef.current = null;
@@ -13726,7 +13918,7 @@ function useDialogue() {
13726
13918
  }
13727
13919
 
13728
13920
  // ../gameplay/src/hooks/useDialogueTree.ts
13729
- import { useState as useState30, useCallback as useCallback32, useRef as useRef46 } from "react";
13921
+ import { useState as useState30, useCallback as useCallback33, useRef as useRef47 } from "react";
13730
13922
  function interpolate(text, vars) {
13731
13923
  return text.replace(/\{(\w+)\}/g, (_, key) => {
13732
13924
  const val = vars[key];
@@ -13744,8 +13936,8 @@ function useDialogueTree() {
13744
13936
  const [active, setActive] = useState30(false);
13745
13937
  const [currentId, setCurrentId] = useState30(null);
13746
13938
  const [variables, setVariables] = useState30({});
13747
- const scriptRef = useRef46(null);
13748
- const varsRef = useRef46({});
13939
+ const scriptRef = useRef47(null);
13940
+ const varsRef = useRef47({});
13749
13941
  function setCurrentNode(id) {
13750
13942
  if (!id || !scriptRef.current) {
13751
13943
  setActive(false);
@@ -13761,7 +13953,7 @@ function useDialogueTree() {
13761
13953
  node.onEnter?.(varsRef.current);
13762
13954
  setCurrentId(id);
13763
13955
  }
13764
- const start2 = useCallback32(
13956
+ const start2 = useCallback33(
13765
13957
  (script, startId, initialVars) => {
13766
13958
  scriptRef.current = script;
13767
13959
  const newVars = { ...varsRef.current, ...initialVars };
@@ -13774,7 +13966,7 @@ function useDialogueTree() {
13774
13966
  // eslint-disable-next-line react-hooks/exhaustive-deps
13775
13967
  []
13776
13968
  );
13777
- const advance = useCallback32(
13969
+ const advance = useCallback33(
13778
13970
  (choiceIndex) => {
13779
13971
  if (!scriptRef.current || !currentId) return;
13780
13972
  const node = scriptRef.current[currentId];
@@ -13813,7 +14005,7 @@ function useDialogueTree() {
13813
14005
  },
13814
14006
  [currentId]
13815
14007
  );
13816
- const jumpTo = useCallback32(
14008
+ const jumpTo = useCallback33(
13817
14009
  (id) => {
13818
14010
  if (!scriptRef.current?.[id]) return;
13819
14011
  const current2 = scriptRef.current[currentId ?? ""];
@@ -13822,11 +14014,11 @@ function useDialogueTree() {
13822
14014
  },
13823
14015
  [currentId]
13824
14016
  );
13825
- const setVar = useCallback32((key, value) => {
14017
+ const setVar = useCallback33((key, value) => {
13826
14018
  varsRef.current = { ...varsRef.current, [key]: value };
13827
14019
  setVariables({ ...varsRef.current });
13828
14020
  }, []);
13829
- const close = useCallback32(() => {
14021
+ const close = useCallback33(() => {
13830
14022
  if (scriptRef.current && currentId) {
13831
14023
  scriptRef.current[currentId]?.onExit?.(varsRef.current);
13832
14024
  }
@@ -13852,7 +14044,7 @@ function useDialogueTree() {
13852
14044
  }
13853
14045
 
13854
14046
  // ../gameplay/src/hooks/useBehaviorTree.ts
13855
- import { useRef as useRef47, useCallback as useCallback33 } from "react";
14047
+ import { useRef as useRef48, useCallback as useCallback34 } from "react";
13856
14048
  function btAction(fn) {
13857
14049
  return {
13858
14050
  tick(dt) {
@@ -14065,16 +14257,16 @@ function btFail(child) {
14065
14257
  };
14066
14258
  }
14067
14259
  function useBehaviorTree(rootFactory) {
14068
- const rootRef = useRef47(null);
14260
+ const rootRef = useRef48(null);
14069
14261
  if (!rootRef.current) rootRef.current = rootFactory();
14070
- const statusRef = useRef47("failure");
14071
- const tick2 = useCallback33((dt) => {
14262
+ const statusRef = useRef48("failure");
14263
+ const tick2 = useCallback34((dt) => {
14072
14264
  if (!rootRef.current) return "failure";
14073
14265
  const s2 = rootRef.current.tick(dt);
14074
14266
  statusRef.current = s2;
14075
14267
  return s2;
14076
14268
  }, []);
14077
- const reset = useCallback33(() => {
14269
+ const reset = useCallback34(() => {
14078
14270
  rootRef.current?.reset();
14079
14271
  }, []);
14080
14272
  return {
@@ -14195,17 +14387,17 @@ function DialogueBox({ dialogue, onAdvance, onClose, style = {} }) {
14195
14387
  }
14196
14388
 
14197
14389
  // ../gameplay/src/hooks/useCutscene.ts
14198
- import { useState as useState31, useCallback as useCallback34, useRef as useRef48, useEffect as useEffect80, useContext as useContext77 } from "react";
14390
+ import { useState as useState31, useCallback as useCallback35, useRef as useRef49, useEffect as useEffect82, useContext as useContext77 } from "react";
14199
14391
  function useCutscene() {
14200
14392
  const engine = useContext77(EngineContext);
14201
14393
  const [playing, setPlaying] = useState31(false);
14202
14394
  const [stepIndex, setStepIndex] = useState31(0);
14203
- const stepsRef = useRef48([]);
14204
- const timerRef = useRef48(0);
14205
- const idxRef = useRef48(0);
14206
- const playingRef = useRef48(false);
14207
- const entityRef = useRef48(null);
14208
- const finish = useCallback34(() => {
14395
+ const stepsRef = useRef49([]);
14396
+ const timerRef = useRef49(0);
14397
+ const idxRef = useRef49(0);
14398
+ const playingRef = useRef49(false);
14399
+ const entityRef = useRef49(null);
14400
+ const finish = useCallback35(() => {
14209
14401
  playingRef.current = false;
14210
14402
  setPlaying(false);
14211
14403
  setStepIndex(0);
@@ -14215,14 +14407,14 @@ function useCutscene() {
14215
14407
  entityRef.current = null;
14216
14408
  }
14217
14409
  }, [engine.ecs]);
14218
- const fireStep = useCallback34((step) => {
14410
+ const fireStep = useCallback35((step) => {
14219
14411
  if (step.type === "call") step.fn();
14220
14412
  if (step.type === "parallel")
14221
14413
  step.steps.forEach((s2) => {
14222
14414
  if (s2.type === "call") s2.fn();
14223
14415
  });
14224
14416
  }, []);
14225
- const play = useCallback34(
14417
+ const play = useCallback35(
14226
14418
  (steps) => {
14227
14419
  stepsRef.current = steps;
14228
14420
  idxRef.current = 0;
@@ -14279,7 +14471,7 @@ function useCutscene() {
14279
14471
  },
14280
14472
  [engine.ecs, finish, fireStep]
14281
14473
  );
14282
- const skip = useCallback34(() => {
14474
+ const skip = useCallback35(() => {
14283
14475
  for (let i = idxRef.current; i < stepsRef.current.length; i++) {
14284
14476
  const step = stepsRef.current[i];
14285
14477
  if (step.type === "call") step.fn();
@@ -14290,7 +14482,7 @@ function useCutscene() {
14290
14482
  }
14291
14483
  finish();
14292
14484
  }, [finish]);
14293
- useEffect80(() => {
14485
+ useEffect82(() => {
14294
14486
  return () => {
14295
14487
  if (entityRef.current !== null && engine.ecs.hasEntity(entityRef.current)) {
14296
14488
  engine.ecs.destroyEntity(entityRef.current);
@@ -14301,7 +14493,7 @@ function useCutscene() {
14301
14493
  }
14302
14494
 
14303
14495
  // ../gameplay/src/hooks/useGameStore.ts
14304
- import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback35 } from "react";
14496
+ import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback36 } from "react";
14305
14497
  function createStore(initialState) {
14306
14498
  let state = { ...initialState };
14307
14499
  const listeners = /* @__PURE__ */ new Set();
@@ -14327,7 +14519,7 @@ function useGameStore(key, initialState) {
14327
14519
  }
14328
14520
  const store = stores.get(key);
14329
14521
  const state = useSyncExternalStore2(store.subscribe, store.getState);
14330
- const setState = useCallback35(
14522
+ const setState = useCallback36(
14331
14523
  (partial) => {
14332
14524
  store.setState(partial);
14333
14525
  },
@@ -14337,21 +14529,21 @@ function useGameStore(key, initialState) {
14337
14529
  }
14338
14530
 
14339
14531
  // ../gameplay/src/hooks/useTween.ts
14340
- import { useRef as useRef49, useCallback as useCallback36, useEffect as useEffect81 } from "react";
14532
+ import { useRef as useRef50, useCallback as useCallback37, useEffect as useEffect83 } from "react";
14341
14533
  function useTween(opts) {
14342
- const rafRef = useRef49(null);
14343
- const startTimeRef = useRef49(0);
14344
- const runningRef = useRef49(false);
14345
- const optsRef = useRef49(opts);
14534
+ const rafRef = useRef50(null);
14535
+ const startTimeRef = useRef50(0);
14536
+ const runningRef = useRef50(false);
14537
+ const optsRef = useRef50(opts);
14346
14538
  optsRef.current = opts;
14347
- const stop = useCallback36(() => {
14539
+ const stop = useCallback37(() => {
14348
14540
  if (rafRef.current !== null) {
14349
14541
  cancelAnimationFrame(rafRef.current);
14350
14542
  rafRef.current = null;
14351
14543
  }
14352
14544
  runningRef.current = false;
14353
14545
  }, []);
14354
- const start2 = useCallback36(() => {
14546
+ const start2 = useCallback37(() => {
14355
14547
  stop();
14356
14548
  runningRef.current = true;
14357
14549
  startTimeRef.current = performance.now();
@@ -14373,12 +14565,12 @@ function useTween(opts) {
14373
14565
  };
14374
14566
  rafRef.current = requestAnimationFrame(tick2);
14375
14567
  }, [stop]);
14376
- useEffect81(() => {
14568
+ useEffect83(() => {
14377
14569
  if (opts.autoStart) {
14378
14570
  start2();
14379
14571
  }
14380
14572
  }, []);
14381
- useEffect81(() => {
14573
+ useEffect83(() => {
14382
14574
  return () => {
14383
14575
  if (rafRef.current !== null) {
14384
14576
  cancelAnimationFrame(rafRef.current);
@@ -14397,15 +14589,15 @@ function useTween(opts) {
14397
14589
  }
14398
14590
 
14399
14591
  // ../gameplay/src/hooks/useObjectPool.ts
14400
- import { useRef as useRef50, useMemo as useMemo17, useEffect as useEffect82 } from "react";
14592
+ import { useRef as useRef51, useMemo as useMemo18, useEffect as useEffect84 } from "react";
14401
14593
  function useObjectPool(factory, reset, initialSize) {
14402
- const poolRef = useRef50([]);
14403
- const activeRef = useRef50(0);
14404
- const factoryRef = useRef50(factory);
14594
+ const poolRef = useRef51([]);
14595
+ const activeRef = useRef51(0);
14596
+ const factoryRef = useRef51(factory);
14405
14597
  factoryRef.current = factory;
14406
- const resetRef = useRef50(reset);
14598
+ const resetRef = useRef51(reset);
14407
14599
  resetRef.current = reset;
14408
- useEffect82(() => {
14600
+ useEffect84(() => {
14409
14601
  if (initialSize != null && initialSize > 0) {
14410
14602
  const pool = poolRef.current;
14411
14603
  for (let i = 0; i < initialSize; i++) {
@@ -14413,7 +14605,7 @@ function useObjectPool(factory, reset, initialSize) {
14413
14605
  }
14414
14606
  }
14415
14607
  }, []);
14416
- return useMemo17(
14608
+ return useMemo18(
14417
14609
  () => ({
14418
14610
  acquire() {
14419
14611
  activeRef.current++;
@@ -14444,11 +14636,11 @@ function useObjectPool(factory, reset, initialSize) {
14444
14636
  }
14445
14637
 
14446
14638
  // ../gameplay/src/hooks/useForces.ts
14447
- import { useContext as useContext78, useMemo as useMemo18 } from "react";
14639
+ import { useContext as useContext78, useMemo as useMemo19 } from "react";
14448
14640
  function useForces() {
14449
14641
  const engine = useContext78(EngineContext);
14450
14642
  const entityId = useContext78(EntityContext);
14451
- return useMemo18(() => {
14643
+ return useMemo19(() => {
14452
14644
  const getRb = () => engine.ecs.getComponent(entityId, "RigidBody");
14453
14645
  const getTransform = () => engine.ecs.getComponent(entityId, "Transform");
14454
14646
  const getCenter = () => {
@@ -14516,11 +14708,11 @@ function useForces() {
14516
14708
  }
14517
14709
 
14518
14710
  // ../gameplay/src/hooks/useCharacterController.ts
14519
- import { useContext as useContext79, useMemo as useMemo19 } from "react";
14711
+ import { useContext as useContext79, useMemo as useMemo20 } from "react";
14520
14712
  function useCharacterController(config = {}) {
14521
14713
  const engine = useContext79(EngineContext);
14522
14714
  const entityId = useContext79(EntityContext);
14523
- return useMemo19(() => {
14715
+ return useMemo20(() => {
14524
14716
  const controller = new CharacterController(config);
14525
14717
  return {
14526
14718
  move(dx, dy) {
@@ -14541,7 +14733,7 @@ function createAtlas(names, _columns) {
14541
14733
  }
14542
14734
 
14543
14735
  // src/components/HUD.tsx
14544
- import { createContext as createContext3, useContext as useContext80, useMemo as useMemo20 } from "react";
14736
+ import { createContext as createContext3, useContext as useContext80, useMemo as useMemo21 } from "react";
14545
14737
  import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
14546
14738
  var HUDContextRef = createContext3(null);
14547
14739
  function HUD({
@@ -14554,7 +14746,7 @@ function HUD({
14554
14746
  className,
14555
14747
  children
14556
14748
  }) {
14557
- const ctx = useMemo20(() => ({ padding, safeArea }), [padding, safeArea]);
14749
+ const ctx = useMemo21(() => ({ padding, safeArea }), [padding, safeArea]);
14558
14750
  const rootStyle = {
14559
14751
  position: "absolute",
14560
14752
  inset: 0,
@@ -14727,7 +14919,7 @@ function definePrefab(name, defaults, render) {
14727
14919
  }
14728
14920
 
14729
14921
  // src/hooks/useNetworkSync.ts
14730
- import { useEffect as useEffect84, useRef as useRef52 } from "react";
14922
+ import { useEffect as useEffect86, useRef as useRef53 } from "react";
14731
14923
 
14732
14924
  // ../../packages/net/src/transport.ts
14733
14925
  function isBinaryTransport(t) {
@@ -15072,7 +15264,7 @@ function syncEntity(config) {
15072
15264
  }
15073
15265
 
15074
15266
  // ../../packages/net/src/useNetworkInput.ts
15075
- import { useState as useState32, useEffect as useEffect83, useRef as useRef51 } from "react";
15267
+ import { useState as useState32, useEffect as useEffect85, useRef as useRef52 } from "react";
15076
15268
  var INPUT_MSG_TYPE = "input:state";
15077
15269
  function useNetworkInput(config) {
15078
15270
  const { room, keys, input, tickRate = 20 } = config;
@@ -15081,8 +15273,8 @@ function useNetworkInput(config) {
15081
15273
  () => Object.fromEntries(keys.map((k) => [k, false]))
15082
15274
  );
15083
15275
  const [remoteInputs] = useState32(() => /* @__PURE__ */ new Map());
15084
- const localInputRef = useRef51(localInput);
15085
- useEffect83(() => {
15276
+ const localInputRef = useRef52(localInput);
15277
+ useEffect85(() => {
15086
15278
  let cleanupDom = null;
15087
15279
  if (!input) {
15088
15280
  let handleKeyDown2 = function(e) {
@@ -15296,9 +15488,9 @@ function lerpState(a, b, t) {
15296
15488
 
15297
15489
  // src/hooks/useNetworkSync.ts
15298
15490
  function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
15299
- const optsRef = useRef52(opts);
15491
+ const optsRef = useRef53(opts);
15300
15492
  optsRef.current = opts;
15301
- useEffect84(() => {
15493
+ useEffect86(() => {
15302
15494
  const sync = syncEntity({
15303
15495
  entityId,
15304
15496
  components,
@@ -15313,18 +15505,18 @@ function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
15313
15505
  }
15314
15506
 
15315
15507
  // src/hooks/useRemotePlayer.ts
15316
- import { useEffect as useEffect85, useRef as useRef53, useState as useState33 } from "react";
15508
+ import { useEffect as useEffect87, useRef as useRef54, useState as useState33 } from "react";
15317
15509
  var PEER_JOIN_MSG = "peer:join";
15318
15510
  var PEER_LEAVE_MSG = "peer:leave";
15319
15511
  function useRemotePlayer(config) {
15320
15512
  const { room, world, createEntity, destroyEntity } = config;
15321
15513
  const [players, setPlayers] = useState33(() => /* @__PURE__ */ new Map());
15322
- const playersRef = useRef53(players);
15323
- const createRef = useRef53(createEntity);
15514
+ const playersRef = useRef54(players);
15515
+ const createRef = useRef54(createEntity);
15324
15516
  createRef.current = createEntity;
15325
- const destroyRef = useRef53(destroyEntity);
15517
+ const destroyRef = useRef54(destroyEntity);
15326
15518
  destroyRef.current = destroyEntity;
15327
- useEffect85(() => {
15519
+ useEffect87(() => {
15328
15520
  function spawnPeer(peerId) {
15329
15521
  if (playersRef.current.has(peerId)) return;
15330
15522
  const entityId = createRef.current(peerId);
@@ -15820,7 +16012,7 @@ function EntityInspector({ entity, components, width = 260, style }) {
15820
16012
  }
15821
16013
 
15822
16014
  // ../editor/src/hooks/useEditorState.ts
15823
- import { useState as useState34, useCallback as useCallback37, useContext as useContext81, useEffect as useEffect86, useRef as useRef54 } from "react";
16015
+ import { useState as useState34, useCallback as useCallback38, useContext as useContext81, useEffect as useEffect88, useRef as useRef55 } from "react";
15824
16016
  function buildEntityInfo(engine) {
15825
16017
  const nameMap = /* @__PURE__ */ new Map();
15826
16018
  engine.entityIds.forEach((eid, name) => nameMap.set(eid, name));
@@ -15838,18 +16030,18 @@ function useEditorState(refreshHz = 4) {
15838
16030
  const engine = useContext81(EngineContext);
15839
16031
  const [entities, setEntities] = useState34([]);
15840
16032
  const [selectedId, setSelectedId] = useState34(null);
15841
- const intervalRef = useRef54(null);
15842
- const refresh = useCallback37(() => {
16033
+ const intervalRef = useRef55(null);
16034
+ const refresh = useCallback38(() => {
15843
16035
  setEntities(buildEntityInfo(engine));
15844
16036
  }, [engine]);
15845
- useEffect86(() => {
16037
+ useEffect88(() => {
15846
16038
  refresh();
15847
16039
  intervalRef.current = setInterval(refresh, 1e3 / refreshHz);
15848
16040
  return () => {
15849
16041
  if (intervalRef.current !== null) clearInterval(intervalRef.current);
15850
16042
  };
15851
16043
  }, [refresh, refreshHz]);
15852
- const select = useCallback37((id) => {
16044
+ const select = useCallback38((id) => {
15853
16045
  setSelectedId(id);
15854
16046
  }, []);
15855
16047
  const selectedComponents = selectedId !== null ? engine.ecs.getEntityComponents(selectedId) : [];
@@ -16164,6 +16356,7 @@ export {
16164
16356
  useDraggable,
16165
16357
  useDropThrough,
16166
16358
  useDroppable,
16359
+ useDynamicCanvas,
16167
16360
  useEditorState,
16168
16361
  useEntity,
16169
16362
  useEvent,
@@ -16183,6 +16376,7 @@ export {
16183
16376
  useHitstop,
16184
16377
  useHoverable,
16185
16378
  useIDBSave,
16379
+ useIdleFrameSkip,
16186
16380
  useInput,
16187
16381
  useInputBuffer,
16188
16382
  useInputContext,