Skip to content

@typegpu/kithistory

History

Compile shader functions with last frame's state to compute motion vectors and reprojection.

kit.history

module

history.create(root, schema, options?) → History

history.temporal(current, previous)

history.previously(callback)

rootborrows
the root
schematakes
the state, such as d.arrayOf(Transform, 256)
Historyreturns
a history you destroy
currenttakes
what the accessor holds in code compiled as is
previoustakes
the value supplied in code compiled with previously
callbacktakes
a 'use gpu' function that reads accessors temporal fills
accessors
epoch
types
History
Optionscreate · 2 fields
usage?

U= storage

Selects the buffer usage.

initial?

d.InferInput<T>= zeros

Sets the initial state for both the current and previous frame.

records
  • advanceframe

advance once a frame, before the passes and kernels that read or move the state.

owns

3 buffers

Current state, previous state and pending writes.

lifecycle

destroy()[Symbol.dispose]

latticeThin beams turning over a sky, embers rising through them, smoothed over frames with TAA.

The lattice stores its turn angle in history.create(root, d.f32, { usage: 'uniform' }). TAA’s motion output runs the vertex code again with previously to read last frame’s angle. Switch to a plain uniform to make both runs read the current angle. The motion view then shows only camera motion. TAA samples last frame’s colour at the wrong positions, and the beams dim as they turn. Raise the speed to see the effect.

Motion vectors and reprojection need last frame’s camera and object positions. Maintaining separate shader functions for current and previous state risks differences between them.

history.previously(fn) compiles fn a second time. In that copy, accessors filled with history.temporal(current, previous) read previous. history.create stores last frame’s buffer state for that fill. The kit’s cameras store their own previous views, and a table or a set of instances created with history: true keeps its own rows. Passes use previously to compute last frame’s vertex for outputs such as motion.

import {
import history
history
} from '@typegpu/kit';
const
const offsets: TgpuAccessor<d.WgslArray<d.Vec3f>>
offsets
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
accessor: <d.WgslArray<d.Vec3f>>(schema: d.WgslArray<d.Vec3f>, defaultValue?: DataAccessorIn<NoInfer<d.WgslArray<d.Vec3f>>> | undefined) => TgpuAccessor<d.WgslArray<d.Vec3f>> (+1 overload)
accessor
(
import d
d
.
arrayOf<d.Vec3f>(elementType: d.Vec3f, elementCount: number): d.WgslArray<d.Vec3f> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const vec3f: d.Vec3f
vec3f
, 64));
const
const placed: history.History<d.WgslArray<d.Vec3f>, "storage">
placed
=
import history
history
.
function create<d.WgslArray<d.Vec3f>, "storage">(root: TgpuRoot, schema: d.WgslArray<d.Vec3f>, options?: history.Options<d.WgslArray<d.Vec3f>, "storage">): history.History<d.WgslArray<d.Vec3f>, "storage">

Creates buffers for the current and previous state. write queues values until advance records copies into the frame encoder. This preserves the previous state, because CPU writes reach the queue before submitted commands.

@param ― schema the state, such as d.arrayOf(Transform, 256)

@returns ― a history you destroy

create
(
const root: TgpuRoot
root
,
import d
d
.
arrayOf<d.Vec3f>(elementType: d.Vec3f, elementCount: number): d.WgslArray<d.Vec3f> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const vec3f: d.Vec3f
vec3f
, 64));
const
const offsetOf: (i: number) => d.v3f
offsetOf
= (
i: number
i
: number) => {
'use gpu';
return
const offsets: TgpuAccessor<d.WgslArray<d.Vec3f>>
offsets
.
TgpuAccessor<WgslArray<Vec3f>>.$: d.v3f[]
$
[
i: number
i
]!;
};
const
const offsetBefore: TgpuGenericFn<(i: number) => d.v3f>
offsetBefore
=
import history
history
.
const previously: <(i: number) => d.v3f>(callback: (i: number) => d.v3f) => TgpuGenericFn<(i: number) => d.v3f>

