Skip to content

Slime mould

Grow a slime mould of 400 000 agents from an empty canvas into glowing threads that find food, with no shader or TypeGPU experience.

Build the slime mould example with 400 000 agents on a dish of agar. Each agent represents a speck of Physarum slime mould. It senses the trail ahead, turns toward the strongest trail, steps forward and leaves more trail. Each agent reads only its surroundings. Together they grow glowing threads that connect the food on the dish.

The GPU repeats the same small job for thousands of agents at once. You write that job in TypeScript, and TypeGPU turns it into a shader, a program the GPU runs. You need a browser with WebGPU. You need no experience with shaders or TypeGPU.

  1. Pour the agar.

    Start with two files. index.ts gives the scene a canvas and runs its frames. scene.ts builds what you draw and records each frame.

    index.ts
    import * as
    import web
    web
    from '@typegpu/kit/web';
    import {
    function createScene(root: TgpuRoot, surface: Surface, options?: SceneOptions): Promise<{
    settings: Settings<{
    count: number;
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    speed: number;
    deposit: number;
    doubling: number;
    endurance: number;
    decay: number;
    blur: number;
    food: number;
    light: number;
    pulse: number;
    glow: number;
    paused: boolean;
    }>;
    pointing: Pointing;
    frame(frame: Frame): void;
    destroy: () => void;
    }>

    A Physarum slime mould whose agents follow and lay a trail, fed by oats a tap drops.

    createScene
    } from './scene.ts';
    import web
    web
    .
    function host<{
    settings: Settings<{
    count: number;
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    speed: number;
    deposit: number;
    doubling: number;
    endurance: number;
    decay: number;
    blur: number;
    food: number;
    light: number;
    pulse: number;
    glow: number;
    paused: boolean;
    }>;
    pointing: Pointing;
    frame(frame: Frame): void;
    destroy: () => void;
    }>(createScene: web.CreateScene<Record<never, never>, {
    settings: Settings<{
    count: number;
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    speed: number;
    deposit: number;
    doubling: number;
    endurance: number;
    decay: number;
    blur: number;
    food: number;
    light: number;
    pulse: number;
    glow: number;
    paused: boolean;
    }>;
    pointing: Pointing;
    frame(frame: Frame): void;
    destroy: () => void;
    }>, hosting: web.HostOptions<...>): web.Hosted<...> (+1 overload)

    Runs a scene on a canvas with a root, surface, frame loop and animation. destroy tears them down in order. Use send to control the scene or onFrame to read it. If scene options are optional, call host(createScene, { canvas }). Throws if both device and root are supplied.

    @param ― createScene the scene file's createScene

    @param ― options the scene's third argument

    @param ― hosting the canvas, the device or root, and what the page runs beside the scene

    @returns ― a hosted scene you send closures to and destroy

    host
    (
    function createScene(root: TgpuRoot, surface: Surface, options?: SceneOptions): Promise<{
    settings: Settings<{
    count: number;
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    speed: number;
    deposit: number;
    doubling: number;
    endurance: number;
    decay: number;
    blur: number;
    food: number;
    light: number;
    pulse: number;
    glow: number;
    paused: boolean;
    }>;
    pointing: Pointing;
    frame(frame: Frame): void;
    destroy: () => void;
    }>

    A Physarum slime mould whose agents follow and lay a trail, fed by oats a tap drops.

    createScene
    , {
    HostOptions<S extends Scene>.canvas: HTMLCanvasElement

    Supplies the canvas for the scene.

    canvas
    :
    var document: Document

    window.document returns a reference to the document contained in the window.

    MDN Reference

    document
    .
    ParentNode.querySelector<"canvas">(selectors: "canvas"): HTMLCanvasElement | null (+4 overloads)

    Returns the first element that is a descendant of node that matches selectors.

    MDN Reference

    querySelector
    ('canvas')! });

    web.host creates a root, a surface, your scene and a frame loop. The root is TypeGPU’s handle on the GPU. You use it to create GPU resources. The surface sizes the canvas to its place on the page. The loop calls the scene’s frame at every animation frame.

    frame records GPU commands into an encoder, a list of commands to run later. When frame returns, the loop submits that list. The GPU runs it while the CPU moves on. Commands come in passes. A render pass draws into an image, and a compute pass runs calculations. The figure shows a larger frame, with its calls on the left and the commands the GPU receives on the right.

    Make the frame throw to see writes arrive while recorded commands and callbacks are dropped. Calls appear on the left and GPU execution on the right, with queue writes before the command buffer.

    Draw the agar first. A draw runs two functions. The vertex function places the corners of triangles on the screen. The fragment function colours each pixel they cover.

    Cover the canvas with one triangle to colour every pixel. common.fullScreenTriangle places its three corners so the triangle spans twice the canvas width and height. The GPU clips the part outside the canvas. It runs the fragment function for each pixel inside and gives it the pixel position.

    Point at a pixel to see its position and where sheetAt maps it on the dish. Three vertices cover the canvas; the GPU clips the outside and runs fragments for the covered pixels in parallel, without a fixed order.
    agar.ts
    /** Picture size in pixels and elapsed growth time in seconds. */
    export const
    const World: d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>

    Picture size in pixels and elapsed growth time in seconds.

    World
    =
    import d
    d
    .
    struct<{
    size: d.Vec2f;
    time: d.F32;
    }>(props: {
    size: d.Vec2f;
    time: d.F32;
    }): d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>
    export struct

    Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

    @example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

    @param ― props Record with string keys and TgpuData values, each entry describing one struct member.

    struct
    ({
    size: d.Vec2f
    size
    :
    import d
    d
    .
    const vec2f: d.Vec2f
    vec2f
    ,
    time: d.F32
    time
    :
    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
    });
    /** Converts an sRGB hex colour, such as 0xc0301c, to linear light. */
    export const
    const hex: (rgb: number) => d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    = (
    rgb: number
    rgb
    : number) =>
    function srgbToLinear(rgb: d.v3f): d.v3f
    srgbToLinear
    (
    function hexToRgb(hex: string | number): d.v3f
    hexToRgb
    (
    rgb: number
    rgb
    ));
    const
    const AGAR: d.v3f
    AGAR
    =
    const hex: (rgb: number) => d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0x080b10);
    const
    const DEEP: d.v3f
    DEEP
    =
    const hex: (rgb: number) => d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0x030406);
    /**
    * Sheet coordinates have (0, 0) at the picture centre, with y pointing down. One unit is half
    * the picture's longer side. The mould grid covers -1 to 1 on both axes, so on a wide picture its
    * top and bottom run past the edges.
    */
    export const
    const sheetAt: (pixel: d.v2f, size: d.v2f) => d.v2f

    Sheet coordinates have (0, 0) at the picture centre, with y pointing down. One unit is half the picture's longer side. The mould grid covers -1 to 1 on both axes, so on a wide picture its top and bottom run past the edges.

    sheetAt
    = (
    pixel: d.v2f
    pixel
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ,
    size: d.v2f
    size
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return ((
    pixel: d.v2f
    pixel
    -
    size: d.v2f
    size
    * 0.5) * 2) /
    import std
    std
    .
    function max(fst: number, ...rest: number[]): number (+1 overload)
    export max
    max
    (
    size: d.v2f
    size
    .
    v2f.x: number
    x
    ,
    size: d.v2f
    size
    .
    v2f.y: number
    y
    );
    };
    // Inside createAgar(root, world, format)
    const
    const ground: TgpuFragmentFn<{}, d.Vec4f>
    ground
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 more ...;
    '~unstable': typeof import("../../node_modules/typegpu/tgpuUnstable");
    }

    @module ― typegpu

    tgpu
    .
    fragmentFn: <{
    pixel: d.BuiltinPosition;
    }, d.Vec4f>(options: {
    in: {
    pixel: d.BuiltinPosition;
    };
    out: d.Vec4f;
    }) => TgpuFragmentFnShell<{
    pixel: d.BuiltinPosition;
    }, d.Vec4f> (+1 overload)
    fragmentFn
    ({
    in: {
    pixel: d.BuiltinPosition;
    }
    in
    : {
    pixel: d.BuiltinPosition
    pixel
    :
    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
    .
    position: d.BuiltinPosition
    position
    },
    out: d.Vec4f
    out
    :
    import d
    d
    .
    const vec4f: d.Vec4f
    vec4f
    })(({
    pixel: d.v4f
    pixel
    ,
    }) => {
    'use gpu';
    const
    const at: d.v2f
    at
    =
    const sheetAt: (pixel: d.v2f, size: d.v2f) => d.v2f

    Sheet coordinates have (0, 0) at the picture centre, with y pointing down. One unit is half the picture's longer side. The mould grid covers -1 to 1 on both axes, so on a wide picture its top and bottom run past the edges.

    sheetAt
    (
    pixel: d.v4f
    pixel
    .
    xy: d.v2f
    xy
    ,
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    .
    TgpuUniform<WgslStruct<{ size: Vec2f; time: F32; }>>.$: {
    size: d.v2f;
    time: number;
    }
    $
    .
    size: d.v2f
    size
    );
    const
    const color: d.v3f
    color
    =
    import std
    std
    .
    mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
    export mix
    mix
    (
    const AGAR: d.v3f
    AGAR
    ,
    const DEEP: d.v3f
    DEEP
    ,
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0.2, 1.1,
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    const at: d.v2f
    at
    )));
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    function linearToSrgb(linear: d.v3f): d.v3f
    linearToSrgb
    (
    const color: d.v3f
    color
    ), 1);
    });
    const
    const drawing: TgpuRenderPipeline<d.Vec4f>
    drawing
    =
    const root: TgpuRoot
    root
    .
    WithBinding.createRenderPipeline<{}, {}, {
    uv: d.Vec2f;
    }, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
    attribs?: {} | undefined;
    vertex: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
    fragment: TgpuFragmentFn<{
    uv: d.Vec2f;
    } & Record<...>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
    targets?: TgpuColorTargetState;
    }): TgpuRenderPipeline<...> (+2 overloads)
    createRenderPipeline
    ({
    vertex: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
    vertex
    :
    import common
    common
    .
    const fullScreenTriangle: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }>
    export fullScreenTriangle

    A vertex function that defines a single full-screen triangle out of three points.

    @example

    import { common } from 'typegpu';
    const pipeline = root.createRenderPipeline({
    vertex: common.fullScreenTriangle,
    fragment: yourFragmentShader,
    });
    pipeline.draw(3);

    fullScreenTriangle
    ,
    fragment: TgpuFragmentFn<{
    uv: d.Vec2f;
    } & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
    uv: d.Vec2f;
    }>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
    readonly $fragDepth: d.BuiltinFragDepth;
    readonly $sampleMask: d.BuiltinSampleMask;
    }>>))
    fragment
    :
    const ground: TgpuFragmentFn<{}, d.Vec4f>
    ground
    ,
    targets?: TgpuColorTargetState
    targets
    : {
    format?: GPUTextureFormat | undefined

    The

    GPUTextureFormat

    of this color target. The pipeline will only be compatible with

    GPURenderPassEncoder

    s which use a

    GPUTextureView

    of this format in the corresponding color attachment.

    @default ― navigator.gpu.getPreferredCanvasFormat()

    format
    },
    });
    const
    const draw: (pass: TgpuRenderCommands) => void
    draw
    = (
    pass: TgpuRenderCommands
    pass
    :
    (alias) interface TgpuRenderCommands
    import TgpuRenderCommands

    The draw-recording surface shared by render passes and render bundle encoders, mirroring

    GPURenderCommandsMixin

    .

    Draw either by binding TypeGPU pipelines to it (pipeline.with(pass).draw(...)), or proxy-style via pass.setPipeline(pipeline) followed by pass.draw(...). Pipeline resolution is lazy: shaders compile on the first draw.

    TgpuRenderCommands
    ) => {
    pass: TgpuRenderCommands
    pass
    .
    TgpuRenderCommands.setPipeline(pipeline: TgpuRenderPipeline): void

    Sets the current

    TgpuRenderPipeline

    for subsequent draw calls

    setPipeline
    (
    const drawing: TgpuRenderPipeline<d.Vec4f>
    drawing
    );
    pass: TgpuRenderCommands
    pass
    .
    TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
    draw
    (3);
    };
    • 'use gpu' marks a function that TypeGPU compiles for the GPU. It can call other such functions and the maths in std. d.vec2f and d.vec3f hold two and three numbers. The operators +, -, * and / work on those vectors inside GPU functions.
    • pixel counts pixels from the canvas’s top-left corner. sheetAt moves the origin to the centre and divides by half the longer side. On a wide canvas, the dish runs from -1 to 1 horizontally. The mould lives on a square from -1 to 1 in both directions, so its top and bottom extend beyond a wide canvas.
    • std.mix(a, b, t) interpolates from a at t = 0 to b at t = 1. std.smoothstep(low, high, x) changes smoothly from 0 to 1 as x goes from low to high. These functions darken the agar toward its edges.
    • world is a uniform, a small piece of GPU memory with the same values for every pixel. Your TypeScript writes it, and a shader reads it as world.$.
    • A pipeline combines a vertex function and a fragment function for drawing. Compilation takes time, so createAgar returns initAsync alongside draw. The scene awaits initAsync before its first frame. draw(3) runs the triangle’s three vertices.
    • Shaders add and mix colours in linear light. hex splits an sRGB colour, as you would pick it in an editor, into red, green and blue with hexToRgb, then converts them into linear light. The canvas displays sRGB, so linearToSrgb encodes the final colour. In step 8, the final pass handles that encoding.

    The scene makes the uniform and the agar, and draws the agar in its pass:

    scene.ts
    import type {
    (alias) interface Frame
    import Frame

    Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

    Frame
    ,
    (alias) interface Surface
    import Surface

    Provides a destination for frames, such as a canvas from web.surface or native.surface, or a texture from offscreenSurface in a test. Pass the frame to view for code that works with every destination.

    Surface
    } from '@typegpu/kit';
    import {
    import d
    d
    , type
    (alias) interface TgpuRoot
    import TgpuRoot
    TgpuRoot
    } from 'typegpu';
    import {
    function createAgar(root: TgpuRoot, world: TgpuUniform<typeof World>, format: GPUTextureFormat): {
    initAsync: () => Promise<void>;
    draw(pass: TgpuRenderCommands): void;
    }

    Draws the agar dish over the whole picture, darkening towards its edges.

    createAgar
    ,
    const World: d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>

    Picture size in pixels and elapsed growth time in seconds.

    World
    } from './agar.ts';
    export async function
    function createScene(root: TgpuRoot, surface: Surface): Promise<{
    frame(frame: Frame): void;
    destroy(): void;
    }>
    createScene
    (
    root: TgpuRoot
    root
    :
    (alias) interface TgpuRoot
    import TgpuRoot
    TgpuRoot
    ,
    surface: Surface
    surface
    :
    (alias) interface Surface
    import Surface

    Provides a destination for frames, such as a canvas from web.surface or native.surface, or a texture from offscreenSurface in a test. Pass the frame to view for code that works with every destination.

    Surface
    ) {
    const
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    =
    root: TgpuRoot
    root
    .
    TgpuRoot.createUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>(typeSchema: d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>>) => void) | d.InferInput<NoInfer<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>> | undefined): TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>> (+1 overload)

    Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. 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)

    createUniform
    (
    const World: d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>

    Picture size in pixels and elapsed growth time in seconds.

    World
    , {
    size: d.v2f
    size
    :
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    surface: Surface
    surface
    .
    Surface.width: number

    Reports the drawing-buffer size in pixels.

    width
    ,
    surface: Surface
    surface
    .
    Surface.height: number
    height
    ),
    time: number
    time
    : 0,
    });
    const
    const agar: {
    initAsync: () => Promise<void>;
    draw(pass: TgpuRenderCommands): void;
    }
    agar
    =
    function createAgar(root: TgpuRoot, world: TgpuUniform<typeof World>, format: GPUTextureFormat): {
    initAsync: () => Promise<void>;
    draw(pass: TgpuRenderCommands): void;
    }

    Draws the agar dish over the whole picture, darkening towards its edges.

    createAgar
    (
    root: TgpuRoot
    root
    ,
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    ,
    surface: Surface
    surface
    .
    Surface.format: GPUTextureFormat
    format
    );
    await
    const agar: {
    initAsync: () => Promise<void>;
    draw(pass: TgpuRenderCommands): void;
    }
    agar
    .
    initAsync: () => Promise<void>
    initAsync
    ();
    return {
    function frame(frame: Frame): void
    frame
    (
    frame: Frame
    frame
    :
    (alias) interface Frame
    import Frame

    Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

    Frame
    ) {
    const
    const pass: TgpuRenderPass
    pass
    =
    frame: 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.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

    Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

    GPUTextureView

    s.

    beginRenderPass
    ({
    TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
    colorAttachments
    : {
    ColorAttachment.view: GPUTextureView | (ColorTextureConstraint & RenderFlag) | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | TgpuTextureRenderView | GPUCanvasContext

    A

    GPUTextureView

    describing the texture subresource that will be output to for this color attachment.

    view
    :
    surface: Surface
    surface
    .
    Surface.view(frame: Frame): GPUTextureView

    Returns a view of the current texture, created once per texture. React Native requires the frame to present after submission.

    view
    (
    frame: Frame
    frame
    ) },
    });
    const agar: {
    initAsync: () => Promise<void>;
    draw(pass: TgpuRenderCommands): void;
    }
    agar
    .
    function draw(pass: TgpuRenderCommands): void
    draw
    (
    const pass: TgpuRenderPass
    pass
    );
    const pass: TgpuRenderPass
    pass
    .
    TgpuRenderPass.end(): void

    Completes the recording of this render pass

    end
    ();
    },
    function destroy(): void
    destroy
    () {},
    };
    }

    destroy frees what the scene makes when the host closes it. A uniform is small, so the garbage collector frees it, and destroy has nothing to free yet.

    Look at the edges of the dish. The near-black agar has a faint cool tint and darkens toward them.

  2. Put agents in a buffer, and move them.

    A buffer is GPU memory that holds many values of one shape. Create one for up to a million agents. Each Agent stores a position, a heading and whether it is awake. Positions use cells of a square grid that covers the dish, 2048 cells across. You create that grid in step 3.

    A kernel is a compute function that the GPU runs many times at once. Each run is a thread, numbered by gid.x from 0. Here, thread n places agent n. The first 16 384 agents start awake on a spiral at the centre. The rest sleep until step 5.

    agents.ts
    /** Initial agent count and fraction of blob cells occupied. */
    const
    const SEED: 16384

    Initial agent count and fraction of blob cells occupied.

    SEED
    = 16384;
    const
    const FILL: 0.2
    FILL
    = 0.2;
    /** The golden angle prevents agents in the initial blob from lining up. */
    const
    const GOLDEN: number

    The golden angle prevents agents in the initial blob from lining up.

    GOLDEN
    =
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    * (3 -
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.sqrt(x: number): number

    Returns the square root of a number.

    @param ― x A numeric expression.

    sqrt
    (5));
    const
    const Agent: d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>
    Agent
    =
    import d
    d
    .
    struct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>(props: {
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }): d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>
    export struct

    Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

    @example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

    @param ― props Record with string keys and TgpuData values, each entry describing one struct member.

    struct
    ({
    position: d.Vec2f
    position
    :
    import d
    d
    .
    const vec2f: d.Vec2f
    vec2f
    ,
    heading: d.F32
    heading
    :
    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
    ,
    awake: d.U32
    awake
    :
    import d
    d
    .
    const u32: d.U32
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    });
    /** Agent count and step distance in trail cells. */
    const
    const Physarum: d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>

    Agent count and step distance in trail cells.

    Physarum
    =
    import d
    d
    .
    struct<{
    count: d.U32;
    speed: d.F32;
    }>(props: {
    count: d.U32;
    speed: d.F32;
    }): d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>
    export struct

    Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

    @example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

    @param ― props Record with string keys and TgpuData values, each entry describing one struct member.

    struct
    ({
    count: d.U32
    count
    :
    import d
    d
    .
    const u32: d.U32
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    ,
    speed: d.F32
    speed
    :
    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 agents: TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>
    agents
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>(typeSchema: d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>> | undefined): TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>> (+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.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>(elementType: d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>, elementCount: number): d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>> (+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
    (
    const Agent: d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>
    Agent
    ,
    const capacity: number
    capacity
    ));
    const
    const physarum: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>
    physarum
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>(typeSchema: d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>>) => void) | d.InferInput<NoInfer<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>> | undefined): TgpuUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>> (+1 overload)

    Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. 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)

    createUniform
    (
    const Physarum: d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>

    Agent count and step distance in trail cells.

    Physarum
    );
    const
    const centre: d.v2f
    centre
    =
    import d
    d
    .
    const vec2f: d.Vec2f
    (xy: number) => d.v2f (+3 overloads)
    vec2f
    (
    const size: number
    size
    / 2);
    const
    const toward: (angle: number) => d.v2f
    toward
    = (
    angle: number
    angle
    : number) => {
    'use gpu';
    return
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    import std
    std
    .
    function cos(value: number): number (+1 overload)
    export cos
    cos
    (
    angle: number
    angle
    ),
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    angle: number
    angle
    ));
    };
    const
    const scatter: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    scatter
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 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
    : [
    const WORKGROUP: 64
    WORKGROUP
    ],
    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
    >=
    const physarum: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ count: U32; speed: F32; }>>.$: {
    count: number;
    speed: number;
    }
    $
    .
    count: number
    count
    ) return;
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    seed: (seed: number) => void

    Threads do not share the generator's State. As a result, unless you change the seed in each thread, each thread will produce the same sequence. randf.randSeed sets the private seed of the thread.

    @param ― seed seed value to set. For the best results, should be in [-1000, 1000] range.

    seed
    (
    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
    (
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ));
    const
    const out: number
    out
    =
    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
    (
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ) *
    const GOLDEN: number

    The golden angle prevents agents in the initial blob from lining up.

    GOLDEN
    ;
    const
    const position: d.v2f
    position
    =
    const centre: d.v2f
    centre
    +
    const toward: (angle: number) => d.v2f
    toward
    (
    const out: number
    out
    ) *
    import std
    std
    .
    function sqrt(value: number): number (+1 overload)
    export sqrt
    sqrt
    (
    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
    (
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ) / (
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    *
    const FILL: 0.2
    FILL
    ));
    const
    const heading: number
    heading
    =
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () *
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    * 2;
    const
    const awake: number
    awake
    =
    import std
    std
    .
    function select(f: number, t: number, cond: boolean): number (+2 overloads)
    export select

    Returns t if cond is true, and f otherwise. Component-wise if cond is a vector.

    @example select(1, 2, false) // returns 1 select(1, 2, true) // returns 2 select(vec2i(1, 2), vec2i(3, 4), true) // returns vec2i(3, 4) select(vec2i(1, 2), vec2i(3, 4), vec2b(false, true)) // returns vec2i(1, 4)

    select
    (
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (0),
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (1),
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    <
    const SEED: 16384

    Initial agent count and fraction of blob cells occupied.

    SEED
    );
    const agents: TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>
    agents
    .
    TgpuMutable<WgslArray<WgslStruct<{ position: Vec2f; heading: F32; awake: U32; }>>>.$: {
    position: d.v2f;
    heading: number;
    awake: number;
    }[]
    $
    [
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ] =
    const Agent: d.WgslStruct
    (props?: {
    position: d.v2f;
    heading: number;
    awake: number;
    } | undefined) => {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    Agent
    ({
    position: d.v2f
    position
    ,
    heading: number
    heading
    ,
    awake: number
    awake
    });
    });
    • createMutable creates a buffer that a kernel can write. physarum is a uniform with the agent count and distance per step. The scene writes it through the set method that createAgents returns.
    • Place agent n at n golden angles around the centre and √(n / (π · FILL)) cells from it. The first n agents fill a disc of n / FILL cells, one agent per five cells. The golden angle prevents rows from lining up.
    • After randf.seed(...), randf.sample() from @typegpu/noise returns a random number from 0 to 1. Seed with the thread number to give each agent its own numbers. std.select(a, b, condition) returns b when the condition holds and a otherwise.

    Threads start in workgroups, groups of threads that run together. Use 64 threads per workgroup and dispatch enough groups to cover every agent, rounded up with dispatchWorkgroups(Math.ceil(count / 64)). The last workgroup can have more threads than agents. The bounds check at the start of the kernel stops those extra threads.

    Change the agent count to see dispatch round up to whole workgroups of 64 threads. The bounds check if (gid.x >= count) return stops extra threads before buffer access.

    Add a second kernel, move, to move each awake agent forward by speed cells. Vary its heading a little each step so it does not walk in a straight line. An agent that would leave the dish stays put and turns back by 135° to 225°. This prevents agents from collecting along the edge.

    agents.ts
    /** How much an agent's heading wanders each step on its own, in radians either way. */
    const
    const WANDER: 0.08

    How much an agent's heading wanders each step on its own, in radians either way.

    WANDER
    = 0.08;
    /** Excludes a one-cell border around the sheet. */
    const
    const onSheet: (position: d.v2f) => boolean

    Excludes a one-cell border around the sheet.

    onSheet
    = (
    position: d.v2f
    position
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return
    import std
    std
    .
    function all(value: boolean | AnyBooleanVecInstance): boolean
    export all

    Returns true if each component of value is true.

    @example all(vec2b(false, true)) // returns false all(vec3b(true, true, true)) // returns true

    all
    (
    import std
    std
    .
    ge<d.v2f>(lhs: d.v2f, rhs: d.v2f): d.v2b
    export ge

    Checks component-wise whether lhs >= rhs. This function does not return bool, for that use-case, wrap the result in all.

    @example ge(vec2f(0.0, 0.0), vec2f(0.0, 1.0)) // returns vec2b(true, false) ge(vec3u(0, 1, 2), vec3u(2, 1, 0)) // returns vec3b(false, true, true) all(ge(vec4i(2, 2, 4, 5), vec4i(1, 2, 3, 4))) // returns true

    ge
    (
    position: d.v2f
    position
    ,
    import d
    d
    .
    const vec2f: d.Vec2f
    (xy: number) => d.v2f (+3 overloads)
    vec2f
    (1))) &&
    import std
    std
    .
    function all(value: boolean | AnyBooleanVecInstance): boolean
    export all

    Returns true if each component of value is true.

    @example all(vec2b(false, true)) // returns false all(vec3b(true, true, true)) // returns true

    all
    (
    import std
    std
    .
    lt<d.v2f>(lhs: d.v2f, rhs: d.v2f): d.v2b
    export lt

    Checks component-wise whether lhs < rhs. This function does not return bool, for that use-case, wrap the result in all.

    @example lt(vec2f(0.0, 0.0), vec2f(0.0, 1.0)) // returns vec2b(false, true) lt(vec3u(0, 1, 2), vec3u(2, 1, 0)) // returns vec3b(true, false, false) all(lt(vec4i(1, 2, 3, 4), vec4i(2, 3, 4, 5))) // returns true

    lt
    (
    position: d.v2f
    position
    ,
    import d
    d
    .
    const vec2f: d.Vec2f
    (xy: number) => d.v2f (+3 overloads)
    vec2f
    (
    const size: number
    size
    - 1)));
    };
    const
    const move: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    move
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 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
    : [
    const WORKGROUP: 64
    WORKGROUP
    ],
    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
    >=
    const physarum: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ count: U32; speed: F32; }>>.$: {
    count: number;
    speed: number;
    }
    $
    .
    count: number
    count
    ) return;
    const
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    =
    const agents: TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>
    agents
    .
    TgpuMutable<WgslArray<WgslStruct<{ position: Vec2f; heading: F32; awake: U32; }>>>.$: {
    position: d.v2f;
    heading: number;
    awake: number;
    }[]
    $
    [
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ]!;
    if (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    awake: number
    awake
    === 0) return;
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    seed3: (seed: d.v3f) => void

    Threads do not share the generator's State. As a result, unless you change the seed in each thread, each thread will produce the same sequence. randf.randSeed3 sets the private seed of the thread. Sets the private seed of the thread.

    @param ― seed seed value to set. For the best results, all elements should be in [-1000, 1000] range.

    seed3
    (
    import d
    d
    .
    const vec3f: d.Vec3f
    (v0: AnyNumericVec2Instance, z: number) => d.v3f (+5 overloads)
    vec3f
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    ,
    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
    (
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    )));
    let
    let heading: number
    heading
    =
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    + (
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () - 0.5) * 2 *
    const WANDER: 0.08

    How much an agent's heading wanders each step on its own, in radians either way.

    WANDER
    ;
    const
    const next: d.v2f
    next
    =
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    +
    const toward: (angle: number) => d.v2f
    toward
    (
    let heading: number
    heading
    ) *
    const physarum: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    speed: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ count: U32; speed: F32; }>>.$: {
    count: number;
    speed: number;
    }
    $
    .
    speed: number
    speed
    ;
    if (!
    const onSheet: (position: d.v2f) => boolean

    Excludes a one-cell border around the sheet.

    onSheet
    (
    const next: d.v2f
    next
    )) {
    let heading: number
    heading
    +=
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    * (0.75 + 0.5 *
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    ());
    } else {
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    =
    import d
    d
    .
    const vec2f: d.Vec2f
    (v: AnyNumericVec2Instance) => d.v2f (+3 overloads)
    vec2f
    (
    const next: d.v2f
    next
    );
    }
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    =
    let heading: number
    heading
    ;
    });

    agent points into the buffer, so assigning its fields writes the buffer. Seed with its position and number to get different random numbers each step. Create a pipeline for each kernel with createComputePipeline({ compute: move }). Return scatter(encoder) to dispatch scatter in its own compute pass. Return move(pass) to dispatch move into the pass the caller supplies.

    Stepping once per frame would make the mould move twice as fast at 120 Hz as at 60 Hz. A fixed step counts how many simulation steps fit in the elapsed time. It carries the unused time into the next frame:

    Stall a frame or change the display rate. Step length stays fixed; catch-up frames run at most maxSteps and drop time beyond the cap, shown at one fifth speed.
    scene.ts
    // Inside createScene
    const
    const own: Owner
    own
    =
    function owner(message?: string): Owner

    Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

    const own = owner('The swarm was destroyed.');
    const screen = own(targets.create(root, surface, { hdr }));
    const settings = own(root.createUniform(Settings));
    own(() => {
    for (const buffer of reads) buffer.destroy();
    });
    return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

    @param ― message what usable throws, naming the module: "The table was destroyed."

    @returns ― an owner you pass each thing to and destroy

    owner
    ();
    const
    const STEP: number
    STEP
    = 1 / 60;
    const
    const sim: clock.FixedStep
    sim
    =
    import clock
    clock
    .
    function fixedStep(options: clock.FixedStepOptions): clock.FixedStep

    Accumulates elapsed time for fixed simulation steps. Accumulates nothing while paused. Throws unless step is finite and above 0 and maxSteps is an integer above 0.

    @returns ― a clock whose advance says how many steps to run each frame

    fixedStep
    ({
    FixedStepOptions.step: number

    Sets the step duration in seconds, such as 1 / 120.

    step
    :
    const STEP: number
    STEP
    });
    const
    const agents: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    =
    const own: Owner
    <{
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }>(item: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }) => {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }

    Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

    own
    (
    function createAgents(root: TgpuRoot, options: {
    size: number;
    capacity: number;
    }): {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    createAgents
    (
    const root: TgpuRoot
    root
    , {
    size: number
    size
    : 2048,
    capacity: number
    capacity
    : 1_000_000 }));
    await
    const agents: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    .
    function initAsync(): Promise<unknown>
    initAsync
    ();
    const agents: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    .
    function set(values: {
    count: number;
    speed: number;
    }): void
    set
    ({
    count: number
    count
    : 400_000,
    speed: number
    speed
    : 1.2 });
    let
    let scattering: boolean
    scattering
    = true;
    // Inside frame, before the render pass
    const
    const steps: number
    steps
    =
    const sim: clock.FixedStep
    sim
    .
    FixedStep.advance(dt: number): number

    Adds dt seconds and returns the number of steps to run. Each step lasts step seconds, so a GPU kernel can use a fixed duration. All steps in a frame see the same queued CPU writes. Keep a step counter on the GPU for values that must change per step.

    advance
    (
    const frame: Frame
    frame
    .
    Frame.dt: number

    Reports seconds since the previous submitted frame. The first frame has a dt of zero.

    dt
    );
    if (
    const steps: number
    steps
    > 0) {
    const world: TgpuUniform<WgslStruct<{
    size: Vec2f;
    time: F32;
    }>>
    world
    .
    TgpuBufferBindingBase<WgslStruct<{ size: Vec2f; time: F32; }>>.patch(data: {
    size?: InferInput<Vec2f> | undefined;
    time?: number | undefined;
    } | undefined): void
    patch
    ({
    time?: number | undefined
    time
    :
    const sim: clock.FixedStep
    sim
    .
    FixedStep.time: number

    Reports the simulated seconds: the sum of every step advance has returned.

    time
    });
    }
    if (
    let scattering: true
    scattering
    )
    const agents: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    .
    function scatter(encoder: TgpuCommandEncoder): void
    scatter
    (
    const frame: 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
    );
    let scattering: boolean
    scattering
    = false;
    for (let
    let i: number
    i
    = 0;
    let i: number
    i
    <
    const steps: number
    steps
    ;
    let i: number
    i
    ++) {
    const
    const pass: TgpuComputePass
    pass
    =
    const frame: 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 agents: {
    initAsync(): Promise<unknown>;
    set(values: {
    count: number;
    speed: number;
    }): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    .
    function move(pass: TgpuComputePass): void
    move
    (
    const pass: TgpuComputePass
    pass
    );
    const pass: TgpuComputePass
    pass
    .
    TgpuComputePass.end(): void

    Completes the recording of this compute pass

    end
    ();
    }

    Record the scatter, the steps and the render pass into one encoder, in that order. The GPU places the agents before it moves them. world.patch writes only the fields you supply, here the elapsed simulation time in seconds. The picture stays the same because nothing draws the agents yet.

    A million agents fill a large buffer, so free it with the scene. Return destroy from createAgents to free its buffers. owner() collects what you pass through own as you make it. Replace the scene’s destroy() {} with destroy: own.destroy, which frees them in reverse order.

  3. Leave a trail.

    Display the mould as a trail on a grid of 2048 × 2048 cells. Store that grid in a texture, an image the GPU can read or write. Reading can blend nearby cells, while writing sets one cell at a time. at(uv) reads the trail, with uv from 0 to 1 across the grid.

    Each step, every agent adds a drop to its cell. If two threads read and add to the same value at once, one update can overwrite the other. Put the drops in a buffer of atomic numbers. An atomic add completes before another thread adds to the same number. These atomics hold whole numbers, so represent a drop of 1 as PARTS, or 1024. A kernel named take moves each cell’s drops into the texture and empties the cell.

    trail.ts
    const
    const PARTS: 1024
    PARTS
    = 1024;
    const
    const WORKGROUP: 16
    WORKGROUP
    = 16;
    const
    const trail: TgpuTexture<{
    size: [number, number];
    format: "rgba16float";
    }> & StorageFlag & SampledFlag
    trail
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createTexture<number, number, number, [number, number], "rgba16float", number, number, GPUTextureFormat[], GPUTextureDimension>(props: CreateTextureOptions<[number, number], "rgba16float", number, number, GPUTextureFormat[], GPUTextureDimension>): TgpuTexture<{
    size: [number, number];
    format: "rgba16float";
    }>
    createTexture
    ({
    size: [number, number]

    The width, height, and depth or layer count of the texture.

    size
    : [
    const size: number
    size
    ,
    const size: number
    size
    ],
    format: "rgba16float"

    The format of the texture.

    format
    : 'rgba16float' })
    .
    TgpuTexture<{ size: [number, number]; format: "rgba16float"; }>.$usage<["storage", "sampled"]>(usages_0: "storage", usages_1: "sampled"): TgpuTexture<{
    size: [number, number];
    format: "rgba16float";
    }> & StorageFlag & SampledFlag
    $usage
    ('storage', 'sampled');
    const
    const reading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    reading
    =
    const trail: TgpuTexture<{
    size: [number, number];
    format: "rgba16float";
    }> & StorageFlag & SampledFlag
    trail
    .
    TgpuTexture<{ size: [number, number]; format: "rgba16float"; }>.createView<d.WgslTexture2d<d.F32>>(schema: d.WgslTexture2d<d.F32>, viewDescriptor?: (TgpuTextureViewDescriptor & {
    sampleType?: "float" | "unfilterable-float" | undefined;
    }) | undefined): TgpuTextureView<d.WgslTexture2d<d.F32>> (+3 overloads)
    createView
    (
    import d
    d
    .
    texture2d<d.F32>(sampleType: d.F32): d.WgslTexture2d<d.F32> (+1 overload)
    export texture2d
    texture2d
    (
    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 writing: TgpuTextureView<d.WgslStorageTexture2d<"rgba16float", "write-only">>
    writing
    =
    const trail: TgpuTexture<{
    size: [number, number];
    format: "rgba16float";
    }> & StorageFlag & SampledFlag
    trail
    .
    TgpuTexture<{ size: [number, number]; format: "rgba16float"; }>.createView<d.WgslStorageTexture2d<"rgba16float", "write-only">>(schema: d.WgslStorageTexture2d<"rgba16float", "write-only">, viewDescriptor?: TgpuTextureViewDescriptor): TgpuTextureView<d.WgslStorageTexture2d<"rgba16float", "write-only">> (+3 overloads)
    createView
    (
    import d
    d
    .
    textureStorage2d<"rgba16float", "write-only">(format: "rgba16float", access: "write-only"): d.WgslStorageTexture2d<"rgba16float", "write-only"> (+1 overload)
    export textureStorage2d
    textureStorage2d
    ('rgba16float', 'write-only'));
    const
    const smooth: TgpuFixedSampler
    smooth
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createSampler(props: WgslSamplerProps): TgpuFixedSampler
    createSampler
    ({
    WgslSamplerProps.magFilter?: GPUFilterMode | undefined

    Specifies the sampling behavior when the sample footprint is smaller than or equal to one texel.

    magFilter
    : 'linear',
    WgslSamplerProps.minFilter?: GPUFilterMode | undefined

    Specifies the sampling behavior when the sample footprint is larger than one texel.

    minFilter
    : 'linear' });
    const
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createMutable<d.WgslArray<d.Atomic<d.U32>>>(typeSchema: d.WgslArray<d.Atomic<d.U32>>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.Atomic<d.U32>>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.Atomic<d.U32>>>> | undefined): TgpuMutable<d.WgslArray<d.Atomic<d.U32>>> (+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.Atomic<d.U32>>(elementType: d.Atomic<d.U32>, elementCount: number): d.WgslArray<d.Atomic<d.U32>> (+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
    .
    atomic<d.U32>(data: d.U32): d.Atomic<d.U32>
    export atomic

    Marks a concrete integer scalar type schema (u32 or i32) as a WGSL atomic.

    @example const atomicU32 = d.atomic(d.u32); const atomicI32 = d.atomic(d.i32);

    @param ― data Underlying type schema.

    atomic
    (
    import d
    d
    .
    const u32: d.U32
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    ),
    const size: number
    size
    *
    const size: number
    size
    ));
    const
    const deposit: (cell: d.v2i, amount: number) => void
    deposit
    = (
    cell: d.v2i
    cell
    :
    import d
    d
    .
    export v2i

    Interface representing its WGSL vector type counterpart: vec2i or vec2. A vector with 2 elements of type i32

    v2i
    ,
    amount: number
    amount
    : number) => {
    'use gpu';
    import std
    std
    .
    atomicAdd<d.atomicU32>(a: d.atomicU32, value: number): number
    export atomicAdd
    atomicAdd
    (
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    cell: d.v2i
    cell
    .
    v2i.y: number
    y
    *
    const size: number
    size
    +
    cell: d.v2i
    cell
    .
    v2i.x: number
    x
    ]!,
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (
    amount: number
    amount
    *
    const PARTS: 1024
    PARTS
    ));
    };
    const
    const at: (uv: d.v2f) => number
    at
    = (
    uv: d.v2f
    uv
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    return
    import std
    std
    .
    textureSampleLevel<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, sampler: d.sampler, coords: d.v2f, level: number): d.v4f (+13 overloads)
    export textureSampleLevel
    textureSampleLevel
    (
    const reading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    reading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const smooth: TgpuFixedSampler
    smooth
    .
    TgpuSampler.$: d.sampler
    $
    ,
    uv: d.v2f
    uv
    , 0).
    v4f.x: number
    x
    ;
    };
    const
    const take: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    take
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 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
    : [
    const WORKGROUP: 16
    WORKGROUP
    ,
    const WORKGROUP: 16
    WORKGROUP
    ],
    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
    >=
    const size: number
    size
    ||
    gid: d.v3u
    gid
    .
    v3u.y: number
    y
    >=
    const size: number
    size
    ) return;
    const
    const index: number
    index
    =
    gid: d.v3u
    gid
    .
    v3u.y: number
    y
    *
    const size: number
    size
    +
    gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ;
    const
    const fresh: number
    fresh
    =
    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
    (
    import std
    std
    .
    atomicLoad<d.atomicU32>(a: d.atomicU32): number
    export atomicLoad
    atomicLoad
    (
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const index: number
    index
    ]!)) /
    const PARTS: 1024
    PARTS
    ;
    import std
    std
    .
    atomicStore<d.atomicU32>(a: d.atomicU32, value: number): void
    export atomicStore
    atomicStore
    (
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const index: number
    index
    ]!, 0);
    import std
    std
    .
    textureStore<d.textureStorage2d<"rgba16float", "write-only">>(texture: d.textureStorage2d<"rgba16float", "write-only">, coords: d.v2i | d.v2u, value: d.v4f): void (+3 overloads)
    export textureStore
    textureStore
    (
    const writing: TgpuTextureView<d.WgslStorageTexture2d<"rgba16float", "write-only">>
    writing
    .
    TgpuTextureView<WgslStorageTexture2d<"rgba16float", "write-only">>.$: d.textureStorage2d<"rgba16float", "write-only">
    $
    ,
    gid: d.v3u
    gid
    .
    xy: d.v2u
    xy
    ,
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    const fresh: number
    fresh
    , 0, 0, 1));
    });
    • Give the texture two views, reading to sample through the smooth sampler and writing to store a cell. The rgba16float format holds four numbers per cell. The trail uses the first.
    • take runs one thread per cell in workgroups of 16 × 16. Return spread(pass) alongside deposit and at from createTrail. It dispatches Math.ceil(2048 / 16) workgroups in each direction.

    Pass the trail to createAgents so it can read the grid size. End move by depositing a drop at the agent’s position. Add deposit: d.f32 to Physarum for the drop size:

    trail.deposit(d.vec2i(agent.position), physarum.$.deposit);

    Draw a second full-screen triangle over the agar in veins.ts. Its fragment function reads the trail under each pixel and makes it orange in proportion to the density. A density of 1 gives full orange.

    veins.ts
    const
    const ORANGE: d.v3f
    ORANGE
    =
    function hex(rgb: number): d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0xff7a2a);
    const
    const veins: TgpuFragmentFn<{}, d.Vec4f>
    veins
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 more ...;
    '~unstable': typeof import("../../node_modules/typegpu/tgpuUnstable");
    }

    @module ― typegpu

    tgpu
    .
    fragmentFn: <{
    pixel: d.BuiltinPosition;
    }, d.Vec4f>(options: {
    in: {
    pixel: d.BuiltinPosition;
    };
    out: d.Vec4f;
    }) => TgpuFragmentFnShell<{
    pixel: d.BuiltinPosition;
    }, d.Vec4f> (+1 overload)
    fragmentFn
    ({
    in: {
    pixel: d.BuiltinPosition;
    }
    in
    : {
    pixel: d.BuiltinPosition
    pixel
    :
    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
    .
    position: d.BuiltinPosition
    position
    },
    out: d.Vec4f
    out
    :
    import d
    d
    .
    const vec4f: d.Vec4f
    vec4f
    })(({
    pixel: d.v4f
    pixel
    ,
    }) => {
    'use gpu';
    const
    const uv: d.v2f
    uv
    =
    function sheetAt(pixel: d.v2f, size: d.v2f): d.v2f

    Sheet coordinates have (0, 0) at the picture centre, with y pointing down. One unit is half the picture's longer side. The mould grid covers -1 to 1 on both axes, so on a wide picture its top and bottom run past the edges.

    sheetAt
    (
    pixel: d.v4f
    pixel
    .
    xy: d.v2f
    xy
    ,
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    .
    TgpuUniform<WgslStruct<{ size: Vec2f; time: F32; }>>.$: {
    size: d.v2f;
    time: number;
    }
    $
    .
    size: d.v2f
    size
    ) * 0.5 + 0.5;
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    function linearToSrgb(linear: d.v3f): d.v3f
    linearToSrgb
    (
    const ORANGE: d.v3f
    ORANGE
    *
    import std
    std
    .
    function saturate(value: number): number (+1 overload)
    export saturate
    saturate
    (
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    ))), 0);
    });
    const
    const drawing: TgpuRenderPipeline<d.Vec4f>
    drawing
    =
    const root: TgpuRoot
    root
    .
    WithBinding.createRenderPipeline<{}, {}, {
    uv: d.Vec2f;
    }, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
    attribs?: {} | undefined;
    vertex: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
    fragment: TgpuFragmentFn<{
    uv: d.Vec2f;
    } & Record<...>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
    targets?: TgpuColorTargetState;
    }): TgpuRenderPipeline<...> (+2 overloads)
    createRenderPipeline
    ({
    vertex: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
    vertex
    :
    import common
    common
    .
    const fullScreenTriangle: TgpuVertexFn<{}, {
    uv: d.Vec2f;
    }>
    export fullScreenTriangle

    A vertex function that defines a single full-screen triangle out of three points.

    @example

    import { common } from 'typegpu';
    const pipeline = root.createRenderPipeline({
    vertex: common.fullScreenTriangle,
    fragment: yourFragmentShader,
    });
    pipeline.draw(3);

    fullScreenTriangle
    ,
    fragment: TgpuFragmentFn<{
    uv: d.Vec2f;
    } & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
    uv: d.Vec2f;
    }>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
    readonly $fragDepth: d.BuiltinFragDepth;
    readonly $sampleMask: d.BuiltinSampleMask;
    }>>))
    fragment
    :
    const veins: TgpuFragmentFn<{}, d.Vec4f>
    veins
    ,
    targets?: TgpuColorTargetState
    targets
    : {
    format?: GPUTextureFormat | undefined

    The

    GPUTextureFormat

    of this color target. The pipeline will only be compatible with

    GPURenderPassEncoder

    s which use a

    GPUTextureView

    of this format in the corresponding color attachment.

    @default ― navigator.gpu.getPreferredCanvasFormat()

    format
    ,
    blend?: GPUBlendState | undefined

    The blending behavior for this color target. If left undefined, disables blending for this color target.

    blend
    :
    import pipelines
    pipelines
    .
    const blend: Readonly<{
    add: Readonly<{
    color: GPUBlendComponent;
    alpha: GPUBlendComponent;
    }>;
    over: Readonly<{
    color: GPUBlendComponent;
    alpha: GPUBlendComponent;
    }>;
    straight: Readonly<{
    color: GPUBlendComponent;
    alpha: GPUBlendComponent;
    }>;
    }>

    Provides blend states for pipeline targets.

    blend
    .
    add: Readonly<{
    color: GPUBlendComponent;
    alpha: GPUBlendComponent;
    }>

    Adds fragment values to the target, for sparks, glow or bloom.

    add
    },
    });

    Use the pipeline’s add blend state to add each pixel’s colour to the agar beneath it. std.saturate clamps a number to the range 0 to 1. Create the trail and veins in the scene, the trail through own to free its texture and drops. In each step’s compute pass, run trail.spread(pass) after agents.move(pass). Draw the veins after the agar.

    Watch the specks spread from the centre as the 16 384 awake agents walk. Each speck shows only the current step’s drops.

  4. Spread the trail, and let it fade.

    A trail that lasts one step is only specks. Real Physarum leaves a chemical that spreads into the agar around it and breaks down. So each step blurs the trail, keeps 93% of it, and adds the new drops.

    Blur each cell by averaging it with its neighbours. Reading and writing one texture would let some threads read neighbours that have already changed. Add a second texture, spare. The first kernel reads the trail, blurs horizontally and writes to spare. The second reads spare, blurs vertically and writes to the trail. This alternation is called ping-pong. The kit’s history uses the same pattern to keep state between frames.

    Lower keep to fade the trail faster. Each step blurs horizontally into the spare texture, then vertically back into the trail with fading and fresh drops, so neither kernel writes the texture it reads.
    trail.ts
    /** Trail retention per step and blur strength, both from 0 to 1. */
    export const
    const Spread: d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>

    Trail retention per step and blur strength, both from 0 to 1.

    Spread
    =
    import d
    d
    .
    struct<{
    keep: d.F32;
    blur: d.F32;
    }>(props: {
    keep: d.F32;
    blur: d.F32;
    }): d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>
    export struct

    Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

    @example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

    @param ― props Record with string keys and TgpuData values, each entry describing one struct member.

    struct
    ({
    keep: d.F32
    keep
    :
    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
    ,
    blur: d.F32
    blur
    :
    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
    });
    // Inside createTrail
    const
    const spread: TgpuUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>
    spread
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>(typeSchema: d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>>) => void) | d.InferInput<NoInfer<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>> | undefined): TgpuUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>> (+1 overload)

    Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. 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)

    createUniform
    (
    const Spread: d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>

    Trail retention per step and blur strength, both from 0 to 1.

    Spread
    );
    const
    const inside: (cell: d.v2i) => d.v2i
    inside
    = (
    cell: d.v2i
    cell
    :
    import d
    d
    .
    export v2i

    Interface representing its WGSL vector type counterpart: vec2i or vec2. A vector with 2 elements of type i32

    v2i
    ) => {
    'use gpu';
    return
    import std
    std
    .
    clamp<d.v2i>(value: d.v2i, low: d.v2i, high: d.v2i): d.v2i (+1 overload)
    export clamp
    clamp
    (
    cell: d.v2i
    cell
    ,
    import d
    d
    .
    const vec2i: d.Vec2i
    (xy: number) => d.v2i (+3 overloads)
    vec2i
    (0),
    import d
    d
    .
    const vec2i: d.Vec2i
    (xy: number) => d.v2i (+3 overloads)
    vec2i
    (
    const size: number
    size
    - 1));
    };
    const
    const across: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    across
    =
    const tgpu: {
    const: {
    <TDataType extends d.AnyData>(dataType: TDataType, value: d.InferGPU<TDataType>): TgpuConst<TDataType>;
    <TElement extends d.AnyWgslData>(dataType: (elementCount: number) => d.WgslArray<TElement>, value: d.InferGPU<d.WgslArray<TElement>>): TgpuConst<d.WgslArray<TElement>>;
    };
    fn: {
    <Args extends d.BaseData[] | []>(argTypes: Args, returnType?: undefined): TgpuFnShell<Args, d.Void>;
    <Args extends d.BaseData[] | [], Return extends d.BaseData>(argTypes: Args, returnType: Return): TgpuFnShell<Args, Return>;
    <T extends AnyFn>(inner: T): TgpuGenericFn<T>;
    };
    ... 17 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
    : [
    const WORKGROUP: number
    WORKGROUP
    ,
    const WORKGROUP: number
    WORKGROUP
    ],
    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
    >=
    const size: number
    size
    ||
    gid: d.v3u
    gid
    .
    v3u.y: number
    y
    >=
    const size: number
    size
    ) return;
    const
    const cell: d.v2i
    cell
    =
    import d
    d
    .
    const vec2i: d.Vec2i
    (v: AnyNumericVec2Instance) => d.v2i (+3 overloads)
    vec2i
    (
    gid: d.v3u
    gid
    .
    xy: d.v2u
    xy
    );
    const
    const here: number
    here
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const reading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    reading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const cell: d.v2i
    cell
    , 0).
    v4f.x: number
    x
    ;
    const
    const left: number
    left
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const reading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    reading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const inside: (cell: d.v2i) => d.v2i
    inside
    (
    const cell: d.v2i
    cell
    -
    import d
    d
    .
    const vec2i: d.Vec2i
    (x: number, y: number) => d.v2i (+3 overloads)
    vec2i
    (1, 0)), 0).
    v4f.x: number
    x
    ;
    const
    const right: number
    right
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const reading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    reading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const inside: (cell: d.v2i) => d.v2i
    inside
    (
    const cell: d.v2i
    cell
    +
    import d
    d
    .
    const vec2i: d.Vec2i
    (x: number, y: number) => d.v2i (+3 overloads)
    vec2i
    (1, 0)), 0).
    v4f.x: number
    x
    ;
    const
    const blurred: number
    blurred
    =
    import std
    std
    .
    function mix(e1: number, e2: number, e3: number): number (+2 overloads)
    export mix
    mix
    (
    const here: number
    here
    , (
    const left: number
    left
    +
    const here: number
    here
    * 2 +
    const right: number
    right
    ) / 4,
    const spread: TgpuUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>
    spread
    .
    TgpuUniform<WgslStruct<{ keep: F32; blur: F32; }>>.$: {
    keep: number;
    blur: number;
    }
    $
    .
    blur: number
    blur
    );
    import std
    std
    .
    textureStore<d.textureStorage2d<"rgba16float", "write-only">>(texture: d.textureStorage2d<"rgba16float", "write-only">, coords: d.v2i | d.v2u, value: d.v4f): void (+3 overloads)
    export textureStore
    textureStore
    (
    const spareWriting: TgpuTextureView<d.WgslStorageTexture2d<"rgba16float", "write-only">>
    spareWriting
    .
    TgpuTextureView<WgslStorageTexture2d<"rgba16float", "write-only">>.$: d.textureStorage2d<"rgba16float", "write-only">
    $
    ,
    gid: d.v3u
    gid
    .
    xy: d.v2u
    xy
    ,
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    const blurred: number
    blurred
    , 0, 0, 1));
    });
    const
    const blurringAcross: TgpuComputePipeline
    blurringAcross
    =
    const root: TgpuRoot
    root
    .
    WithBinding.createComputePipeline<{
    gid: d.BuiltinGlobalInvocationId;
    }>(descriptor: TgpuComputePipeline.Descriptor<{
    gid: d.BuiltinGlobalInvocationId;
    }>): TgpuComputePipeline
    createComputePipeline
    ({
    compute: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    compute
    :
    const across: TgpuComputeFn<{
    gid: d.BuiltinGlobalInvocationId;
    }>
    across
    });
    const
    const groups: number
    groups
    =
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.ceil(x: number): number

    Returns the smallest integer greater than or equal to its numeric argument.

    @param ― x A numeric expression.

    ceil
    (
    const size: number
    size
    /
    const WORKGROUP: number
    WORKGROUP
    );
    return {
    // Beside deposit and at
    function spread(pass: TgpuComputePass): void
    spread
    (
    pass: TgpuComputePass
    pass
    :
    (alias) interface TgpuComputePass
    import TgpuComputePass

    A compute pass recording into a

    TgpuCommandEncoder

    .

    Dispatch either by binding TypeGPU pipelines to it (pipeline.with(pass).dispatchWorkgroups(...)), or proxy-style via pass.setPipeline(pipeline) followed by pass.dispatchWorkgroups(...).

    Call end() when done recording.

    TgpuComputePass
    ) {
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

    Sets the current

    TgpuComputePipeline

    for subsequent dispatches

    setPipeline
    (
    const blurringAcross: TgpuComputePipeline
    blurringAcross
    );
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
    dispatchWorkgroups
    (
    const groups: number
    groups
    ,
    const groups: number
    groups
    );
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

    Sets the current

    TgpuComputePipeline

    for subsequent dispatches

    setPipeline
    (
    const blurringDown: TgpuComputePipeline
    blurringDown
    );
    pass: TgpuComputePass
    pass
    .
    TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
    dispatchWorkgroups
    (
    const groups: number
    groups
    ,
    const groups: number
    groups
    );
    },
    };

    std.textureLoad reads one cell without blending. inside keeps reads of neighbouring cells within the grid. Rename take to down. Read spare above and below the cell, then store the blurred, faded trail plus the drops:

    trail.ts
    // Inside down, after the bounds check
    const
    const cell: d.v2i
    cell
    =
    import d
    d
    .
    const vec2i: d.Vec2i
    (v: AnyNumericVec2Instance) => d.v2i (+3 overloads)
    vec2i
    (
    const gid: d.v3u
    gid
    .
    xy: d.v2u
    xy
    );
    const
    const here: number
    here
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const spareReading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    spareReading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const cell: d.v2i
    cell
    , 0).
    v4f.x: number
    x
    ;
    const
    const up: number
    up
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const spareReading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    spareReading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const inside: (cell: d.v2i) => d.v2i
    inside
    (
    const cell: d.v2i
    cell
    -
    import d
    d
    .
    const vec2i: d.Vec2i
    (x: number, y: number) => d.v2i (+3 overloads)
    vec2i
    (0, 1)), 0).
    v4f.x: number
    x
    ;
    const
    const below: number
    below
    =
    import std
    std
    .
    textureLoad<d.texture2d<d.F32>>(texture: d.texture2d<d.F32>, coords: d.v2i | d.v2u, level: number): d.v4f (+12 overloads)
    export textureLoad
    textureLoad
    (
    const spareReading: TgpuTextureView<d.WgslTexture2d<d.F32>>
    spareReading
    .
    TgpuTextureView<WgslTexture2d<F32>>.$: d.texture2d<d.F32>
    $
    ,
    const inside: (cell: d.v2i) => d.v2i
    inside
    (
    const cell: d.v2i
    cell
    +
    import d
    d
    .
    const vec2i: d.Vec2i
    (x: number, y: number) => d.v2i (+3 overloads)
    vec2i
    (0, 1)), 0).
    v4f.x: number
    x
    ;
    const
    const blurred: number
    blurred
    =
    import std
    std
    .
    function mix(e1: number, e2: number, e3: number): number (+2 overloads)
    export mix
    mix
    (
    const here: number
    here
    , (
    const up: number
    up
    +
    const here: number
    here
    * 2 +
    const below: number
    below
    ) / 4,
    const spread: TgpuUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>
    spread
    .
    TgpuUniform<WgslStruct<{ keep: F32; blur: F32; }>>.$: {
    keep: number;
    blur: number;
    }
    $
    .
    blur: number
    blur
    );
    const
    const index: number
    index
    =
    const gid: d.v3u
    gid
    .
    v3u.y: number
    y
    *
    const size: number
    size
    +
    const gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ;
    const
    const fresh: number
    fresh
    =
    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
    (
    import std
    std
    .
    atomicLoad<d.atomicU32>(a: d.atomicU32): number
    export atomicLoad
    atomicLoad
    (
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const index: number
    index
    ]!)) /
    const PARTS: number
    PARTS
    ;
    import std
    std
    .
    atomicStore<d.atomicU32>(a: d.atomicU32, value: number): void
    export atomicStore
    atomicStore
    (
    const dropped: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    dropped
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const index: number
    index
    ]!, 0);
    import std
    std
    .
    textureStore<d.textureStorage2d<"rgba16float", "write-only">>(texture: d.textureStorage2d<"rgba16float", "write-only">, coords: d.v2i | d.v2u, value: d.v4f): void (+3 overloads)
    export textureStore
    textureStore
    (
    const writing: TgpuTextureView<d.WgslStorageTexture2d<"rgba16float", "write-only">>
    writing
    .
    TgpuTextureView<WgslStorageTexture2d<"rgba16float", "write-only">>.$: d.textureStorage2d<"rgba16float", "write-only">
    $
    ,
    const gid: d.v3u
    gid
    .
    xy: d.v2u
    xy
    ,
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    const blurred: number
    blurred
    *
    const spread: TgpuUniform<d.WgslStruct<{
    keep: d.F32;
    blur: d.F32;
    }>>
    spread
    .
    TgpuUniform<WgslStruct<{ keep: F32; blur: F32; }>>.$: {
    keep: number;
    blur: number;
    }
    $
    .
    keep: number
    keep
    +
    const fresh: number
    fresh
    , 0, 0, 1));

    Dispatch both kernels in the step’s compute pass, one after the other. The GPU finishes one dispatch’s writes before the next reads them. Return a set method from createTrail, and use it in the scene to set keep to 0.93 and blur to 1.

    Watch the trails fade behind the agents. With no steering yet, their walks spread the blob into an even haze.

  5. Sense and steer.

    Give each agent three sensors, one ahead and one to either side. The side sensors sit sensorAngle radians from its heading. All three sit sensorDistance cells away. Read the trail at each sensor and turn toward the strongest. If the trail ahead is weakest, turn left or right at random. Agents follow and strengthen each other’s trails, so the haze gathers into threads.

    Widen the angle to switch between threads, or shorten the distance to follow bends. Over this patch of a few dozen cells, brightness shows trail strength; the agent turns toward the strongest sensor or randomly left or right when the forward sensor is weakest.
    agents.ts
    /** Samples trail attraction `sensorDistance` cells away at `angle` radians from the heading. */
    const
    const sniff: (agent: d.Infer<typeof Agent>, angle: number) => number

    Samples trail attraction sensorDistance cells away at angle radians from the heading.

    sniff
    = (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    :
    import d
    d
    .
    type Infer<T> = T extends {
    readonly [$repr]: infer TRepr;
    } ? TRepr : T
    export Infer

    Extracts the inferred representation of a resource. For inferring types as seen by the GPU, see

    InferGPU

    @example type A = Infer // => number type B = Infer<WgslArray> // => number[] type C = Infer<Atomic> // => number

    Infer
    <typeof
    const Agent: d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>
    Agent
    >,
    angle: number
    angle
    : number) => {
    'use gpu';
    const
    const uv: d.v2f
    uv
    = (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    +
    const toward: (angle: number) => d.v2f
    toward
    (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    +
    angle: number
    angle
    ) *
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorAngle: d.F32;
    sensorDistance: d.F32;
    turn: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorAngle: F32; sensorDistance: F32; turn: F32; }>>.$: {
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    }
    $
    .
    sensorDistance: number
    sensorDistance
    ) /
    const size: number
    size
    ;
    return
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    );
    };
    // Inside move, after the seed, replacing the random walk
    const
    const angle: number
    angle
    =
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorAngle: d.F32;
    sensorDistance: d.F32;
    turn: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorAngle: F32; sensorDistance: F32; turn: F32; }>>.$: {
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    }
    $
    .
    sensorAngle: number
    sensorAngle
    ;
    const
    const ahead: number
    ahead
    =
    const sniff: (agent: d.Infer<typeof Agent>, angle: number) => number

    Samples trail attraction sensorDistance cells away at angle radians from the heading.

    sniff
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    ,
    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
    (0));
    const
    const left: number
    left
    =
    const sniff: (agent: d.Infer<typeof Agent>, angle: number) => number

    Samples trail attraction sensorDistance cells away at angle radians from the heading.

    sniff
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    , -
    const angle: number
    angle
    );
    const
    const right: number
    right
    =
    const sniff: (agent: d.Infer<typeof Agent>, angle: number) => number

    Samples trail attraction sensorDistance cells away at angle radians from the heading.

    sniff
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    ,
    const angle: number
    angle
    );
    const
    const turn: number
    turn
    =
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorAngle: d.F32;
    sensorDistance: d.F32;
    turn: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorAngle: F32; sensorDistance: F32; turn: F32; }>>.$: {
    sensorAngle: number;
    sensorDistance: number;
    turn: number;
    }
    $
    .
    turn: number
    turn
    * (0.5 +
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    ());
    let
    let heading: number
    heading
    =
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    + (
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () - 0.5) * 2 *
    const WANDER: number
    WANDER
    ;
    if (
    const ahead: number
    ahead
    <
    const left: number
    left
    &&
    const ahead: number
    ahead
    <
    const right: number
    right
    ) {
    let heading: number
    heading
    +=
    import std
    std
    .
    function select(f: number, t: number, cond: boolean): number (+2 overloads)
    export select

    Returns t if cond is true, and f otherwise. Component-wise if cond is a vector.

    @example select(1, 2, false) // returns 1 select(1, 2, true) // returns 2 select(vec2i(1, 2), vec2i(3, 4), true) // returns vec2i(3, 4) select(vec2i(1, 2), vec2i(3, 4), vec2b(false, true)) // returns vec2i(1, 4)

    select
    (-
    const turn: number
    turn
    ,
    const turn: number
    turn
    ,
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () < 0.5);
    } else if (
    const left: number
    left
    >
    const ahead: number
    ahead
    &&
    const left: number
    left
    >
    const right: number
    right
    ) {
    let heading: number
    heading
    -=
    const turn: number
    turn
    ;
    } else if (
    const right: number
    right
    >
    const ahead: number
    ahead
    &&
    const right: number
    right
    >
    const left: number
    left
    ) {
    let heading: number
    heading
    +=
    const turn: number
    turn
    ;
    }

    Add sensorAngle, sensorDistance and turn to Physarum. Set them from 30°, 35 cells and 45° in the scene. Convert the two angles to radians. Vary each turn between half and one and a half times turn so agents do not all turn by the same amount.

    Without another rule, agents collect in a few thick threads and strengthen them further. The sleeping agents also need a way to wake. Track the agent count per cell in taken and use it for two rules:

    • Occasionally put an agent to sleep if the 3 × 3 cells around it contain 30 or more agents.
    • Let a sleeping agent pick an awake agent at random. Wake it two cells ahead of that agent if fewer than 5 agents stand there.

    These rules move agents out of crowds and let the mould grow where its edges have room.

    agents.ts
    const
    const taken: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    taken
    =
    const root: TgpuRoot
    root
    .
    TgpuRoot.createMutable<d.WgslArray<d.Atomic<d.U32>>>(typeSchema: d.WgslArray<d.Atomic<d.U32>>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.Atomic<d.U32>>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.Atomic<d.U32>>>> | undefined): TgpuMutable<d.WgslArray<d.Atomic<d.U32>>> (+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.Atomic<d.U32>>(elementType: d.Atomic<d.U32>, elementCount: number): d.WgslArray<d.Atomic<d.U32>> (+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
    .
    atomic<d.U32>(data: d.U32): d.Atomic<d.U32>
    export atomic

    Marks a concrete integer scalar type schema (u32 or i32) as a WGSL atomic.

    @example const atomicU32 = d.atomic(d.u32); const atomicI32 = d.atomic(d.i32);

    @param ― data Underlying type schema.

    atomic
    (
    import d
    d
    .
    const u32: d.U32
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    ),
    const size: number
    size
    *
    const size: number
    size
    ));
    const
    const neighbours: (position: d.v2f) => number
    neighbours
    = (
    position: d.v2f
    position
    :
    import d
    d
    .
    export v2f

    Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

    v2f
    ) => {
    'use gpu';
    let
    let count: number
    count
    =
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (0);
    for (let
    let y: number
    y
    = -1;
    let y: number
    y
    <= 1;
    let y: number
    y
    ++) {
    for (let
    let x: number
    x
    = -1;
    let x: number
    x
    <= 1;
    let x: number
    x
    ++) {
    let count: number
    count
    +=
    import std
    std
    .
    atomicLoad<d.atomicU32>(a: d.atomicU32): number
    export atomicLoad
    atomicLoad
    (
    const taken: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    taken
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const cellOf: (position: d.v2f) => number
    cellOf
    (
    position: d.v2f
    position
    +
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    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
    (
    let x: number
    x
    ),
    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
    (
    let y: number
    y
    )))]!);
    }
    }
    return
    let count: number
    count
    ;
    };
    // Inside move, replacing the early return for sleeping agents
    if (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    awake: number
    awake
    === 0) {
    if (
    const gid: d.v3u
    gid
    .
    v3u.x: number
    x
    >=
    const physarum: TgpuUniform<d.WgslStruct<{
    alive: d.U32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ alive: U32; }>>.$: {
    alive: number;
    }
    $
    .
    alive: number
    alive
    ) return;
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    seed2: (seed: d.v2f) => void

    Threads do not share the generator's State. As a result, unless you change the seed in each thread, each thread will produce the same sequence. randf.randSeed2 sets the private seed of the thread.

    @param ― seed seed value to set. For the best results, all elements should be in [-1000, 1000] range.

    seed2
    (
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    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
    (
    const gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ),
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    ));
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    heading: number
    heading
    =
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () *
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    * 2;
    const
    const parent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    parent
    =
    const agents: TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>
    agents
    .
    TgpuMutable<WgslArray<WgslStruct<{ position: Vec2f; heading: F32; awake: U32; }>>>.$: {
    position: d.v2f;
    heading: number;
    awake: number;
    }[]
    $
    [
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () *
    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
    (
    const physarum: TgpuUniform<d.WgslStruct<{
    alive: d.U32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ alive: U32; }>>.$: {
    alive: number;
    }
    $
    .
    alive: number
    alive
    ))]!;
    if (
    const parent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    parent
    .
    awake: number
    awake
    === 0) return;
    const
    const heading: number
    heading
    =
    const parent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    parent
    .
    heading: number
    heading
    + (
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () - 0.5) * 2;
    const
    const position: d.v2f
    position
    =
    const parent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    parent
    .
    position: d.v2f
    position
    +
    const toward: (angle: number) => d.v2f
    toward
    (
    const heading: number
    heading
    ) * 2;
    if (
    const neighbours: (position: d.v2f) => number
    neighbours
    (
    const position: d.v2f
    position
    ) <
    const ROOM: number
    ROOM
    &&
    const enter: (position: d.v2f) => boolean
    enter
    (
    const position: d.v2f
    position
    )) {
    const agents: TgpuMutable<d.WgslArray<d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    }>>>
    agents
    .
    TgpuMutable<WgslArray<WgslStruct<{ position: Vec2f; heading: F32; awake: U32; }>>>.$: {
    position: d.v2f;
    heading: number;
    awake: number;
    }[]
    $
    [
    const gid: d.v3u
    gid
    .
    v3u.x: number
    x
    ] =
    const Agent: d.WgslStruct
    (props?: {
    position: d.v2f;
    heading: number;
    awake: number;
    } | undefined) => {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    Agent
    ({
    position: d.v2f
    position
    ,
    heading: number
    heading
    ,
    awake: number
    awake
    : 1 });
    }
    return;
    }
    // After the seed
    if (
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () <
    const THINNING: number
    THINNING
    &&
    const neighbours: (position: d.v2f) => number
    neighbours
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    ) >=
    const CROWD: number
    CROWD
    ) {
    import std
    std
    .
    atomicSub<d.atomicU32>(a: d.atomicU32, value: number): number
    export atomicSub
    atomicSub
    (
    const taken: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    taken
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const cellOf: (position: d.v2f) => number
    cellOf
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    position: d.v2f
    position
    )]!, 1);
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    }
    agent
    .
    awake: number
    awake
    = 0;
    return;
    }
    • taken holds an atomic count per cell. enter(position) adds 1 to the cell at that position. Call it in scatter for each awake agent. When move crosses into another cell, subtract 1 from the old cell with std.atomicSub and add 1 to the destination.
    • A sleeping agent changes its heading each step and uses it to seed random numbers. This lets it try a different parent each step until it wakes.
    • Add alive to Physarum to limit how many agents can wake. Return grow(seconds, doubling) from createAgents. It sets alive to 16 384 doubled every doubling seconds, capped at the agent count. Call agents.grow(sim.time, 2) after each frame’s steps so the mould grows gradually.

    Watch the blob form threads within a second and grow outward as agents wake at its edges. Threads merge and branch as the network changes.

  6. Feed it.

    Real Physarum grows toward food and keeps paths between food sources. Add up to 32 oat flakes in oats.ts, each a small rotated oval. When the flakes change, paint them into a texture the same size as the trail, with a baker, as a function of each cell’s position. Store each cell’s flake coverage, food scent, bright core and faint halo. The scent fades over about a tenth of the dish’s width. Create the oats in the scene with createOats, through own to free the texture.

    oats.ts
    // Inside the baker's function, at cell at, with coordinates from -1 to 1 and width cell
    for (let
    let i: number
    i
    =
    import d
    d
    .
    function u32(v?: number | boolean): number
    export u32

    A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

    Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

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

    @example const value = u32(7); // 7

    @example const value = u32(3.14); // 3

    @example const value = u32(-1); // 4294967295

    @example const value = u32(-3.1); // 0

    u32
    (0);
    let i: number
    i
    <
    const food: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    strength: d.F32;
    reach: d.F32;
    }>>
    food
    .
    TgpuUniform<WgslStruct<{ count: U32; strength: F32; reach: F32; }>>.$: {
    count: number;
    strength: number;
    reach: number;
    }
    $
    .
    count: number
    count
    ;
    let i: number
    i
    ++) {
    const
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    =
    const oats: TgpuReadonly<d.WgslArray<d.WgslStruct<{
    at: d.Vec2f;
    size: d.Vec2f;
    angle: d.F32;
    }>>>
    oats
    .
    TgpuReadonly<WgslArray<WgslStruct<{ at: Vec2f; size: Vec2f; angle: F32; }>>>.$: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }[]
    $
    [
    let i: number
    i
    ]!;
    // Transform the ellipse into a unit circle before measuring distance
    const
    const local: d.v2f
    local
    =
    const rotate: (v: d.v2f, angle: number) => d.v2f
    rotate
    (
    const at: d.v2f
    at
    -
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    .
    at: d.v2f
    at
    , -
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    .
    angle: number
    angle
    ) /
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    .
    size: d.v2f
    size
    ;
    const
    const r: number
    r
    =
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    const local: d.v2f
    local
    );
    const
    const edge: number
    edge
    = (
    const r: number
    r
    - 1) *
    import std
    std
    .
    function min(fst: number, ...rest: number[]): number (+1 overload)
    export min
    min
    (
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    .
    size: d.v2f
    size
    .
    v2f.x: number
    x
    ,
    const oat: {
    at: d.v2f;
    size: d.v2f;
    angle: number;
    }
    oat
    .
    size: d.v2f
    size
    .
    v2f.y: number
    y
    );
    let cover: number
    cover
    =
    import std
    std
    .
    function max(fst: number, ...rest: number[]): number (+1 overload)
    export max
    max
    (
    let cover: number
    cover
    ,
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (
    const cell: number
    cell
    , -
    const cell: number
    cell
    ,
    const edge: number
    edge
    ));
    const
    const reach: number
    reach
    =
    import std
    std
    .
    function max(fst: number, ...rest: number[]): number (+1 overload)
    export max
    max
    (
    const edge: number
    edge
    , 0) /
    const food: TgpuUniform<d.WgslStruct<{
    count: d.U32;
    strength: d.F32;
    reach: d.F32;
    }>>
    food
    .
    TgpuUniform<WgslStruct<{ count: U32; strength: F32; reach: F32; }>>.$: {
    count: number;
    strength: number;
    reach: number;
    }
    $
    .
    reach: number
    reach
    ;
    let smell: number
    smell
    +=
    import std
    std
    .
    function exp(value: number): number (+1 overload)
    export exp
    exp
    (-
    const reach: number
    reach
    *
    const reach: number
    reach
    );
    let core: number
    core
    +=
    import std
    std
    .
    function exp(value: number): number (+1 overload)
    export exp
    exp
    (-
    const r: number
    r
    *
    const r: number
    r
    *
    const CORE: number
    CORE
    );
    let halo: number
    halo
    +=
    import std
    std
    .
    function exp(value: number): number (+1 overload)
    export exp
    exp
    (-
    const r: number
    r
    *
    const r: number
    r
    *
    const HALO: number
    HALO
    );
    }
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    let cover: number
    cover
    ,
    let smell: number
    smell
    ,
    let core: number
    core
    ,
    let halo: number
    halo
    );

    oats.foodAt(uv) returns the food scent and coverage at an agent’s position. It multiplies the scent by strength, which the scene sets to 100. Pass this function to createAgents as its food option. Add a fed field to each agent, with 1 for fully fed and 0 for starved:

    agents.ts
    const
    const sniff: (agent: d.Infer<typeof Agent>, angle: number) => number
    sniff
    = (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    :
    import d
    d
    .
    type Infer<T> = T extends {
    readonly [$repr]: infer TRepr;
    } ? TRepr : T
    export Infer

    Extracts the inferred representation of a resource. For inferring types as seen by the GPU, see

    InferGPU

    @example type A = Infer // => number type B = Infer<WgslArray> // => number[] type C = Infer<Atomic> // => number

    Infer
    <typeof
    const Agent: d.WgslStruct<{
    position: d.Vec2f;
    heading: d.F32;
    awake: d.U32;
    fed: d.F32;
    }>
    Agent
    >,
    angle: number
    angle
    : number) => {
    'use gpu';
    const
    const uv: d.v2f
    uv
    = (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    position: d.v2f
    position
    +
    const toward: (angle: number) => d.v2f
    toward
    (
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    heading: number
    heading
    +
    angle: number
    angle
    ) *
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorDistance: d.F32;
    deposit: d.F32;
    hunger: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorDistance: F32; deposit: F32; hunger: F32; }>>.$: {
    sensorDistance: number;
    deposit: number;
    hunger: number;
    }
    $
    .
    sensorDistance: number
    sensorDistance
    ) /
    const size: number
    size
    ;
    const
    const hunger: number
    hunger
    =
    import std
    std
    .
    function saturate(value: number): number (+1 overload)
    export saturate
    saturate
    (1 -
    agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    * 2);
    return
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    ) +
    const food: (uv: d.v2f) => d.v2f
    food
    (
    const uv: d.v2f
    uv
    ).
    v2f.x: number
    x
    *
    const hunger: number
    hunger
    ;
    };
    // Inside move, after the seed
    const
    const eats: boolean
    eats
    =
    const food: (uv: d.v2f) => d.v2f
    food
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    position: d.v2f
    position
    /
    const size: number
    size
    ).
    v2f.y: number
    y
    > 0.5;
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    =
    import std
    std
    .
    function select(f: number, t: number, cond: boolean): number (+2 overloads)
    export select

    Returns t if cond is true, and f otherwise. Component-wise if cond is a vector.

    @example select(1, 2, false) // returns 1 select(1, 2, true) // returns 2 select(vec2i(1, 2), vec2i(3, 4), true) // returns vec2i(3, 4) select(vec2i(1, 2), vec2i(3, 4), vec2b(false, true)) // returns vec2i(1, 4)

    select
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    -
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorDistance: d.F32;
    deposit: d.F32;
    hunger: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorDistance: F32; deposit: F32; hunger: F32; }>>.$: {
    sensorDistance: number;
    deposit: number;
    hunger: number;
    }
    $
    .
    hunger: number
    hunger
    , 1,
    const eats: boolean
    eats
    );
    const
    const crowded: boolean
    crowded
    =
    const randf: {
    seed: TgpuFn<(seed: d.F32) => d.Void>;
    seed2: TgpuFn<(seed: d.Vec2f) => d.Void>;
    seed3: TgpuFn<(seed: d.Vec3f) => d.Void>;
    seed4: TgpuFn<(seed: d.Vec4f) => d.Void>;
    sample: TgpuFn<() => d.F32>;
    sampleExclusive: TgpuFn<() => d.F32>;
    normal: TgpuFn<(mu: d.F32, sigma: d.F32) => d.F32>;
    ... 10 more ...;
    onUnitSphere: TgpuFn<...>;
    }
    randf
    .
    sample: () => number

    Returns a random f32 value in [0, 1) range.

    sample
    () <
    const THINNING: number
    THINNING
    &&
    const neighbours: (position: d.v2f) => number
    neighbours
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    position: d.v2f
    position
    ) >=
    const CROWD: number
    CROWD
    ;
    if (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    <= 0 ||
    const crowded: boolean
    crowded
    ) {
    import std
    std
    .
    atomicSub<d.atomicU32>(a: d.atomicU32, value: number): number
    export atomicSub
    atomicSub
    (
    const taken: TgpuMutable<d.WgslArray<d.Atomic<d.U32>>>
    taken
    .
    TgpuMutable<WgslArray<Atomic<U32>>>.$: d.atomicU32[]
    $
    [
    const cellOf: (position: d.v2f) => number
    cellOf
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    position: d.v2f
    position
    )]!, 1);
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    awake: number
    awake
    = 0;
    return;
    }
    // Replace the deposit with an amount based on fed
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    deposit: (cell: d.v2i, amount: number) => void

    Atomically adds amount to a cell, allowing concurrent deposits.

    deposit
    (
    import d
    d
    .
    const vec2i: d.Vec2i
    (v: AnyNumericVec2Instance) => d.v2i (+3 overloads)
    vec2i
    (
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    position: d.v2f
    position
    ),
    const physarum: TgpuUniform<d.WgslStruct<{
    sensorDistance: d.F32;
    deposit: d.F32;
    hunger: d.F32;
    }>>
    physarum
    .
    TgpuUniform<WgslStruct<{ sensorDistance: F32; deposit: F32; hunger: F32; }>>.$: {
    sensorDistance: number;
    deposit: number;
    hunger: number;
    }
    $
    .
    deposit: number
    deposit
    * (0.3 + 1.7 *
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    *
    const agent: {
    position: d.v2f;
    heading: number;
    awake: number;
    fed: number;
    }
    agent
    .
    fed: number
    fed
    ));

    Three rules make the mould forage:

    • Hunger. Set fed to 1 when an agent stands on a flake. Otherwise subtract hunger each step. With hunger at STEP / 6, a fully fed agent lasts 6 seconds without food. At 0 it sleeps, as crowded agents do. Branches without food die back, and their agents can wake where others have fed. Give a waking agent its parent’s fed value or a random value, whichever is greater. In scatter, start agents between 0.5 and 1 so they do not all starve at once.
    • Smell. Below half fed, an agent senses food as well as trail. The hungrier it is, the more it follows food. Fed agents follow the network while hungry agents search for food.
    • Deposit. Vary the drop size from 0.3 times deposit when starving to 2 times deposit when fully fed. The strongest trails form at food and along paths from it, creating thick branches.

    Draw the food in veins by adding a bright point and faint halo to the orange trail. oats.glowAt(uv) returns these from the texture’s last two numbers. Call oats.paint(frame.encoder) after the scatter in the scene’s frame. It records work only when the flakes have changed.

    Give the scene the methods and values a page needs. feed(x, y) takes a tap, with x and y from 0 to 1 across and down the canvas, and adds a flake there. A 'use gpu' function also runs as plain TypeScript, so use sheetAt on the CPU to convert the tap to a position on the dish. values holds the numbers a panel changes, and write sends them to the uniforms. regrow clears the trail and scatters the blob again. Keep the time it did so in born, and grow with agents.grow(sim.time - born, 2) from then on.

    scene.ts
    // Inside createScene, replacing the fixed agents.set and trail.set
    const
    const values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    = {
    sensorAngle: number
    sensorAngle
    : 30,
    sensorDistance: number
    sensorDistance
    : 35,
    decay: number
    decay
    : 0.07,
    food: number
    food
    : 100 };
    const
    const write: () => void
    write
    = () => {
    const agents: {
    initAsync: () => Promise<[void, void]>;
    set(values: Omit<d.InferInput<typeof Physarum>, "alive">): void;
    grow(seconds: number, doubling: number): void;
    scatter(encoder: TgpuCommandEncoder): void;
    move(pass: TgpuComputePass): void;
    destroy(): void;
    }
    agents
    .
    function set(values: Omit<d.InferInput<typeof Physarum>, "alive">): void

    Sets the growth limit, sensing, steering and hunger rate. Clamps the count to capacity.

    set
    ({
    count: number
    count
    : 400_000,
    sensorAngle: number
    sensorAngle
    :
    const values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    .
    sensorAngle: number
    sensorAngle
    *
    const DEGREES: number
    DEGREES
    ,
    sensorDistance: number
    sensorDistance
    :
    const values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    .
    sensorDistance: number
    sensorDistance
    ,
    turn: number
    turn
    : 45 *
    const DEGREES: number
    DEGREES
    ,
    speed: number
    speed
    : 1.2,
    deposit: number
    deposit
    : 0.5,
    hunger: number
    hunger
    :
    const STEP: number
    STEP
    / 6,
    });
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    set: (values: d.InferInput<typeof Spread>) => void
    set
    ({
    keep: number
    keep
    : 1 -
    const values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    .
    decay: number
    decay
    ,
    blur: number
    blur
    : 1 });
    const oats: {
    foodAt: (uv: d.v2f) => d.v2f;
    glowAt: (uv: d.v2f) => d.v2f;
    initAsync: () => Promise<void>;
    set(strength: number): void;
    drop(at: d.v2f): void;
    paint(encoder: TgpuCommandEncoder): void;
    destroy(): void;
    }
    oats
    .
    function set(strength: number): void
    set
    (
    const values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    .
    food: number
    food
    );
    };
    const write: () => void
    write
    ();
    // Beside frame and destroy
    return {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    ,
    write: () => void
    write
    ,
    function regrow(): void
    regrow
    () {
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    function clear(): void
    clear
    ();
    let scattering: boolean
    scattering
    = true;
    let born: number
    born
    =
    const sim: clock.FixedStep
    sim
    .
    FixedStep.time: number

    Reports the simulated seconds: the sum of every step advance has returned.

    time
    ;
    },
    function feed(x: number, y: number): void
    feed
    (
    x: number
    x
    : number,
    y: number
    y
    : number) {
    const {
    const width: number

    Reports the drawing-buffer size in pixels.

    width
    ,
    const height: number
    height
    } =
    const surface: Surface
    surface
    ;
    const oats: {
    foodAt: (uv: d.v2f) => d.v2f;
    glowAt: (uv: d.v2f) => d.v2f;
    initAsync: () => Promise<void>;
    set(strength: number): void;
    drop(at: d.v2f): void;
    paint(encoder: TgpuCommandEncoder): void;
    destroy(): void;
    }
    oats
    .
    function drop(at: d.v2f): void

    Adds a flake at sheet coordinates at, from -1 to 1. Replaces the oldest flake at capacity.

    drop
    (
    function sheetAt(pixel: d.v2f, size: d.v2f): d.v2f

    Sheet coordinates have (0, 0) at the picture centre, with y pointing down. One unit is half the picture's longer side. The mould grid covers -1 to 1 on both axes, so on a wide picture its top and bottom run past the edges.

    sheetAt
    (
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    x: number
    x
    *
    const width: number

    Reports the drawing-buffer size in pixels.

    width
    ,
    y: number
    y
    *
    const height: number
    height
    ),
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    const width: number

    Reports the drawing-buffer size in pixels.

    width
    ,
    const height: number
    height
    )));
    },
    };

    index.ts connects them to the page in attach, which runs once the scene is built. web.pointer calls down with the tap’s position. A panel from lil-gui gets a slider for each value and a button for regrow. Add lil-gui to the project:

    npm i lil-gui

    The host disposes what attach returns with the scene, so return a disposable that removes both:

    index.ts
    import * as
    import web
    web
    from '@typegpu/kit/web';
    import
    import GUI
    GUI
    from 'lil-gui';
    import {
    function createScene(root: TgpuRoot, surface: Surface): Promise<{
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }>
    createScene
    } from './scene.ts';
    const
    const canvas: HTMLCanvasElement
    canvas
    =
    var document: Document

    window.document returns a reference to the document contained in the window.

    MDN Reference

    document
    .
    ParentNode.querySelector<"canvas">(selectors: "canvas"): HTMLCanvasElement | null (+4 overloads)

    Returns the first element that is a descendant of node that matches selectors.

    MDN Reference

    querySelector
    ('canvas')!;
    import web
    web
    .
    function host<{
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }>(createScene: web.CreateScene<Record<never, never>, {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }>, hosting: web.HostOptions<{
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }>): web.Hosted<...> (+1 overload)

    Runs a scene on a canvas with a root, surface, frame loop and animation. destroy tears them down in order. Use send to control the scene or onFrame to read it. If scene options are optional, call host(createScene, { canvas }). Throws if both device and root are supplied.

    @param ― createScene the scene file's createScene

    @param ― options the scene's third argument

    @param ― hosting the canvas, the device or root, and what the page runs beside the scene

    @returns ― a hosted scene you send closures to and destroy

    host
    (
    function createScene(root: TgpuRoot, surface: Surface): Promise<{
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }>
    createScene
    , {
    HostOptions<S extends Scene>.canvas: HTMLCanvasElement

    Supplies the canvas for the scene.

    canvas
    ,
    HostOptions<{ values: { sensorAngle: number; sensorDistance: number; decay: number; food: number; }; write(): void; regrow(): void; feed(x: number, y: number): void; frame(frame: Frame): void; destroy(): void; }, Surface>.attach?: ((stage: Stage<{
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }, web.Surface>) => Disposable | void) | undefined

    Connects page code, such as orbit controls, to the stage after scene creation. The host disposes its return value with the scene. Default none.

    attach
    ({
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    }) {
    const
    const pointer: web.Pointer
    pointer
    =
    import web
    web
    .
    function pointer(element: HTMLElement, options?: web.PointerOptions): web.Pointer

    Follows the mouse, touch or pen on an element in the coordinates rayAt takes, 0 to 1 across the element with +y down. Read the position each frame or take each move, press, release and zoom. A button pressed on the element keeps the pointer until it comes up, so a drag continues outside and its positions run past 0 and 1.

    @param ― element what the pointer moves over, usually the canvas

    @returns ― the pointer, which you destroy to remove the listeners

    pointer
    (
    const canvas: HTMLCanvasElement
    canvas
    , {
    PointerOptions.down?: web.PointerHandler | undefined

    Runs as a button goes down on the element. Read the button from event.button. Default none.

    down
    : (
    x: number
    x
    ,
    y: number
    y
    ) =>
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    function feed(x: number, y: number): void
    feed
    (
    x: number
    x
    ,
    y: number
    y
    ) });
    const
    const gui: any
    gui
    = new
    import GUI
    GUI
    ({
    title: string
    title
    : 'slime mould' });
    const gui: any
    gui
    .
    any
    add
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    , 'sensorAngle', 5, 90, 1).
    any
    name
    ('sensor angle (degrees)');
    const gui: any
    gui
    .
    any
    add
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    , 'sensorDistance', 2, 40, 0.5).
    any
    name
    ('sensor distance (cells)');
    const gui: any
    gui
    .
    any
    add
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    , 'decay', 0, 0.5, 0.005);
    const gui: any
    gui
    .
    any
    add
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    }
    values
    , 'food', 0, 200, 1);
    const gui: any
    gui
    .
    any
    onChange
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    .
    function write(): void
    write
    );
    const gui: any
    gui
    .
    any
    add
    (
    scene: {
    values: {
    sensorAngle: number;
    sensorDistance: number;
    decay: number;
    food: number;
    };
    write(): void;
    regrow(): void;
    feed(x: number, y: number): void;
    frame(frame: Frame): void;
    destroy(): void;
    }
    scene
    , 'regrow');
    return {
    [
    var Symbol: SymbolConstructor
    Symbol
    .
    SymbolConstructor.dispose: typeof Symbol.dispose

    A method that is used to release resources held by an object. Called by the semantics of the using statement.

    dispose
    ]() {
    const pointer: web.Pointer
    pointer
    .
    Pointer.destroy(): void

    Removes the listeners and restores the element's touch-action. Also [Symbol.dispose].

    destroy
    ();
    const gui: any
    gui
    .
    any
    destroy
    ();
    },
    };
    },
    });

    Watch the mould reach for the flakes and keep the threads between them. The centre empties as the food sources connect and unfed threads die back.

  7. Turn density into light.

    A thread made by one agent and a hub of hundreds can differ a hundredfold in density. Mapping density directly to brightness cannot show both. saturate(density) makes dense threads the same flat orange. Dividing density enough to distinguish the hubs makes the fine threads too dark.

    Use log(1 + density × GAIN) to brighten faint trails while compressing dense ones. Divide by the same curve at FULL, or 18, to get a tone from 0 to 1. Use that tone to select a colour along a ramp. It runs from crimson for faint threads through red and orange to gold at the hubs.

    Raise GAIN to brighten faint threads while compressing dense hubs. The strips compare the same cross-section using flat amber or a curve and ramp that distinguish threads by brightness and colour.
    veins.ts
    const
    const CRIMSON: d.v3f
    CRIMSON
    =
    function hex(rgb: number): d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0x3a0610);
    const
    const RED: d.v3f
    RED
    =
    function hex(rgb: number): d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0xb0201a);
    const
    const GOLD: d.v3f
    GOLD
    =
    function hex(rgb: number): d.v3f

    Converts an sRGB hex colour, such as 0xc0301c, to linear light.

    hex
    (0xffe0a0);
    const
    const FULL: 18
    FULL
    = 18;
    const
    const GAIN: 0.6
    GAIN
    = 0.6;
    const
    const ramp: (tone: number) => d.v3f
    ramp
    = (
    tone: number
    tone
    : number) => {
    'use gpu';
    let
    let color: d.v3f
    color
    =
    const CRIMSON: d.v3f
    CRIMSON
    *
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0, 0.25,
    tone: number
    tone
    );
    let color: d.v3f
    color
    =
    import std
    std
    .
    mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
    export mix
    mix
    (
    let color: d.v3f
    color
    ,
    const RED: d.v3f
    RED
    ,
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0.2, 0.5,
    tone: number
    tone
    ));
    let color: d.v3f
    color
    =
    import std
    std
    .
    mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
    export mix
    mix
    (
    let color: d.v3f
    color
    ,
    const ORANGE: d.v3f
    ORANGE
    ,
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0.45, 0.8,
    tone: number
    tone
    ));
    return
    import std
    std
    .
    mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
    export mix
    mix
    (
    let color: d.v3f
    color
    ,
    const GOLD: d.v3f
    GOLD
    ,
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0.75, 1,
    tone: number
    tone
    ));
    };
    // Inside veins, replacing the plain orange
    const
    const reach: number
    reach
    = 0.75 /
    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
    (
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    size: number

    Grid width and height in cells.

    size
    );
    const
    const density: number
    density
    =
    (
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    ) * 2 +
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    +
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    const reach: number
    reach
    , 0)) +
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    -
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    const reach: number
    reach
    , 0)) +
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    +
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (0,
    const reach: number
    reach
    )) +
    const trail: {
    size: number;
    deposit: (cell: d.v2i, amount: number) => void;
    at: (uv: d.v2f) => number;
    initAsync: () => Promise<[void, void]>;
    set: (values: d.InferInput<typeof Spread>) => void;
    spread(pass: TgpuComputePass): void;
    clear(): void;
    destroy(): void;
    }
    trail
    .
    at: (uv: d.v2f) => number

    Samples the trail at grid coordinates uv from 0 to 1, with interpolation between cells.

    at
    (
    const uv: d.v2f
    uv
    -
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (0,
    const reach: number
    reach
    ))) /
    6;
    const
    const tone: number
    tone
    =
    import std
    std
    .
    function log(value: number): number (+1 overload)
    export log
    log
    (1 +
    const density: number
    density
    *
    const GAIN: 0.6
    GAIN
    ) /
    import std
    std
    .
    function log(value: number): number (+1 overload)
    export log
    log
    (1 +
    const FULL: 18
    FULL
    *
    const GAIN: 0.6
    GAIN
    );
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    function linearToSrgb(linear: d.v3f): d.v3f
    linearToSrgb
    (
    const ramp: (tone: number) => d.v3f
    ramp
    (
    const tone: number
    tone
    ) *
    const look: TgpuUniform<d.WgslStruct<{
    light: d.F32;
    pulse: d.F32;
    }>>
    look
    .
    TgpuUniform<WgslStruct<{ light: F32; pulse: F32; }>>.$: {
    light: number;
    pulse: number;
    }
    $
    .
    light: number
    light
    +
    const food: d.v3f
    food
    ), 0);

    This curve would make individual drops look grainy. Smooth the density by averaging the trail under the pixel with four surrounding points. Store the brightness, light, in a look uniform. Add light: 1 to values, write it in write with veins.look.write, and give it a slider from 0 to 2 in index.ts. Mix the ramp’s colours in linear light and encode the result with linearToSrgb until the next step.

    slime-mouldA slime mould grows glowing threads across dark agar and connects the food it finds.
    Change the brightness to see fine crimson threads, red and orange branches, and gold paths to the food. Pulse and glow are off for this step.
  8. Pulse, and glow.

    In living Physarum, cytoplasm flows back and forth through the veins every minute or two. Represent that motion with a brightness wave that travels outward along the thick branches every 3.5 seconds:

    • Decrease its phase with distance from the centre so the wave travels outward.
    • Vary the phase with angle to bend each wavefront slightly.
    • Use smoothstep to keep the pulse in dense branches, leaving fine threads still.
    veins.ts
    const
    const PERIOD: 3.5
    PERIOD
    = 3.5;
    const
    const WAVES: 1.6
    WAVES
    = 1.6;
    // Inside veins, after calculating tone
    const
    const swirl: number
    swirl
    = 0.15 *
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    import std
    std
    .
    function atan2(y: number, x: number): number (+1 overload)
    export atan2
    atan2
    (
    const at: d.v2f
    at
    .
    v2f.y: number
    y
    ,
    const at: d.v2f
    at
    .
    v2f.x: number
    x
    ) * 3 +
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    const at: d.v2f
    at
    ) * 5);
    const
    const phase: number
    phase
    =
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    .
    TgpuUniform<WgslStruct<{ size: Vec2f; time: F32; }>>.$: {
    size: d.v2f;
    time: number;
    }
    $
    .
    time: number
    time
    /
    const PERIOD: 3.5
    PERIOD
    -
    import std
    std
    .
    length<d.v2f>(value: d.v2f): number (+1 overload)
    export length
    length
    (
    const at: d.v2f
    at
    ) *
    const WAVES: 1.6
    WAVES
    +
    const swirl: number
    swirl
    ;
    const
    const wave: number
    wave
    =
    import std
    std
    .
    function sin(value: number): number (+1 overload)
    export sin
    sin
    (
    const phase: number
    phase
    *
    var Math: Math

    An intrinsic object that provides basic mathematics functionality and constants.

    Math
    .
    Math.PI: number

    Pi. This is the ratio of the circumference of a circle to its diameter.

    PI
    * 2) *
    import std
    std
    .
    function smoothstep(edge0: number, edge1: number, x: number): number (+1 overload)
    export smoothstep
    smoothstep
    (0.55, 0.9,
    const tone: number
    tone
    );
    const
    const pulse: number
    pulse
    = 1 + 0.4 *
    const look: TgpuUniform<d.WgslStruct<{
    light: d.F32;
    pulse: d.F32;
    }>>
    look
    .
    TgpuUniform<WgslStruct<{ light: F32; pulse: F32; }>>.$: {
    light: number;
    pulse: number;
    }
    $
    .
    pulse: number
    pulse
    *
    const wave: number
    wave
    ;
    return
    import d
    d
    .
    const vec4f: d.Vec4f
    (v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
    vec4f
    (
    const ramp: (tone: number) => d.v3f
    ramp
    (
    const tone: number
    tone
    ) * (
    const look: TgpuUniform<d.WgslStruct<{
    light: d.F32;
    pulse: d.F32;
    }>>
    look
    .
    TgpuUniform<WgslStruct<{ light: F32; pulse: F32; }>>.$: {
    light: number;
    pulse: number;
    }
    $
    .
    light: number
    light
    *
    const pulse: number
    pulse
    ) +
    const food: d.v3f
    food
    , 0);

    at is sheetAt(pixel.xy, world.$.size). world.$.time holds elapsed simulation seconds, written by the scene as in step 2. Add pulse to Look and to values, with a slider from 0 to 2.

    Let the brightest hubs exceed the canvas’s range. Change the ramp’s last line to raise the densest trail to 2.2 times gold:

    return color * (1 + 1.2 * std.smoothstep(0.85, 1.2, tone));

    The canvas holds colour values from 0 to 1 and clips anything brighter. Draw into an hdr render target with the rgba16float format to keep values above 1. Finish the frame with two passes from @typegpu/kit:

    • Bloom blurs what is brighter than its threshold into a wide, soft glow, as a camera lens does.
    • The final pass adds bloom, maps brightness into the canvas’s range of 0 to 1, darkens the corners and encodes sRGB. Return linear colours from the agar and veins fragment functions, without linearToSrgb.

    In the figure, each disc is twice as bright as the previous one. Change the threshold to choose which discs glow.

    Raise the threshold to stop dimmer discs glowing, or reduce levels to narrow the glow. Each disc is twice as bright as the previous one.
    scene.ts
    // Inside createScene, give agar and veins the HDR format
    const
    const HDR: "rgba16float"
    HDR
    = 'rgba16float';
    const
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    =
    const own: Owner
    <targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>>(item: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>) => targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>

    Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

    own
    (
    import targets
    targets
    .
    function create<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>(root: TgpuRoot, source: targets.SizeSource, specs: {
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }, options?: targets.Options): targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>

    Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

    @param ― source the size it follows, such as a surface

    @param ― specs a format and options per target, by name

    @param ― options.resolution the fraction of each side drawn, which changes without allocating

    @returns ― a set of targets you fit every frame and destroy

    create
    (
    const root: TgpuRoot
    root
    ,
    const surface: targets.Surface
    surface
    , {
    hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    }
    hdr
    : {
    format: "rgba16float"
    format
    :
    const HDR: "rgba16float"
    HDR
    ,
    sampled: true
    sampled
    : true } }));
    const
    const hdr: () => targets.Texture<{
    readonly format: "rgba16float";
    readonly sampled: true;
    }>
    hdr
    = () =>
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    .
    Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
    readonly hdr: targets.Texture<{
    readonly format: "rgba16float";
    readonly sampled: true;
    }>;
    }

    Provides the current textures. A size change replaces them. Throws after destroy.

    textures
    .
    hdr: targets.Texture<{
    readonly format: "rgba16float";
    readonly sampled: true;
    }>
    hdr
    ;
    const
    const glow: bloom.Bloom
    glow
    =
    const own: Owner
    <bloom.Bloom>(item: bloom.Bloom) => bloom.Bloom

    Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

    own
    (
    import bloom
    bloom
    .
    function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

    Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

    @allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

    @owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

    @records ― record after whatever wrote the source; then output holds the glow.

    @param ― options.source the HDR picture, read at every record

    @param ― options.region the part of the source that holds the picture, read at every record

    @returns ― a bloom you record every frame and destroy

    create
    (
    const root: TgpuRoot
    root
    , {
    Options.source: (() => finish.FloatTexture) | finish.Rendered

    Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

    source
    :
    const hdr: () => targets.Texture<{
    readonly format: "rgba16float";
    readonly sampled: true;
    }>
    hdr
    ,
    Options.settings?: Partial<d.InferInput<d.WgslStruct<{
    threshold: d.F32;
    knee: d.F32;
    spread: d.F32;
    }>>> | undefined

    Sets initial values over defaults. Default defaults, with threshold 1, knee 0.5 and spread 1.

    settings
    : {
    threshold: number
    threshold
    : 1,
    knee: number
    knee
    : 0.5 } }));
    const
    const final: finish.Finish
    final
    =
    const own: Owner
    <finish.Finish>(item: finish.Finish) => finish.Finish

    Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

    own
    (
    import finish
    finish
    .
    function create(root: TgpuRoot, options: finish.Options): finish.Finish

    Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

    @allocates ― on resize: a bind group for a new source or glow

    @owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

    @records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

    @param ― options.source the picture, rendered or a texture, read at every draw

    @param ― options.region the part of the source that holds the picture, read at every draw

    @param ― options.glow a bloom, or a glow of your own, added before tone mapping

    @param ― options.exposure an adapted exposure, read on the GPU

    @returns ― a final pass you draw every frame and destroy

    create
    (
    const root: TgpuRoot
    root
    , {
    Options.format: GPUTextureFormat

    Sets the output format, such as the canvas format.

    format
    :
    const surface: targets.Surface
    surface
    .
    Surface.format: GPUTextureFormat
    format
    ,
    Options.source: finish.Rendered | (() => finish.FloatTexture)

    Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

    source
    :
    const hdr: () => targets.Texture<{
    readonly format: "rgba16float";
    readonly sampled: true;
    }>
    hdr
    ,
    Options.glow?: finish.Rendered | undefined

    Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

    glow
    ,
    Options.settings?: Partial<d.InferInput<d.WgslStruct<{
    exposure: d.F32;
    vignette: d.F32;
    dither: d.F32;
    glowIntensity: d.F32;
    }>>> | undefined

    Sets initial values over defaults. Default defaults, with exposure 1, vignette 0.35, dither 1 and glowIntensity 0.12.

    settings
    : {
    glowIntensity: number
    glowIntensity
    : 0.2,
    vignette: number
    vignette
    : 0.3 },
    }),
    );
    await
    import pipelines
    pipelines
    .
    function compile(items: readonly pipelines.Warmable[], options?: pipelines.CompileOptions): Promise<void>

    Starts compiling every item, yielding to the event loop past the time budget so the page's other work, such as a loading animation, keeps running. Use before the frame loop starts. Waits for every item to finish, then rejects if any failed.

    @param ― items pipelines, and modules that compile their own

    @returns ― a promise that resolves once every item has compiled

    compile
    ([
    const agar: pipelines.Warmable
    agar
    ,
    const trail: pipelines.Warmable
    trail
    ,
    const oats: pipelines.Warmable
    oats
    ,
    const agents: pipelines.Warmable
    agents
    ,
    const veins: pipelines.Warmable
    veins
    ,
    const glow: bloom.Bloom
    glow
    ,
    const final: finish.Finish
    final
    ]);
    // Inside frame
    if (
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    .
    Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.fit(): boolean

    Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

    fit
    ())
    const world: TgpuUniform<d.WgslStruct<{
    size: d.Vec2f;
    time: d.F32;
    }>>
    world
    .
    TgpuBufferBindingBase<WgslStruct<{ size: Vec2f; time: F32; }>>.patch(data: {
    size?: d.InferInput<d.Vec2f> | undefined;
    time?: number | undefined;
    } | undefined): void
    patch
    ({
    size?: d.InferInput<d.Vec2f> | undefined
    size
    :
    import d
    d
    .
    const vec2f: d.Vec2f
    (x: number, y: number) => d.v2f (+3 overloads)
    vec2f
    (
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    .
    Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.width: number

    Reports the source size rounded up to whole pixels, before applying each target's scale.

    width
    ,
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    .
    Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.height: number
    height
    ) });
    // Record the simulation steps here
    const
    const pass: TgpuRenderPass
    pass
    =
    const frame: 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.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

    Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

    GPUTextureView

    s.

    beginRenderPass
    ({
    TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
    colorAttachments
    : {
    ColorAttachment.view: TgpuTextureRenderView | (ColorTextureConstraint & RenderFlag) | GPUTextureView | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | GPUCanvasContext

    A

    GPUTextureView

    describing the texture subresource that will be output to for this color attachment.

    view
    :
    const screen: targets.Targets<{
    readonly hdr: {
    readonly format: "rgba16float";
    readonly sampled: true;
    };
    }>
    screen
    .
    Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.views: {
    readonly hdr: GPUTextureView;
    }

    Provides attachment views of mip 0, created once per rebuild. Throws after destroy.

    views
    .
    hdr: GPUTextureView
    hdr
    } });
    // Draw the agar and veins here
    const pass: TgpuRenderPass
    pass
    .
    TgpuRenderPass.end(): void

    Completes the recording of this render pass

    end
    ();
    const glow: bloom.Bloom
    glow
    .
    Bloom.record(target: Frame | TgpuCommandEncoder, options?: Timed): void

    Records bloom passes after the source is written. Optional timer writes measure the passes together.

    record
    (
    const frame: Frame
    frame
    );
    const final: finish.Finish
    final
    .
    Finish.record(target: Frame | TgpuCommandEncoder, view: GPUTextureView, options?: finish.RecordOptions): void

    Opens a pass on view, such as surface.view(frame), draws into it and ends it. A viewport draws the picture into that rectangle and clears the rest, transparent with alpha: 'premultiplied' and black otherwise. Optional timer writes measure the pass.

    record
    (
    const frame: Frame
    frame
    ,
    const surface: targets.Surface
    surface
    .
    Surface.view(frame: Frame): GPUTextureView

    Returns a view of the current texture, created once per texture. React Native requires the frame to present after submission.

    view
    (
    const frame: Frame
    frame
    ));

    Draw only the final pass into the canvas. final.record opens a pass on the canvas, draws the final pass and ends it. When the canvas resizes, screen.fit() creates matching textures and returns true. Write that size to world. Use pipelines.compile to compile all pipelines together instead of awaiting each initAsync. For a glow slider, add glow: 1 to values and patch final.settings with glowIntensity: 0.2 * values.glow in write.

    slime-mouldA slime mould grows glowing threads across dark agar and connects the food it finds.
    Tap to add food, widen the sensor angle for a looser network, or increase the sensor distance for longer, straighter threads. The mould grows toward food; regrow to start again.
  • Directorysrc/
    • index.ts web.host, the tap and the panel
    • scene.ts createScene(root, surface) with the uniform, target, modules, values and frame
    • agar.ts the dish, sheetAt and hex
    • agents.ts the agents’ buffer, the crowd counts and the two kernels
    • trail.ts the trail’s two textures, the drops and the blur
    • oats.ts the flakes, their texture and the baker that paints it
    • veins.ts the tone curve, the ramp, the pulse and the draw

The example’s files match yours, except that its scene.ts also describes its panel and taps for the example page, which hosts it without an index.ts.

Things to try in the example:

  • Add a second species with its own buffer. Let its agents sense their own trail and avoid the first species’ trail.
  • Make food run out by lowering a flake’s strength while agents eat on it. The mould should move on.
  • Raise the count toward its million on a fast GPU, or lower it on a phone, and see how the network’s grain changes.

Domino run builds a 3D scene with 1 474 dominoes that fall on the GPU from the one you tap. Write a module builds a module of your own and tests it.