@solidrt/flux-types 0.0.55 → 0.0.57

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). */
@@ -152,7 +155,14 @@ declare module "flux:gpu" {
152
155
  * not overridable - the chain either exists on the id or it does not.
153
156
  */
154
157
  export type TextureBinding = TextureId | { id: TextureId; filter?: FilterMode; wrap?: WrapMode }
155
- /** 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
+ */
156
166
  export type TextureBindings = Record<string, TextureBinding>
157
167
  /**
158
168
  * A free-form debug name every create accepts (WebGPU's label): surfaced by
@@ -174,8 +184,45 @@ declare module "flux:gpu" {
174
184
  * texture as `(v, 0, 0, 1)` - read `.r`; displaying one via `<texture src>`
175
185
  * shows that same red-channel reading. Shader/pipeline targets and
176
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).
177
224
  */
178
- export type TextureFormat = "rgba8" | "r8"
225
+ export type TextureFormat = "rgba8" | "rgba8-srgb" | "r8" | "r32f" | "rgba32f" | "rgba16f"
179
226
  export type TextureFormatOption = { format?: TextureFormat }
180
227
  /**
181
228
  * This device's hard ceilings, queried once at startup: process constants.
@@ -189,6 +236,8 @@ declare module "flux:gpu" {
189
236
  export let limits: {
190
237
  /** Largest width/height of any texture or render target, in pixels (>= 2048). */
191
238
  maxTextureSize: number
239
+ /** Largest face edge of a cube map, in pixels (>= 2048); its own GL ceiling. */
240
+ maxCubeMapSize: number
192
241
  /**
193
242
  * Sampler inputs one pass may bind (>= 16): a target's `textures`
194
243
  * entries; on a window shader the runtime-filled `uSource` (and
@@ -205,26 +254,65 @@ declare module "flux:gpu" {
205
254
  * goes" spelling.
206
255
  */
207
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
208
271
  }
209
272
  /**
210
- * Create an immutable texture from a pixel buffer (exactly
211
- * width*height*bytesPerPixel bytes: *4 for the default "rgba8" format, *1
212
- * 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.
213
299
  */
214
- 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
215
301
  /**
216
302
  * Create a texture intended to be updated later via {@link uploadTexture}. The
217
- * seed buffer must hold at least one frame (width*height bytes at the
218
- * declared format's pixel size) and may hold more (uploadTexture selects a
219
- * 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).
220
307
  */
221
- 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
222
309
  /**
223
- * Replace a mutable texture's pixels; the frame size follows the format the
224
- * id was created with. `data` may hold several frames; `offset` (default 0)
225
- * 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.
226
314
  */
227
- export function uploadTexture(id: TextureId, data: Uint8Array, offset?: number): void
315
+ export function uploadTexture(id: TextureId, data: Uint8Array | Float32Array, offset?: number): void
228
316
  /**
229
317
  * Replace a texture's storage with a new size at the same id (an id-stable
230
318
  * resize): `<texture src>` references and shader sampler bindings keep
@@ -234,7 +322,7 @@ declare module "flux:gpu" {
234
322
  * state). Render target ids are rejected - resize those with
235
323
  * {@link setTargetSize}.
236
324
  */
237
- 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
238
326
  /**
239
327
  * Destroy a texture (immutable, mutable, or shader). Frame-safe: the id is
240
328
  * reclaimed by the runtime once the render tree no longer references it, so
@@ -445,6 +533,8 @@ declare module "flux:gpu" {
445
533
  /** One buffer per instance slot of the pipeline (index = the
446
534
  * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
447
535
  instanceBuffers?: BufferId[]
536
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
537
+ instanceOrder?: InstanceOrder
448
538
  clearColor?: [number, number, number, number]
449
539
  render?: "auto" | "manual"
450
540
  loadOp?: "clear" | "load"
@@ -603,6 +693,90 @@ declare module "flux:gpu" {
603
693
  instanceBuffer?: BufferId
604
694
  instanceBuffers?: BufferId[]
605
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] }
606
780
 
607
781
  /**
608
782
  * Compile a GLSL ES vertex+fragment pipeline into an offscreen texture of
@@ -651,6 +825,8 @@ declare module "flux:gpu" {
651
825
  /** One buffer per instance slot (index = the attributes' `slot`);
652
826
  * pass this OR `instanceBuffer`, not both. */
