UNAVI Docs unavi.xyz
Build · Scripting API

wired:scene

Documents and the prims they hold: the scene a script reads and edits.

A document is a tree of prims. A prim is nothing but an id. Everything about it, from its place in the tree to its mesh, is a property. Each property is composed from layers, strongest first:

A script may write its own document, the documents it creates, and every document authored by the same user as its own. It may read any document it can open, and write only those.

Interface properties

Uses vec2, vec3, transform, color, document-id, prim-id.

The built-in properties a prim can carry.

topology

enum

How a mesh's vertices are assembled into primitives.

point-list
line-list
line-strip
triangle-list
triangle-strip

vertex-attribute

enum

A per-vertex stream of a mesh, and how many floats each vertex takes.

position3 floats, in the prim's own frame. Required for a mesh to draw.
normal3 floats, unit length.
tangent4 floats: a unit tangent and the bitangent sign.
color4 floats, linear RGBA.
uv02 floats.
uv12 floats.

vertex-stream

record
attribute: vertex-attribute
values: list<f32>Packed per vertex, every stream of a mesh holding the same number of vertices. At most 4 MiB.

alpha-mode

enum
opaque
maskDiscards fragments below alpha-cutoff.
blend
add
multiply
premultiplied

material

record

A physically based material. Every unset field takes the renderer's default. Textures are relations, set beside it.

base-color: option<color>
emissive: option<color>Added light, in linear RGB. Alpha is ignored.
metallic: option<f32>0 to 1.
roughness: option<f32>0 to 1.
alpha-mode: option<alpha-mode>
alpha-cutoff: option<f32>
double-sided: option<bool>

address-mode

enum

How a sampler treats coordinates outside 0 to 1.

repeat
mirror-repeat
clamp-to-edge

filter-mode

enum
linear
nearest

image-sampler

record

How an image is sampled. The encoded image itself is image-data.

address-mode-u: option<address-mode>
address-mode-v: option<address-mode>
address-mode-w: option<address-mode>
mag-filter: option<filter-mode>
min-filter: option<filter-mode>
mipmap-filter: option<filter-mode>
srgb: option<bool>Whether the image holds sRGB color, which is the default, rather than linear data such as a normal map.

collider-capsule

record

Along the prim's Y axis, centered on its origin.

height: f32Of the cylinder between the two hemispheres.
radius: f32

collider-cylinder

record

Along the prim's Y axis, centered on its origin.

height: f32
radius: f32

collider

variant

A collision shape in the prim's own frame.

capsule(collider-capsule)
convex-hullThe hull of collider-vertices.
cuboid(vec3)A box of these full extents, centered on the origin.
cylinder(collider-cylinder)
sphere(f32)A sphere of this radius.
trimeshThe triangles of collider-vertices and collider-indices.

rigid-body-kind

enum
dynamicMoved by the simulation.
kinematicMoved only by its transform, pushing dynamic bodies aside.
staticNever moves.

rigid-body

record

Makes the prim's collider part of the simulation. Unset fields take the simulation's defaults.

kind: rigid-body-kind
mass: option<f32>Kilograms.
friction: option<f32>
restitution: option<f32>0 for no bounce, 1 for a perfectly elastic one.
linear-damping: option<f32>
angular-damping: option<f32>
typelink-id = tuple<u64, u64>

128 bits shared by the two halves of a two-way portal.

portal-destination

record
space: document-idThe space the portal looks into.
link: option<link-id>Pairs this portal with the one in space bearing the same link. A destination without one is one-way.

portal

record

A rectangular window into another space, in the prim's XY plane, facing +Z.

width: f32
height: f32
destination: option<portal-destination>

spawn

record

Marks the prim as a spawn area: arriving users appear at a random point within radius of its origin, on its XZ plane.

radius: f32

text-align

enum
left
center
right

text-anchor

enum

Which line of the text block sits on the prim's origin.

baseline
top
middle
bottom

text-billboard

enum
none
yawTurns about Y to face the viewer, staying upright.
fullTurns fully to face the viewer.

text

record

