@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.
@@ -143,8 +143,12 @@ export declare class ComputeParticleEntity extends Entity {
143
143
  * The backend holds one resident SoA store, so a Scene with multiple particle
144
144
  * entities reuses it sequentially — origin is therefore re-gathered each call
145
145
  * (not upload-once), a couple of extra f32 reads per particle.
146
+ *
147
+ * Returns `false` if the kernel declined the call and {@link updateCPU} ran
148
+ * instead, so the Scene can report which path actually simulated this frame
149
+ * rather than assuming an installed backend did the work.
146
150
  */
147
- stepWithBackend(backend: ParticleBackend, dt: number, mouseX: number, mouseY: number, width: number, height: number): void;
151
+ stepWithBackend(backend: ParticleBackend, dt: number, mouseX: number, mouseY: number, width: number, height: number): boolean;
148
152
  destroy(): void;
149
153
  /**
150
154
  * Frees all GPU resources allocated for WebGPU simulation.
@@ -208,6 +208,60 @@ export interface SceneOptions {
208
208
  }
209
209
  /** Frame-rate the loop is capped to when the OS requests reduced motion. */
210
210
  export declare const REDUCED_MOTION_FPS = 30;
211
+ /**
212
+ * Why an accelerator did or did not run on the most recent frame.
213
+ *
214
+ * `'active'` is the only value that means the accelerator ran. Everything else
215
+ * is a distinct decline, kept separate because they call for different actions:
216
+ * `'not-installed'` means enable it, `'below-gate'` means the workload is too
217
+ * small to be worth it (working as designed), and `'rejected'` means the kernel
218
+ * refused its arguments — a fault worth reporting, not a tuning outcome.
219
+ */
220
+ export type AcceleratorReason =
221
+ /** Ran on this frame. */
222
+ 'active'
223
+ /** No backend installed; the JS path is the permanent fallback. */
224
+ | 'not-installed'
225
+ /** Installed, but the per-frame gate chose JS (workload below threshold). */
226
+ | 'below-gate'
227
+ /** Installed and gated in, but the kernel rejected the call and wrote nothing. */
228
+ | 'rejected'
229
+ /** Not applicable to this pass (e.g. a non-main renderer, or nothing to do). */
230
+ | 'not-applicable';
231
+ /**
232
+ * One accelerator's per-frame status, read from {@link Scene.accelerators}.
233
+ *
234
+ * The pair exists because `available` and `activeThisFrame` genuinely differ:
235
+ * before this shape, `transformBackend`/`animBackend` reported only that a
236
+ * backend was *installed*, which invites concluding an accelerator is doing work
237
+ * when its gate never opens. Read `activeThisFrame` for what actually happened
238
+ * and `reason` for why.
239
+ */
240
+ export interface AcceleratorStatus {
241
+ /** A backend is installed and could run, gate permitting. */
242
+ available: boolean;
243
+ /** It ran on the most recent frame. */
244
+ activeThisFrame: boolean;
245
+ /** Why it did or did not run. */
246
+ reason: AcceleratorReason;
247
+ /** Which implementation actually did the work on the most recent frame. */
248
+ path: string;
249
+ }
250
+ /**
251
+ * Per-frame status of every invisible accelerator, read from
252
+ * {@link Scene.accelerators}. Each is independent: a scene can compose
253
+ * transforms in WASM while ticking drivers in JS.
254
+ */
255
+ export interface AcceleratorReport {
256
+ /** World-matrix composition (`compose_simd`). */
257
+ transform: AcceleratorStatus;
258
+ /** Batched property drivers (`spring_step`/`tween_step`). */
259
+ animation: AcceleratorStatus;
260
+ /** Hit-test broad phase (`hit_build`/`hit_query`) and its gather source. */
261
+ hitTest: AcceleratorStatus;
262
+ /** Particle simulation — WebGPU compute, the WASM CPU kernel, or JS. */
263
+ particle: AcceleratorStatus;
264
+ }
211
265
  /**
212
266
  * Live render-loop telemetry, read from {@link Scene.frameStats}. See that
213
267
  * getter for how each field is measured.
@@ -574,6 +628,41 @@ export declare class Scene {
574
628
  private _wasmAabbsFresh;
575
629
  /** Did the last hit-grid build use the fused (WASM-store) gather? */
576
630
  get hitGatherPath(): 'fused' | 'js';
631
+ /**
632
+ * Why the transform accelerator did or did not run on the most recent frame.
633
+ * Written by the render walk and `_syncWasmStore`.
634
+ */
635
+ private _transformReason;
636
+ /** Why the batched-driver accelerator did or did not run. */
637
+ private _animReason;
638
+ /**
639
+ * Why the hit-test accelerator did or did not serve the last pointer query.
640
+ * The grid is built lazily on demand, not every frame, so this describes the
641
+ * most recent BUILD. Starts at `'not-installed'` because that is the truth
642
+ * before a backend exists; `_ensureHitGrid` moves it to `'not-applicable'`
643
+ * once one is installed but nothing has queried yet.
644
+ */
645
+ private _hitReason;
646
+ /** Why the particle accelerator did or did not run. */
647
+ private _particleReason;
648
+ /** Which particle implementation actually simulated the most recent frame. */
649
+ private _particlePath;
650
+ /**
651
+ * Per-frame status of every invisible accelerator: whether each is installed,
652
+ * whether it actually ran on the most recent frame, and why.
653
+ *
654
+ * This exists because the older per-accelerator getters
655
+ * ({@link transformBackend}, {@link animBackend}, {@link hitTestBackend},
656
+ * {@link particleBackend}) report only that a backend is INSTALLED. Reading
657
+ * `'wasm'` from one of those and concluding the accelerator is doing work is
658
+ * wrong whenever a gate never opens, a kernel rejects its arguments, or a
659
+ * faster backend takes the pass instead. Read {@link AcceleratorStatus.reason}
660
+ * for which of those happened.
661
+ *
662
+ * Reflects the most recent main-renderer frame; a secondary renderer (SVG
663
+ * export, offscreen snapshot) does not overwrite it.
664
+ */
665
+ get accelerators(): AcceleratorReport;
577
666
  /** Which backend answers `findEntityAt` for the main tree. */
578
667
  get hitTestBackend(): 'js' | 'wasm';
579
668
  /** Install (or clear) a WASM hit-test backend directly. Prefer
@@ -56,10 +56,27 @@ export declare class AnimBackend {
56
56
  * writing first and sizing after would write into a stale buffer.
57
57
  */
58
58
  ensure(springCount: number, tweenCount: number): void;
59
- /** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
60
- stepSprings(dtMs: number, count: number): void;
61
- /** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
62
- stepTweens(dtMs: number, count: number): void;
59
+ /**
60
+ * Advance `count` springs (from index 0) by `dtMs` milliseconds, in place.
61
+ * Returns `true` when the kernel ran. `false` means it rejected the call
62
+ * (count beyond the capacity {@link ensure} allocated, or no `anim_init` yet)
63
+ * and wrote nothing, so the caller must tick those drivers in JS instead of
64
+ * scattering back a pack the kernel never touched. See {@link lastStatus}.
65
+ */
66
+ stepSprings(dtMs: number, count: number): boolean;
67
+ /**
68
+ * Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`.
69
+ * Returns `true` when the kernel ran; `false` means it rejected the call and
70
+ * wrote nothing (see {@link stepSprings}). `elapsed` is kernel-side state, so
71
+ * a rejected tween pack must not be read back — it is unadvanced, not
72
+ * partially advanced.
73
+ */
74
+ stepTweens(dtMs: number, count: number): boolean;
75
+ /**
76
+ * Status of the most recent kernel call — `WASM_STATUS.OK` unless the kernel
77
+ * declined it. Mirrors {@link TransformBackend.lastStatus}.
78
+ */
79
+ lastStatus: number;
63
80
  private refreshViews;
64
81
  }