Compiles callback to read previous values from accessors with temporal fills. Call it from any 'use gpu' function in any stage.

@param ― callback a 'use gpu' function that reads accessors temporal fills

@returns ― a function that reads them as they were last frame

previously
(
const offsetOf: (i: number) => d.v3f
offsetOf
); // last frame's
const
const moved: TgpuMutable<d.WgslArray<d.F32>>
moved
=
const root: TgpuRoot
root
.
TgpuRoot.createMutable<d.WgslArray<d.F32>>(typeSchema: d.WgslArray<d.F32>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.F32>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.F32>>> | undefined): TgpuMutable<d.WgslArray<d.F32>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Can be mutated in-place on the GPU. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createMutable
(
import d
d
.
arrayOf<d.F32>(elementType: d.F32, elementCount: number): d.WgslArray<d.F32> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
, 64));
const
const measure: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
measure
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
if (
gid: d.v3u
gid
.
v3u.x: number
x
>= 64) return;
const moved: TgpuMutable<d.WgslArray<d.F32>>
moved
.
TgpuMutable<WgslArray<F32>>.$: number[]
$
[
gid: d.v3u
gid
.
v3u.x: number
x
] =
import std
std
.
distance<d.v3f>(a: d.v3f, b: d.v3f): number (+1 overload)
export distance
distance
(
const offsetOf: (i: number) => d.v3f
offsetOf
(
gid: d.v3u
gid
.
v3u.x: number
x
),
const offsetBefore: TgpuGenericFn
(i: number) => d.v3f
offsetBefore
(
gid: d.v3u
gid
.
v3u.x: number
x
));
});
const
const measuring: TgpuComputePipeline
measuring
=
const root: TgpuRoot
root
.
Withable<WithBinding>.with<d.WgslArray<d.Vec3f>>(accessor: TgpuAccessor<d.WgslArray<d.Vec3f>>, value: DataAccessorIn<NoInfer<d.WgslArray<d.Vec3f>>>): WithBinding (+2 overloads)
with
(
const offsets: TgpuAccessor<d.WgslArray<d.Vec3f>>
offsets
,
const placed: history.History<d.WgslArray<d.Vec3f>, "storage">
placed
.
History<WgslArray<Vec3f>, "storage">.reading: TgpuMutable<d.WgslArray<d.Vec3f>> | TgpuReadonly<d.WgslArray<d.Vec3f>> | TgpuUniform<d.WgslArray<d.Vec3f>> | (d.v3f[] & TgpuMutable<d.WgslArray<d.Vec3f>>) | (d.v3f[] & TgpuReadonly<d.WgslArray<d.Vec3f>>) | (d.v3f[] & TgpuUniform<d.WgslArray<d.Vec3f>>) | (d.v3f[] & (() => d.v3f[] | MutableDataAccessorIn<d.WgslArray<d.Vec3f>>)) | (d.v3f[] & TgpuVar<VariableScope, d.WgslArray<...>>) | ... 11 more ... | (TgpuImmediateVar<...> & (() => d.v3f[] | MutableDataAccessorIn<d.WgslArray<d.Vec3f>>))

Fills an accessor with current, or previous in code compiled with previously. Storage binds read-only in render pipelines and read-write in compute pipelines. This fill also works with mutable accessors that kernels write through.

reading
).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const measure: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
measure
});
const placed: history.History<d.WgslArray<d.Vec3f>, "storage">
placed
.
History<WgslArray<Vec3f>, "storage">.write(value: d.InferInput<d.WgslArray<d.Vec3f>>): void

Queues a value for the next advance to apply.

write
(
const next: d.v3f[]
next
); // lands at the next advance
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const placed: history.History<d.WgslArray<d.Vec3f>, "storage">
placed
.
History<WgslArray<Vec3f>, "storage">.advance(frame: frames.Frame): void

