@solidrt/flux-types 0.0.54 → 0.0.56

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/gui/gpu.d.ts CHANGED
@@ -107,9 +107,12 @@ declare module "flux:gpu" {
107
107
  * A uniform that is declared but optimized out by the compiler is accepted
108
108
  * with a warning and the write is skipped, so one param object can drive
109
109
  * shader variants that do not all use every uniform.
110
- * An `undefined` value is skipped, so conditional spreads stay usable.
110
+ * An `undefined` (or `null`) value is skipped, so conditional spreads stay
111
+ * usable. A `Float32Array`/`Float64Array` is accepted as a flat array. The
112
+ * same decoder serves the `<texture params>`, view and window shader props,
113
+ * so the prop and the imperative path accept and reject identically.
111
114
  */
112
- export type ShaderParams = Record<string, number | number[]>
115
+ export type ShaderParams = Record<string, number | number[] | Float32Array | Float64Array>
113
116
  /** Magnification/minification filter; "linear" (default) or hard-pixel "nearest". */
114
117
  export type FilterMode = "linear" | "nearest"
115
118
  /** Sampling outside 0..1: "clamp" (default, extend edge pixels) or "repeat" (tile). */
@@ -129,8 +132,19 @@ declare module "flux:gpu" {
129
132
  * shader sampling minifies through the chain; the `<texture>` display
130
133
  * draw samples the full-size level. Regeneration is one GPU pass per
131
134
  * upload or render, so a per-frame texture pays it per frame.
135
+ *
136
+ * `anisotropy` (default 1 = off) is the anisotropic filtering level, Three's
137
+ * `texture.anisotropy`: the most taps a shader sample may take along the
138
+ * axis a surface is foreshortened on, so a tiled ground plane seen at a
139
+ * grazing angle stays sharp to the horizon instead of smearing into the
140
+ * mip level its long axis picks. Any number >= 1; rounded down to a power
141
+ * of two, capped at 16 and then at the device's `limits.maxAnisotropy`
142
+ * (1 on a device without the extension - the texture then samples as
143
+ * today, no error). Only meaningful with `mipmap: true` (it filters
144
+ * through the chain), ignored by the `<texture>` display draw like the
145
+ * chain itself. Below 1 or non-finite throws.
132
146
  */
133
- export type SamplerOptions = { filter?: FilterMode; wrap?: WrapMode; mipmap?: boolean }
147
+ export type SamplerOptions = { filter?: FilterMode; wrap?: WrapMode; mipmap?: boolean; anisotropy?: number }
134
148
  /**
135
149
  * One `textures` binding value: a texture id, sampled with the texture's
136
150
  * own declared state, or `{ id, filter?, wrap? }` to sample that id with a
@@ -141,7 +155,14 @@ declare module "flux:gpu" {
141
155
  * not overridable - the chain either exists on the id or it does not.
142
156
  */
143
157
  export type TextureBinding = TextureId | { id: TextureId; filter?: FilterMode; wrap?: WrapMode }
144
- /** Sampler2D inputs by uniform name; see {@link TextureBinding}. */
158
+ /**
159
+ * Sampler inputs by uniform name; see {@link TextureBinding}. The uniform's
160
+ * declared type picks what may bind: a `sampler2D` takes any 2D texture,
161
+ * a `samplerCube` a cube map ({@link createCubeTexture}), a
162
+ * `sampler2DShadow` a draw target's depth texture; a mismatch throws. An
163
+ * `undefined` (or `null`) binding is skipped, like a param; an id must be
164
+ * a non-negative integer (a negative or fractional value throws).
165
+ */
145
166
  export type TextureBindings = Record<string, TextureBinding>
146
167
  /**
147
168
  * A free-form debug name every create accepts (WebGPU's label): surfaced by
@@ -163,8 +184,45 @@ declare module "flux:gpu" {
163
184
  * texture as `(v, 0, 0, 1)` - read `.r`; displaying one via `<texture src>`
164
185
  * shows that same red-channel reading. Shader/pipeline targets and
165
186
  * readbacks stay RGBA8.
187
+ *
188
+ * "r32f" and "rgba32f" are the float data-texture formats, for float
189
+ * payloads a shader fetches - per-sector heights read in a vertex stage,
190
+ * bone matrices at scale, lookup tables wider than 8 bits - without
191
+ * fixed-point encoding into RGBA8 channels. One f32 per channel: data is a
192
+ * Float32Array of width*height (r32f, sampled as `(v, 0, 0, 1)`) or
193
+ * width*height*4 (rgba32f, a vec4 per texel). Upload-and-sample only, and
194
+ * nearest-only - `filter` defaults to "nearest" and "linear", `mipmap` and
195
+ * `anisotropy` throw (float linear filtering is not in core GLES 3.0;
196
+ * fetch exact texels with `texelFetch`). No readback or copy path
197
+ * (readTexture and copyTexture throw: float is not color-renderable in
198
+ * core), and displaying one via `<texture src>` is out of contract - these
199
+ * are shader data, not image content.
200
+ *
201
+ * "rgba16f" is the HDR image format (environment maps, panoramas): the same
202
+ * Float32Array payload (width*height*4), packed to half float on upload,
203
+ * and filterable like a byte format (RGBA16F is texture-filterable in core
204
+ * GLES 3.0), so `filter` defaults to "linear" and `anisotropy` applies.
205
+ * `mipmap: true` needs half float to be color-renderable on the device
206
+ * (`limits.halfFloatRenderable`; an extension almost every device has) and
207
+ * throws where it is not. Sample-only like the 32-bit formats (no
208
+ * readback, copy or display); also a draw target format where half float
209
+ * is renderable ({@link createDrawTarget}, {@link createCubeDrawTarget}:
210
+ * HDR views, probes and bakes).
211
+ *
212
+ * "rgba8-srgb" is "rgba8" whose bytes are sRGB-encoded: a shader sampling
213
+ * it reads linear light (the decode is done in hardware, before filtering,
214
+ * so `filter`, `mipmap` and `anisotropy` are all correct); alpha is not
215
+ * decoded. The color-map format of linear-space lighting - base color and
216
+ * emissive maps - while data maps (normal, roughness, metallic) stay
217
+ * "rgba8". Sample-only: readTexture and copyTexture throw (a readback
218
+ * would decode rather than return the stored bytes) and displaying one
219
+ * via `<texture src>` is out of contract; also a draw target format (the
220
+ * target encodes what a pass writes).
221
+ *
222
+ * Reserved future value of this same vocabulary: "etc2-rgba8" (compressed
223
+ * uploads).
166
224
  */
167
- export type TextureFormat = "rgba8" | "r8"
225
+ export type TextureFormat = "rgba8" | "rgba8-srgb" | "r8" | "r32f" | "rgba32f" | "rgba16f"
168
226
  export type TextureFormatOption = { format?: TextureFormat }
169
227
  /**
170
228
  * This device's hard ceilings, queried once at startup: process constants.
@@ -178,6 +236,8 @@ declare module "flux:gpu" {
178
236
  export let limits: {
179
237
  /** Largest width/height of any texture or render target, in pixels (>= 2048). */
180
238
  maxTextureSize: number
239
+ /** Largest face edge of a cube map, in pixels (>= 2048); its own GL ceiling. */
240
+ maxCubeMapSize: number
181
241
  /**
182
242
  * Sampler inputs one pass may bind (>= 16): a target's `textures`
183
243
  * entries; on a window shader the runtime-filled `uSource` (and
@@ -186,26 +246,73 @@ declare module "flux:gpu" {
186
246
  maxTextureUnits: number
187
247
  /** Vertex attributes one pipeline may declare (>= 16). */
188
248
  maxVertexAttribs: number
249
+ /**
250
+ * Highest `anisotropy` level the device samples at (a power of two, 1
251
+ * when the device has no anisotropic filtering). A report, not a
252
+ * ceiling: a higher requested level is clamped to it silently, so
253
+ * `anisotropy: limits.maxAnisotropy` is the "as sharp as this device
254
+ * goes" spelling.
255
+ */
256
+ maxAnisotropy: number
257
+ /**
258
+ * vec4 uniform slots a vertex stage may declare (>= 256): a mat4 costs
259
+ * 4, a `mat4 u[N]` array 4N. What bounds a bone-palette array - a
260
+ * dynamically indexed array keeps every declared element active, so
261
+ * the declaration itself must fit or the program fails to link.
262
+ */
263
+ maxVertexUniformVectors: number
264
+ /**
265
+ * Whether half float is color-renderable on this device (the
266
+ * EXT_color_buffer_half_float extension, which almost every device
267
+ * has): what a generated mip chain needs, so `mipmap: true` on an
268
+ * "rgba16f" texture throws where this is false.
269
+ */
270
+ halfFloatRenderable: boolean
189
271
  }
190
272
  /**
191
- * Create an immutable texture from a pixel buffer (exactly
192
- * width*height*bytesPerPixel bytes: *4 for the default "rgba8" format, *1
193
- * for "r8"). Returns the texture id.
273
+ * Create an immutable texture from a pixel buffer holding exactly one
274
+ * frame at the declared format's size. The view type must match the
275
+ * format: byte formats ("rgba8", "rgba8-srgb", "r8") take a Uint8Array,
276
+ * float formats ("r32f", "rgba32f", "rgba16f") a Float32Array. Returns
277
+ * the texture id.
278
+ */
279
+ export function createTexture(data: Uint8Array | Float32Array, width: number, height: number, opts?: SamplerOptions & TextureFormatOption & LabelOption): TextureId
280
+ /**
281
+ * Create a cube map from six square faces in GL order (+X, -X, +Y, -Y,
282
+ * +Z, -Z), each exactly one `size` x `size` frame at the declared format
283
+ * (view type per format as in {@link createTexture}) - or from an explicit
284
+ * mip chain: an array of such six-face arrays, level 0 first, sizes
285
+ * halving down to the 1x1 level (the FULL chain; a shorter one throws),
286
+ * uploaded as given instead of generated, so it carries prefiltered
287
+ * levels and needs no color-renderable format (an "rgba16f" chain works
288
+ * on every device). Explicit levels imply `mipmap: true` (`mipmap: false`
289
+ * with them throws). Returns an ordinary texture id that only a `uniform
290
+ * samplerCube` binding consumes - sampled by direction (`texture(uEnv,
291
+ * dir)`, `textureLod` for a level when the chain exists). Sampler-only
292
+ * and create-once: `<texture src>` cannot display it, and uploadTexture,
293
+ * resizeTexture, readTexture and copyTexture throw for it. `wrap` is
294
+ * accepted and has no effect (cube filtering is seamless). A cube map on a
295
+ * `sampler2D`, or a 2D texture on a `samplerCube`, throws at the bind.
296
+ * GL's convention holds: each face is what a lookup in that direction
297
+ * returns (the cube seen from outside), so a face set authored as seen
298
+ * from inside (Three's) is mirrored per image before upload.
194
299
  */
195
- export function createTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & TextureFormatOption & LabelOption): TextureId
300
+ export function createCubeTexture(faces: (Uint8Array | Float32Array)[] | (Uint8Array | Float32Array)[][], size: number, opts?: SamplerOptions & TextureFormatOption & LabelOption): TextureId
196
301
  /**
197
302
  * Create a texture intended to be updated later via {@link uploadTexture}. The
198
- * seed buffer must hold at least one frame (width*height bytes at the
199
- * declared format's pixel size) and may hold more (uploadTexture selects a
200
- * frame by offset).
303
+ * seed buffer must hold at least one frame at the declared format's size
304
+ * and may hold more (uploadTexture selects a frame by offset); like
305
+ * {@link createTexture}, the view type must match the format (Uint8Array
306
+ * for byte formats, Float32Array for float formats).
201
307
  */