A string drawn in the prim's XY plane, reading along +X. One style for the whole string. There is no markup.

value: stringAt most 4 KiB.
size: option<f32>Em height in meters.
align: option<text-align>
anchor: option<text-anchor>
wrap: option<f32>Wrap width in meters. Unset breaks only on newlines.
line-height: option<f32>A multiple of the font's line spacing.
color: option<color>
outline: option<color>Drawn behind the glyphs.
outline-width: option<f32>In ems.
emissive: option<f32>Glow strength, 0 for none.
billboard: option<text-billboard>

relation

variant

A named link from one prim to another in the same document.

shader-bindingThe shader graph this prim draws with, authored on another prim.
base-color-texture
emissive-texture
metallic-roughness-texture
normal-texture
occlusion-texture
shader-texture(u8)The texture a shader graph samples at this slot, 0 to 3.
custom(string)An application-defined relation, named group/field. The group must not be one the host defines.

property

variant

A property and its value. Each case names one key.

parent(option<prim-id>)The prim's parent, none for a root of the document.
name(string)At most 1 KiB.
transform(transform)Relative to the parent, or to the document for a root.
reference(document-id)Places another document under this prim, as it currently stands.
mesh-topology(topology)
mesh-vertices(vertex-stream)
mesh-indices(list<u32>)Indices into the vertex streams. At most 4 MiB.
material(material)
image-sampler(image-sampler)
image-data(list<u8>)An encoded PNG, JPEG or KTX2 image. At most 16 MiB.
text(text)
collider(collider)
collider-vertices(list<f32>)Packed XYZ positions. At most 4 MiB.
collider-indices(list<u32>)Triangle indices into collider-vertices. At most 4 MiB.
rigid-body(rigid-body)
gravity-scale(f32)A multiple of the world's gravity. Defaults to 1.
portal(portal)
spawn(spawn)
relation(tuple<relation, prim-id>)
custom(tuple<string, list<u8>>)Application data under group/field, outside the groups the host defines. At most 64 KiB. The host stores and replicates the bytes but never reads them.

property-key

variant

Names a property without its value.

parent
name
transform
reference
mesh-topology
mesh-vertices(vertex-attribute)
mesh-indices
material
image-sampler
image-data
text
collider
collider-vertices
collider-indices
rigid-body
gravity-scale
portal
spawn
relation(relation)
custom(string)

Interface document

Uses transform, document-id, prim-id, prim-ref, error, property, property-key.

layer

enum

Where a write lands. Neither layer is durable.

localThis peer only, gone when the document unloads. Every peer runs the same scripts, so a deterministic local write is seen everywhere without being sent anywhere. Reach for shared when the value depends on one peer, such as user input.
sharedEvery peer present in the space, live, until this peer leaves. Each peer's shared writes form its own layer on the document, and each viewer decides what that layer contributes. The author's layer contributes every property and is the strongest. Any other peer's contributes only properties in the state group, and a portal pairing when the viewer trusts that peer to open portals. Its other writes are kept but have no effect. Relations cannot be shared.

edit

variant
set(tuple<prim-id, property>)Sets a property. Setting parent moves the prim. The prim must exist, and the new parent must not lie beneath it.
clear(tuple<prim-id, property-key>)Drops this layer's value for a key, so weaker layers show through. A shared value stays stated for the session, so clearing one states that the key has no value, hiding weaker layers too. parent cannot be cleared. Use remove.
remove(prim-id)Takes a prim and everything beneath it out of the scene.

anchor

variant

What a placed document's root hangs from.

spaceThe root of the space.
prim(prim-ref)A prim of any loaded document.

document

resource

A document in the world. Holding one is the capability to read it, and to write it when the script may write it. Writes to a document the script may not write fail with forbidden.

id

methodid: func() -> document-id

may-write

methodmay-write: func() -> bool

Whether the script may write this document: its own, one it created, or one authored by the same user as its own.

contains

methodcontains: func(prim: prim-id) -> bool

Whether prim is in the scene.

roots

methodroots: func() -> list<prim-id>

Prims with no parent, in id order.

prims

methodprims: func() -> list<prim-id>