Copies the current state to previous, then applies pending writes. Call once per frame before passes and kernels that read or update the state. A write without a following advance never becomes current, and a frame that does not submit leaves it for the next.

advance
(
frame: frames.Frame
frame
);
const
const pass: TgpuComputePass
pass
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const pass: TgpuComputePass
pass
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const measuring: TgpuComputePipeline
measuring
);
const pass: TgpuComputePass
pass
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(1);
const pass: TgpuComputePass
pass
.
TgpuComputePass.end(): void

Completes the recording of this compute pass

end
();
}),
);
  • history.epoch is a slot, 'current' or 'previous', and previously resolves its function with 'previous'. Call what it returns from any 'use gpu' function, in any stage.
  • history.temporal(current, previous) fills an accessor with any values it accepts. You can use two uniforms, two buffers, or a current constant and a previous uniform.
  • Code that never calls a previously function binds current only.

history.create(root, schema, options) owns three buffers, for current state, previous state and the next write.

  • reading fills a state accessor. The default storage usage binds read-only in render pipelines and read-write in compute pipelines. It can fill a mutable accessor for kernels that update the state. usage: 'uniform' suits small state written by the CPU.
  • write(value) holds a value for the next advance. A queue write lands before everything the frame submits, so a write straight into current would be copied aside as last frame’s too.
  • advance(frame) copies current to previous, then the held write into current. Call it once a frame, before what reads or moves the state. A write that no advance follows never shows. A frame that does not submit, such as one that throws, leaves the write for the next.
  • current and previous are the buffers, for readback and copies.
  • destroy(), or [Symbol.dispose], destroys the three buffers.

Choose write on click, then write a frame index t. Both versions copy current to previous, but direct writes arrive before the copy and erase motion; history preserves it for the written frame.

Store state that moves a drawing, such as the lattice’s turn angle, in a history. Read it through a vertex-code accessor filled with reading by the drawing. Outputs that compute last frame’s vertex then include the object’s motion:

import {
import history
history
} from '@typegpu/kit';
// How far the triangle has turned, now and last frame
const
const turn: TgpuAccessor<d.F32>
turn
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
accessor: <d.F32>(schema: d.F32, defaultValue?: DataAccessorIn<NoInfer<d.F32>> | undefined) => TgpuAccessor<d.F32> (+1 overload)
accessor
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
);
const
const turned: history.History<d.F32, "uniform">
turned
=
import history
history
.
function create<d.F32, "uniform">(root: TgpuRoot, schema: d.F32, options?: history.Options<d.F32, "uniform">): history.History<d.F32, "uniform">

Creates buffers for the current and previous state. write queues values until advance records copies into the frame encoder. This preserves the previous state, because CPU writes reach the queue before submitted commands.

@param ― schema the state, such as d.arrayOf(Transform, 256)

@returns ― a history you destroy

create
(
const root: TgpuRoot
root
,
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
, {
Options<F32, "uniform">.usage?: "uniform" | undefined

Selects the buffer usage. Use uniform for small CPU-written state, such as a clock, or storage for arrays that kernels can also update. Default storage.

usage
: 'uniform' });
const
const at: (vertexIndex: number, _instanceIndex: number) => {
position: d.v4f;
}
at
= (
vertexIndex: number
vertexIndex
: number,
_instanceIndex: number
_instanceIndex
: number) => {
'use gpu';
const
const angle: number
angle
=
const turn: TgpuAccessor<d.F32>
turn
.
TgpuAccessor<F32>.$: number
$
+
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
vertexIndex: number
vertexIndex
) * 2.1;
const
const world: d.v3f
world
=
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(
import std
std
.
function cos(value: number): number (+1 overload)
export cos
cos
(
const angle: number
angle
),
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
const angle: number
angle
), 0);
return
const Corner: d.WgslStruct
(props?: {
position: d.v4f;
} | undefined) => {
position: d.v4f;
}
Corner
({
position: d.v4f
position
:
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const world: d.v3f
world
, 1) });
};
const
const spinner: passes.Drawn
spinner
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
readonly position: d.v4f;
}>;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly motion: Output<unknown, { readonly position: d.v4f; }>; }>.add<d.WgslStruct<{
position: d.Vec4f;
}>, {
color: d.v4f;
}>(this: passes.Passes<passes.Fed<{
color: d.v4f;
}, {
position: d.v4f;
}>>, drawing: passes.Drawing<d.WgslStruct<{
position: d.Vec4f;
}>, {
color: d.v4f;
}>): passes.Drawn (+1 overload)