202
- export function createMutableTexture(data: Uint8Array, width: number, height: number, opts?: SamplerOptions & TextureFormatOption & LabelOption): TextureId
308
+ export function createMutableTexture(data: Uint8Array | Float32Array, width: number, height: number, opts?: SamplerOptions & TextureFormatOption & LabelOption): TextureId
203
309
  /**
204
- * Replace a mutable texture's pixels; the frame size follows the format the
205
- * id was created with. `data` may hold several frames; `offset` (default 0)
206
- * selects which frame to upload.
310
+ * Replace a mutable texture's pixels; the frame size and required view
311
+ * type (Uint8Array for byte formats, Float32Array for float formats)
312
+ * follow the format the id was created with. `data` may hold several
313
+ * frames; `offset` (default 0) selects which frame to upload.
207
314
  */
208
- export function uploadTexture(id: TextureId, data: Uint8Array, offset?: number): void
315
+ export function uploadTexture(id: TextureId, data: Uint8Array | Float32Array, offset?: number): void
209
316
  /**
210
317
  * Replace a texture's storage with a new size at the same id (an id-stable
211
318
  * resize): `<texture src>` references and shader sampler bindings keep
@@ -215,7 +322,7 @@ declare module "flux:gpu" {
215
322
  * state). Render target ids are rejected - resize those with
216
323
  * {@link setTargetSize}.
217
324
  */
