cubeforge 0.4.13 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +118 -2
  2. package/dist/index.js +541 -230
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -506,6 +506,7 @@ var GameLoop = class {
506
506
  constructor(onTick, options) {
507
507
  this.onTick = onTick;
508
508
  this.fixedDt = options?.fixedDt;
509
+ this.onRender = options?.onRender;
509
510
  }
510
511
  rafId = 0;
511
512
  lastTime = 0;
@@ -513,6 +514,8 @@ var GameLoop = class {
513
514
  paused = false;
514
515
  hitPauseTimer = 0;
515
516
  fixedDt;
517
+ onRender;
518
+ /** Freeze gameplay for `duration` seconds. Physics/scripts stop but rendering continues. */
516
519
  hitPause(duration) {
517
520
  this.hitPauseTimer = duration;
518
521
  }
@@ -552,7 +555,8 @@ var GameLoop = class {
552
555
  this.lastTime = time;
553
556
  const dt = this.fixedDt ?? rawDt;
554
557
  if (this.hitPauseTimer > 0) {
555
- this.hitPauseTimer -= dt;
558
+ this.hitPauseTimer -= rawDt;
559
+ this.onRender?.(0);
556
560
  } else {
557
561
  this.onTick(dt);
558
562
  }
@@ -1222,74 +1226,159 @@ function tween(from, to, duration, ease = Ease.linear, onUpdate, onComplete, opt
1222
1226
  }
1223
1227
 
1224
1228
  // ../../packages/core/src/tweenTimeline.ts
1225
- function createTimeline() {
1226
- const entries = [];
1229
+ function createTimeline(opts) {
1230
+ const segments = [];
1231
+ const labels = /* @__PURE__ */ new Map();
1232
+ let cursor = 0;
1233
+ let elapsed = 0;
1227
1234
  let running = false;
1228
- let currentTween = null;
1229
- let delayTimer = null;
1230
- let rafId = null;
1231
- let lastTime = 0;
1232
- function clearCurrent() {
1233
- if (currentTween) {
1234
- currentTween.stop();
1235
- currentTween = null;
1236
- }
1237
- if (delayTimer !== null) {
1238
- clearTimeout(delayTimer);
1239
- delayTimer = null;
1240
- }
1241
- if (rafId !== null) {
1242
- cancelAnimationFrame(rafId);
1243
- rafId = null;
1235
+ let complete = false;
1236
+ const maxRepeats = opts?.repeat ?? (opts?.loop ? Infinity : 0);
1237
+ let repeatsDone = 0;
1238
+ function resolveStartTime(addOpts) {
1239
+ if (addOpts?.at) {
1240
+ const labelTime = labels.get(addOpts.at);
1241
+ return labelTime ?? cursor;
1244
1242
  }
1243
+ return cursor;
1245
1244
  }
1246
- function tick(now) {
1247
- if (!running || !currentTween) return;
1248
- const dt = (now - lastTime) / 1e3;
1249
- lastTime = now;
1250
- currentTween.update(dt);
1251
- if (!currentTween.isComplete) {
1252
- rafId = requestAnimationFrame(tick);
1245
+ function resetSegments() {
1246
+ for (const seg of segments) {
1247
+ seg.handle?.stop();
1248
+ seg.handle = void 0;
1249
+ seg.started = false;
1250
+ seg.complete = false;
1253
1251
  }
1254
1252
  }
1255
- function playEntry(index) {
1256
- if (index >= entries.length) {
1257
- running = false;
1258
- return;
1259
- }
1260
- const entry = entries[index];
1261
- const delay = entry.delay ?? 0;
1262
- const startTween = () => {
1263
- if (!running) return;
1264
- currentTween = tween(entry.from, entry.to, entry.duration, entry.ease ?? Ease.linear, entry.onUpdate, () => {
1265
- entry.onComplete?.();
1266
- playEntry(index + 1);
1267
- });
1268
- lastTime = performance.now();
1269
- rafId = requestAnimationFrame(tick);
1270
- };
1271
- if (delay > 0) {
1272
- delayTimer = setTimeout(startTween, delay * 1e3);
1273
- } else {
1274
- startTween();
1253
+ function computeTotalDuration() {
1254
+ let max = 0;
1255
+ for (const seg of segments) {
1256
+ const end = seg.startTime + seg.delay + seg.entry.duration;
1257
+ if (end > max) max = end;
1275
1258
  }
1259
+ return max;
1276
1260
  }
1277
1261
  const timeline = {
1278
- add(entry) {
1279
- entries.push(entry);
1262
+ add(entry, addOpts) {
1263
+ const start = resolveStartTime(addOpts);
1264
+ const delay = addOpts?.delay ?? 0;
1265
+ segments.push({ startTime: start, delay, entry, started: false, complete: false });
1266
+ cursor = start + delay + entry.duration;
1267
+ return timeline;
1268
+ },
1269
+ addLabel(name) {
1270
+ labels.set(name, cursor);
1280
1271
  return timeline;
1281
1272
  },
1273
+ addParallel(entries, addOpts) {
1274
+ const start = resolveStartTime(addOpts);
1275
+ const delay = addOpts?.delay ?? 0;
1276
+ let maxEnd = cursor;
1277
+ for (const entry of entries) {
1278
+ segments.push({ startTime: start, delay, entry, started: false, complete: false });
1279
+ const end = start + delay + entry.duration;
1280
+ if (end > maxEnd) maxEnd = end;
1281
+ }
1282
+ cursor = maxEnd;
1283
+ return timeline;
1284
+ },
1285
+ update(dt) {
1286
+ if (!running || complete) return;
1287
+ elapsed += dt;
1288
+ let allDone = true;
1289
+ for (const seg of segments) {
1290
+ if (seg.complete) continue;
1291
+ const effectiveStart = seg.startTime + seg.delay;
1292
+ if (elapsed < effectiveStart) {
1293
+ allDone = false;
1294
+ continue;
1295
+ }
1296
+ if (!seg.started) {
1297
+ seg.started = true;
1298
+ seg.handle = tween(
1299
+ seg.entry.from,
1300
+ seg.entry.to,
1301
+ seg.entry.duration,
1302
+ seg.entry.ease ?? Ease.linear,
1303
+ seg.entry.onUpdate,
1304
+ () => {
1305
+ seg.complete = true;
1306
+ seg.entry.onComplete?.();
1307
+ }
1308
+ );
1309
+ const overflow = elapsed - effectiveStart;
1310
+ if (overflow > 0) seg.handle.update(overflow);
1311
+ } else if (seg.handle) {
1312
+ seg.handle.update(dt);
1313
+ }
1314
+ if (!seg.complete) allDone = false;
1315
+ }
1316
+ if (allDone && segments.length > 0) {
1317
+ if (repeatsDone < maxRepeats) {
1318
+ repeatsDone++;
1319
+ elapsed = 0;
1320
+ resetSegments();
1321
+ } else {
1322
+ complete = true;
1323
+ running = false;
1324
+ opts?.onComplete?.();
1325
+ }
1326
+ }
1327
+ },
1282
1328
  start() {
1283
- clearCurrent();
1329
+ elapsed = 0;
1284
1330
  running = true;
1285
- playEntry(0);
1331
+ complete = false;
1332
+ repeatsDone = 0;
1333
+ resetSegments();
1286
1334
  },
1287
1335
  stop() {
1288
1336
  running = false;
1289
- clearCurrent();
1337
+ complete = false;
1338
+ elapsed = 0;
1339
+ repeatsDone = 0;
1340
+ resetSegments();
1341
+ },
1342
+ seek(time) {
1343
+ elapsed = 0;
1344
+ resetSegments();
1345
+ elapsed = 0;
1346
+ const step = 1 / 60;
1347
+ while (elapsed < time) {
1348
+ const d = Math.min(step, time - elapsed);
1349
+ elapsed += d;
1350
+ for (const seg of segments) {
1351
+ if (seg.complete) continue;
1352
+ const effectiveStart = seg.startTime + seg.delay;
1353
+ if (elapsed < effectiveStart) continue;
1354
+ if (!seg.started) {
1355
+ seg.started = true;
1356
+ seg.handle = tween(
1357
+ seg.entry.from,
1358
+ seg.entry.to,
1359
+ seg.entry.duration,
1360
+ seg.entry.ease ?? Ease.linear,
1361
+ seg.entry.onUpdate,
1362
+ () => {
1363
+ seg.complete = true;
1364
+ }
1365
+ );
1366
+ const overflow = elapsed - effectiveStart;
1367
+ if (overflow > 0) seg.handle.update(overflow);
1368
+ } else if (seg.handle) {
1369
+ seg.handle.update(d);
1370
+ }
1371
+ }
1372
+ }
1290
1373
  },
1291
- isRunning() {
1374
+ get isRunning() {
1292
1375
  return running;
1376
+ },
1377
+ get isComplete() {
1378
+ return complete;
1379
+ },
1380
+ get totalDuration() {
1381
+ return computeTotalDuration();
1293
1382
  }
1294
1383
  };
1295
1384
  return timeline;
@@ -3389,18 +3478,34 @@ var RenderSystem = class {
3389
3478
  animator._entered = true;
3390
3479
  stateDef.onEnter?.();
3391
3480
  }
3392
- if (stateDef.transitions && stateDef.transitions.length > 0) {
3393
- const sorted = [...stateDef.transitions].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
3394
- for (const trans of sorted) {
3395
- if (trans.exitTime != null && anim.frames.length > 0) {
3396
- const progress = anim.currentIndex / anim.frames.length;
3397
- if (progress < trans.exitTime) continue;
3398
- }
3399
- if (evaluateConditions(trans.when, animator.params)) {
3400
- stateDef.onExit?.();
3401
- animator.currentState = trans.to;
3402
- animator._entered = false;
3403
- break;
3481
+ if (animator._blendTimer != null && animator._blendTimer > 0) {
3482
+ animator._blendTimer -= dt;
3483
+ if (animator._blendTimer <= 0 && animator._blendToState) {
3484
+ stateDef.onExit?.();
3485
+ animator.currentState = animator._blendToState;
3486
+ animator._entered = false;
3487
+ animator._blendTimer = void 0;
3488
+ animator._blendToState = void 0;
3489
+ }
3490
+ } else {
3491
+ if (stateDef.transitions && stateDef.transitions.length > 0) {
3492
+ const sorted = [...stateDef.transitions].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
3493
+ for (const trans of sorted) {
3494
+ if (trans.exitTime != null && anim.frames.length > 0) {
3495
+ const progress = anim.currentIndex / anim.frames.length;
3496
+ if (progress < trans.exitTime) continue;
3497
+ }
3498
+ if (evaluateConditions(trans.when, animator.params)) {
3499
+ if (trans.blendDuration && trans.blendDuration > 0) {
3500
+ animator._blendTimer = trans.blendDuration;
3501
+ animator._blendToState = trans.to;
3502
+ } else {
3503
+ stateDef.onExit?.();
3504
+ animator.currentState = trans.to;
3505
+ animator._entered = false;
3506
+ }
3507
+ break;
3508
+ }
3404
3509
  }
3405
3510
  }
3406
3511
  }
@@ -14557,7 +14662,11 @@ function Game({
14557
14662
  handle.onFrame?.();
14558
14663
  }
14559
14664
  },
14560
- deterministic ? { fixedDt: 1 / 60 } : void 0
14665
+ {
14666
+ ...deterministic ? { fixedDt: 1 / 60 } : {},
14667
+ // During hit-pause, re-render the last frame so the screen isn't blank
14668
+ onRender: () => renderSystem.update(ecs, 0)
14669
+ }
14561
14670
  );
14562
14671
  const postProcessStack = createPostProcessStack();
14563
14672
  const state = {
@@ -16057,7 +16166,10 @@ function Tilemap({
16057
16166
  triggerLayer: triggerLayerName = "triggers",
16058
16167
  onTileProperty,
16059
16168
  navGrid,
16060
- mergeColliders = true
16169
+ mergeColliders = true,
16170
+ solidTiles,
16171
+ autoColliders = false,
16172
+ tileColliderProps: _tileColliderProps
16061
16173
  }) {
16062
16174
  const engine = useContext20(EngineContext);
16063
16175
  const [spawnedNodes, setSpawnedNodes] = useState6([]);
@@ -16118,7 +16230,7 @@ function Tilemap({
16118
16230
  if (layerFilter && !layerFilter(layer)) continue;
16119
16231
  if (!layer.visible) continue;
16120
16232
  if (layer.type === "tilelayer" && layer.data) {
16121
- const collision = isCollisionLayer(layer, collisionLayer);
16233
+ const collision = isCollisionLayer(layer, collisionLayer) || autoColliders && solidTiles && solidTiles.length > 0;
16122
16234
  const trigger = !collision && isTriggerLayer(layer, triggerLayerName);
16123
16235
  if (collision && navGrid) {
16124
16236
  for (let row = 0; row < mapData.height; row++) {
@@ -16130,11 +16242,13 @@ function Tilemap({
16130
16242
  }
16131
16243
  if (collision || trigger) {
16132
16244
  if (mergeColliders) {
16245
+ const solidSet = solidTiles ? new Set(solidTiles) : null;
16133
16246
  const solidGrid = [];
16134
16247
  for (let row = 0; row < mapData.height; row++) {
16135
16248
  solidGrid[row] = [];
16136
16249
  for (let col = 0; col < mapData.width; col++) {
16137
- solidGrid[row][col] = layer.data[row * mapData.width + col] !== 0;
16250
+ const gid = layer.data[row * mapData.width + col];
16251
+ solidGrid[row][col] = gid !== 0 && (!solidSet || solidSet.has(gid));
16138
16252
  }
16139
16253
  }
16140
16254
  const merged = mergeTileColliders(solidGrid, tilewidth, tileheight, 0, 0);
@@ -17764,6 +17878,200 @@ function useSceneManager(initialScene) {
17764
17878
  };
17765
17879
  }
17766
17880
 
17881
+ // src/hooks/useSceneTransition.ts
17882
+ import { useState as useState13, useCallback as useCallback7, useRef as useRef24, useEffect as useEffect60 } from "react";
17883
+ function useSceneTransition(initialScene, defaultTransition) {
17884
+ const [stack, setStack] = useState13([initialScene]);
17885
+ const [transState, setTransState] = useState13({
17886
+ phase: "idle",
17887
+ progress: 0,
17888
+ effect: null,
17889
+ pendingAction: null
17890
+ });
17891
+ const stackRef = useRef24(stack);
17892
+ stackRef.current = stack;
17893
+ const transRef = useRef24(transState);
17894
+ transRef.current = transState;
17895
+ useEffect60(() => {
17896
+ if (transState.phase === "idle") return;
17897
+ const effect = transState.effect;
17898
+ if (!effect || effect.type === "instant") return;
17899
+ const duration = (effect.duration ?? 0.4) / 2;
17900
+ let rafId;
17901
+ let start = performance.now();
17902
+ const animate = (now) => {
17903
+ const elapsed = (now - start) / 1e3;
17904
+ const t = Math.min(elapsed / duration, 1);
17905
+ if (transRef.current.phase === "out") {
17906
+ setTransState((prev) => ({ ...prev, progress: t }));
17907
+ if (t >= 1) {
17908
+ transRef.current.pendingAction?.();
17909
+ start = performance.now();
17910
+ setTransState((prev) => ({ ...prev, phase: "in", progress: 1, pendingAction: null }));
17911
+ }
17912
+ } else if (transRef.current.phase === "in") {
17913
+ setTransState((prev) => ({ ...prev, progress: 1 - t }));
17914
+ if (t >= 1) {
17915
+ setTransState({ phase: "idle", progress: 0, effect: null, pendingAction: null });
17916
+ return;
17917
+ }
17918
+ }
17919
+ rafId = requestAnimationFrame(animate);
17920
+ };
17921
+ rafId = requestAnimationFrame(animate);
17922
+ return () => cancelAnimationFrame(rafId);
17923
+ }, [transState.phase, transState.effect]);
17924
+ const startTransition = useCallback7(
17925
+ (effect, action) => {
17926
+ const eff = effect ?? defaultTransition ?? { type: "instant" };
17927
+ if (eff.type === "instant") {
17928
+ action();
17929
+ return;
17930
+ }
17931
+ setTransState({
17932
+ phase: "out",
17933
+ progress: 0,
17934
+ effect: eff,
17935
+ pendingAction: action
17936
+ });
17937
+ },
17938
+ [defaultTransition]
17939
+ );
17940
+ const push = useCallback7(
17941
+ (scene, transition) => {
17942
+ startTransition(transition, () => setStack((prev) => [...prev, scene]));
17943
+ },
17944
+ [startTransition]
17945
+ );
17946
+ const pop = useCallback7(
17947
+ (transition) => {
17948
+ const prev = stackRef.current;
17949
+ if (prev.length <= 1) return void 0;
17950
+ const popped = prev[prev.length - 1];
17951
+ startTransition(transition, () => setStack((p) => p.slice(0, -1)));
17952
+ return popped;
17953
+ },
17954
+ [startTransition]
17955
+ );
17956
+ const replace = useCallback7(
17957
+ (scene, transition) => {
17958
+ startTransition(transition, () => setStack((prev) => [...prev.slice(0, -1), scene]));
17959
+ },
17960
+ [startTransition]
17961
+ );
17962
+ const reset = useCallback7(
17963
+ (scene, transition) => {
17964
+ startTransition(transition, () => setStack([scene]));
17965
+ },
17966
+ [startTransition]
17967
+ );
17968
+ return {
17969
+ current: stack[stack.length - 1],
17970
+ stack,
17971
+ push,
17972
+ pop,
17973
+ replace,
17974
+ reset,
17975
+ progress: transState.progress,
17976
+ phase: transState.phase,
17977
+ activeTransition: transState.effect
17978
+ };
17979
+ }
17980
+
17981
+ // src/components/SceneTransitionOverlay.tsx
17982
+ import React9 from "react";
17983
+ function getOverlayStyle(effect, progress) {
17984
+ const color = effect.color ?? "#000";
17985
+ switch (effect.type) {
17986
+ case "fade":
17987
+ return {
17988
+ position: "absolute",
17989
+ inset: 0,
17990
+ backgroundColor: color,
17991
+ opacity: progress,
17992
+ pointerEvents: "none",
17993
+ zIndex: 9999
17994
+ };
17995
+ case "wipe": {
17996
+ const dir = effect.direction ?? "left";
17997
+ let clipPath;
17998
+ switch (dir) {
17999
+ case "left":
18000
+ clipPath = `inset(0 ${(1 - progress) * 100}% 0 0)`;
18001
+ break;
18002
+ case "right":
18003
+ clipPath = `inset(0 0 0 ${(1 - progress) * 100}%)`;
18004
+ break;
18005
+ case "up":
18006
+ clipPath = `inset(0 0 ${(1 - progress) * 100}% 0)`;
18007
+ break;
18008
+ case "down":
18009
+ clipPath = `inset(${(1 - progress) * 100}% 0 0 0)`;
18010
+ break;
18011
+ default:
18012
+ clipPath = `inset(0 ${(1 - progress) * 100}% 0 0)`;
18013
+ }
18014
+ return {
18015
+ position: "absolute",
18016
+ inset: 0,
18017
+ backgroundColor: color,
18018
+ clipPath,
18019
+ pointerEvents: "none",
18020
+ zIndex: 9999
18021
+ };
18022
+ }
18023
+ case "circle-close": {
18024
+ const radius = (1 - progress) * 72;
18025
+ return {
18026
+ position: "absolute",
18027
+ inset: 0,
18028
+ backgroundColor: color,
18029
+ clipPath: `circle(${radius}% at 50% 50%)`,
18030
+ pointerEvents: "none",
18031
+ zIndex: 9999
18032
+ };
18033
+ }
18034
+ default:
18035
+ return { display: "none" };
18036
+ }
18037
+ }
18038
+ function SceneTransitionOverlay({ controls }) {
18039
+ if (controls.phase === "idle" || !controls.activeTransition || controls.activeTransition.type === "instant") {
18040
+ return null;
18041
+ }
18042
+ if (controls.activeTransition.type === "circle-close") {
18043
+ const color = controls.activeTransition.color ?? "#000";
18044
+ const radius = (1 - controls.progress) * 72;
18045
+ return React9.createElement("div", {
18046
+ style: {
18047
+ position: "absolute",
18048
+ inset: 0,
18049
+ backgroundColor: color,
18050
+ WebkitMaskImage: `radial-gradient(circle ${radius}vmax at 50% 50%, transparent 100%, black 100%)`,
18051
+ maskImage: `radial-gradient(circle ${radius}vmax at 50% 50%, transparent 100%, black 100%)`,
18052
+ pointerEvents: "none",
18053
+ zIndex: 9999
18054
+ }
18055
+ });
18056
+ }
18057
+ return React9.createElement("div", {
18058
+ style: getOverlayStyle(controls.activeTransition, controls.progress)
18059
+ });
18060
+ }
18061
+
18062
+ // src/hooks/useHitstop.ts
18063
+ import { useContext as useContext51, useCallback as useCallback8 } from "react";
18064
+ function useHitstop() {
18065
+ const engine = useContext51(EngineContext);
18066
+ const freeze = useCallback8(
18067
+ (seconds) => {
18068
+ engine.loop.hitPause(seconds);
18069
+ },
18070
+ [engine]
18071
+ );
18072
+ return { freeze };
18073
+ }
18074
+
17767
18075
  // src/hooks/useInputBuffer.ts
17768
18076
  import { useMemo as useMemo9 } from "react";
17769
18077
  function useInputBuffer(opts) {
@@ -17771,9 +18079,9 @@ function useInputBuffer(opts) {
17771
18079
  }
17772
18080
 
17773
18081
  // src/hooks/useComboDetector.ts
17774
- import { useMemo as useMemo10, useRef as useRef24 } from "react";
18082
+ import { useMemo as useMemo10, useRef as useRef25 } from "react";
17775
18083
  function useComboDetector(combos) {
17776
- const lastComboRef = useRef24(null);
18084
+ const lastComboRef = useRef25(null);
17777
18085
  const result = useMemo10(() => {
17778
18086
  const detector = new ComboDetector({ combos });
17779
18087
  const api = {
@@ -17796,11 +18104,11 @@ function useComboDetector(combos) {
17796
18104
  }
17797
18105
 
17798
18106
  // src/hooks/useParent.ts
17799
- import { useEffect as useEffect60, useContext as useContext51 } from "react";
18107
+ import { useEffect as useEffect61, useContext as useContext52 } from "react";
17800
18108
  function useParent(childEntityId, parentEntityId) {
17801
- const engine = useContext51(EngineContext);
18109
+ const engine = useContext52(EngineContext);
17802
18110
  if (!engine) throw new Error("useParent must be used inside <Game>");
17803
- useEffect60(() => {
18111
+ useEffect61(() => {
17804
18112
  setParent(engine.ecs, childEntityId, parentEntityId);
17805
18113
  return () => {
17806
18114
  removeParent(engine.ecs, childEntityId);
@@ -17809,7 +18117,7 @@ function useParent(childEntityId, parentEntityId) {
17809
18117
  }
17810
18118
 
17811
18119
  // src/hooks/useAccessibility.ts
17812
- import { useCallback as useCallback7, useSyncExternalStore } from "react";
18120
+ import { useCallback as useCallback9, useSyncExternalStore } from "react";
17813
18121
  var _version = 0;
17814
18122
  var _listeners = /* @__PURE__ */ new Set();
17815
18123
  function subscribe(cb) {
@@ -17821,12 +18129,12 @@ function getSnapshot() {
17821
18129
  }
17822
18130
  function useAccessibility() {
17823
18131
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
17824
- const setOptions = useCallback7((opts) => {
18132
+ const setOptions = useCallback9((opts) => {
17825
18133
  setAccessibilityOptions(opts);
17826
18134
  _version++;
17827
18135
  for (const cb of _listeners) cb();
17828
18136
  }, []);
17829
- const announce = useCallback7((text, priority) => {
18137
+ const announce = useCallback9((text, priority) => {
17830
18138
  announceToScreenReader(text, priority);
17831
18139
  }, []);
17832
18140
  return {
@@ -17837,18 +18145,18 @@ function useAccessibility() {
17837
18145
  }
17838
18146
 
17839
18147
  // src/hooks/useHMR.ts
17840
- import { useEffect as useEffect61, useRef as useRef25, useMemo as useMemo11 } from "react";
18148
+ import { useEffect as useEffect62, useRef as useRef26, useMemo as useMemo11 } from "react";
17841
18149
  var idCounter = 0;
17842
18150
  function useHMR(hmrKey) {
17843
- const idRef = useRef25(null);
18151
+ const idRef = useRef26(null);
17844
18152
  if (idRef.current === null) {
17845
18153
  idRef.current = hmrKey ?? `__hmr_${idCounter++}`;
17846
18154
  }
17847
18155
  const key = idRef.current;
17848
18156
  const isHotReload = hmrLoadState(key) !== void 0;
17849
- const disposeRef = useRef25(null);
17850
- const acceptRef = useRef25(null);
17851
- useEffect61(() => {
18157
+ const disposeRef = useRef26(null);
18158
+ const acceptRef = useRef26(null);
18159
+ useEffect62(() => {
17852
18160
  const hot = import.meta.hot;
17853
18161
  if (!hot) return;
17854
18162
  hot.dispose(() => {
@@ -17881,11 +18189,11 @@ function useHMR(hmrKey) {
17881
18189
  }
17882
18190
 
17883
18191
  // src/hooks/useSquashStretch.ts
17884
- import { useCallback as useCallback8, useContext as useContext52 } from "react";
18192
+ import { useCallback as useCallback10, useContext as useContext53 } from "react";
17885
18193
  function useSquashStretch() {
17886
- const engine = useContext52(EngineContext);
17887
- const entityId = useContext52(EntityContext);
17888
- const trigger = useCallback8(
18194
+ const engine = useContext53(EngineContext);
18195
+ const entityId = useContext53(EntityContext);
18196
+ const trigger = useCallback10(
17889
18197
  (scaleX, scaleY) => {
17890
18198
  const ss = engine.ecs.getComponent(entityId, "SquashStretch");
17891
18199
  if (!ss) return;
@@ -17898,10 +18206,10 @@ function useSquashStretch() {
17898
18206
  }
17899
18207
 
17900
18208
  // ../gameplay/src/hooks/useAnimationController.ts
17901
- import { useState as useState13, useCallback as useCallback9 } from "react";
18209
+ import { useState as useState14, useCallback as useCallback11 } from "react";
17902
18210
  function useAnimationController(states, initial) {
17903
- const [stateName, setStateName] = useState13(initial);
17904
- const setState = useCallback9((next) => {
18211
+ const [stateName, setStateName] = useState14(initial);
18212
+ const setState = useCallback11((next) => {
17905
18213
  setStateName((prev) => prev === next ? prev : next);
17906
18214
  }, []);
17907
18215
  const clip = states[stateName] ?? states[initial];
@@ -17923,39 +18231,39 @@ function useAnimationController(states, initial) {
17923
18231
  }
17924
18232
 
17925
18233
  // ../gameplay/src/hooks/useAISteering.ts
17926
- import { useCallback as useCallback10 } from "react";
18234
+ import { useCallback as useCallback12 } from "react";
17927
18235
  function useAISteering() {
17928
- const seek$ = useCallback10((pos, target, speed) => seek(pos, target, speed), []);
17929
- const flee$ = useCallback10((pos, threat, speed) => flee(pos, threat, speed), []);
17930
- const arrive$ = useCallback10(
18236
+ const seek$ = useCallback12((pos, target, speed) => seek(pos, target, speed), []);
18237
+ const flee$ = useCallback12((pos, threat, speed) => flee(pos, threat, speed), []);
18238
+ const arrive$ = useCallback12(
17931
18239
  (pos, target, speed, slowRadius) => arrive(pos, target, speed, slowRadius),
17932
18240
  []
17933
18241
  );
17934
- const patrol$ = useCallback10(
18242
+ const patrol$ = useCallback12(
17935
18243
  (pos, waypoints, speed, currentIdx, arriveThreshold) => patrol(pos, waypoints, speed, currentIdx, arriveThreshold),
17936
18244
  []
17937
18245
  );
17938
- const wander$ = useCallback10(
18246
+ const wander$ = useCallback12(
17939
18247
  (pos, angle, speed, jitter) => wander(pos, angle, speed, jitter),
17940
18248
  []
17941
18249
  );
17942
- const pursuit$ = useCallback10(
18250
+ const pursuit$ = useCallback12(
17943
18251
  (pos, targetPos, targetVel, speed, lookAhead) => pursuit(pos, targetPos, targetVel, speed, lookAhead),
17944
18252
  []
17945
18253
  );
17946
- const evade$ = useCallback10(
18254
+ const evade$ = useCallback12(
17947
18255
  (pos, threatPos, threatVel, speed, lookAhead) => evade(pos, threatPos, threatVel, speed, lookAhead),
17948
18256
  []
17949
18257
  );
17950
- const separation$ = useCallback10(
18258
+ const separation$ = useCallback12(
17951
18259
  (pos, neighbors, speed, radius) => separation(pos, neighbors, speed, radius),
17952
18260
  []
17953
18261
  );
17954
- const cohesion$ = useCallback10(
18262
+ const cohesion$ = useCallback12(
17955
18263
  (pos, neighbors, speed) => cohesion(pos, neighbors, speed),
17956
18264
  []
17957
18265
  );
17958
- const alignment$ = useCallback10(
18266
+ const alignment$ = useCallback12(
17959
18267
  (neighborVelocities, speed) => alignment(neighborVelocities, speed),
17960
18268
  []
17961
18269
  );
@@ -17974,9 +18282,9 @@ function useAISteering() {
17974
18282
  }
17975
18283
 
17976
18284
  // ../gameplay/src/hooks/useDamageZone.ts
17977
- import { useContext as useContext53 } from "react";
18285
+ import { useContext as useContext54 } from "react";
17978
18286
  function useDamageZone(damage, opts = {}) {
17979
- const engine = useContext53(EngineContext);
18287
+ const engine = useContext54(EngineContext);
17980
18288
  useTriggerEnter(
17981
18289
  (other) => {
17982
18290
  engine.events.emit(`damage:${other}`, { amount: damage });
@@ -17986,11 +18294,11 @@ function useDamageZone(damage, opts = {}) {
17986
18294
  }
17987
18295
 
17988
18296
  // ../gameplay/src/hooks/useDropThrough.ts
17989
- import { useContext as useContext54, useCallback as useCallback11 } from "react";
18297
+ import { useContext as useContext55, useCallback as useCallback13 } from "react";
17990
18298
  function useDropThrough(frames = 8) {
17991
- const engine = useContext54(EngineContext);
17992
- const entityId = useContext54(EntityContext);
17993
- const dropThrough = useCallback11(() => {
18299
+ const engine = useContext55(EngineContext);
18300
+ const entityId = useContext55(EntityContext);
18301
+ const dropThrough = useCallback13(() => {
17994
18302
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
17995
18303
  if (rb) rb.dropThrough = frames;
17996
18304
  }, [engine.ecs, entityId, frames]);
@@ -17998,17 +18306,17 @@ function useDropThrough(frames = 8) {
17998
18306
  }
17999
18307
 
18000
18308
  // ../gameplay/src/hooks/useGameStateMachine.ts
18001
- import { useState as useState14, useRef as useRef26, useCallback as useCallback12, useEffect as useEffect62, useContext as useContext55 } from "react";
18309
+ import { useState as useState15, useRef as useRef27, useCallback as useCallback14, useEffect as useEffect63, useContext as useContext56 } from "react";
18002
18310
  function useGameStateMachine(states, initial) {
18003
- const engine = useContext55(EngineContext);
18004
- const [state, setState] = useState14(initial);
18005
- const stateRef = useRef26(initial);
18006
- const statesRef = useRef26(states);
18311
+ const engine = useContext56(EngineContext);
18312
+ const [state, setState] = useState15(initial);
18313
+ const stateRef = useRef27(initial);
18314
+ const statesRef = useRef27(states);
18007
18315
  statesRef.current = states;
18008
- useEffect62(() => {
18316
+ useEffect63(() => {
18009
18317
  statesRef.current[initial]?.onEnter?.();
18010
18318
  }, []);
18011
- const transition = useCallback12((to) => {
18319
+ const transition = useCallback14((to) => {
18012
18320
  const current = stateRef.current;
18013
18321
  if (current === to) return;
18014
18322
  statesRef.current[current]?.onExit?.();
@@ -18016,7 +18324,7 @@ function useGameStateMachine(states, initial) {
18016
18324
  setState(to);
18017
18325
  statesRef.current[to]?.onEnter?.();
18018
18326
  }, []);
18019
- useEffect62(() => {
18327
+ useEffect63(() => {
18020
18328
  const eid = engine.ecs.createEntity();
18021
18329
  engine.ecs.addComponent(
18022
18330
  eid,
@@ -18032,27 +18340,27 @@ function useGameStateMachine(states, initial) {
18032
18340
  }
18033
18341
 
18034
18342
  // ../gameplay/src/hooks/useHealth.ts
18035
- import { useRef as useRef27, useEffect as useEffect63, useContext as useContext56, useCallback as useCallback13 } from "react";
18343
+ import { useRef as useRef28, useEffect as useEffect64, useContext as useContext57, useCallback as useCallback15 } from "react";
18036
18344
  function useHealth(maxHp, opts = {}) {
18037
- const engine = useContext56(EngineContext);
18038
- const entityId = useContext56(EntityContext);
18039
- const hpRef = useRef27(maxHp);
18040
- const invincibleRef = useRef27(false);
18345
+ const engine = useContext57(EngineContext);
18346
+ const entityId = useContext57(EntityContext);
18347
+ const hpRef = useRef28(maxHp);
18348
+ const invincibleRef = useRef28(false);
18041
18349
  const iFrameDuration = opts.iFrames ?? 1;
18042
- const onDeathRef = useRef27(opts.onDeath);
18043
- const onDamageRef = useRef27(opts.onDamage);
18044
- useEffect63(() => {
18350
+ const onDeathRef = useRef28(opts.onDeath);
18351
+ const onDamageRef = useRef28(opts.onDamage);
18352
+ useEffect64(() => {
18045
18353
  onDeathRef.current = opts.onDeath;
18046
18354
  });
18047
- useEffect63(() => {
18355
+ useEffect64(() => {
18048
18356
  onDamageRef.current = opts.onDamage;
18049
18357
  });
18050
- const timerRef = useRef27(
18358
+ const timerRef = useRef28(
18051
18359
  createTimer(iFrameDuration, () => {
18052
18360
  invincibleRef.current = false;
18053
18361
  })
18054
18362
  );
18055
- const takeDamage = useCallback13(
18363
+ const takeDamage = useCallback15(
18056
18364
  (amount = 1) => {
18057
18365
  if (invincibleRef.current || hpRef.current <= 0) return;
18058
18366
  hpRef.current = Math.max(0, hpRef.current - amount);
@@ -18065,28 +18373,28 @@ function useHealth(maxHp, opts = {}) {
18065
18373
  },
18066
18374
  [iFrameDuration]
18067
18375
  );
18068
- const takeDamageRef = useRef27(takeDamage);
18069
- useEffect63(() => {
18376
+ const takeDamageRef = useRef28(takeDamage);
18377
+ useEffect64(() => {
18070
18378
  takeDamageRef.current = takeDamage;
18071
18379
  }, [takeDamage]);
18072
- useEffect63(() => {
18380
+ useEffect64(() => {
18073
18381
  return engine.events.on(`damage:${entityId}`, ({ amount }) => {
18074
18382
  takeDamageRef.current(amount);
18075
18383
  });
18076
18384
  }, [engine.events, entityId]);
18077
- const heal = useCallback13(
18385
+ const heal = useCallback15(
18078
18386
  (amount) => {
18079
18387
  hpRef.current = Math.min(maxHp, hpRef.current + amount);
18080
18388
  },
18081
18389
  [maxHp]
18082
18390
  );
18083
- const setHp = useCallback13(
18391
+ const setHp = useCallback15(
18084
18392
  (hp) => {
18085
18393
  hpRef.current = Math.min(maxHp, Math.max(0, hp));
18086
18394
  },
18087
18395
  [maxHp]
18088
18396
  );
18089
- const update = useCallback13((dt) => {
18397
+ const update = useCallback15((dt) => {
18090
18398
  timerRef.current.update(dt);
18091
18399
  }, []);
18092
18400
  return {
@@ -18110,11 +18418,11 @@ function useHealth(maxHp, opts = {}) {
18110
18418
  }
18111
18419
 
18112
18420
  // ../gameplay/src/hooks/useKinematicBody.ts
18113
- import { useContext as useContext57, useCallback as useCallback14 } from "react";
18421
+ import { useContext as useContext58, useCallback as useCallback16 } from "react";
18114
18422
  function useKinematicBody() {
18115
- const engine = useContext57(EngineContext);
18116
- const entityId = useContext57(EntityContext);
18117
- const moveAndCollide = useCallback14(
18423
+ const engine = useContext58(EngineContext);
18424
+ const entityId = useContext58(EntityContext);
18425
+ const moveAndCollide = useCallback16(
18118
18426
  (dx, dy) => {
18119
18427
  const transform = engine.ecs.getComponent(entityId, "Transform");
18120
18428
  if (!transform) return { dx: 0, dy: 0 };
@@ -18150,7 +18458,7 @@ function useKinematicBody() {
18150
18458
  },
18151
18459
  [engine.ecs, entityId]
18152
18460
  );
18153
- const setVelocity = useCallback14(
18461
+ const setVelocity = useCallback16(
18154
18462
  (vx, vy) => {
18155
18463
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
18156
18464
  if (rb) {
@@ -18160,7 +18468,7 @@ function useKinematicBody() {
18160
18468
  },
18161
18469
  [engine.ecs, entityId]
18162
18470
  );
18163
- const setAngularVelocity = useCallback14(
18471
+ const setAngularVelocity = useCallback16(
18164
18472
  (w) => {
18165
18473
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
18166
18474
  if (rb) rb.angularVelocity = w;
@@ -18171,12 +18479,12 @@ function useKinematicBody() {
18171
18479
  }
18172
18480
 
18173
18481
  // ../gameplay/src/hooks/useLevelTransition.ts
18174
- import { useState as useState15, useRef as useRef28, useCallback as useCallback15 } from "react";
18482
+ import { useState as useState16, useRef as useRef29, useCallback as useCallback17 } from "react";
18175
18483
  function useLevelTransition(initial) {
18176
- const [currentLevel, setCurrentLevel] = useState15(initial);
18177
- const [isTransitioning, setIsTransitioning] = useState15(false);
18178
- const overlayRef = useRef28(null);
18179
- const transitionTo = useCallback15((level, opts = {}) => {
18484
+ const [currentLevel, setCurrentLevel] = useState16(initial);
18485
+ const [isTransitioning, setIsTransitioning] = useState16(false);
18486
+ const overlayRef = useRef29(null);
18487
+ const transitionTo = useCallback17((level, opts = {}) => {
18180
18488
  const { duration = 0.4, type = "fade" } = opts;
18181
18489
  if (type === "instant") {
18182
18490
  setCurrentLevel(level);
@@ -18213,13 +18521,13 @@ function useLevelTransition(initial) {
18213
18521
  }
18214
18522
 
18215
18523
  // ../gameplay/src/hooks/usePlatformerController.ts
18216
- import { useContext as useContext58, useEffect as useEffect64 } from "react";
18524
+ import { useContext as useContext59, useEffect as useEffect65 } from "react";
18217
18525
  function normalizeKeys(val, defaults) {
18218
18526
  if (!val) return defaults;
18219
18527
  return Array.isArray(val) ? val : [val];
18220
18528
  }
18221
18529
  function usePlatformerController(entityId, opts = {}) {
18222
- const engine = useContext58(EngineContext);
18530
+ const engine = useContext59(EngineContext);
18223
18531
  const {
18224
18532
  speed = 200,
18225
18533
  jumpForce = -500,
@@ -18237,7 +18545,7 @@ function usePlatformerController(entityId, opts = {}) {
18237
18545
  const leftKeys = normalizeKeys(bindings?.left, ["ArrowLeft", "KeyA", "a"]);
18238
18546
  const rightKeys = normalizeKeys(bindings?.right, ["ArrowRight", "KeyD", "d"]);
18239
18547
  const jumpKeys = normalizeKeys(bindings?.jump, ["Space", "ArrowUp", "KeyW", "w"]);
18240
- useEffect64(() => {
18548
+ useEffect65(() => {
18241
18549
  const state = {
18242
18550
  coyoteTimer: 0,
18243
18551
  jumpBuffer: 0,
@@ -18301,26 +18609,26 @@ function usePlatformerController(entityId, opts = {}) {
18301
18609
  }
18302
18610
 
18303
18611
  // ../gameplay/src/hooks/usePathfinding.ts
18304
- import { useCallback as useCallback16 } from "react";
18612
+ import { useCallback as useCallback18 } from "react";
18305
18613
  function usePathfinding() {
18306
- const createGrid$ = useCallback16(
18614
+ const createGrid$ = useCallback18(
18307
18615
  (cols, rows, cellSize) => createNavGrid(cols, rows, cellSize),
18308
18616
  []
18309
18617
  );
18310
- const setWalkable$ = useCallback16(
18618
+ const setWalkable$ = useCallback18(
18311
18619
  (grid, col, row, walkable) => setWalkable(grid, col, row, walkable),
18312
18620
  []
18313
18621
  );
18314
- const findPath$ = useCallback16((grid, start, goal) => findPath(grid, start, goal), []);
18622
+ const findPath$ = useCallback18((grid, start, goal) => findPath(grid, start, goal), []);
18315
18623
  return { createGrid: createGrid$, setWalkable: setWalkable$, findPath: findPath$ };
18316
18624
  }
18317
18625
 
18318
18626
  // ../gameplay/src/hooks/usePersistedBindings.ts
18319
- import { useState as useState16, useCallback as useCallback17, useMemo as useMemo12, useContext as useContext59 } from "react";
18627
+ import { useState as useState17, useCallback as useCallback19, useMemo as useMemo12, useContext as useContext60 } from "react";
18320
18628
  function usePersistedBindings(storageKey, defaults) {
18321
- const engine = useContext59(EngineContext);
18629
+ const engine = useContext60(EngineContext);
18322
18630
  const input = engine.input;
18323
- const [bindings, setBindings] = useState16(() => {
18631
+ const [bindings, setBindings] = useState17(() => {
18324
18632
  try {
18325
18633
  const stored = localStorage.getItem(storageKey);
18326
18634
  if (stored) return { ...defaults, ...JSON.parse(stored) };
@@ -18336,7 +18644,7 @@ function usePersistedBindings(storageKey, defaults) {
18336
18644
  }
18337
18645
  return out;
18338
18646
  }, [bindings]);
18339
- const rebind = useCallback17(
18647
+ const rebind = useCallback19(
18340
18648
  (action, keys) => {
18341
18649
  setBindings((prev) => {
18342
18650
  const next = { ...prev, [action]: Array.isArray(keys) ? keys : [keys] };
@@ -18349,7 +18657,7 @@ function usePersistedBindings(storageKey, defaults) {
18349
18657
  },
18350
18658
  [storageKey]
18351
18659
  );
18352
- const reset = useCallback17(() => {
18660
+ const reset = useCallback19(() => {
18353
18661
  try {
18354
18662
  localStorage.removeItem(storageKey);
18355
18663
  } catch {
@@ -18370,21 +18678,21 @@ function usePersistedBindings(storageKey, defaults) {
18370
18678
  }
18371
18679
 
18372
18680
  // ../gameplay/src/hooks/useRestart.ts
18373
- import { useState as useState17, useCallback as useCallback18 } from "react";
18681
+ import { useState as useState18, useCallback as useCallback20 } from "react";
18374
18682
  function useRestart() {
18375
- const [restartKey, setRestartKey] = useState17(0);
18376
- const restart = useCallback18(() => {
18683
+ const [restartKey, setRestartKey] = useState18(0);
18684
+ const restart = useCallback20(() => {
18377
18685
  setRestartKey((k) => k + 1);
18378
18686
  }, []);
18379
18687
  return { restartKey, restart };
18380
18688
  }
18381
18689
 
18382
18690
  // ../gameplay/src/hooks/useSave.ts
18383
- import { useCallback as useCallback19, useRef as useRef29 } from "react";
18691
+ import { useCallback as useCallback21, useRef as useRef30 } from "react";
18384
18692
  function useSave(key, defaultValue, opts = {}) {
18385
18693
  const version = opts.version ?? 1;
18386
- const dataRef = useRef29(defaultValue);
18387
- const save = useCallback19(
18694
+ const dataRef = useRef30(defaultValue);
18695
+ const save = useCallback21(
18388
18696
  (value) => {
18389
18697
  dataRef.current = value;
18390
18698
  const slot = { version, data: value };
@@ -18396,7 +18704,7 @@ function useSave(key, defaultValue, opts = {}) {
18396
18704
  },
18397
18705
  [key, version]
18398
18706
  );
18399
- const load = useCallback19(() => {
18707
+ const load = useCallback21(() => {
18400
18708
  try {
18401
18709
  const raw = localStorage.getItem(key);
18402
18710
  if (!raw) return defaultValue;
@@ -18416,11 +18724,11 @@ function useSave(key, defaultValue, opts = {}) {
18416
18724
  return defaultValue;
18417
18725
  }
18418
18726
  }, [key, version]);
18419
- const clear = useCallback19(() => {
18727
+ const clear = useCallback21(() => {
18420
18728
  localStorage.removeItem(key);
18421
18729
  dataRef.current = defaultValue;
18422
18730
  }, [key]);
18423
- const exists = useCallback19(() => {
18731
+ const exists = useCallback21(() => {
18424
18732
  return localStorage.getItem(key) !== null;
18425
18733
  }, [key]);
18426
18734
  return {
@@ -18435,7 +18743,7 @@ function useSave(key, defaultValue, opts = {}) {
18435
18743
  }
18436
18744
 
18437
18745
  // ../gameplay/src/hooks/useIDBSave.ts
18438
- import { useCallback as useCallback20, useRef as useRef30 } from "react";
18746
+ import { useCallback as useCallback22, useRef as useRef31 } from "react";
18439
18747
  var DB_NAME = "cubeforge-saves";
18440
18748
  var STORE_NAME = "saves";
18441
18749
  function openDB() {
@@ -18453,12 +18761,12 @@ function openDB() {
18453
18761
  }
18454
18762
  function useIDBSave(key, defaultValue, opts = {}) {
18455
18763
  const version = opts.version ?? 1;
18456
- const dbRef = useRef30(null);
18457
- const getDB = useCallback20(() => {
18764
+ const dbRef = useRef31(null);
18765
+ const getDB = useCallback22(() => {
18458
18766
  if (!dbRef.current) dbRef.current = openDB();
18459
18767
  return dbRef.current;
18460
18768
  }, []);
18461
- const save = useCallback20(
18769
+ const save = useCallback22(
18462
18770
  async (value) => {
18463
18771
  const db = await getDB();
18464
18772
  const slot = { version, data: value };
@@ -18471,7 +18779,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
18471
18779
  },
18472
18780
  [getDB, key, version]
18473
18781
  );
18474
- const load = useCallback20(async () => {
18782
+ const load = useCallback22(async () => {
18475
18783
  const db = await getDB();
18476
18784
  return new Promise((resolve, reject) => {
18477
18785
  const tx = db.transaction(STORE_NAME, "readonly");
@@ -18497,7 +18805,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
18497
18805
  req.onerror = () => reject(req.error);
18498
18806
  });
18499
18807
  }, [getDB, key, version]);
18500
- const clear = useCallback20(async () => {
18808
+ const clear = useCallback22(async () => {
18501
18809
  const db = await getDB();
18502
18810
  return new Promise((resolve, reject) => {
18503
18811
  const tx = db.transaction(STORE_NAME, "readwrite");
@@ -18506,7 +18814,7 @@ function useIDBSave(key, defaultValue, opts = {}) {
18506
18814
  req.onerror = () => reject(req.error);
18507
18815
  });
18508
18816
  }, [getDB, key]);
18509
- const exists = useCallback20(async () => {
18817
+ const exists = useCallback22(async () => {
18510
18818
  const db = await getDB();
18511
18819
  return new Promise((resolve, reject) => {
18512
18820
  const tx = db.transaction(STORE_NAME, "readonly");
@@ -18519,16 +18827,16 @@ function useIDBSave(key, defaultValue, opts = {}) {
18519
18827
  }
18520
18828
 
18521
18829
  // ../gameplay/src/hooks/useTopDownMovement.ts
18522
- import { useContext as useContext60, useEffect as useEffect65 } from "react";
18830
+ import { useContext as useContext61, useEffect as useEffect66 } from "react";
18523
18831
  function moveToward(current, target, maxDelta) {
18524
18832
  const diff = target - current;
18525
18833
  if (Math.abs(diff) <= maxDelta) return target;
18526
18834
  return current + Math.sign(diff) * maxDelta;
18527
18835
  }
18528
18836
  function useTopDownMovement(entityId, opts = {}) {
18529
- const engine = useContext60(EngineContext);
18837
+ const engine = useContext61(EngineContext);
18530
18838
  const { speed = 200, normalizeDiagonal = true, acceleration = Infinity, deceleration = acceleration } = opts;
18531
- useEffect65(() => {
18839
+ useEffect66(() => {
18532
18840
  const updateFn = (id, world, input, dt) => {
18533
18841
  if (!world.hasEntity(id)) return;
18534
18842
  const rb = world.getComponent(id, "RigidBody");
@@ -18568,18 +18876,18 @@ function useTopDownMovement(entityId, opts = {}) {
18568
18876
  }
18569
18877
 
18570
18878
  // ../gameplay/src/hooks/useDialogue.ts
18571
- import { useState as useState18, useCallback as useCallback21, useRef as useRef31 } from "react";
18879
+ import { useState as useState19, useCallback as useCallback23, useRef as useRef32 } from "react";
18572
18880
  function useDialogue() {
18573
- const [active, setActive] = useState18(false);
18574
- const [currentId, setCurrentId] = useState18(null);
18575
- const scriptRef = useRef31(null);
18576
- const start = useCallback21((script, startId) => {
18881
+ const [active, setActive] = useState19(false);
18882
+ const [currentId, setCurrentId] = useState19(null);
18883
+ const scriptRef = useRef32(null);
18884
+ const start = useCallback23((script, startId) => {
18577
18885
  scriptRef.current = script;
18578
18886
  const id = startId ?? Object.keys(script)[0];
18579
18887
  setCurrentId(id);
18580
18888
  setActive(true);
18581
18889
  }, []);
18582
- const advance = useCallback21(
18890
+ const advance = useCallback23(
18583
18891
  (choiceIndex) => {
18584
18892
  if (!scriptRef.current || !currentId) return;
18585
18893
  const line = scriptRef.current[currentId];
@@ -18609,7 +18917,7 @@ function useDialogue() {
18609
18917
  },
18610
18918
  [currentId]
18611
18919
  );
18612
- const close = useCallback21(() => {
18920
+ const close = useCallback23(() => {
18613
18921
  setActive(false);
18614
18922
  setCurrentId(null);
18615
18923
  scriptRef.current = null;
@@ -18727,17 +19035,17 @@ function DialogueBox({ dialogue, onAdvance, onClose, style = {} }) {
18727
19035
  }
18728
19036
 
18729
19037
  // ../gameplay/src/hooks/useCutscene.ts
18730
- import { useState as useState19, useCallback as useCallback22, useRef as useRef32, useEffect as useEffect66, useContext as useContext61 } from "react";
19038
+ import { useState as useState20, useCallback as useCallback24, useRef as useRef33, useEffect as useEffect67, useContext as useContext62 } from "react";
18731
19039
  function useCutscene() {
18732
- const engine = useContext61(EngineContext);
18733
- const [playing, setPlaying] = useState19(false);
18734
- const [stepIndex, setStepIndex] = useState19(0);
18735
- const stepsRef = useRef32([]);
18736
- const timerRef = useRef32(0);
18737
- const idxRef = useRef32(0);
18738
- const playingRef = useRef32(false);
18739
- const entityRef = useRef32(null);
18740
- const finish = useCallback22(() => {
19040
+ const engine = useContext62(EngineContext);
19041
+ const [playing, setPlaying] = useState20(false);
19042
+ const [stepIndex, setStepIndex] = useState20(0);
19043
+ const stepsRef = useRef33([]);
19044
+ const timerRef = useRef33(0);
19045
+ const idxRef = useRef33(0);
19046
+ const playingRef = useRef33(false);
19047
+ const entityRef = useRef33(null);
19048
+ const finish = useCallback24(() => {
18741
19049
  playingRef.current = false;
18742
19050
  setPlaying(false);
18743
19051
  setStepIndex(0);
@@ -18747,14 +19055,14 @@ function useCutscene() {
18747
19055
  entityRef.current = null;
18748
19056
  }
18749
19057
  }, [engine.ecs]);
18750
- const fireStep = useCallback22((step) => {
19058
+ const fireStep = useCallback24((step) => {
18751
19059
  if (step.type === "call") step.fn();
18752
19060
  if (step.type === "parallel")
18753
19061
  step.steps.forEach((s2) => {
18754
19062
  if (s2.type === "call") s2.fn();
18755
19063
  });
18756
19064
  }, []);
18757
- const play = useCallback22(
19065
+ const play = useCallback24(
18758
19066
  (steps) => {
18759
19067
  stepsRef.current = steps;
18760
19068
  idxRef.current = 0;
@@ -18811,7 +19119,7 @@ function useCutscene() {
18811
19119
  },
18812
19120
  [engine.ecs, finish, fireStep]
18813
19121
  );
18814
- const skip = useCallback22(() => {
19122
+ const skip = useCallback24(() => {
18815
19123
  for (let i = idxRef.current; i < stepsRef.current.length; i++) {
18816
19124
  const step = stepsRef.current[i];
18817
19125
  if (step.type === "call") step.fn();
@@ -18822,7 +19130,7 @@ function useCutscene() {
18822
19130
  }
18823
19131
  finish();
18824
19132
  }, [finish]);
18825
- useEffect66(() => {
19133
+ useEffect67(() => {
18826
19134
  return () => {
18827
19135
  if (entityRef.current !== null && engine.ecs.hasEntity(entityRef.current)) {
18828
19136
  engine.ecs.destroyEntity(entityRef.current);
@@ -18833,7 +19141,7 @@ function useCutscene() {
18833
19141
  }
18834
19142
 
18835
19143
  // ../gameplay/src/hooks/useGameStore.ts
18836
- import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback23 } from "react";
19144
+ import { useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback25 } from "react";
18837
19145
  function createStore(initialState) {
18838
19146
  let state = { ...initialState };
18839
19147
  const listeners = /* @__PURE__ */ new Set();
@@ -18859,7 +19167,7 @@ function useGameStore(key, initialState) {
18859
19167
  }
18860
19168
  const store = stores.get(key);
18861
19169
  const state = useSyncExternalStore2(store.subscribe, store.getState);
18862
- const setState = useCallback23(
19170
+ const setState = useCallback25(
18863
19171
  (partial) => {
18864
19172
  store.setState(partial);
18865
19173
  },
@@ -18869,21 +19177,21 @@ function useGameStore(key, initialState) {
18869
19177
  }
18870
19178
 
18871
19179
  // ../gameplay/src/hooks/useTween.ts
18872
- import { useRef as useRef33, useCallback as useCallback24, useEffect as useEffect67 } from "react";
19180
+ import { useRef as useRef34, useCallback as useCallback26, useEffect as useEffect68 } from "react";
18873
19181
  function useTween(opts) {
18874
- const rafRef = useRef33(null);
18875
- const startTimeRef = useRef33(0);
18876
- const runningRef = useRef33(false);
18877
- const optsRef = useRef33(opts);
19182
+ const rafRef = useRef34(null);
19183
+ const startTimeRef = useRef34(0);
19184
+ const runningRef = useRef34(false);
19185
+ const optsRef = useRef34(opts);
18878
19186
  optsRef.current = opts;
18879
- const stop = useCallback24(() => {
19187
+ const stop = useCallback26(() => {
18880
19188
  if (rafRef.current !== null) {
18881
19189
  cancelAnimationFrame(rafRef.current);
18882
19190
  rafRef.current = null;
18883
19191
  }
18884
19192
  runningRef.current = false;
18885
19193
  }, []);
18886
- const start = useCallback24(() => {
19194
+ const start = useCallback26(() => {
18887
19195
  stop();
18888
19196
  runningRef.current = true;
18889
19197
  startTimeRef.current = performance.now();
@@ -18905,12 +19213,12 @@ function useTween(opts) {
18905
19213
  };
18906
19214
  rafRef.current = requestAnimationFrame(tick);
18907
19215
  }, [stop]);
18908
- useEffect67(() => {
19216
+ useEffect68(() => {
18909
19217
  if (opts.autoStart) {
18910
19218
  start();
18911
19219
  }
18912
19220
  }, []);
18913
- useEffect67(() => {
19221
+ useEffect68(() => {
18914
19222
  return () => {
18915
19223
  if (rafRef.current !== null) {
18916
19224
  cancelAnimationFrame(rafRef.current);
@@ -18929,15 +19237,15 @@ function useTween(opts) {
18929
19237
  }
18930
19238
 
18931
19239
  // ../gameplay/src/hooks/useObjectPool.ts
18932
- import { useRef as useRef34, useMemo as useMemo13, useEffect as useEffect68 } from "react";
19240
+ import { useRef as useRef35, useMemo as useMemo13, useEffect as useEffect69 } from "react";
18933
19241
  function useObjectPool(factory, reset, initialSize) {
18934
- const poolRef = useRef34([]);
18935
- const activeRef = useRef34(0);
18936
- const factoryRef = useRef34(factory);
19242
+ const poolRef = useRef35([]);
19243
+ const activeRef = useRef35(0);
19244
+ const factoryRef = useRef35(factory);
18937
19245
  factoryRef.current = factory;
18938
- const resetRef = useRef34(reset);
19246
+ const resetRef = useRef35(reset);
18939
19247
  resetRef.current = reset;
18940
- useEffect68(() => {
19248
+ useEffect69(() => {
18941
19249
  if (initialSize != null && initialSize > 0) {
18942
19250
  const pool = poolRef.current;
18943
19251
  for (let i = 0; i < initialSize; i++) {
@@ -18976,10 +19284,10 @@ function useObjectPool(factory, reset, initialSize) {
18976
19284
  }
18977
19285
 
18978
19286
  // ../gameplay/src/hooks/useForces.ts
18979
- import { useContext as useContext62, useMemo as useMemo14 } from "react";
19287
+ import { useContext as useContext63, useMemo as useMemo14 } from "react";
18980
19288
  function useForces() {
18981
- const engine = useContext62(EngineContext);
18982
- const entityId = useContext62(EntityContext);
19289
+ const engine = useContext63(EngineContext);
19290
+ const entityId = useContext63(EntityContext);
18983
19291
  return useMemo14(() => {
18984
19292
  const getRb = () => engine.ecs.getComponent(entityId, "RigidBody");
18985
19293
  const getTransform = () => engine.ecs.getComponent(entityId, "Transform");
@@ -19048,10 +19356,10 @@ function useForces() {
19048
19356
  }
19049
19357
 
19050
19358
  // ../gameplay/src/hooks/useCharacterController.ts
19051
- import { useContext as useContext63, useMemo as useMemo15 } from "react";
19359
+ import { useContext as useContext64, useMemo as useMemo15 } from "react";
19052
19360
  function useCharacterController(config = {}) {
19053
- const engine = useContext63(EngineContext);
19054
- const entityId = useContext63(EntityContext);
19361
+ const engine = useContext64(EngineContext);
19362
+ const entityId = useContext64(EntityContext);
19055
19363
  return useMemo15(() => {
19056
19364
  const controller = new CharacterController(config);
19057
19365
  return {
@@ -19101,7 +19409,7 @@ function definePrefab(name, defaults, render) {
19101
19409
  }
19102
19410
 
19103
19411
  // src/hooks/useNetworkSync.ts
19104
- import { useEffect as useEffect70, useRef as useRef36 } from "react";
19412
+ import { useEffect as useEffect71, useRef as useRef37 } from "react";
19105
19413
 
19106
19414
  // ../../packages/net/src/transport.ts
19107
19415
  function isBinaryTransport(t) {
@@ -19446,17 +19754,17 @@ function syncEntity(config) {
19446
19754
  }
19447
19755
 
19448
19756
  // ../../packages/net/src/useNetworkInput.ts
19449
- import { useState as useState20, useEffect as useEffect69, useRef as useRef35 } from "react";
19757
+ import { useState as useState21, useEffect as useEffect70, useRef as useRef36 } from "react";
19450
19758
  var INPUT_MSG_TYPE = "input:state";
19451
19759
  function useNetworkInput(config) {
19452
19760
  const { room, keys, input, tickRate = 20 } = config;
19453
19761
  const intervalMs = 1e3 / tickRate;
19454
- const [localInput, setLocalInput] = useState20(
19762
+ const [localInput, setLocalInput] = useState21(
19455
19763
  () => Object.fromEntries(keys.map((k) => [k, false]))
19456
19764
  );
19457
- const [remoteInputs] = useState20(() => /* @__PURE__ */ new Map());
19458
- const localInputRef = useRef35(localInput);
19459
- useEffect69(() => {
19765
+ const [remoteInputs] = useState21(() => /* @__PURE__ */ new Map());
19766
+ const localInputRef = useRef36(localInput);
19767
+ useEffect70(() => {
19460
19768
  let cleanupDom = null;
19461
19769
  if (!input) {
19462
19770
  let handleKeyDown2 = function(e) {
@@ -19670,9 +19978,9 @@ function lerpState(a, b, t) {
19670
19978
 
19671
19979
  // src/hooks/useNetworkSync.ts
19672
19980
  function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
19673
- const optsRef = useRef36(opts);
19981
+ const optsRef = useRef37(opts);
19674
19982
  optsRef.current = opts;
19675
- useEffect70(() => {
19983
+ useEffect71(() => {
19676
19984
  const sync = syncEntity({
19677
19985
  entityId,
19678
19986
  components,
@@ -19687,18 +19995,18 @@ function useNetworkSync(entityId, components, room, world, owner, opts = {}) {
19687
19995
  }
19688
19996
 
19689
19997
  // src/hooks/useRemotePlayer.ts
19690
- import { useEffect as useEffect71, useRef as useRef37, useState as useState21 } from "react";
19998
+ import { useEffect as useEffect72, useRef as useRef38, useState as useState22 } from "react";
19691
19999
  var PEER_JOIN_MSG = "peer:join";
19692
20000
  var PEER_LEAVE_MSG = "peer:leave";
19693
20001
  function useRemotePlayer(config) {
19694
20002
  const { room, world, createEntity, destroyEntity } = config;
19695
- const [players, setPlayers] = useState21(() => /* @__PURE__ */ new Map());
19696
- const playersRef = useRef37(players);
19697
- const createRef = useRef37(createEntity);
20003
+ const [players, setPlayers] = useState22(() => /* @__PURE__ */ new Map());
20004
+ const playersRef = useRef38(players);
20005
+ const createRef = useRef38(createEntity);
19698
20006
  createRef.current = createEntity;
19699
- const destroyRef = useRef37(destroyEntity);
20007
+ const destroyRef = useRef38(destroyEntity);
19700
20008
  destroyRef.current = destroyEntity;
19701
- useEffect71(() => {
20009
+ useEffect72(() => {
19702
20010
  function spawnPeer(peerId) {
19703
20011
  if (playersRef.current.has(peerId)) return;
19704
20012
  const entityId = createRef.current(peerId);
@@ -19788,6 +20096,7 @@ export {
19788
20096
  RenderSystem,
19789
20097
  RigidBody,
19790
20098
  Room,
20099
+ SceneTransitionOverlay,
19791
20100
  ScreenFlash,
19792
20101
  Script,
19793
20102
  SegmentCollider,
@@ -19985,6 +20294,7 @@ export {
19985
20294
  useGestures,
19986
20295
  useHMR,
19987
20296
  useHealth,
20297
+ useHitstop,
19988
20298
  useIDBSave,
19989
20299
  useInput,
19990
20300
  useInputBuffer,
@@ -20012,6 +20322,7 @@ export {
20012
20322
  useRestart,
20013
20323
  useSave,
20014
20324
  useSceneManager,
20325
+ useSceneTransition,
20015
20326
  useSnapshot,
20016
20327
  useSound,
20017
20328
  useSpatialSound,