653
827
  instanceBuffers?: BufferId[]
828
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
829
+ instanceOrder?: InstanceOrder
654
830
  topology?: Topology
655
831
  depth?: boolean
656
832
  depthWrite?: boolean
@@ -693,12 +869,18 @@ declare module "flux:gpu" {
693
869
  * closes and nothing is published. Always closes the lease, error or not;
694
870
  * throws when no write is open or `byteLength` exceeds the buffer size.
695
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.
696
875
  */
697
876
  export function endBufferWrite(id: BufferId, byteLength?: number): void
698
877
  /**
699
878
  * Overwrite part of a vertex buffer at `byteOffset` (default 0), within the
700
879
  * size it was created with. Pipelines drawing from the buffer re-render
701
- * 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}.
702
884
  */
703
885
  export function writeBuffer(id: BufferId, data: Uint8Array, byteOffset?: number): void
704
886
  /**
@@ -726,7 +908,7 @@ declare module "flux:gpu" {
726
908
  * buffer and extends the range into it, and a call that throws changes
727
909
  * nothing (range and buffers commit together or not at all).
728
910
  */
729
- export function setDraw(id: TextureId, draw: (DrawRange | IndexRange) & BufferUpdate): void
911
+ export function setDraw(id: TextureId, draw: (DrawRange | IndexRange) & BufferUpdate & OrderUpdate): void
730
912
  /**
731
913
  * Create a draw target: a render target whose contents are an ordered,
732
914
  * mutable LIST of draws - one render clears once, then executes every
@@ -767,6 +949,17 @@ declare module "flux:gpu" {
767
949
  * Returns a texture id (display, resize, destroy like any target; entries
768
950
  * die with it).
769
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
+ *
770
963
  * `into` makes a SUB-TARGET: a draw target that renders into the `width`
771
964
  * x `height` rectangle at `x`/`y` (top-left origin, the texture leaf's
772
965
  * `srcX`/`srcY` space; default 0) of draw target `into`'s storage instead
@@ -778,8 +971,8 @@ declare module "flux:gpu" {
778
971
  * cost one pass instead of N. The returned id is not a texture: sample,
779
972
  * display (`<d-texture src={parent} srcX srcY srcW srcH>`), read back and
780
973
  * copy the PARENT; `depthTexture(parent)` is the tile's depth too. Depth,
781
- * `samples`, `render` and `loadOp` are the parent's (passing them
782
- * 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
783
976
  * parent is clipped; {@link setTargetRect} moves it. Tiles do not nest.
784
977
  * Destroying the parent destroys its tiles.
785
978
  */
@@ -789,6 +982,7 @@ declare module "flux:gpu" {
789
982
  params?: ShaderParams | null,
790
983
  opts?: {
791
984
  depth?: boolean | "texture"
985
+ format?: "rgba8" | "rgba8-srgb" | "rgba16f"
792
986
  textures?: TextureBindings
793
987
  clearColor?: [number, number, number, number]
794
988
  render?: "auto" | "manual"
@@ -800,6 +994,46 @@ declare module "flux:gpu" {
800
994
  } & SamplerOptions &
801
995
  LabelOption,
802
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
803
1037
  /**
804
1038
  * The depth texture of a draw target created with `depth: "texture"`: a
805
1039
  * texture id of its own, stable for the target's life (a
@@ -812,9 +1046,15 @@ declare module "flux:gpu" {
812
1046
  * depth throws. SAMPLER-ONLY: it is not an upload texture and not a
813
1047
  * readback source (`readTexture`/`copyTexture` throw - render it through a
814
1048
  * pass to read it), its sampling is fixed at `nearest`/`clamp` (a depth
815
- * texture is only complete at nearest without a comparison mode - filter
816
- * in the shader, e.g. a PCF loop), and it dies with its target
817
- * (`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>`
818
1058
  * shows the depth in the red channel. Throws for a target without texture
819
1059
  * depth.
820
1060
  */
@@ -859,6 +1099,8 @@ declare module "flux:gpu" {
859
1099
  /** One buffer per instance slot of the pipeline (index = the
860
1100
  * attributes' `slot`); pass this OR `instanceBuffer`, not both. */
861
1101
  instanceBuffers?: BufferId[]
1102
+ /** Draw the instance records in key order (see {@link InstanceOrder}). */
1103
+ instanceOrder?: InstanceOrder
862
1104
  before?: DrawId
863
1105
  } & (DrawRange | (IndexBinding & IndexRange)),
864
1106
  ): DrawId
@@ -955,7 +1197,7 @@ declare module "flux:gpu" {
955
1197
  * single entry, same partial merge, bounds validation, and vocabulary
956
1198
  * rule (an indexed entry speaks {@link IndexRange}).
957
1199
  */
958
- export function setDrawRange(target: TextureId, draw: DrawId, update: DrawRange | IndexRange): void
1200
+ export function setDrawRange(target: TextureId, draw: DrawId, update: (DrawRange | IndexRange) & OrderUpdate): void
959
1201
  /**
960
1202
  * Swap one draw entry's buffers: the {@link BufferUpdate} half of
961
1203
  * {@link setDraw} addressed to a single entry, same replace-only rule and
@@ -971,7 +1213,9 @@ declare module "flux:gpu" {
971
1213
  * back-to-front, and re-issue the order when the camera moves. Entry
972
1214
  * state (params, textures, ranges) rides along untouched; ids are
973
1215
  * unaffected. Like every draw-list write it re-renders an auto target
974
- * 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}.)
975
1219
  */
976
1220
  export function setDrawOrder(target: TextureId, order: DrawId[]): void
977
1221
  /**
@@ -986,9 +1230,16 @@ declare module "flux:gpu" {
986
1230
  * the one counting. Ping-pong feedback is two manual targets sampling
987
1231
  * each other, stepped alternately from `onFrame`; binding a target to
988
1232
  * ITSELF still throws (same-pass GL feedback, undefined pixels regardless
989
- * 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).
990
1241
  */
991
- export function renderTarget(id: TextureId): void
1242
+ export function renderTarget(id: TextureId, face?: number, level?: number): void
992
1243
  /**
993
1244
  * Overwrite a `render: "manual"` target with another texture's current
994
1245
  * pixels, GPU-side: the seed/history analog of {@link uploadTexture}
@@ -1000,7 +1251,9 @@ declare module "flux:gpu" {
1000
1251
  * copy sees the copy, and targets sampling `dst` update afterwards.
1001
1252
  * Throws if either id is unknown, `dst` is not a manual target (the
1002
1253
  * runtime owns those contents), or `src === dst`. `src` may be any
1003
- * 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).
1004
1257
  */
1005
1258
  export function copyTexture(src: TextureId, dst: TextureId): void
1006
1259
  /**
@@ -1039,7 +1292,8 @@ declare module "flux:gpu" {
1039
1292
  * Read back a registered texture's current pixels as RGBA8 (tightly packed,
1040
1293
  * top-to-bottom rows), for any texture id whatever created it (createTexture,
1041
1294
  * createShaderTexture, a render target). Synchronous. Throws if the id is
1042
- * 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).
1043
1297
  */
1044
1298
  export function readTexture(id: TextureId): { width: number; height: number; data: Uint8Array }
1045
1299
  }
@@ -189,6 +189,15 @@ declare module "flux:rendertree" {
189
189
  * semantics), for comparing against pointer event coordinates.
190
190
  */
191
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
192
201
  /**
193
202
  * The texture id of a snapshot repaint boundary's retained rasterization
194
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
@@ -127,19 +148,100 @@ declare module "flux:spatial" {
127
148
  /** Free a shape; nodes still referencing it fall back to their box. */
128
149
  export function destroyShape(shape: ShapeId): void
129
150
  export function setShape(node: NodeId, shape: ShapeId | null): void
151
+ /** The node's layer membership bitmask (default 1), what a query's
152
+ * `layers` mask is tested against. Query-only: needs no flush. */
153
+ export function setLayers(node: NodeId, layers: number): void
154
+ /**
155
+ * The filter every query takes as its trailing argument, each field
156
+ * optional: `root` admits only the nodes under it (the caller's own
157
+ * subtree in the arena every scene and layer shares), `layers` only
158
+ * nodes whose mask intersects it (a node's mask of 0 then never
159
+ * reports), `nodes` only the listed ones. A dead root throws.
160
+ */
161
+ export type QueryFilter = {
162
+ root?: NodeId
163
+ layers?: number
164
+ nodes?: NodeId[]
165
+ }
130
166
  /** Every shown node with bounds the ray strikes, nearest first. The
131
167
  * direction need not be normalized; distances are world units. Reads
132
168
  * the index as of the last flush. */
133
- export function raycast(origin: Float32Array, direction: Float32Array): Hit[]
169
+ export function raycast(origin: Float32Array, direction: Float32Array, filter?: QueryFilter): Hit[]
170
+ /** A query volume's layout for overlap/sweep: "capsule" is
171
+ * 7 floats (a, b, radius; a sphere when a == b), "box" is 10 (center,
172
+ * half extents, unit quaternion xyzw). */
173
+ export type VolumeKind = "capsule" | "box"
174
+ /** One node overlap() touches: its deepest contact - the point on
175
+ * the node's surface, the unit direction out of it, and the depth along
176
+ * that direction that clears the contact. */
177
+ export type Overlap = {
178
+ node: NodeId
179
+ point: [number, number, number]
180
+ normal: [number, number, number]
181
+ depth: number
182
+ }
183
+ /** One node sweep() touches: `time` is the fraction of the motion
184
+ * at first touch, `point` the touch point on the node's surface,
185
+ * `normal` the unit normal there facing the volume. */
186
+ export type Impact = {
187
+ node: NodeId
188
+ time: number
189
+ point: [number, number, number]
190
+ normal: [number, number, number]
191
+ }
192
+ /**
193
+ * Every shown node with bounds the volume touches, each with its
194
+ * deepest contact. A node with a shape is tested per triangle in world
195
+ * space, so it holds under any transform; a node without one is its
196
+ * local box's twelve triangles (a flat rect is four of them, so a
197
+ * rotated sprite tests exactly - the 2d marquee's box query is a "box"
198
+ * volume). Surfaces, not solids: a volume wholly inside a closed mesh
199
+ * with no triangle in reach touches nothing. Unordered; reads the index
200
+ * as of the last flush, like raycast.
201
+ */
202
+ export function overlap(kind: VolumeKind, volume: Float32Array, filter?: QueryFilter): Overlap[]
203
+ /**
204
+ * The volume moved by `motion` (a Float32Array of 3): every shown node
205
+ * with bounds it touches on the way, at its first touch, earliest
206
+ * first. A node already in contact reports time 0 while the motion
207
+ * closes in, and nothing while it leaves or slides along the contact;
208
+ * a zero motion touches nothing. Same testing and index contract as
209
+ * overlap.
210
+ */
211
+ export function sweep(kind: VolumeKind, volume: Float32Array, motion: Float32Array, filter?: QueryFilter): Impact[]
212
+ /** The mover's tuning, every field optional over the core's defaults:
213
+ * `up` (the direction floors face, [0, 1, 0]), `floorMaxAngle` (radians
214
+ * from `up` a contact still counts as floor, 45 degrees), `maxSlides`
215
+ * (6), `skin` (the gap kept from every surface, 0.01) and `floorSnap`
216
+ * (how far below a floor is pulled to when the motion does not rise,
217
+ * 0.1; 0 disables). */
218
+ export type MoveOptions = {
219
+ up?: [number, number, number]
220
+ floorMaxAngle?: number
221
+ maxSlides?: number
222
+ skin?: number
223
+ floorSnap?: number
224
+ }
225
+ /** What moveAndSlide() returns: the displacement the body gets, the
226
+ * unit normal of the floor it ends on (null when airborne or on a slope
227
+ * too steep to stand on), whether a wall or a ceiling was met, and
228
+ * every impact in order, the floor snap's last. */
229
+ export type MoveResult = {
230
+ motion: [number, number, number]
231
+ floor: [number, number, number] | null
232
+ wall: boolean
233
+ ceiling: boolean
234
+ hits: Impact[]
235
+ }
134
236
  /**
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.
237
+ * Move the volume by `motion` through the admitted nodes, sliding along
238
+ * what it hits (Godot's move_and_slide, Unity's CharacterController.Move
239
+ * as one pure call): a push out of anything it starts inside, then
240
+ * sweep, stop a skin short, slide the rest along the contact, up to
241
+ * `maxSlides` times, then unless the motion rises a snap down onto a
242
+ * floor within `floorSnap`. Same index and filter contract as sweep.
141
243
  */
142
- export function overlap(bounds: Float32Array): NodeId[]
244
+ export function moveAndSlide(kind: VolumeKind, volume: Float32Array, motion: Float32Array, opts?: MoveOptions, filter?: QueryFilter): MoveResult
143
245
 
144
246
  /**
145
247
  * Route the world DIRECTION of the node's local `vector` (a
@@ -150,14 +252,46 @@ declare module "flux:spatial" {
150
252
  * zeros. Every sink naming the same param shares one array (`len` must
151
253
  * agree); what the slots mean - light directions, an emitter axis - is
152
254
  * 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.
255
+ * One slot sink per (target, name): rebinding the same param on the
256
+ * same target replaces that sink (the abandoned slot zeroes); another
257
+ * param or another target adds one, so a node may feed several arrays
258
+ * of one target (a spot light: its direction and its position).
155
259
  */
156
260
  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
261
+ /**
262
+ * Route the node's world POSITION into vec3 slot `index` of the
263
+ * `len`-float shared array param `name` on a draw target -
264
+ * bindDirectionSlot's translation sibling, with the same sharing,
265
+ * zeroing and per-(target, name) replacement rules. What a positional
266
+ * light's slot follows.
267
+ */
268
+ export function bindPositionSlot(node: NodeId, target: TextureId, name: string, len: number, index: number): void
269
+ /** Remove the node's slot sinks on `target`, or every slot sink without
158
270
  * one (the abandoned slots zero at the next flush). */
159
271
  export function unbindSlot(node: NodeId, target?: TextureId): void
160
272
 
273
+ /**
274
+ * Route the node's world matrix, post-multiplied by the constant `post`
275
+ * (a Float32Array of 16, column-major), into row `row` of a float
276
+ * texture: the flush writes the 16 floats as the row's four rgba32f
277
+ * texels - the matrix-palette channel a vertex shader `texelFetch`es
278
+ * (a skin binds each joint node with `post` its inverse bind). Writes
279
+ * batch: however many bound nodes moved, each flush uploads at most one
280
+ * whole palette per texture. With `anchor` - one node shared by every
281
+ * bind on the texture, and an ANCESTOR of every bound node - rows are
282
+ * anchor-local (`inverse(anchorWorld) * world * post`), so a model
283
+ * root's placement stays out of its own palette. Rows keep updating
284
+ * while nodes are hidden (visibility is the drawing entry's business),
285
+ * and an unbound or destroyed node's row keeps its last value. Validated
286
+ * at bind time: the texture must be an uploadable rgba32f texture 4
287
+ * texels wide with `row` inside it. Rebinding the same texture replaces
288
+ * the node's slot there; another texture adds one.
289
+ */
290
+ export function bindTextureSlot(node: NodeId, texture: TextureId, row: number, post: Float32Array, anchor?: NodeId): void
291
+ /** Remove the node's texture slot on `texture`, or every texture slot
292
+ * without one (abandoned rows keep their last value). */
293
+ export function unbindTextureSlot(node: NodeId, texture?: TextureId): void
294
+
161
295
  /**
162
296
  * Route the node's world pose to record slot `index` of vertex buffer
163
297
  * `buffer` used as an instance buffer: the flush writes the 5 floats
@@ -172,7 +306,23 @@ declare module "flux:spatial" {
172
306
  * Rebinding replaces the node's record sink; the abandoned slot zeroes.
173
307
  */
174
308
  export function bindPoseRecord(node: NodeId, buffer: BufferId, index: number): void
175
- /** Remove the node's record sink (its slot zeroes at the next flush). */
309
+ /**
310
+ * Route the node's world matrix to record slot `index` of vertex buffer
311
+ * `buffer` as 16 floats (one column-major mat4) at float offset
312
+ * index * 16: the per-instance model matrix a vertex stage reads as
313
+ * four vec4 attributes. With `anchor` (an ANCESTOR of the node, one per
314
+ * buffer - every bind on the buffer names the same one) the record is
315
+ * the matrix RELATIVE to it, `inverse(anchorWorld) * world`, so a mesh's
316
+ * instances stay in the mesh's own space and its uModel still places
317
+ * the whole population (`uModel * instanceMatrix`); without one it is
318
+ * the plain world matrix. Batching, growth (retargetRecords) and
319
+ * rebinding as bindPoseRecord; a hidden, unbound or destroyed node's
320
+ * slot becomes a zero-scale matrix (w stays 1), so the instance
321
+ * collapses to a point and draws nothing. Every sink on one buffer
322
+ * shares one projection.
323
+ */
324
+ export function bindMatrixRecord(node: NodeId, buffer: BufferId, index: number, anchor?: NodeId): void
325
+ /** Remove the node's record sink (its slot hides at the next flush). */
176
326
  export function unbindRecord(node: NodeId): void
177
327
  /**
178
328
  * Move every record sink on buffer `old` to buffer `new`, slot indices
@@ -185,4 +335,83 @@ declare module "flux:spatial" {
185
335
  * when `new` already carries record sinks.
186
336
  */
187
337
  export function retargetRecords(old: BufferId, next: BufferId): void
338
+
339
+ /** A registered clip's handle (createClip). */
340
+ export type ClipId = number
341
+ /** A playing clip instance's handle (createPlayer). */
342
+ export type PlayerId = number
343
+
344
+ /**
345
+ * Register a baked animation clip: `duration` in seconds, `meta` four
346
+ * words per channel - [targetSlot, path (0 position, 1 rotation,
347
+ * 2 scale), interpolation (0 step, 1 linear, 2 cubic), keyCount] - and
348
+ * `times`/`values` every channel's key arrays concatenated in meta
349
+ * order (3 floats per key, 4 for rotation; cubic stores three elements
350
+ * per key: in-tangent, value, out-tangent, tangents per second - the
351
+ * glTF CUBICSPLINE layout). A clip is shared data: `targetSlot`
352
+ * indexes the target table each PLAYER supplies, so one clip drives
353
+ * any number of instances, and retargeting is a different table.
354
+ */
355
+ export function createClip(duration: number, meta: Uint32Array, times: Float32Array, values: Float32Array): ClipId
356
+ /** Free a clip. Players still on it drop at their next advance (a
357
+ * "dropped" spatialClipEnd). */
358
+ export function destroyClip(clip: ClipId): void
359
+ /**
360
+ * Start playing `clip`: `targets[slot]` is the node each channel
361
+ * animates - every target must be a live scene node (throws
362
+ * otherwise). Players advance on the frame clock BEFORE each frame's
363
+ * JS, sample and weight-blend every active player per (node, path) -
364
+ * two players on one node crossfade - and write the blended TRS into
365
+ * the arena, so frame handlers read and may overwrite freshly posed
366
+ * nodes (last write wins) and the frame's flush publishes the result.
367
+ * `speed` scales clip time (1 = as authored); `loop` wraps, else the
368
+ * player holds its final pose and reports once; `weight`/`fade` start
369
+ * the crossfade state (weight 0..1, fade = weight change per second -
370
+ * positive fades in, negative out; past 0 the player is removed).
371
+ * When a player finishes or is removed without finishing (faded out,
372
+ * clip or target destroyed), the "spatialClipEnd" engine event
373
+ * (srt:events) fires with payload { player, reason: "finished" |
374
+ * "dropped" }, before the same frame's handlers.
375
+ */
376
+ export function createPlayer(clip: ClipId, targets: NodeId[], speed: number, loop: boolean, weight: number, fade: number): PlayerId
377
+ /** Write the given fields of a player - the O(changes) crossfade
378
+ * channel. Setting `time` (seconds) also re-arms a finished player's
379
+ * end report. Throws on an id that already dropped. */
380
+ export function setPlayer(player: PlayerId, update: { weight?: number; fade?: number; speed?: number; time?: number }): void
381
+ /**
382
+ * Bind root motion to a player: `channel` of `clip` (a position channel
383
+ * - normally the ROOT track of the authored clip, while the player
384
+ * plays an in-place variant that holds the root still) is sampled at
385
+ * the player's time each advance, and its travel since the previous
386
+ * advance (continuous across a loop wrap, weighted by the player's
387
+ * weight) is reported as one "spatialRootMotion" engine event
388
+ * (srt:events) per advance, payload { player, x, y, z, yaw }, before
389
+ * the frame's handlers. The translation is given in the root's CURRENT
390
+ * facing (the clip's own turn so far undone), i.e. the character's
391
+ * local frame. `rotation`, a rotation channel of the same clip (the
392
+ * root's), adds the yaw half: the twist about `up`, in radians (zero
393
+ * without it). With an `anchor` the translation, rotated by the
394
+ * anchor's own rotation, is added to the anchor's position and the yaw
395
+ * turns the anchor about `up` in its own frame - Unity's
396
+ * applyRootMotion; without one it is Godot's root_motion_track, the
397
+ * app's to apply. `opts.up` is the root's parent-space up axis (default
398
+ * [0, 1, 0]); `opts.vertical: false` drops the component along it from
399
+ * the delta (the height stays in the pose). Binding primes at the
400
+ * player's current time, so the next advance already delivers. A
401
+ * second call rebinds. Throws on a dropped player, a wrong channel
402
+ * kind, a zero up axis, or a dead anchor.
403
+ */
404
+ export function bindRootMotion(player: PlayerId, clip: ClipId, channel: number, rotation?: number, anchor?: NodeId, opts?: { up?: [number, number, number]; vertical?: boolean }): void
405
+ /** Remove a player at once, holding whatever pose it last wrote (no
406
+ * event; stop-with-fade is a setPlayer fade write instead). A dropped
407
+ * id is fine. */
408
+ export function destroyPlayer(player: PlayerId): void
409
+ /**
410
+ * Fill `out` (a Float32Array of 10) with the node's CURRENT local
411
+ * transform - position xyz, quaternion xyzw, scale xyz - as the arena
412
+ * holds it, players' writes included. The pose read for root-motion
413
+ * strips and skeleton copies: JS-side mirrors of a node animated by a
414
+ * player go stale, this does not.
415
+ */
416
+ export function readTransform(node: NodeId, out: Float32Array): void
188
417
  }
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.55",
3
+ "version": "0.0.57",
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