cubeforge 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ import {
63
63
  triangleArea,
64
64
  triangleMassProperties,
65
65
  velocityAtPoint
66
- } from "./chunk-OT6I5RYW.js";
66
+ } from "./chunk-UMHYQ4EP.js";
67
67
 
68
68
  // src/components/Game.tsx
69
69
  import { useEffect as useEffect11, useRef as useRef11, useState as useState3 } from "react";
@@ -1496,7 +1496,6 @@ function createTimeline(opts) {
1496
1496
  seek(time) {
1497
1497
  elapsed = 0;
1498
1498
  resetSegments();
1499
- elapsed = 0;
1500
1499
  const step = 1 / 60;
1501
1500
  while (elapsed < time) {
1502
1501
  const d = Math.min(step, time - elapsed);
@@ -5437,6 +5436,10 @@ function useAudioScheduler(opts) {
5437
5436
  if (isRunningRef.current) return;
5438
5437
  const ctx = getAudioCtx();
5439
5438
  if (ctx.state === "suspended") void ctx.resume();
5439
+ if (timerRef.current !== null) {
5440
+ clearInterval(timerRef.current);
5441
+ timerRef.current = null;
5442
+ }
5440
5443
  nextBeatTimeRef.current = ctx.currentTime;
5441
5444
  nextBeatIndexRef.current = 0;
5442
5445
  currentBeatRef.current = 0;
@@ -7047,7 +7050,7 @@ function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
7047
7050
  }
7048
7051
 
7049
7052
  // src/components/Sprite.tsx
7050
- import { useEffect as useEffect15, useContext as useContext6 } from "react";
7053
+ import { useEffect as useEffect15, useContext as useContext6, useRef as useRef12 } from "react";
7051
7054
  function Sprite({
7052
7055
  width,
7053
7056
  height,
@@ -7150,6 +7153,37 @@ function Sprite({
7150
7153
  }
7151
7154
  return () => engine.ecs.removeComponent(entityId, "Sprite");
7152
7155
  }, []);
7156
+ const didMount = useRef12(false);
7157
+ useEffect15(() => {
7158
+ if (!didMount.current) {
7159
+ didMount.current = true;
7160
+ return;
7161
+ }
7162
+ const comp = engine.ecs.getComponent(entityId, "Sprite");
7163
+ if (!comp) return;
7164
+ if (!src) {
7165
+ comp.image = void 0;
7166
+ comp.src = void 0;
7167
+ return;
7168
+ }
7169
+ let cancelled = false;
7170
+ engine.assets.loadImage(src).then((img) => {
7171
+ if (cancelled) return;
7172
+ const c = engine.ecs.getComponent(entityId, "Sprite");
7173
+ if (c) {
7174
+ c.image = img;
7175
+ c.src = img.src;
7176
+ }
7177
+ }).catch((err) => {
7178
+ if (cancelled) return;
7179
+ const message = err instanceof Error ? err.message : String(err);
7180
+ engine.events.emit("asset:error", { type: "image", src, entityId, error: err });
7181
+ console.error(`[Cubeforge] <Sprite> failed to reload image "${src}": ${message}`);
7182
+ });
7183
+ return () => {
7184
+ cancelled = true;
7185
+ };
7186
+ }, [src, engine, entityId]);
7153
7187
  useEffect15(() => {
7154
7188
  const comp = engine.ecs.getComponent(entityId, "Sprite");
7155
7189
  if (!comp) return;
@@ -7500,7 +7534,7 @@ function CompoundCollider({ shapes, isTrigger = false, layer = "default", mask =
7500
7534
  }
7501
7535
 
7502
7536
  // src/components/Script.tsx
7503
- import { useEffect as useEffect22, useContext as useContext13, useRef as useRef12 } from "react";
7537
+ import { useEffect as useEffect22, useContext as useContext13, useRef as useRef13 } from "react";
7504
7538
  function Script({ init, update }) {
7505
7539
  const engine = useContext13(EngineContext);
7506
7540
  const entityId = useContext13(EntityContext);
@@ -7509,9 +7543,9 @@ function Script({ init, update }) {
7509
7543
  console.warn("[Cubeforge] <Script> must be inside an <Entity>. No EntityContext found.");
7510
7544
  }
7511
7545
  }
7512
- const initRef = useRef12(init);
7546
+ const initRef = useRef13(init);
7513
7547
  initRef.current = init;
7514
- const updateRef = useRef12(update);
7548
+ const updateRef = useRef13(update);
7515
7549
  updateRef.current = update;
7516
7550
  useEffect22(() => {
7517
7551
  if (initRef.current) {
@@ -7795,6 +7829,7 @@ import { useEffect as useEffect28, useContext as useContext19 } from "react";
7795
7829
 
7796
7830
  // src/components/particlePresets.ts
7797
7831
  var PARTICLE_PRESETS = {
7832
+ // ── Action / combat ──
7798
7833
  explosion: {
7799
7834
  rate: 60,
7800
7835
  speed: 200,
@@ -7803,8 +7838,12 @@ var PARTICLE_PRESETS = {
7803
7838
  particleLife: 0.5,
7804
7839
  particleSize: 6,
7805
7840
  color: "#ff6b35",
7841
+ colorOverLife: ["#fff3b0", "#ff6b35", "#6d4c41"],
7842
+ sizeOverLife: { start: 1.4, end: 0 },
7806
7843
  gravity: 300,
7807
- maxParticles: 80
7844
+ maxParticles: 80,
7845
+ blendMode: "additive",
7846
+ particleShape: "soft"
7808
7847
  },
7809
7848
  spark: {
7810
7849
  rate: 40,
@@ -7815,7 +7854,70 @@ var PARTICLE_PRESETS = {
7815
7854
  particleSize: 3,
7816
7855
  color: "#ffd54f",
7817
7856
  gravity: 400,
7818
- maxParticles: 50
7857
+ maxParticles: 50,
7858
+ blendMode: "additive",
7859
+ particleShape: "soft"
7860
+ },
7861
+ damage: {
7862
+ rate: 45,
7863
+ speed: 120,
7864
+ spread: Math.PI * 2,
7865
+ angle: 0,
7866
+ particleLife: 0.35,
7867
+ particleSize: 4,
7868
+ color: "#ef5350",
7869
+ colorOverLife: ["#ffab91", "#ef5350", "#4a148c"],
7870
+ sizeOverLife: { start: 1, end: 0.2 },
7871
+ gravity: 150,
7872
+ maxParticles: 40,
7873
+ blendMode: "normal",
7874
+ particleShape: "circle"
7875
+ },
7876
+ heal: {
7877
+ rate: 22,
7878
+ speed: 50,
7879
+ spread: Math.PI / 2,
7880
+ angle: -Math.PI / 2,
7881
+ particleLife: 0.9,
7882
+ particleSize: 5,
7883
+ color: "#8bc34a",
7884
+ colorOverLife: ["#e8f5e9", "#8bc34a"],
7885
+ sizeOverLife: { start: 0.5, end: 1.2 },
7886
+ gravity: -60,
7887
+ maxParticles: 30,
7888
+ blendMode: "additive",
7889
+ particleShape: "soft"
7890
+ },
7891
+ magic: {
7892
+ rate: 32,
7893
+ speed: 80,
7894
+ spread: Math.PI * 2,
7895
+ angle: 0,
7896
+ particleLife: 1.1,
7897
+ particleSize: 5,
7898
+ color: "#ba68c8",
7899
+ colorOverLife: ["#e1bee7", "#ba68c8", "#4a148c"],
7900
+ sizeOverLife: { start: 1, end: 0 },
7901
+ gravity: -20,
7902
+ maxParticles: 60,
7903
+ blendMode: "additive",
7904
+ particleShape: "soft"
7905
+ },
7906
+ // ── Environment ──
7907
+ fire: {
7908
+ rate: 40,
7909
+ speed: 60,
7910
+ spread: 0.6,
7911
+ angle: -Math.PI / 2,
7912
+ particleLife: 0.7,
7913
+ particleSize: 7,
7914
+ color: "#ff9800",
7915
+ colorOverLife: ["#ffe082", "#ff9800", "#6d4c41", "#37474f"],
7916
+ sizeOverLife: { start: 1.2, end: 0.3 },
7917
+ gravity: -120,
7918
+ maxParticles: 70,
7919
+ blendMode: "additive",
7920
+ particleShape: "soft"
7819
7921
  },
7820
7922
  smoke: {
7821
7923
  rate: 15,
@@ -7825,9 +7927,54 @@ var PARTICLE_PRESETS = {
7825
7927
  particleLife: 1.2,
7826
7928
  particleSize: 10,
7827
7929
  color: "#90a4ae",
7930
+ colorOverLife: ["#90a4ae", "rgba(55,71,79,0)"],
7931
+ sizeOverLife: { start: 0.5, end: 1.8 },
7828
7932
  gravity: -20,
7829
- maxParticles: 40
7933
+ maxParticles: 40,
7934
+ blendMode: "normal",
7935
+ particleShape: "soft"
7936
+ },
7937
+ rain: {
7938
+ rate: 80,
7939
+ speed: 350,
7940
+ spread: 0.05,
7941
+ angle: Math.PI / 2,
7942
+ particleLife: 0.8,
7943
+ particleSize: 2,
7944
+ color: "#81d4fa",
7945
+ gravity: 900,
7946
+ maxParticles: 300,
7947
+ blendMode: "normal",
7948
+ particleShape: "square"
7949
+ },
7950
+ snow: {
7951
+ rate: 20,
7952
+ speed: 40,
7953
+ spread: 0.4,
7954
+ angle: Math.PI / 2,
7955
+ particleLife: 4,
7956
+ particleSize: 3,
7957
+ color: "#ffffff",
7958
+ gravity: 20,
7959
+ maxParticles: 150,
7960
+ blendMode: "normal",
7961
+ particleShape: "circle"
7830
7962
  },
7963
+ fountain: {
7964
+ rate: 50,
7965
+ speed: 300,
7966
+ spread: 0.3,
7967
+ angle: -Math.PI / 2,
7968
+ particleLife: 1.5,
7969
+ particleSize: 4,
7970
+ color: "#4fc3f7",
7971
+ colorOverLife: ["#b3e5fc", "#4fc3f7", "#0277bd"],
7972
+ gravity: 500,
7973
+ maxParticles: 200,
7974
+ blendMode: "additive",
7975
+ particleShape: "soft"
7976
+ },
7977
+ // ── UI / reward ──
7831
7978
  coinPickup: {
7832
7979
  rate: 30,
7833
7980
  speed: 80,
@@ -7837,8 +7984,54 @@ var PARTICLE_PRESETS = {
7837
7984
  particleSize: 4,
7838
7985
  color: "#ffd700",
7839
7986
  gravity: 200,
7840
- maxParticles: 20
7987
+ maxParticles: 20,
7988
+ blendMode: "additive",
7989
+ particleShape: "soft"
7841
7990
  },
7991
+ pickup: {
7992
+ rate: 35,
7993
+ speed: 70,
7994
+ spread: Math.PI * 2,
7995
+ angle: 0,
7996
+ particleLife: 0.5,
7997
+ particleSize: 5,
7998
+ color: "#4fc3f7",
7999
+ colorOverLife: ["#ffffff", "#4fc3f7"],
8000
+ sizeOverLife: { start: 1, end: 0 },
8001
+ gravity: -80,
8002
+ maxParticles: 24,
8003
+ blendMode: "additive",
8004
+ particleShape: "soft"
8005
+ },
8006
+ sparkle: {
8007
+ rate: 14,
8008
+ speed: 40,
8009
+ spread: Math.PI * 2,
8010
+ angle: 0,
8011
+ particleLife: 0.8,
8012
+ particleSize: 3,
8013
+ color: "#fffde7",
8014
+ sizeOverLife: { start: 0, end: 1 },
8015
+ gravity: 0,
8016
+ maxParticles: 30,
8017
+ blendMode: "additive",
8018
+ particleShape: "soft"
8019
+ },
8020
+ confetti: {
8021
+ rate: 70,
8022
+ speed: 280,
8023
+ spread: Math.PI / 3,
8024
+ angle: -Math.PI / 2,
8025
+ particleLife: 2.2,
8026
+ particleSize: 5,
8027
+ color: "#ffc107",
8028
+ colorOverLife: ["#f44336", "#ffc107", "#4caf50", "#2196f3", "#9c27b0"],
8029
+ gravity: 400,
8030
+ maxParticles: 150,
8031
+ blendMode: "normal",
8032
+ particleShape: "square"
8033
+ },
8034
+ // ── Character ──
7842
8035
  jumpDust: {
7843
8036
  rate: 25,
7844
8037
  speed: 60,
@@ -7848,7 +8041,23 @@ var PARTICLE_PRESETS = {
7848
8041
  particleSize: 5,
7849
8042
  color: "#b0bec5",
7850
8043
  gravity: 80,
7851
- maxParticles: 20
8044
+ maxParticles: 20,
8045
+ blendMode: "normal",
8046
+ particleShape: "soft"
8047
+ },
8048
+ trail: {
8049
+ rate: 30,
8050
+ speed: 8,
8051
+ spread: 0.2,
8052
+ angle: Math.PI / 2,
8053
+ particleLife: 0.5,
8054
+ particleSize: 4,
8055
+ color: "#4fc3f7",
8056
+ sizeOverLife: { start: 1, end: 0 },
8057
+ gravity: 0,
8058
+ maxParticles: 40,
8059
+ blendMode: "additive",
8060
+ particleShape: "soft"
7852
8061
  }
7853
8062
  };
7854
8063
 
@@ -7894,6 +8103,10 @@ function ParticleEmitter({
7894
8103
  const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
7895
8104
  const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
7896
8105
  const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
8106
+ const resolvedBlendMode = blendMode ?? presetConfig.blendMode;
8107
+ const resolvedParticleShape = particleShape ?? presetConfig.particleShape;
8108
+ const resolvedColorOverLife = colorOverLife ?? presetConfig.colorOverLife;
8109
+ const resolvedSizeOverLife = sizeOverLife ?? presetConfig.sizeOverLife;
7897
8110
  const engine = useContext19(EngineContext);
7898
8111
  const entityId = useContext19(EntityContext);
7899
8112
  useEffect28(() => {
@@ -7919,15 +8132,15 @@ function ParticleEmitter({
7919
8132
  textureSrc,
7920
8133
  enableRotation,
7921
8134
  rotationSpeedRange,
7922
- sizeOverLife,
8135
+ sizeOverLife: resolvedSizeOverLife,
7923
8136
  attractors,
7924
- colorOverLife,
7925
- blendMode,
8137
+ colorOverLife: resolvedColorOverLife,
8138
+ blendMode: resolvedBlendMode,
7926
8139
  mode,
7927
8140
  formationPoints,
7928
8141
  seekStrength,
7929
8142
  colorTransitionDuration,
7930
- particleShape
8143
+ particleShape: resolvedParticleShape
7931
8144
  });
7932
8145
  return () => engine.ecs.removeComponent(entityId, "ParticlePool");
7933
8146
  }, []);
@@ -7939,9 +8152,9 @@ function ParticleEmitter({
7939
8152
  useEffect28(() => {
7940
8153
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
7941
8154
  if (!pool) return;
7942
- pool.blendMode = blendMode;
7943
- pool.particleShape = particleShape;
7944
- }, [blendMode, particleShape, engine, entityId]);
8155
+ pool.blendMode = resolvedBlendMode;
8156
+ pool.particleShape = resolvedParticleShape;
8157
+ }, [resolvedBlendMode, resolvedParticleShape, engine, entityId]);
7945
8158
  useEffect28(() => {
7946
8159
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
7947
8160
  if (!pool) return;
@@ -7971,7 +8184,7 @@ function ParticleEmitter({
7971
8184
  }
7972
8185
 
7973
8186
  // src/components/VirtualJoystick.tsx
7974
- import { useRef as useRef13 } from "react";
8187
+ import { useRef as useRef14 } from "react";
7975
8188
 
7976
8189
  // src/hooks/useVirtualInput.ts
7977
8190
  var _axes = { x: 0, y: 0 };
@@ -8005,10 +8218,10 @@ function VirtualJoystick({
8005
8218
  actionLabel = "A",
8006
8219
  actionName = "action"
8007
8220
  }) {
8008
- const baseRef = useRef13(null);
8009
- const stickRef = useRef13(null);
8010
- const activePtr = useRef13(null);
8011
- const baseCenterRef = useRef13({ x: 0, y: 0 });
8221
+ const baseRef = useRef14(null);
8222
+ const stickRef = useRef14(null);
8223
+ const activePtr = useRef14(null);
8224
+ const baseCenterRef = useRef14({ x: 0, y: 0 });
8012
8225
  const radius = size / 2 - 16;
8013
8226
  const applyStickPosition = (dx, dy) => {
8014
8227
  if (!stickRef.current) return;
@@ -8556,10 +8769,10 @@ function ParallaxLayer({
8556
8769
  }
8557
8770
 
8558
8771
  // src/components/ScreenFlash.tsx
8559
- import { forwardRef, useImperativeHandle, useRef as useRef14 } from "react";
8772
+ import { forwardRef, useImperativeHandle, useRef as useRef15 } from "react";
8560
8773
  import { jsx as jsx12 } from "react/jsx-runtime";
8561
8774
  var ScreenFlash = forwardRef((_, ref) => {
8562
- const divRef = useRef14(null);
8775
+ const divRef = useRef15(null);
8563
8776
  useImperativeHandle(ref, () => ({
8564
8777
  flash(color, duration) {
8565
8778
  const el = divRef.current;
@@ -8595,12 +8808,12 @@ var ScreenFlash = forwardRef((_, ref) => {
8595
8808
  ScreenFlash.displayName = "ScreenFlash";
8596
8809
 
8597
8810
  // src/components/CameraZone.tsx
8598
- import { useEffect as useEffect31, useContext as useContext22, useRef as useRef15 } from "react";
8811
+ import { useEffect as useEffect31, useContext as useContext22, useRef as useRef16 } from "react";
8599
8812
  import { Fragment as Fragment6, jsx as jsx13 } from "react/jsx-runtime";
8600
8813
  function CameraZone({ x, y, width, height, watchTag = "player", targetX, targetY, children }) {
8601
8814
  const engine = useContext22(EngineContext);
8602
- const prevFollowRef = useRef15(void 0);
8603
- const activeRef = useRef15(false);
8815
+ const prevFollowRef = useRef16(void 0);
8816
+ const activeRef = useRef16(false);
8604
8817
  useEffect31(() => {
8605
8818
  const eid = engine.ecs.createEntity();
8606
8819
  engine.ecs.addComponent(
@@ -9356,7 +9569,7 @@ function useInputMap(bindings) {
9356
9569
  }
9357
9570
 
9358
9571
  // src/hooks/useEvents.ts
9359
- import { useContext as useContext43, useEffect as useEffect49, useRef as useRef16 } from "react";
9572
+ import { useContext as useContext43, useEffect as useEffect49, useRef as useRef17 } from "react";
9360
9573
  function useEvents() {
9361
9574
  const engine = useContext43(EngineContext);
9362
9575
  if (!engine) throw new Error("useEvents must be used inside <Game>");
@@ -9364,7 +9577,7 @@ function useEvents() {
9364
9577
  }
9365
9578
  function useEvent(event, handler) {
9366
9579
  const events = useEvents();
9367
- const handlerRef = useRef16(handler);
9580
+ const handlerRef = useRef17(handler);
9368
9581
  handlerRef.current = handler;
9369
9582
  useEffect49(() => {
9370
9583
  return events.on(event, (data) => handlerRef.current(data));
@@ -9407,13 +9620,13 @@ function useCoordinates() {
9407
9620
  }
9408
9621
 
9409
9622
  // src/hooks/useWorldQuery.ts
9410
- import { useEffect as useEffect50, useRef as useRef17, useState as useState8 } from "react";
9623
+ import { useEffect as useEffect50, useRef as useRef18, useState as useState8 } from "react";
9411
9624
  function useWorldQuery(...components) {
9412
9625
  const engine = useGame();
9413
9626
  const [result, setResult] = useState8(() => engine.ecs.query(...components));
9414
- const prevRef = useRef17([]);
9415
- const rafRef = useRef17(0);
9416
- const mountedRef = useRef17(true);
9627
+ const prevRef = useRef18([]);
9628
+ const rafRef = useRef18(0);
9629
+ const mountedRef = useRef18(true);
9417
9630
  useEffect50(() => {
9418
9631
  mountedRef.current = true;
9419
9632
  const tick2 = () => {
@@ -9480,11 +9693,11 @@ function useInputRecorder() {
9480
9693
  }
9481
9694
 
9482
9695
  // src/hooks/useGamepad.ts
9483
- import { useEffect as useEffect52, useRef as useRef18, useState as useState9 } from "react";
9696
+ import { useEffect as useEffect52, useRef as useRef19, useState as useState9 } from "react";
9484
9697
  var EMPTY_STATE = { connected: false, axes: [], buttons: [] };
9485
9698
  function useGamepad(playerIndex = 0) {
9486
9699
  const [state, setState] = useState9(EMPTY_STATE);
9487
- const rafRef = useRef18(0);
9700
+ const rafRef = useRef19(0);
9488
9701
  useEffect52(() => {
9489
9702
  const poll = () => {
9490
9703
  const gp = navigator.getGamepads()[playerIndex];
@@ -9526,7 +9739,7 @@ function usePause() {
9526
9739
  }
9527
9740
 
9528
9741
  // src/hooks/useProfiler.ts
9529
- import { useContext as useContext46, useEffect as useEffect53, useRef as useRef19, useState as useState11 } from "react";
9742
+ import { useContext as useContext46, useEffect as useEffect53, useRef as useRef20, useState as useState11 } from "react";
9530
9743
  var EMPTY = {
9531
9744
  fps: 0,
9532
9745
  frameTime: 0,
@@ -9536,9 +9749,9 @@ var EMPTY = {
9536
9749
  function useProfiler() {
9537
9750
  const engine = useContext46(EngineContext);
9538
9751
  const [data, setData] = useState11(EMPTY);
9539
- const frameTimesRef = useRef19([]);
9540
- const lastUpdateRef = useRef19(0);
9541
- const prevTimeRef = useRef19(0);
9752
+ const frameTimesRef = useRef20([]);
9753
+ const lastUpdateRef = useRef20(0);
9754
+ const prevTimeRef = useRef20(0);
9542
9755
  useEffect53(() => {
9543
9756
  if (!engine) return;
9544
9757
  let rafId2;
@@ -9652,11 +9865,11 @@ function useTouch() {
9652
9865
  }
9653
9866
 
9654
9867
  // src/hooks/useGestures.ts
9655
- import { useEffect as useEffect57, useRef as useRef20 } from "react";
9868
+ import { useEffect as useEffect57, useRef as useRef21 } from "react";
9656
9869
  function useGestures(handlers, opts = {}) {
9657
- const handlersRef = useRef20(handlers);
9870
+ const handlersRef = useRef21(handlers);
9658
9871
  handlersRef.current = handlers;
9659
- const optsRef = useRef20(opts);
9872
+ const optsRef = useRef21(opts);
9660
9873
  optsRef.current = opts;
9661
9874
  useEffect57(() => {
9662
9875
  const target = optsRef.current.target ?? window;
@@ -9793,14 +10006,14 @@ function useGamepadHaptics(playerIndex = 0) {
9793
10006
  }
9794
10007
 
9795
10008
  // src/hooks/useTimer.ts
9796
- import { useRef as useRef21, useCallback as useCallback5, useEffect as useEffect58, useContext as useContext49 } from "react";
10009
+ import { useRef as useRef22, useCallback as useCallback5, useEffect as useEffect58, useContext as useContext49 } from "react";
9797
10010
  function useTimer(duration, onComplete, opts) {
9798
10011
  const engine = useContext49(EngineContext);
9799
- const onCompleteRef = useRef21(onComplete);
10012
+ const onCompleteRef = useRef22(onComplete);
9800
10013
  onCompleteRef.current = onComplete;
9801
- const loopRef = useRef21(opts?.loop ?? false);
10014
+ const loopRef = useRef22(opts?.loop ?? false);
9802
10015
  loopRef.current = opts?.loop ?? false;
9803
- const timerRef = useRef21(null);
10016
+ const timerRef = useRef22(null);
9804
10017
  if (!timerRef.current) {
9805
10018
  timerRef.current = createTimer(
9806
10019
  duration,
@@ -9854,14 +10067,14 @@ function useTimer(duration, onComplete, opts) {
9854
10067
  }
9855
10068
 
9856
10069
  // src/hooks/useCoroutine.ts
9857
- import { useRef as useRef22, useEffect as useEffect59, useContext as useContext50 } from "react";
10070
+ import { useRef as useRef23, useEffect as useEffect59, useContext as useContext50 } from "react";
9858
10071
  var wait = (seconds) => ({ type: "wait", seconds });
9859
10072
  var waitFrames = (frames) => ({ type: "waitFrames", frames });
9860
10073
  var waitUntil = (condition) => ({ type: "waitUntil", condition });
9861
10074
  var nextCoroutineId = 1;
9862
10075
  function useCoroutine() {
9863
10076
  const engine = useContext50(EngineContext);
9864
- const coroutinesRef = useRef22(/* @__PURE__ */ new Map());
10077
+ const coroutinesRef = useRef23(/* @__PURE__ */ new Map());
9865
10078
  useEffect59(() => {
9866
10079
  const eid = engine.ecs.createEntity();
9867
10080
  engine.ecs.addComponent(
@@ -9927,15 +10140,15 @@ function useCoroutine() {
9927
10140
  return coroutinesRef.current.size;
9928
10141
  }
9929
10142
  };
9930
- const controlsRef = useRef22(controls);
10143
+ const controlsRef = useRef23(controls);
9931
10144
  return controlsRef.current;
9932
10145
  }
9933
10146
 
9934
10147
  // src/hooks/useSceneManager.ts
9935
- import { useState as useState12, useCallback as useCallback6, useRef as useRef23 } from "react";
10148
+ import { useState as useState12, useCallback as useCallback6, useRef as useRef24 } from "react";
9936
10149
  function useSceneManager(initialScene) {
9937
10150
  const [stack, setStack] = useState12([initialScene]);
9938
- const stackRef = useRef23(stack);
10151
+ const stackRef = useRef24(stack);
9939
10152
  stackRef.current = stack;
9940
10153
  const push = useCallback6((scene) => {
9941
10154
  setStack((prev) => [...prev, scene]);
@@ -9971,7 +10184,7 @@ function useSceneManager(initialScene) {
9971
10184
  }
9972
10185
 
9973
10186
  // src/hooks/useSceneTransition.ts
9974
- import { useState as useState13, useCallback as useCallback7, useRef as useRef24, useEffect as useEffect60 } from "react";
10187
+ import { useState as useState13, useCallback as useCallback7, useRef as useRef25, useEffect as useEffect60 } from "react";
9975
10188
  function useSceneTransition(initialScene, defaultTransition) {
9976
10189
  const [stack, setStack] = useState13([initialScene]);
9977
10190
  const [transState, setTransState] = useState13({
@@ -9980,9 +10193,9 @@ function useSceneTransition(initialScene, defaultTransition) {
9980
10193
  effect: null,
9981
10194
  pendingAction: null
9982
10195
  });
9983
- const stackRef = useRef24(stack);
10196
+ const stackRef = useRef25(stack);
9984
10197
  stackRef.current = stack;
9985
- const transRef = useRef24(transState);
10198
+ const transRef = useRef25(transState);
9986
10199
  transRef.current = transState;
9987
10200
  useEffect60(() => {
9988
10201
  if (transState.phase === "idle") return;
@@ -10171,9 +10384,9 @@ function useInputBuffer(opts) {
10171
10384
  }
10172
10385
 
10173
10386
  // src/hooks/useComboDetector.ts
10174
- import { useMemo as useMemo10, useRef as useRef25 } from "react";
10387
+ import { useMemo as useMemo10, useRef as useRef26 } from "react";
10175
10388
  function useComboDetector(combos) {
10176
- const lastComboRef = useRef25(null);
10389
+ const lastComboRef = useRef26(null);
10177
10390
  const result = useMemo10(() => {
10178
10391
  const detector = new ComboDetector({ combos });
10179
10392
  const api = {
@@ -10237,17 +10450,17 @@ function useAccessibility() {
10237
10450
  }
10238
10451
 
10239
10452
  // src/hooks/useHMR.ts
10240
- import { useEffect as useEffect62, useRef as useRef26, useMemo as useMemo11 } from "react";
10453
+ import { useEffect as useEffect62, useRef as useRef27, useMemo as useMemo11 } from "react";
10241
10454
  var idCounter = 0;
10242
10455
  function useHMR(hmrKey) {
10243
- const idRef = useRef26(null);
10456
+ const idRef = useRef27(null);
10244
10457
  if (idRef.current === null) {
10245
10458
  idRef.current = hmrKey ?? `__hmr_${idCounter++}`;
10246
10459
  }
10247
10460
  const key = idRef.current;
10248
10461
  const isHotReload = hmrLoadState(key) !== void 0;
10249
- const disposeRef = useRef26(null);
10250
- const acceptRef = useRef26(null);
10462
+ const disposeRef = useRef27(null);
10463
+ const acceptRef = useRef27(null);
10251
10464
  useEffect62(() => {
10252
10465
  const hot = import.meta.hot;
10253
10466
  if (!hot) return;
@@ -10298,13 +10511,13 @@ function useSquashStretch() {
10298
10511
  }
10299
10512
 
10300
10513
  // src/hooks/useHistory.ts
10301
- import { useCallback as useCallback11, useContext as useContext54, useEffect as useEffect63, useRef as useRef27, useState as useState14 } from "react";
10514
+ import { useCallback as useCallback11, useContext as useContext54, useEffect as useEffect63, useRef as useRef28, useState as useState14 } from "react";
10302
10515
  function useHistory(options) {
10303
10516
  const engine = useContext54(EngineContext);
10304
10517
  const capacity = options?.capacity ?? 50;
10305
10518
  const bindKeys = options?.bindKeyboardShortcuts ?? false;
10306
- const stackRef = useRef27([]);
10307
- const indexRef = useRef27(-1);
10519
+ const stackRef = useRef28([]);
10520
+ const indexRef = useRef28(-1);
10308
10521
  const [version, setVersion] = useState14(0);
10309
10522
  const bump = useCallback11(() => setVersion((v) => v + 1), []);
10310
10523
  const push = useCallback11(() => {
@@ -10438,7 +10651,7 @@ function useSelection() {
10438
10651
  }
10439
10652
 
10440
10653
  // src/components/TransformHandles.tsx
10441
- import { useContext as useContext56, useEffect as useEffect66, useRef as useRef28 } from "react";
10654
+ import { useContext as useContext56, useEffect as useEffect66, useRef as useRef29 } from "react";
10442
10655
 
10443
10656
  // src/hooks/useOverlayTick.ts
10444
10657
  import { useEffect as useEffect65 } from "react";
@@ -10480,8 +10693,8 @@ function TransformHandles({
10480
10693
  }) {
10481
10694
  const engine = useContext56(EngineContext);
10482
10695
  const selection = useSelection();
10483
- const overlayRef = useRef28(null);
10484
- const dragRef = useRef28(null);
10696
+ const overlayRef = useRef29(null);
10697
+ const dragRef = useRef29(null);
10485
10698
  useOverlayTick(() => {
10486
10699
  if (!engine) return;
10487
10700
  const overlay = overlayRef.current;
@@ -10831,7 +11044,7 @@ function getBounds(ecs, id) {
10831
11044
  }
10832
11045
 
10833
11046
  // src/components/EditableText.tsx
10834
- import { useContext as useContext58, useEffect as useEffect67, useRef as useRef29 } from "react";
11047
+ import { useContext as useContext58, useEffect as useEffect67, useRef as useRef30 } from "react";
10835
11048
  import { jsx as jsx17 } from "react/jsx-runtime";
10836
11049
  function EditableText({
10837
11050
  value,
@@ -10855,8 +11068,8 @@ function EditableText({
10855
11068
  }) {
10856
11069
  const engine = useContext58(EngineContext);
10857
11070
  const entityId = useContext58(EntityContext);
10858
- const containerRef = useRef29(null);
10859
- const inputRef = useRef29(null);
11071
+ const containerRef = useRef30(null);
11072
+ const inputRef = useRef30(null);
10860
11073
  useOverlayTick(() => {
10861
11074
  if (!engine || entityId === null || entityId === void 0) return;
10862
11075
  const container = containerRef.current;
@@ -11014,7 +11227,7 @@ function copyRegion(source, region, scale) {
11014
11227
  }
11015
11228
 
11016
11229
  // src/components/A11yNode.tsx
11017
- import { useContext as useContext59, useRef as useRef30 } from "react";
11230
+ import { useContext as useContext59, useRef as useRef31 } from "react";
11018
11231
  import { jsx as jsx18 } from "react/jsx-runtime";
11019
11232
  function A11yNode({
11020
11233
  label,
@@ -11030,7 +11243,7 @@ function A11yNode({
11030
11243
  }) {
11031
11244
  const engine = useContext59(EngineContext);
11032
11245
  const entityId = useContext59(EntityContext);
11033
- const nodeRef = useRef30(null);
11246
+ const nodeRef = useRef31(null);
11034
11247
  useOverlayTick(() => {
11035
11248
  if (!engine || entityId === null || entityId === void 0) return;
11036
11249
  const node = nodeRef.current;
@@ -11119,7 +11332,7 @@ function worldToScreenCss3(engine, wx, wy) {
11119
11332
  }
11120
11333
 
11121
11334
  // src/components/VectorPath.tsx
11122
- import { useContext as useContext60, useRef as useRef31 } from "react";
11335
+ import { useContext as useContext60, useRef as useRef32 } from "react";
11123
11336
  import { jsx as jsx19 } from "react/jsx-runtime";
11124
11337
  function VectorPath({
11125
11338
  d,
@@ -11135,7 +11348,7 @@ function VectorPath({
11135
11348
  }) {
11136
11349
  const engine = useContext60(EngineContext);
11137
11350
  const entityId = useContext60(EntityContext);
11138
- const svgRef = useRef31(null);
11351
+ const svgRef = useRef32(null);
11139
11352
  useOverlayTick(() => {
11140
11353
  if (!engine || entityId === null || entityId === void 0) return;
11141
11354
  const svg = svgRef.current;
@@ -11196,12 +11409,12 @@ function worldToScreenCss4(engine, wx, wy) {
11196
11409
  }
11197
11410
 
11198
11411
  // src/hooks/useGrid.ts
11199
- import { useCallback as useCallback14, useContext as useContext61, useMemo as useMemo13, useRef as useRef32, useState as useState16 } from "react";
11412
+ import { useCallback as useCallback14, useContext as useContext61, useMemo as useMemo13, useRef as useRef33, useState as useState16 } from "react";
11200
11413
  function useGrid({ width, height, fill }) {
11201
11414
  const engine = useContext61(EngineContext);
11202
11415
  const [version, setVersion] = useState16(0);
11203
11416
  const bump = useCallback14(() => setVersion((v) => v + 1), []);
11204
- const dataRef = useRef32(null);
11417
+ const dataRef = useRef33(null);
11205
11418
  if (dataRef.current === null) {
11206
11419
  const arr = new Array(width * height);
11207
11420
  if (typeof fill === "function") {
@@ -11381,7 +11594,7 @@ function useGrid({ width, height, fill }) {
11381
11594
  }
11382
11595
 
11383
11596
  // src/hooks/useTurnSystem.ts
11384
- import { useCallback as useCallback15, useContext as useContext62, useEffect as useEffect68, useMemo as useMemo14, useRef as useRef33, useState as useState17 } from "react";
11597
+ import { useCallback as useCallback15, useContext as useContext62, useEffect as useEffect68, useMemo as useMemo14, useRef as useRef34, useState as useState17 } from "react";
11385
11598
  function useTurnSystem({
11386
11599
  players,
11387
11600
  initialIndex = 0,
@@ -11394,10 +11607,10 @@ function useTurnSystem({
11394
11607
  const [activeIndex, setActiveIndex] = useState17(initialIndex % players.length);
11395
11608
  const [turn, setTurn] = useState17(0);
11396
11609
  const [isPending, setIsPending] = useState17(false);
11397
- const pendingRafId = useRef33(null);
11398
- const pendingRemaining = useRef33(0);
11399
- const pendingTarget = useRef33(null);
11400
- const pendingLastTime = useRef33(0);
11610
+ const pendingRafId = useRef34(null);
11611
+ const pendingRemaining = useRef34(0);
11612
+ const pendingTarget = useRef34(null);
11613
+ const pendingLastTime = useRef34(0);
11401
11614
  const clearPending = useCallback15(() => {
11402
11615
  if (pendingRafId.current !== null) {
11403
11616
  cancelAnimationFrame(pendingRafId.current);
@@ -11406,7 +11619,7 @@ function useTurnSystem({
11406
11619
  pendingTarget.current = null;
11407
11620
  setIsPending(false);
11408
11621
  }, []);
11409
- const startedOnce = useRef33(false);
11622
+ const startedOnce = useRef34(false);
11410
11623
  useEffect68(() => {
11411
11624
  if (startedOnce.current) return;
11412
11625
  startedOnce.current = true;
@@ -11583,7 +11796,7 @@ function screenToWorld(engine, cssX, cssY) {
11583
11796
  }
11584
11797
 
11585
11798
  // src/hooks/useDragDrop.ts
11586
- import { useContext as useContext64, useEffect as useEffect70, useRef as useRef34, useState as useState19 } from "react";
11799
+ import { useContext as useContext64, useEffect as useEffect70, useRef as useRef35, useState as useState19 } from "react";
11587
11800
  var dragStateByEngine = /* @__PURE__ */ new WeakMap();
11588
11801
  function getActiveDrag(engine) {
11589
11802
  return dragStateByEngine.get(engine) ?? null;
@@ -11608,7 +11821,7 @@ function useDraggable(options) {
11608
11821
  const entityId = options?.entityId ?? ctxEntityId;
11609
11822
  const [isDragging, setIsDragging] = useState19(false);
11610
11823
  const [position, setPosition] = useState19({ x: 0, y: 0 });
11611
- const optsRef = useRef34(options);
11824
+ const optsRef = useRef35(options);
11612
11825
  optsRef.current = options;
11613
11826
  useEffect70(() => {
11614
11827
  if (!engine || entityId === null || entityId === void 0 || options?.disabled) return;
@@ -11720,7 +11933,7 @@ function useDroppable(options) {
11720
11933
  const entityId = options?.entityId ?? ctxEntityId;
11721
11934
  const [isOver, setIsOver] = useState19(false);
11722
11935
  const [hoveredBy, setHoveredBy] = useState19(null);
11723
- const optsRef = useRef34(options);
11936
+ const optsRef = useRef35(options);
11724
11937
  optsRef.current = options;
11725
11938
  useEffect70(() => {
11726
11939
  if (!engine || entityId === null || entityId === void 0 || options?.disabled) return;
@@ -11824,7 +12037,7 @@ function findDroppableUnder(engine, drag) {
11824
12037
  }
11825
12038
 
11826
12039
  // src/hooks/useKeyboardFocus.ts
11827
- import { useContext as useContext65, useEffect as useEffect71, useState as useState20 } from "react";
12040
+ import { useContext as useContext65, useEffect as useEffect71, useRef as useRef36, useState as useState20 } from "react";
11828
12041
  var registry = /* @__PURE__ */ new Map();
11829
12042
  var focusedId = null;
11830
12043
  var subscribers = /* @__PURE__ */ new Set();
@@ -11841,17 +12054,19 @@ function setFocused(id) {
11841
12054
  }
11842
12055
  function useFocusable(options) {
11843
12056
  const entityIdCtx = useContext65(EntityContext);
12057
+ const optsRef = useRef36(options);
12058
+ optsRef.current = options;
11844
12059
  useEffect71(() => {
11845
12060
  if (entityIdCtx === null || entityIdCtx === void 0) return;
11846
12061
  const id = entityIdCtx;
11847
12062
  const entry = {
11848
12063
  entityId: id,
11849
- onFocus: options?.onFocus,
11850
- onBlur: options?.onBlur,
11851
- onActivate: options?.onActivate
12064
+ onFocus: (eid) => optsRef.current?.onFocus?.(eid),
12065
+ onBlur: (eid) => optsRef.current?.onBlur?.(eid),
12066
+ onActivate: (eid) => optsRef.current?.onActivate?.(eid)
11852
12067
  };
11853
12068
  registry.set(id, entry);
11854
- if (options?.autoFocus && focusedId === null) setFocused(id);
12069
+ if (optsRef.current?.autoFocus && focusedId === null) setFocused(id);
11855
12070
  return () => {
11856
12071
  registry.delete(id);
11857
12072
  if (focusedId === id) setFocused(null);
@@ -11994,7 +12209,7 @@ function subscribeFocus(cb) {
11994
12209
  }
11995
12210
 
11996
12211
  // src/components/FocusRing.tsx
11997
- import { useContext as useContext66, useEffect as useEffect72, useRef as useRef35, useState as useState21 } from "react";
12212
+ import { useContext as useContext66, useEffect as useEffect72, useRef as useRef37, useState as useState21 } from "react";
11998
12213
  import { Fragment as Fragment9, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
11999
12214
  function FocusRing({
12000
12215
  color = "#4fc3f7",
@@ -12004,7 +12219,7 @@ function FocusRing({
12004
12219
  pulse = true
12005
12220
  }) {
12006
12221
  const engine = useContext66(EngineContext);
12007
- const ringRef = useRef35(null);
12222
+ const ringRef = useRef37(null);
12008
12223
  const [focused, setFocused2] = useState21(getFocusedEntityId());
12009
12224
  const [reducedMotion, setReducedMotion] = useState21(false);
12010
12225
  useEffect72(() => {
@@ -12248,12 +12463,12 @@ function useDropThrough(frames = 8) {
12248
12463
  }
12249
12464
 
12250
12465
  // ../gameplay/src/hooks/useGameStateMachine.ts
12251
- import { useState as useState23, useRef as useRef36, useCallback as useCallback19, useEffect as useEffect73, useContext as useContext69 } from "react";
12466
+ import { useState as useState23, useRef as useRef38, useCallback as useCallback19, useEffect as useEffect73, useContext as useContext69 } from "react";
12252
12467
  function useGameStateMachine(states, initial) {
12253
12468
  const engine = useContext69(EngineContext);
12254
12469
  const [state, setState] = useState23(initial);
12255
- const stateRef = useRef36(initial);
12256
- const statesRef = useRef36(states);
12470
+ const stateRef = useRef38(initial);
12471
+ const statesRef = useRef38(states);
12257
12472
  statesRef.current = states;
12258
12473
  useEffect73(() => {
12259
12474
  statesRef.current[initial]?.onEnter?.();
@@ -12282,22 +12497,22 @@ function useGameStateMachine(states, initial) {
12282
12497
  }
12283
12498
 
12284
12499
  // ../gameplay/src/hooks/useHealth.ts
12285
- import { useRef as useRef37, useEffect as useEffect74, useContext as useContext70, useCallback as useCallback20 } from "react";
12500
+ import { useRef as useRef39, useEffect as useEffect74, useContext as useContext70, useCallback as useCallback20 } from "react";
12286
12501
  function useHealth(maxHp, opts = {}) {
12287
12502
  const engine = useContext70(EngineContext);
12288
12503
  const entityId = useContext70(EntityContext);
12289
- const hpRef = useRef37(maxHp);
12290
- const invincibleRef = useRef37(false);
12504
+ const hpRef = useRef39(maxHp);
12505
+ const invincibleRef = useRef39(false);
12291
12506
  const iFrameDuration = opts.iFrames ?? 1;
12292
- const onDeathRef = useRef37(opts.onDeath);
12293
- const onDamageRef = useRef37(opts.onDamage);
12507
+ const onDeathRef = useRef39(opts.onDeath);
12508
+ const onDamageRef = useRef39(opts.onDamage);
12294
12509
  useEffect74(() => {
12295
12510
  onDeathRef.current = opts.onDeath;
12296
12511
  });
12297
12512
  useEffect74(() => {
12298
12513
  onDamageRef.current = opts.onDamage;
12299
12514
  });
12300
- const timerRef = useRef37(
12515
+ const timerRef = useRef39(
12301
12516
  createTimer(iFrameDuration, () => {
12302
12517
  invincibleRef.current = false;
12303
12518
  })
@@ -12315,7 +12530,7 @@ function useHealth(maxHp, opts = {}) {
12315
12530
  },
12316
12531
  [iFrameDuration]
12317
12532
  );
12318
- const takeDamageRef = useRef37(takeDamage);
12533
+ const takeDamageRef = useRef39(takeDamage);
12319
12534
  useEffect74(() => {
12320
12535
  takeDamageRef.current = takeDamage;
12321
12536
  }, [takeDamage]);
@@ -12421,11 +12636,11 @@ function useKinematicBody() {
12421
12636
  }
12422
12637
 
12423
12638
  // ../gameplay/src/hooks/useLevelTransition.ts
12424
- import { useState as useState24, useRef as useRef38, useCallback as useCallback22 } from "react";
12639
+ import { useState as useState24, useRef as useRef40, useCallback as useCallback22 } from "react";
12425
12640
  function useLevelTransition(initial) {
12426
12641
  const [currentLevel, setCurrentLevel] = useState24(initial);
12427
12642
  const [isTransitioning, setIsTransitioning] = useState24(false);
12428
- const overlayRef = useRef38(null);
12643
+ const overlayRef = useRef40(null);
12429
12644
  const transitionTo = useCallback22((level, opts = {}) => {
12430
12645
  const { duration = 0.4, type = "fade" } = opts;
12431
12646
  if (type === "instant") {
@@ -12630,10 +12845,10 @@ function useRestart() {
12630
12845
  }
12631
12846
 
12632
12847
  // ../gameplay/src/hooks/useSave.ts
12633
- import { useCallback as useCallback26, useRef as useRef39 } from "react";
12848
+ import { useCallback as useCallback26, useRef as useRef41 } from "react";
12634
12849
  function useSave(key, defaultValue, opts = {}) {
12635
12850
  const version = opts.version ?? 1;
12636
- const dataRef = useRef39(defaultValue);
12851
+ const dataRef = useRef41(defaultValue);
12637
12852
  const save = useCallback26(
12638
12853
  (value) => {
12639
12854
  dataRef.current = value;
@@ -12685,7 +12900,7 @@ function useSave(key, defaultValue, opts = {}) {
12685
12900
  }
12686
12901
 
12687
12902
  // ../gameplay/src/hooks/useIDBSave.ts
12688
- import { useCallback as useCallback27, useRef as useRef40 } from "react";
12903
+ import { useCallback as useCallback27, useRef as useRef42 } from "react";
12689
12904
  var DB_NAME = "cubeforge-saves";
12690
12905
  var STORE_NAME = "saves";
12691
12906
  function openDB() {
@@ -12703,7 +12918,7 @@ function openDB() {
12703
12918
  }
12704
12919
  function useIDBSave(key, defaultValue, opts = {}) {
12705
12920
  const version = opts.version ?? 1;
12706
- const dbRef = useRef40(null);
12921
+ const dbRef = useRef42(null);
12707
12922
  const getDB = useCallback27(() => {
12708
12923
  if (!dbRef.current) dbRef.current = openDB();
12709
12924
  return dbRef.current;
@@ -12818,11 +13033,11 @@ function useTopDownMovement(entityId, opts = {}) {
12818
13033
  }
12819
13034
 
12820
13035
  // ../gameplay/src/hooks/useDialogue.ts
12821
- import { useState as useState27, useCallback as useCallback28, useRef as useRef41 } from "react";
13036
+ import { useState as useState27, useCallback as useCallback28, useRef as useRef43 } from "react";
12822
13037
  function useDialogue() {
12823
13038
  const [active, setActive] = useState27(false);
12824
13039
  const [currentId, setCurrentId] = useState27(null);
12825
- const scriptRef = useRef41(null);
13040
+ const scriptRef = useRef43(null);
12826
13041
  const start2 = useCallback28((script, startId) => {
12827
13042
  scriptRef.current = script;
12828
13043
  const id = startId ?? Object.keys(script)[0];
@@ -12977,16 +13192,16 @@ function DialogueBox({ dialogue, onAdvance, onClose, style = {} }) {
12977
13192
  }
12978
13193
 
12979
13194
  // ../gameplay/src/hooks/useCutscene.ts
12980
- import { useState as useState28, useCallback as useCallback29, useRef as useRef42, useEffect as useEffect77, useContext as useContext75 } from "react";
13195
+ import { useState as useState28, useCallback as useCallback29, useRef as useRef44, useEffect as useEffect77, useContext as useContext75 } from "react";
12981
13196
  function useCutscene() {
12982
13197
  const engine = useContext75(EngineContext);
12983
13198
  const [playing, setPlaying] = useState28(false);
12984
13199
  const [stepIndex, setStepIndex] = useState28(0);
12985
- const stepsRef = useRef42([]);
12986
- const timerRef = useRef42(0);
12987
- const idxRef = useRef42(0);
12988
- const playingRef = useRef42(false);
12989
- const entityRef = useRef42(null);
13200
+ const stepsRef = useRef44([]);
13201
+ const timerRef = useRef44(0);
13202
+ const idxRef = useRef44(0);
13203
+ const playingRef = useRef44(false);
13204
+ const entityRef = useRef44(null);
12990
13205
  const finish = useCallback29(() => {
12991
13206
  playingRef.current = false;
12992
13207
  setPlaying(false);
@@ -13119,12 +13334,12 @@ function useGameStore(key, initialState) {
13119
13334
  }
13120
13335
 
13121
13336
  // ../gameplay/src/hooks/useTween.ts
13122
- import { useRef as useRef43, useCallback as useCallback31, useEffect as useEffect78 } from "react";
13337
+ import { useRef as useRef45, useCallback as useCallback31, useEffect as useEffect78 } from "react";
13123
13338
  function useTween(opts) {
13124
- const rafRef = useRef43(null);
13125
- const startTimeRef = useRef43(0);
13126
- const runningRef = useRef43(false);
13127
- const optsRef = useRef43(opts);
13339
+ const rafRef = useRef45(null);
13340
+ const startTimeRef = useRef45(0);
13341
+ const runningRef = useRef45(false);
13342
+ const optsRef = useRef45(opts);
13128
13343
  optsRef.current = opts;
13129
13344
  const stop = useCallback31(() => {
13130
13345
  if (rafRef.current !== null) {
@@ -13179,13 +13394,13 @@ function useTween(opts) {
13179
13394
  }
13180
13395
 
13181
13396
  // ../gameplay/src/hooks/useObjectPool.ts
13182
- import { useRef as useRef44, useMemo as useMemo16, useEffect as useEffect79 } from "react";
13397
+ import { useRef as useRef46, useMemo as useMemo16, useEffect as useEffect79 } from "react";
13183
13398
  function useObjectPool(factory, reset, initialSize) {
13184
- const poolRef = useRef44([]);
13185
- const activeRef = useRef44(0);
13186
- const factoryRef = useRef44(factory);
13399
+ const poolRef = useRef46([]);
13400
+ const activeRef = useRef46(0);
13401
+ const factoryRef = useRef46(factory);
13187
13402
  factoryRef.current = factory;
13188
- const resetRef = useRef44(reset);
13403
+ const resetRef = useRef46(reset);
13189
13404
  resetRef.current = reset;
13190
13405
  useEffect79(() => {
13191
13406
  if (initialSize != null && initialSize > 0) {
@@ -13322,6 +13537,164 @@ function createAtlas(names, _columns) {
13322
13537
  return atlas;
13323
13538
  }
13324
13539
 
13540
+ // src/components/HUD.tsx
13541
+ import { createContext as createContext3, useContext as useContext78, useMemo as useMemo19 } from "react";
13542
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
13543
+ var HUDContextRef = createContext3(null);
13544
+ function HUD({
13545
+ dimDuringTransitions = true,
13546
+ dimmedOpacity = 0.25,
13547
+ visible = true,
13548
+ safeArea = true,
13549
+ padding = 12,
13550
+ style,
13551
+ className,
13552
+ children
13553
+ }) {
13554
+ const ctx = useMemo19(() => ({ padding, safeArea }), [padding, safeArea]);
13555
+ const rootStyle = {
13556
+ position: "absolute",
13557
+ inset: 0,
13558
+ pointerEvents: "none",
13559
+ zIndex: 100,
13560
+ opacity: visible ? 1 : 0,
13561
+ transition: "opacity 180ms ease-out",
13562
+ userSelect: "none",
13563
+ ...style
13564
+ };
13565
+ void dimDuringTransitions;
13566
+ void dimmedOpacity;
13567
+ return /* @__PURE__ */ jsx22(HUDContextRef.Provider, { value: ctx, children: /* @__PURE__ */ jsx22("div", { className, style: rootStyle, "data-cubeforge-hud": "true", children }) });
13568
+ }
13569
+ function useHUD() {
13570
+ return useContext78(HUDContextRef) ?? { padding: 12, safeArea: false };
13571
+ }
13572
+ function HUDZone({
13573
+ position = "topLeft",
13574
+ direction,
13575
+ gap = 8,
13576
+ interactive = true,
13577
+ style,
13578
+ className,
13579
+ children
13580
+ }) {
13581
+ const { padding, safeArea } = useHUD();
13582
+ const isTop = position.startsWith("top");
13583
+ const isBottom = position.startsWith("bottom");
13584
+ const isCenter = position.startsWith("center");
13585
+ const isLeft = position.endsWith("Left") || position === "centerLeft";
13586
+ const isRight = position.endsWith("Right") || position === "centerRight";
13587
+ const isHCenter = position.endsWith("Center") || position === "center";
13588
+ const flexDirection = direction ?? (isTop || isBottom ? "row" : "column");
13589
+ const sa = (side) => safeArea ? `max(${padding}px, env(safe-area-inset-${side}))` : `${padding}px`;
13590
+ const zoneStyle = {
13591
+ position: "absolute",
13592
+ display: "flex",
13593
+ flexDirection,
13594
+ gap,
13595
+ pointerEvents: interactive ? "auto" : "none",
13596
+ ...isTop && { top: sa("top") },
13597
+ ...isBottom && { bottom: sa("bottom") },
13598
+ ...isLeft && { left: sa("left") },
13599
+ ...isRight && { right: sa("right") },
13600
+ ...isCenter && !isHCenter && { top: "50%", transform: "translateY(-50%)" },
13601
+ ...isHCenter && !isCenter && { left: "50%", transform: "translateX(-50%)" },
13602
+ ...position === "center" && { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
13603
+ ...isHCenter && !isCenter && isTop && { top: sa("top") },
13604
+ ...isHCenter && !isCenter && isBottom && { bottom: sa("bottom") },
13605
+ alignItems: isHCenter ? "center" : isRight ? "flex-end" : "flex-start",
13606
+ ...style
13607
+ };
13608
+ return /* @__PURE__ */ jsx22("div", { className, style: zoneStyle, children });
13609
+ }
13610
+ function HUDBar({
13611
+ value,
13612
+ max,
13613
+ label,
13614
+ color = "#4fc3f7",
13615
+ trackColor = "rgba(255,255,255,0.08)",
13616
+ width = 180,
13617
+ height = 14,
13618
+ showValue = true,
13619
+ rtl = false,
13620
+ rounded = true,
13621
+ animated = true,
13622
+ style
13623
+ }) {
13624
+ const pct = max > 0 ? Math.max(0, Math.min(1, value / max)) : 0;
13625
+ const fillStyle = {
13626
+ width: `${pct * 100}%`,
13627
+ height: "100%",
13628
+ background: color,
13629
+ borderRadius: rounded ? height / 2 : 0,
13630
+ transition: animated ? "width 160ms ease-out" : void 0,
13631
+ ...rtl && { marginLeft: "auto" }
13632
+ };
13633
+ const trackStyle = {
13634
+ width,
13635
+ height,
13636
+ background: trackColor,
13637
+ borderRadius: rounded ? height / 2 : 0,
13638
+ overflow: "hidden",
13639
+ position: "relative",
13640
+ display: "flex",
13641
+ flexDirection: rtl ? "row-reverse" : "row"
13642
+ };
13643
+ const labelStyle = {
13644
+ fontFamily: "system-ui, sans-serif",
13645
+ fontSize: 11,
13646
+ letterSpacing: 0.5,
13647
+ textTransform: "uppercase",
13648
+ color: "#e0e7f1",
13649
+ opacity: 0.85,
13650
+ display: "flex",
13651
+ justifyContent: "space-between",
13652
+ marginBottom: 4
13653
+ };
13654
+ return /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flexDirection: "column", ...style }, "aria-label": label, children: [
13655
+ (label || showValue) && /* @__PURE__ */ jsxs11("div", { style: labelStyle, children: [
13656
+ label && /* @__PURE__ */ jsx22("span", { children: label }),
13657
+ showValue && /* @__PURE__ */ jsxs11("span", { style: { fontVariantNumeric: "tabular-nums" }, children: [
13658
+ Math.round(value),
13659
+ " / ",
13660
+ max
13661
+ ] })
13662
+ ] }),
13663
+ /* @__PURE__ */ jsx22("div", { style: trackStyle, role: "progressbar", "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, children: /* @__PURE__ */ jsx22("div", { style: fillStyle }) })
13664
+ ] });
13665
+ }
13666
+ function HUDCounter({
13667
+ value,
13668
+ icon,
13669
+ label,
13670
+ pulse = true,
13671
+ color = "#fff",
13672
+ fontSize = 18,
13673
+ style
13674
+ }) {
13675
+ void pulse;
13676
+ return /* @__PURE__ */ jsxs11(
13677
+ "div",
13678
+ {
13679
+ style: {
13680
+ display: "flex",
13681
+ alignItems: "center",
13682
+ gap: 6,
13683
+ fontFamily: "system-ui, sans-serif",
13684
+ fontSize,
13685
+ color,
13686
+ fontVariantNumeric: "tabular-nums",
13687
+ ...style
13688
+ },
13689
+ children: [
13690
+ icon && /* @__PURE__ */ jsx22("span", { style: { fontSize: fontSize * 1.1 }, children: icon }),
13691
+ /* @__PURE__ */ jsx22("span", { children: value }),
13692
+ label && /* @__PURE__ */ jsx22("span", { style: { opacity: 0.7, fontSize: fontSize * 0.75 }, children: label })
13693
+ ]
13694
+ }
13695
+ );
13696
+ }
13697
+
13325
13698
  // src/utils/animationHelpers.ts
13326
13699
  function playClip(world, entityId, clipName) {
13327
13700
  const anim = world.getComponent(entityId, "AnimationState");
@@ -13351,7 +13724,7 @@ function definePrefab(name, defaults, render) {
13351
13724
  }
13352
13725
 
13353
13726
  // src/hooks/useNetworkSync.ts
13354
- import { useEffect as useEffect81, useRef as useRef46 } from "react";
13727
+ import { useEffect as useEffect81, useRef as useRef48 } from "react";
13355
13728
 
13356
13729
  // ../../packages/net/src/transport.ts
13357
13730
  function isBinaryTransport(t) {
@@ -13696,7 +14069,7 @@ function syncEntity(config) {
13696
14069
  }
13697
14070
 
13698
14071
  // ../../packages/net/src/useNetworkInput.ts
13699
- import { useState as useState29, useEffect as useEffect80, useRef as useRef45 } from "react";
14072
+ import { useState as useState29, useEffect as useEffect80, useRef as useRef47 } from "react";
13700
14073
  var INPUT_MSG_TYPE = "input:state";
13701
14074
  function useNetworkInput(config) {
13702
14075
  const { room, keys, input, tickRate = 20 } = config;
@@ -13705,7 +14078,7 @@ function useNetworkInput(config) {
13705
14078
  () => Object.fromEntries(keys.map((k) => [k, false]))
13706
14079
  );
13707
14080
  const [remoteInputs] = useState29(() => /* @__PURE__ */ new Map());
13708
- const localInputRef = useRef45(localInput);
14081
+ const localInputRef = useRef47(localInput);
13709
14082
  useEffect80(() => {
13710
14083
  let cleanupDom = null;
13711
14084
  if (!input) {
@@ -13920,7 +14293,7 @@ function lerpState(a, b, t) {
13920
14293
 
13921
14294
  // src/hooks/useNetworkSync.ts
13922
14295
  function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
13923
- const optsRef = useRef46(opts);
14296
+ const optsRef = useRef48(opts);
13924
14297
  optsRef.current = opts;
13925
14298
  useEffect81(() => {
13926
14299
  const sync = syncEntity({
@@ -13937,16 +14310,16 @@ function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
13937
14310
  }
13938
14311
 
13939
14312
  // src/hooks/useRemotePlayer.ts
13940
- import { useEffect as useEffect82, useRef as useRef47, useState as useState30 } from "react";
14313
+ import { useEffect as useEffect82, useRef as useRef49, useState as useState30 } from "react";
13941
14314
  var PEER_JOIN_MSG = "peer:join";
13942
14315
  var PEER_LEAVE_MSG = "peer:leave";
13943
14316
  function useRemotePlayer(config) {
13944
14317
  const { room, world, createEntity, destroyEntity } = config;
13945
14318
  const [players, setPlayers] = useState30(() => /* @__PURE__ */ new Map());
13946
- const playersRef = useRef47(players);
13947
- const createRef = useRef47(createEntity);
14319
+ const playersRef = useRef49(players);
14320
+ const createRef = useRef49(createEntity);
13948
14321
  createRef.current = createEntity;
13949
- const destroyRef = useRef47(destroyEntity);
14322
+ const destroyRef = useRef49(destroyEntity);
13950
14323
  destroyRef.current = destroyEntity;
13951
14324
  useEffect82(() => {
13952
14325
  function spawnPeer(peerId) {
@@ -14020,6 +14393,10 @@ export {
14020
14393
  FocusRing,
14021
14394
  Game,
14022
14395
  Gradient,
14396
+ HUD,
14397
+ HUDBar,
14398
+ HUDCounter,
14399
+ HUDZone,
14023
14400
  HalfSpaceCollider,
14024
14401
  HeightFieldCollider,
14025
14402
  HierarchySystem,
@@ -14030,6 +14407,7 @@ export {
14030
14407
  Mask,
14031
14408
  MovingPlatform,
14032
14409
  NineSlice,
14410
+ PARTICLE_PRESETS,
14033
14411
  ParallaxLayer,
14034
14412
  ParticleEmitter,
14035
14413
  Polygon,