@vectojs/core 1.16.3 → 1.17.1

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
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
3
 
4
4
 
@@ -469,7 +469,158 @@ function buildTreeStore(root) {
469
469
  return { store, indexOf };
470
470
  }
471
471
 
472
+ // src/wasm/hit-store.ts
473
+ function gatherHitAABBs(root, currentFrame) {
474
+ const slotEntity = [];
475
+ const boundless = [];
476
+ const minxs = [];
477
+ const minys = [];
478
+ const maxxs = [];
479
+ const maxys = [];
480
+ const scratch = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
481
+ const visit = (node) => {
482
+ const index = slotEntity.length;
483
+ slotEntity.push(node);
484
+ const bounds = node.getBounds();
485
+ if (bounds === null) {
486
+ boundless.push({ entity: node, index });
487
+ minxs.push(0);
488
+ minys.push(0);
489
+ maxxs.push(0);
490
+ maxys.push(0);
491
+ } else {
492
+ if (!node._readWorldCache(currentFrame, scratch)) {
493
+ const t = node.getWorldTransform();
494
+ scratch.a = t.a;
495
+ scratch.b = t.b;
496
+ scratch.c = t.c;
497
+ scratch.d = t.d;
498
+ scratch.e = t.e;
499
+ scratch.f = t.f;
500
+ }
501
+ const { a, b, c, d, e, f } = scratch;
502
+ let minX = Infinity;
503
+ let minY = Infinity;
504
+ let maxX = -Infinity;
505
+ let maxY = -Infinity;
506
+ for (let i = 0; i < 4; i++) {
507
+ const lx = i & 1 ? bounds.x + bounds.width : bounds.x;
508
+ const ly = i & 2 ? bounds.y + bounds.height : bounds.y;
509
+ const wx = a * lx + c * ly + e;
510
+ const wy = b * lx + d * ly + f;
511
+ if (wx < minX) minX = wx;
512
+ if (wx > maxX) maxX = wx;
513
+ if (wy < minY) minY = wy;
514
+ if (wy > maxY) maxY = wy;
515
+ }
516
+ minxs.push(minX);
517
+ minys.push(minY);
518
+ maxxs.push(maxX);
519
+ maxys.push(maxY);
520
+ }
521
+ const kids = node.children;
522
+ for (let i = 0; i < kids.length; i++) visit(kids[i]);
523
+ };
524
+ visit(root);
525
+ return {
526
+ count: slotEntity.length,
527
+ slotEntity,
528
+ boundless,
529
+ minx: Float64Array.from(minxs),
530
+ miny: Float64Array.from(minys),
531
+ maxx: Float64Array.from(maxxs),
532
+ maxy: Float64Array.from(maxys)
533
+ };
534
+ }
535
+
536
+ // src/wasm/hit-store-fused.ts
537
+ function gatherHitAABBsFromStore(root, aabbs, storeSlotEntity, out) {
538
+ const slotEntity = out.slotEntity;
539
+ const boundless = out.boundless;
540
+ slotEntity.length = 0;
541
+ boundless.length = 0;
542
+ let count = 0;
543
+ let capacity = out.minx.length;
544
+ let minx = out.minx;
545
+ let miny = out.miny;
546
+ let maxx = out.maxx;
547
+ let maxy = out.maxy;
548
+ const grow = (needed) => {
549
+ if (needed <= capacity) return;
550
+ let next = capacity || 256;
551
+ while (next < needed) next *= 2;
552
+ const nminx = new Float64Array(next);
553
+ const nminy = new Float64Array(next);
554
+ const nmaxx = new Float64Array(next);
555
+ const nmaxy = new Float64Array(next);
556
+ nminx.set(minx);
557
+ nminy.set(miny);
558
+ nmaxx.set(maxx);
559
+ nmaxy.set(maxy);
560
+ minx = nminx;
561
+ miny = nminy;
562
+ maxx = nmaxx;
563
+ maxy = nmaxy;
564
+ capacity = next;
565
+ };
566
+ let bailed = false;
567
+ const visit = (node) => {
568
+ if (bailed) return;
569
+ const index = count;
570
+ slotEntity.push(node);
571
+ count++;
572
+ grow(count);
573
+ if (node.getBounds() === null) {
574
+ boundless.push({ entity: node, index });
575
+ minx[index] = 0;
576
+ miny[index] = 0;
577
+ maxx[index] = 0;
578
+ maxy[index] = 0;
579
+ } else {
580
+ const slot = node._storeSlot;
581
+ if (slot < 0 || slot >= aabbs.aminx.length || storeSlotEntity[slot] !== node) {
582
+ bailed = true;
583
+ return;
584
+ }
585
+ minx[index] = aabbs.aminx[slot];
586
+ miny[index] = aabbs.aminy[slot];
587
+ maxx[index] = aabbs.amaxx[slot];
588
+ maxy[index] = aabbs.amaxy[slot];
589
+ }
590
+ const children = node.children;
591
+ for (let i = 0; i < children.length; i++) visit(children[i]);
592
+ };
593
+ visit(root);
594
+ if (bailed) return null;
595
+ out.count = count;
596
+ out.minx = minx;
597
+ out.miny = miny;
598
+ out.maxx = maxx;
599
+ out.maxy = maxy;
600
+ return out;
601
+ }
602
+ function createHitGatherBuffer() {
603
+ return {
604
+ count: 0,
605
+ slotEntity: [],
606
+ boundless: [],
607
+ minx: new Float64Array(256),
608
+ miny: new Float64Array(256),
609
+ maxx: new Float64Array(256),
610
+ maxy: new Float64Array(256)
611
+ };
612
+ }
613
+
472
614
  // src/wasm/backend.ts
615
+ var WASM_STATUS = {
616
+ OK: 0,
617
+ /** A count exceeded what `init` allocated. */
618
+ CAPACITY: 1,
619
+ /** A kernel ran before `init`. */
620
+ UNINITIALIZED: 2,
621
+ /** A sibling run addressed a slot or parent outside the store. */
622
+ BAD_RUN: 3
623
+ };
473
624
  var PAD2 = 8;
