@vectojs/core 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -308,12 +308,16 @@ var ComputeParticleEntity = (_class = class extends _chunkAGP4VLF4js.Entity {
308
308
  * The backend holds one resident SoA store, so a Scene with multiple particle
309
309
  * entities reuses it sequentially — origin is therefore re-gathered each call
310
310
  * (not upload-once), a couple of extra f32 reads per particle.
311
+ *
312
+ * Returns `false` if the kernel declined the call and {@link updateCPU} ran
313
+ * instead, so the Scene can report which path actually simulated this frame
314
+ * rather than assuming an installed backend did the work.
311
315
  */
312
316
  stepWithBackend(backend, dt, mouseX, mouseY, width, height) {
313
317
  const count = this.maxParticles;
314
318
  backend.ensure(count);
315
319
  backend.gather(this.particleData, count, true);
316
- this._wasmPending = backend.step(count, {
320
+ const pending = backend.step(count, {
317
321
  dt,
318
322
  mouseX,
319
323
  mouseY,
@@ -325,8 +329,14 @@ var ComputeParticleEntity = (_class = class extends _chunkAGP4VLF4js.Entity {
325
329
  maxVelocity: this.maxVelocity,
326
330
  explosion: this.pendingExplosion
327
331
  });
332
+ if (pending === null) {
333
+ this.updateCPU(dt, mouseX, mouseY, width, height);
334
+ return false;
335
+ }
336
+ this._wasmPending = pending;
328
337
  backend.scatter(this.particleData, count);
329
338
  this.pendingExplosion = null;
339
+ return true;
330
340
  }
331
341
  destroy() {
332
342
  this.destroyGPUResources();
@@ -470,148 +480,6 @@ function buildTreeStore(root) {
470
480
  return { store, indexOf };
471
481
  }
472
482
 
473
- // src/wasm/hit-store.ts
474
- function gatherHitAABBs(root, currentFrame) {
475
- const slotEntity = [];
476
- const boundless = [];
477
- const minxs = [];
478
- const minys = [];
479
- const maxxs = [];
480
- const maxys = [];
481
- const scratch = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
482
- const visit = (node) => {
483
- const index = slotEntity.length;
484
- slotEntity.push(node);
485
- const bounds = node.getBounds();
486
- if (bounds === null) {
487
- boundless.push({ entity: node, index });
488
- minxs.push(0);
489
- minys.push(0);
490
- maxxs.push(0);
491
- maxys.push(0);
492
- } else {
493
- if (!node._readWorldCache(currentFrame, scratch)) {
494
- const t = node.getWorldTransform();
495
- scratch.a = t.a;
496
- scratch.b = t.b;
497
- scratch.c = t.c;
498
- scratch.d = t.d;
499
- scratch.e = t.e;
500
- scratch.f = t.f;
501
- }
502
- const { a, b, c, d, e, f } = scratch;
503
- let minX = Infinity;
504
- let minY = Infinity;
505
- let maxX = -Infinity;
506
- let maxY = -Infinity;
507
- for (let i = 0; i < 4; i++) {
508
- const lx = i & 1 ? bounds.x + bounds.width : bounds.x;
509
- const ly = i & 2 ? bounds.y + bounds.height : bounds.y;
510
- const wx = a * lx + c * ly + e;
511
- const wy = b * lx + d * ly + f;
512
- if (wx < minX) minX = wx;
513
- if (wx > maxX) maxX = wx;
514
- if (wy < minY) minY = wy;
515
- if (wy > maxY) maxY = wy;
516
- }
517
- minxs.push(minX);
518
- minys.push(minY);
519
- maxxs.push(maxX);
520
- maxys.push(maxY);
521
- }
522
- const kids = node.children;
523
- for (let i = 0; i < kids.length; i++) visit(kids[i]);
524
- };
525
- visit(root);
526
- return {
527
- count: slotEntity.length,
528
- slotEntity,
529
- boundless,
530
- minx: Float64Array.from(minxs),
531
- miny: Float64Array.from(minys),
532
- maxx: Float64Array.from(maxxs),
533
- maxy: Float64Array.from(maxys)
534
- };
535
- }
536
-
537
- // src/wasm/hit-store-fused.ts
538
- function gatherHitAABBsFromStore(root, aabbs, storeSlotEntity, out) {
539
- const slotEntity = out.slotEntity;
540
- const boundless = out.boundless;
541
- slotEntity.length = 0;
542
- boundless.length = 0;
543
- let count = 0;
544
- let capacity = out.minx.length;
545
- let minx = out.minx;
546
- let miny = out.miny;
547
- let maxx = out.maxx;
548
- let maxy = out.maxy;
549
- const grow = (needed) => {
550
- if (needed <= capacity) return;
551
- let next = capacity || 256;
552
- while (next < needed) next *= 2;
553
- const nminx = new Float64Array(next);
554
- const nminy = new Float64Array(next);
555
- const nmaxx = new Float64Array(next);
556
- const nmaxy = new Float64Array(next);
557
- nminx.set(minx);
558
- nminy.set(miny);
559
- nmaxx.set(maxx);
560
- nmaxy.set(maxy);
561
- minx = nminx;
562
- miny = nminy;
563
- maxx = nmaxx;
564
- maxy = nmaxy;
565
- capacity = next;
566
- };
567
- let bailed = false;
568
- const visit = (node) => {
569
- if (bailed) return;
570
- const index = count;
571
- slotEntity.push(node);
572
- count++;
573
- grow(count);
574
- if (node.getBounds() === null) {
575
- boundless.push({ entity: node, index });
576
- minx[index] = 0;
577
- miny[index] = 0;
578
- maxx[index] = 0;
579
- maxy[index] = 0;
580
- } else {
581
- const slot = node._storeSlot;
582
- if (slot < 0 || slot >= aabbs.aminx.length || storeSlotEntity[slot] !== node) {
583
- bailed = true;
584
- return;
585
- }
586
- minx[index] = aabbs.aminx[slot];
587
- miny[index] = aabbs.aminy[slot];
588
- maxx[index] = aabbs.amaxx[slot];
589
- maxy[index] = aabbs.amaxy[slot];
590
- }
591
- const children = node.children;
592
- for (let i = 0; i < children.length; i++) visit(children[i]);
593
- };
594
- visit(root);
595
- if (bailed) return null;
596
- out.count = count;
597
- out.minx = minx;
598
- out.miny = miny;
599
- out.maxx = maxx;
600
- out.maxy = maxy;
601
- return out;
602
- }
603
- function createHitGatherBuffer() {
604
- return {
605
- count: 0,
606
- slotEntity: [],
607
- boundless: [],
608
- minx: new Float64Array(256),
609
- miny: new Float64Array(256),
610
- maxx: new Float64Array(256),
611
- maxy: new Float64Array(256)
612
- };
613
- }
614
-
615
483
  // src/wasm/backend.ts
616
484
  var WASM_STATUS = {
617
485
  OK: 0,
@@ -704,7 +572,10 @@ var WasmTransformBackend = (_class2 = class {
704
572
  return status;
705
573
  }
706
574
  /** Upload only the run table + count (topology), leaving per-entity inputs to
707
- * the resident views. Call when the tree structure changes, not per frame. */
575
+ * the resident views. Call when the tree structure changes, not per frame.
576
+ * Returns `false` if the crate rejected the run count, in which case the
577
+ * PREVIOUS topology is still published and any kernel run against it would
578
+ * compose the wrong tree — the caller must not proceed. */
708
579
  uploadRuns(store) {
709
580
  this.ensure(store.count, store.runCount);
710
581
  const rc = store.runCount;
@@ -712,6 +583,7 @@ var WasmTransformBackend = (_class2 = class {
712
583
  this.vrs.set(store.runStart.subarray(0, rc));
713
584
  this.vrl.set(store.runLen.subarray(0, rc));
714
585
  this.lastStatus = this.ex.set_run_count(rc);
586
+ return this.lastStatus === WASM_STATUS.OK;
715
587
  }
716
588
  /**
717
589
  * Status of the most recent kernel or run-table call. `WASM_STATUS.OK` unless
@@ -760,7 +632,8 @@ var WasmTransformBackend = (_class2 = class {
760
632
  this.vby.set(store.by.subarray(0, n));
761
633
  this.vbw.set(store.bw.subarray(0, n));
762
634
  this.vbh.set(store.bh.subarray(0, n));
763
- this.ex.compute_aabbs(n);
635
+ this.lastStatus = this.ex.compute_aabbs(n);
636
+ if (this.lastStatus !== WASM_STATUS.OK) return;
764
637
  store.aminx.set(this.vaminx.subarray(0, n));
765
638
  store.aminy.set(this.vaminy.subarray(0, n));
766
639
  store.amaxx.set(this.vamaxx.subarray(0, n));
@@ -768,9 +641,12 @@ var WasmTransformBackend = (_class2 = class {
768
641
  }
769
642
  /** Run the AABB pass only, over `count` entities already resident in wasm
770
643
  * memory (bounds written via {@link boundsView}, world matrices already
771
- * composed). No upload/readback — the per-frame resident path. */
644
+ * composed). No upload/readback — the per-frame resident path. Returns
645
+ * `false` if the kernel rejected `count` (beyond capacity, or uninitialized),
646
+ * in which case {@link aabbView} still holds the previous frame's bounds. */
772
647
  runAabbs(count) {
773
- this.ex.compute_aabbs(count);
648
+ this.lastStatus = this.ex.compute_aabbs(count);
649
+ return this.lastStatus === WASM_STATUS.OK;
774
650
  }
775
651
  /** Resident wasm local-bounds input views (`bx,by,bw,bh`) for the AABB pass. */
776
652
  boundsView() {
@@ -844,6 +720,148 @@ var WasmTransformBackend = (_class2 = class {
844
720
  }
845
721
  }, _class2);
846
722
 
723
+ // src/wasm/hit-store.ts
724
+ function gatherHitAABBs(root, currentFrame) {
725
+ const slotEntity = [];
726
+ const boundless = [];
727
+ const minxs = [];
728
+ const minys = [];
729
+ const maxxs = [];
730
+ const maxys = [];
731
+ const scratch = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
732
+ const visit = (node) => {
733
+ const index = slotEntity.length;
734
+ slotEntity.push(node);
735
+ const bounds = node.getBounds();
736
+ if (bounds === null) {
737
+ boundless.push({ entity: node, index });
738
+ minxs.push(0);
739
+ minys.push(0);
740
+ maxxs.push(0);
741
+ maxys.push(0);
742
+ } else {
743
+ if (!node._readWorldCache(currentFrame, scratch)) {
744
+ const t = node.getWorldTransform();
745
+ scratch.a = t.a;
746
+ scratch.b = t.b;
747
+ scratch.c = t.c;
748
+ scratch.d = t.d;
749
+ scratch.e = t.e;
750
+ scratch.f = t.f;
751
+ }
752
+ const { a, b, c, d, e, f } = scratch;
753
+ let minX = Infinity;
754
+ let minY = Infinity;
755
+ let maxX = -Infinity;
756
+ let maxY = -Infinity;
757
+ for (let i = 0; i < 4; i++) {
758
+ const lx = i & 1 ? bounds.x + bounds.width : bounds.x;
759
+ const ly = i & 2 ? bounds.y + bounds.height : bounds.y;
760
+ const wx = a * lx + c * ly + e;
761
+ const wy = b * lx + d * ly + f;
762
+ if (wx < minX) minX = wx;
763
+ if (wx > maxX) maxX = wx;
764
+ if (wy < minY) minY = wy;
765
+ if (wy > maxY) maxY = wy;
766
+ }
767
+ minxs.push(minX);
768
+ minys.push(minY);
769
+ maxxs.push(maxX);
770
+ maxys.push(maxY);
771
+ }
772
+ const kids = node.children;
773
+ for (let i = 0; i < kids.length; i++) visit(kids[i]);
774
+ };
775
+ visit(root);
776
+ return {
777
+ count: slotEntity.length,
778
+ slotEntity,
779
+ boundless,
780
+ minx: Float64Array.from(minxs),
781
+ miny: Float64Array.from(minys),
782
+ maxx: Float64Array.from(maxxs),
783
+ maxy: Float64Array.from(maxys)
784
+ };
785
+ }
786
+
787
+ // src/wasm/hit-store-fused.ts
788
+ function gatherHitAABBsFromStore(root, aabbs, storeSlotEntity, out) {
789
+ const slotEntity = out.slotEntity;
790
+ const boundless = out.boundless;
791
+ slotEntity.length = 0;
792
+ boundless.length = 0;
793
+ let count = 0;
794
+ let capacity = out.minx.length;
795
+ let minx = out.minx;
796
+ let miny = out.miny;
797
+ let maxx = out.maxx;
798
+ let maxy = out.maxy;
799
+ const grow = (needed) => {
800
+ if (needed <= capacity) return;
801
+ let next = capacity || 256;
802
+ while (next < needed) next *= 2;
803
+ const nminx = new Float64Array(next);
804
+ const nminy = new Float64Array(next);
805
+ const nmaxx = new Float64Array(next);
806
+ const nmaxy = new Float64Array(next);
807
+ nminx.set(minx);
808
+ nminy.set(miny);
809
+ nmaxx.set(maxx);
810
+ nmaxy.set(maxy);
811
+ minx = nminx;
812
+ miny = nminy;
813
+ maxx = nmaxx;
814
+ maxy = nmaxy;
815
+ capacity = next;
816
+ };
817
+ let bailed = false;
818
+ const visit = (node) => {
819
+ if (bailed) return;
820
+ const index = count;
821
+ slotEntity.push(node);
822
+ count++;
823
+ grow(count);
824
+ if (node.getBounds() === null) {
825
+ boundless.push({ entity: node, index });
826
+ minx[index] = 0;
827
+ miny[index] = 0;
828
+ maxx[index] = 0;
829
+ maxy[index] = 0;
830
+ } else {
831
+ const slot = node._storeSlot;
832
+ if (slot < 0 || slot >= aabbs.aminx.length || storeSlotEntity[slot] !== node) {
833
+ bailed = true;
834
+ return;
835
+ }
836
+ minx[index] = aabbs.aminx[slot];
837
+ miny[index] = aabbs.aminy[slot];
838
+ maxx[index] = aabbs.amaxx[slot];
839
+ maxy[index] = aabbs.amaxy[slot];
840
+ }
841
+ const children = node.children;
842
+ for (let i = 0; i < children.length; i++) visit(children[i]);
843
+ };
844
+ visit(root);
845
+ if (bailed) return null;
846
+ out.count = count;
847
+ out.minx = minx;
848
+ out.miny = miny;
849
+ out.maxx = maxx;
850
+ out.maxy = maxy;
851
+ return out;
852
+ }
853
+ function createHitGatherBuffer() {
854
+ return {
855
+ count: 0,
856
+ slotEntity: [],
857
+ boundless: [],
858
+ minx: new Float64Array(256),
859
+ miny: new Float64Array(256),
860
+ maxx: new Float64Array(256),
861
+ maxy: new Float64Array(256)
862
+ };
863
+ }
864
+
847
865
  // src/wasm/anim-backend.ts
848
866
  var PAD3 = 8;
849
867
  var AnimBackend = (_class3 = class {
@@ -852,7 +870,7 @@ var AnimBackend = (_class3 = class {
852
870
  __init13() {this.tweenCap = 0}
853
871
 
854
872
 
855
- constructor(instance) {;_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);
873
+ constructor(instance) {;_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);
856
874
  this.ex = instance.exports;
857
875
  }
858
876
  /** The resident spring SoA input/output views, valid until the next capacity
@@ -878,14 +896,33 @@ var AnimBackend = (_class3 = class {
878
896
  this.ex.anim_init(this.springCap, this.tweenCap);
879
897
  this.refreshViews();
880
898
  }
881
- /** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
899
+ /**
900
+ * Advance `count` springs (from index 0) by `dtMs` milliseconds, in place.
901
+ * Returns `true` when the kernel ran. `false` means it rejected the call
902
+ * (count beyond the capacity {@link ensure} allocated, or no `anim_init` yet)
903
+ * and wrote nothing, so the caller must tick those drivers in JS instead of
904
+ * scattering back a pack the kernel never touched. See {@link lastStatus}.
905
+ */
882
906
  stepSprings(dtMs, count) {
883
- this.ex.spring_step(dtMs / 1e3, count);
907
+ this.lastStatus = this.ex.spring_step(dtMs / 1e3, count);
908
+ return this.lastStatus === WASM_STATUS.OK;
884
909
  }
885
- /** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
910
+ /**
911
+ * Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`.
912
+ * Returns `true` when the kernel ran; `false` means it rejected the call and
913
+ * wrote nothing (see {@link stepSprings}). `elapsed` is kernel-side state, so
914
+ * a rejected tween pack must not be read back — it is unadvanced, not
915
+ * partially advanced.
916
+ */
886
917
  stepTweens(dtMs, count) {
887
- this.ex.tween_step(dtMs, count);
918
+ this.lastStatus = this.ex.tween_step(dtMs, count);
919
+ return this.lastStatus === WASM_STATUS.OK;
888
920
  }
921
+ /**
922
+ * Status of the most recent kernel call — `WASM_STATUS.OK` unless the kernel
923
+ * declined it. Mirrors {@link TransformBackend.lastStatus}.
924
+ */
925
+ __init14() {this.lastStatus = WASM_STATUS.OK}
889
926
  refreshViews() {
890
927
  const buf = this.ex.memory.buffer;
891
928
  const sCap = this.springCap;
@@ -915,9 +952,9 @@ var PAD4 = 8;
915
952
  var CELLS_PER_ENTITY_HINT = 4;
916
953
  var HitTestBackend = (_class4 = class {
917
954
 
918
- __init14() {this.entityCap = 0}
919
- __init15() {this.cellCap = 0}
920
- __init16() {this.itemCap = 0}
955
+ __init15() {this.entityCap = 0}
956
+ __init16() {this.cellCap = 0}
957
+ __init17() {this.itemCap = 0}
921
958
 
922
959
 
923
960
 
@@ -926,10 +963,10 @@ var HitTestBackend = (_class4 = class {
926
963
 
927
964
 
928
965
  /** Grid geometry from the last {@link ensure} call. */
929
- __init17() {this.gridW = 0}
930
- __init18() {this.gridH = 0}
931
- __init19() {this.cellSize = 64}
932
- constructor(instance) {;_class4.prototype.__init14.call(this);_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this);
966
+ __init18() {this.gridW = 0}
967
+ __init19() {this.gridH = 0}
968
+ __init20() {this.cellSize = 64}
969
+ constructor(instance) {;_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this);_class4.prototype.__init20.call(this);
933
970
  this.ex = instance.exports;
934
971
  }
935
972
  /** The resident AABB input views (`minx/miny/maxx/maxy`), valid until the
@@ -1013,9 +1050,9 @@ var HitTestBackend = (_class4 = class {
1013
1050
  var PAD5 = 8;
1014
1051
  var ParticleBackend = (_class5 = class {
1015
1052
 
1016
- __init20() {this.cap = 0}
1053
+ __init21() {this.cap = 0}
1017
1054
 
1018
- constructor(instance) {;_class5.prototype.__init20.call(this);
1055
+ constructor(instance) {;_class5.prototype.__init21.call(this);_class5.prototype.__init22.call(this);
1019
1056
  this.ex = instance.exports;
1020
1057
  }
1021
1058
  /** The resident SoA views, valid until the next capacity growth. */
@@ -1036,6 +1073,12 @@ var ParticleBackend = (_class5 = class {
1036
1073
  * Advance `count` particles one step in place. Returns `true` when at least
1037
1074
  * one live particle is still moving or off-origin beyond epsilon (the fused
1038
1075
  * `hasPendingAnimations` flag), so the caller need not re-scan the buffer.
1076
+ *
1077
+ * Returns `null` when the kernel REJECTED the call — `count` beyond the
1078
+ * capacity {@link ensure} allocated, or no `particle_init` yet. Nothing was
1079
+ * written, so the caller must NOT {@link scatter} (that would write the
1080
+ * gathered pre-step values back and freeze the simulation) and should fall
1081
+ * back to the JS `updateCPU` path for this frame. See {@link lastStatus}.
1039
1082
  */
1040
1083
  step(count, p) {
1041
1084
  const e = p.explosion;
@@ -1055,8 +1098,18 @@ var ParticleBackend = (_class5 = class {
1055
1098
  e ? e.force : 0,
1056
1099
  count
1057
1100
  );
1101
+ if (flag < 0) {
1102
+ this.lastStatus = -flag;
1103
+ return null;
1104
+ }
1105
+ this.lastStatus = WASM_STATUS.OK;
1058
1106
  return flag !== 0;
1059
1107
  }
1108
+ /**
1109
+ * Status of the most recent {@link step} — `WASM_STATUS.OK` unless the kernel
1110
+ * declined it. Mirrors {@link TransformBackend.lastStatus}.
1111
+ */
1112
+ __init22() {this.lastStatus = WASM_STATUS.OK}
1060
1113
  /** Transpose the AoS stride-8 buffer into the SoA views (position/velocity/
1061
1114
  * life every frame; origin upload-once when `withOrigin`). */
1062
1115
  gather(data, count, withOrigin) {
@@ -1147,11 +1200,11 @@ async function loadCoreWasmModule(source) {
1147
1200
  }
1148
1201
  var CoreWasmRuntime = (_class6 = class {
1149
1202
 
1150
- __init21() {this.transformBackend = null}
1151
- __init22() {this.animBackend = null}
1152
- __init23() {this.hitBackend = null}
1153
- __init24() {this.particleBackendInstance = null}
1154
- constructor(instance) {;_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);
1203
+ __init23() {this.transformBackend = null}
1204
+ __init24() {this.animBackend = null}
1205
+ __init25() {this.hitBackend = null}
1206
+ __init26() {this.particleBackendInstance = null}
1207
+ constructor(instance) {;_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);
1155
1208
  this.instance = instance;
1156
1209
  }
1157
1210
  /**
@@ -1520,15 +1573,15 @@ var Scene = (_class7 = class _Scene {
1520
1573
 
1521
1574
 
1522
1575
 
1523
- __init25() {this.isRunning = false}
1576
+ __init27() {this.isRunning = false}
1524
1577
  /** Whether the canvas is at least partially in the viewport. When it scrolls
1525
1578
  * fully off-screen the rAF loop pauses (stops rescheduling) instead of
1526
1579
  * burning frames on a scene nobody can see; an IntersectionObserver resumes
1527
1580
  * it on re-entry. Defaults true (and stays true where IntersectionObserver
1528
1581
  * is unavailable, e.g. SSR/jsdom, so behavior is unchanged there). */
1529
- __init26() {this._canvasOnScreen = true}
1530
- __init27() {this._canvasObserver = null}
1531
- __init28() {this.lastTime = 0}
1582
+ __init28() {this._canvasOnScreen = true}
1583
+ __init29() {this._canvasObserver = null}
1584
+ __init30() {this.lastTime = 0}
1532
1585
 
1533
1586
  /**
1534
1587
  * Redraw strategy:
@@ -1537,11 +1590,11 @@ var Scene = (_class7 = class _Scene {
1537
1590
  * {@link markDirty}) or while an animation is pending. Ideal for static /
1538
1591
  * event-driven UIs where idle frames should cost ~0.
1539
1592
  */
1540
- __init29() {this.renderMode = "always"}
1593
+ __init31() {this.renderMode = "always"}
1541
1594
  /** Cap on distinct recorded dirty reasons (see `recordDirtyReason`). */
1542
1595
  static __initStatic4() {this.MAX_DIRTY_REASONS = 200}
1543
- __init30() {this._phaseTiming = false}
1544
- __init31() {this._phaseTotals = /* @__PURE__ */ new Map()}
1596
+ __init32() {this._phaseTiming = false}
1597
+ __init33() {this._phaseTotals = /* @__PURE__ */ new Map()}
1545
1598
  /**
1546
1599
  * Start or stop per-phase render timing.
1547
1600
  *
@@ -1604,32 +1657,32 @@ var Scene = (_class7 = class _Scene {
1604
1657
  clearRenderPhases() {
1605
1658
  this._phaseTotals.clear();
1606
1659
  }
1607
- __init32() {this._dirtyTracking = false}
1608
- __init33() {this._dirtyReasons = /* @__PURE__ */ new Map()}
1609
- __init34() {this.dirty = true}
1660
+ __init34() {this._dirtyTracking = false}
1661
+ __init35() {this._dirtyReasons = /* @__PURE__ */ new Map()}
1662
+ __init36() {this.dirty = true}
1610
1663
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
1611
- __init35() {this.autoThrottle = true}
1664
+ __init37() {this.autoThrottle = true}
1612
1665
  // --- Frame telemetry (read via `frameStats`) ---------------------------
1613
1666
  /** Wall-clock ms spent inside the last `render()` call. */
1614
- __init36() {this._lastFrameMs = 0}
1667
+ __init38() {this._lastFrameMs = 0}
1615
1668
  /** Rolling exponential average of rendered-frame intervals, in ms. */
1616
- __init37() {this._avgFrameIntervalMs = 0}
1669
+ __init39() {this._avgFrameIntervalMs = 0}
1617
1670
  /** dt (ms) handed to the last rendered frame. */
1618
- __init38() {this._lastDt = 0}
1671
+ __init40() {this._lastDt = 0}
1619
1672
  /** Count of frames actually rendered since the loop started. */
1620
- __init39() {this._renderedFrames = 0}
1673
+ __init41() {this._renderedFrames = 0}
1621
1674
  /** Count of rAF ticks skipped (idle / capped) since the loop started. */
1622
- __init40() {this._skippedFrames = 0}
1675
+ __init42() {this._skippedFrames = 0}
1623
1676
  /** `time` of the previous *rendered* frame, for interval measurement. */
1624
- __init41() {this._lastRenderTick = 0}
1677
+ __init43() {this._lastRenderTick = 0}
1625
1678
  /**
1626
1679
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
1627
1680
  * the loop renders at most `maxFPS` times per second; animations still run,
1628
1681
  * just less often. See {@link SceneOptions.maxFPS}.
1629
1682
  */
1630
- __init42() {this.maxFPS = 60}
1683
+ __init44() {this.maxFPS = 60}
1631
1684
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
1632
- __init43() {this.respectReducedMotion = true}
1685
+ __init45() {this.respectReducedMotion = true}
1633
1686
  /**
1634
1687
  * Reading direction for accessibility tab/traversal order (`'ltr'` default,
1635
1688
  * `'rtl'`). Controls the inline sort within a visual row in
@@ -1645,15 +1698,15 @@ var Scene = (_class7 = class _Scene {
1645
1698
  this.a11yNeedsReorder = true;
1646
1699
  }
1647
1700
  }
1648
- __init44() {this._readingDirection = "ltr"}
1701
+ __init46() {this._readingDirection = "ltr"}
1649
1702
  /** Cached media-query list; `.matches` is read live each frame. */
1650
- __init45() {this.reducedMotionQuery = null}
1703
+ __init47() {this.reducedMotionQuery = null}
1651
1704
  /** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
1652
1705
  * canvas gets NO automatic forced-colors treatment from the browser (it's
1653
1706
  * opaque pixels), so components must read {@link forcedColors} and repaint
1654
1707
  * with system colors themselves; a change listener repaints idle scenes. */
1655
- __init46() {this.forcedColorsQuery = null}
1656
- __init47() {this.forcedColorsChangeHandler = null}
1708
+ __init48() {this.forcedColorsQuery = null}
1709
+ __init49() {this.forcedColorsChangeHandler = null}
1657
1710
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
1658
1711
  get prefersReducedMotion() {
1659
1712
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _21 => _21.reducedMotionQuery, 'optionalAccess', _22 => _22.matches]);
@@ -1673,22 +1726,22 @@ var Scene = (_class7 = class _Scene {
1673
1726
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
1674
1727
  * frame. See {@link SceneOptions.a11ySyncInterval}.
1675
1728
  */
1676
- __init48() {this.a11ySyncInterval = 0}
1729
+ __init50() {this.a11ySyncInterval = 0}
1677
1730
  /** Timestamp of the last a11y sync, for throttling. */
1678
- __init49() {this.lastA11ySync = -Infinity}
1731
+ __init51() {this.lastA11ySync = -Infinity}
1679
1732
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
1680
- __init50() {this.a11yPendingSyncAfterAnimation = false}
1733
+ __init52() {this.a11yPendingSyncAfterAnimation = false}
1681
1734
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
1682
1735
  // whole projection degrades to a no-op so the engine's logic stays usable
1683
1736
  // server-side (e.g. headless layout / vector export) without jsdom.
1684
1737
 
1685
- __init51() {this.a11yElements = /* @__PURE__ */ new Map()}
1738
+ __init53() {this.a11yElements = /* @__PURE__ */ new Map()}
1686
1739
  /** DOM nodes mirroring static text content, keyed by entity id. */
1687
- __init52() {this.contentElements = /* @__PURE__ */ new Map()}
1740
+ __init54() {this.contentElements = /* @__PURE__ */ new Map()}
1688
1741
  /** Pending cold font-calibration frame per projected grid entity. */
1689
- __init53() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1742
+ __init55() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1690
1743
  /** Detached, untransformed font probes used by the cold calibration pass. */
1691
- __init54() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1744
+ __init56() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1692
1745
  /**
1693
1746
  * Monotonic stamp identifying the conditions grid cells were calibrated under.
1694
1747
  *
@@ -1709,69 +1762,69 @@ var Scene = (_class7 = class _Scene {
1709
1762
  * A plain incrementing integer rather than the descriptive calibration key,
1710
1763
  * because it goes into an attribute selector and must not need escaping.
1711
1764
  */
1712
- __init55() {this.contentGridCalibrationGeneration = 0}
1765
+ __init57() {this.contentGridCalibrationGeneration = 0}
1713
1766
  /** The `(fontEpoch, pageScale)` pair the current generation corresponds to. */
1714
- __init56() {this.contentGridCalibrationStamp = ""}
1767
+ __init58() {this.contentGridCalibrationStamp = ""}
1715
1768
  /** Invalidates grid font calibration after browser font availability changes. */
1716
- __init57() {this.contentFontEpoch = 0}
1769
+ __init59() {this.contentFontEpoch = 0}
1717
1770
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
1718
- __init58() {this.contentMetricScaleEpoch = -1}
1719
- __init59() {this.contentMetricScaleX = 1}
1720
- __init60() {this.contentProjectionEnabled = true}
1771
+ __init60() {this.contentMetricScaleEpoch = -1}
1772
+ __init61() {this.contentMetricScaleX = 1}
1773
+ __init62() {this.contentProjectionEnabled = true}
1721
1774
  // Virtualization margin (px) for content projection; `undefined` → one
1722
1775
  // viewport height, resolved at sync time. `Infinity` = materialize everything.
1723
- __init61() {this.contentProjectionMargin = void 0}
1776
+ __init63() {this.contentProjectionMargin = void 0}
1724
1777
  /**
1725
1778
  * True while a text-selection drag that started on a projection's blank
1726
1779
  * region (no text node under the press) is being driven manually — the
1727
1780
  * browser has no native anchor for it, so mousemove extends the Selection
1728
1781
  * from the position we resolved ourselves.
1729
1782
  */
1730
- __init62() {this.blankRegionSelectionDrag = false}
1731
- __init63() {this.contentSelectionAnchor = null}
1732
- __init64() {this.contentSelectionEndListener = null}
1783
+ __init64() {this.blankRegionSelectionDrag = false}
1784
+ __init65() {this.contentSelectionAnchor = null}
1785
+ __init66() {this.contentSelectionEndListener = null}
1733
1786
  // Animation/interactive flags collected during the render walk (tree-walk
1734
1787
  // fusion): the loop reads last frame's answers instead of re-walking the
1735
1788
  // tree up to 4× per tick. Start true so the first tick stays conservative.
1736
- __init65() {this.frameHadAnimation = true}
1737
- __init66() {this.frameHadInteractive = true}
1789
+ __init67() {this.frameHadAnimation = true}
1790
+ __init68() {this.frameHadInteractive = true}
1738
1791
 
1739
1792
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
1740
1793
  * (window moved between monitors, browser zoom) so the canvas backing store
1741
1794
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
1742
1795
  * A resolution media query only fires when leaving its exact value, so the
1743
1796
  * handler re-arms a fresh query for the new DPR each time. */
1744
- __init67() {this.dprMediaQuery = null}
1797
+ __init69() {this.dprMediaQuery = null}
1745
1798
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
1746
1799
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
1747
1800
  * listener never fires for these (the window isn't what changed), so without
1748
1801
  * this an embedded canvas stayed at its initial size forever. */
1749
- __init68() {this.canvasResizeObserver = null}
1750
- __init69() {this.dprChangeHandler = null}
1751
- __init70() {this.focusedA11yElement = null}
1802
+ __init70() {this.canvasResizeObserver = null}
1803
+ __init71() {this.dprChangeHandler = null}
1804
+ __init72() {this.focusedA11yElement = null}
1752
1805
  /** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
1753
1806
  * style writes entirely. Reset to `null` to force the next sync (a new overlay
1754
1807
  * layer was created and has never been positioned). */
1755
- __init71() {this._overlayGeometry = null}
1808
+ __init73() {this._overlayGeometry = null}
1756
1809
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
1757
1810
  * mid-hover synthesize the `pointerleave` the browser never sends for a
1758
1811
  * detached element, so the entity doesn't keep its hover state. */
1759
- __init72() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1812
+ __init74() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1760
1813
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
1761
1814
  * pruned (virtualization/streaming/removal) while it holds focus, we move
1762
1815
  * focus here instead of letting the browser drop it to <body> — keeping the
1763
1816
  * screen-reader virtual cursor inside the scene's a11y region. */
1764
- __init73() {this.focusSentinel = null}
1765
- __init74() {this.caretBlinkTimer = null}
1766
- __init75() {this.a11yNeedsReorder = true}
1767
- __init76() {this.portalRoot = null}
1768
- __init77() {this.fullViewportElements = []}
1769
- __init78() {this.normalElements = []}
1770
- __init79() {this.activeIds = /* @__PURE__ */ new Set()}
1771
- __init80() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1772
- __init81() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1773
- __init82() {this.portalEntities = /* @__PURE__ */ new Map()}
1774
- __init83() {this.renderOrderCounter = 0}
1817
+ __init75() {this.focusSentinel = null}
1818
+ __init76() {this.caretBlinkTimer = null}
1819
+ __init77() {this.a11yNeedsReorder = true}
1820
+ __init78() {this.portalRoot = null}
1821
+ __init79() {this.fullViewportElements = []}
1822
+ __init80() {this.normalElements = []}
1823
+ __init81() {this.activeIds = /* @__PURE__ */ new Set()}
1824
+ __init82() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1825
+ __init83() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1826
+ __init84() {this.portalEntities = /* @__PURE__ */ new Map()}
1827
+ __init85() {this.renderOrderCounter = 0}
1775
1828
  /**
1776
1829
  * Monotonic render-frame counter, bumped once per authoritative `render()`
1777
1830
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -1780,7 +1833,7 @@ var Scene = (_class7 = class _Scene {
1780
1833
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
1781
1834
  * `_setWorldCache` are: it is a cross-class render-internal contract.
1782
1835
  */
1783
- __init84() {this.currentFrame = 0}
1836
+ __init86() {this.currentFrame = 0}
1784
1837
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
1785
1838
  // When `_transformBackend === 'wasm'`, the main render walk sources each
1786
1839
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -1788,24 +1841,24 @@ var Scene = (_class7 = class _Scene {
1788
1841
  // fallback and the default: a null backend, a non-main renderer, or any entity
1789
1842
  // absent from the store all fall back to the JS composition, so WASM can only
1790
1843
  // ever change *how fast* a world matrix is produced, never *what* it is.
1791
- __init85() {this._wasm = null}
1792
- __init86() {this._transformBackend = "js"}
1844
+ __init87() {this._wasm = null}
1845
+ __init88() {this._transformBackend = "js"}
1793
1846
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
1794
1847
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
1795
1848
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
1796
1849
  // rebuilds the per-frame cost is: gather each entity's transform into the
1797
1850
  // resident wasm input view + run the kernel — no reallocation, no readback.
1798
- __init87() {this._treeStore = null}
1799
- __init88() {this._slotEntity = []}
1851
+ __init89() {this._treeStore = null}
1852
+ __init90() {this._slotEntity = []}
1800
1853
  // store slot -> entity (also validates slots)
1801
- __init89() {this._wasmInputs = null}
1802
- __init90() {this._wasmWorld = null}
1803
- __init91() {this._structureVersion = 0}
1804
- __init92() {this._storeStructureVersion = -1}
1854
+ __init91() {this._wasmInputs = null}
1855
+ __init92() {this._wasmWorld = null}
1856
+ __init93() {this._structureVersion = 0}
1857
+ __init94() {this._storeStructureVersion = -1}
1805
1858
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
1806
1859
  // structure version it was gathered at. Rebuilt only on a topology change.
1807
- __init93() {this._computeEntities = []}
1808
- __init94() {this._computeEntitiesVersion = -1}
1860
+ __init95() {this._computeEntities = []}
1861
+ __init96() {this._computeEntitiesVersion = -1}
1809
1862
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
1810
1863
  * it. Called by `Entity.add`/`remove` (topology changes only). */
1811
1864
  markStructureChanged() {
@@ -1867,10 +1920,13 @@ var Scene = (_class7 = class _Scene {
1867
1920
  return true;
1868
1921
  }
1869
1922
  // ── WASM hit-test backend (invisible accelerator, G3) ───────────────────────
1870
- // A separate WASM module instance from the transform backend (each crate
1871
- // export lives in independent linear memory per instance, so there is no
1872
- // shared-state hazard in running both) that indexes the main tree's world
1873
- // AABBs into a dense viewport grid for findEntityAt. The JS depth-first walk
1923
+ // Served by the same instance as every other accelerator (see
1924
+ // `_wasmRuntime`): the crate keeps transform/anim/hit/particle in distinct
1925
+ // statics, so one instance runs them all without aliasing. It indexes the
1926
+ // main tree's world AABBs into a dense viewport grid for findEntityAt. Note
1927
+ // that sharing one linear memory means an allocation here can grow it and
1928
+ // detach views built over the old buffer, so each backend re-checks buffer
1929
+ // identity (`revalidateViews`) before use. The JS depth-first walk
1874
1930
  // (findHitRecursively) is the permanent fallback: a null backend, a build
1875
1931
  // that overflows its item budget, or the overlay tree (never indexed — small
1876
1932
  // and rare, not worth accelerating) all fall through to it, so WASM can only
@@ -1887,7 +1943,7 @@ var Scene = (_class7 = class _Scene {
1887
1943
  * cached globally; the instance is per-Scene, which is the isolation that
1888
1944
  * actually matters.
1889
1945
  */
1890
- __init95() {this._wasmRuntime = null}
1946
+ __init97() {this._wasmRuntime = null}
1891
1947
  /**
1892
1948
  * Load (or reuse) this Scene's shared WASM runtime.
1893
1949
  *
@@ -1915,34 +1971,98 @@ var Scene = (_class7 = class _Scene {
1915
1971
  get wasmRuntime() {
1916
1972
  return this._wasmRuntime;
1917
1973
  }
1918
- __init96() {this._hitWasm = null}
1974
+ __init98() {this._hitWasm = null}
1919
1975
  // Cache key: which frame + structure version the grid was last (successfully,
1920
1976
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
1921
1977
  // hover/click), not every frame, so the grid is refreshed lazily on demand
1922
1978
  // rather than proactively every render() — unlike the transform store, which
1923
1979
  // every frame's draw depends on.
1924
- __init97() {this._hitGridFrame = -1}
1925
- __init98() {this._hitGridOk = false}
1926
- __init99() {this._hitSlotEntity = []}
1927
- __init100() {this._hitBoundless = []}
1980
+ __init99() {this._hitGridFrame = -1}
1981
+ __init100() {this._hitGridOk = false}
1982
+ __init101() {this._hitSlotEntity = []}
1983
+ __init102() {this._hitBoundless = []}
1928
1984
  /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
1929
- __init101() {this._hitGatherBuffer = null}
1985
+ __init103() {this._hitGatherBuffer = null}
1930
1986
  /**
1931
1987
  * Whether the last grid build sourced its AABBs from the WASM transform store
1932
1988
  * rather than recomputing them in JS. Diagnostic only — both paths must
1933
1989
  * produce the same entity for a given point.
1934
1990
  */
1935
- __init102() {this._hitFusedGather = false}
1991
+ __init104() {this._hitFusedGather = false}
1936
1992
  /**
1937
1993
  * Whether `compute_aabbs` has run against the current frame's world matrices.
1938
1994
  * The AABB pass is only meaningful after a `compose_*`, so the fused gather
1939
1995
  * must not read the views before then.
1940
1996
  */
1941
- __init103() {this._wasmAabbsFresh = false}
1997
+ __init105() {this._wasmAabbsFresh = false}
1942
1998
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
1943
1999
  get hitGatherPath() {
1944
2000
  return this._hitFusedGather ? "fused" : "js";
1945
2001
  }
2002
+ /**
2003
+ * Why the transform accelerator did or did not run on the most recent frame.
2004
+ * Written by the render walk and `_syncWasmStore`.
2005
+ */
2006
+ __init106() {this._transformReason = "not-installed"}
2007
+ /** Why the batched-driver accelerator did or did not run. */
2008
+ __init107() {this._animReason = "not-installed"}
2009
+ /**
2010
+ * Why the hit-test accelerator did or did not serve the last pointer query.
2011
+ * The grid is built lazily on demand, not every frame, so this describes the
2012
+ * most recent BUILD. Starts at `'not-installed'` because that is the truth
2013
+ * before a backend exists; `_ensureHitGrid` moves it to `'not-applicable'`
2014
+ * once one is installed but nothing has queried yet.
2015
+ */
2016
+ __init108() {this._hitReason = "not-installed"}
2017
+ /** Why the particle accelerator did or did not run. */
2018
+ __init109() {this._particleReason = "not-applicable"}
2019
+ /** Which particle implementation actually simulated the most recent frame. */
2020
+ __init110() {this._particlePath = "none"}
2021
+ /**
2022
+ * Per-frame status of every invisible accelerator: whether each is installed,
2023
+ * whether it actually ran on the most recent frame, and why.
2024
+ *
2025
+ * This exists because the older per-accelerator getters
2026
+ * ({@link transformBackend}, {@link animBackend}, {@link hitTestBackend},
2027
+ * {@link particleBackend}) report only that a backend is INSTALLED. Reading
2028
+ * `'wasm'` from one of those and concluding the accelerator is doing work is
2029
+ * wrong whenever a gate never opens, a kernel rejects its arguments, or a
2030
+ * faster backend takes the pass instead. Read {@link AcceleratorStatus.reason}
2031
+ * for which of those happened.
2032
+ *
2033
+ * Reflects the most recent main-renderer frame; a secondary renderer (SVG
2034
+ * export, offscreen snapshot) does not overwrite it.
2035
+ */
2036
+ get accelerators() {
2037
+ return {
2038
+ transform: {
2039
+ available: this._wasm !== null && this._transformBackend === "wasm",
2040
+ activeThisFrame: this._transformReason === "active",
2041
+ reason: this._transformReason,
2042
+ path: this._transformReason === "active" ? "wasm" : "js"
2043
+ },
2044
+ animation: {
2045
+ available: this._animWasm !== null,
2046
+ activeThisFrame: this._animBatchedLastFrame,
2047
+ reason: this._animReason,
2048
+ path: this._animBatchedLastFrame ? "wasm" : "js"
2049
+ },
2050
+ hitTest: {
2051
+ available: this._hitWasm !== null,
2052
+ // The grid is built lazily on a pointer query, not every frame, so this
2053
+ // describes the last BUILD rather than the last frame.
2054
+ activeThisFrame: this._hitReason === "active",
2055
+ reason: this._hitReason,
2056
+ path: this._hitReason !== "active" ? "js" : this._hitFusedGather ? "wasm-fused" : "wasm"
2057
+ },
2058
+ particle: {
2059
+ available: this._particleWasm !== null || this.webgpuActive,
2060
+ activeThisFrame: this._particleReason === "active",
2061
+ reason: this._particleReason,
2062
+ path: this._particlePath
2063
+ }
2064
+ };
2065
+ }
1946
2066
  /** Which backend answers `findEntityAt` for the main tree. */
1947
2067
  get hitTestBackend() {
1948
2068
  return this._hitWasm ? "wasm" : "js";
@@ -1952,6 +2072,7 @@ var Scene = (_class7 = class _Scene {
1952
2072
  setHitTestBackend(backend) {
1953
2073
  this._hitWasm = backend;
1954
2074
  this._hitGridFrame = -1;
2075
+ this._hitReason = backend ? "not-applicable" : "not-installed";
1955
2076
  }
1956
2077
  /**
1957
2078
  * Asynchronously instantiate the WASM hit-test core and, on success, hot-swap
@@ -1978,7 +2099,10 @@ var Scene = (_class7 = class _Scene {
1978
2099
  */
1979
2100
  _ensureHitGrid() {
1980
2101
  const backend = this._hitWasm;
1981
- if (!backend) return false;
2102
+ if (!backend) {
2103
+ this._hitReason = "not-installed";
2104
+ return false;
2105
+ }
1982
2106
  if (this._hitGridFrame === this.currentFrame) return this._hitGridOk;
1983
2107
  let gathered = null;
1984
2108
  if (this._wasm && this._ensureWasmAabbs()) {
@@ -2007,6 +2131,7 @@ var Scene = (_class7 = class _Scene {
2007
2131
  this._hitBoundless = gathered.boundless;
2008
2132
  this._hitGridFrame = this.currentFrame;
2009
2133
  this._hitGridOk = ok;
2134
+ this._hitReason = ok ? "active" : "rejected";
2010
2135
  return ok;
2011
2136
  }
2012
2137
  /**
@@ -2052,25 +2177,25 @@ var Scene = (_class7 = class _Scene {
2052
2177
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
2053
2178
  // only ever change *how* a driver is advanced, never *what* value it lands
2054
2179
  // on.
2055
- __init104() {this._animWasm = null}
2180
+ __init111() {this._animWasm = null}
2056
2181
  // Entities with at least one active driver, added by Entity._spawnDriver.
2057
2182
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
2058
2183
  // an entity whose drivers have since all completed or been removed. This is
2059
2184
  // what lets the batch pass find its candidates in O(active drivers), not
2060
2185
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
2061
- __init105() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2186
+ __init112() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
2062
2187
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
2063
2188
  // driver} objects every call — the integrated benchmark
2064
2189
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
2065
2190
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
2066
2191
  // truncated to the live count after each use so a stale tail slot never
2067
2192
  // pins a no-longer-active entity/driver in memory.
2068
- __init106() {this._springEntities = []}
2069
- __init107() {this._springProps = []}
2070
- __init108() {this._springDrivers = []}
2071
- __init109() {this._tweenEntities = []}
2072
- __init110() {this._tweenProps = []}
2073
- __init111() {this._tweenDrivers = []}
2193
+ __init113() {this._springEntities = []}
2194
+ __init114() {this._springProps = []}
2195
+ __init115() {this._springDrivers = []}
2196
+ __init116() {this._tweenEntities = []}
2197
+ __init117() {this._tweenProps = []}
2198
+ __init118() {this._tweenDrivers = []}
2074
2199
  /**
2075
2200
  * Minimum number of batchable (spring, or named-easing tween) active drivers
2076
2201
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -2142,7 +2267,7 @@ var Scene = (_class7 = class _Scene {
2142
2267
  * Setting {@link animDriverGateCount} overwrites all three, so existing code
2143
2268
  * that tuned the single knob keeps working unchanged.
2144
2269
  */
2145
- __init112() {this._animBatchedLastFrame = false}
2270
+ __init119() {this._animBatchedLastFrame = false}
2146
2271
  /**
2147
2272
  * Whether the WASM batch path actually ran on the most recent frame.
2148
2273
  *
@@ -2154,7 +2279,7 @@ var Scene = (_class7 = class _Scene {
2154
2279
  get animBatchedLastFrame() {
2155
2280
  return this._animBatchedLastFrame;
2156
2281
  }
2157
- __init113() {this.animGate = {
2282
+ __init120() {this.animGate = {
2158
2283
  spring: 128,
2159
2284
  tween: 256,
2160
2285
  mixed: 128
@@ -2192,7 +2317,7 @@ var Scene = (_class7 = class _Scene {
2192
2317
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
2193
2318
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
2194
2319
  // no backend is installed or a scene runs on WebGPU.
2195
- __init114() {this._particleWasm = null}
2320
+ __init121() {this._particleWasm = null}
2196
2321
  /** Which backend runs the CPU particle simulation. Reflects only whether a
2197
2322
  * backend is installed (the WebGPU compute path, when active, is used first
2198
2323
  * regardless). */
@@ -2272,7 +2397,10 @@ var Scene = (_class7 = class _Scene {
2272
2397
  * pre-pass.
2273
2398
  */
2274
2399
  _tickBatchedDrivers(dt) {
2275
- if (this._activeDriverEntities.size === 0) return;
2400
+ if (this._activeDriverEntities.size === 0) {
2401
+ this._animReason = "not-applicable";
2402
+ return;
2403
+ }
2276
2404
  let springBatchable = 0;
2277
2405
  let tweenBatchable = 0;
2278
2406
  for (const entity of this._activeDriverEntities) {
@@ -2289,10 +2417,17 @@ var Scene = (_class7 = class _Scene {
2289
2417
  const batchable = springBatchable + tweenBatchable;
2290
2418
  const backend = this._animWasm;
2291
2419
  this._animBatchedLastFrame = false;
2292
- if (!backend) return;
2420
+ if (!backend) {
2421
+ this._animReason = "not-installed";
2422
+ return;
2423
+ }
2293
2424
  const gate = springBatchable > 0 && tweenBatchable > 0 ? this.animGate.mixed : tweenBatchable > 0 ? this.animGate.tween : this.animGate.spring;
2294
- if (batchable < gate) return;
2425
+ if (batchable < gate) {
2426
+ this._animReason = "below-gate";
2427
+ return;
2428
+ }
2295
2429
  this._animBatchedLastFrame = true;
2430
+ this._animReason = "active";
2296
2431
  const sE = this._springEntities;
2297
2432
  const sP = this._springProps;
2298
2433
  const sD = this._springDrivers;
@@ -2339,8 +2474,13 @@ var Scene = (_class7 = class _Scene {
2339
2474
  sv.damp[i] = phys.damping;
2340
2475
  sv.mass[i] = phys.mass;
2341
2476
  }
2342
- backend.stepSprings(dt, springCount);
2343
- for (let i = 0; i < springCount; i++) sD[i].syncExternal(sv.val[i], sv.vel[i]);
2477
+ if (backend.stepSprings(dt, springCount)) {
2478
+ for (let i = 0; i < springCount; i++) sD[i].syncExternal(sv.val[i], sv.vel[i]);
2479
+ } else {
2480
+ for (let i = 0; i < springCount; i++) sD[i].tick(dt);
2481
+ this._animReason = "rejected";
2482
+ this._animBatchedLastFrame = false;
2483
+ }
2344
2484
  }
2345
2485
  if (tweenCount > 0) {
2346
2486
  const tv = backend.tweenView();
@@ -2353,8 +2493,13 @@ var Scene = (_class7 = class _Scene {
2353
2493
  tv.delay[i] = d.delayMs;
2354
2494
  tv.ease[i] = d.wasmEasingId;
2355
2495
  }
2356
- backend.stepTweens(dt, tweenCount);
2357
- for (let i = 0; i < tweenCount; i++) tD[i].syncExternal(tv.val[i], tv.elapsed[i]);
2496
+ if (backend.stepTweens(dt, tweenCount)) {
2497
+ for (let i = 0; i < tweenCount; i++) tD[i].syncExternal(tv.val[i], tv.elapsed[i]);
2498
+ } else {
2499
+ for (let i = 0; i < tweenCount; i++) tD[i].tick(dt);
2500
+ this._animReason = "rejected";
2501
+ this._animBatchedLastFrame = false;
2502
+ }
2358
2503
  }
2359
2504
  for (let i = 0; i < springCount; i++) sE[i]._applyDriverTick(sP[i], sD[i]);
2360
2505
  for (let i = 0; i < tweenCount; i++) tE[i]._applyDriverTick(tP[i], tD[i]);
@@ -2376,7 +2521,10 @@ var Scene = (_class7 = class _Scene {
2376
2521
  slotEntity2[slot] = entity;
2377
2522
  entity._storeSlot = slot;
2378
2523
  }
2379
- backend.uploadRuns(built.store);
2524
+ if (!backend.uploadRuns(built.store)) {
2525
+ this._transformReason = "rejected";
2526
+ return null;
2527
+ }
2380
2528
  this._treeStore = built.store;
2381
2529
  this._slotEntity = slotEntity2;
2382
2530
  this._wasmInputs = backend.inputView();
@@ -2401,8 +2549,12 @@ var Scene = (_class7 = class _Scene {
2401
2549
  inp.sin[slot] = trig.sin;
2402
2550
  inp.opacity[slot] = e.opacity;
2403
2551
  }
2404
- backend.runKernel("simd");
2552
+ if (backend.runKernel("simd") !== WASM_STATUS.OK) {
2553
+ this._transformReason = "rejected";
2554
+ return null;
2555
+ }
2405
2556
  this._wasmAabbsFresh = false;
2557
+ this._transformReason = "active";
2406
2558
  return this._wasmWorld;
2407
2559
  }
2408
2560
  /**
@@ -2432,7 +2584,7 @@ var Scene = (_class7 = class _Scene {
2432
2584
  bounds.bw[slot] = b ? b.width : 0;
2433
2585
  bounds.bh[slot] = b ? b.height : 0;
2434
2586
  }
2435
- backend.runAabbs(slotEntity.length);
2587
+ if (!backend.runAabbs(slotEntity.length)) return false;
2436
2588
  this._wasmAabbsFresh = true;
2437
2589
  return true;
2438
2590
  }
@@ -2442,24 +2594,24 @@ var Scene = (_class7 = class _Scene {
2442
2594
  * sync, so retaining the order prevents a newly opened overlay from spending
2443
2595
  * its first frame below previously projected controls.
2444
2596
  */
2445
- __init115() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2597
+ __init122() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2446
2598
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2447
- __init116() {this.pointRenderer = null}
2448
- __init117() {this.glCanvas = null}
2449
- __init118() {this.glContextLostHandler = null}
2450
- __init119() {this.glContextRestoredHandler = null}
2599
+ __init123() {this.pointRenderer = null}
2600
+ __init124() {this.glCanvas = null}
2601
+ __init125() {this.glContextLostHandler = null}
2602
+ __init126() {this.glContextRestoredHandler = null}
2451
2603
 
2452
2604
 
2453
2605
 
2454
- __init120() {this.disableWindowResize = false}
2606
+ __init127() {this.disableWindowResize = false}
2455
2607
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2456
2608
 
2457
2609
  // WebGPU properties
2458
- __init121() {this.destroyed = false}
2459
- __init122() {this.device = null}
2460
- __init123() {this.deviceLost = false}
2461
- __init124() {this.particleBackend = "auto"}
2462
- __init125() {this._webgpuDisabled = false}
2610
+ __init128() {this.destroyed = false}
2611
+ __init129() {this.device = null}
2612
+ __init130() {this.deviceLost = false}
2613
+ __init131() {this.particleBackend = "auto"}
2614
+ __init132() {this._webgpuDisabled = false}
2463
2615
  get webgpuDisabled() {
2464
2616
  return this._webgpuDisabled || this.particleBackend === "cpu";
2465
2617
  }
@@ -2487,22 +2639,22 @@ var Scene = (_class7 = class _Scene {
2487
2639
  set webgpuDisabled(value) {
2488
2640
  this._webgpuDisabled = value;
2489
2641
  }
2490
- __init126() {this.recoveryTimerId = null}
2491
- __init127() {this.manager = null}
2492
- __init128() {this.initializingWebGPU = false}
2493
- __init129() {this.gpuCanvas = null}
2494
- __init130() {this.gpuContext = null}
2642
+ __init133() {this.recoveryTimerId = null}
2643
+ __init134() {this.manager = null}
2644
+ __init135() {this.initializingWebGPU = false}
2645
+ __init136() {this.gpuCanvas = null}
2646
+ __init137() {this.gpuContext = null}
2495
2647
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
2496
- __init131() {this.gpuHasContent = false}
2497
- __init132() {this.mouseX = -9999}
2498
- __init133() {this.mouseY = -9999}
2499
- __init134() {this.pointerMoveListener = null}
2500
- __init135() {this.pointerLeaveListener = null}
2648
+ __init138() {this.gpuHasContent = false}
2649
+ __init139() {this.mouseX = -9999}
2650
+ __init140() {this.mouseY = -9999}
2651
+ __init141() {this.pointerMoveListener = null}
2652
+ __init142() {this.pointerLeaveListener = null}
2501
2653
  /** Element the pointer listeners are bound to (parent container if present,
2502
2654
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2503
- __init136() {this.pointerEventTarget = null}
2504
- __init137() {this.hasWarnedZeroSize = false}
2505
- __init138() {this.fontLoadHandler = null}
2655
+ __init143() {this.pointerEventTarget = null}
2656
+ __init144() {this.hasWarnedZeroSize = false}
2657
+ __init145() {this.fontLoadHandler = null}
2506
2658
  // ── Dev-mode warning infrastructure ──────────────────────────────
2507
2659
  //
2508
2660
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2520,7 +2672,7 @@ var Scene = (_class7 = class _Scene {
2520
2672
  return false;
2521
2673
  }
2522
2674
 
2523
- __init139() {this._devFrameCount = 0}
2675
+ __init146() {this._devFrameCount = 0}
2524
2676
  _devWarn(message) {
2525
2677
  if (!this._devActive) return;
2526
2678
  console.warn(`[vectojs/dev] ${message}`);
@@ -2564,7 +2716,7 @@ var Scene = (_class7 = class _Scene {
2564
2716
  };
2565
2717
  walkProjections(this.root);
2566
2718
  }
2567
- constructor(canvas, options = {}) {;_class7.prototype.__init25.call(this);_class7.prototype.__init26.call(this);_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);_class7.prototype.__init136.call(this);_class7.prototype.__init137.call(this);_class7.prototype.__init138.call(this);_class7.prototype.__init139.call(this);
2719
+ constructor(canvas, options = {}) {;_class7.prototype.__init27.call(this);_class7.prototype.__init28.call(this);_class7.prototype.__init29.call(this);_class7.prototype.__init30.call(this);_class7.prototype.__init31.call(this);_class7.prototype.__init32.call(this);_class7.prototype.__init33.call(this);_class7.prototype.__init34.call(this);_class7.prototype.__init35.call(this);_class7.prototype.__init36.call(this);_class7.prototype.__init37.call(this);_class7.prototype.__init38.call(this);_class7.prototype.__init39.call(this);_class7.prototype.__init40.call(this);_class7.prototype.__init41.call(this);_class7.prototype.__init42.call(this);_class7.prototype.__init43.call(this);_class7.prototype.__init44.call(this);_class7.prototype.__init45.call(this);_class7.prototype.__init46.call(this);_class7.prototype.__init47.call(this);_class7.prototype.__init48.call(this);_class7.prototype.__init49.call(this);_class7.prototype.__init50.call(this);_class7.prototype.__init51.call(this);_class7.prototype.__init52.call(this);_class7.prototype.__init53.call(this);_class7.prototype.__init54.call(this);_class7.prototype.__init55.call(this);_class7.prototype.__init56.call(this);_class7.prototype.__init57.call(this);_class7.prototype.__init58.call(this);_class7.prototype.__init59.call(this);_class7.prototype.__init60.call(this);_class7.prototype.__init61.call(this);_class7.prototype.__init62.call(this);_class7.prototype.__init63.call(this);_class7.prototype.__init64.call(this);_class7.prototype.__init65.call(this);_class7.prototype.__init66.call(this);_class7.prototype.__init67.call(this);_class7.prototype.__init68.call(this);_class7.prototype.__init69.call(this);_class7.prototype.__init70.call(this);_class7.prototype.__init71.call(this);_class7.prototype.__init72.call(this);_class7.prototype.__init73.call(this);_class7.prototype.__init74.call(this);_class7.prototype.__init75.call(this);_class7.prototype.__init76.call(this);_class7.prototype.__init77.call(this);_class7.prototype.__init78.call(this);_class7.prototype.__init79.call(this);_class7.prototype.__init80.call(this);_class7.prototype.__init81.call(this);_class7.prototype.__init82.call(this);_class7.prototype.__init83.call(this);_class7.prototype.__init84.call(this);_class7.prototype.__init85.call(this);_class7.prototype.__init86.call(this);_class7.prototype.__init87.call(this);_class7.prototype.__init88.call(this);_class7.prototype.__init89.call(this);_class7.prototype.__init90.call(this);_class7.prototype.__init91.call(this);_class7.prototype.__init92.call(this);_class7.prototype.__init93.call(this);_class7.prototype.__init94.call(this);_class7.prototype.__init95.call(this);_class7.prototype.__init96.call(this);_class7.prototype.__init97.call(this);_class7.prototype.__init98.call(this);_class7.prototype.__init99.call(this);_class7.prototype.__init100.call(this);_class7.prototype.__init101.call(this);_class7.prototype.__init102.call(this);_class7.prototype.__init103.call(this);_class7.prototype.__init104.call(this);_class7.prototype.__init105.call(this);_class7.prototype.__init106.call(this);_class7.prototype.__init107.call(this);_class7.prototype.__init108.call(this);_class7.prototype.__init109.call(this);_class7.prototype.__init110.call(this);_class7.prototype.__init111.call(this);_class7.prototype.__init112.call(this);_class7.prototype.__init113.call(this);_class7.prototype.__init114.call(this);_class7.prototype.__init115.call(this);_class7.prototype.__init116.call(this);_class7.prototype.__init117.call(this);_class7.prototype.__init118.call(this);_class7.prototype.__init119.call(this);_class7.prototype.__init120.call(this);_class7.prototype.__init121.call(this);_class7.prototype.__init122.call(this);_class7.prototype.__init123.call(this);_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);_class7.prototype.__init130.call(this);_class7.prototype.__init131.call(this);_class7.prototype.__init132.call(this);_class7.prototype.__init133.call(this);_class7.prototype.__init134.call(this);_class7.prototype.__init135.call(this);_class7.prototype.__init136.call(this);_class7.prototype.__init137.call(this);_class7.prototype.__init138.call(this);_class7.prototype.__init139.call(this);_class7.prototype.__init140.call(this);_class7.prototype.__init141.call(this);_class7.prototype.__init142.call(this);_class7.prototype.__init143.call(this);_class7.prototype.__init144.call(this);_class7.prototype.__init145.call(this);_class7.prototype.__init146.call(this);
2568
2720
  this.canvas = canvas;
2569
2721
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
2570
2722
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -4706,6 +4858,10 @@ var Scene = (_class7 = class _Scene {
4706
4858
  const computeEntities = this._computeEntitiesFor(this._structureVersion);
4707
4859
  if (computeEntities.length > 0) {
4708
4860
  const isMainRenderPath = renderer === this.renderer;
4861
+ if (isMainRenderPath) {
4862
+ this._particleReason = this._particleWasm ? "active" : "not-installed";
4863
+ this._particlePath = this._particleWasm ? "wasm" : "js";
4864
+ }
4709
4865
  if (isMainRenderPath && !this.device && !this.webgpuDisabled && !this.initializingWebGPU && !this.deviceLost) {
4710
4866
  this.initializingWebGPU = true;
4711
4867
  this.initWebGPUContext(computeEntities).then((newDevice) => {
@@ -4781,6 +4937,8 @@ var Scene = (_class7 = class _Scene {
4781
4937
  }
4782
4938
  this.device.queue.submit([commandEncoder.finish()]);
4783
4939
  if (this.gpuContext) this.gpuHasContent = true;
4940
+ this._particleReason = "active";
4941
+ this._particlePath = "webgpu";
4784
4942
  } catch (e) {
4785
4943
  console.error("WebGPU frame execution failed. Falling back.", e);
4786
4944
  this.deviceLost = true;
@@ -4802,13 +4960,25 @@ var Scene = (_class7 = class _Scene {
4802
4960
  }
4803
4961
  }
4804
4962
  if (this._particleWasm) {
4805
- entity.stepWithBackend(this._particleWasm, dt / 1e3, mx, my, this.width, this.height);
4963
+ if (!entity.stepWithBackend(
4964
+ this._particleWasm,
4965
+ dt / 1e3,
4966
+ mx,
4967
+ my,
4968
+ this.width,
4969
+ this.height
4970
+ )) {
4971
+ this._particleReason = "rejected";
4972
+ this._particlePath = "js";
4973
+ }
4806
4974
  } else {
4807
4975
  entity.updateCPU(dt / 1e3, mx, my, this.width, this.height);
4808
4976
  }
4809
4977
  }
4810
4978
  }
4811
4979
  } else if (isMainRenderer) {
4980
+ this._particleReason = "not-applicable";
4981
+ this._particlePath = "none";
4812
4982
  this.clearGPUCanvasIfStale();
4813
4983
  }
4814
4984
  renderer.clear();
@@ -4837,6 +5007,9 @@ var Scene = (_class7 = class _Scene {
4837
5007
  }
4838
5008
  };
4839
5009
  const wasmMain = isMainRenderer && this._wasm !== null && this._transformBackend === "wasm";
5010
+ if (isMainRenderer) {
5011
+ this._transformReason = wasmMain ? "active" : "not-installed";
5012
+ }
4840
5013
  if (wasmMain) {
4841
5014
  const updateWalk = (node) => {
4842
5015
  runUpdate(node);
@@ -5340,15 +5513,15 @@ var TextEntity = (_class8 = class extends _chunkAGP4VLF4js.Entity {
5340
5513
 
5341
5514
 
5342
5515
 
5343
- __init140() {this.nodes = []}
5516
+ __init147() {this.nodes = []}
5344
5517
 
5345
- __init141() {this.fillStyle = "#94a3b8"}
5346
- __init142() {this.strokeStyle = null}
5347
- __init143() {this.hoveredFillStyle = "#ffffff"}
5348
- __init144() {this.lineWidth = 1}
5349
- __init145() {this.isHovered = false}
5518
+ __init148() {this.fillStyle = "#94a3b8"}
5519
+ __init149() {this.strokeStyle = null}
5520
+ __init150() {this.hoveredFillStyle = "#ffffff"}
5521
+ __init151() {this.lineWidth = 1}
5522
+ __init152() {this.isHovered = false}
5350
5523
  constructor(text, atlas, maxWidth, fontSize = 24) {
5351
- super();_class8.prototype.__init140.call(this);_class8.prototype.__init141.call(this);_class8.prototype.__init142.call(this);_class8.prototype.__init143.call(this);_class8.prototype.__init144.call(this);_class8.prototype.__init145.call(this);;
5524
+ super();_class8.prototype.__init147.call(this);_class8.prototype.__init148.call(this);_class8.prototype.__init149.call(this);_class8.prototype.__init150.call(this);_class8.prototype.__init151.call(this);_class8.prototype.__init152.call(this);;
5352
5525
  this.text = text;
5353
5526
  this.atlas = atlas;
5354
5527
  this.fontSize = fontSize;
@@ -5463,15 +5636,15 @@ var TextEntity = (_class8 = class extends _chunkAGP4VLF4js.Entity {
5463
5636
  // src/components/GridTextEntity.ts
5464
5637
  var GridTextEntity = (_class9 = class extends _chunkAGP4VLF4js.Entity {
5465
5638
 
5466
- __init146() {this.fillStyle = "#ffffff"}
5467
- __init147() {this.grid = []}
5639
+ __init153() {this.fillStyle = "#ffffff"}
5640
+ __init154() {this.grid = []}
5468
5641
  // Array of rows
5469
- __init148() {this.cols = 0}
5470
- __init149() {this.rows = 0}
5642
+ __init155() {this.cols = 0}
5643
+ __init156() {this.rows = 0}
5471
5644
 
5472
5645
 
5473
5646
  constructor(_atlas, fontSize = 10) {
5474
- super();_class9.prototype.__init146.call(this);_class9.prototype.__init147.call(this);_class9.prototype.__init148.call(this);_class9.prototype.__init149.call(this);;
5647
+ super();_class9.prototype.__init153.call(this);_class9.prototype.__init154.call(this);_class9.prototype.__init155.call(this);_class9.prototype.__init156.call(this);;
5475
5648
  this.fontSize = fontSize;
5476
5649
  this.charWidth = fontSize * 1;
5477
5650
  this.charHeight = fontSize * 1.1;
@@ -5563,23 +5736,23 @@ var SplineEntity = (_class10 = class extends _chunkAGP4VLF4js.Entity {
5563
5736
 
5564
5737
 
5565
5738
 
5566
- __init150() {this.offscreen = null}
5567
- __init151() {this.baked = false}
5739
+ __init157() {this.offscreen = null}
5740
+ __init158() {this.baked = false}
5568
5741
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
5569
- __init152() {this.bakedWidth = 0}
5570
- __init153() {this.bakedHeight = 0}
5742
+ __init159() {this.bakedWidth = 0}
5743
+ __init160() {this.bakedHeight = 0}
5571
5744
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
5572
5745
 
5573
5746
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
5574
- __init154() {this.polylines = null}
5747
+ __init161() {this.polylines = null}
5575
5748
  /**
5576
5749
  * When `true`, the renderer draws a rounded-rect outline of the entity's
5577
5750
  * local bounds after painting the curves. Useful for drag feedback and
5578
5751
  * debugging hit areas. Defaults to `false`.
5579
5752
  */
5580
- __init155() {this.showBounds = false}
5753
+ __init162() {this.showBounds = false}
5581
5754
  constructor(doc, opts = {}) {
5582
- super();_class10.prototype.__init150.call(this);_class10.prototype.__init151.call(this);_class10.prototype.__init152.call(this);_class10.prototype.__init153.call(this);_class10.prototype.__init154.call(this);_class10.prototype.__init155.call(this);;
5755
+ super();_class10.prototype.__init157.call(this);_class10.prototype.__init158.call(this);_class10.prototype.__init159.call(this);_class10.prototype.__init160.call(this);_class10.prototype.__init161.call(this);_class10.prototype.__init162.call(this);;
5583
5756
  this.doc = doc;
5584
5757
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
5585
5758
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -5960,19 +6133,19 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
5960
6133
  // src/tree/DOMPortalEntity.ts
5961
6134
  var DOMPortalEntity = (_class11 = class extends _chunkAGP4VLF4js.Entity {
5962
6135
 
5963
- __init156() {this.isDOMPortal = true}
5964
- __init157() {this.domListeners = []}
5965
- __init158() {this.resizeObserver = null}
5966
- __init159() {this.domBound = false}
5967
- __init160() {this.cachedWidth = 100}
5968
- __init161() {this.cachedHeight = 100}
5969
- __init162() {this.lastWidth = ""}
5970
- __init163() {this.lastHeight = ""}
5971
- __init164() {this.lastTransform = ""}
5972
- __init165() {this.lastZIndex = ""}
5973
- __init166() {this.lastOpacity = ""}
6136
+ __init163() {this.isDOMPortal = true}
6137
+ __init164() {this.domListeners = []}
6138
+ __init165() {this.resizeObserver = null}
6139
+ __init166() {this.domBound = false}
6140
+ __init167() {this.cachedWidth = 100}
6141
+ __init168() {this.cachedHeight = 100}
6142
+ __init169() {this.lastWidth = ""}
6143
+ __init170() {this.lastHeight = ""}
6144
+ __init171() {this.lastTransform = ""}
6145
+ __init172() {this.lastZIndex = ""}
6146
+ __init173() {this.lastOpacity = ""}
5974
6147
  constructor(domElement, width, height, id) {
5975
- super(id);_class11.prototype.__init156.call(this);_class11.prototype.__init157.call(this);_class11.prototype.__init158.call(this);_class11.prototype.__init159.call(this);_class11.prototype.__init160.call(this);_class11.prototype.__init161.call(this);_class11.prototype.__init162.call(this);_class11.prototype.__init163.call(this);_class11.prototype.__init164.call(this);_class11.prototype.__init165.call(this);_class11.prototype.__init166.call(this);;
6148
+ super(id);_class11.prototype.__init163.call(this);_class11.prototype.__init164.call(this);_class11.prototype.__init165.call(this);_class11.prototype.__init166.call(this);_class11.prototype.__init167.call(this);_class11.prototype.__init168.call(this);_class11.prototype.__init169.call(this);_class11.prototype.__init170.call(this);_class11.prototype.__init171.call(this);_class11.prototype.__init172.call(this);_class11.prototype.__init173.call(this);;
5976
6149
  this.domElement = domElement;
5977
6150
  this.width = _nullishCoalesce(width, () => ( 0));
5978
6151
  this.height = _nullishCoalesce(height, () => ( 0));