65
82
  /**
@@ -84,8 +84,11 @@ export declare class WasmTransformBackend {
84
84
  */
85
85
  runKernel(kernel?: Kernel): number;
86
86
  /** Upload only the run table + count (topology), leaving per-entity inputs to
87
- * the resident views. Call when the tree structure changes, not per frame. */
88
- uploadRuns(store: TransformStore): void;
87
+ * the resident views. Call when the tree structure changes, not per frame.
88
+ * Returns `false` if the crate rejected the run count, in which case the
89
+ * PREVIOUS topology is still published and any kernel run against it would
90
+ * compose the wrong tree — the caller must not proceed. */
91
+ uploadRuns(store: TransformStore): boolean;
89
92
  /**
90
93
  * Status of the most recent kernel or run-table call. `WASM_STATUS.OK` unless
91
94
  * the crate rejected its arguments, in which case that call was a no-op.
@@ -125,8 +128,10 @@ export declare class WasmTransformBackend {
125
128
  computeAabbs(store: TransformStore): void;
126
129
  /** Run the AABB pass only, over `count` entities already resident in wasm
127
130
  * memory (bounds written via {@link boundsView}, world matrices already
128
- * composed). No upload/readback — the per-frame resident path. */
129
- runAabbs(count: number): void;
131
+ * composed). No upload/readback — the per-frame resident path. Returns
132
+ * `false` if the kernel rejected `count` (beyond capacity, or uninitialized),
133
+ * in which case {@link aabbView} still holds the previous frame's bounds. */
134
+ runAabbs(count: number): boolean;
130
135
  /** Resident wasm local-bounds input views (`bx,by,bw,bh`) for the AABB pass. */
131
136
  boundsView(): {
132
137
  bx: Float64Array;
@@ -71,8 +71,19 @@ export declare class ParticleBackend {
71
71
  * Advance `count` particles one step in place. Returns `true` when at least
72
72
  * one live particle is still moving or off-origin beyond epsilon (the fused
73
73
  * `hasPendingAnimations` flag), so the caller need not re-scan the buffer.
74
+ *
75
+ * Returns `null` when the kernel REJECTED the call — `count` beyond the
76
+ * capacity {@link ensure} allocated, or no `particle_init` yet. Nothing was
77
+ * written, so the caller must NOT {@link scatter} (that would write the
78
+ * gathered pre-step values back and freeze the simulation) and should fall
79
+ * back to the JS `updateCPU` path for this frame. See {@link lastStatus}.
74
80
  */
75
- step(count: number, p: ParticleStepParams): boolean;
81
+ step(count: number, p: ParticleStepParams): boolean | null;
82
+ /**
83
+ * Status of the most recent {@link step} — `WASM_STATUS.OK` unless the kernel
84
+ * declined it. Mirrors {@link TransformBackend.lastStatus}.
85
+ */
86
+ lastStatus: number;
76
87
  /** Transpose the AoS stride-8 buffer into the SoA views (position/velocity/
77
88
  * life every frame; origin upload-once when `withOrigin`). */
78
89
  gather(data: Float32Array, count: number, withOrigin: boolean): void;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },