Appearance
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
- Splat –
entity.representation.splatreaches a Gaussian splat capture's per-splat data:count,boundingBox,sphericalHarmonicDegree, and the five attributes. Each attribute vends.readand.writetokens for a compute kernel, so reveals, dissolves and interactive effects run on the GPU rather than being rebuilt in JavaScript.nullon any other representation and wherever captures are unavailable. - InstanceData –
entity.instancesandrep.instanceshand 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.
- Lights –
createSpotLight(...),createPointLight(...)andcreateDirectionalLight(...)build light objects the same waycreateBoxbuilds a shape, so they takename,anchorandtransformlike anything else. Colour, intensity, cone angles, attenuation, shadows and a projected texture are set at creation.projectedTexturetakes 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 writingintensity,color,innerAngle,outerAngle,attenuationRadius,falloffExponent,projectedTextureOffset,projectedTextureScaleandprojectedTextureRotationtakes effect on the next frame, which makes a pulsing lamp or a drifting pattern oneanimateValueaway. 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.nullswitches it off.scene.setToneMapping(...)– set the scene's tone curve (exposure, toe, shoulder).nullreturns 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 untypedt.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 withscene.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.
animateValueused 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, soonUpdateandscene.on('render')see the same frames. An animation also starts at itsfromvalue 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. Awaitand an animation completion started together can therefore finish in the other order if the experience is paused in between.traits.pivottakes 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.setInstancesneeds 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,inputTexturesandoutputTexturestake 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
- Location – read the device location from a script via
environment.location(requestAccess,current,watch, precise-accuracy opt-in) with aGeoCoordinatetype carryingdistanceTo/bearingTohelpers. - Paths / splines –
scene.createPath({ points })builds a runtime spline; drive an object along it withentity.followPath(path, { duration, constantSpeed, alignsToTangent }). - Mesh instancing – render thousands of copies of one object with
rep.setInstances(Instances.transforms([...]))(rendering-only; far cheaper than cloning). - Compute kernels – run GPU compute from a script:
Kernel.fromSource/Kernel.fromAsset,Bufferhandles,Texture.compute, andmesh.runCompute(...). - Dynamic meshes – build and update geometry at runtime with
createMesh(...),writeVertices/writeIndices, and per-frame draw-range updates. - User interface – author panels from a script with
createPanel(...)and theUI.*builders (label, slider, stacks, …), live${var}label interpolation,findView, and property binding. (Beta.) - Materials – inline material factories:
Material.pbr(...),Material.unlit(...),Material.occlusion(...),Material.customShader(...),Material.materialX(...),Material.video(...), with web-standard property names and texture maps. - Screen gestures –
scene.on('pan' | 'pinch' | 'rotate')withvelocity(the raw input for mechanics like flick-to-launch),entity.on('doubleTap' | 'longPress'), per-object camera-claim arbitration, andenvironment.deviceCategory. iPhone / iPad / Mac only – gate withenvironment.features.has('screenGestures'). - Physics primitives –
entity.applyImpulse,applyAngularImpulse,setVelocity/setAngularVelocity, and the.physics({...})trait for giving an object a body. - Gestures trait – the typed
.gestures({...})API, includingreleaseBehavior(stay/reset/momentum) for drag-and-release mechanics. - Live value views – write through vector components directly, e.g.
entity.position.x = 2. - Tracking providers – opt into hand tracking and read articulated joint transforms without planting a hand-anchored element:
scene.tracking.hands(enable,left/right,anchors), plusscene.tracking.facefor blendshapes / eye transforms. Gate withenvironment.features.has('handTracking'). (Apple Vision Pro.) - Cross-platform surface input –
scene.createSurfaceInput({ target })yields interaction points on a surface in its own local frame, using fingertips on Apple Vision Pro and screen taps / drags elsewhere – the same points either way, so your logic never branches on platform. - Authored object descriptors – read and adjust an object you built in the editor before it's shown:
scene.getObjectDescriptor(nameOrId)→ edit with the trait methods /.asset(...)(swap a model or video source) /.representation(...)(swap the whole visual) →scene.setObjectDescriptor(...). Plusdescriptor.clone()(fresh-id copy for spawning variants) and read-onlydescriptor.id/descriptor.kind.
Changed
- Traits flattened onto the descriptor. The trait configurators are now direct chainable methods –
createSphere(0.05).physics({...}).material(...).shadow({...})– instead of the.traits(t => …)callback..traits(fn)still works but is deprecated; see the Migration Guide. - Options-object substrate for primitive factories, e.g.
createBox(w, h, d, r, { physics, material, … }).
0.1.0
Initial scripting runtime.