cubeforge 0.3.14 → 0.3.16

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 +208 -11
  2. package/dist/index.js +1243 -293
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/Game.tsx
2
- import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
2
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
3
3
 
4
4
  // ../../packages/core/src/ecs/world.ts
5
5
  var ECSWorld = class {
@@ -734,14 +734,95 @@ var ScriptSystem = class {
734
734
  // ../../packages/core/src/tween.ts
735
735
  var Ease = {
736
736
  linear: (t) => t,
737
+ // Quad
737
738
  easeInQuad: (t) => t * t,
738
739
  easeOutQuad: (t) => t * (2 - t),
739
740
  easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
741
+ // Cubic
742
+ easeInCubic: (t) => t * t * t,
743
+ easeOutCubic: (t) => {
744
+ const t1 = t - 1;
745
+ return t1 * t1 * t1 + 1;
746
+ },
747
+ easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
748
+ // Quart
749
+ easeInQuart: (t) => t * t * t * t,
750
+ easeOutQuart: (t) => {
751
+ const t1 = t - 1;
752
+ return 1 - t1 * t1 * t1 * t1;
753
+ },
754
+ easeInOutQuart: (t) => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (t - 1) * (t - 1) * (t - 1) * (t - 1),
755
+ // Quint
756
+ easeInQuint: (t) => t * t * t * t * t,
757
+ easeOutQuint: (t) => {
758
+ const t1 = t - 1;
759
+ return 1 + t1 * t1 * t1 * t1 * t1;
760
+ },
761
+ easeInOutQuint: (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (t - 1) * (t - 1) * (t - 1) * (t - 1) * (t - 1),
762
+ // Sine
763
+ easeInSine: (t) => 1 - Math.cos(t * Math.PI / 2),
764
+ easeOutSine: (t) => Math.sin(t * Math.PI / 2),
765
+ easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
766
+ // Expo
767
+ easeInExpo: (t) => t === 0 ? 0 : Math.pow(2, 10 * (t - 1)),
768
+ easeOutExpo: (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t),
769
+ easeInOutExpo: (t) => {
770
+ if (t === 0) return 0;
771
+ if (t === 1) return 1;
772
+ return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
773
+ },
774
+ // Circ
775
+ easeInCirc: (t) => 1 - Math.sqrt(1 - t * t),
776
+ easeOutCirc: (t) => Math.sqrt(1 - (t - 1) * (t - 1)),
777
+ easeInOutCirc: (t) => t < 0.5 ? (1 - Math.sqrt(1 - 4 * t * t)) / 2 : (Math.sqrt(1 - (-2 * t + 2) * (-2 * t + 2)) + 1) / 2,
778
+ // Back
779
+ easeInBack: (t) => {
780
+ const c1 = 1.70158;
781
+ const c3 = c1 + 1;
782
+ return c3 * t * t * t - c1 * t * t;
783
+ },
740
784
  easeOutBack: (t) => {
741
785
  const c1 = 1.70158;
742
786
  const c3 = c1 + 1;
743
787
  return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
744
- }
788
+ },
789
+ easeInOutBack: (t) => {
790
+ const c1 = 1.70158;
791
+ const c2 = c1 * 1.525;
792
+ return t < 0.5 ? 2 * t * (2 * t) * ((c2 + 1) * 2 * t - c2) / 2 : ((2 * t - 2) * (2 * t - 2) * ((c2 + 1) * (2 * t - 2) + c2) + 2) / 2;
793
+ },
794
+ // Elastic
795
+ easeInElastic: (t) => {
796
+ if (t === 0 || t === 1) return t;
797
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * (2 * Math.PI / 3));
798
+ },
799
+ easeOutElastic: (t) => {
800
+ if (t === 0 || t === 1) return t;
801
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
802
+ },
803
+ easeInOutElastic: (t) => {
804
+ if (t === 0 || t === 1) return t;
805
+ const c5 = 2 * Math.PI / 4.5;
806
+ return t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 : Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5) / 2 + 1;
807
+ },
808
+ // Bounce
809
+ easeOutBounce: (t) => {
810
+ const n1 = 7.5625;
811
+ const d1 = 2.75;
812
+ if (t < 1 / d1) return n1 * t * t;
813
+ if (t < 2 / d1) {
814
+ const t22 = t - 1.5 / d1;
815
+ return n1 * t22 * t22 + 0.75;
816
+ }
817
+ if (t < 2.5 / d1) {
818
+ const t22 = t - 2.25 / d1;
819
+ return n1 * t22 * t22 + 0.9375;
820
+ }
821
+ const t2 = t - 2.625 / d1;
822
+ return n1 * t2 * t2 + 0.984375;
823
+ },
824
+ easeInBounce: (t) => 1 - Ease.easeOutBounce(1 - t),
825
+ easeInOutBounce: (t) => t < 0.5 ? (1 - Ease.easeOutBounce(1 - 2 * t)) / 2 : (1 + Ease.easeOutBounce(2 * t - 1)) / 2
745
826
  };
746
827
  function tween(from, to, duration, ease = Ease.linear, onUpdate, onComplete) {
747
828
  let elapsed = 0;
@@ -767,6 +848,87 @@ function tween(from, to, duration, ease = Ease.linear, onUpdate, onComplete) {
767
848
  };
768
849
  }
769
850
 
851
+ // ../../packages/core/src/tweenTimeline.ts
852
+ function createTimeline() {
853
+ const entries = [];
854
+ let running = false;
855
+ let currentTween = null;
856
+ let delayTimer = null;
857
+ let rafId = null;
858
+ let lastTime = 0;
859
+ function clearCurrent() {
860
+ if (currentTween) {
861
+ currentTween.stop();
862
+ currentTween = null;
863
+ }
864
+ if (delayTimer !== null) {
865
+ clearTimeout(delayTimer);
866
+ delayTimer = null;
867
+ }
868
+ if (rafId !== null) {
869
+ cancelAnimationFrame(rafId);
870
+ rafId = null;
871
+ }
872
+ }
873
+ function tick(now) {
874
+ if (!running || !currentTween) return;
875
+ const dt = (now - lastTime) / 1e3;
876
+ lastTime = now;
877
+ currentTween.update(dt);
878
+ if (!currentTween.isComplete) {
879
+ rafId = requestAnimationFrame(tick);
880
+ }
881
+ }
882
+ function playEntry(index) {
883
+ if (index >= entries.length) {
884
+ running = false;
885
+ return;
886
+ }
887
+ const entry = entries[index];
888
+ const delay = entry.delay ?? 0;
889
+ const startTween = () => {
890
+ if (!running) return;
891
+ currentTween = tween(
892
+ entry.from,
893
+ entry.to,
894
+ entry.duration,
895
+ entry.ease ?? Ease.linear,
896
+ entry.onUpdate,
897
+ () => {
898
+ entry.onComplete?.();
899
+ playEntry(index + 1);
900
+ }
901
+ );
902
+ lastTime = performance.now();
903
+ rafId = requestAnimationFrame(tick);
904
+ };
905
+ if (delay > 0) {
906
+ delayTimer = setTimeout(startTween, delay * 1e3);
907
+ } else {
908
+ startTween();
909
+ }
910
+ }
911
+ const timeline = {
912
+ add(entry) {
913
+ entries.push(entry);
914
+ return timeline;
915
+ },
916
+ start() {
917
+ clearCurrent();
918
+ running = true;
919
+ playEntry(0);
920
+ },
921
+ stop() {
922
+ running = false;
923
+ clearCurrent();
924
+ },
925
+ isRunning() {
926
+ return running;
927
+ }
928
+ };
929
+ return timeline;
930
+ }
931
+
770
932
  // ../../packages/core/src/timer.ts