Builds a pipeline for drawing that writes every described output and returns its draw methods. If the drawing cannot supply the fields an output needs, type checking fails with that output's name. Use without to leave it out or instead to replace it.

add
({
Shaders<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.Vertex: d.WgslStruct<{
position: d.Vec4f;
}>

Defines a vertex with its position and data carried to pixels.

Vertex
:
const Corner: d.WgslStruct<{
position: d.Vec4f;
}>
Corner
,
Shaders<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.at: (vertexIndex: number, instanceIndex: number) => {
position: d.v4f;
}
at
,
Shaders<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.surfaceOf?: ((vertex: {
position: d.v4f;
}) => {
color: d.v4f;
}) | undefined

Computes data at a pixel from the interpolated vertex. position is the pixel position. Required for descriptions that write colour and for depthOf.

surfaceOf
,
Shaders<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.color?: passes.Output<{
color: d.v4f;
}, {
position: d.v4f;
}> | undefined

Supplies the output for the color target when the description has one.

color
:
import passes
passes
.
const shaded: passes.Output<{
readonly color: d.v4f;
}, unknown>

Writes the surface's color unchanged as a renderer's color output.

shaded
,
Drawing<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.inject?(): Inject

Fills shader accessors over the fills given to the description's beneath and under those piped into it, which take precedence.

inject
: () => (
cfg: Configurable
cfg
) =>
cfg: Configurable
cfg
.
Withable<Configurable>.with<d.F32>(accessor: TgpuAccessor<d.F32>, value: DataAccessorIn<NoInfer<d.F32>>): Configurable (+2 overloads)
with
(
const turn: TgpuAccessor<d.F32>
turn
,
const turned: history.History<d.F32, "uniform">
turned
.
History<F32, "uniform">.reading: DataAccessorIn<d.F32>

Fills an accessor with current, or previous in code compiled with previously. Storage binds read-only in render pipelines and read-write in compute pipelines. This fill also works with mutable accessors that kernels write through.

reading
),
Drawing<WgslStruct<{ position: Vec4f; }>, { color: v4f; }>.draw(pass: TgpuRenderCommands): void

Records a draw into the pass, on which the description has set the pipeline it built. That pipeline binds indices if supplied. drawIndexed throws without an index buffer. A group the drawing sets with pass.setBindGroup stays for later drawings in the pass that read its layout, so give such a layout to one drawing.

draw
: (
pass: TgpuRenderCommands
pass
) =>
pass: TgpuRenderCommands
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(3),
});
// Each frame, before the pass that draws it
const turned: history.History<d.F32, "uniform">
turned
.
History<F32, "uniform">.write(value: number): void

Queues a value for the next advance to apply.

write
(
const frame: Frame
frame
.
Frame.time: number

Reports seconds since the first submitted frame.

time
);
const turned: history.History<d.F32, "uniform">
turned
.
History<F32, "uniform">.advance(frame: Frame): void

Copies the current state to previous, then applies pending writes. Call once per frame before passes and kernels that read or update the state. A write without a following advance never becomes current, and a frame that does not submit leaves it for the next.

advance
(
const frame: Frame
frame
);

Filled with a plain uniform, last frame’s code reads this frame’s turn, and the motion output sees only the camera move.

  • History stores only one previous frame.
  • advance copies the whole state every frame, and again in a frame with a write.
  • A write that jumps the state, such as a teleport, reads as motion for a frame.