218
- export function resizeTexture(id: TextureId, data: Uint8Array, width: number, height: number): void
325
+ export function resizeTexture(id: TextureId, data: Uint8Array | Float32Array, width: number, height: number): void
219
326
  /**
220
327
  * Destroy a texture (immutable, mutable, or shader). Frame-safe: the id is
221
328
  * reclaimed by the runtime once the render tree no longer references it, so
@@ -277,7 +384,14 @@ declare module "flux:gpu" {
277
384
  * 300 es`, `precision highp float;`, `uniform vec2 iResolution;`, plus
278
385
  * `out vec4 fragColor;` for a fragment stage (the same text
279
386
  * {@link createPipelineTexture} injects). Do not combine `header` with your
280
- * own `#version` line. Returns a shader (stage) id in its own id space;
387
+ * own `#version` line. The header declares those names rather than only
388
+ * filling them, so a source compiled with `header: true` must not declare
389
+ * them again: `uniform vec3 iResolution;` carried over from a ported shader
390
+ * is the usual mistake and fails as a redefinition (under the header
391
+ * iResolution is vec2; only a complete source may declare it vec3 and get
392
+ * `(w, h, 1.0)`). Everything else - samplers, app-driven uniforms,
393
+ * varyings (no `vUV` here) - is the source's own declaration.
394
+ * Returns a shader (stage) id in its own id space;
281
395
  * compile errors throw here, synchronously, at a call site the app chose.
282
396
  * Free with {@link destroyShader}.
283
397
  *
@@ -419,6 +533,8 @@ declare module "flux:gpu" {
419
533
  /** One buffer per instance slot of the pipeline (index = the
420
534
  * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
421
535
  instanceBuffers?: BufferId[]
536
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
537
+ instanceOrder?: InstanceOrder
422
538
  clearColor?: [number, number, number, number]
423
539
  render?: "auto" | "manual"
424
540
  loadOp?: "clear" | "load"
@@ -577,6 +693,90 @@ declare module "flux:gpu" {
577
693
  instanceBuffer?: BufferId
578
694
  instanceBuffers?: BufferId[]
579
695
  } & ({} | IndexBinding)
696
+ /**
697
+ * A draw entry's instance ORDER, declared at creation via the
698
+ * `instanceOrder` option: the entry draws the records of its instance
699
+ * buffer in KEY order instead of buffer order, while the writer keeps
700
+ * addressing stable slots - raise and y-sort without record churn, and
701
+ * back-to-front over a large population without per-record JS. (This
702
+ * orders one entry's RECORDS; {@link setDrawOrder} orders a target's
703
+ * ENTRIES - different granularity, unrelated verbs.)
704
+ *
705
+ * The key, one of two modes fixed at creation, both offsets counted in
706
+ * FLOATS into one instance record: `field` reads the single f32 at that
707
+ * offset (a y coordinate, an age, an explicit sort field); `position`
708
+ * reads the vec3 at that offset and keys on dot(position, `direction`) -
709
+ * view depth along a direction you update per camera move with
710
+ * `orderDirection` (see {@link OrderUpdate}). Ascending by default
711
+ * (smallest key draws first); `descending: true` for back-to-front when
712
+ * larger keys are nearer. The sort is stable - equal keys keep slot
713
+ * order, so untouched populations draw exactly as without an order - and
714
+ * NaN keys sort last (ascending).
715
+ *
716
+ * The order materializes when the buffer is PUBLISHED: each
717
+ * {@link beginBufferWrite}/{@link endBufferWrite} publish gathers the
718
+ * records into key order on the way to the GPU (one extra copy of the
719
+ * published bytes; a single-buffer entry retains nothing). A population
720
+ * that republishes each frame re-orders each frame for free; a direction
721
+ * change alone takes effect at the next publish - unless the entry opts
722
+ * into `retain`, where it re-orders immediately from core's retained
723
+ * copy. Because the GPU-side
724
+ * contents are the gathered records, {@link writeBuffer} on an ordered
725
+ * buffer throws (a byte-offset write would land on the wrong records -
726
+ * publish whole record sets through the lease), {@link readBuffer} reads
727
+ * back gathered order, a publish must be a whole number of records, and
728
+ * `instanceCount` below the published record count draws the first N in
729
+ * key order.
730
+ *
731
+ * An entry with SEVERAL instance buffers orders them all under ONE
732
+ * permutation: the key reads from slot 0's records, and every other
733
+ * slot's publishes gather to match, so split records (a core-written
734
+ * pose buffer plus an app-written style buffer) always describe the same
735
+ * draw order. This retains a copy of each slot's records: when the key
736
+ * order changes - including through spatial record sinks, with no
737
+ * publish from the app anywhere - the sibling buffers republish
738
+ * themselves in the same frame. The entry's instance buffers must be
739
+ * pairwise distinct, each ordered by no other entry; a buffer swap
740
+ * ({@link BufferUpdate}) carries the order to the new buffers, every
741
+ * swapped slot at once - republish app-written slots after a swap, the
742
+ * new buffer starts empty.
743
+ */
744
+ export type InstanceOrder = ({ field: number } | { position: number; direction: [number, number, number] }) & {
745
+ descending?: boolean
746
+ /**
747
+ * Which instance slot's records hold the key (default 0). On a
748
+ * multi-buffer entry the key may live in any one slot - a core-written
749
+ * pose slot keyed by position, or an app-written style slot keyed by
750
+ * an explicit sort field - and every other slot gathers under its
751
+ * permutation.
752
+ */
753
+ slot?: number
754
+ /**
755
+ * The retained-copy strategy, for write-once populations (a splat
756
+ * cloud: records written once, only the ORDER changes per camera
757
+ * move). Core keeps a CPU copy of each slot's published records, and
758
+ * an `orderDirection` update re-sorts that copy and republishes the
759
+ * entry's buffers core-side, in the same call, with no publish from
760
+ * the app; when the re-sort leaves the order unchanged nothing
761
+ * uploads, so a parked camera costs nothing. The copy is the
762
+ * deliberate cost (the records' size in CPU memory); publishes still
763
+ * go through the lease and update the copy. Position keys only - a
764
+ * field key re-orders when its records republish, so retaining buys
765
+ * nothing there and throws.
766
+ */
767
+ retain?: boolean
768
+ }
769
+ /**
770
+ * The order half of a draw-entry update: `orderDirection` replaces the
771
+ * projected key's direction ({@link InstanceOrder}'s `position` mode) -
772
+ * the per-camera-move update. On a gather entry it stages sort state for
773
+ * the entry's NEXT publish and renders nothing by itself; on a retained
774
+ * entry (`retain: true`) it re-sorts core's copy right away and, when
775
+ * the order changed, republishes the entry's buffers - no publish from
776
+ * the app. On an entry without a position-keyed instance order it
777
+ * throws.
778
+ */
779
+ export type OrderUpdate = { orderDirection?: [number, number, number] }
580
780
 
581
781
  /**
582
782
  * Compile a GLSL ES vertex+fragment pipeline into an offscreen texture of
@@ -625,6 +825,8 @@ declare module "flux:gpu" {
625
825
  /** One buffer per instance slot (index = the attributes' `slot`);
626
826
  * pass this OR `instanceBuffer`, not both. */