474
625
  var WasmTransformBackend = (_class2 = class {
475
626
  __init8() {this.available = true}
@@ -502,7 +653,7 @@ var WasmTransformBackend = (_class2 = class {
502
653
 
503
654
 
504
655
 
505
- constructor(instance) {;_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);
656
+ constructor(instance) {;_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);
506
657
  this.ex = instance.exports;
507
658
  }
508
659
  /** Compose world matrices for `store` in WASM, writing back into its
@@ -521,9 +672,13 @@ var WasmTransformBackend = (_class2 = class {
521
672
  this.vrp.set(store.runParent.subarray(0, rc));
522
673
  this.vrs.set(store.runStart.subarray(0, rc));
523
674
  this.vrl.set(store.runLen.subarray(0, rc));
524
- this.ex.set_run_count(rc);
525
- if (kernel === "scalar") this.ex.compose_scalar();
526
- else this.ex.compose_simd();
675
+ if (this.ex.set_run_count(rc) !== WASM_STATUS.OK) {
676
+ this.lastStatus = WASM_STATUS.CAPACITY;
677
+ return;
678
+ }
679
+ const status = kernel === "scalar" ? this.ex.compose_scalar() : this.ex.compose_simd();
680
+ this.lastStatus = status;
681
+ if (status !== WASM_STATUS.OK) return;
527
682
  store.wa.set(this.vwa.subarray(0, n));
528
683
  store.wb.set(this.vwb.subarray(0, n));
529
684
  store.wc.set(this.vwc.subarray(0, n));
@@ -543,8 +698,9 @@ var WasmTransformBackend = (_class2 = class {
543
698
  * {@link uploadRuns} after a topology change.
544
699
  */
545
700
  runKernel(kernel = "simd") {
546
- if (kernel === "scalar") this.ex.compose_scalar();
547
- else this.ex.compose_simd();
701
+ const status = kernel === "scalar" ? this.ex.compose_scalar() : this.ex.compose_simd();
702
+ this.lastStatus = status;
703
+ return status;
548
704
  }
549
705
  /** Upload only the run table + count (topology), leaving per-entity inputs to
550
706
  * the resident views. Call when the tree structure changes, not per frame. */
@@ -554,8 +710,13 @@ var WasmTransformBackend = (_class2 = class {
554
710
  this.vrp.set(store.runParent.subarray(0, rc));
555
711
  this.vrs.set(store.runStart.subarray(0, rc));
556
712
  this.vrl.set(store.runLen.subarray(0, rc));
557
- this.ex.set_run_count(rc);
713
+ this.lastStatus = this.ex.set_run_count(rc);
558
714
  }
715
+ /**
716
+ * Status of the most recent kernel or run-table call. `WASM_STATUS.OK` unless
717
+ * the crate rejected its arguments, in which case that call was a no-op.
718
+ */
719
+ __init11() {this.lastStatus = WASM_STATUS.OK}
559
720
  /** The resident wasm input views (`x,y,sx,sy,cos,sin,opacity`), valid until
560
721
  * the next capacity growth. Writing here is what makes uploads unnecessary. */
561
722
  inputView() {
@@ -623,6 +784,22 @@ var WasmTransformBackend = (_class2 = class {
623
784
  amaxy: this.vamaxy
624
785
  };
625
786
  }
787
+ /**
788
+ * Re-create the typed-array views if the memory buffer they were built over has
789
+ * been detached.
790
+ *
791
+ * Necessary because all backends of a Scene now share one instance, and
792
+ * therefore one linear memory: another backend's allocation (notably
793
+ * `hit_init`, which allocates its own grid arrays) can grow the memory and
794
+ * detach every view built over the old buffer. A detached `Float64Array` reads
795
+ * as length 0 and silently returns `undefined` for every index, so without this
796
+ * the transform store appears empty rather than failing loudly.
797
+ */
798
+ revalidateViews() {
799
+ if (this.cap === 0) return;
800
+ if (this.vwa.length === this.cap && this.vwa.buffer === this.ex.memory.buffer) return;
801
+ this.refreshViews();
802
+ }
626
803
  ensure(count, runCount) {
627
804
  if (count + PAD2 <= this.cap && runCount <= this.runCap) return;
628
805
  this.cap = count + PAD2;
@@ -665,106 +842,81 @@ var WasmTransformBackend = (_class2 = class {
665
842
  this.vrl = i32(this.ex.p_run_len());
666
843
  }
667
844
  }, _class2);
668
- async function instantiateAsync(bytes) {
669
- try {
670
- const { instance } = await WebAssembly.instantiate(bytes, {});
671
- return new WasmTransformBackend(instance);
672
- } catch (e3) {
673
- return null;
845
+
846
+ // src/wasm/anim-backend.ts
847
+ var PAD3 = 8;
848
+ var AnimBackend = (_class3 = class {
849
+
850
+ __init12() {this.springCap = 0}
851
+ __init13() {this.tweenCap = 0}
852
+
853
+
854
+ constructor(instance) {;_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);
855
+ this.ex = instance.exports;
674
856
  }
675
- }
676
- async function instantiateStreaming(source) {
677
- try {
678
- const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
679
- if (typeof WebAssembly.instantiateStreaming === "function") {
680
- const buffered = resp.clone();
681
- try {
682
- const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
683
- return new WasmTransformBackend(instance2);
684
- } catch (e4) {
685
- const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
686
- return new WasmTransformBackend(instance2);
687
- }
688
- }
689
- const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
690
- return new WasmTransformBackend(instance);
691
- } catch (e5) {
692
- return null;
857
+ /** The resident spring SoA input/output views, valid until the next capacity
858
+ * growth. Write gathered driver state here before calling {@link stepSprings}. */
859
+ springView() {
860
+ return this.sv;
693
861
  }
694
- }
695
-
696
- // src/wasm/hit-store.ts
697
- function gatherHitAABBs(root, currentFrame) {
698
- const slotEntity = [];
699
- const boundless = [];
700
- const minxs = [];
701
- const minys = [];
702
- const maxxs = [];
703
- const maxys = [];
704
- const scratch = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
705
- const visit = (node) => {
706
- const index = slotEntity.length;
707
- slotEntity.push(node);
708
- const bounds = node.getBounds();
709
- if (bounds === null) {
710
- boundless.push({ entity: node, index });
711
- minxs.push(0);
712
- minys.push(0);
713
- maxxs.push(0);
714
- maxys.push(0);
715
- } else {
716
- if (!node._readWorldCache(currentFrame, scratch)) {
717
- const t = node.getWorldTransform();
718
- scratch.a = t.a;
719
- scratch.b = t.b;
720
- scratch.c = t.c;
721
- scratch.d = t.d;
722
- scratch.e = t.e;
723
- scratch.f = t.f;
724
- }
725
- const { a, b, c, d, e, f } = scratch;
726
- let minX = Infinity;
727
- let minY = Infinity;
728
- let maxX = -Infinity;
729
- let maxY = -Infinity;
730
- for (let i = 0; i < 4; i++) {
731
- const lx = i & 1 ? bounds.x + bounds.width : bounds.x;
732
- const ly = i & 2 ? bounds.y + bounds.height : bounds.y;
733
- const wx = a * lx + c * ly + e;
734
- const wy = b * lx + d * ly + f;
735
- if (wx < minX) minX = wx;
736
- if (wx > maxX) maxX = wx;
737
- if (wy < minY) minY = wy;
738
- if (wy > maxY) maxY = wy;
739
- }
740
- minxs.push(minX);
741
- minys.push(minY);
742
- maxxs.push(maxX);
743
- maxys.push(maxY);
744
- }
745
- const kids = node.children;
746
- for (let i = 0; i < kids.length; i++) visit(kids[i]);
747
- };
748
- visit(root);
749
- return {
750
- count: slotEntity.length,
751
- slotEntity,
752
- boundless,
753
- minx: Float64Array.from(minxs),
754
- miny: Float64Array.from(minys),
755
- maxx: Float64Array.from(maxxs),
756
- maxy: Float64Array.from(maxys)
757
- };
758
- }
862
+ /** The resident tween SoA input/output views, valid until the next capacity
863
+ * growth. Write gathered driver state here before calling {@link stepTweens}. */
864
+ tweenView() {
865
+ return this.tv;
866
+ }
867
+ /**
868
+ * Size (and grow, if needed) capacity for `springCount` springs and
869
+ * `tweenCount` tweens. Call this BEFORE writing into {@link springView}/
870
+ * {@link tweenView} — a capacity growth detaches the previous views, so
871
+ * writing first and sizing after would write into a stale buffer.
872
+ */
873
+ ensure(springCount, tweenCount) {
874
+ if (springCount + PAD3 <= this.springCap && tweenCount + PAD3 <= this.tweenCap) return;
875
+ this.springCap = springCount + PAD3;
876
+ this.tweenCap = tweenCount + PAD3;
877
+ this.ex.anim_init(this.springCap, this.tweenCap);
878
+ this.refreshViews();
879
+ }
880
+ /** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
881
+ stepSprings(dtMs, count) {
882
+ this.ex.spring_step(dtMs / 1e3, count);
883
+ }
884
+ /** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
885
+ stepTweens(dtMs, count) {
886
+ this.ex.tween_step(dtMs, count);
887
+ }
888
+ refreshViews() {
889
+ const buf = this.ex.memory.buffer;
890
+ const sCap = this.springCap;
891
+ const tCap = this.tweenCap;
892
+ this.sv = {
893
+ val: new Float64Array(buf, this.ex.p_s_val(), sCap),
894
+ target: new Float64Array(buf, this.ex.p_s_target(), sCap),
895
+ vel: new Float64Array(buf, this.ex.p_s_vel(), sCap),
896
+ stiff: new Float64Array(buf, this.ex.p_s_stiff(), sCap),
897
+ damp: new Float64Array(buf, this.ex.p_s_damp(), sCap),
898
+ mass: new Float64Array(buf, this.ex.p_s_mass(), sCap)
899
+ };
900
+ this.tv = {
901
+ from: new Float64Array(buf, this.ex.p_t_from(), tCap),
902
+ to: new Float64Array(buf, this.ex.p_t_to(), tCap),
903
+ elapsed: new Float64Array(buf, this.ex.p_t_elapsed(), tCap),
904
+ dur: new Float64Array(buf, this.ex.p_t_dur(), tCap),
905
+ delay: new Float64Array(buf, this.ex.p_t_delay(), tCap),
906
+ ease: new Float64Array(buf, this.ex.p_t_ease(), tCap),
907
+ val: new Float64Array(buf, this.ex.p_t_val(), tCap)
908
+ };
909
+ }
910
+ }, _class3);
759
911
 
760
912
  // src/wasm/hit-backend.ts
761
- var PAD3 = 8;
913
+ var PAD4 = 8;
762
914
  var CELLS_PER_ENTITY_HINT = 4;
763
- var HitTestBackend = (_class3 = class {
915
+ var HitTestBackend = (_class4 = class {
764
916
 
765
- __init11() {this.entityCap = 0}
766
- __init12() {this.cellCap = 0}
767
- __init13() {this.itemCap = 0}
917
+ __init14() {this.entityCap = 0}
918
+ __init15() {this.cellCap = 0}
919
+ __init16() {this.itemCap = 0}
768
920
 
769
921
 
770
922
 
@@ -773,10 +925,10 @@ var HitTestBackend = (_class3 = class {
773
925
 
774
926
 
775
927
  /** Grid geometry from the last {@link ensure} call. */
776
- __init14() {this.gridW = 0}
777
- __init15() {this.gridH = 0}
778
- __init16() {this.cellSize = 64}
779
- constructor(instance) {;_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);
928
+ __init17() {this.gridW = 0}
929
+ __init18() {this.gridH = 0}
930
+ __init19() {this.cellSize = 64}
931
+ 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);
780
932
  this.ex = instance.exports;
781
933
  }
782
934
  /** The resident AABB input views (`minx/miny/maxx/maxy`), valid until the
@@ -830,10 +982,10 @@ var HitTestBackend = (_class3 = class {
830
982
  return this.vItems.subarray(start, start + count);
831
983
  }
832
984
  growIfNeeded(count, cellCount, itemCount) {
833
- if (count + PAD3 <= this.entityCap && cellCount <= this.cellCap && itemCount <= this.itemCap) {
985
+ if (count + PAD4 <= this.entityCap && cellCount <= this.cellCap && itemCount <= this.itemCap) {
834
986
  return;
835
987
  }
836
- this.entityCap = count + PAD3;
988
+ this.entityCap = count + PAD4;
837
989
  this.cellCap = Math.max(cellCount, 1);
838
990
  this.itemCap = Math.max(itemCount, count, 1);
839
991
  this.ex.hit_init(count, this.cellCap, this.itemCap);
@@ -854,135 +1006,15 @@ var HitTestBackend = (_class3 = class {
854
1006
  this.vCellCount = new Int32Array(buf, this.ex.p_h_cell_count(), cCap);
855
1007
  this.vItems = new Int32Array(buf, this.ex.p_h_items(), iCap);
856
1008
  }
857
- }, _class3);
858
- async function instantiateAsync2(bytes) {
859
- try {
860
- const { instance } = await WebAssembly.instantiate(bytes, {});
861
- return new HitTestBackend(instance);
862
- } catch (e6) {
863
- return null;
864
- }
865
- }
866
- async function instantiateStreaming2(source) {
867
- try {
868
- const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
869
- if (typeof WebAssembly.instantiateStreaming === "function") {
870
- const buffered = resp.clone();
871
- try {
872
- const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
873
- return new HitTestBackend(instance2);
874
- } catch (e7) {
875
- const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
876
- return new HitTestBackend(instance2);
877
- }
878
- }
879
- const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
880
- return new HitTestBackend(instance);
881
- } catch (e8) {
882
- return null;
883
- }
884
- }
885
-
886
- // src/wasm/anim-backend.ts
887
- var PAD4 = 8;
888
- var AnimBackend = (_class4 = class {
889
-
890
- __init17() {this.springCap = 0}
891
- __init18() {this.tweenCap = 0}
892
-
893
-
894
- constructor(instance) {;_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);
895
- this.ex = instance.exports;
896
- }
897
- /** The resident spring SoA input/output views, valid until the next capacity
898
- * growth. Write gathered driver state here before calling {@link stepSprings}. */
899
- springView() {
900
- return this.sv;
901
- }
902
- /** The resident tween SoA input/output views, valid until the next capacity
903
- * growth. Write gathered driver state here before calling {@link stepTweens}. */
904
- tweenView() {
905
- return this.tv;
906
- }
907
- /**
908
- * Size (and grow, if needed) capacity for `springCount` springs and
909
- * `tweenCount` tweens. Call this BEFORE writing into {@link springView}/
910
- * {@link tweenView} — a capacity growth detaches the previous views, so
911
- * writing first and sizing after would write into a stale buffer.
912
- */
913
- ensure(springCount, tweenCount) {
914
- if (springCount + PAD4 <= this.springCap && tweenCount + PAD4 <= this.tweenCap) return;
915
- this.springCap = springCount + PAD4;
916
- this.tweenCap = tweenCount + PAD4;
917
- this.ex.anim_init(this.springCap, this.tweenCap);
918
- this.refreshViews();
919
- }
920
- /** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
921
- stepSprings(dtMs, count) {
922
- this.ex.spring_step(dtMs / 1e3, count);
923
- }
924
- /** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
925
- stepTweens(dtMs, count) {
926
- this.ex.tween_step(dtMs, count);
927
- }
928
- refreshViews() {
929
- const buf = this.ex.memory.buffer;
930
- const sCap = this.springCap;
931
- const tCap = this.tweenCap;
932
- this.sv = {
933
- val: new Float64Array(buf, this.ex.p_s_val(), sCap),
934
- target: new Float64Array(buf, this.ex.p_s_target(), sCap),
935
- vel: new Float64Array(buf, this.ex.p_s_vel(), sCap),
936
- stiff: new Float64Array(buf, this.ex.p_s_stiff(), sCap),
937
- damp: new Float64Array(buf, this.ex.p_s_damp(), sCap),
938
- mass: new Float64Array(buf, this.ex.p_s_mass(), sCap)
939
- };
940
- this.tv = {
941
- from: new Float64Array(buf, this.ex.p_t_from(), tCap),
942
- to: new Float64Array(buf, this.ex.p_t_to(), tCap),
943
- elapsed: new Float64Array(buf, this.ex.p_t_elapsed(), tCap),
944
- dur: new Float64Array(buf, this.ex.p_t_dur(), tCap),
945
- delay: new Float64Array(buf, this.ex.p_t_delay(), tCap),
946
- ease: new Float64Array(buf, this.ex.p_t_ease(), tCap),
947
- val: new Float64Array(buf, this.ex.p_t_val(), tCap)
948
- };
949
- }
950
1009
  }, _class4);
951
- async function instantiateAsync3(bytes) {
952
- try {
953
- const { instance } = await WebAssembly.instantiate(bytes, {});
954
- return new AnimBackend(instance);
955
- } catch (e9) {
956
- return null;
957
- }
958
- }
959
- async function instantiateStreaming3(source) {
960
- try {
961
- const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
962
- if (typeof WebAssembly.instantiateStreaming === "function") {
963
- const buffered = resp.clone();
964
- try {
965
- const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
966
- return new AnimBackend(instance2);
967
- } catch (e10) {
968
- const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
969
- return new AnimBackend(instance2);
970
- }
971
- }
972
- const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
973
- return new AnimBackend(instance);
974
- } catch (e11) {
975
- return null;
976
- }
977
- }
978
1010
 
979
1011
  // src/wasm/particle-backend.ts
980
1012
  var PAD5 = 8;
981
1013
  var ParticleBackend = (_class5 = class {
982
1014
 
983
- __init19() {this.cap = 0}
1015
+ __init20() {this.cap = 0}
984
1016
 
985
- constructor(instance) {;_class5.prototype.__init19.call(this);
1017
+ constructor(instance) {;_class5.prototype.__init20.call(this);
986
1018
  this.ex = instance.exports;
987
1019
  }
988
1020
  /** The resident SoA views, valid until the next capacity growth. */
@@ -1067,33 +1099,95 @@ var ParticleBackend = (_class5 = class {
1067
1099
  };
1068
1100
  }
1069
1101
  }, _class5);
1070
- async function instantiateAsync4(bytes) {
1071
- try {
1072
- const { instance } = await WebAssembly.instantiate(bytes, {});
1073
- return new ParticleBackend(instance);
1074
- } catch (e12) {
1075
- return null;
1102
+
1103
+ // src/wasm/runtime.ts
1104
+ var moduleCache = /* @__PURE__ */ new Map();
1105
+ function cacheKey(source) {
1106
+ if (typeof source === "string") return source;
1107
+ if (source instanceof URL) return source.href;
1108
+ return null;
1109
+ }
1110
+ function compileBytes(bytes) {
1111
+ return new WebAssembly.Module(bytes);
1112
+ }
1113
+ async function compileRemote(source) {
1114
+ const response = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
1115
+ if (typeof WebAssembly.compileStreaming === "function") {
1116
+ const buffered = response.clone();
1117
+ try {
1118
+ return await WebAssembly.compileStreaming(response);
1119
+ } catch (e3) {
1120
+ return await WebAssembly.compile(await buffered.arrayBuffer());
1121
+ }
1076
1122
  }
1123
+ return await WebAssembly.compile(await response.arrayBuffer());
1077
1124
  }
1078
- async function instantiateStreaming4(source) {
1079
- try {
1080
- const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
1081
- if (typeof WebAssembly.instantiateStreaming === "function") {
1082
- const buffered = resp.clone();
1125
+ async function loadCoreWasmModule(source) {
1126
+ const key = cacheKey(source);
1127
+ if (key !== null) {
1128
+ const hit = moduleCache.get(key);
1129
+ if (hit) {
1083
1130
  try {
1084
- const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
1085
- return new ParticleBackend(instance2);
1086
- } catch (e13) {
1087
- const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
1088
- return new ParticleBackend(instance2);
1131
+ return await hit;
1132
+ } catch (e4) {
1133
+ moduleCache.delete(key);
1134
+ return null;
1089
1135
  }
1090
1136
  }
1091
- const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
1092
- return new ParticleBackend(instance);
1093
- } catch (e14) {
1137
+ }
1138
+ const pending = source instanceof ArrayBuffer || ArrayBuffer.isView(source) ? Promise.resolve().then(() => compileBytes(source)) : compileRemote(source);
1139
+ if (key !== null) moduleCache.set(key, pending);
1140
+ try {
1141
+ return await pending;
1142
+ } catch (e5) {
1143
+ if (key !== null) moduleCache.delete(key);
1144
+ return null;
1145
+ }
1146
+ }
1147
+ var CoreWasmRuntime = (_class6 = class {
1148
+
1149
+ __init21() {this.transformBackend = null}
1150
+ __init22() {this.animBackend = null}
1151
+ __init23() {this.hitBackend = null}
1152
+ __init24() {this.particleBackendInstance = null}
1153
+ constructor(instance) {;_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);
1154
+ this.instance = instance;
1155
+ }
1156
+ /**
1157
+ * Backends are constructed lazily and memoised: each one's constructor calls
1158
+ * into the instance to size its store and build typed-array views, so building
1159
+ * all four up front would pay for accelerators the Scene never enables.
1160
+ */
1161
+ transform() {
1162
+ if (!this.transformBackend) this.transformBackend = new WasmTransformBackend(this.instance);
1163
+ return this.transformBackend;
1164
+ }
1165
+ anim() {
1166
+ if (!this.animBackend) this.animBackend = new AnimBackend(this.instance);
1167
+ return this.animBackend;
1168
+ }
1169
+ hit() {
1170
+ if (!this.hitBackend) this.hitBackend = new HitTestBackend(this.instance);
1171
+ return this.hitBackend;
1172
+ }
1173
+ particle() {
1174
+ if (!this.particleBackendInstance)
1175
+ this.particleBackendInstance = new ParticleBackend(this.instance);
1176
+ return this.particleBackendInstance;
1177
+ }
1178
+ }, _class6);
1179
+ function createCoreWasmRuntime(module) {
1180
+ try {
1181
+ return new CoreWasmRuntime(new WebAssembly.Instance(module, {}));
1182
+ } catch (e6) {
1094
1183
  return null;
1095
1184
  }
1096
1185
  }
1186
+ async function loadCoreWasmRuntime(source) {
1187
+ const module = await loadCoreWasmModule(source);
1188
+ if (!module) return null;
1189
+ return createCoreWasmRuntime(module);
1190
+ }
1097
1191
 
1098
1192
  // src/tree/Scene.ts
1099
1193
  var _text = require('@vectojs/text'); _createStarExport(_text);
@@ -1383,13 +1477,13 @@ function extendSelection(selection, anchor, focus) {
1383
1477
  try {
1384
1478
  selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
1385
1479
  return;
1386
- } catch (e15) {
1480
+ } catch (e7) {
1387
1481
  }
1388
1482
  try {
1389
1483
  selection.collapse(anchor.node, anchor.offset);
1390
1484
  selection.extend(focus.node, focus.offset);
1391
1485
  return;
1392
- } catch (e16) {
1486
+ } catch (e8) {
1393
1487
  }
1394
1488
  const anchorRange = document.createRange();
1395
1489
  anchorRange.setStart(anchor.node, anchor.offset);
@@ -1408,7 +1502,7 @@ function extendSelection(selection, anchor, focus) {
1408
1502
  selection.removeAllRanges();
1409
1503
  selection.addRange(range);
1410
1504
  }
1411
- var Scene = (_class6 = class _Scene {
1505
+ var Scene = (_class7 = class _Scene {
1412
1506
  static __initStatic() {this.webglCreator = null}
1413
1507
  static __initStatic2() {this.webgpuManagerClass = null}
1414
1508
  /** Upper bound (ms) on a single frame's `dt`. Caps the giant elapsed gap a
@@ -1424,15 +1518,15 @@ var Scene = (_class6 = class _Scene {
1424
1518
 
1425
1519
 
1426
1520
 
1427
- __init20() {this.isRunning = false}
1521
+ __init25() {this.isRunning = false}
1428
1522
  /** Whether the canvas is at least partially in the viewport. When it scrolls
1429
1523
  * fully off-screen the rAF loop pauses (stops rescheduling) instead of
1430
1524
  * burning frames on a scene nobody can see; an IntersectionObserver resumes
1431
1525
  * it on re-entry. Defaults true (and stays true where IntersectionObserver
1432
1526
  * is unavailable, e.g. SSR/jsdom, so behavior is unchanged there). */
1433
- __init21() {this._canvasOnScreen = true}
1434
- __init22() {this._canvasObserver = null}
1435
- __init23() {this.lastTime = 0}
1527
+ __init26() {this._canvasOnScreen = true}
1528
+ __init27() {this._canvasObserver = null}
1529
+ __init28() {this.lastTime = 0}
1436
1530
 
1437
1531
  /**
1438
1532
  * Redraw strategy:
@@ -1441,31 +1535,31 @@ var Scene = (_class6 = class _Scene {
1441
1535
  * {@link markDirty}) or while an animation is pending. Ideal for static /
1442
1536
  * event-driven UIs where idle frames should cost ~0.
1443
1537
  */
1444
- __init24() {this.renderMode = "always"}
1445
- __init25() {this.dirty = true}
1538
+ __init29() {this.renderMode = "always"}
1539
+ __init30() {this.dirty = true}
1446
1540
  /** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
1447
- __init26() {this.autoThrottle = true}
1541
+ __init31() {this.autoThrottle = true}
1448
1542
  // --- Frame telemetry (read via `frameStats`) ---------------------------
1449
1543
  /** Wall-clock ms spent inside the last `render()` call. */
1450
- __init27() {this._lastFrameMs = 0}
1544
+ __init32() {this._lastFrameMs = 0}
1451
1545
  /** Rolling exponential average of rendered-frame intervals, in ms. */
1452
- __init28() {this._avgFrameIntervalMs = 0}
1546
+ __init33() {this._avgFrameIntervalMs = 0}
1453
1547
  /** dt (ms) handed to the last rendered frame. */
1454
- __init29() {this._lastDt = 0}
1548
+ __init34() {this._lastDt = 0}
1455
1549
  /** Count of frames actually rendered since the loop started. */
1456
- __init30() {this._renderedFrames = 0}
1550
+ __init35() {this._renderedFrames = 0}
1457
1551
  /** Count of rAF ticks skipped (idle / capped) since the loop started. */
1458
- __init31() {this._skippedFrames = 0}
1552
+ __init36() {this._skippedFrames = 0}
1459
1553
  /** `time` of the previous *rendered* frame, for interval measurement. */
1460
- __init32() {this._lastRenderTick = 0}
1554
+ __init37() {this._lastRenderTick = 0}
1461
1555
  /**
1462
1556
  * Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
1463
1557
  * the loop renders at most `maxFPS` times per second; animations still run,
1464
1558
  * just less often. See {@link SceneOptions.maxFPS}.
1465
1559
  */
1466
- __init33() {this.maxFPS = 60}
1560
+ __init38() {this.maxFPS = 60}
1467
1561
  /** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
1468
- __init34() {this.respectReducedMotion = true}
1562
+ __init39() {this.respectReducedMotion = true}
1469
1563
  /**
1470
1564
  * Reading direction for accessibility tab/traversal order (`'ltr'` default,
1471
1565
  * `'rtl'`). Controls the inline sort within a visual row in
@@ -1481,15 +1575,15 @@ var Scene = (_class6 = class _Scene {
1481
1575
  this.a11yNeedsReorder = true;
1482
1576
  }
1483
1577
  }
1484
- __init35() {this._readingDirection = "ltr"}
1578
+ __init40() {this._readingDirection = "ltr"}
1485
1579
  /** Cached media-query list; `.matches` is read live each frame. */
1486
- __init36() {this.reducedMotionQuery = null}
1580
+ __init41() {this.reducedMotionQuery = null}
1487
1581
  /** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
1488
1582
  * canvas gets NO automatic forced-colors treatment from the browser (it's
1489
1583
  * opaque pixels), so components must read {@link forcedColors} and repaint
1490
1584
  * with system colors themselves; a change listener repaints idle scenes. */
1491
- __init37() {this.forcedColorsQuery = null}
1492
- __init38() {this.forcedColorsChangeHandler = null}
1585
+ __init42() {this.forcedColorsQuery = null}
1586
+ __init43() {this.forcedColorsChangeHandler = null}
1493
1587
  /** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
1494
1588
  get prefersReducedMotion() {
1495
1589
  return this.respectReducedMotion && !!_optionalChain([this, 'access', _21 => _21.reducedMotionQuery, 'optionalAccess', _22 => _22.matches]);
@@ -1509,82 +1603,82 @@ var Scene = (_class6 = class _Scene {
1509
1603
  * Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
1510
1604
  * frame. See {@link SceneOptions.a11ySyncInterval}.
1511
1605
  */
1512
- __init39() {this.a11ySyncInterval = 0}
1606
+ __init44() {this.a11ySyncInterval = 0}
1513
1607
  /** Timestamp of the last a11y sync, for throttling. */
1514
- __init40() {this.lastA11ySync = -Infinity}
1608
+ __init45() {this.lastA11ySync = -Infinity}
1515
1609
  /** True if we skipped an a11y sync during animation and need to sync when at rest. */
1516
- __init41() {this.a11yPendingSyncAfterAnimation = false}
1610
+ __init46() {this.a11yPendingSyncAfterAnimation = false}
1517
1611
  // A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
1518
1612
  // whole projection degrades to a no-op so the engine's logic stays usable
1519
1613
  // server-side (e.g. headless layout / vector export) without jsdom.
1520
1614
 
1521
- __init42() {this.a11yElements = /* @__PURE__ */ new Map()}
1615
+ __init47() {this.a11yElements = /* @__PURE__ */ new Map()}
1522
1616
  /** DOM nodes mirroring static text content, keyed by entity id. */
1523
- __init43() {this.contentElements = /* @__PURE__ */ new Map()}
1617
+ __init48() {this.contentElements = /* @__PURE__ */ new Map()}
1524
1618
  /** Pending cold font-calibration frame per projected grid entity. */
1525
- __init44() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1619
+ __init49() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
1526
1620
  /** Detached, untransformed font probes used by the cold calibration pass. */
1527
- __init45() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1621
+ __init50() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
1528
1622
  /** Invalidates grid font calibration after browser font availability changes. */
1529
- __init46() {this.contentFontEpoch = 0}
1623
+ __init51() {this.contentFontEpoch = 0}
1530
1624
  /** Cached Canvas-to-client scale for the current font/viewport epoch. */
1531
- __init47() {this.contentMetricScaleEpoch = -1}
1532
- __init48() {this.contentMetricScaleX = 1}
1533
- __init49() {this.contentProjectionEnabled = true}
1625
+ __init52() {this.contentMetricScaleEpoch = -1}
1626
+ __init53() {this.contentMetricScaleX = 1}
1627
+ __init54() {this.contentProjectionEnabled = true}
1534
1628
  // Virtualization margin (px) for content projection; `undefined` → one
1535
1629
  // viewport height, resolved at sync time. `Infinity` = materialize everything.
1536
- __init50() {this.contentProjectionMargin = void 0}
1630
+ __init55() {this.contentProjectionMargin = void 0}
1537
1631
  /**
1538
1632
  * True while a text-selection drag that started on a projection's blank
1539
1633
  * region (no text node under the press) is being driven manually — the
1540
1634
  * browser has no native anchor for it, so mousemove extends the Selection
1541
1635
  * from the position we resolved ourselves.
1542
1636
  */
1543
- __init51() {this.blankRegionSelectionDrag = false}
1544
- __init52() {this.contentSelectionAnchor = null}
1545
- __init53() {this.contentSelectionEndListener = null}
1637
+ __init56() {this.blankRegionSelectionDrag = false}
1638
+ __init57() {this.contentSelectionAnchor = null}
1639
+ __init58() {this.contentSelectionEndListener = null}
1546
1640
  // Animation/interactive flags collected during the render walk (tree-walk
1547
1641
  // fusion): the loop reads last frame's answers instead of re-walking the
1548
1642
  // tree up to 4× per tick. Start true so the first tick stays conservative.
1549
- __init54() {this.frameHadAnimation = true}
1550
- __init55() {this.frameHadInteractive = true}
1643
+ __init59() {this.frameHadAnimation = true}
1644
+ __init60() {this.frameHadInteractive = true}
1551
1645
 
1552
1646
  /** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
1553
1647
  * (window moved between monitors, browser zoom) so the canvas backing store
1554
1648
  * can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
1555
1649
  * A resolution media query only fires when leaving its exact value, so the
1556
1650
  * handler re-arms a fresh query for the new DPR each time. */
1557
- __init56() {this.dprMediaQuery = null}
1651
+ __init61() {this.dprMediaQuery = null}
1558
1652
  /** For embedded (`disableWindowResize`) scenes: observes the canvas element so
1559
1653
  * a CSS/layout-driven size change re-runs `resize()`. A window `resize`
1560
1654
  * listener never fires for these (the window isn't what changed), so without
1561
1655
  * this an embedded canvas stayed at its initial size forever. */
1562
- __init57() {this.canvasResizeObserver = null}
1563
- __init58() {this.dprChangeHandler = null}
1564
- __init59() {this.focusedA11yElement = null}
1656
+ __init62() {this.canvasResizeObserver = null}
1657
+ __init63() {this.dprChangeHandler = null}
1658
+ __init64() {this.focusedA11yElement = null}
1565
1659
  /** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
1566
1660
  * style writes entirely. Reset to `null` to force the next sync (a new overlay
1567
1661
  * layer was created and has never been positioned). */
1568
- __init60() {this._overlayGeometry = null}
1662
+ __init65() {this._overlayGeometry = null}
1569
1663
  /** Shadow elements the pointer is currently inside. Lets a removal that happens
1570
1664
  * mid-hover synthesize the `pointerleave` the browser never sends for a
1571
1665
  * detached element, so the entity doesn't keep its hover state. */
1572
- __init61() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1666
+ __init66() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
1573
1667
  /** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
1574
1668
  * pruned (virtualization/streaming/removal) while it holds focus, we move
1575
1669
  * focus here instead of letting the browser drop it to <body> — keeping the
1576
1670
  * screen-reader virtual cursor inside the scene's a11y region. */
1577
- __init62() {this.focusSentinel = null}
1578
- __init63() {this.caretBlinkTimer = null}
1579
- __init64() {this.a11yNeedsReorder = true}
1580
- __init65() {this.portalRoot = null}
1581
- __init66() {this.fullViewportElements = []}
1582
- __init67() {this.normalElements = []}
1583
- __init68() {this.activeIds = /* @__PURE__ */ new Set()}
1584
- __init69() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1585
- __init70() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1586
- __init71() {this.portalEntities = /* @__PURE__ */ new Map()}
1587
- __init72() {this.renderOrderCounter = 0}
1671
+ __init67() {this.focusSentinel = null}
1672
+ __init68() {this.caretBlinkTimer = null}
1673
+ __init69() {this.a11yNeedsReorder = true}
1674
+ __init70() {this.portalRoot = null}
1675
+ __init71() {this.fullViewportElements = []}
1676
+ __init72() {this.normalElements = []}
1677
+ __init73() {this.activeIds = /* @__PURE__ */ new Set()}
1678
+ __init74() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
1679
+ __init75() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
1680
+ __init76() {this.portalEntities = /* @__PURE__ */ new Map()}
1681
+ __init77() {this.renderOrderCounter = 0}
1588
1682
  /**
1589
1683
  * Monotonic render-frame counter, bumped once per authoritative `render()`
1590
1684
  * pass. Entities stamp their per-frame world-matrix cache with this value and
@@ -1593,7 +1687,7 @@ var Scene = (_class6 = class _Scene {
1593
1687
  * back to the ancestor walk. Public for the same reason `Entity._getTrig`/
1594
1688
  * `_setWorldCache` are: it is a cross-class render-internal contract.
1595
1689
  */
1596
- __init73() {this.currentFrame = 0}
1690
+ __init78() {this.currentFrame = 0}
1597
1691
  // ── WASM transform backend (invisible accelerator) ──────────────────────────
1598
1692
  // When `_transformBackend === 'wasm'`, the main render walk sources each
1599
1693
  // entity's world matrix from an SoA store composed by `_wasm` (see
@@ -1601,24 +1695,24 @@ var Scene = (_class6 = class _Scene {
1601
1695
  // fallback and the default: a null backend, a non-main renderer, or any entity
1602
1696
  // absent from the store all fall back to the JS composition, so WASM can only
1603
1697
  // ever change *how fast* a world matrix is produced, never *what* it is.
1604
- __init74() {this._wasm = null}
1605
- __init75() {this._transformBackend = "js"}
1698
+ __init79() {this._wasm = null}
1699
+ __init80() {this._transformBackend = "js"}
1606
1700
  // Resident store state (Stage 3). The store layout — slot assignment + sibling
1607
1701
  // runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
1608
1702
  // structure changes (add/remove/reparent bump `_structureVersion`). Between
1609
1703
  // rebuilds the per-frame cost is: gather each entity's transform into the
1610
1704
  // resident wasm input view + run the kernel — no reallocation, no readback.
1611
- __init76() {this._treeStore = null}
1612
- __init77() {this._slotEntity = []}
1705
+ __init81() {this._treeStore = null}
1706
+ __init82() {this._slotEntity = []}
1613
1707
  // store slot -> entity (also validates slots)
1614
- __init78() {this._wasmInputs = null}
1615
- __init79() {this._wasmWorld = null}
1616
- __init80() {this._structureVersion = 0}
1617
- __init81() {this._storeStructureVersion = -1}
1708
+ __init83() {this._wasmInputs = null}
1709
+ __init84() {this._wasmWorld = null}
1710
+ __init85() {this._structureVersion = 0}
1711
+ __init86() {this._storeStructureVersion = -1}
1618
1712
  // Cached list of ComputeParticleEntity instances in the tree, keyed by the
1619
1713
  // structure version it was gathered at. Rebuilt only on a topology change.
1620
- __init82() {this._computeEntities = []}
1621
- __init83() {this._computeEntitiesVersion = -1}
1714
+ __init87() {this._computeEntities = []}
1715
+ __init88() {this._computeEntitiesVersion = -1}
1622
1716
  /** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
1623
1717
  * it. Called by `Entity.add`/`remove` (topology changes only). */
1624
1718
  markStructureChanged() {
@@ -1674,10 +1768,9 @@ var Scene = (_class6 = class _Scene {
1674
1768
  * Resolves `true` if WASM is now active, `false` if the JS path remains.
1675
1769
  */
1676
1770
  async enableWasmTransforms(source) {
1677
- const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
1678
- const backend = isBytes ? await instantiateAsync(source) : await instantiateStreaming(source);
1679
- if (!backend) return false;
1680
- this.setTransformBackend(backend);
1771
+ const runtime = await this.ensureWasmRuntime(source);
1772
+ if (!runtime) return false;
1773
+ this.setTransformBackend(runtime.transform());
1681
1774
  return true;
1682
1775
  }
1683
1776
  // ── WASM hit-test backend (invisible accelerator, G3) ───────────────────────
@@ -1691,16 +1784,72 @@ var Scene = (_class6 = class _Scene {
1691
1784
  // ever change *how fast* a hit is found, never *which* entity is returned —
1692
1785
  // every grid candidate is re-confirmed against its own precise
1693
1786
  // isPointInside before being trusted (see hit-store.ts / hit-backend.ts).
1694
- __init84() {this._hitWasm = null}
1787
+ /**
1788
+ * The one WASM instance this Scene's accelerators share.
1789
+ *
1790
+ * Each `enableWasm*` used to instantiate the binary itself, so enabling all
1791
+ * four compiled the same module four times and held four linear memories. The
1792
+ * Rust crate already keeps transform/anim/hit/particle in separate statics, so
1793
+ * one instance serves all of them without aliasing. The compiled module is
1794
+ * cached globally; the instance is per-Scene, which is the isolation that
1795
+ * actually matters.
1796
+ */
1797
+ __init89() {this._wasmRuntime = null}
1798
+ /**
1799
+ * Load (or reuse) this Scene's shared WASM runtime.
1800
+ *
1801
+ * Returns `null` on any failure — CSP `wasm-unsafe-eval`, a 404, corrupt bytes,
1802
+ * unsupported SIMD — so every caller keeps its JS path. Failure is the default
1803
+ * state here, not an error path.
1804
+ */
1805
+ async ensureWasmRuntime(source) {
1806
+ if (this._wasmRuntime) return this._wasmRuntime;
1807
+ const runtime = await loadCoreWasmRuntime(source);
1808
+ if (!runtime) return null;
1809
+ if (this._wasmRuntime) return this._wasmRuntime;
1810
+ this._wasmRuntime = runtime;
1811
+ return runtime;
1812
+ }
1813
+ /**
1814
+ * Install a pre-built runtime, so several Scenes can share one compile while
1815
+ * each keeps its own stores. Pass `null` to detach (backends already installed
1816
+ * keep working; only subsequent `enableWasm*` calls re-load).
1817
+ */
1818
+ setWasmRuntime(runtime) {
1819
+ this._wasmRuntime = runtime;
1820
+ }
1821
+ /** The shared WASM runtime, if one has been loaded. */
1822
+ get wasmRuntime() {
1823
+ return this._wasmRuntime;
1824
+ }
1825
+ __init90() {this._hitWasm = null}
1695
1826
  // Cache key: which frame + structure version the grid was last (successfully,
1696
1827
  // non-overflowing) built for. findEntityAt is called ad-hoc (pointer
1697
1828
  // hover/click), not every frame, so the grid is refreshed lazily on demand
1698
1829
  // rather than proactively every render() — unlike the transform store, which
1699
1830
  // every frame's draw depends on.
1700
- __init85() {this._hitGridFrame = -1}
1701
- __init86() {this._hitGridOk = false}
1702
- __init87() {this._hitSlotEntity = []}
1703
- __init88() {this._hitBoundless = []}
1831
+ __init91() {this._hitGridFrame = -1}
1832
+ __init92() {this._hitGridOk = false}
1833
+ __init93() {this._hitSlotEntity = []}
1834
+ __init94() {this._hitBoundless = []}
1835
+ /** Reused buffer for the fused gather, so a pointer query allocates nothing. */
1836
+ __init95() {this._hitGatherBuffer = null}
1837
+ /**
1838
+ * Whether the last grid build sourced its AABBs from the WASM transform store
1839
+ * rather than recomputing them in JS. Diagnostic only — both paths must
1840
+ * produce the same entity for a given point.
1841
+ */
1842
+ __init96() {this._hitFusedGather = false}
1843
+ /**
1844
+ * Whether `compute_aabbs` has run against the current frame's world matrices.
1845
+ * The AABB pass is only meaningful after a `compose_*`, so the fused gather
1846
+ * must not read the views before then.
1847
+ */
1848
+ __init97() {this._wasmAabbsFresh = false}
1849
+ /** Did the last hit-grid build use the fused (WASM-store) gather? */
1850
+ get hitGatherPath() {
1851
+ return this._hitFusedGather ? "fused" : "js";
1852
+ }
1704
1853
  /** Which backend answers `findEntityAt` for the main tree. */
1705
1854
  get hitTestBackend() {
1706
1855
  return this._hitWasm ? "wasm" : "js";
@@ -1719,10 +1868,9 @@ var Scene = (_class6 = class _Scene {
1719
1868
  * state, not an error path. Resolves `true` if WASM is now active.
1720
1869
  */
1721
1870
  async enableWasmHitTest(source) {
1722
- const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
1723
- const backend = isBytes ? await instantiateAsync2(source) : await instantiateStreaming2(source);
1724
- if (!backend) return false;
1725
- this.setHitTestBackend(backend);
1871
+ const runtime = await this.ensureWasmRuntime(source);
1872
+ if (!runtime) return false;
1873
+ this.setHitTestBackend(runtime.hit());
1726
1874
  return true;
1727
1875
  }
1728
1876
  /**
@@ -1739,7 +1887,22 @@ var Scene = (_class6 = class _Scene {
1739
1887
  const backend = this._hitWasm;
1740
1888
  if (!backend) return false;
1741
1889
  if (this._hitGridFrame === this.currentFrame) return this._hitGridOk;
1742
- const gathered = gatherHitAABBs(this.root, this.currentFrame);
1890
+ let gathered = null;
1891
+ if (this._wasm && this._ensureWasmAabbs()) {
1892
+ this._hitGatherBuffer ??= createHitGatherBuffer();
1893
+ this._wasm.revalidateViews();
1894
+ gathered = gatherHitAABBsFromStore(
1895
+ this.root,
1896
+ this._wasm.aabbView(),
1897
+ this._slotEntity,
1898
+ this._hitGatherBuffer
1899
+ );
1900
+ if (gathered) this._hitFusedGather = true;
1901
+ }
1902
+ if (!gathered) {
1903
+ this._hitFusedGather = false;
1904
+ gathered = gatherHitAABBs(this.root, this.currentFrame);
1905
+ }
1743
1906
  backend.ensure(gathered.count, this.width, this.height, 64);
1744
1907
  const view = backend.inputView();
1745
1908
  view.minx.set(gathered.minx.subarray(0, gathered.count));
@@ -1796,25 +1959,25 @@ var Scene = (_class6 = class _Scene {
1796
1959
  // EasingFn (which cannot cross into WASM) all fall through to it — WASM can
1797
1960
  // only ever change *how* a driver is advanced, never *what* value it lands
1798
1961
  // on.
1799
- __init89() {this._animWasm = null}
1962
+ __init98() {this._animWasm = null}
1800
1963
  // Entities with at least one active driver, added by Entity._spawnDriver.
1801
1964
  // Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
1802
1965
  // an entity whose drivers have since all completed or been removed. This is
1803
1966
  // what lets the batch pass find its candidates in O(active drivers), not
1804
1967
  // O(tree size) — the exact mistake G3's first integrated benchmark made.
1805
- __init90() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
1968
+ __init99() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
1806
1969
  // Reused across frames instead of allocating a fresh array + N {entity,prop,
1807
1970
  // driver} objects every call — the integrated benchmark
1808
1971
  // (benchmarks/anim-wasm-scene) found that allocation churn was the
1809
1972
  // dominant integrated cost, not the wasm kernel itself. Parallel arrays,
1810
1973
  // truncated to the live count after each use so a stale tail slot never
1811
1974
  // pins a no-longer-active entity/driver in memory.
1812
- __init91() {this._springEntities = []}
1813
- __init92() {this._springProps = []}
1814
- __init93() {this._springDrivers = []}
1815
- __init94() {this._tweenEntities = []}
1816
- __init95() {this._tweenProps = []}
1817
- __init96() {this._tweenDrivers = []}
1975
+ __init100() {this._springEntities = []}
1976
+ __init101() {this._springProps = []}
1977
+ __init102() {this._springDrivers = []}
1978
+ __init103() {this._tweenEntities = []}
1979
+ __init104() {this._tweenProps = []}
1980
+ __init105() {this._tweenDrivers = []}
1818
1981
  /**
1819
1982
  * Minimum number of batchable (spring, or named-easing tween) active drivers
1820
1983
  * before a frame engages the WASM batch path at all; below it, every driver
@@ -1852,7 +2015,57 @@ var Scene = (_class6 = class _Scene {
1852
2015
  * own target browser mix and driver-kind distribution, or leave WASM
1853
2016
  * animation batching disabled entirely on a Firefox-heavy audience.
1854
2017
  */
1855
- __init97() {this.animDriverGateCount = 256}
2018
+ /**
2019
+ * Back-compat alias for {@link animGate}. Reading it returns the tween gate
2020
+ * (the conservative one the single knob used to represent); writing it sets all
2021
+ * three, so code that tuned one number keeps behaving as before.
2022
+ *
2023
+ * Prefer {@link animGate} — a single threshold cannot be right for both kinds,
2024
+ * which is why this exists as an alias rather than the primary control.
2025
+ */
2026
+ get animDriverGateCount() {
2027
+ return this.animGate.tween;
2028
+ }
2029
+ set animDriverGateCount(n) {
2030
+ this.animGate = { spring: n, tween: n, mixed: n };
2031
+ }
2032
+ /**
2033
+ * Per-kind driver gates, in active batchable drivers.
2034
+ *
2035
+ * Measured on the integrated path (`benchmarks/anim-wasm-scene`, real Chrome
2036
+ * 150 / Firefox 153): spring and mixed workloads are a ~1.4-2.3x win from 128
2037
+ * drivers up through 16384, while pure tween is a **0.71x loss** at 128 and
2038
+ * only turns net-positive near 256. One scalar threshold therefore had to be
2039
+ * set for the worst kind, discarding the 128-255 spring win to avoid making a
2040
+ * tween-heavy scene slower.
2041
+ *
2042
+ * Firefox is a net loss at every count measured up to 16384 — not an
2043
+ * allocation artifact (confirmed after removing all per-frame allocation from
2044
+ * gather/scatter); SpiderMonkey's wasm-boundary cost for this call shape
2045
+ * appears to structurally exceed the saving at these scales. These defaults are
2046
+ * Chrome-oriented; on a Firefox-heavy audience, leave
2047
+ * {@link enableWasmAnimBatching} off entirely rather than tuning these.
2048
+ *
2049
+ * Setting {@link animDriverGateCount} overwrites all three, so existing code
2050
+ * that tuned the single knob keeps working unchanged.
2051
+ */
2052
+ __init106() {this._animBatchedLastFrame = false}
2053
+ /**
2054
+ * Whether the WASM batch path actually ran on the most recent frame.
2055
+ *
2056
+ * Distinct from {@link animBackend}, which reports only that a backend is
2057
+ * installed — a gate below the driver count means the frame still ticked in JS.
2058
+ * Conflating the two makes it easy to believe an accelerator is active when it
2059
+ * never opens.
2060
+ */
2061
+ get animBatchedLastFrame() {
2062
+ return this._animBatchedLastFrame;
2063
+ }
2064
+ __init107() {this.animGate = {
2065
+ spring: 128,
2066
+ tween: 256,
2067
+ mixed: 128
2068
+ }}
1856
2069
  /** Which backend advances active property drivers on the current gate
1857
2070
  * decision. Reflects only whether a backend is installed — the per-frame
1858
2071
  * gate can still choose the JS path even when this reads `'wasm'`. */
@@ -1873,10 +2086,9 @@ var Scene = (_class6 = class _Scene {
1873
2086
  * if WASM is now available (not necessarily active every frame).
1874
2087
  */
1875
2088
  async enableWasmAnimBatching(source) {
1876
- const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
1877
- const backend = isBytes ? await instantiateAsync3(source) : await instantiateStreaming3(source);
1878
- if (!backend) return false;
1879
- this.setAnimBackend(backend);
2089
+ const runtime = await this.ensureWasmRuntime(source);
2090
+ if (!runtime) return false;
2091
+ this.setAnimBackend(runtime.anim());
1880
2092
  return true;
1881
2093
  }
1882
2094
  // ── WASM particle CPU-sim backend (invisible accelerator, G4) ───────────────
@@ -1887,7 +2099,7 @@ var Scene = (_class6 = class _Scene {
1887
2099
  // (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
1888
2100
  // a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
1889
2101
  // no backend is installed or a scene runs on WebGPU.
1890
- __init98() {this._particleWasm = null}
2102
+ __init108() {this._particleWasm = null}
1891
2103
  /** Which backend runs the CPU particle simulation. Reflects only whether a
1892
2104
  * backend is installed (the WebGPU compute path, when active, is used first
1893
2105
  * regardless). */
@@ -1907,10 +2119,9 @@ var Scene = (_class6 = class _Scene {
1907
2119
  * Resolves `true` if WASM is now active.
1908
2120
  */
1909
2121
  async enableWasmParticles(source) {
1910
- const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
1911
- const backend = isBytes ? await instantiateAsync4(source) : await instantiateStreaming4(source);
1912
- if (!backend) return false;
1913
- this.setParticleBackend(backend);
2122
+ const runtime = await this.ensureWasmRuntime(source);
2123
+ if (!runtime) return false;
2124
+ this.setParticleBackend(runtime.particle());
1914
2125
  return true;
1915
2126
  }
1916
2127
  /** Internal: called by `Entity._spawnDriver` when a new property driver
@@ -1969,7 +2180,8 @@ var Scene = (_class6 = class _Scene {
1969
2180
  */
1970
2181
  _tickBatchedDrivers(dt) {
1971
2182
  if (this._activeDriverEntities.size === 0) return;
1972
- let batchable = 0;
2183
+ let springBatchable = 0;
2184
+ let tweenBatchable = 0;
1973
2185
  for (const entity of this._activeDriverEntities) {
1974
2186
  const entries = entity._driverEntries();
1975
2187
  if (!entries || entries.size === 0) {
@@ -1977,12 +2189,17 @@ var Scene = (_class6 = class _Scene {
1977
2189
  continue;
1978
2190
  }
1979
2191
  for (const driver of entries.values()) {
1980
- if (driver instanceof _animation.SpringDriver) batchable++;
1981
- else if (driver instanceof _animation.TweenDriver && driver.wasmEasingId !== null) batchable++;
2192
+ if (driver instanceof _animation.SpringDriver) springBatchable++;
2193
+ else if (driver instanceof _animation.TweenDriver && driver.wasmEasingId !== null) tweenBatchable++;
1982
2194
  }
1983
2195
  }
2196
+ const batchable = springBatchable + tweenBatchable;
1984
2197
  const backend = this._animWasm;
1985
- if (!backend || batchable < this.animDriverGateCount) return;
2198
+ this._animBatchedLastFrame = false;
2199
+ if (!backend) return;
2200
+ const gate = springBatchable > 0 && tweenBatchable > 0 ? this.animGate.mixed : tweenBatchable > 0 ? this.animGate.tween : this.animGate.spring;
2201
+ if (batchable < gate) return;
2202
+ this._animBatchedLastFrame = true;
1986
2203
  const sE = this._springEntities;
1987
2204
  const sP = this._springProps;
1988
2205
  const sD = this._springDrivers;
@@ -2073,6 +2290,11 @@ var Scene = (_class6 = class _Scene {
2073
2290
  this._wasmWorld = backend.worldView();
2074
2291
  this._storeStructureVersion = this._structureVersion;
2075
2292
  }
2293
+ backend.revalidateViews();
2294
+ if (this._wasmInputs && this._wasmInputs.x.length === 0) {
2295
+ this._wasmInputs = backend.inputView();
2296
+ this._wasmWorld = backend.worldView();
2297
+ }
2076
2298
  const inp = this._wasmInputs;
2077
2299
  const slotEntity = this._slotEntity;
2078
2300
  for (let slot = 1; slot < slotEntity.length; slot++) {
@@ -2087,54 +2309,86 @@ var Scene = (_class6 = class _Scene {
2087
2309
  inp.opacity[slot] = e.opacity;
2088
2310
  }
2089
2311
  backend.runKernel("simd");
2312
+ this._wasmAabbsFresh = false;
2090
2313
  return this._wasmWorld;
2091
2314
  }
2315
+ /**
2316
+ * Run the WASM world-AABB pass over the current frame's world matrices, so the
2317
+ * fused hit gather can read AABBs straight out of the store.
2318
+ *
2319
+ * Local bounds are uploaded here rather than in the per-frame transform sync
2320
+ * because `getBounds()` is a virtual call that allocates a rect on most
2321
+ * entities — paying it every frame for a query that may never come would move
2322
+ * cost onto the render path to save it on hover. Returns `false` if any entity
2323
+ * cannot supply bounds through the store, so the caller uses the JS gather.
2324
+ */
2325
+ _ensureWasmAabbs() {
2326
+ const backend = this._wasm;
2327
+ const store = this._treeStore;
2328
+ if (!backend || !store) return false;
2329
+ if (this._wasmAabbsFresh) return true;
2330
+ backend.revalidateViews();
2331
+ const bounds = backend.boundsView();
2332
+ const slotEntity = this._slotEntity;
2333
+ for (let slot = 0; slot < slotEntity.length; slot++) {
2334
+ const e = slotEntity[slot];
2335
+ if (!e) continue;
2336
+ const b = e.getBounds();
2337
+ bounds.bx[slot] = b ? b.x : 0;
2338
+ bounds.by[slot] = b ? b.y : 0;
2339
+ bounds.bw[slot] = b ? b.width : 0;
2340
+ bounds.bh[slot] = b ? b.height : 0;
2341
+ }
2342
+ backend.runAabbs(slotEntity.length);
2343
+ this._wasmAabbsFresh = true;
2344
+ return true;
2345
+ }
2092
2346
  /**
2093
2347
  * Authoritative paint order for semantic nodes discovered during the main
2094
2348
  * render. A node may not have a DOM projection until the following a11y
2095
2349
  * sync, so retaining the order prevents a newly opened overlay from spending
2096
2350
  * its first frame below previously projected controls.
2097
2351
  */
2098
- __init99() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2352
+ __init109() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
2099
2353
  // Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
2100
- __init100() {this.pointRenderer = null}
2101
- __init101() {this.glCanvas = null}
2102
- __init102() {this.glContextLostHandler = null}
2103
- __init103() {this.glContextRestoredHandler = null}
2354
+ __init110() {this.pointRenderer = null}
2355
+ __init111() {this.glCanvas = null}
2356
+ __init112() {this.glContextLostHandler = null}
2357
+ __init113() {this.glContextRestoredHandler = null}
2104
2358
 
2105
2359
 
2106
2360
 
2107
- __init104() {this.disableWindowResize = false}
2361
+ __init114() {this.disableWindowResize = false}
2108
2362
  /** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
2109
2363
 
2110
2364
  // WebGPU properties
2111
- __init105() {this.destroyed = false}
2112
- __init106() {this.device = null}
2113
- __init107() {this.deviceLost = false}
2114
- __init108() {this.particleBackend = "auto"}
2115
- __init109() {this._webgpuDisabled = false}
2365
+ __init115() {this.destroyed = false}
2366
+ __init116() {this.device = null}
2367
+ __init117() {this.deviceLost = false}
2368
+ __init118() {this.particleBackend = "auto"}
2369
+ __init119() {this._webgpuDisabled = false}
2116
2370
  get webgpuDisabled() {
2117
2371
  return this._webgpuDisabled || this.particleBackend === "cpu";
2118
2372
  }
2119
2373
  set webgpuDisabled(value) {
2120
2374
  this._webgpuDisabled = value;
2121
2375
  }
2122
- __init110() {this.recoveryTimerId = null}
2123
- __init111() {this.manager = null}
2124
- __init112() {this.initializingWebGPU = false}
2125
- __init113() {this.gpuCanvas = null}
2126
- __init114() {this.gpuContext = null}
2376
+ __init120() {this.recoveryTimerId = null}
2377
+ __init121() {this.manager = null}
2378
+ __init122() {this.initializingWebGPU = false}
2379
+ __init123() {this.gpuCanvas = null}
2380
+ __init124() {this.gpuContext = null}
2127
2381
  /** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
2128
- __init115() {this.gpuHasContent = false}
2129
- __init116() {this.mouseX = -9999}
2130
- __init117() {this.mouseY = -9999}
2131
- __init118() {this.pointerMoveListener = null}
2132
- __init119() {this.pointerLeaveListener = null}
2382
+ __init125() {this.gpuHasContent = false}
2383
+ __init126() {this.mouseX = -9999}
2384
+ __init127() {this.mouseY = -9999}
2385
+ __init128() {this.pointerMoveListener = null}
2386
+ __init129() {this.pointerLeaveListener = null}
2133
2387
  /** Element the pointer listeners are bound to (parent container if present,
2134
2388
  * else the canvas). Stored so `destroy()` detaches from the same element. */
2135
- __init120() {this.pointerEventTarget = null}
2136
- __init121() {this.hasWarnedZeroSize = false}
2137
- __init122() {this.fontLoadHandler = null}
2389
+ __init130() {this.pointerEventTarget = null}
2390
+ __init131() {this.hasWarnedZeroSize = false}
2391
+ __init132() {this.fontLoadHandler = null}
2138
2392
  // ── Dev-mode warning infrastructure ──────────────────────────────
2139
2393
  //
2140
2394
  // Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
@@ -2152,7 +2406,7 @@ var Scene = (_class6 = class _Scene {
2152
2406
  return false;
2153
2407
  }
2154
2408
 
2155
- __init123() {this._devFrameCount = 0}
2409
+ __init133() {this._devFrameCount = 0}
2156
2410
  _devWarn(message) {
2157
2411
  if (!this._devActive) return;
2158
2412
  console.warn(`[vectojs/dev] ${message}`);
@@ -2196,7 +2450,7 @@ var Scene = (_class6 = class _Scene {
2196
2450
  };
2197
2451
  walkProjections(this.root);
2198
2452
  }
2199
- constructor(canvas, options = {}) {;_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);_class6.prototype.__init40.call(this);_class6.prototype.__init41.call(this);_class6.prototype.__init42.call(this);_class6.prototype.__init43.call(this);_class6.prototype.__init44.call(this);_class6.prototype.__init45.call(this);_class6.prototype.__init46.call(this);_class6.prototype.__init47.call(this);_class6.prototype.__init48.call(this);_class6.prototype.__init49.call(this);_class6.prototype.__init50.call(this);_class6.prototype.__init51.call(this);_class6.prototype.__init52.call(this);_class6.prototype.__init53.call(this);_class6.prototype.__init54.call(this);_class6.prototype.__init55.call(this);_class6.prototype.__init56.call(this);_class6.prototype.__init57.call(this);_class6.prototype.__init58.call(this);_class6.prototype.__init59.call(this);_class6.prototype.__init60.call(this);_class6.prototype.__init61.call(this);_class6.prototype.__init62.call(this);_class6.prototype.__init63.call(this);_class6.prototype.__init64.call(this);_class6.prototype.__init65.call(this);_class6.prototype.__init66.call(this);_class6.prototype.__init67.call(this);_class6.prototype.__init68.call(this);_class6.prototype.__init69.call(this);_class6.prototype.__init70.call(this);_class6.prototype.__init71.call(this);_class6.prototype.__init72.call(this);_class6.prototype.__init73.call(this);_class6.prototype.__init74.call(this);_class6.prototype.__init75.call(this);_class6.prototype.__init76.call(this);_class6.prototype.__init77.call(this);_class6.prototype.__init78.call(this);_class6.prototype.__init79.call(this);_class6.prototype.__init80.call(this);_class6.prototype.__init81.call(this);_class6.prototype.__init82.call(this);_class6.prototype.__init83.call(this);_class6.prototype.__init84.call(this);_class6.prototype.__init85.call(this);_class6.prototype.__init86.call(this);_class6.prototype.__init87.call(this);_class6.prototype.__init88.call(this);_class6.prototype.__init89.call(this);_class6.prototype.__init90.call(this);_class6.prototype.__init91.call(this);_class6.prototype.__init92.call(this);_class6.prototype.__init93.call(this);_class6.prototype.__init94.call(this);_class6.prototype.__init95.call(this);_class6.prototype.__init96.call(this);_class6.prototype.__init97.call(this);_class6.prototype.__init98.call(this);_class6.prototype.__init99.call(this);_class6.prototype.__init100.call(this);_class6.prototype.__init101.call(this);_class6.prototype.__init102.call(this);_class6.prototype.__init103.call(this);_class6.prototype.__init104.call(this);_class6.prototype.__init105.call(this);_class6.prototype.__init106.call(this);_class6.prototype.__init107.call(this);_class6.prototype.__init108.call(this);_class6.prototype.__init109.call(this);_class6.prototype.__init110.call(this);_class6.prototype.__init111.call(this);_class6.prototype.__init112.call(this);_class6.prototype.__init113.call(this);_class6.prototype.__init114.call(this);_class6.prototype.__init115.call(this);_class6.prototype.__init116.call(this);_class6.prototype.__init117.call(this);_class6.prototype.__init118.call(this);_class6.prototype.__init119.call(this);_class6.prototype.__init120.call(this);_class6.prototype.__init121.call(this);_class6.prototype.__init122.call(this);_class6.prototype.__init123.call(this);
2453
+ 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);
2200
2454
  this.canvas = canvas;
2201
2455
  this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
2202
2456
  this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
@@ -2524,7 +2778,7 @@ var Scene = (_class6 = class _Scene {
2524
2778
  if (!anchor || !focus) return;
2525
2779
  try {
2526
2780
  selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
2527
- } catch (e17) {
2781
+ } catch (e9) {
2528
2782
  }
2529
2783
  }
2530
2784
  /**
@@ -3223,11 +3477,17 @@ var Scene = (_class6 = class _Scene {
3223
3477
  this.syncOptionalAttribute(el, "aria-modal", attrs.ariaModal);
3224
3478
  this.syncOptionalAttribute(el, "aria-labelledby", attrs.labelledby);
3225
3479
  this.syncOptionalAttribute(el, "aria-describedby", attrs.describedby);
3226
- this.syncOptionalAttribute(
3227
- el,
3228
- "aria-required",
3229
- attrs.required === void 0 ? void 0 : String(attrs.required)
3230
- );
3480
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
3481
+ const wantRequired = attrs.required === true;
3482
+ if (el.required !== wantRequired) el.required = wantRequired;
3483
+ this.syncOptionalAttribute(el, "aria-required", void 0);
3484
+ } else {
3485
+ this.syncOptionalAttribute(
3486
+ el,
3487
+ "aria-required",
3488
+ attrs.required === void 0 ? void 0 : String(attrs.required)
3489
+ );
3490
+ }
3231
3491
  this.syncOptionalAttribute(
3232
3492
  el,
3233
3493
  "aria-invalid",
@@ -4460,7 +4720,7 @@ var Scene = (_class6 = class _Scene {
4460
4720
  });
4461
4721
  pass.end();
4462
4722
  this.device.queue.submit([encoder.finish()]);
4463
- } catch (e18) {
4723
+ } catch (e10) {
4464
4724
  }
4465
4725
  this.gpuHasContent = false;
4466
4726
  }
@@ -4628,7 +4888,7 @@ var Scene = (_class6 = class _Scene {
4628
4888
  }
4629
4889
  return true;
4630
4890
  }
4631
- }, _class6.__initStatic(), _class6.__initStatic2(), _class6.__initStatic3(), _class6.__initStatic4(), _class6);
4891
+ }, _class7.__initStatic(), _class7.__initStatic2(), _class7.__initStatic3(), _class7.__initStatic4(), _class7);
4632
4892
  function intersectBounds(a, b) {
4633
4893
  const x = Math.max(a.x, b.x);
4634
4894
  const y = Math.max(a.y, b.y);
@@ -4655,20 +4915,20 @@ function defaultMeasurer() {
4655
4915
  if (sharedMeasurer === void 0) sharedMeasurer = _layout.createCanvasMeasurer.call(void 0, "sans-serif");
4656
4916
  return sharedMeasurer;
4657
4917
  }
4658
- var TextEntity = (_class7 = class extends _chunkDUYB4GX4js.Entity {
4918
+ var TextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
4659
4919
 
4660
4920
 
4661
4921
 
4662
4922
 
4663
- __init124() {this.nodes = []}
4923
+ __init134() {this.nodes = []}
4664
4924
 
4665
- __init125() {this.fillStyle = "#94a3b8"}
4666
- __init126() {this.strokeStyle = null}
4667
- __init127() {this.hoveredFillStyle = "#ffffff"}
4668
- __init128() {this.lineWidth = 1}
4669
- __init129() {this.isHovered = false}
4925
+ __init135() {this.fillStyle = "#94a3b8"}
4926
+ __init136() {this.strokeStyle = null}
4927
+ __init137() {this.hoveredFillStyle = "#ffffff"}
4928
+ __init138() {this.lineWidth = 1}
4929
+ __init139() {this.isHovered = false}
4670
4930
  constructor(text, atlas, maxWidth, fontSize = 24) {
4671
- super();_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);;
4931
+ super();_class8.prototype.__init134.call(this);_class8.prototype.__init135.call(this);_class8.prototype.__init136.call(this);_class8.prototype.__init137.call(this);_class8.prototype.__init138.call(this);_class8.prototype.__init139.call(this);;
4672
4932
  this.text = text;
4673
4933
  this.atlas = atlas;
4674
4934
  this.fontSize = fontSize;
@@ -4778,20 +5038,20 @@ var TextEntity = (_class7 = class extends _chunkDUYB4GX4js.Entity {
4778
5038
  renderer.restore();
4779
5039
  }
4780
5040
  }
4781
- }, _class7);
5041
+ }, _class8);
4782
5042
 
4783
5043
  // src/components/GridTextEntity.ts
4784
- var GridTextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
5044
+ var GridTextEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
4785
5045
 
4786
- __init130() {this.fillStyle = "#ffffff"}
4787
- __init131() {this.grid = []}
5046
+ __init140() {this.fillStyle = "#ffffff"}
5047
+ __init141() {this.grid = []}
4788
5048
  // Array of rows
4789
- __init132() {this.cols = 0}
4790
- __init133() {this.rows = 0}
5049
+ __init142() {this.cols = 0}
5050
+ __init143() {this.rows = 0}
4791
5051
 
4792
5052
 
4793
5053
  constructor(_atlas, fontSize = 10) {
4794
- super();_class8.prototype.__init130.call(this);_class8.prototype.__init131.call(this);_class8.prototype.__init132.call(this);_class8.prototype.__init133.call(this);;
5054
+ super();_class9.prototype.__init140.call(this);_class9.prototype.__init141.call(this);_class9.prototype.__init142.call(this);_class9.prototype.__init143.call(this);;
4795
5055
  this.fontSize = fontSize;
4796
5056
  this.charWidth = fontSize * 1;
4797
5057
  this.charHeight = fontSize * 1.1;
@@ -4822,7 +5082,7 @@ var GridTextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
4822
5082
  }
4823
5083
  }
4824
5084
  }
4825
- }, _class8);
5085
+ }, _class9);
4826
5086
 
4827
5087
  // src/components/SplineEntity.ts
4828
5088
  function polySegmentToBezier(seg) {
@@ -4875,7 +5135,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
4875
5135
  const ey = py - cy;
4876
5136
  return ex * ex + ey * ey;
4877
5137
  }
4878
- var SplineEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
5138
+ var SplineEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
4879
5139
 
4880
5140
 
4881
5141
 
@@ -4883,23 +5143,23 @@ var SplineEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
4883
5143
 
4884
5144
 
4885
5145
 
4886
- __init134() {this.offscreen = null}
4887
- __init135() {this.baked = false}
5146
+ __init144() {this.offscreen = null}
5147
+ __init145() {this.baked = false}
4888
5148
  /** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
4889
- __init136() {this.bakedWidth = 0}
4890
- __init137() {this.bakedHeight = 0}
5149
+ __init146() {this.bakedWidth = 0}
5150
+ __init147() {this.bakedHeight = 0}
4891
5151
  /** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
4892
5152
 
4893
5153
  /** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
4894
- __init138() {this.polylines = null}
5154
+ __init148() {this.polylines = null}
4895
5155
  /**
4896
5156
  * When `true`, the renderer draws a rounded-rect outline of the entity's
4897
5157
  * local bounds after painting the curves. Useful for drag feedback and
4898
5158
  * debugging hit areas. Defaults to `false`.
4899
5159
  */
4900
- __init139() {this.showBounds = false}
5160
+ __init149() {this.showBounds = false}
4901
5161
  constructor(doc, opts = {}) {
4902
- super();_class9.prototype.__init134.call(this);_class9.prototype.__init135.call(this);_class9.prototype.__init136.call(this);_class9.prototype.__init137.call(this);_class9.prototype.__init138.call(this);_class9.prototype.__init139.call(this);;
5162
+ super();_class10.prototype.__init144.call(this);_class10.prototype.__init145.call(this);_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);;
4903
5163
  this.doc = doc;
4904
5164
  this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
4905
5165
  this.cache = _nullishCoalesce(opts.cache, () => ( true));
@@ -5143,7 +5403,7 @@ var SplineEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
5143
5403
  r.stroke("rgba(0, 150, 255, 0.8)", 2);
5144
5404
  }
5145
5405
  }
5146
- }, _class9);
5406
+ }, _class10);
5147
5407
  async function loadSpline(url) {
5148
5408
  const res = await fetch(url);
5149
5409
  return await res.json();
@@ -5278,21 +5538,21 @@ var _math = require('@vectojs/math'); _createStarExport(_math);
5278
5538
 
5279
5539
 
5280
5540
  // src/tree/DOMPortalEntity.ts
5281
- var DOMPortalEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5541
+ var DOMPortalEntity = (_class11 = class extends _chunkDUYB4GX4js.Entity {
5282
5542
 
5283
- __init140() {this.isDOMPortal = true}
5284
- __init141() {this.domListeners = []}
5285
- __init142() {this.resizeObserver = null}
5286
- __init143() {this.domBound = false}
5287
- __init144() {this.cachedWidth = 100}
5288
- __init145() {this.cachedHeight = 100}
5289
- __init146() {this.lastWidth = ""}
5290
- __init147() {this.lastHeight = ""}
5291
- __init148() {this.lastTransform = ""}
5292
- __init149() {this.lastZIndex = ""}
5293
- __init150() {this.lastOpacity = ""}
5543
+ __init150() {this.isDOMPortal = true}
5544
+ __init151() {this.domListeners = []}
5545
+ __init152() {this.resizeObserver = null}
5546
+ __init153() {this.domBound = false}
5547
+ __init154() {this.cachedWidth = 100}
5548
+ __init155() {this.cachedHeight = 100}
5549
+ __init156() {this.lastWidth = ""}
5550
+ __init157() {this.lastHeight = ""}
5551
+ __init158() {this.lastTransform = ""}
5552
+ __init159() {this.lastZIndex = ""}
5553
+ __init160() {this.lastOpacity = ""}
5294
5554
  constructor(domElement, width, height, id) {
5295
- super(id);_class10.prototype.__init140.call(this);_class10.prototype.__init141.call(this);_class10.prototype.__init142.call(this);_class10.prototype.__init143.call(this);_class10.prototype.__init144.call(this);_class10.prototype.__init145.call(this);_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);_class10.prototype.__init150.call(this);;
5555
+ super(id);_class11.prototype.__init150.call(this);_class11.prototype.__init151.call(this);_class11.prototype.__init152.call(this);_class11.prototype.__init153.call(this);_class11.prototype.__init154.call(this);_class11.prototype.__init155.call(this);_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);;
5296
5556
  this.domElement = domElement;
5297
5557
  this.width = _nullishCoalesce(width, () => ( 0));
5298
5558
  this.height = _nullishCoalesce(height, () => ( 0));
@@ -5401,7 +5661,7 @@ var DOMPortalEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
5401
5661
  }
5402
5662
  super.destroy();
5403
5663
  }
5404
- }, _class10);
5664
+ }, _class11);
5405
5665
 
5406
5666
  // src/index.ts
5407
5667
  Scene.registerWebGLPointRendererCreator(_chunkL4SWVP2Hjs.createWebGLPointRenderer);