Every prim in the scene, in no particular order.

children

methodchildren: func(prim: prim-id) -> list<prim-id>

In id order. Empty for a prim not in the scene.

find-by-name

methodfind-by-name: func(name: string) -> list<prim-id>

Every prim whose name is exactly name.

get

methodget: func(prim: prim-id, key: property-key) -> option<property>

The composed value of a property: the strongest layer's. For transform that is the stated value, not where physics has since moved the prim. See world-transform.

get-many

methodget-many: func(keys: list<tuple<prim-id, property-key>>) -> list<option<property>>

get for many keys in one call, answered in order.

keys

methodkeys: func(prim: prim-id) -> list<property-key>

Every key that has a value on prim.

world-transform

methodworld-transform: func(prim: prim-id) -> result<transform, error>

Where prim is in the world this frame, including what physics and animation did to it since its transform was written.

offset-to

methodoffset-to: func(other: borrow<document>) -> option<transform>

The transform from this document's root to other's, or none when either is not placed.

create-prim

methodcreate-prim: func(layer: layer, parent: option<prim-id>) -> result<prim-id, error>

Adds a prim under parent, or as a root, and answers its id.

apply

methodapply: func(layer: layer, edits: list<edit>) -> result<_, error>

Applies every edit in layer together, or none of them, so peers never see half a batch. At most 4096 edits. In local, an edit naming a prim the document does not hold fails the batch with not-found.

commit

methodcommit: func(keys: list<tuple<prim-id, property-key>>) -> result<_, error>

Makes the live value of each key durable, in the document itself when this peer can write it, or as an override in the document referencing it. Fails with forbidden when it can write neither.

Takes effect only on the device that authors the script's own document. Elsewhere it succeeds and does nothing, so a script running on every peer commits once. A script outside any space runs on one peer, which always commits.

place

methodplace: func(anchor: anchor, offset: transform) -> result<_, error>

Puts the document into the scene, or moves it, at offset from anchor. Per peer, never stored.

A created document is held out of the scene until it is placed, so it can be built in full and then appear at once.

script-document

funcscript-document: func() -> result<document, error>

The document the calling script belongs to.

script-prim

funcscript-prim: func() -> prim-id

The prim the calling script is attached to, in its own document.

open-document

funcopen-document: func(id: document-id) -> result<option<document>, error>

A document loaded on this peer, or none when it is not.

create-document

funccreate-document: func() -> result<document, error>

Mints an empty document the script may write, held until it is placed.

copy-document

funccopy-document: func(source: borrow<document>) -> result<document, error>

Mints a document holding what source durably holds, which the two then diverge from. Live layers are not copied.

delete-document

funcdelete-document: func(document: document) -> result<_, error>

Removes a document this script created from the world, and deletes this peer's copy of it.

Source

/// Documents and the prims they hold: the scene a script reads and edits.
///
/// A document is a tree of prims. A prim is nothing but an id. Everything
/// about it, from its place in the tree to its mesh, is a property. Each
/// property is composed from layers, strongest first:
///
/// - `shared`: what each peer present in the space states, one layer per
///   peer, the author's strongest.
/// - `local`: what this peer states, for itself only.
/// - the document's durable value, which `commit` writes.
///
/// A script may write its own document, the documents it creates, and every
/// document authored by the same user as its own. It may read any document
/// it can open, and write only those.
package wired:scene;

/// The built-in properties a prim can carry.
interface properties {
  use wired:core/math.{vec2, vec3, transform, color};
  use wired:core/ids.{document-id, prim-id};

  /// How a mesh's vertices are assembled into primitives.
  enum topology {
    point-list,
    line-list,
    line-strip,
    triangle-list,
    triangle-strip,
  }

  /// A per-vertex stream of a mesh, and how many floats each vertex takes.
  enum vertex-attribute {
    /// 3 floats, in the prim's own frame. Required for a mesh to draw.
    position,
    /// 3 floats, unit length.
    normal,
    /// 4 floats: a unit tangent and the bitangent sign.
    tangent,
    /// 4 floats, linear RGBA.
    color,
    /// 2 floats.
    uv0,
    /// 2 floats.
    uv1,
  }