627
827
  instanceBuffers?: BufferId[]
828
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
829
+ instanceOrder?: InstanceOrder
628
830
  topology?: Topology
629
831
  depth?: boolean
630
832
  depthWrite?: boolean
@@ -667,12 +869,18 @@ declare module "flux:gpu" {
667
869
  * closes and nothing is published. Always closes the lease, error or not;
668
870
  * throws when no write is open or `byteLength` exceeds the buffer size.
669
871
  * Pipelines drawing from the buffer re-render, like {@link writeBuffer}.
872
+ * On an ordered instance buffer ({@link InstanceOrder}) the publish is
873
+ * where the order materializes: the records reach the GPU gathered into
874
+ * key order, and `byteLength` must be a whole number of records.
670
875
  */
671
876
  export function endBufferWrite(id: BufferId, byteLength?: number): void
672
877
  /**
673
878
  * Overwrite part of a vertex buffer at `byteOffset` (default 0), within the
674
879
  * size it was created with. Pipelines drawing from the buffer re-render
675
- * with their last-applied params.
880
+ * with their last-applied params. Throws on an ordered instance buffer
881
+ * ({@link InstanceOrder}): its GPU contents are in key order, so a
882
+ * byte-offset write would land on the wrong records - publish whole
883
+ * record sets through {@link beginBufferWrite}/{@link endBufferWrite}.
676
884
  */
677
885
  export function writeBuffer(id: BufferId, data: Uint8Array, byteOffset?: number): void
678
886
  /**
@@ -700,7 +908,7 @@ declare module "flux:gpu" {
700
908
  * buffer and extends the range into it, and a call that throws changes
701
909
  * nothing (range and buffers commit together or not at all).
702
910
  */
703
- export function setDraw(id: TextureId, draw: (DrawRange | IndexRange) & BufferUpdate): void
911
+ export function setDraw(id: TextureId, draw: (DrawRange | IndexRange) & BufferUpdate & OrderUpdate): void
704
912
  /**
705
913
  * Create a draw target: a render target whose contents are an ordered,
706
914
  * mutable LIST of draws - one render clears once, then executes every
@@ -741,6 +949,17 @@ declare module "flux:gpu" {
741
949
  * Returns a texture id (display, resize, destroy like any target; entries
742
950
  * die with it).
743
951
  *
952
+ * `format` is the color storage: "rgba8" (default), "rgba8-srgb" (the
953
+ * target encodes what a pass writes and decodes on sample, like an
954
+ * uploaded sRGB texture) or "rgba16f" (half float: the range a pass
955
+ * writes survives - an HDR view or bake; renderable where
956
+ * `limits.halfFloatRenderable`, throws elsewhere). Other formats throw
957
+ * (not color-renderable). A target of either non-default format is
958
+ * SAMPLER-ONLY: bind it to a `sampler2D`; a `<texture>` leaf cannot
959
+ * display it, {@link readTexture} and {@link copyTexture} refuse it -
960
+ * render it through a pass into an rgba8 target for those. Multisampling
961
+ * and resize work at the format.
962
+ *
744
963
  * `into` makes a SUB-TARGET: a draw target that renders into the `width`
745
964
  * x `height` rectangle at `x`/`y` (top-left origin, the texture leaf's
746
965
  * `srcX`/`srcY` space; default 0) of draw target `into`'s storage instead
@@ -752,8 +971,8 @@ declare module "flux:gpu" {
752
971
  * cost one pass instead of N. The returned id is not a texture: sample,
753
972
  * display (`<d-texture src={parent} srcX srcY srcW srcH>`), read back and
754
973
  * copy the PARENT; `depthTexture(parent)` is the tile's depth too. Depth,
755
- * `samples`, `render` and `loadOp` are the parent's (passing them
756
- * throws), `clearColor` is the tile's own. A rectangle partly outside the
974
+ * `samples`, `format`, `render` and `loadOp` are the parent's (passing
975
+ * them throws), `clearColor` is the tile's own. A rectangle partly outside the
757
976
  * parent is clipped; {@link setTargetRect} moves it. Tiles do not nest.
758
977
  * Destroying the parent destroys its tiles.
759
978
  */
@@ -763,6 +982,7 @@ declare module "flux:gpu" {
763
982
  params?: ShaderParams | null,
764
983
  opts?: {
765
984
  depth?: boolean | "texture"
985
+ format?: "rgba8" | "rgba8-srgb" | "rgba16f"
766
986
  textures?: TextureBindings
767
987
  clearColor?: [number, number, number, number]
768
988
  render?: "auto" | "manual"
@@ -774,6 +994,46 @@ declare module "flux:gpu" {
774
994
  } & SamplerOptions &
775
995
  LabelOption,
776
996
  ): TextureId
997
+ /**
998
+ * Create a cube draw target: a draw target whose output is a `size` x
999
+ * `size` CUBE MAP, rendered one face at a time with `renderTarget(id,
1000
+ * face)` (GL order: 0 +X, 1 -X, 2 +Y, 3 -Y, 4 +Z, 5 -Z) - the
1001
+ * reflection-probe and sky-bake primitive. One entry list, shared params
1002
+ * and bindings as on {@link createDrawTarget}; the app writes the face's
1003
+ * camera into the shared params and renders, six times. Manual by
1004
+ * contract (`render` defaults to "manual"; "auto" throws), one private
1005
+ * depth renderbuffer for all faces (`depth: true`; "texture" throws), no
1006
+ * `samples`, no `into`; `format` rgba8, rgba8-srgb or rgba16f. `mipmap:
1007
+ * true` allocates the whole chain: a face
1008
+ * render without a level regenerates it from level 0 (as every content
1009
+ * write on a mipmapped target), a render into an explicit `level`
1010
+ * writes that level alone at its own edge - how a prefiltered
1011
+ * environment chain is rendered level by level. The id is a cube map to
1012
+ * every consumer: bind it to a `samplerCube`, never display, read back,
1013
+ * copy or resize it. Each face pass inverts the front-face rule: a GL cube
1014
+ * face is seen from outside, the x mirror of a 2D target's image, so
1015
+ * render every face through an x-mirrored projection and the pipelines'
1016
+ * cull modes keep their meaning (derivative-built tangent frames flip
1017
+ * with the mirror - a known artifact of mirrored views).
1018
+ */
1019
+ export function createCubeDrawTarget(
1020
+ size: number,
1021
+ params?: ShaderParams | null,
1022
+ opts?: {
1023
+ depth?: boolean
1024
+ /** rgba8 (default); rgba8-srgb: the cube encodes what a pass writes
1025
+ * and decodes on sample, like an uploaded sRGB cube; rgba16f: half
1026
+ * float, the range a pass writes survives (HDR probes), renderable
1027
+ * where `limits.halfFloatRenderable` (throws elsewhere - fall back to
1028
+ * rgba8). Other formats throw: not color-renderable. */
1029
+ format?: "rgba8" | "rgba8-srgb" | "rgba16f"
1030
+ textures?: TextureBindings
1031
+ clearColor?: [number, number, number, number]
1032
+ render?: "manual"
1033
+ loadOp?: "clear" | "load"
1034
+ } & SamplerOptions &
1035
+ LabelOption,
1036
+ ): TextureId
777
1037
  /**
778
1038
  * The depth texture of a draw target created with `depth: "texture"`: a
779
1039
  * texture id of its own, stable for the target's life (a
@@ -786,9 +1046,15 @@ declare module "flux:gpu" {
786
1046
  * depth throws. SAMPLER-ONLY: it is not an upload texture and not a
787
1047
  * readback source (`readTexture`/`copyTexture` throw - render it through a
788
1048
  * pass to read it), its sampling is fixed at `nearest`/`clamp` (a depth
789
- * texture is only complete at nearest without a comparison mode - filter
790
- * in the shader, e.g. a PCF loop), and it dies with its target
791
- * (`destroyTexture` on it throws). Displaying it via `<texture src>`
1049
+ * texture is only complete at nearest without a comparison mode), and it
1050
+ * dies with its target (`destroyTexture` on it throws). A pipeline
1051
+ * program may instead declare the uniform as `sampler2DShadow`: the
1052
+ * engine then binds a COMPARISON sampler for it - `texture(map,
1053
+ * vec3(uv, ref))` returns the hardware LEQUAL compare of `ref` against
1054
+ * the map, LINEAR-weighted over the 2x2 footprint (one-tap PCF). Only a
1055
+ * depth texture may back a sampler2DShadow uniform (anything else is
1056
+ * refused at bind); a plain `sampler2D` keeps the raw nearest reads.
1057
+ * Displaying it via `<texture src>`
792
1058
  * shows the depth in the red channel. Throws for a target without texture
793
1059
  * depth.
794
1060
  */
@@ -833,6 +1099,8 @@ declare module "flux:gpu" {
833
1099
  /** One buffer per instance slot of the pipeline (index = the
834
1100
  * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
835
1101
  instanceBuffers?: BufferId[]
1102
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
1103
+ instanceOrder?: InstanceOrder
836
1104
  before?: DrawId
837
1105
  } & (DrawRange | (IndexBinding & IndexRange)),
838
1106
  ): DrawId
@@ -929,7 +1197,7 @@ declare module "flux:gpu" {
929
1197
  * single entry, same partial merge, bounds validation, and vocabulary
930
1198
  * rule (an indexed entry speaks {@link IndexRange}).
931
1199
  */
932
- export function setDrawRange(target: TextureId, draw: DrawId, update: DrawRange | IndexRange): void
1200
+ export function setDrawRange(target: TextureId, draw: DrawId, update: (DrawRange | IndexRange) & OrderUpdate): void
933
1201
  /**
934
1202
  * Swap one draw entry's buffers: the {@link BufferUpdate} half of
935
1203
  * {@link setDraw} addressed to a single entry, same replace-only rule and
@@ -945,7 +1213,9 @@ declare module "flux:gpu" {
945
1213
  * back-to-front, and re-issue the order when the camera moves. Entry
946
1214
  * state (params, textures, ranges) rides along untouched; ids are
947
1215
  * unaffected. Like every draw-list write it re-renders an auto target
948
- * once at the next flush, and folds silently on a manual one.
1216
+ * once at the next flush, and folds silently on a manual one. (This
1217
+ * orders a target's ENTRIES; ordering the instance RECORDS within one
1218
+ * entry is {@link InstanceOrder}.)
949
1219
  */
950
1220
  export function setDrawOrder(target: TextureId, order: DrawId[]): void
951
1221
  /**
@@ -960,9 +1230,16 @@ declare module "flux:gpu" {
960
1230
  * the one counting. Ping-pong feedback is two manual targets sampling
961
1231
  * each other, stepped alternately from `onFrame`; binding a target to
962
1232
  * ITSELF still throws (same-pass GL feedback, undefined pixels regardless
963
- * of who schedules it).
1233
+ * of who schedules it). `face` (0..5) names the face of a cube draw
1234
+ * target ({@link createCubeDrawTarget}) and is required there; a 2D
1235
+ * target takes none. `level` names the mip level of that face to render
1236
+ * into, on a `mipmap: true` cube target (0 up to the chain's last; the
1237
+ * pass runs at the level's edge and regenerates nothing); without it the
1238
+ * face's level 0 renders and the chain regenerates. Throws for a level
1239
+ * on a 2D target or beyond the chain (a cube target without `mipmap`
1240
+ * has level 0 only).
964
1241
  */
965
- export function renderTarget(id: TextureId): void
1242
+ export function renderTarget(id: TextureId, face?: number, level?: number): void
966
1243
  /**
967
1244
  * Overwrite a `render: "manual"` target with another texture's current
968
1245
  * pixels, GPU-side: the seed/history analog of {@link uploadTexture}
@@ -974,7 +1251,9 @@ declare module "flux:gpu" {
974
1251
  * copy sees the copy, and targets sampling `dst` update afterwards.
975
1252
  * Throws if either id is unknown, `dst` is not a manual target (the
976
1253
  * runtime owns those contents), or `src === dst`. `src` may be any
977
- * texture: uploaded, mutable, a camera frame, or another target's output.
1254
+ * texture: uploaded, mutable, a camera frame, or another target's output -
1255
+ * except a float-format texture (the copy would quantize its values to the
1256
+ * target's rgba8; float textures are sample-only, so it throws).
978
1257
  */
979
1258
  export function copyTexture(src: TextureId, dst: TextureId): void
980
1259
  /**
@@ -1013,7 +1292,8 @@ declare module "flux:gpu" {
1013
1292
  * Read back a registered texture's current pixels as RGBA8 (tightly packed,
1014
1293
  * top-to-bottom rows), for any texture id whatever created it (createTexture,
1015
1294
  * createShaderTexture, a render target). Synchronous. Throws if the id is
1016
- * unknown.
1295
+ * unknown, or names a float-format texture (upload-and-sample only: float
1296
+ * is not color-renderable in core GLES 3.0, so no readback path exists).
1017
1297
  */
1018
1298
  export function readTexture(id: TextureId): { width: number; height: number; data: Uint8Array }
1019
1299
  }
@@ -15,7 +15,7 @@ declare module "flux:rendertree" {
15
15
  lineHeight?: number
16
16
  /** measureText only. */
17
17
  maxLines?: number
18
- /** prepareText only: also report each unit's {@link TextUnit.carets}. */
18
+ /** prepareText only: also report each unit's {@link TextUnit.carets} (kerned per-glyph positions). */
19
19
  carets?: boolean
20
20
  /**
21
21
  * prepareText only: styled ranges over the text, in JS string offsets,
@@ -65,7 +65,11 @@ declare module "flux:rendertree" {
65
65
  * With `carets`: the caret positions inside the unit, one per grapheme
66
66
  * cluster boundary from its start (`offset` = start, x 0) to the end of
67
67
  * its shaped text (before any break characters), in order. `offset` is
68
- * into the prepared text, `x` from the unit's pen position.
68
+ * into the prepared text, `x` from the unit's pen position. These are
69
+ * the per-glyph x positions of the shaping that is drawn, kerning
70
+ * included: for placing or animating single glyphs, use them rather
71
+ * than measuring characters one at a time with measureText, which sees
72
+ * no neighbors and drifts on every kerning pair.
69
73
  */
70
74
  carets?: { offset: number, x: number }[]
71
75
  }
@@ -165,8 +169,11 @@ declare module "flux:rendertree" {
165
169
  export function measureText(text: string, options?: MeasureTextOptions): { width: number, height: number }
166
170
  /**
167
171
  * Segment `text` into wrap units and shape each in the given font (through
168
- * the shared word cache), for laying lines out in app code; see
169
- * layoutNextLine in @solidrt/core. Single style; `maxLines` is ignored.
172
+ * the shared word cache, so a text already drawn or prepared costs no
173
+ * shaping), for app-side line breaking - text into a shape, around an
174
+ * obstacle, across columns, fitted by size, per glyph; see layoutNextLine
175
+ * in @solidrt/core. `runs` restyle ranges, `carets` adds glyph positions;
176
+ * `maxLines` is ignored.
170
177
  */
171
178
  export function prepareText(text: string, options?: MeasureTextOptions): PreparedText
172
179
  /**
@@ -182,6 +189,15 @@ declare module "flux:rendertree" {
182
189
  * semantics), for comparing against pointer event coordinates.
183
190
  */
184
191
  export function getBoundingBoxViewport(id: number): { x: number, y: number, width: number, height: number } | null
192
+ /**
193
+ * The node's laid-out box in its parent's frame: layout output before any
194
+ * transform composes (DOM offsetLeft/offsetTop/offsetWidth/offsetHeight
195
+ * semantics). The untransformed companion to getBoundingBox: pointer events
196
+ * report local coordinates in this box's units, whatever designSize fits or
197
+ * transforms sit on the ancestor chain. Null for detached nodes and before
198
+ * the first layout.
199
+ */
200
+ export function getLayoutBox(id: number): { x: number, y: number, width: number, height: number } | null
185
201
  /**
186
202
  * The texture id of a snapshot repaint boundary's retained rasterization
187
203
  * (its subtree's pixels at display scale, premultiplied, top-left origin,
package/gui/spatial.d.ts CHANGED
@@ -97,16 +97,17 @@ declare module "flux:spatial" {
97
97
 
98
98
  export type ShapeId = number & { readonly __spatialShape: unique symbol }
99
99
 
100
- /** One hit of raycast(), nearest first. `face`/`uv`/`normal` are present
101
- * for nodes with a shape (uv only when the shape has UVs); a node with
102
- * bounds but no shape reports its local box, distance and point only. */
100
+ /** One hit of raycast(), nearest first. `face`/`uv` are present for
101
+ * nodes with a shape (uv only when the shape has UVs); a node with
102
+ * bounds but no shape is its local box's twelve triangles, so its hit
103
+ * carries the struck face's normal and no face/uv. */
103
104
  export type Hit = {
104
105
  node: NodeId
105
106
  /** World units along the normalized ray. */
106
107
  distance: number
107
108
  point: [number, number, number]
108
109
  /** World-space geometric normal, facing the ray. */
109
- normal?: [number, number, number]
110
+ normal: [number, number, number]
110
111
  /** Triangle index into the shape's index list. */
111
112
  face?: number
112
113
  uv?: [number, number]
@@ -116,6 +117,26 @@ declare module "flux:spatial" {
116
117
  * maxY, maxZ]. With one the node is in the picking index: its world box
117
118
  * follows the flush; hidden nodes stay in and are skipped at query time. */
118
119
  export function setBounds(node: NodeId, bounds: Float32Array | null): void
120
+ /**
121
+ * The clip volume gating every draw sink on `target`: its view-projection
122
+ * (Float32Array of 16, column-major), or null to lift it. An entry whose
123
+ * node box (grown by its cull margin) falls wholly outside reads
124
+ * instanceCount 0 like a hidden node, and comes back with a fresh params
125
+ * write. Nodes without a box, or with culling off, are never gated. Read
126
+ * at flush: set it before the flush that should see it.
127
+ */
128
+ export function setFrustum(target: TextureId, viewProj: Float32Array | null): void
129
+ /** Whether frustums gate the node's draw sinks (default true), and the
130
+ * world-unit margin the test grows its box by (default 0). */
131
+ export function setCull(node: NodeId, enabled: boolean, margin: number): void
132
+ /** A local box for culling ONLY - the node stays out of the picking
133
+ * index (setBounds is the indexed one). null falls back to its bounds. */
134
+ export function setCullBounds(node: NodeId, bounds: Float32Array | null): void
135
+ /** Cull the node by the union of these nodes' world boxes instead of
136
+ * its own (a skinned mesh by its joints): an empty list restores its
137
+ * own. Members without a box contribute nothing; with none at all the
138
+ * node is not culled. */
139
+ export function setCullGroup(node: NodeId, members: NodeId[]): void
119
140
  /**
120
141
  * Triangle data for the picking narrowphase, one copy shared by every
121
142
  * node that references it: positions read from an interleaved vertex
@@ -131,15 +152,48 @@ declare module "flux:spatial" {
131
152
  * direction need not be normalized; distances are world units. Reads
132
153
  * the index as of the last flush. */
133
154
  export function raycast(origin: Float32Array, direction: Float32Array): Hit[]
155
+ /** A query volume's layout for overlap/sweep: "capsule" is
156
+ * 7 floats (a, b, radius; a sphere when a == b), "box" is 10 (center,
157
+ * half extents, unit quaternion xyzw). */
158
+ export type VolumeKind = "capsule" | "box"
159
+ /** One node overlap() touches: its deepest contact - the point on
160
+ * the node's surface, the unit direction out of it, and the depth along
161
+ * that direction that clears the contact. */
162
+ export type Overlap = {
163
+ node: NodeId
164
+ point: [number, number, number]
165
+ normal: [number, number, number]
166
+ depth: number
167
+ }
168
+ /** One node sweep() touches: `time` is the fraction of the motion
169
+ * at first touch, `point` the touch point on the node's surface,
170
+ * `normal` the unit normal there facing the volume. */
171
+ export type Impact = {
172
+ node: NodeId
173
+ time: number
174
+ point: [number, number, number]
175
+ normal: [number, number, number]
176
+ }
177
+ /**
178
+ * Every shown node with bounds the volume touches, each with its
179
+ * deepest contact. A node with a shape is tested per triangle in world
180
+ * space, so it holds under any transform; a node without one is its
181
+ * local box's twelve triangles (a flat rect is four of them, so a
182
+ * rotated sprite tests exactly - the 2d marquee's box query is a "box"
183
+ * volume). Surfaces, not solids: a volume wholly inside a closed mesh
184
+ * with no triangle in reach touches nothing. Unordered; reads the index
185
+ * as of the last flush, like raycast.
186
+ */
187
+ export function overlap(kind: VolumeKind, volume: Float32Array): Overlap[]
134
188
  /**
135
- * Every shown node with bounds whose local box, carried through its
136
- * world transform, overlaps the world-axis box `bounds` (a Float32Array
137
- * of 6: [minX, minY, minZ, maxX, maxY, maxZ]; touching counts, a point
138
- * is min == max). Tested by separating axes, so a rotated flat rect -
139
- * the 2d marquee case - tests exactly, never by its world AABB.
140
- * Unordered; reads the index as of the last flush, like raycast.
189
+ * The volume moved by `motion` (a Float32Array of 3): every shown node
190
+ * with bounds it touches on the way, at its first touch, earliest
191
+ * first. A node already in contact reports time 0 while the motion
192
+ * closes in, and nothing while it leaves or slides along the contact;
193
+ * a zero motion touches nothing. Same testing and index contract as
194
+ * overlap.
141
195
  */
142
- export function overlap(bounds: Float32Array): NodeId[]
196
+ export function sweep(kind: VolumeKind, volume: Float32Array, motion: Float32Array): Impact[]
143
197
 
144
198
  /**
145
199
  * Route the world DIRECTION of the node's local `vector` (a
@@ -150,14 +204,46 @@ declare module "flux:spatial" {
150
204
  * zeros. Every sink naming the same param shares one array (`len` must
151
205
  * agree); what the slots mean - light directions, an emitter axis - is
152
206
  * the caller's business, packed alongside its own non-spatial params.
153
- * One slot sink per target, like bindDraw: rebinding on the same target
154
- * replaces that sink (the abandoned slot zeroes), another target adds one.
207
+ * One slot sink per (target, name): rebinding the same param on the
208
+ * same target replaces that sink (the abandoned slot zeroes); another
209
+ * param or another target adds one, so a node may feed several arrays
210
+ * of one target (a spot light: its direction and its position).
155
211
  */
156
212
  export function bindDirectionSlot(node: NodeId, target: TextureId, name: string, len: number, index: number, vector: Float32Array): void
157
- /** Remove the node's slot sink on `target`, or every slot sink without
213
+ /**
214
+ * Route the node's world POSITION into vec3 slot `index` of the
215
+ * `len`-float shared array param `name` on a draw target -
216
+ * bindDirectionSlot's translation sibling, with the same sharing,
217
+ * zeroing and per-(target, name) replacement rules. What a positional
218
+ * light's slot follows.
219
+ */
220
+ export function bindPositionSlot(node: NodeId, target: TextureId, name: string, len: number, index: number): void
221
+ /** Remove the node's slot sinks on `target`, or every slot sink without
158
222
  * one (the abandoned slots zero at the next flush). */
159
223
  export function unbindSlot(node: NodeId, target?: TextureId): void
160
224
 
225
+ /**
226
+ * Route the node's world matrix, post-multiplied by the constant `post`
227
+ * (a Float32Array of 16, column-major), into row `row` of a float
228
+ * texture: the flush writes the 16 floats as the row's four rgba32f
229
+ * texels - the matrix-palette channel a vertex shader `texelFetch`es
230
+ * (a skin binds each joint node with `post` its inverse bind). Writes
231
+ * batch: however many bound nodes moved, each flush uploads at most one
232
+ * whole palette per texture. With `anchor` - one node shared by every
233
+ * bind on the texture, and an ANCESTOR of every bound node - rows are
234
+ * anchor-local (`inverse(anchorWorld) * world * post`), so a model
235
+ * root's placement stays out of its own palette. Rows keep updating
236
+ * while nodes are hidden (visibility is the drawing entry's business),
237
+ * and an unbound or destroyed node's row keeps its last value. Validated
238
+ * at bind time: the texture must be an uploadable rgba32f texture 4
239
+ * texels wide with `row` inside it. Rebinding the same texture replaces
240
+ * the node's slot there; another texture adds one.
241
+ */
242
+ export function bindTextureSlot(node: NodeId, texture: TextureId, row: number, post: Float32Array, anchor?: NodeId): void
243
+ /** Remove the node's texture slot on `texture`, or every texture slot
244
+ * without one (abandoned rows keep their last value). */
245
+ export function unbindTextureSlot(node: NodeId, texture?: TextureId): void
246
+
161
247
  /**
162
248
  * Route the node's world pose to record slot `index` of vertex buffer
163
249
  * `buffer` used as an instance buffer: the flush writes the 5 floats
@@ -172,7 +258,23 @@ declare module "flux:spatial" {
172
258
  * Rebinding replaces the node's record sink; the abandoned slot zeroes.
173
259
  */
174
260
  export function bindPoseRecord(node: NodeId, buffer: BufferId, index: number): void
175
- /** Remove the node's record sink (its slot zeroes at the next flush). */
261
+ /**
262
+ * Route the node's world matrix to record slot `index` of vertex buffer
263
+ * `buffer` as 16 floats (one column-major mat4) at float offset
264
+ * index * 16: the per-instance model matrix a vertex stage reads as
265
+ * four vec4 attributes. With `anchor` (an ANCESTOR of the node, one per
266
+ * buffer - every bind on the buffer names the same one) the record is
267
+ * the matrix RELATIVE to it, `inverse(anchorWorld) * world`, so a mesh's
268
+ * instances stay in the mesh's own space and its uModel still places
269
+ * the whole population (`uModel * instanceMatrix`); without one it is
270
+ * the plain world matrix. Batching, growth (retargetRecords) and
271
+ * rebinding as bindPoseRecord; a hidden, unbound or destroyed node's
272
+ * slot becomes a zero-scale matrix (w stays 1), so the instance
273
+ * collapses to a point and draws nothing. Every sink on one buffer
274
+ * shares one projection.
275
+ */
276
+ export function bindMatrixRecord(node: NodeId, buffer: BufferId, index: number, anchor?: NodeId): void
277
+ /** Remove the node's record sink (its slot hides at the next flush). */
176
278
  export function unbindRecord(node: NodeId): void
177
279
  /**
178
280
  * Move every record sink on buffer `old` to buffer `new`, slot indices
@@ -185,4 +287,83 @@ declare module "flux:spatial" {
185
287
  * when `new` already carries record sinks.
186
288
  */
187
289
  export function retargetRecords(old: BufferId, next: BufferId): void
290
+
291
+ /** A registered clip's handle (createClip). */
292
+ export type ClipId = number
293
+ /** A playing clip instance's handle (createPlayer). */
294
+ export type PlayerId = number
295
+
296
+ /**
297
+ * Register a baked animation clip: `duration` in seconds, `meta` four
298
+ * words per channel - [targetSlot, path (0 position, 1 rotation,
299
+ * 2 scale), interpolation (0 step, 1 linear, 2 cubic), keyCount] - and
300
+ * `times`/`values` every channel's key arrays concatenated in meta
301
+ * order (3 floats per key, 4 for rotation; cubic stores three elements
302
+ * per key: in-tangent, value, out-tangent, tangents per second - the
303
+ * glTF CUBICSPLINE layout). A clip is shared data: `targetSlot`
304
+ * indexes the target table each PLAYER supplies, so one clip drives
305
+ * any number of instances, and retargeting is a different table.
306
+ */
307
+ export function createClip(duration: number, meta: Uint32Array, times: Float32Array, values: Float32Array): ClipId
308
+ /** Free a clip. Players still on it drop at their next advance (a
309
+ * "dropped" spatialClipEnd). */
310
+ export function destroyClip(clip: ClipId): void
311
+ /**
312
+ * Start playing `clip`: `targets[slot]` is the node each channel
313
+ * animates - every target must be a live scene node (throws
314
+ * otherwise). Players advance on the frame clock BEFORE each frame's
315
+ * JS, sample and weight-blend every active player per (node, path) -
316
+ * two players on one node crossfade - and write the blended TRS into
317
+ * the arena, so frame handlers read and may overwrite freshly posed
318
+ * nodes (last write wins) and the frame's flush publishes the result.
319
+ * `speed` scales clip time (1 = as authored); `loop` wraps, else the
320
+ * player holds its final pose and reports once; `weight`/`fade` start
321
+ * the crossfade state (weight 0..1, fade = weight change per second -
322
+ * positive fades in, negative out; past 0 the player is removed).
323
+ * When a player finishes or is removed without finishing (faded out,
324
+ * clip or target destroyed), the "spatialClipEnd" engine event
325
+ * (srt:events) fires with payload { player, reason: "finished" |
326
+ * "dropped" }, before the same frame's handlers.
327
+ */
328
+ export function createPlayer(clip: ClipId, targets: NodeId[], speed: number, loop: boolean, weight: number, fade: number): PlayerId
329
+ /** Write the given fields of a player - the O(changes) crossfade
330
+ * channel. Setting `time` (seconds) also re-arms a finished player's
331
+ * end report. Throws on an id that already dropped. */
332
+ export function setPlayer(player: PlayerId, update: { weight?: number; fade?: number; speed?: number; time?: number }): void
333
+ /**
334
+ * Bind root motion to a player: `channel` of `clip` (a position channel
335
+ * - normally the ROOT track of the authored clip, while the player
336
+ * plays an in-place variant that holds the root still) is sampled at
337
+ * the player's time each advance, and its travel since the previous
338
+ * advance (continuous across a loop wrap, weighted by the player's
339
+ * weight) is reported as one "spatialRootMotion" engine event
340
+ * (srt:events) per advance, payload { player, x, y, z, yaw }, before
341
+ * the frame's handlers. The translation is given in the root's CURRENT
342
+ * facing (the clip's own turn so far undone), i.e. the character's
343
+ * local frame. `rotation`, a rotation channel of the same clip (the
344
+ * root's), adds the yaw half: the twist about `up`, in radians (zero
345
+ * without it). With an `anchor` the translation, rotated by the
346
+ * anchor's own rotation, is added to the anchor's position and the yaw
347
+ * turns the anchor about `up` in its own frame - Unity's
348
+ * applyRootMotion; without one it is Godot's root_motion_track, the
349
+ * app's to apply. `opts.up` is the root's parent-space up axis (default
350
+ * [0, 1, 0]); `opts.vertical: false` drops the component along it from
351
+ * the delta (the height stays in the pose). Binding primes at the
352
+ * player's current time, so the next advance already delivers. A
353
+ * second call rebinds. Throws on a dropped player, a wrong channel
354
+ * kind, a zero up axis, or a dead anchor.
355
+ */
356
+ export function bindRootMotion(player: PlayerId, clip: ClipId, channel: number, rotation?: number, anchor?: NodeId, opts?: { up?: [number, number, number]; vertical?: boolean }): void
357
+ /** Remove a player at once, holding whatever pose it last wrote (no
358
+ * event; stop-with-fade is a setPlayer fade write instead). A dropped
359
+ * id is fine. */
360
+ export function destroyPlayer(player: PlayerId): void
361
+ /**
362
+ * Fill `out` (a Float32Array of 10) with the node's CURRENT local
363
+ * transform - position xyz, quaternion xyzw, scale xyz - as the arena
364
+ * holds it, players' writes included. The pose read for root-motion
365
+ * strips and skeleton copies: JS-side mirrors of a node animated by a
366
+ * player go stale, this does not.
367
+ */
368
+ export function readTransform(node: NodeId, out: Float32Array): void
188
369
  }
package/index.d.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  /// <reference path="./standards/websocket.d.ts" />
26
26
  /// <reference path="./standards/abort.d.ts" />
27
27
  /// <reference path="./standards/crypto.d.ts" />
28
+ /// <reference path="./standards/clipboard.d.ts" />
28
29
 
29
30
  // GUI capabilities (present only on a gui-enabled runtime). rendertree/camera/
30
31
  // microphone/gpu are flux:* modules like the rest; requestAnimationFrame stays a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/flux-types",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -0,0 +1,25 @@
1
+ // navigator. The one navigator surface flux provides: the clipboard, text
2
+ // only. Solidrt semantics on the standard shape: no permissions model and no
3
+ // clipboard events - a single known app does not ask itself for permission.
4
+ // GUI builds only (the clipboard belongs to the windowing platform); on a
5
+ // headless flux there is no `navigator` at all, so
6
+ // `typeof navigator !== "undefined"` is the availability check.
7
+
8
+ interface Clipboard {
9
+ /**
10
+ * Resolves to the OS clipboard's text, "" when the clipboard is empty or
11
+ * holds no text. Rejects on platform failure.
12
+ */
13
+ readText(): Promise<string>
14
+ /**
15
+ * Replaces the OS clipboard's contents with `text`. Rejects on platform
16
+ * failure or a non-string argument.
17
+ */
18
+ writeText(text: string): Promise<void>
19
+ }
20
+
21
+ interface Navigator {
22
+ readonly clipboard: Clipboard
23
+ }
24
+
25
+ declare var navigator: Navigator