771
933
  function createTimer(duration, onComplete, autoStart = false) {
772
934
  let _duration = duration;
@@ -831,6 +993,48 @@ function hotReloadPlugin(engine, oldPlugin, newPlugin, oldSystems) {
831
993
  newPlugin.onInit?.(engine);
832
994
  }
833
995
 
996
+ // ../../packages/core/src/tilemapMerge.ts
997
+ function mergeTileColliders(solidGrid, tileWidth, tileHeight, originX, originY) {
998
+ const rows = solidGrid.length;
999
+ if (rows === 0) return [];
1000
+ const cols = solidGrid[0].length;
1001
+ const visited = [];
1002
+ for (let r = 0; r < rows; r++) {
1003
+ visited[r] = new Array(cols).fill(false);
1004
+ }
1005
+ const rects = [];
1006
+ for (let row = 0; row < rows; row++) {
1007
+ for (let col = 0; col < cols; col++) {
1008
+ if (!solidGrid[row][col] || visited[row][col]) continue;
1009
+ let maxCol = col;
1010
+ while (maxCol + 1 < cols && solidGrid[row][maxCol + 1] && !visited[row][maxCol + 1]) {
1011
+ maxCol++;
1012
+ }
1013
+ let maxRow = row;
1014
+ outer:
1015
+ for (let r = row + 1; r < rows; r++) {
1016
+ for (let c = col; c <= maxCol; c++) {
1017
+ if (!solidGrid[r][c] || visited[r][c]) break outer;
1018
+ }
1019
+ maxRow = r;
1020
+ }
1021
+ for (let r = row; r <= maxRow; r++) {
1022
+ for (let c = col; c <= maxCol; c++) {
1023
+ visited[r][c] = true;
1024
+ }
1025
+ }
1026
+ const spanCols = maxCol - col + 1;
1027
+ const spanRows = maxRow - row + 1;
1028
+ const width = spanCols * tileWidth;
1029
+ const height = spanRows * tileHeight;
1030
+ const x = originX + col * tileWidth + width / 2;
1031
+ const y = originY + row * tileHeight + height / 2;
1032
+ rects.push({ x, y, width, height });
1033
+ }
1034
+ }
1035
+ return rects;
1036
+ }
1037
+
834
1038
  // ../../packages/input/src/keyboard.ts
835
1039
  var Keyboard = class {
836
1040
  held = /* @__PURE__ */ new Set();
@@ -1156,13 +1360,36 @@ function createSprite(opts) {
1156
1360
  zIndex: 0,
1157
1361
  visible: true,
1158
1362
  flipX: false,
1363
+ flipY: false,
1159
1364
  anchorX: 0.5,
1160
1365
  anchorY: 0.5,
1161
1366
  frameIndex: 0,
1367
+ blendMode: "normal",
1368
+ layer: "default",
1162
1369
  ...opts
1163
1370
  };
1164
1371
  }
1165
1372
 
1373
+ // ../../packages/renderer/src/renderLayers.ts
1374
+ var defaultLayers = [
1375
+ { name: "background", order: -100 },
1376
+ { name: "default", order: 0 },
1377
+ { name: "foreground", order: 100 },
1378
+ { name: "ui", order: 200 }
1379
+ ];
1380
+ function createRenderLayerManager(layers = defaultLayers) {
1381
+ const map = /* @__PURE__ */ new Map();
1382
+ for (const l of layers) map.set(l.name, l.order);
1383
+ return {
1384
+ addLayer(name, order) {
1385
+ map.set(name, order);
1386
+ },
1387
+ getOrder(name) {
1388
+ return map.get(name) ?? 0;
1389
+ }
1390
+ };
1391
+ }
1392
+
1166
1393
  // ../../packages/renderer/src/textureFilter.ts
1167
1394
  var TextureFilter = {
1168
1395
  /** Nearest-neighbor — sharp pixels, ideal for pixel art */
@@ -1269,8 +1496,9 @@ layout(location = 4) in float i_rot;
1269
1496
  layout(location = 5) in vec2 i_anchor;
1270
1497
  layout(location = 6) in vec2 i_offset;
1271
1498
  layout(location = 7) in float i_flipX;
1272
- layout(location = 8) in vec4 i_color;
1273
- layout(location = 9) in vec4 i_uvRect;
1499
+ layout(location = 8) in float i_flipY;
1500
+ layout(location = 9) in vec4 i_color;
1501
+ layout(location = 10) in vec4 i_uvRect;
1274
1502
 
1275
1503
  uniform vec2 u_camPos;
1276
1504
  uniform float u_zoom;
@@ -1286,6 +1514,8 @@ void main() {
1286
1514
 
1287
1515
  // Horizontal flip
1288
1516
  if (i_flipX > 0.5) local.x = -local.x;
1517
+ // Vertical flip
1518
+ if (i_flipY > 0.5) local.y = -local.y;
1289
1519
 
1290
1520
  // Rotate around local origin
1291
1521
  float c = cos(i_rot);
@@ -1397,9 +1627,9 @@ function parseCSSColor(css) {
1397
1627
  }
1398
1628
 
1399
1629
  // ../../packages/renderer/src/webglRenderSystem.ts
1400
- var FLOATS_PER_INSTANCE = 18;
1401
- var MAX_INSTANCES = 8192;
1402
- var MAX_SPRITE_TEXTURES = 512;
1630
+ var FLOATS_PER_INSTANCE = 19;
1631
+ var MAX_INSTANCES = 16384;
1632
+ var MAX_SPRITE_TEXTURES = 1024;
1403
1633
  var MAX_TEXT_CACHE = 200;
1404
1634
  function compileShader(gl, type, src) {
1405
1635
  const shader = gl.createShader(type);
@@ -1531,8 +1761,9 @@ var RenderSystem = class {
1531
1761
  addAttr(5, 2);
1532
1762
  addAttr(6, 2);
1533
1763
  addAttr(7, 1);
1534
- addAttr(8, 4);
1764
+ addAttr(8, 1);
1535
1765
  addAttr(9, 4);
1766
+ addAttr(10, 4);
1536
1767
  gl.bindVertexArray(null);
1537
1768
  gl.useProgram(this.program);
1538
1769
  this.uCamPos = gl.getUniformLocation(this.program, "u_camPos");
@@ -1604,6 +1835,8 @@ var RenderSystem = class {
1604
1835
  textureCache = /* @__PURE__ */ new Map();
1605
1836
  /** Insertion-order key list for LRU-style eviction. */
1606
1837
  textureCacheKeys = [];
1838
+ // ── Render layer manager ────────────────────────────────────────────────
1839
+ layers = createRenderLayerManager();
1607
1840
  // ── Texture sampling ────────────────────────────────────────────────────
1608
1841
  _defaultSampling = DEFAULT_SAMPLING;
1609
1842
  /** Set the global default texture sampling mode for all sprites that don't specify their own. */
@@ -1768,10 +2001,29 @@ var RenderSystem = class {
1768
2001
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, toGLMinFilter(gl, min));
1769
2002
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, toGLMagFilter(gl, mag));
1770
2003
  }
2004
+ // ── Blend mode helper ────────────────────────────────────────────────────
2005
+ applyBlendMode(mode) {
2006
+ const { gl } = this;
2007
+ switch (mode) {
2008
+ case "additive":
2009
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
2010
+ break;
2011
+ case "multiply":
2012
+ gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
2013
+ break;
2014
+ case "screen":
2015
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
2016
+ break;
2017
+ default:
2018
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
2019
+ break;
2020
+ }
2021
+ }
1771
2022
  // ── Instanced draw call ────────────────────────────────────────────────────
1772
- flush(count, textureKey, sampling) {
2023
+ flush(count, textureKey, sampling, blendMode) {
1773
2024
  if (count === 0) return;
1774
2025
  const { gl } = this;
2026
+ if (blendMode && blendMode !== "normal") this.applyBlendMode(blendMode);
1775
2027
  const isColor = textureKey.startsWith("__color__");
1776
2028
  const tex = isColor ? this.whiteTexture : this.loadTexture(textureKey);
1777
2029
  gl.bindTexture(gl.TEXTURE_2D, tex);
@@ -1781,6 +2033,7 @@ var RenderSystem = class {
1781
2033
  gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer);
1782
2034
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.instanceData, 0, count * FLOATS_PER_INSTANCE);
1783
2035
  gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count);
2036
+ if (blendMode && blendMode !== "normal") this.applyBlendMode("normal");
1784
2037
  }
1785
2038
  flushWithTex(count, tex, useTexture) {
1786
2039
  if (count === 0) return;
@@ -1793,7 +2046,7 @@ var RenderSystem = class {
1793
2046
  gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count);
1794
2047
  }
1795
2048
  // ── Write one sprite instance into instanceData ───────────────────────────
1796
- writeInstance(base, x, y, w, h, rot, anchorX, anchorY, offsetX, offsetY, flipX, r, g, b, a, u, v, uw, vh) {
2049
+ writeInstance(base, x, y, w, h, rot, anchorX, anchorY, offsetX, offsetY, flipX, flipY, r, g, b, a, u, v, uw, vh) {
1797
2050
  const d = this.instanceData;
1798
2051
  d[base + 0] = x;
1799
2052
  d[base + 1] = y;
@@ -1805,14 +2058,15 @@ var RenderSystem = class {
1805
2058
  d[base + 7] = offsetX;
1806
2059
  d[base + 8] = offsetY;
1807
2060
  d[base + 9] = flipX ? 1 : 0;
1808
- d[base + 10] = r;
1809
- d[base + 11] = g;
1810
- d[base + 12] = b;
1811
- d[base + 13] = a;
1812
- d[base + 14] = u;
1813
- d[base + 15] = v;
1814
- d[base + 16] = uw;
1815
- d[base + 17] = vh;
2061
+ d[base + 10] = flipY ? 1 : 0;
2062
+ d[base + 11] = r;
2063
+ d[base + 12] = g;
2064
+ d[base + 13] = b;
2065
+ d[base + 14] = a;
2066
+ d[base + 15] = u;
2067
+ d[base + 16] = v;
2068
+ d[base + 17] = uw;
2069
+ d[base + 18] = vh;
1816
2070
  }
1817
2071
  // ── Main update loop ───────────────────────────────────────────────────────
1818
2072
  update(world, dt) {
@@ -1872,9 +2126,47 @@ var RenderSystem = class {
1872
2126
  camY = cam.y;
1873
2127
  zoom = cam.zoom;
1874
2128
  }
2129
+ for (const id of world.query("Animator", "AnimationState")) {
2130
+ const animator = world.getComponent(id, "Animator");
2131
+ const anim = world.getComponent(id, "AnimationState");
2132
+ if (!animator.playing) continue;
2133
+ if (!animator.states[animator.currentState]) {
2134
+ animator.currentState = animator.initialState;
2135
+ animator._entered = false;
2136
+ }
2137
+ const stateDef = animator.states[animator.currentState];
2138
+ if (!stateDef) continue;
2139
+ if (!animator._entered) {
2140
+ anim.currentClip = stateDef.clip;
2141
+ animator._entered = true;
2142
+ stateDef.onEnter?.();
2143
+ }
2144
+ if (stateDef.transitions && stateDef.transitions.length > 0) {
2145
+ const sorted = [...stateDef.transitions].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
2146
+ for (const trans of sorted) {
2147
+ if (trans.exitTime != null && anim.frames.length > 0) {
2148
+ const progress = anim.currentIndex / anim.frames.length;
2149
+ if (progress < trans.exitTime) continue;
2150
+ }
2151
+ if (evaluateConditions(trans.when, animator.params)) {
2152
+ stateDef.onExit?.();
2153
+ animator.currentState = trans.to;
2154
+ animator._entered = false;
2155
+ break;
2156
+ }
2157
+ }
2158
+ }
2159
+ }
1875
2160
  for (const id of world.query("AnimationState", "Sprite")) {
1876
2161
  const anim = world.getComponent(id, "AnimationState");
1877
2162
  const sprite = world.getComponent(id, "Sprite");
2163
+ if (anim.clips && anim.currentClip && anim._resolvedClip !== anim.currentClip) {
2164
+ const clip = anim.clips[anim.currentClip];
2165
+ if (clip) {
2166
+ resolveClip(anim, clip);
2167
+ anim._resolvedClip = anim.currentClip;
2168
+ }
2169
+ }
1878
2170
  if (!anim.playing || anim.frames.length === 0) continue;
1879
2171
  anim.timer += dt;
1880
2172
  const frameDuration = 1 / anim.fps;
@@ -1882,8 +2174,24 @@ var RenderSystem = class {
1882
2174
  anim.timer -= frameDuration;
1883
2175
  anim.currentIndex++;
1884
2176
  if (anim.currentIndex >= anim.frames.length) {
1885
- anim.currentIndex = anim.loop ? 0 : anim.frames.length - 1;
2177
+ if (anim.loop) {
2178
+ anim.currentIndex = 0;
2179
+ } else {
2180
+ anim.currentIndex = anim.frames.length - 1;
2181
+ anim.playing = false;
2182
+ if (anim.onComplete && !anim._completed) {
2183
+ anim._completed = true;
2184
+ anim.onComplete();
2185
+ }
2186
+ if (anim.clips && anim.currentClip) {
2187
+ const currentClipDef = anim.clips[anim.currentClip];
2188
+ if (currentClipDef?.next && anim.clips[currentClipDef.next]) {
2189
+ anim.currentClip = currentClipDef.next;
2190
+ }
2191
+ }
2192
+ }
1886
2193
  }
2194
+ anim.frameEvents?.[anim.currentIndex]?.();
1887
2195
  }
1888
2196
  sprite.frameIndex = anim.frames[anim.currentIndex];
1889
2197
  }
@@ -1947,6 +2255,8 @@ var RenderSystem = class {
1947
2255
  renderables.sort((a, b) => {
1948
2256
  const sa = world.getComponent(a, "Sprite");
1949
2257
  const sb = world.getComponent(b, "Sprite");
2258
+ const ld = this.layers.getOrder(sa.layer) - this.layers.getOrder(sb.layer);
2259
+ if (ld !== 0) return ld;
1950
2260
  const zd = sa.zIndex - sb.zIndex;
1951
2261
  if (zd !== 0) return zd;
1952
2262
  const ka = getTextureKey(sa), kb = getTextureKey(sb);
@@ -1955,9 +2265,10 @@ var RenderSystem = class {
1955
2265
  let batchCount = 0;
1956
2266
  let batchKey = "";
1957
2267
  let batchSampling;
2268
+ let batchBlendMode = "normal";
1958
2269
  for (let i = 0; i <= renderables.length; i++) {
1959
2270
  if (i === renderables.length) {
1960
- this.flush(batchCount, batchKey, batchSampling);
2271
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
1961
2272
  break;
1962
2273
  }
1963
2274
  const id = renderables[i];
@@ -2006,12 +2317,14 @@ var RenderSystem = class {
2006
2317
  sprite.image = img;
2007
2318
  }
2008
2319
  const key = getTextureKey(sprite);
2009
- if (key !== batchKey && batchCount > 0 || batchCount >= MAX_INSTANCES) {
2010
- this.flush(batchCount, batchKey, batchSampling);
2320
+ const spriteBlend = sprite.blendMode ?? "normal";
2321
+ if ((key !== batchKey || spriteBlend !== batchBlendMode) && batchCount > 0 || batchCount >= MAX_INSTANCES) {
2322
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
2011
2323
  batchCount = 0;
2012
2324
  }
2013
2325
  batchKey = key;
2014
2326
  batchSampling = sprite.sampling;
2327
+ batchBlendMode = spriteBlend;
2015
2328
  const ss = world.getComponent(id, "SquashStretch");
2016
2329
  const scaleXMod = ss ? ss.currentScaleX : 1;
2017
2330
  const scaleYMod = ss ? ss.currentScaleY : 1;
@@ -2030,6 +2343,7 @@ var RenderSystem = class {
2030
2343
  sprite.offsetX,
2031
2344
  sprite.offsetY,
2032
2345
  sprite.flipX,
2346
+ sprite.flipY ?? false,
2033
2347
  r,
2034
2348
  g,
2035
2349
  b,
@@ -2053,10 +2367,11 @@ var RenderSystem = class {
2053
2367
  if (!text.visible) continue;
2054
2368
  const entry = this.getOrCreateTextTexture(text);
2055
2369
  if (!entry) continue;
2056
- this.flush(batchCount, batchKey, batchSampling);
2370
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
2057
2371
  batchCount = 0;
2058
2372
  batchKey = "";
2059
2373
  batchSampling = void 0;
2374
+ batchBlendMode = "normal";
2060
2375
  this.writeInstance(
2061
2376
  0,
2062
2377
  transform.x + text.offsetX,
@@ -2070,6 +2385,9 @@ var RenderSystem = class {
2070
2385
  0,
2071
2386
  0,
2072
2387
  false,
2388
+ // flipX
2389
+ false,
2390
+ // flipY
2073
2391
  1,
2074
2392
  1,
2075
2393
  1,
@@ -2093,15 +2411,33 @@ var RenderSystem = class {
2093
2411
  return p.life > 0;
2094
2412
  });
2095
2413
  if (pool.active && pool.particles.length < pool.maxParticles) {
2096
- pool.timer += dt;
2097
- const spawnCount = Math.floor(pool.timer * pool.rate);
2098
- pool.timer -= spawnCount / pool.rate;
2414
+ let spawnCount;
2415
+ if (pool.burstCount != null && pool.burstCount > 0) {
2416
+ spawnCount = pool.burstCount;
2417
+ pool.active = false;
2418
+ } else {
2419
+ pool.timer += dt;
2420
+ spawnCount = Math.floor(pool.timer * pool.rate);
2421
+ pool.timer -= spawnCount / pool.rate;
2422
+ }
2099
2423
  for (let i = 0; i < spawnCount && pool.particles.length < pool.maxParticles; i++) {
2100
2424
  const angle = pool.angle + (world.rng() - 0.5) * pool.spread;
2101
2425
  const speed = pool.speed * (0.5 + world.rng() * 0.5);
2426
+ let ox = 0;
2427
+ let oy = 0;
2428
+ const shape = pool.emitShape ?? "point";
2429
+ if (shape === "circle") {
2430
+ const r = (pool.emitRadius ?? 0) * Math.sqrt(world.rng());
2431
+ const a = world.rng() * Math.PI * 2;
2432
+ ox = Math.cos(a) * r;
2433
+ oy = Math.sin(a) * r;
2434
+ } else if (shape === "box") {
2435
+ ox = (world.rng() - 0.5) * (pool.emitWidth ?? 0);
2436
+ oy = (world.rng() - 0.5) * (pool.emitHeight ?? 0);
2437
+ }
2102
2438
  pool.particles.push({
2103
- x: t.x,
2104
- y: t.y,
2439
+ x: t.x + ox,
2440
+ y: t.y + oy,
2105
2441
  vx: Math.cos(angle) * speed,
2106
2442
  vy: Math.sin(angle) * speed,
2107
2443
  life: pool.particleLife,
@@ -2133,6 +2469,7 @@ var RenderSystem = class {
2133
2469
  0,
2134
2470
  0,
2135
2471
  false,
2472
+ false,
2136
2473
  r,
2137
2474
  g,
2138
2475
  b,
@@ -2173,6 +2510,7 @@ var RenderSystem = class {
2173
2510
  0,
2174
2511
  0,
2175
2512
  false,
2513
+ false,
2176
2514
  tr,
2177
2515
  tg,
2178
2516
  tb,
@@ -2210,6 +2548,7 @@ var RenderSystem = class {
2210
2548
  0,
2211
2549
  0,
2212
2550
  false,
2551
+ false,
2213
2552
  walkable ? 0 : 1,
2214
2553
  walkable ? 1 : 0,
2215
2554
  0,
@@ -2243,6 +2582,7 @@ var RenderSystem = class {
2243
2582
  0,
2244
2583
  0,
2245
2584
  false,
2585
+ false,
2246
2586
  1,
2247
2587
  0.3,
2248
2588
  0.3,
@@ -2266,6 +2606,110 @@ var RenderSystem = class {
2266
2606
  this.lastTimestamp = now;
2267
2607
  }
2268
2608
  };
2609
+ function resolveClip(anim, clip) {
2610
+ anim.frames = clip.frames;
2611
+ anim.fps = clip.fps ?? 12;
2612
+ anim.loop = clip.loop ?? true;
2613
+ anim.onComplete = clip.onComplete;
2614
+ anim.frameEvents = clip.frameEvents;
2615
+ anim.currentIndex = 0;
2616
+ anim.timer = 0;
2617
+ anim._completed = false;
2618
+ anim.playing = true;
2619
+ }
2620
+ function evaluateConditions(conditions, params) {
2621
+ for (const cond of conditions) {
2622
+ const val = params[cond.param];
2623
+ if (val === void 0) return false;
2624
+ switch (cond.op) {
2625
+ case "==":
2626
+ if (val !== cond.value) return false;
2627
+ break;
2628
+ case "!=":
2629
+ if (val === cond.value) return false;
2630
+ break;
2631
+ case ">":
2632
+ if (val <= cond.value) return false;
2633
+ break;
2634
+ case ">=":
2635
+ if (val < cond.value) return false;
2636
+ break;
2637
+ case "<":
2638
+ if (val >= cond.value) return false;
2639
+ break;
2640
+ case "<=":
2641
+ if (val > cond.value) return false;
2642
+ break;
2643
+ }
2644
+ }
2645
+ return true;
2646
+ }
2647
+
2648
+ // ../../packages/renderer/src/postProcess.ts
2649
+ function createPostProcessStack() {
2650
+ const effects = [];
2651
+ return {
2652
+ add(effect) {
2653
+ if (!effects.includes(effect)) {
2654
+ effects.push(effect);
2655
+ }
2656
+ },
2657
+ remove(effect) {
2658
+ const idx = effects.indexOf(effect);
2659
+ if (idx !== -1) effects.splice(idx, 1);
2660
+ },
2661
+ apply(ctx, width, height, dt) {
2662
+ for (const effect of effects) {
2663
+ ctx.save();
2664
+ effect(ctx, width, height, dt);
2665
+ ctx.restore();
2666
+ }
2667
+ },
2668
+ clear() {
2669
+ effects.length = 0;
2670
+ }
2671
+ };
2672
+ }
2673
+ function vignetteEffect(intensity = 0.4) {
2674
+ return (ctx, width, height) => {
2675
+ const cx = width / 2;
2676
+ const cy = height / 2;
2677
+ const radius = Math.sqrt(cx * cx + cy * cy);
2678
+ const gradient = ctx.createRadialGradient(cx, cy, radius * 0.3, cx, cy, radius);
2679
+ gradient.addColorStop(0, "rgba(0,0,0,0)");
2680
+ gradient.addColorStop(1, `rgba(0,0,0,${intensity})`);
2681
+ ctx.fillStyle = gradient;
2682
+ ctx.fillRect(0, 0, width, height);
2683
+ };
2684
+ }
2685
+ function scanlineEffect(gap = 3, opacity = 0.15) {
2686
+ return (ctx, width, height) => {
2687
+ ctx.fillStyle = `rgba(0,0,0,${opacity})`;
2688
+ for (let y = 0; y < height; y += gap) {
2689
+ ctx.fillRect(0, y, width, 1);
2690
+ }
2691
+ };
2692
+ }
2693
+ function chromaticAberrationEffect(offset = 2) {
2694
+ return (ctx, width, height) => {
2695
+ if (width === 0 || height === 0) return;
2696
+ const imageData = ctx.getImageData(0, 0, width, height);
2697
+ const { data } = imageData;
2698
+ const copy = new Uint8ClampedArray(data);
2699
+ for (let y = 0; y < height; y++) {
2700
+ for (let x = 0; x < width; x++) {
2701
+ const i = (y * width + x) * 4;
2702
+ const srcR = Math.min(x + offset, width - 1);
2703
+ const iR = (y * width + srcR) * 4;
2704
+ data[i] = copy[iR];
2705
+ const srcB = Math.max(x - offset, 0);
2706
+ const iB = (y * width + srcB) * 4;
2707
+ data[i + 2] = copy[iB + 2];
2708
+ }
2709
+ }
2710
+ ctx.putImageData(imageData, 0, 0);
2711
+ };
2712
+ }
2269
2713
 
2270
2714
  // ../../packages/renderer/src/canvas2d.ts
2271
2715
  var Canvas2DRenderer = class {
@@ -2310,6 +2754,9 @@ function createRigidBody(opts) {
2310
2754
  isKinematic: false,
2311
2755
  dropThrough: 0,
2312
2756
  ccd: false,
2757
+ angularVelocity: 0,
2758
+ angularDamping: 0,
2759
+ linearDamping: 0,
2313
2760
  ...opts
2314
2761
  };
2315
2762
  }
@@ -2644,6 +3091,15 @@ var PhysicsSystem = class {
2644
3091
  if (!rb.lockY) rb.vy += this.gravity * rb.gravityScale * dt;
2645
3092
  if (rb.lockX) rb.vx = 0;
2646
3093
  if (rb.lockY) rb.vy = 0;
3094
+ if (rb.linearDamping > 0) {
3095
+ rb.vx *= 1 - rb.linearDamping;
3096
+ rb.vy *= 1 - rb.linearDamping;
3097
+ }
3098
+ if (rb.angularVelocity !== 0) {
3099
+ const transform = world.getComponent(id, "Transform");
3100
+ transform.rotation += rb.angularVelocity * dt;
3101
+ if (rb.angularDamping > 0) rb.angularVelocity *= 1 - rb.angularDamping;
3102
+ }
2647
3103
  if (rb.dropThrough > 0) rb.dropThrough--;
2648
3104
  }
2649
3105
  const ccdPrev = /* @__PURE__ */ new Map();
@@ -2662,6 +3118,15 @@ var PhysicsSystem = class {
2662
3118
  if (!rb.lockY) rb.vy += this.gravity * rb.gravityScale * dt;
2663
3119
  if (rb.lockX) rb.vx = 0;
2664
3120
  if (rb.lockY) rb.vy = 0;
3121
+ if (rb.linearDamping > 0) {
3122
+ rb.vx *= 1 - rb.linearDamping;
3123
+ rb.vy *= 1 - rb.linearDamping;
3124
+ }
3125
+ if (rb.angularVelocity !== 0) {
3126
+ const transform = world.getComponent(id, "Transform");
3127
+ transform.rotation += rb.angularVelocity * dt;
3128
+ if (rb.angularDamping > 0) rb.angularVelocity *= 1 - rb.angularDamping;
3129
+ }
2665
3130
  if (rb.dropThrough > 0) rb.dropThrough--;
2666
3131
  }
2667
3132
  for (const id of dynamics) {
@@ -3543,10 +4008,48 @@ function useCircleStay(handler, opts) {
3543
4008
  useContactEvent("circleStay", handler, opts);
3544
4009
  }
3545
4010
 
4011
+ // ../context/src/useCollidingWith.ts
4012
+ import { useContext as useContext2, useEffect as useEffect2, useRef as useRef2 } from "react";
4013
+ function useCollidingWith() {
4014
+ const engine = useContext2(EngineContext);
4015
+ const entityId = useContext2(EntityContext);
4016
+ if (!engine) throw new Error("useCollidingWith hook must be used inside <Game>");
4017
+ if (entityId === null) throw new Error("useCollidingWith hook must be used inside <Entity>");
4018
+ const setRef = useRef2(/* @__PURE__ */ new Set());
4019
+ useEffect2(() => {
4020
+ const set = setRef.current;
4021
+ function handleEnter({ a, b }) {
4022
+ const isA = a === entityId;
4023
+ const isB = b === entityId;
4024
+ if (!isA && !isB) return;
4025
+ set.add(isA ? b : a);
4026
+ }
4027
+ function handleExit({ a, b }) {
4028
+ const isA = a === entityId;
4029
+ const isB = b === entityId;
4030
+ if (!isA && !isB) return;
4031
+ set.delete(isA ? b : a);
4032
+ }
4033
+ const unsubs = [
4034
+ engine.events.on("collisionEnter", handleEnter),
4035
+ engine.events.on("collisionExit", handleExit),
4036
+ engine.events.on("triggerEnter", handleEnter),
4037
+ engine.events.on("triggerExit", handleExit),
4038
+ engine.events.on("circleEnter", handleEnter),
4039
+ engine.events.on("circleExit", handleExit)
4040
+ ];
4041
+ return () => {
4042
+ unsubs.forEach((unsub) => unsub());
4043
+ set.clear();
4044
+ };
4045
+ }, [engine.events, entityId]);
4046
+ return setRef.current;
4047
+ }
4048
+
3546
4049
  // ../devtools/src/DevTools.tsx
3547
4050
  import React from "react";
3548
4051
  import { createPortal } from "react-dom";
3549
- import { useState, useEffect as useEffect2, useCallback, useRef as useRef2 } from "react";
4052
+ import { useState, useEffect as useEffect3, useCallback, useRef as useRef3 } from "react";
3550
4053
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3551
4054
  var MAX_DEVTOOLS_FRAMES = 600;
3552
4055
  var C = {
@@ -3710,8 +4213,8 @@ function DevToolsOverlay({ handle, loop, ecs, engine }) {
3710
4213
  const [contactLog, setContactLog] = useState([]);
3711
4214
  const [showNavGrid, setShowNavGrid] = useState(false);
3712
4215
  const [showContactFlash, setShowContactFlash] = useState(false);
3713
- const frameRef = useRef2(0);
3714
- useEffect2(() => {
4216
+ const frameRef = useRef3(0);
4217
+ useEffect3(() => {
3715
4218
  handle.onFrame = () => {
3716
4219
  frameRef.current++;
3717
4220
  if (!paused) {
@@ -3723,7 +4226,7 @@ function DevToolsOverlay({ handle, loop, ecs, engine }) {
3723
4226
  handle.onFrame = void 0;
3724
4227
  };
3725
4228
  }, [handle, paused]);
3726
- useEffect2(() => {
4229
+ useEffect3(() => {
3727
4230
  if (!engine) return;
3728
4231
  const events = engine.events;
3729
4232
  const types = ["triggerEnter", "triggerExit", "collisionEnter", "collisionExit", "circleEnter", "circleExit"];
@@ -4234,13 +4737,13 @@ function Game({
4234
4737
  className,
4235
4738
  children
4236
4739
  }) {
4237
- const canvasRef = useRef3(null);
4238
- const debugCanvasRef = useRef3(null);
4239
- const wrapperRef = useRef3(null);
4740
+ const canvasRef = useRef4(null);
4741
+ const debugCanvasRef = useRef4(null);
4742
+ const wrapperRef = useRef4(null);
4240
4743
  const [engine, setEngine] = useState2(null);
4241
4744
  const [assetsReady, setAssetsReady] = useState2(asyncAssets);
4242
- const devtoolsHandle = useRef3({ buffer: [] });
4243
- useEffect3(() => {
4745
+ const devtoolsHandle = useRef4({ buffer: [] });
4746
+ useEffect4(() => {
4244
4747
  const canvas = canvasRef.current;
4245
4748
  const ecs = new ECSWorld();
4246
4749
  if (deterministic) ecs.setDeterministicSeed(seed);
@@ -4283,6 +4786,7 @@ function Game({
4283
4786
  handle.onFrame?.();
4284
4787
  }
4285
4788
  }, deterministic ? { fixedDt: 1 / 60 } : void 0);
4789
+ const postProcessStack = createPostProcessStack();
4286
4790
  const state = {
4287
4791
  ecs,
4288
4792
  input,
@@ -4293,7 +4797,8 @@ function Game({
4293
4797
  loop,
4294
4798
  canvas,
4295
4799
  entityIds,
4296
- systemTimings
4800
+ systemTimings,
4801
+ postProcessStack
4297
4802
  };
4298
4803
  setEngine(state);
4299
4804
  if (plugins) {
@@ -4355,7 +4860,7 @@ function Game({
4355
4860
  }
4356
4861
  };
4357
4862
  }, []);
4358
- useEffect3(() => {
4863
+ useEffect4(() => {
4359
4864
  if (!engine) return;
4360
4865
  let cancelled = false;
4361
4866
  if (asyncAssets) {
@@ -4373,13 +4878,13 @@ function Game({
4373
4878
  cancelled = true;
4374
4879
  };
4375
4880
  }, [engine]);
4376
- useEffect3(() => {
4881
+ useEffect4(() => {
4377
4882
  if (!engine) return;
4378
4883
  const canvas = engine.canvas;
4379
4884
  if (canvas.width !== width) canvas.width = width;
4380
4885
  if (canvas.height !== height) canvas.height = height;
4381
4886
  }, [width, height, engine]);
4382
- useEffect3(() => {
4887
+ useEffect4(() => {
4383
4888
  engine?.physics.setGravity(gravity);
4384
4889
  }, [gravity, engine]);
4385
4890
  const canvasStyle = {
@@ -4460,15 +4965,15 @@ function Game({
4460
4965
  }
4461
4966
 
4462
4967
  // src/components/World.tsx
4463
- import { useEffect as useEffect4, useContext as useContext2 } from "react";
4968
+ import { useEffect as useEffect5, useContext as useContext3 } from "react";
4464
4969
  import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
4465
4970
  function World({ gravity, background = "#1a1a2e", children }) {
4466
- const engine = useContext2(EngineContext);
4467
- useEffect4(() => {
4971
+ const engine = useContext3(EngineContext);
4972
+ useEffect5(() => {
4468
4973
  if (!engine) return;
4469
4974
  if (gravity !== void 0) engine.physics.setGravity(gravity);
4470
4975
  }, [gravity, engine]);
4471
- useEffect4(() => {
4976
+ useEffect5(() => {
4472
4977
  if (!engine) return;
4473
4978
  const camId = engine.ecs.queryOne("Camera2D");
4474
4979
  if (camId !== void 0) {
@@ -4482,12 +4987,17 @@ function World({ gravity, background = "#1a1a2e", children }) {
4482
4987
  }
4483
4988
 
4484
4989
  // src/components/Entity.tsx
4485
- import { useEffect as useEffect5, useContext as useContext3, useState as useState3 } from "react";
4990
+ import { useEffect as useEffect6, useContext as useContext4, useState as useState3 } from "react";
4486
4991
  import { jsx as jsx4 } from "react/jsx-runtime";
4487
4992
  function Entity({ id, tags = [], children }) {
4488
- const engine = useContext3(EngineContext);
4993
+ const engine = useContext4(EngineContext);
4489
4994
  const [entityId, setEntityId] = useState3(null);
4490
- useEffect5(() => {
4995
+ if (process.env.NODE_ENV !== "production") {
4996
+ if (!engine) {
4997
+ console.warn("[Cubeforge] <Entity> must be inside a <World>. No EngineContext found.");
4998
+ }
4999
+ }
5000
+ useEffect6(() => {
4491
5001
  const eid = engine.ecs.createEntity();
4492
5002
  if (id) {
4493
5003
  if (engine.entityIds.has(id)) {
@@ -4507,15 +5017,15 @@ function Entity({ id, tags = [], children }) {
4507
5017
  }
4508
5018
 
4509
5019
  // src/components/Transform.tsx
4510
- import { useEffect as useEffect6, useContext as useContext4 } from "react";
5020
+ import { useEffect as useEffect7, useContext as useContext5 } from "react";
4511
5021
  function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
4512
- const engine = useContext4(EngineContext);
4513
- const entityId = useContext4(EntityContext);
4514
- useEffect6(() => {
5022
+ const engine = useContext5(EngineContext);
5023
+ const entityId = useContext5(EntityContext);
5024
+ useEffect7(() => {
4515
5025
  engine.ecs.addComponent(entityId, createTransform(x, y, rotation, scaleX, scaleY));
4516
5026
  return () => engine.ecs.removeComponent(entityId, "Transform");
4517
5027
  }, []);
4518
- useEffect6(() => {
5028
+ useEffect7(() => {
4519
5029
  const comp = engine.ecs.getComponent(entityId, "Transform");
4520
5030
  if (comp) {
4521
5031
  comp.x = x;
@@ -4529,7 +5039,7 @@ function Transform({ x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1 }) {
4529
5039
  }
4530
5040
 
4531
5041
  // src/components/Sprite.tsx
4532
- import { useEffect as useEffect7, useContext as useContext5 } from "react";
5042
+ import { useEffect as useEffect8, useContext as useContext6 } from "react";
4533
5043
  function Sprite({
4534
5044
  width,
4535
5045
  height,
@@ -4540,6 +5050,7 @@ function Sprite({
4540
5050
  zIndex = 0,
4541
5051
  visible = true,
4542
5052
  flipX = false,
5053
+ flipY = false,
4543
5054
  anchorX = 0.5,
4544
5055
  anchorY = 0.5,
4545
5056
  frameIndex = 0,
@@ -4552,12 +5063,22 @@ function Sprite({
4552
5063
  tileY,
4553
5064
  tileSizeX,
4554
5065
  tileSizeY,
4555
- sampling
5066
+ sampling,
5067
+ blendMode = "normal",
5068
+ layer = "default"
4556
5069
  }) {
4557
5070
  const resolvedFrameIndex = atlas && frame != null ? atlas[frame] ?? 0 : frameIndex;
4558
- const engine = useContext5(EngineContext);
4559
- const entityId = useContext5(EntityContext);
4560
- useEffect7(() => {
5071
+ const engine = useContext6(EngineContext);
5072
+ const entityId = useContext6(EntityContext);
5073
+ if (process.env.NODE_ENV !== "production") {
5074
+ if (entityId === null) {
5075
+ console.warn("[Cubeforge] <Sprite> must be inside an <Entity>. No EntityContext found.");
5076
+ }
5077
+ if ((frameWidth != null || frameHeight != null || frameColumns != null) && !src) {
5078
+ console.warn("[Cubeforge] <Sprite> has frameWidth/frameHeight/frameColumns but no `src`. Sprite-sheet props require an image source.");
5079
+ }
5080
+ }
5081
+ useEffect8(() => {
4561
5082
  const comp = createSprite({
4562
5083
  width,
4563
5084
  height,
@@ -4568,6 +5089,7 @@ function Sprite({
4568
5089
  zIndex,
4569
5090
  visible,
4570
5091
  flipX,
5092
+ flipY,
4571
5093
  anchorX,
4572
5094
  anchorY,
4573
5095
  frameIndex: resolvedFrameIndex,
@@ -4578,7 +5100,9 @@ function Sprite({
4578
5100
  tileY,
4579
5101
  tileSizeX,
4580
5102
  tileSizeY,
4581
- sampling
5103
+ sampling,
5104
+ blendMode,
5105
+ layer
4582
5106
  });
4583
5107
  engine.ecs.addComponent(entityId, comp);
4584
5108
  if (src) {
@@ -4592,20 +5116,23 @@ function Sprite({
4592
5116
  }
4593
5117
  return () => engine.ecs.removeComponent(entityId, "Sprite");
4594
5118
  }, []);
4595
- useEffect7(() => {
5119
+ useEffect8(() => {
4596
5120
  const comp = engine.ecs.getComponent(entityId, "Sprite");
4597
5121
  if (!comp) return;
4598
5122
  comp.color = color;
4599
5123
  comp.visible = visible;
4600
5124
  comp.flipX = flipX;
5125
+ comp.flipY = flipY;
4601
5126
  comp.zIndex = zIndex;
4602
5127
  comp.frameIndex = resolvedFrameIndex;
4603
- }, [color, visible, flipX, zIndex, resolvedFrameIndex, engine, entityId]);
5128
+ comp.blendMode = blendMode;
5129
+ comp.layer = layer;
5130
+ }, [color, visible, flipX, flipY, zIndex, resolvedFrameIndex, blendMode, layer, engine, entityId]);
4604
5131
  return null;
4605
5132
  }
4606
5133
 
4607
5134
  // src/components/Text.tsx
4608
- import { useEffect as useEffect8, useContext as useContext6 } from "react";
5135
+ import { useEffect as useEffect9, useContext as useContext7 } from "react";
4609
5136
  function Text({
4610
5137
  text,
4611
5138
  fontSize = 16,
@@ -4619,9 +5146,9 @@ function Text({
4619
5146
  offsetX = 0,
4620
5147
  offsetY = 0
4621
5148
  }) {
4622
- const engine = useContext6(EngineContext);
4623
- const entityId = useContext6(EntityContext);
4624
- useEffect8(() => {
5149
+ const engine = useContext7(EngineContext);
5150
+ const entityId = useContext7(EntityContext);
5151
+ useEffect9(() => {
4625
5152
  const comp = {
4626
5153
  type: "Text",
4627
5154
  text,
@@ -4639,7 +5166,7 @@ function Text({
4639
5166
  engine.ecs.addComponent(entityId, comp);
4640
5167
  return () => engine.ecs.removeComponent(entityId, "Text");
4641
5168
  }, []);
4642
- useEffect8(() => {
5169
+ useEffect9(() => {
4643
5170
  const comp = engine.ecs.getComponent(entityId, "Text");
4644
5171
  if (!comp) return;
4645
5172
  comp.text = text;
@@ -4651,7 +5178,7 @@ function Text({
4651
5178
  }
4652
5179
 
4653
5180
  // src/components/RigidBody.tsx
4654
- import { useEffect as useEffect9, useContext as useContext7 } from "react";
5181
+ import { useEffect as useEffect10, useContext as useContext8 } from "react";
4655
5182
  function RigidBody({
4656
5183
  mass = 1,
4657
5184
  gravityScale = 1,
@@ -4662,19 +5189,27 @@ function RigidBody({
4662
5189
  vy = 0,
4663
5190
  lockX = false,
4664
5191
  lockY = false,
4665
- ccd = false
5192
+ ccd = false,
5193
+ angularVelocity = 0,
5194
+ angularDamping = 0,
5195
+ linearDamping = 0
4666
5196
  }) {
4667
- const engine = useContext7(EngineContext);
4668
- const entityId = useContext7(EntityContext);
4669
- useEffect9(() => {
4670
- engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, ccd }));
5197
+ const engine = useContext8(EngineContext);
5198
+ const entityId = useContext8(EntityContext);
5199
+ if (process.env.NODE_ENV !== "production") {
5200
+ if (entityId === null) {
5201
+ console.warn("[Cubeforge] <RigidBody> must be inside an <Entity>. No EntityContext found.");
5202
+ }
5203
+ }
5204
+ useEffect10(() => {
5205
+ engine.ecs.addComponent(entityId, createRigidBody({ mass, gravityScale, isStatic, bounce, friction, vx, vy, lockX, lockY, ccd, angularVelocity, angularDamping, linearDamping }));
4671
5206
  return () => engine.ecs.removeComponent(entityId, "RigidBody");
4672
5207
  }, []);
4673
5208
  return null;
4674
5209
  }
4675
5210
 
4676
5211
  // src/components/BoxCollider.tsx
4677
- import { useEffect as useEffect10, useContext as useContext8 } from "react";
5212
+ import { useEffect as useEffect11, useContext as useContext9 } from "react";
4678
5213
  function BoxCollider({
4679
5214
  width,
4680
5215
  height,
@@ -4685,13 +5220,18 @@ function BoxCollider({
4685
5220
  mask = "*",
4686
5221
  oneWay = false
4687
5222
  }) {
4688
- const engine = useContext8(EngineContext);
4689
- const entityId = useContext8(EntityContext);
4690
- useEffect10(() => {
5223
+ const engine = useContext9(EngineContext);
5224
+ const entityId = useContext9(EntityContext);
5225
+ useEffect11(() => {
4691
5226
  engine.ecs.addComponent(entityId, createBoxCollider(width, height, { offsetX, offsetY, isTrigger, layer, mask, oneWay }));
4692
5227
  const checkId = setTimeout(() => {
4693
- if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
4694
- console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no Transform. Physics requires Transform.`);
5228
+ if (process.env.NODE_ENV !== "production") {
5229
+ if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
5230
+ console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no Transform. Physics requires Transform.`);
5231
+ }
5232
+ if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "RigidBody")) {
5233
+ console.warn(`[Cubeforge] BoxCollider on entity ${entityId} has no RigidBody. Add a <RigidBody> sibling for physics to work.`);
5234
+ }
4695
5235
  }
4696
5236
  }, 0);
4697
5237
  return () => {
@@ -4703,7 +5243,7 @@ function BoxCollider({
4703
5243
  }
4704
5244
 
4705
5245
  // src/components/CircleCollider.tsx
4706
- import { useEffect as useEffect11, useContext as useContext9 } from "react";
5246
+ import { useEffect as useEffect12, useContext as useContext10 } from "react";
4707
5247
  function CircleCollider({
4708
5248
  radius,
4709
5249
  offsetX = 0,
@@ -4712,9 +5252,9 @@ function CircleCollider({
4712
5252
  layer = "default",
4713
5253
  mask = "*"
4714
5254
  }) {
4715
- const engine = useContext9(EngineContext);
4716
- const entityId = useContext9(EntityContext);
4717
- useEffect11(() => {
5255
+ const engine = useContext10(EngineContext);
5256
+ const entityId = useContext10(EntityContext);
5257
+ useEffect12(() => {
4718
5258
  engine.ecs.addComponent(entityId, createCircleCollider(radius, { offsetX, offsetY, isTrigger, layer, mask }));
4719
5259
  return () => engine.ecs.removeComponent(entityId, "CircleCollider");
4720
5260
  }, []);
@@ -4722,7 +5262,7 @@ function CircleCollider({
4722
5262
  }
4723
5263
 
4724
5264
  // src/components/CapsuleCollider.tsx
4725
- import { useEffect as useEffect12, useContext as useContext10 } from "react";
5265
+ import { useEffect as useEffect13, useContext as useContext11 } from "react";
4726
5266
  function CapsuleCollider({
4727
5267
  width,
4728
5268
  height,
@@ -4732,9 +5272,9 @@ function CapsuleCollider({
4732
5272
  layer = "default",
4733
5273
  mask = "*"
4734
5274
  }) {
4735
- const engine = useContext10(EngineContext);
4736
- const entityId = useContext10(EntityContext);
4737
- useEffect12(() => {
5275
+ const engine = useContext11(EngineContext);
5276
+ const entityId = useContext11(EntityContext);
5277
+ useEffect13(() => {
4738
5278
  engine.ecs.addComponent(entityId, createCapsuleCollider(width, height, { offsetX, offsetY, isTrigger, layer, mask }));
4739
5279
  return () => engine.ecs.removeComponent(entityId, "CapsuleCollider");
4740
5280
  }, []);
@@ -4742,16 +5282,16 @@ function CapsuleCollider({
4742
5282
  }
4743
5283
 
4744
5284
  // src/components/CompoundCollider.tsx
4745
- import { useEffect as useEffect13, useContext as useContext11 } from "react";
5285
+ import { useEffect as useEffect14, useContext as useContext12 } from "react";
4746
5286
  function CompoundCollider({
4747
5287
  shapes,
4748
5288
  isTrigger = false,
4749
5289
  layer = "default",
4750
5290
  mask = "*"
4751
5291
  }) {
4752
- const engine = useContext11(EngineContext);
4753
- const entityId = useContext11(EntityContext);
4754
- useEffect13(() => {
5292
+ const engine = useContext12(EngineContext);
5293
+ const entityId = useContext12(EntityContext);
5294
+ useEffect14(() => {
4755
5295
  engine.ecs.addComponent(entityId, createCompoundCollider(shapes, { isTrigger, layer, mask }));
4756
5296
  const checkId = setTimeout(() => {
4757
5297
  if (engine.ecs.hasEntity(entityId) && !engine.ecs.hasComponent(entityId, "Transform")) {
@@ -4767,15 +5307,20 @@ function CompoundCollider({
4767
5307
  }
4768
5308
 
4769
5309
  // src/components/Script.tsx
4770
- import { useEffect as useEffect14, useContext as useContext12, useRef as useRef4 } from "react";
5310
+ import { useEffect as useEffect15, useContext as useContext13, useRef as useRef5 } from "react";
4771
5311
  function Script({ init, update }) {
4772
- const engine = useContext12(EngineContext);
4773
- const entityId = useContext12(EntityContext);
4774
- const initRef = useRef4(init);
5312
+ const engine = useContext13(EngineContext);
5313
+ const entityId = useContext13(EntityContext);
5314
+ if (process.env.NODE_ENV !== "production") {
5315
+ if (entityId === null) {
5316
+ console.warn("[Cubeforge] <Script> must be inside an <Entity>. No EntityContext found.");
5317
+ }
5318
+ }
5319
+ const initRef = useRef5(init);
4775
5320
  initRef.current = init;
4776
- const updateRef = useRef4(update);
5321
+ const updateRef = useRef5(update);
4777
5322
  updateRef.current = update;
4778
- useEffect14(() => {
5323
+ useEffect15(() => {
4779
5324
  if (initRef.current) {
4780
5325
  try {
4781
5326
  initRef.current(entityId, engine.ecs);
@@ -4791,7 +5336,7 @@ function Script({ init, update }) {
4791
5336
  }
4792
5337
 
4793
5338
  // src/components/Camera2D.tsx
4794
- import { useEffect as useEffect15, useContext as useContext13 } from "react";
5339
+ import { useEffect as useEffect16, useContext as useContext14 } from "react";
4795
5340
  function Camera2D({
4796
5341
  followEntity,
4797
5342
  x = 0,
@@ -4804,8 +5349,8 @@ function Camera2D({
4804
5349
  followOffsetX = 0,
4805
5350
  followOffsetY = 0
4806
5351
  }) {
4807
- const engine = useContext13(EngineContext);
4808
- useEffect15(() => {
5352
+ const engine = useContext14(EngineContext);
5353
+ useEffect16(() => {
4809
5354
  const entityId = engine.ecs.createEntity();
4810
5355
  engine.ecs.addComponent(entityId, createCamera2D({
4811
5356
  followEntityId: followEntity,
@@ -4821,7 +5366,7 @@ function Camera2D({
4821
5366
  }));
4822
5367
  return () => engine.ecs.destroyEntity(entityId);
4823
5368
  }, []);
4824
- useEffect15(() => {
5369
+ useEffect16(() => {
4825
5370
  const camId = engine.ecs.queryOne("Camera2D");
4826
5371
  if (camId === void 0) return;
4827
5372
  const cam = engine.ecs.getComponent(camId, "Camera2D");
@@ -4840,11 +5385,11 @@ function Camera2D({
4840
5385
  }
4841
5386
 
4842
5387
  // src/components/Animation.tsx
4843
- import { useEffect as useEffect16, useContext as useContext14 } from "react";
5388
+ import { useEffect as useEffect17, useContext as useContext15 } from "react";
4844
5389
  function Animation({ frames, fps = 12, loop = true, playing = true, onComplete, frameEvents }) {
4845
- const engine = useContext14(EngineContext);
4846
- const entityId = useContext14(EntityContext);
4847
- useEffect16(() => {
5390
+ const engine = useContext15(EngineContext);
5391
+ const entityId = useContext15(EntityContext);
5392
+ useEffect17(() => {
4848
5393
  const state = {
4849
5394
  type: "AnimationState",
4850
5395
  frames,
@@ -4862,7 +5407,7 @@ function Animation({ frames, fps = 12, loop = true, playing = true, onComplete,
4862
5407
  engine.ecs.removeComponent(entityId, "AnimationState");
4863
5408
  };
4864
5409
  }, []);
4865
- useEffect16(() => {
5410
+ useEffect17(() => {
4866
5411
  const anim = engine.ecs.getComponent(entityId, "AnimationState");
4867
5412
  if (!anim) return;
4868
5413
  const wasFramesChanged = anim.frames !== frames;
@@ -4881,12 +5426,162 @@ function Animation({ frames, fps = 12, loop = true, playing = true, onComplete,
4881
5426
  return null;
4882
5427
  }
4883
5428
 
5429
+ // src/components/AnimatedSprite.tsx
5430
+ import { useEffect as useEffect18, useContext as useContext16 } from "react";
5431
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
5432
+ function defineAnimations(clips) {
5433
+ return clips;
5434
+ }
5435
+ function AnimatedSprite(props) {
5436
+ const {
5437
+ width,
5438
+ height,
5439
+ src,
5440
+ color,
5441
+ offsetX,
5442
+ offsetY,
5443
+ zIndex,
5444
+ visible,
5445
+ flipX,
5446
+ flipY,
5447
+ anchorX,
5448
+ anchorY,
5449
+ frameWidth,
5450
+ frameHeight,
5451
+ frameColumns,
5452
+ atlas,
5453
+ frame,
5454
+ tileX,
5455
+ tileY,
5456
+ tileSizeX,
5457
+ tileSizeY,
5458
+ sampling,
5459
+ blendMode
5460
+ } = props;
5461
+ const spriteEl = /* @__PURE__ */ jsx5(
5462
+ Sprite,
5463
+ {
5464
+ width,
5465
+ height,
5466
+ src,
5467
+ color,
5468
+ offsetX,
5469
+ offsetY,
5470
+ zIndex,
5471
+ visible,
5472
+ flipX,
5473
+ flipY,
5474
+ anchorX,
5475
+ anchorY,
5476
+ frameWidth,
5477
+ frameHeight,
5478
+ frameColumns,
5479
+ atlas,
5480
+ frame,
5481
+ tileX,
5482
+ tileY,
5483
+ tileSizeX,
5484
+ tileSizeY,
5485
+ sampling,
5486
+ blendMode
5487
+ }
5488
+ );
5489
+ if (props.animations) {
5490
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
5491
+ spriteEl,
5492
+ /* @__PURE__ */ jsx5(
5493
+ MultiClipAnimation,
5494
+ {
5495
+ animations: props.animations,
5496
+ current: props.current
5497
+ }
5498
+ )
5499
+ ] });
5500
+ }
5501
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
5502
+ spriteEl,
5503
+ /* @__PURE__ */ jsx5(
5504
+ Animation,
5505
+ {
5506
+ frames: props.frames,
5507
+ fps: props.fps,
5508
+ loop: props.loop,
5509
+ playing: props.playing,
5510
+ onComplete: props.onComplete,
5511
+ frameEvents: props.frameEvents
5512
+ }
5513
+ )
5514
+ ] });
5515
+ }
5516
+ function MultiClipAnimation({ animations, current }) {
5517
+ const engine = useContext16(EngineContext);
5518
+ const entityId = useContext16(EntityContext);
5519
+ useEffect18(() => {
5520
+ const clip = animations[current] ?? Object.values(animations)[0];
5521
+ const state = {
5522
+ type: "AnimationState",
5523
+ clips: animations,
5524
+ currentClip: current,
5525
+ _resolvedClip: current,
5526
+ frames: clip.frames,
5527
+ fps: clip.fps ?? 12,
5528
+ loop: clip.loop ?? true,
5529
+ playing: true,
5530
+ currentIndex: 0,
5531
+ timer: 0,
5532
+ _completed: false,
5533
+ onComplete: clip.onComplete
5534
+ };
5535
+ engine.ecs.addComponent(entityId, state);
5536
+ return () => engine.ecs.removeComponent(entityId, "AnimationState");
5537
+ }, []);
5538
+ useEffect18(() => {
5539
+ const anim = engine.ecs.getComponent(entityId, "AnimationState");
5540
+ if (!anim) return;
5541
+ anim.clips = animations;
5542
+ anim.currentClip = current;
5543
+ }, [current, animations, engine, entityId]);
5544
+ return null;
5545
+ }
5546
+
5547
+ // src/components/Animator.tsx
5548
+ import { useEffect as useEffect19, useContext as useContext17 } from "react";
5549
+ function Animator({ initial, states, params = {}, playing = true }) {
5550
+ const engine = useContext17(EngineContext);
5551
+ const entityId = useContext17(EntityContext);
5552
+ useEffect19(() => {
5553
+ const comp = {
5554
+ type: "Animator",
5555
+ initialState: initial,
5556
+ currentState: initial,
5557
+ states,
5558
+ params: { ...params },
5559
+ playing,
5560
+ _entered: false
5561
+ };
5562
+ engine.ecs.addComponent(entityId, comp);
5563
+ return () => engine.ecs.removeComponent(entityId, "Animator");
5564
+ }, []);
5565
+ useEffect19(() => {
5566
+ const comp = engine.ecs.getComponent(entityId, "Animator");
5567
+ if (!comp) return;
5568
+ Object.assign(comp.params, params);
5569
+ comp.playing = playing;
5570
+ }, [params, playing, engine, entityId]);
5571
+ useEffect19(() => {
5572
+ const comp = engine.ecs.getComponent(entityId, "Animator");
5573
+ if (!comp) return;
5574
+ comp.states = states;
5575
+ }, [states, engine, entityId]);
5576
+ return null;
5577
+ }
5578
+
4884
5579
  // src/components/SquashStretch.tsx
4885
- import { useEffect as useEffect17, useContext as useContext15 } from "react";
5580
+ import { useEffect as useEffect20, useContext as useContext18 } from "react";
4886
5581
  function SquashStretch({ intensity = 0.2, recovery = 8 }) {
4887
- const engine = useContext15(EngineContext);
4888
- const entityId = useContext15(EntityContext);
4889
- useEffect17(() => {
5582
+ const engine = useContext18(EngineContext);
5583
+ const entityId = useContext18(EntityContext);
5584
+ useEffect20(() => {
4890
5585
  engine.ecs.addComponent(entityId, {
4891
5586
  type: "SquashStretch",
4892
5587
  intensity,
@@ -4900,7 +5595,7 @@ function SquashStretch({ intensity = 0.2, recovery = 8 }) {
4900
5595
  }
4901
5596
 
4902
5597
  // src/components/ParticleEmitter.tsx
4903
- import { useEffect as useEffect18, useContext as useContext16 } from "react";
5598
+ import { useEffect as useEffect21, useContext as useContext19 } from "react";
4904
5599
 
4905
5600
  // src/components/particlePresets.ts
4906
5601
  var PARTICLE_PRESETS = {
@@ -4973,7 +5668,12 @@ function ParticleEmitter({
4973
5668
  particleSize,
4974
5669
  color,
4975
5670
  gravity,
4976
- maxParticles
5671
+ maxParticles,
5672
+ burstCount,
5673
+ emitShape,
5674
+ emitRadius,
5675
+ emitWidth,
5676
+ emitHeight
4977
5677
  }) {
4978
5678
  const presetConfig = preset ? PARTICLE_PRESETS[preset] : {};
4979
5679
  const resolvedRate = rate ?? presetConfig.rate ?? 20;
@@ -4985,9 +5685,9 @@ function ParticleEmitter({
4985
5685
  const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
4986
5686
  const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
4987
5687
  const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
4988
- const engine = useContext16(EngineContext);
4989
- const entityId = useContext16(EntityContext);
4990
- useEffect18(() => {
5688
+ const engine = useContext19(EngineContext);
5689
+ const entityId = useContext19(EntityContext);
5690
+ useEffect21(() => {
4991
5691
  engine.ecs.addComponent(entityId, {
4992
5692
  type: "ParticlePool",
4993
5693
  particles: [],
@@ -5001,11 +5701,16 @@ function ParticleEmitter({
5001
5701
  particleLife: resolvedParticleLife,
5002
5702
  particleSize: resolvedParticleSize,
5003
5703
  color: resolvedColor,
5004
- gravity: resolvedGravity
5704
+ gravity: resolvedGravity,
5705
+ burstCount,
5706
+ emitShape,
5707
+ emitRadius,
5708
+ emitWidth,
5709
+ emitHeight
5005
5710
  });
5006
5711
  return () => engine.ecs.removeComponent(entityId, "ParticlePool");
5007
5712
  }, []);
5008
- useEffect18(() => {
5713
+ useEffect21(() => {
5009
5714
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
5010
5715
  if (!pool) return;
5011
5716
  pool.active = active;
@@ -5014,7 +5719,7 @@ function ParticleEmitter({
5014
5719
  }
5015
5720
 
5016
5721
  // src/components/VirtualJoystick.tsx
5017
- import { useRef as useRef5 } from "react";
5722
+ import { useRef as useRef6 } from "react";
5018
5723
 
5019
5724
  // src/hooks/useVirtualInput.ts
5020
5725
  var _axes = { x: 0, y: 0 };
@@ -5039,7 +5744,7 @@ function useVirtualInput() {
5039
5744
  }
5040
5745
 
5041
5746
  // src/components/VirtualJoystick.tsx
5042
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
5747
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
5043
5748
  function VirtualJoystick({
5044
5749
  size = 120,
5045
5750
  position = "left",
@@ -5048,10 +5753,10 @@ function VirtualJoystick({
5048
5753
  actionLabel = "A",
5049
5754
  actionName = "action"
5050
5755
  }) {
5051
- const baseRef = useRef5(null);
5052
- const stickRef = useRef5(null);
5053
- const activePtr = useRef5(null);
5054
- const baseCenterRef = useRef5({ x: 0, y: 0 });
5756
+ const baseRef = useRef6(null);
5757
+ const stickRef = useRef6(null);
5758
+ const activePtr = useRef6(null);
5759
+ const baseCenterRef = useRef6({ x: 0, y: 0 });
5055
5760
  const radius = size / 2 - 16;
5056
5761
  const applyStickPosition = (dx, dy) => {
5057
5762
  if (!stickRef.current) return;
@@ -5096,8 +5801,8 @@ function VirtualJoystick({
5096
5801
  };
5097
5802
  const cornerStyle = position === "left" ? { left: 24, bottom: 24 } : { right: 24, bottom: 24 };
5098
5803
  const actionCorner = position === "left" ? { right: 24, bottom: 24 } : { left: 24, bottom: 24 };
5099
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
5100
- /* @__PURE__ */ jsx5(
5804
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
5805
+ /* @__PURE__ */ jsx6(
5101
5806
  "div",
5102
5807
  {
5103
5808
  onPointerDown: handlePointerDown,
@@ -5118,7 +5823,7 @@ function VirtualJoystick({
5118
5823
  ...style
5119
5824
  },
5120
5825
  ref: baseRef,
5121
- children: /* @__PURE__ */ jsx5(
5826
+ children: /* @__PURE__ */ jsx6(
5122
5827
  "div",
5123
5828
  {
5124
5829
  ref: stickRef,
@@ -5138,7 +5843,7 @@ function VirtualJoystick({
5138
5843
  )
5139
5844
  }
5140
5845
  ),
5141
- actionButton && /* @__PURE__ */ jsx5(
5846
+ actionButton && /* @__PURE__ */ jsx6(
5142
5847
  "div",
5143
5848
  {
5144
5849
  onPointerDown: (e) => {
@@ -5173,7 +5878,7 @@ function VirtualJoystick({
5173
5878
  }
5174
5879
 
5175
5880
  // src/components/MovingPlatform.tsx
5176
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
5881
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
5177
5882
  var platformPhases = /* @__PURE__ */ new Map();
5178
5883
  function MovingPlatform({
5179
5884
  x1,
@@ -5185,12 +5890,12 @@ function MovingPlatform({
5185
5890
  duration = 3,
5186
5891
  color = "#37474f"
5187
5892
  }) {
5188
- return /* @__PURE__ */ jsxs4(Entity, { children: [
5189
- /* @__PURE__ */ jsx6(Transform, { x: x1, y: y1 }),
5190
- /* @__PURE__ */ jsx6(Sprite, { width, height, color, zIndex: 5 }),
5191
- /* @__PURE__ */ jsx6(RigidBody, { isStatic: true }),
5192
- /* @__PURE__ */ jsx6(BoxCollider, { width, height }),
5193
- /* @__PURE__ */ jsx6(
5893
+ return /* @__PURE__ */ jsxs5(Entity, { children: [
5894
+ /* @__PURE__ */ jsx7(Transform, { x: x1, y: y1 }),
5895
+ /* @__PURE__ */ jsx7(Sprite, { width, height, color, zIndex: 5 }),
5896
+ /* @__PURE__ */ jsx7(RigidBody, { isStatic: true }),
5897
+ /* @__PURE__ */ jsx7(BoxCollider, { width, height }),
5898
+ /* @__PURE__ */ jsx7(
5194
5899
  Script,
5195
5900
  {
5196
5901
  init: () => {
@@ -5212,7 +5917,7 @@ function MovingPlatform({
5212
5917
 
5213
5918
  // src/components/Checkpoint.tsx
5214
5919
  import { useState as useState4 } from "react";
5215
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
5920
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
5216
5921
  function CheckpointActivator({ onActivate }) {
5217
5922
  const [used, setUsed] = useState4(false);
5218
5923
  useTriggerEnter(() => {
@@ -5230,17 +5935,17 @@ function Checkpoint({
5230
5935
  color = "#ffd54f",
5231
5936
  onActivate
5232
5937
  }) {
5233
- return /* @__PURE__ */ jsxs5(Entity, { tags: ["checkpoint"], children: [
5234
- /* @__PURE__ */ jsx7(Transform, { x, y }),
5235
- /* @__PURE__ */ jsx7(Sprite, { width, height, color, zIndex: 5 }),
5236
- /* @__PURE__ */ jsx7(BoxCollider, { width, height, isTrigger: true }),
5237
- /* @__PURE__ */ jsx7(CheckpointActivator, { onActivate })
5938
+ return /* @__PURE__ */ jsxs6(Entity, { tags: ["checkpoint"], children: [
5939
+ /* @__PURE__ */ jsx8(Transform, { x, y }),
5940
+ /* @__PURE__ */ jsx8(Sprite, { width, height, color, zIndex: 5 }),
5941
+ /* @__PURE__ */ jsx8(BoxCollider, { width, height, isTrigger: true }),
5942
+ /* @__PURE__ */ jsx8(CheckpointActivator, { onActivate })
5238
5943
  ] });
5239
5944
  }
5240
5945
 
5241
5946
  // src/components/Tilemap.tsx
5242
- import { useEffect as useEffect19, useState as useState5, useContext as useContext17 } from "react";
5243
- import { Fragment as Fragment4, jsx as jsx8 } from "react/jsx-runtime";
5947
+ import { useEffect as useEffect22, useState as useState5, useContext as useContext20 } from "react";
5948
+ import { Fragment as Fragment5, jsx as jsx9 } from "react/jsx-runtime";
5244
5949
  var animatedTiles = /* @__PURE__ */ new Map();
5245
5950
  function getProperty(props, name) {
5246
5951
  return props?.find((p) => p.name === name)?.value;
@@ -5262,11 +5967,12 @@ function Tilemap({
5262
5967
  collisionLayer = "collision",
5263
5968
  triggerLayer: triggerLayerName = "triggers",
5264
5969
  onTileProperty,
5265
- navGrid
5970
+ navGrid,
5971
+ mergeColliders = true
5266
5972
  }) {
5267
- const engine = useContext17(EngineContext);
5973
+ const engine = useContext20(EngineContext);
5268
5974
  const [spawnedNodes, setSpawnedNodes] = useState5([]);
5269
- useEffect19(() => {
5975
+ useEffect22(() => {
5270
5976
  if (!engine) return;
5271
5977
  const createdEntities = [];
5272
5978
  async function load() {
@@ -5334,32 +6040,54 @@ function Tilemap({
5334
6040
  }
5335
6041
  }
5336
6042
  if (collision || trigger) {
5337
- for (let row = 0; row < mapData.height; row++) {
5338
- let col = 0;
5339
- while (col < mapData.width) {
5340
- const i = row * mapData.width + col;
5341
- const gid = layer.data[i];
5342
- if (gid === 0) {
5343
- col++;
5344
- continue;
6043
+ if (mergeColliders) {
6044
+ const solidGrid = [];
6045
+ for (let row = 0; row < mapData.height; row++) {
6046
+ solidGrid[row] = [];
6047
+ for (let col = 0; col < mapData.width; col++) {
6048
+ solidGrid[row][col] = layer.data[row * mapData.width + col] !== 0;
5345
6049
  }
5346
- let runLength = 1;
5347
- while (col + runLength < mapData.width && layer.data[row * mapData.width + col + runLength] !== 0) {
5348
- runLength++;
5349
- }
5350
- const runWidth = runLength * tilewidth;
5351
- const x = col * tilewidth + runWidth / 2;
5352
- const y = row * tileheight + tileheight / 2;
6050
+ }
6051
+ const merged = mergeTileColliders(solidGrid, tilewidth, tileheight, 0, 0);
6052
+ for (const rect of merged) {
5353
6053
  const eid = engine.ecs.createEntity();
5354
6054
  createdEntities.push(eid);
5355
- engine.ecs.addComponent(eid, createTransform(x, y));
6055
+ engine.ecs.addComponent(eid, createTransform(rect.x, rect.y));
5356
6056
  if (collision) {
5357
6057
  engine.ecs.addComponent(eid, createRigidBody({ isStatic: true }));
5358
- engine.ecs.addComponent(eid, createBoxCollider(runWidth, tileheight));
6058
+ engine.ecs.addComponent(eid, createBoxCollider(rect.width, rect.height));
5359
6059
  } else {
5360
- engine.ecs.addComponent(eid, createBoxCollider(runWidth, tileheight, { isTrigger: true }));
6060
+ engine.ecs.addComponent(eid, createBoxCollider(rect.width, rect.height, { isTrigger: true }));
6061
+ }
6062
+ }
6063
+ } else {
6064
+ for (let row = 0; row < mapData.height; row++) {
6065
+ let col = 0;
6066
+ while (col < mapData.width) {
6067
+ const i = row * mapData.width + col;
6068
+ const gid = layer.data[i];
6069
+ if (gid === 0) {
6070
+ col++;
6071
+ continue;
6072
+ }
6073
+ let runLength = 1;
6074
+ while (col + runLength < mapData.width && layer.data[row * mapData.width + col + runLength] !== 0) {
6075
+ runLength++;
6076
+ }
6077
+ const runWidth = runLength * tilewidth;
6078
+ const x = col * tilewidth + runWidth / 2;
6079
+ const y = row * tileheight + tileheight / 2;
6080
+ const eid = engine.ecs.createEntity();
6081
+ createdEntities.push(eid);
6082
+ engine.ecs.addComponent(eid, createTransform(x, y));
6083
+ if (collision) {
6084
+ engine.ecs.addComponent(eid, createRigidBody({ isStatic: true }));
6085
+ engine.ecs.addComponent(eid, createBoxCollider(runWidth, tileheight));
6086
+ } else {
6087
+ engine.ecs.addComponent(eid, createBoxCollider(runWidth, tileheight, { isTrigger: true }));
6088
+ }
6089
+ col += runLength;
5361
6090
  }
5362
- col += runLength;
5363
6091
  }
5364
6092
  }
5365
6093
  } else {
@@ -5453,12 +6181,12 @@ function Tilemap({
5453
6181
  };
5454
6182
  }, [src]);
5455
6183
  if (spawnedNodes.length === 0) return null;
5456
- return /* @__PURE__ */ jsx8(Fragment4, { children: spawnedNodes });
6184
+ return /* @__PURE__ */ jsx9(Fragment5, { children: spawnedNodes });
5457
6185
  }
5458
6186
 
5459
6187
  // src/components/ParallaxLayer.tsx
5460
- import { useEffect as useEffect20, useContext as useContext18 } from "react";
5461
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
6188
+ import { useEffect as useEffect23, useContext as useContext21 } from "react";
6189
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
5462
6190
  function ParallaxLayerInner({
5463
6191
  src,
5464
6192
  speedX,
@@ -5469,9 +6197,9 @@ function ParallaxLayerInner({
5469
6197
  offsetX,
5470
6198
  offsetY
5471
6199
  }) {
5472
- const engine = useContext18(EngineContext);
5473
- const entityId = useContext18(EntityContext);
5474
- useEffect20(() => {
6200
+ const engine = useContext21(EngineContext);
6201
+ const entityId = useContext21(EntityContext);
6202
+ useEffect23(() => {
5475
6203
  engine.ecs.addComponent(entityId, {
5476
6204
  type: "ParallaxLayer",
5477
6205
  src,
@@ -5487,7 +6215,7 @@ function ParallaxLayerInner({
5487
6215
  });
5488
6216
  return () => engine.ecs.removeComponent(entityId, "ParallaxLayer");
5489
6217
  }, []);
5490
- useEffect20(() => {
6218
+ useEffect23(() => {
5491
6219
  const layer = engine.ecs.getComponent(entityId, "ParallaxLayer");
5492
6220
  if (!layer) return;
5493
6221
  layer.src = src;
@@ -5511,9 +6239,9 @@ function ParallaxLayer({
5511
6239
  offsetX = 0,
5512
6240
  offsetY = 0
5513
6241
  }) {
5514
- return /* @__PURE__ */ jsxs6(Entity, { children: [
5515
- /* @__PURE__ */ jsx9(Transform, { x: 0, y: 0 }),
5516
- /* @__PURE__ */ jsx9(
6242
+ return /* @__PURE__ */ jsxs7(Entity, { children: [
6243
+ /* @__PURE__ */ jsx10(Transform, { x: 0, y: 0 }),
6244
+ /* @__PURE__ */ jsx10(
5517
6245
  ParallaxLayerInner,
5518
6246
  {
5519
6247
  src,
@@ -5530,10 +6258,10 @@ function ParallaxLayer({
5530
6258
  }
5531
6259
 
5532
6260
  // src/components/ScreenFlash.tsx
5533
- import { forwardRef, useImperativeHandle, useRef as useRef6 } from "react";
5534
- import { jsx as jsx10 } from "react/jsx-runtime";
6261
+ import { forwardRef, useImperativeHandle, useRef as useRef7 } from "react";
6262
+ import { jsx as jsx11 } from "react/jsx-runtime";
5535
6263
  var ScreenFlash = forwardRef((_, ref) => {
5536
- const divRef = useRef6(null);
6264
+ const divRef = useRef7(null);
5537
6265
  useImperativeHandle(ref, () => ({
5538
6266
  flash(color, duration) {
5539
6267
  const el = divRef.current;
@@ -5551,7 +6279,7 @@ var ScreenFlash = forwardRef((_, ref) => {
5551
6279
  });
5552
6280
  }
5553
6281
  }));
5554
- return /* @__PURE__ */ jsx10(
6282
+ return /* @__PURE__ */ jsx11(
5555
6283
  "div",
5556
6284
  {
5557
6285
  ref: divRef,
@@ -5569,8 +6297,8 @@ var ScreenFlash = forwardRef((_, ref) => {
5569
6297
  ScreenFlash.displayName = "ScreenFlash";
5570
6298
 
5571
6299
  // src/components/CameraZone.tsx
5572
- import { useEffect as useEffect21, useContext as useContext19, useRef as useRef7 } from "react";
5573
- import { Fragment as Fragment5, jsx as jsx11 } from "react/jsx-runtime";
6300
+ import { useEffect as useEffect24, useContext as useContext22, useRef as useRef8 } from "react";
6301
+ import { Fragment as Fragment6, jsx as jsx12 } from "react/jsx-runtime";
5574
6302
  function CameraZone({
5575
6303
  x,
5576
6304
  y,
@@ -5581,10 +6309,10 @@ function CameraZone({
5581
6309
  targetY,
5582
6310
  children
5583
6311
  }) {
5584
- const engine = useContext19(EngineContext);
5585
- const prevFollowRef = useRef7(void 0);
5586
- const activeRef = useRef7(false);
5587
- useEffect21(() => {
6312
+ const engine = useContext22(EngineContext);
6313
+ const prevFollowRef = useRef8(void 0);
6314
+ const activeRef = useRef8(false);
6315
+ useEffect24(() => {
5588
6316
  const eid = engine.ecs.createEntity();
5589
6317
  engine.ecs.addComponent(eid, createScript(() => {
5590
6318
  const cam = engine.ecs.queryOne("Camera2D");
@@ -5627,15 +6355,15 @@ function CameraZone({
5627
6355
  if (engine.ecs.hasEntity(eid)) engine.ecs.destroyEntity(eid);
5628
6356
  };
5629
6357
  }, [engine.ecs, x, y, width, height, watchTag, targetX, targetY]);
5630
- return /* @__PURE__ */ jsx11(Fragment5, { children: children ?? null });
6358
+ return /* @__PURE__ */ jsx12(Fragment6, { children: children ?? null });
5631
6359
  }
5632
6360
 
5633
6361
  // src/components/Trail.tsx
5634
- import { useEffect as useEffect22, useContext as useContext20 } from "react";
6362
+ import { useEffect as useEffect25, useContext as useContext23 } from "react";
5635
6363
  function Trail({ length = 20, color = "#ffffff", width = 3 }) {
5636
- const engine = useContext20(EngineContext);
5637
- const entityId = useContext20(EntityContext);
5638
- useEffect22(() => {
6364
+ const engine = useContext23(EngineContext);
6365
+ const entityId = useContext23(EntityContext);
6366
+ useEffect25(() => {
5639
6367
  engine.ecs.addComponent(entityId, createTrail({ length, color, width }));
5640
6368
  return () => engine.ecs.removeComponent(entityId, "Trail");
5641
6369
  }, []);
@@ -5643,7 +6371,7 @@ function Trail({ length = 20, color = "#ffffff", width = 3 }) {
5643
6371
  }
5644
6372
 
5645
6373
  // src/components/NineSlice.tsx
5646
- import { useEffect as useEffect23, useContext as useContext21 } from "react";
6374
+ import { useEffect as useEffect26, useContext as useContext24 } from "react";
5647
6375
  function NineSlice({
5648
6376
  src,
5649
6377
  width,
@@ -5654,9 +6382,9 @@ function NineSlice({
5654
6382
  borderLeft = 8,
5655
6383
  zIndex = 0
5656
6384
  }) {
5657
- const engine = useContext21(EngineContext);
5658
- const entityId = useContext21(EntityContext);
5659
- useEffect23(() => {
6385
+ const engine = useContext24(EngineContext);
6386
+ const entityId = useContext24(EntityContext);
6387
+ useEffect26(() => {
5660
6388
  engine.ecs.addComponent(
5661
6389
  entityId,
5662
6390
  createNineSlice(src, width, height, {
@@ -5673,14 +6401,14 @@ function NineSlice({
5673
6401
  }
5674
6402
 
5675
6403
  // src/components/AssetLoader.tsx
5676
- import { useEffect as useEffect25 } from "react";
6404
+ import { useEffect as useEffect28 } from "react";
5677
6405
 
5678
6406
  // src/hooks/usePreload.ts
5679
- import { useState as useState6, useEffect as useEffect24, useContext as useContext22 } from "react";
6407
+ import { useState as useState6, useEffect as useEffect27, useContext as useContext25 } from "react";
5680
6408
  function usePreload(assets) {
5681
- const engine = useContext22(EngineContext);
6409
+ const engine = useContext25(EngineContext);
5682
6410
  const [state, setState] = useState6({ progress: assets.length === 0 ? 1 : 0, loaded: assets.length === 0, error: null });
5683
- useEffect24(() => {
6411
+ useEffect27(() => {
5684
6412
  if (assets.length === 0) {
5685
6413
  setState({ progress: 1, loaded: true, error: null });
5686
6414
  return;
@@ -5708,22 +6436,22 @@ function usePreload(assets) {
5708
6436
  }
5709
6437
 
5710
6438
  // src/components/AssetLoader.tsx
5711
- import { Fragment as Fragment6, jsx as jsx12 } from "react/jsx-runtime";
6439
+ import { Fragment as Fragment7, jsx as jsx13 } from "react/jsx-runtime";
5712
6440
  function AssetLoader({ assets, fallback = null, onError, children }) {
5713
6441
  const { loaded, error } = usePreload(assets);
5714
- useEffect25(() => {
6442
+ useEffect28(() => {
5715
6443
  if (error && onError) onError(error);
5716
6444
  }, [error, onError]);
5717
6445
  if (!loaded) {
5718
- return /* @__PURE__ */ jsx12(Fragment6, { children: fallback });
6446
+ return /* @__PURE__ */ jsx13(Fragment7, { children: fallback });
5719
6447
  }
5720
- return /* @__PURE__ */ jsx12(Fragment6, { children });
6448
+ return /* @__PURE__ */ jsx13(Fragment7, { children });
5721
6449
  }
5722
6450
 
5723
6451
  // src/hooks/useGame.ts
5724
- import { useContext as useContext23 } from "react";
6452
+ import { useContext as useContext26 } from "react";
5725
6453
  function useGame() {
5726
- const engine = useContext23(EngineContext);
6454
+ const engine = useContext26(EngineContext);
5727
6455
  if (!engine) throw new Error("useGame must be used inside <Game>");
5728
6456
  return engine;
5729
6457
  }
@@ -5780,18 +6508,18 @@ function useSnapshot() {
5780
6508
  }
5781
6509
 
5782
6510
  // src/hooks/useEntity.ts
5783
- import { useContext as useContext24 } from "react";
6511
+ import { useContext as useContext27 } from "react";
5784
6512
  function useEntity() {
5785
- const id = useContext24(EntityContext);
6513
+ const id = useContext27(EntityContext);
5786
6514
  if (id === null) throw new Error("useEntity must be used inside <Entity>");
5787
6515
  return id;
5788
6516
  }
5789
6517
 
5790
6518
  // src/hooks/useDestroyEntity.ts
5791
- import { useCallback as useCallback2, useContext as useContext25 } from "react";
6519
+ import { useCallback as useCallback2, useContext as useContext28 } from "react";
5792
6520
  function useDestroyEntity() {
5793
- const engine = useContext25(EngineContext);
5794
- const entityId = useContext25(EntityContext);
6521
+ const engine = useContext28(EngineContext);
6522
+ const entityId = useContext28(EntityContext);
5795
6523
  if (!engine) throw new Error("useDestroyEntity must be used inside <Game>");
5796
6524
  if (entityId === null) throw new Error("useDestroyEntity must be used inside <Entity>");
5797
6525
  return useCallback2(() => {
@@ -5802,9 +6530,9 @@ function useDestroyEntity() {
5802
6530
  }
5803
6531
 
5804
6532
  // src/hooks/useInput.ts
5805
- import { useContext as useContext26 } from "react";
6533
+ import { useContext as useContext29 } from "react";
5806
6534
  function useInput() {
5807
- const engine = useContext26(EngineContext);
6535
+ const engine = useContext29(EngineContext);
5808
6536
  if (!engine) throw new Error("useInput must be used inside <Game>");
5809
6537
  return engine.input;
5810
6538
  }
@@ -5824,25 +6552,25 @@ function useInputMap(bindings) {
5824
6552
  }
5825
6553
 
5826
6554
  // src/hooks/useEvents.ts
5827
- import { useContext as useContext27, useEffect as useEffect26, useRef as useRef8 } from "react";
6555
+ import { useContext as useContext30, useEffect as useEffect29, useRef as useRef9 } from "react";
5828
6556
  function useEvents() {
5829
- const engine = useContext27(EngineContext);
6557
+ const engine = useContext30(EngineContext);
5830
6558
  if (!engine) throw new Error("useEvents must be used inside <Game>");
5831
6559
  return engine.events;
5832
6560
  }
5833
6561
  function useEvent(event, handler) {
5834
6562
  const events = useEvents();
5835
- const handlerRef = useRef8(handler);
6563
+ const handlerRef = useRef9(handler);
5836
6564
  handlerRef.current = handler;
5837
- useEffect26(() => {
6565
+ useEffect29(() => {
5838
6566
  return events.on(event, (data) => handlerRef.current(data));
5839
6567
  }, [events, event]);
5840
6568
  }
5841
6569
 
5842
6570
  // src/hooks/useCoordinates.ts
5843
- import { useCallback as useCallback3, useContext as useContext28 } from "react";
6571
+ import { useCallback as useCallback3, useContext as useContext31 } from "react";
5844
6572
  function useCoordinates() {
5845
- const engine = useContext28(EngineContext);
6573
+ const engine = useContext31(EngineContext);
5846
6574
  const worldToScreen = useCallback3((wx, wy) => {
5847
6575
  const canvas = engine.canvas;
5848
6576
  const camId = engine.ecs.queryOne("Camera2D");
@@ -5869,9 +6597,9 @@ function useCoordinates() {
5869
6597
  }
5870
6598
 
5871
6599
  // src/hooks/useInputContext.ts
5872
- import { useEffect as useEffect27, useMemo as useMemo4 } from "react";
6600
+ import { useEffect as useEffect30, useMemo as useMemo4 } from "react";
5873
6601
  function useInputContext(ctx) {
5874
- useEffect27(() => {
6602
+ useEffect30(() => {
5875
6603
  if (!ctx) return;
5876
6604
  globalInputContext.push(ctx);
5877
6605
  return () => globalInputContext.pop(ctx);
@@ -5910,12 +6638,12 @@ function useInputRecorder() {
5910
6638
  }
5911
6639
 
5912
6640
  // src/hooks/useGamepad.ts
5913
- import { useEffect as useEffect28, useRef as useRef9, useState as useState7 } from "react";
6641
+ import { useEffect as useEffect31, useRef as useRef10, useState as useState7 } from "react";
5914
6642
  var EMPTY_STATE = { connected: false, axes: [], buttons: [] };
5915
6643
  function useGamepad(playerIndex = 0) {
5916
6644
  const [state, setState] = useState7(EMPTY_STATE);
5917
- const rafRef = useRef9(0);
5918
- useEffect28(() => {
6645
+ const rafRef = useRef10(0);
6646
+ useEffect31(() => {
5919
6647
  const poll = () => {
5920
6648
  const gp = navigator.getGamepads()[playerIndex];
5921
6649
  if (gp) {
@@ -5936,9 +6664,9 @@ function useGamepad(playerIndex = 0) {
5936
6664
  }
5937
6665
 
5938
6666
  // src/hooks/usePause.ts
5939
- import { useContext as useContext29, useState as useState8, useCallback as useCallback4 } from "react";
6667
+ import { useContext as useContext32, useState as useState8, useCallback as useCallback4 } from "react";
5940
6668
  function usePause() {
5941
- const engine = useContext29(EngineContext);
6669
+ const engine = useContext32(EngineContext);
5942
6670
  const [paused, setPaused] = useState8(false);
5943
6671
  const pause = useCallback4(() => {
5944
6672
  engine.loop.pause();
@@ -5955,10 +6683,75 @@ function usePause() {
5955
6683
  return { paused, pause, resume, toggle };
5956
6684
  }
5957
6685
 
6686
+ // src/hooks/useProfiler.ts
6687
+ import { useContext as useContext33, useEffect as useEffect32, useRef as useRef11, useState as useState9 } from "react";
6688
+ var EMPTY = {
6689
+ fps: 0,
6690
+ frameTime: 0,
6691
+ entityCount: 0,
6692
+ systemTimings: /* @__PURE__ */ new Map()
6693
+ };
6694
+ function useProfiler() {
6695
+ const engine = useContext33(EngineContext);
6696
+ const [data, setData] = useState9(EMPTY);
6697
+ const frameTimesRef = useRef11([]);
6698
+ const lastUpdateRef = useRef11(0);
6699
+ const prevTimeRef = useRef11(0);
6700
+ useEffect32(() => {
6701
+ if (!engine) return;
6702
+ let rafId;
6703
+ const frameTimes = frameTimesRef.current;
6704
+ const sample = (now) => {
6705
+ if (prevTimeRef.current > 0) {
6706
+ frameTimes.push(now - prevTimeRef.current);
6707
+ }
6708
+ prevTimeRef.current = now;
6709
+ if (now - lastUpdateRef.current >= 500) {
6710
+ lastUpdateRef.current = now;
6711
+ let fps = 0;
6712
+ let avgFrameTime = 0;
6713
+ if (frameTimes.length > 0) {
6714
+ const sum = frameTimes.reduce((a, b) => a + b, 0);
6715
+ avgFrameTime = sum / frameTimes.length;
6716
+ fps = avgFrameTime > 0 ? 1e3 / avgFrameTime : 0;
6717
+ frameTimes.length = 0;
6718
+ }
6719
+ setData({
6720
+ fps: Math.round(fps * 10) / 10,
6721
+ frameTime: Math.round(avgFrameTime * 100) / 100,
6722
+ entityCount: engine.ecs.entityCount,
6723
+ systemTimings: new Map(engine.systemTimings)
6724
+ });
6725
+ }
6726
+ rafId = requestAnimationFrame(sample);
6727
+ };
6728
+ rafId = requestAnimationFrame(sample);
6729
+ return () => {
6730
+ cancelAnimationFrame(rafId);
6731
+ frameTimes.length = 0;
6732
+ prevTimeRef.current = 0;
6733
+ lastUpdateRef.current = 0;
6734
+ };
6735
+ }, [engine]);
6736
+ return data;
6737
+ }
6738
+
6739
+ // src/hooks/usePostProcess.ts
6740
+ import { useEffect as useEffect33 } from "react";
6741
+ function usePostProcess(effect) {
6742
+ const engine = useGame();
6743
+ useEffect33(() => {
6744
+ engine.postProcessStack.add(effect);
6745
+ return () => {
6746
+ engine.postProcessStack.remove(effect);
6747
+ };
6748
+ }, [engine, effect]);
6749
+ }
6750
+
5958
6751
  // ../gameplay/src/hooks/useAnimationController.ts
5959
- import { useState as useState9, useCallback as useCallback5 } from "react";
6752
+ import { useState as useState10, useCallback as useCallback5 } from "react";
5960
6753
  function useAnimationController(states, initial) {
5961
- const [stateName, setStateName] = useState9(initial);
6754
+ const [stateName, setStateName] = useState10(initial);
5962
6755
  const setState = useCallback5((next) => {
5963
6756
  setStateName((prev) => prev === next ? prev : next);
5964
6757
  }, []);
@@ -6007,19 +6800,19 @@ function useAISteering() {
6007
6800
  }
6008
6801
 
6009
6802
  // ../gameplay/src/hooks/useDamageZone.ts
6010
- import { useContext as useContext30 } from "react";
6803
+ import { useContext as useContext34 } from "react";
6011
6804
  function useDamageZone(damage, opts = {}) {
6012
- const engine = useContext30(EngineContext);
6805
+ const engine = useContext34(EngineContext);
6013
6806
  useTriggerEnter((other) => {
6014
6807
  engine.events.emit(`damage:${other}`, { amount: damage });
6015
6808
  }, { tag: opts.tag, layer: opts.layer });
6016
6809
  }
6017
6810
 
6018
6811
  // ../gameplay/src/hooks/useDropThrough.ts
6019
- import { useContext as useContext31, useCallback as useCallback7 } from "react";
6812
+ import { useContext as useContext35, useCallback as useCallback7 } from "react";
6020
6813
  function useDropThrough(frames = 8) {
6021
- const engine = useContext31(EngineContext);
6022
- const entityId = useContext31(EntityContext);
6814
+ const engine = useContext35(EngineContext);
6815
+ const entityId = useContext35(EntityContext);
6023
6816
  const dropThrough = useCallback7(() => {
6024
6817
  const rb = engine.ecs.getComponent(entityId, "RigidBody");
6025
6818
  if (rb) rb.dropThrough = frames;
@@ -6028,14 +6821,14 @@ function useDropThrough(frames = 8) {
6028
6821
  }
6029
6822
 
6030
6823
  // ../gameplay/src/hooks/useGameStateMachine.ts
6031
- import { useState as useState10, useRef as useRef10, useCallback as useCallback8, useEffect as useEffect29, useContext as useContext32 } from "react";
6824
+ import { useState as useState11, useRef as useRef12, useCallback as useCallback8, useEffect as useEffect34, useContext as useContext36 } from "react";
6032
6825
  function useGameStateMachine(states, initial) {
6033
- const engine = useContext32(EngineContext);
6034
- const [state, setState] = useState10(initial);
6035
- const stateRef = useRef10(initial);
6036
- const statesRef = useRef10(states);
6826
+ const engine = useContext36(EngineContext);
6827
+ const [state, setState] = useState11(initial);
6828
+ const stateRef = useRef12(initial);
6829
+ const statesRef = useRef12(states);
6037
6830
  statesRef.current = states;
6038
- useEffect29(() => {
6831
+ useEffect34(() => {
6039
6832
  statesRef.current[initial]?.onEnter?.();
6040
6833
  }, []);
6041
6834
  const transition = useCallback8((to) => {
@@ -6046,7 +6839,7 @@ function useGameStateMachine(states, initial) {
6046
6839
  setState(to);
6047
6840
  statesRef.current[to]?.onEnter?.();
6048
6841
  }, []);
6049
- useEffect29(() => {
6842
+ useEffect34(() => {
6050
6843
  const eid = engine.ecs.createEntity();
6051
6844
  engine.ecs.addComponent(eid, createScript((_id, _world, _input, dt) => {
6052
6845
  statesRef.current[stateRef.current]?.onUpdate?.(dt);
@@ -6059,22 +6852,22 @@ function useGameStateMachine(states, initial) {
6059
6852
  }
6060
6853
 
6061
6854
  // ../gameplay/src/hooks/useHealth.ts
6062
- import { useRef as useRef11, useEffect as useEffect30, useContext as useContext33, useCallback as useCallback9 } from "react";
6855
+ import { useRef as useRef13, useEffect as useEffect35, useContext as useContext37, useCallback as useCallback9 } from "react";
6063
6856
  function useHealth(maxHp, opts = {}) {
6064
- const engine = useContext33(EngineContext);
6065
- const entityId = useContext33(EntityContext);
6066
- const hpRef = useRef11(maxHp);
6067
- const invincibleRef = useRef11(false);
6857
+ const engine = useContext37(EngineContext);
6858
+ const entityId = useContext37(EntityContext);
6859
+ const hpRef = useRef13(maxHp);
6860
+ const invincibleRef = useRef13(false);
6068
6861
  const iFrameDuration = opts.iFrames ?? 1;
6069
- const onDeathRef = useRef11(opts.onDeath);
6070
- const onDamageRef = useRef11(opts.onDamage);
6071
- useEffect30(() => {
6862
+ const onDeathRef = useRef13(opts.onDeath);
6863
+ const onDamageRef = useRef13(opts.onDamage);
6864
+ useEffect35(() => {
6072
6865
  onDeathRef.current = opts.onDeath;
6073
6866
  });
6074
- useEffect30(() => {
6867
+ useEffect35(() => {
6075
6868
  onDamageRef.current = opts.onDamage;
6076
6869
  });
6077
- const timerRef = useRef11(
6870
+ const timerRef = useRef13(
6078
6871
  createTimer(iFrameDuration, () => {
6079
6872
  invincibleRef.current = false;
6080
6873
  })
@@ -6089,11 +6882,11 @@ function useHealth(maxHp, opts = {}) {
6089
6882
  }
6090
6883
  if (hpRef.current <= 0) onDeathRef.current?.();
6091
6884
  }, [iFrameDuration]);
6092
- const takeDamageRef = useRef11(takeDamage);
6093
- useEffect30(() => {
6885
+ const takeDamageRef = useRef13(takeDamage);
6886
+ useEffect35(() => {
6094
6887
  takeDamageRef.current = takeDamage;
6095
6888
  }, [takeDamage]);
6096
- useEffect30(() => {
6889
+ useEffect35(() => {
6097
6890
  return engine.events.on(`damage:${entityId}`, ({ amount }) => {
6098
6891
  takeDamageRef.current(amount);
6099
6892
  });
@@ -6128,10 +6921,10 @@ function useHealth(maxHp, opts = {}) {
6128
6921
  }
6129
6922
 
6130
6923
  // ../gameplay/src/hooks/useKinematicBody.ts
6131
- import { useContext as useContext34, useCallback as useCallback10 } from "react";
6924
+ import { useContext as useContext38, useCallback as useCallback10 } from "react";
6132
6925
  function useKinematicBody() {
6133
- const engine = useContext34(EngineContext);
6134
- const entityId = useContext34(EntityContext);
6926
+ const engine = useContext38(EngineContext);
6927
+ const entityId = useContext38(EntityContext);
6135
6928
  const moveAndCollide = useCallback10((dx, dy) => {
6136
6929
  const transform = engine.ecs.getComponent(entityId, "Transform");
6137
6930
  if (!transform) return { dx: 0, dy: 0 };
@@ -6176,11 +6969,11 @@ function useKinematicBody() {
6176
6969
  }
6177
6970
 
6178
6971
  // ../gameplay/src/hooks/useLevelTransition.ts
6179
- import { useState as useState11, useRef as useRef12, useCallback as useCallback11 } from "react";
6972
+ import { useState as useState12, useRef as useRef14, useCallback as useCallback11 } from "react";
6180
6973
  function useLevelTransition(initial) {
6181
- const [currentLevel, setCurrentLevel] = useState11(initial);
6182
- const [isTransitioning, setIsTransitioning] = useState11(false);
6183
- const overlayRef = useRef12(null);
6974
+ const [currentLevel, setCurrentLevel] = useState12(initial);
6975
+ const [isTransitioning, setIsTransitioning] = useState12(false);
6976
+ const overlayRef = useRef14(null);
6184
6977
  const transitionTo = useCallback11((level, opts = {}) => {
6185
6978
  const { duration = 0.4, type = "fade" } = opts;
6186
6979
  if (type === "instant") {
@@ -6218,13 +7011,13 @@ function useLevelTransition(initial) {
6218
7011
  }
6219
7012
 
6220
7013
  // ../gameplay/src/hooks/usePlatformerController.ts
6221
- import { useContext as useContext35, useEffect as useEffect31 } from "react";
7014
+ import { useContext as useContext39, useEffect as useEffect36 } from "react";
6222
7015
  function normalizeKeys(val, defaults) {
6223
7016
  if (!val) return defaults;
6224
7017
  return Array.isArray(val) ? val : [val];
6225
7018
  }
6226
7019
  function usePlatformerController(entityId, opts = {}) {
6227
- const engine = useContext35(EngineContext);
7020
+ const engine = useContext39(EngineContext);
6228
7021
  const {
6229
7022
  speed = 200,
6230
7023
  jumpForce = -500,
@@ -6237,7 +7030,7 @@ function usePlatformerController(entityId, opts = {}) {
6237
7030
  const leftKeys = normalizeKeys(bindings?.left, ["ArrowLeft", "KeyA", "a"]);
6238
7031
  const rightKeys = normalizeKeys(bindings?.right, ["ArrowRight", "KeyD", "d"]);
6239
7032
  const jumpKeys = normalizeKeys(bindings?.jump, ["Space", "ArrowUp", "KeyW", "w"]);
6240
- useEffect31(() => {
7033
+ useEffect36(() => {
6241
7034
  const state = {
6242
7035
  coyoteTimer: 0,
6243
7036
  jumpBuffer: 0,
@@ -6301,11 +7094,11 @@ function usePathfinding() {
6301
7094
  }
6302
7095
 
6303
7096
  // ../gameplay/src/hooks/usePersistedBindings.ts
6304
- import { useState as useState12, useCallback as useCallback13, useMemo as useMemo8, useContext as useContext36 } from "react";
7097
+ import { useState as useState13, useCallback as useCallback13, useMemo as useMemo8, useContext as useContext40 } from "react";
6305
7098
  function usePersistedBindings(storageKey, defaults) {
6306
- const engine = useContext36(EngineContext);
7099
+ const engine = useContext40(EngineContext);
6307
7100
  const input = engine.input;
6308
- const [bindings, setBindings] = useState12(() => {
7101
+ const [bindings, setBindings] = useState13(() => {
6309
7102
  try {
6310
7103
  const stored = localStorage.getItem(storageKey);
6311
7104
  if (stored) return { ...defaults, ...JSON.parse(stored) };
@@ -6349,9 +7142,9 @@ function usePersistedBindings(storageKey, defaults) {
6349
7142
  }
6350
7143
 
6351
7144
  // ../gameplay/src/hooks/useRestart.ts
6352
- import { useState as useState13, useCallback as useCallback14 } from "react";
7145
+ import { useState as useState14, useCallback as useCallback14 } from "react";
6353
7146
  function useRestart() {
6354
- const [restartKey, setRestartKey] = useState13(0);
7147
+ const [restartKey, setRestartKey] = useState14(0);
6355
7148
  const restart = useCallback14(() => {
6356
7149
  setRestartKey((k) => k + 1);
6357
7150
  }, []);
@@ -6359,10 +7152,10 @@ function useRestart() {
6359
7152
  }
6360
7153
 
6361
7154
  // ../gameplay/src/hooks/useSave.ts
6362
- import { useCallback as useCallback15, useRef as useRef13 } from "react";
7155
+ import { useCallback as useCallback15, useRef as useRef15 } from "react";
6363
7156
  function useSave(key, defaultValue, opts = {}) {
6364
7157
  const version = opts.version ?? 1;
6365
- const dataRef = useRef13(defaultValue);
7158
+ const dataRef = useRef15(defaultValue);
6366
7159
  const save = useCallback15((value) => {
6367
7160
  dataRef.current = value;
6368
7161
  const slot = { version, data: value };
@@ -6408,11 +7201,11 @@ function useSave(key, defaultValue, opts = {}) {
6408
7201
  }
6409
7202
 
6410
7203
  // ../gameplay/src/hooks/useTopDownMovement.ts
6411
- import { useContext as useContext37, useEffect as useEffect32 } from "react";
7204
+ import { useContext as useContext41, useEffect as useEffect37 } from "react";
6412
7205
  function useTopDownMovement(entityId, opts = {}) {
6413
- const engine = useContext37(EngineContext);
7206
+ const engine = useContext41(EngineContext);
6414
7207
  const { speed = 200, normalizeDiagonal = true } = opts;
6415
- useEffect32(() => {
7208
+ useEffect37(() => {
6416
7209
  const updateFn = (id, world, input) => {
6417
7210
  if (!world.hasEntity(id)) return;
6418
7211
  const rb = world.getComponent(id, "RigidBody");
@@ -6437,11 +7230,11 @@ function useTopDownMovement(entityId, opts = {}) {
6437
7230
  }
6438
7231
 
6439
7232
  // ../gameplay/src/hooks/useDialogue.ts
6440
- import { useState as useState14, useCallback as useCallback16, useRef as useRef14 } from "react";
7233
+ import { useState as useState15, useCallback as useCallback16, useRef as useRef16 } from "react";
6441
7234
  function useDialogue() {
6442
- const [active, setActive] = useState14(false);
6443
- const [currentId, setCurrentId] = useState14(null);
6444
- const scriptRef = useRef14(null);
7235
+ const [active, setActive] = useState15(false);
7236
+ const [currentId, setCurrentId] = useState15(null);
7237
+ const scriptRef = useRef16(null);
6445
7238
  const start = useCallback16((script, startId) => {
6446
7239
  scriptRef.current = script;
6447
7240
  const id = startId ?? Object.keys(script)[0];
@@ -6485,16 +7278,16 @@ function useDialogue() {
6485
7278
  }
6486
7279
 
6487
7280
  // ../gameplay/src/hooks/useCutscene.ts
6488
- import { useState as useState15, useCallback as useCallback17, useRef as useRef15, useEffect as useEffect33, useContext as useContext38 } from "react";
7281
+ import { useState as useState16, useCallback as useCallback17, useRef as useRef17, useEffect as useEffect38, useContext as useContext42 } from "react";
6489
7282
  function useCutscene() {
6490
- const engine = useContext38(EngineContext);
6491
- const [playing, setPlaying] = useState15(false);
6492
- const [stepIndex, setStepIndex] = useState15(0);
6493
- const stepsRef = useRef15([]);
6494
- const timerRef = useRef15(0);
6495
- const idxRef = useRef15(0);
6496
- const playingRef = useRef15(false);
6497
- const entityRef = useRef15(null);
7283
+ const engine = useContext42(EngineContext);
7284
+ const [playing, setPlaying] = useState16(false);
7285
+ const [stepIndex, setStepIndex] = useState16(0);
7286
+ const stepsRef = useRef17([]);
7287
+ const timerRef = useRef17(0);
7288
+ const idxRef = useRef17(0);
7289
+ const playingRef = useRef17(false);
7290
+ const entityRef = useRef17(null);
6498
7291
  const finish = useCallback17(() => {
6499
7292
  playingRef.current = false;
6500
7293
  setPlaying(false);
@@ -6572,7 +7365,7 @@ function useCutscene() {
6572
7365
  }
6573
7366
  finish();
6574
7367
  }, [finish]);
6575
- useEffect33(() => {
7368
+ useEffect38(() => {
6576
7369
  return () => {
6577
7370
  if (entityRef.current !== null && engine.ecs.hasEntity(entityRef.current)) {
6578
7371
  engine.ecs.destroyEntity(entityRef.current);
@@ -6618,8 +7411,112 @@ function useGameStore(key, initialState) {
6618
7411
  return [state, setState];
6619
7412
  }
6620
7413
 
7414
+ // ../gameplay/src/hooks/useTween.ts
7415
+ import { useRef as useRef18, useCallback as useCallback19, useEffect as useEffect39 } from "react";
7416
+ function useTween(opts) {
7417
+ const rafRef = useRef18(null);
7418
+ const startTimeRef = useRef18(0);
7419
+ const runningRef = useRef18(false);
7420
+ const optsRef = useRef18(opts);
7421
+ optsRef.current = opts;
7422
+ const stop = useCallback19(() => {
7423
+ if (rafRef.current !== null) {
7424
+ cancelAnimationFrame(rafRef.current);
7425
+ rafRef.current = null;
7426
+ }
7427
+ runningRef.current = false;
7428
+ }, []);
7429
+ const start = useCallback19(() => {
7430
+ stop();
7431
+ runningRef.current = true;
7432
+ startTimeRef.current = performance.now();
7433
+ const tick = (now) => {
7434
+ if (!runningRef.current) return;
7435
+ const { from, to, duration, ease, onUpdate, onComplete } = optsRef.current;
7436
+ const easeFn = ease ?? Ease.linear;
7437
+ const elapsed = (now - startTimeRef.current) / 1e3;
7438
+ const t = duration > 0 ? Math.min(elapsed / duration, 1) : 1;
7439
+ const value = from + (to - from) * easeFn(t);
7440
+ onUpdate(value);
7441
+ if (t >= 1) {
7442
+ runningRef.current = false;
7443
+ rafRef.current = null;
7444
+ onComplete?.();
7445
+ } else {
7446
+ rafRef.current = requestAnimationFrame(tick);
7447
+ }
7448
+ };
7449
+ rafRef.current = requestAnimationFrame(tick);
7450
+ }, [stop]);
7451
+ useEffect39(() => {
7452
+ if (opts.autoStart) {
7453
+ start();
7454
+ }
7455
+ }, []);
7456
+ useEffect39(() => {
7457
+ return () => {
7458
+ if (rafRef.current !== null) {
7459
+ cancelAnimationFrame(rafRef.current);
7460
+ rafRef.current = null;
7461
+ }
7462
+ runningRef.current = false;
7463
+ };
7464
+ }, []);
7465
+ return {
7466
+ start,
7467
+ stop,
7468
+ get isRunning() {
7469
+ return runningRef.current;
7470
+ }
7471
+ };
7472
+ }
7473
+
7474
+ // ../gameplay/src/hooks/useObjectPool.ts
7475
+ import { useRef as useRef19, useMemo as useMemo9, useEffect as useEffect40 } from "react";
7476
+ function useObjectPool(factory, reset, initialSize) {
7477
+ const poolRef = useRef19([]);
7478
+ const activeRef = useRef19(0);
7479
+ const factoryRef = useRef19(factory);
7480
+ factoryRef.current = factory;
7481
+ const resetRef = useRef19(reset);
7482
+ resetRef.current = reset;
7483
+ useEffect40(() => {
7484
+ if (initialSize != null && initialSize > 0) {
7485
+ const pool = poolRef.current;
7486
+ for (let i = 0; i < initialSize; i++) {
7487
+ pool.push(factoryRef.current());
7488
+ }
7489
+ }
7490
+ }, []);
7491
+ return useMemo9(() => ({
7492
+ acquire() {
7493
+ activeRef.current++;
7494
+ if (poolRef.current.length > 0) {
7495
+ return poolRef.current.pop();
7496
+ }
7497
+ return factoryRef.current();
7498
+ },
7499
+ release(obj) {
7500
+ resetRef.current(obj);
7501
+ activeRef.current = Math.max(0, activeRef.current - 1);
7502
+ poolRef.current.push(obj);
7503
+ },
7504
+ prewarm(count) {
7505
+ for (let i = 0; i < count; i++) {
7506
+ poolRef.current.push(factoryRef.current());
7507
+ }
7508
+ },
7509
+ get activeCount() {
7510
+ return activeRef.current;
7511
+ },
7512
+ get poolSize() {
7513
+ return poolRef.current.length;
7514
+ }
7515
+ }), []);
7516
+ }
7517
+
6621
7518
  // ../../packages/audio/src/useSound.ts
6622
- import { useEffect as useEffect34, useRef as useRef16 } from "react";
7519
+ import { useEffect as useEffect41, useRef as useRef20 } from "react";
6623
7520
  var _audioCtx = null;
6624
7521
  function getAudioCtx() {
6625
7522
  if (!_audioCtx) _audioCtx = new AudioContext();
@@ -6686,13 +7583,13 @@ async function loadBuffer(src) {
6686
7583
  return buf;
6687
7584
  }
6688
7585
  function useSound(src, opts = {}) {
6689
- const bufferRef = useRef16(null);
6690
- const sourceRef = useRef16(null);
6691
- const gainRef = useRef16(null);
6692
- const volRef = useRef16(opts.volume ?? 1);
6693
- const loopRef = useRef16(opts.loop ?? false);
6694
- const groupRef = useRef16(opts.group);
6695
- useEffect34(() => {
7586
+ const bufferRef = useRef20(null);
7587
+ const sourceRef = useRef20(null);
7588
+ const gainRef = useRef20(null);
7589
+ const volRef = useRef20(opts.volume ?? 1);
7590
+ const loopRef = useRef20(opts.loop ?? false);
7591
+ const groupRef = useRef20(opts.group);
7592
+ useEffect41(() => {
6696
7593
  let cancelled = false;
6697
7594
  loadBuffer(src).then((buf) => {
6698
7595
  if (!cancelled) bufferRef.current = buf;
@@ -6714,7 +7611,7 @@ function useSound(src, opts = {}) {
6714
7611
  };
6715
7612
  }, [src]);
6716
7613
  const getDestination = () => groupRef.current ? getGroupGainNode(groupRef.current) : getGroupGainNode("master");
6717
- const play = () => {
7614
+ const play = (playOpts) => {
6718
7615
  if (!bufferRef.current) return;
6719
7616
  const ctx = getAudioCtx();
6720
7617
  if (ctx.state === "suspended") void ctx.resume();
@@ -6733,7 +7630,12 @@ function useSound(src, opts = {}) {
6733
7630
  source.buffer = bufferRef.current;
6734
7631
  source.loop = loopRef.current;
6735
7632
  source.connect(gain);
6736
- source.start();
7633
+ const delay = playOpts?.delay;
7634
+ if (delay && delay > 0) {
7635
+ source.start(ctx.currentTime + delay);
7636
+ } else {
7637
+ source.start();
7638
+ }
6737
7639
  source.onended = () => {
6738
7640
  sourceRef.current = null;
6739
7641
  };
@@ -6827,8 +7729,38 @@ function createAtlas(names, _columns) {
6827
7729
  });
6828
7730
  return atlas;
6829
7731
  }
7732
+
7733
+ // src/utils/animationHelpers.ts
7734
+ function playClip(world, entityId, clipName) {
7735
+ const anim = world.getComponent(entityId, "AnimationState");
7736
+ if (anim) anim.currentClip = clipName;
7737
+ }
7738
+ function setAnimationState(world, entityId, stateName) {
7739
+ const animator = world.getComponent(entityId, "Animator");
7740
+ if (animator) {
7741
+ animator.currentState = stateName;
7742
+ animator._entered = false;
7743
+ }
7744
+ }
7745
+ function setAnimatorParam(world, entityId, name, value) {
7746
+ const animator = world.getComponent(entityId, "Animator");
7747
+ if (animator) animator.params[name] = value;
7748
+ }
7749
+
7750
+ // src/utils/prefab.ts
7751
+ import { memo } from "react";
7752
+ function definePrefab(name, defaults, render) {
7753
+ const component = memo((props) => {
7754
+ const merged = { ...defaults, ...props };
7755
+ return render(merged);
7756
+ });
7757
+ component.displayName = name;
7758
+ return component;
7759
+ }
6830
7760
  export {
7761
+ AnimatedSprite,
6831
7762
  Animation,
7763
+ Animator,
6832
7764
  AssetLoader,
6833
7765
  BoxCollider,
6834
7766
  Camera2D,
@@ -6858,30 +7790,42 @@ export {
6858
7790
  VirtualJoystick,
6859
7791
  World,
6860
7792
  arrive,
7793
+ chromaticAberrationEffect,
6861
7794
  createAtlas,
6862
7795
  createCompoundCollider,
6863
7796
  createInputMap,
6864
7797
  createInputRecorder,
6865
7798
  createNineSlice,
6866
7799
  createPlayerInput,
7800
+ createPostProcessStack,
7801
+ createRenderLayerManager,
6867
7802
  createSprite,
6868
7803
  createTag,
7804
+ createTimeline,
6869
7805
  createTimer,
6870
7806
  createTransform,
7807
+ defaultLayers,
7808
+ defineAnimations,
6871
7809
  definePlugin,
7810
+ definePrefab,
6872
7811
  duck,
6873
7812
  findByTag,
6874
7813
  flee,
6875
7814
  getGroupVolume,
6876
7815
  globalInputContext,
6877
7816
  hotReloadPlugin,
7817
+ mergeTileColliders,
6878
7818
  overlapBox,
6879
7819
  overlapCircle,
6880
7820
  patrol,
7821
+ playClip,
6881
7822
  preloadManifest,
6882
7823
  raycast,
6883
7824
  raycastAll,
7825
+ scanlineEffect,
6884
7826
  seek,
7827
+ setAnimationState,
7828
+ setAnimatorParam,
6885
7829
  setGroupMute,
6886
7830
  setGroupVolume,
6887
7831
  setMasterVolume,
@@ -6894,6 +7838,7 @@ export {
6894
7838
  useCircleEnter,
6895
7839
  useCircleExit,
6896
7840
  useCircleStay,
7841
+ useCollidingWith,
6897
7842
  useCollisionEnter,
6898
7843
  useCollisionExit,
6899
7844
  useCollisionStay,
@@ -6918,12 +7863,15 @@ export {
6918
7863
  useKinematicBody,
6919
7864
  useLevelTransition,
6920
7865
  useLocalMultiplayer,
7866
+ useObjectPool,
6921
7867
  usePathfinding,
6922
7868
  usePause,
6923
7869
  usePersistedBindings,
6924
7870
  usePlatformerController,
6925
7871
  usePlayerInput,
7872
+ usePostProcess,
6926
7873
  usePreload,
7874
+ useProfiler,
6927
7875
  useRestart,
6928
7876
  useSave,
6929
7877
  useSnapshot,
@@ -6932,6 +7880,8 @@ export {
6932
7880
  useTriggerEnter,
6933
7881
  useTriggerExit,
6934
7882
  useTriggerStay,
7883
+ useTween,
6935
7884
  useVirtualInput,
7885
+ vignetteEffect,
6936
7886
  wander
6937
7887
  };