  record vertex-stream {
    attribute: vertex-attribute,
    /// Packed per vertex, every stream of a mesh holding the same number of
    /// vertices. At most 4 MiB.
    values:    list<f32>,
  }

  enum alpha-mode {
    opaque,
    /// Discards fragments below `alpha-cutoff`.
    mask,
    blend,
    add,
    multiply,
    premultiplied,
  }

  /// A physically based material. Every unset field takes the renderer's
  /// default. Textures are `relation`s, set beside it.
  record material {
    base-color:   option<color>,
    /// Added light, in linear RGB. Alpha is ignored.
    emissive:     option<color>,
    /// 0 to 1.
    metallic:     option<f32>,
    /// 0 to 1.
    roughness:    option<f32>,
    alpha-mode:   option<alpha-mode>,
    alpha-cutoff: option<f32>,
    double-sided: option<bool>,
  }

  /// How a sampler treats coordinates outside 0 to 1.
  enum address-mode {
    repeat,
    mirror-repeat,
    clamp-to-edge,
  }

  enum filter-mode {
    linear,
    nearest,
  }

  /// How an image is sampled. The encoded image itself is `image-data`.
  record image-sampler {
    address-mode-u: option<address-mode>,
    address-mode-v: option<address-mode>,
    address-mode-w: option<address-mode>,
    mag-filter:     option<filter-mode>,
    min-filter:     option<filter-mode>,
    mipmap-filter:  option<filter-mode>,
    /// Whether the image holds sRGB color, which is the default, rather
    /// than linear data such as a normal map.
    srgb:           option<bool>,
  }

  /// Along the prim's Y axis, centered on its origin.
  record collider-capsule {
    /// Of the cylinder between the two hemispheres.
    height: f32,
    radius: f32,
  }

  /// Along the prim's Y axis, centered on its origin.
  record collider-cylinder {
    height: f32,
    radius: f32,
  }

  /// A collision shape in the prim's own frame.
  variant collider {
    capsule(collider-capsule),
    /// The hull of `collider-vertices`.
    convex-hull,
    /// A box of these full extents, centered on the origin.
    cuboid(vec3),
    cylinder(collider-cylinder),
    /// A sphere of this radius.
    sphere(f32),
    /// The triangles of `collider-vertices` and `collider-indices`.
    trimesh,
  }

  enum rigid-body-kind {
    /// Moved by the simulation.
    dynamic,
    /// Moved only by its transform, pushing dynamic bodies aside.
    kinematic,
    /// Never moves.
    %static,
  }

  /// Makes the prim's collider part of the simulation. Unset fields take
  /// the simulation's defaults.
  record rigid-body {
    kind:            rigid-body-kind,
    /// Kilograms.
    mass:            option<f32>,
    friction:        option<f32>,
    /// 0 for no bounce, 1 for a perfectly elastic one.
    restitution:     option<f32>,
    linear-damping:  option<f32>,
    angular-damping: option<f32>,
  }

  /// 128 bits shared by the two halves of a two-way portal.
  type link-id = tuple<u64, u64>;

  record portal-destination {
    /// The space the portal looks into.
    space: document-id,
    /// Pairs this portal with the one in `space` bearing the same link. A
    /// destination without one is one-way.
    link:  option<link-id>,
  }

  /// A rectangular window into another space, in the prim's XY plane,
  /// facing +Z.
  record portal {
    width:       f32,
    height:      f32,
    destination: option<portal-destination>,
  }

  /// Marks the prim as a spawn area: arriving users appear at a random
  /// point within `radius` of its origin, on its XZ plane.
  record spawn {
    radius: f32,
  }

  enum text-align {
    left,
    center,
    right,
  }

  /// Which line of the text block sits on the prim's origin.
  enum text-anchor {
    baseline,
    top,
    middle,
    bottom,
  }

  enum text-billboard {
    none,
    /// Turns about Y to face the viewer, staying upright.
    yaw,
    /// Turns fully to face the viewer.
    full,
  }

