Skip to content

Changelog

What changed between runtime versions. Scripts pin to the version they were written against, so older experiences keep running unchanged. See the Migration Guide when you move an experience forward.

0.3.0 Beta

Lights, the two scene-wide looks that go with them, and GPU access to splat captures and mesh instances. Additive: nothing in 0.2.0 changed.

New APIs

  • Splatentity.representation.splat reaches a Gaussian splat capture's per-splat data: count, boundingBox, sphericalHarmonicDegree, and the five attributes. Each attribute vends .read and .write tokens for a compute kernel, so reveals, dissolves and interactive effects run on the GPU rather than being rebuilt in JavaScript. null on any other representation and wherever captures are unavailable.
  • InstanceDataentity.instances and rep.instances hand the mesh instancing transform buffer to a kernel, so thousands of copies can move per frame without rebuilding the transform list.
  • EngineBufferBinding – the token type both of those return, alongside the existing TextureBinding.
  • LightscreateSpotLight(...), createPointLight(...) and createDirectionalLight(...) build light objects the same way createBox builds a shape, so they take name, anchor and transform like anything else. Colour, intensity, cone angles, attenuation, shadows and a projected texture are set at creation. projectedTexture takes an asset id, a URL, or a handle from Texture, so a spot can project a video, the camera feed or a compute kernel's output as easily as a stored image.
  • Light – the live handle, entity.representation.light. Reading and writing intensity, color, innerAngle, outerAngle, attenuationRadius, falloffExponent, projectedTextureOffset, projectedTextureScale and projectedTextureRotation takes effect on the next frame, which makes a pulsing lamp or a drifting pattern one animateValue away. Writes are runtime state and do not change the saved experience.
  • scene.setBloom(...) – make bright pixels glow, scoped to the whole scene or to objects that opt in. null switches it off.
  • scene.setToneMapping(...) – set the scene's tone curve (exposure, toe, shoulder). null returns to the platform default.
  • .revealAnimation(...) / .removalAnimation(...) – choose how a created object animates in and out, using the same four styles as the editor's Reveal and Removal Animation pickers: 'default', 'disabled', 'fade', 'fadeAndScale'. .revealAnimation(false) is shorthand for no animation, which is what a script that runs its own reveal wants. Previously reachable only through the untyped t.set('modelAdjustments.revealAnimation', …).
  • instances.meshToLocal / instances.localToMesh – the conversion between the instance buffer's units and metres, as 16 numbers in column-major order for a kernel's uniforms. The buffer holds the instanced mesh's own units, which are rarely metres: a model scaled to a fitting box measured 407 buffer units to the metre, so a copy placed at 0.82 m sits in the buffer at 335. A kernel that adds a fixed offset has to convert, or it moves things by the wrong amount and usually looks frozen. Identity for a primitive.
  • .hideByDefault(...) – create an object hidden, so a script can prepare it before anyone sees it, then bring it up with scene.toggle([entity], true). Mirrors the editor's Hide By Default toggle. It still loads: the representation is built and the physics body precomputed, the object is simply not shown and its physics stays paused.

Feature flags

Six additions to environment.features, each with a support condition a script can actually hit: softShadows, shadowCascades, projectedLightTexture, surroundingsLight, bloom, toneMapping. Bloom wants a capable GPU as well as a recent OS, and the surroundings light exists on Apple Vision Pro alone. Gate on them and a portable script degrades instead of looking broken.

Changed

  • Value animations run in step with rendering. animateValue used to be driven by its own display refresh clock, separate from the one the scene renders on. That clock could be served at a fraction of the frame rate, giving a visibly coarse animation, or at twice it, computing values no frame ever painted. It now ticks once per rendered frame, so onUpdate and scene.on('render') see the same frames. An animation also starts at its from value rather than a frame or two along.

    Two consequences worth knowing. A value animation now pauses with the experience and resumes where it froze, instead of running on through a pause and jumping. And on Apple Vision Pro it pauses while the headset has no position, such as while an immersive space is opening or tracking is interrupted.

    wait() is unchanged and still runs on a timer, so it keeps counting through a pause. A wait and an animation completion started together can therefore finish in the other order if the experience is paused in between.

  • traits.pivot takes all six faces. Alongside "top", "center" and "bottom" it now accepts "left", "right", "front" and "back". "back" seats a model flat against an upright surface the way "bottom" stands one on a floor, which is what content placed on a wall needs: left on its own origin it straddles the surface, so half of anything solid ends up inside the wall. Existing values are unchanged.

  • rep.setInstances needs a single mesh. A primitive always is one; a model is once its Flatten adjustment is on, which loads it collapsed the way every other engine expects for instancing. An unflattened model with several meshes is now refused, with the console naming them, instead of quietly writing a separate buffer per mesh that no kernel could read back as one. Flattened models also place their copies correctly now: the transform the mesh file used to position its geometry was being discarded, which drew every copy at the raw size.

  • Kernel bindings keep the order you wrote them. Entries in inputBuffers, outputBuffers, inputTextures and outputTextures take kernel slots in the order they appear in the object. They previously came out in an unspecified order, which only ever looked right because no example bound more than one entry per map. One caveat is JavaScript's, not ours: keys that look like integers ({ 2: …, 0: … }) sort numerically ahead of everything else, so name your slots.

  • A binding that cannot be honoured now cancels the dispatch instead of skipping the entry. Skipping left its slot unclaimed, which slid every later buffer into the wrong one, so a single destroyed handle silently corrupted the whole pass. Binding past the GPU's 31-slot limit now says so as well.

Fixed

  • buffer.readSync() waits for the GPU. It read shared memory without waiting for the dispatch that was writing it, so the marching-cubes pattern of dispatching and then reading the triangle counter got the previous frame's value. buffer.writeSync(...) waits too, so reseeding a counter between passes cannot land under a running kernel.
  • A partial transform in a declarative options object (transform: { rotation: … }) no longer throws on the fields it omits.

0.2.0 Beta

A large, mostly additive release. Highlights:

New APIs

Changed

0.1.0

Initial scripting runtime.