  /// A string drawn in the prim's XY plane, reading along +X. One style for
  /// the whole string. There is no markup.
  record text {
    /// At most 4 KiB.
    value:         string,
    /// Em height in meters.
    size:          option<f32>,
    align:         option<text-align>,
    anchor:        option<text-anchor>,
    /// Wrap width in meters. Unset breaks only on newlines.
    wrap:          option<f32>,
    /// A multiple of the font's line spacing.
    line-height:   option<f32>,
    color:         option<color>,
    /// Drawn behind the glyphs.
    outline:       option<color>,
    /// In ems.
    outline-width: option<f32>,
    /// Glow strength, 0 for none.
    emissive:      option<f32>,
    billboard:     option<text-billboard>,
  }

  /// A named link from one prim to another in the same document.
  variant relation {
    /// The shader graph this prim draws with, authored on another prim.
    shader-binding,
    base-color-texture,
    emissive-texture,
    metallic-roughness-texture,
    normal-texture,
    occlusion-texture,
    /// The texture a shader graph samples at this slot, 0 to 3.
    shader-texture(u8),
    /// An application-defined relation, named `group/field`. The group must
    /// not be one the host defines.
    custom(string),
  }

  /// A property and its value. Each case names one key.
  variant property {
    /// The prim's parent, `none` for a root of the document.
    parent(option<prim-id>),
    /// At most 1 KiB.
    name(string),
    /// Relative to the parent, or to the document for a root.
    transform(transform),
    /// Places another document under this prim, as it currently stands.
    reference(document-id),
    mesh-topology(topology),
    mesh-vertices(vertex-stream),
    /// Indices into the vertex streams. At most 4 MiB.
    mesh-indices(list<u32>),
    material(material),
    image-sampler(image-sampler),
    /// An encoded PNG, JPEG or KTX2 image. At most 16 MiB.
    image-data(list<u8>),
    text(text),
    collider(collider),
    /// Packed XYZ positions. At most 4 MiB.
    collider-vertices(list<f32>),
    /// Triangle indices into `collider-vertices`. At most 4 MiB.
    collider-indices(list<u32>),
    rigid-body(rigid-body),
    /// A multiple of the world's gravity. Defaults to 1.
    gravity-scale(f32),
    portal(portal),
    spawn(spawn),
    relation(tuple<relation, prim-id>),
    /// Application data under `group/field`, outside the groups the host
    /// defines. At most 64 KiB. The host stores and replicates the bytes but
    /// never reads them.
    custom(tuple<string, list<u8>>),
  }

  /// Names a property without its value.
  variant property-key {
    parent,
    name,
    transform,
    reference,
    mesh-topology,
    mesh-vertices(vertex-attribute),
    mesh-indices,
    material,
    image-sampler,
    image-data,
    text,
    collider,
    collider-vertices,
    collider-indices,
    rigid-body,
    gravity-scale,
    portal,
    spawn,
    relation(relation),
    custom(string),
  }
}

interface document {
  use wired:core/math.{transform};
  use wired:core/ids.{document-id, prim-id, prim-ref};
  use wired:core/error.{error};
  use properties.{property, property-key};

  /// Where a write lands. Neither layer is durable.
  enum layer {
    /// This peer only, gone when the document unloads.
    ///
    /// Every peer runs the same scripts, so a deterministic local write is
    /// seen everywhere without being sent anywhere. Reach for `shared` when
    /// the value depends on one peer, such as user input.
    local,
    /// Every peer present in the space, live, until this peer leaves.
    ///
    /// Each peer's shared writes form its own layer on the document, and each
    /// viewer decides what that layer contributes. The author's layer
    /// contributes every property and is the strongest. Any other peer's
    /// contributes only properties in the `state` group, and a portal pairing
    /// when the viewer trusts that peer to open portals. Its other writes are
    /// kept but have no effect. Relations cannot be shared.
    shared,
  }

  variant edit {
    /// Sets a property. Setting `parent` moves the prim. The prim must exist,
    /// and the new parent must not lie beneath it.
    set(tuple<prim-id, property>),
    /// Drops this layer's value for a key, so weaker layers show through.
    /// A `shared` value stays stated for the session, so clearing one states
    /// that the key has no value, hiding weaker layers too. `parent` cannot be
    /// cleared. Use `remove`.
    clear(tuple<prim-id, property-key>),
    /// Takes a prim and everything beneath it out of the scene.
    remove(prim-id),
  }

  /// What a placed document's root hangs from.
  variant anchor {
    /// The root of the space.
    space,
    /// A prim of any loaded document.
    prim(prim-ref),
  }

  /// A document in the world. Holding one is the capability to read it, and
  /// to write it when the script may write it. Writes to a document the
  /// script may not write fail with `forbidden`.
  resource document {
    id: func() -> document-id;

    /// Whether the script may write this document: its own, one it created,
    /// or one authored by the same user as its own.
    may-write: func() -> bool;

    /// Whether `prim` is in the scene.
    contains: func(prim: prim-id) -> bool;
    /// Prims with no parent, in id order.
    roots:    func() -> list<prim-id>;
    /// Every prim in the scene, in no particular order.
    prims:    func() -> list<prim-id>;
    /// In id order. Empty for a prim not in the scene.
    children: func(prim: prim-id) -> list<prim-id>;
    /// Every prim whose `name` is exactly `name`.
    find-by-name: func(name: string) -> list<prim-id>;

    /// The composed value of a property: the strongest layer's. For
    /// `transform` that is the stated value, not where physics has since
    /// moved the prim. See `world-transform`.
    get:      func(prim: prim-id, key: property-key) -> option<property>;
    /// `get` for many keys in one call, answered in order.
    get-many: func(keys: list<tuple<prim-id, property-key>>) -> list<option<property>>;
    /// Every key that has a value on `prim`.
    keys:     func(prim: prim-id) -> list<property-key>;

    /// Where `prim` is in the world this frame, including what physics and
    /// animation did to it since its `transform` was written.
    world-transform: func(prim: prim-id) -> result<transform, error>;
    /// The transform from this document's root to `other`'s, or `none` when
    /// either is not placed.
    offset-to: func(other: borrow<document>) -> option<transform>;

    /// Adds a prim under `parent`, or as a root, and answers its id.
    create-prim: func(layer: layer, parent: option<prim-id>) -> result<prim-id, error>;

    /// Applies every edit in `layer` together, or none of them, so peers
    /// never see half a batch. At most 4096 edits. In `local`, an edit naming
    /// a prim the document does not hold fails the batch with `not-found`.
    apply: func(layer: layer, edits: list<edit>) -> result<_, error>;

    /// Makes the live value of each key durable, in the document itself
    /// when this peer can write it, or as an override in the document
    /// referencing it. Fails with `forbidden` when it can write neither.
    ///
    /// Takes effect only on the device that authors the script's own
    /// document. Elsewhere it succeeds and does nothing, so a script running
    /// on every peer commits once. A script outside any space runs on one
    /// peer, which always commits.
    commit: func(keys: list<tuple<prim-id, property-key>>) -> result<_, error>;

    /// Puts the document into the scene, or moves it, at `offset` from
    /// `anchor`. Per peer, never stored.
    ///
    /// A created document is held out of the scene until it is placed, so it
    /// can be built in full and then appear at once.
    place: func(anchor: anchor, offset: transform) -> result<_, error>;
  }

  /// The document the calling script belongs to.
  script-document: func() -> result<document, error>;
  /// The prim the calling script is attached to, in its own document.
  script-prim: func() -> prim-id;

  /// A document loaded on this peer, or `none` when it is not.
  open-document: func(id: document-id) -> result<option<document>, error>;
  /// Mints an empty document the script may write, held until it is placed.
  create-document: func() -> result<document, error>;
  /// Mints a document holding what `source` durably holds, which the two
  /// then diverge from. Live layers are not copied.
  copy-document: func(source: borrow<document>) -> result<document, error>;
  /// Removes a document this script created from the world, and deletes
  /// this peer's copy of it.
  delete-document: func(document: document) -> result<_, error>;
}