Jelly world

A port of TypeGPU's Jelly Slider, after Voicu Apostol's design, where a jelly bends on its slotted tray until a critter pulls the lever and a candy lawn grows around it with seventeen more. Drag the jelly to stretch it, carry a critter or pull the lever yourself, and turn the view with the secondary button or two fingers and zoom with the wheel or a pinch.

Files, in reading order

  1. scene.tsCreates the scene: its resources, its settings and what each frame records.
  2. slider.tsA Verlet spine on the CPU, drawn as quadratic Bézier segments that a kernel bakes into a distance field the march samples.
  3. jelly.tsThe jelly, its slotted floor and the label under it, marched per pixel.
  4. label.tsThe slider's value as text, drawn into a texture only when it changes.
  5. land.tsThe jelly world on the tray: finding and shading its props and critters, and the lawn they stand on, as the jelly's scenery.
  6. world.tsThe jelly world's trees and houses, grown by a front sweeping along x.
  7. critters.tsCritters walking the jelly world, sliding down the jelly and carried by the pointer.
  8. camera.tsThe orbit camera over the tray.
  9. tray.ts
  10. backdrop.ts
  11. candy.ts
  12. body.tsCritter i standing on the tray: the herald at its spot, the others anywhere off the slot.
  13. decals.ts
  14. figures.ts
  15. props.ts
  16. moves.tsClimbs the jelly or slides down it, carried as it moves, until it goes off an end, falls off where it overhangs or is thrown.
  17. poses.tsSteps a foot towards where it belongs under the hips once the other is planted.
  18. spine.ts
  19. walking.tsOne step of a critter on the ground.
  20. steering.tsThe way towards the goal: past the slot's nearer end, halfway in towards it, when the goal lies across it; out to the goal's side first when in line with the slot past an end and the goal lies along it; and back from the front.
  21. assets.ts

Kit modules baker, cameras, clock, dynamicResolution, finish, font, history, owner, passes, pipelines, profiler, taa, table, targets, text. Also @typegpu/noise, @typegpu/sdf, typegpu, wgpu-matrix.

scene.ts

import {  cameras,  clock,  owner,  pipelines,  profiler,  dynamicResolution,  targets,  type Frame,  type Surface,  font,  finish,  taa,  type Baked,} from '@typegpu/kit';import { d, std, type TgpuRoot } from 'typegpu';import type { Pointing } from '../contract.ts';import type { Settings } from '../settings.ts';import { createSlider, RANGE, STEP } from './slider.ts';import { createJelly, Look, Moment, MOTION } from './jelly.ts';import { createLabel } from './label.ts';import { createLand } from './land.ts';import { createWorld } from './world.ts';import { createCritters } from './critters.ts';import { createCamera } from './camera.ts';import type { V3 } from './tray.ts';export interface SceneOptions {  /** The font of the value under the jelly, as `font.create` takes it. */  readonly font: Baked;}/** Jelly colours in display values. */const FLAVOURS = {  orange: [1, 0.45, 0.075],  lime: [0.55, 0.9, 0.12],  berry: [0.9, 0.16, 0.42],  blue: [0.15, 0.55, 1],} as const;type Flavour = keyof typeof FLAVOURS;/** How fast the jelly's colour eases to a new flavour, as a rate per second. */const FLAVOUR_EASE = 8;/** World x of the jelly world's front before and after the lever reveals it, past the view. */const FRONT = [-2.7, 2.7] as const;/** How fast the front sweeps to where it is going, as a rate per second. */const SWEEP = 1.4;/** Where the lever sends the jelly's end, as progress: from the upper half, then from the lower. */const SENDS = [0.05, 1] as const;/** The light's direction at either end of the `light` setting. */const LIGHT = [d.vec3f(0.19, -0.24, 0.75), d.vec3f(-0.5, -0.14, -0.8)] as const;/** * The share of the display's frame interval the frame's GPU time keeps under while `auto` sets * the detail, and the detail's range. */const DETAIL = { share: 0.75, range: [0.3, 1] } as const;/** How near a ray must pass the jelly's spine for a stroke to drag the jelly. */const REACH = 0.12;/** Pointer rays nearer level than this find no point on the planes below. */const LEVEL = { grazing: 1e-4 };/** Height over the tray that critters look at under the pointer. */const WATCH_HEIGHT = 0.08;/** Height of the plane the pointer drags the jelly's end along. */const DRAG_PLANE = 0.05;/** How fast the jelly's end eases to where it is going, as a rate per second. */const FOLLOW = 5;/** The idle sway of the jelly's end: its rate in radians a second, and its ends as progress. */const SWAY = { speed: 0.6, low: 0.2, high: 0.95 };/** Where the jelly's end sits in world x at `progress` from 0 to 1. */const endAt = (progress: number) => RANGE[0] + (RANGE[1] - RANGE[0]) * progress;/** Where the idle sway puts the jelly's end, `swing` from 0 to 1 between its ends. */const swayed = (swing: number) => endAt(SWAY.low + (SWAY.high - SWAY.low) * swing);/** A jelly slider marched per pixel over a tray, whose lever grows a jelly world of critters. */export async function createScene(root: TgpuRoot, surface: Surface, options: SceneOptions) {  const own = owner();  const gpu = own(profiler.create(root));  const marchTime = gpu.timer('march');  const outputTime = gpu.timer('output');  const resolution = dynamicResolution.create(gpu.frame, {    budget: (interval) => DETAIL.share * interval,    range: DETAIL.range,  });  const values = {    /** Where the jelly's end sits from 0 to 1. Setting it stops the idle sway. */    progress: 1,    flavour: 'orange' as Flavour,    /** Swings the light from behind the jelly to in front of it. */    light: 0,    /** Blurs the floor seen through the jelly. */    frosted: false,    /** Fraction of each side of the screen marched, where `auto` starts. */    detail: 1,    /**     * Sets `detail` to keep the frame's GPU time under three quarters of the display's frame     * interval. Needs `timestamp-query`.     */    auto: gpu.available,  };  const camera = createCamera(root, surface);  const view = camera.view;  const screen = own(    targets.create(      root,      surface,      {        color: { format: 'rgba16float', sampled: true },        motion: { format: MOTION, sampled: true },        depth: { format: 'depth24plus', sampled: true },      },      { resolution: () => resolution.scale },    ),  );  const region = screen.regions.color;  const aa = own(    taa.create(root, {      source: () => screen.textures.color,      motion: () => screen.textures.motion,      depth: () => screen.textures.depth,      region,      camera: view,    }),  );  const final = own(    finish.create(root, {      format: surface.format,      source: aa,      tone: (color) => {        'use gpu';        return std.tanh(color * 1.3);      },      encode: false,      settings: { vignette: 0 },    }),  );  const sim = clock.fixedStep({ step: STEP });  /** Where the jelly's end is going and where it is, starting in the middle of the sway. */  let goal = swayed(0.5);  let at = goal;  const slider = own(createSlider(root, at));  const sans = own(font.create(root, options.font));  const label = own(createLabel(root, sans));  const look = root.createUniform(Look);  const world = own(createWorld(root));  const now = root.createUniform(Moment);  const critters = own(createCritters(root, world));  const land = own(createLand(root, { camera: view, world, critters, now, look }));  const jelly = own(    createJelly(root, {      camera: view,      look,      field: slider.field,      end: slider.end,      moved: slider.moved,      label: label.view,      offset: aa.offset,      now,      scenery: land.scenery,    }),  );  await pipelines.compile([slider, land, jelly, label, aa, final]);  /** The jelly's colour, easing to the flavour's. */  const colour = d.vec4f(...FLAVOURS.orange, 1);  const moment = { front: 0, time: 0, seed: d.vec2f() };  const ray: { origin: V3; direction: V3 } = { origin: [0, 0, 0], direction: [0, 0, 1] };  const grip: V3 = [0, 0, 0];  const eye: V3 = [0, 0, 0];  const point: V3 = [0, 0, 0];  /** Whether the jelly's end has been set by hand, which stops the idle sway. */  let touched = false;  let time = 0;  /** Whether the spine's field is still to be baked for the first frame. */  let unbaked = true;  /** Whether the floor's baked shadows match the light. */  let lit = false;  let revealed = false;  /** Where the jelly world's front is in world x, sweeping towards `FRONT`'s end. */  let front: number = FRONT[0];  /** Whether the stroke started away from the jelly, the lever and the critters, which it ignores. */  let idle = false;  const settings: Settings<typeof values> = {    values,    knobs: {      progress: { min: 0, max: 1, step: 0.01 },      flavour: { options: Object.keys(FLAVOURS) as Flavour[] },      light: { min: 0, max: 1, step: 0.01 },      frosted: {},      detail: { min: DETAIL.range[0], max: DETAIL.range[1], step: 0.05, live: true },      auto: { live: true, ...(gpu.available ? {} : { disabled: 'needs timestamp-query' }) },    },    write(key) {      // Moving the detail by hand turns `auto` off; either restarts its timing from the detail      if (key === 'detail') values.auto = false;      if (key === undefined || key === 'detail' || key === 'auto') {        resolution.scale = values.detail;      }      if (key === undefined || key === 'light') {        look.patch({ light: std.normalize(std.mix(LIGHT[0], LIGHT[1], values.light)) });        lit = false;      }      if (key === undefined || key === 'frosted') look.patch({ blur: values.frosted ? 1 : 0 });      if (key === undefined) look.patch({ jelly: colour });      if (key === 'progress') {        touched = true;        goal = endAt(values.progress);      }    },  };  const pointing: Pointing = {    move(x, y) {      view.rayAt(x, y, ray);      if (cameras.hitHeight(ray, WATCH_HEIGHT, point, LEVEL) !== undefined) critters.watch(point);    },    leave() {      critters.watch(undefined);    },    drag(x, y, start) {      if (start) idle = false;      if (idle) return;      view.rayAt(x, y, ray);      const { origin, direction } = ray;      if (start && world.grab(origin, direction)) return;      if (world.holding) {        world.pull(origin, direction);        return;      }      if (start && critters.pick(origin, direction)) return;      if (critters.holding) {        critters.hold(origin, direction);        return;      }      if (start && !slider.passes(origin, direction, REACH)) {        idle = true;        return;      }      if (cameras.hitHeight(ray, DRAG_PLANE, grip, LEVEL) === undefined) return;      touched = true;      goal = grip[0];    },    /** Lets go of the lever or a critter the pointer carries. */    end() {      world.release();      critters.drop();    },  };  /** Sets the detail from GPU time while `auto`. */  const pace = (frame: Frame) => {    if (!values.auto) return;    resolution.update(frame);    values.detail = resolution.scale;  };  /** Eases the jelly's colour towards its flavour's, writing it while it moves. */  const easeFlavour = (dt: number) => {    const flavour = FLAVOURS[values.flavour];    const blend = clock.ease(dt, FLAVOUR_EASE);    let easing = false;    for (let i = 0; i < 3; i++) {      const left = flavour[i]! - colour[i]!;      colour[i]! += left * blend;      easing ||= Math.abs(left) > 1e-4;    }    if (easing) look.patch({ jelly: colour });  };  /**   * Sweeps the front and swings the lever. Its first pull reveals the jelly world, and later pulls   * send the jelly's end, now at `progress`, to the other side of the tray.   */  const reveal = (dt: number, progress: number) => {    front += (FRONT[revealed ? 1 : 0] - front) * clock.ease(dt, SWEEP);    if (!world.update(dt, front)) return;    if (revealed) {      touched = true;      goal = endAt(progress > 0.5 ? SENDS[0] : SENDS[1]);    }    revealed = true;    critters.hop();  };  return {    view,    settings,    pointing,    frame(frame: Frame) {      const dt = Math.min(frame.dt, 0.1);      time += dt;      view.update(dt);      pace(frame);      if (screen.fit()) aa.reset();      if (!touched) goal = swayed(0.5 + 0.5 * Math.sin(time * SWAY.speed));      at += (goal - at) * clock.ease(dt, FOLLOW);      slider.drag(at);      easeFlavour(dt);      let stepped = unbaked;      unbaked = false;      for (let steps = sim.advance(dt); steps > 0; steps--) {        slider.step();        critters.feel(slider.spine);        stepped = true;      }      const progress = std.saturate((slider.target - RANGE[0]) / (RANGE[1] - RANGE[0]));      reveal(dt, progress);      camera.follow(dt, revealed);      moment.front = front;      moment.time = time;      moment.seed.x = Math.random() * 2 - 1;      moment.seed.y = Math.random() * 2 - 1;      now.write(moment);      label.show(frame, Math.round(progress * 100));      cameras.eyeOf(view.pose, eye);      critters.update(dt, time, front, slider.spine, eye);      critters.weigh(slider.loads, slider.spine);      slider.track();      const shading = !lit || world.moving;      lit = true;      const compute = frame.encoder.beginComputePass();      if (stepped) slider.bake(compute);      if (shading) land.bake(compute);      land.cull(compute, region.width, region.height);      compute.end();      const march = frame.encoder.beginRenderPass({        colorAttachments: [{ view: screen.views.color }, { view: screen.views.motion }],        depthStencilAttachment: {          view: screen.views.depth,          depthClearValue: 1,          depthLoadOp: 'clear',          depthStoreOp: 'store',        },        timestampWrites: marchTime.writes(),      });      march.setViewport(0, 0, region.width, region.height, 0, 1);      jelly.draw(march);      march.end();      aa.record(frame);      final.record(frame, surface.view(frame), { timestampWrites: outputTime.writes() });      gpu.capture(frame);    },    destroy: own.destroy,  };}

slider.ts

import { sdBezier } from '@typegpu/sdf';import { baker } from '@typegpu/kit';import { d, std, type TgpuComputePass, type TgpuRoot } from 'typegpu';import { vec3n } from 'wgpu-matrix';import type { V3 } from './tray.ts';const offset: V3 = [0, 0, 0];/** How far a ray from `origin` along unit `direction` passes from `point`. */export const missOf = (origin: V3, direction: V3, point: V3) => {  vec3n.sub(point, origin, offset);  return vec3n.length(vec3n.addScaled(offset, direction, -vec3n.dot(offset, direction), offset));};/** Points along the jelly's spine, from the pinned anchor to the dragged end. */export const POINTS = 17;/** The anchor and the rest position of the dragged end, in world units on the slider plane. */const START = [-1, 0] as const;const END_X = 0.9;/** Lifts the spine off the floor. The dragged end rides a little higher than the rest. */const LIFT = -0.03;const BASE = START[1] + LIFT;const END_Y = 0.08 + LIFT;const LENGTH = END_X - START[0];const REST = LENGTH / (POINTS - 1);/** Where the dragged end may go, in world x. */export const RANGE = [-0.33, END_X] as const;/** Half the jelly's thickness around its spine. */export const SKIN = 0.024;/** Physics step in seconds and the substeps and constraint passes within it. */export const STEP = 1 / 120;const SUBSTEPS = 3;const ITERATIONS = 16;/** Velocity lost per substep. */const DAMPING = 0.01;/** Stiffness of the spine against stretching, per constraint pass. */const STRETCHING = 0.1;/** Stiffness of the spine against bending, strongest at its ends. */const BENDING = { strength: 0.1, exponent: 1.2 };/** Upward push in the middle of a compressed spine, which makes it arch. */const ARCH = { strength: 2, deadzone: 0.01 };/** Downward pull on the spine per rider. */const LOAD = 0.6;/** Pulls the points next to each end towards the floor. */const FLAT_ENDS = 0.05;/** * The box the jelly lies in, in world units, with half its depth across the slider plane in `z`. * The baked field covers its x and y, each texel holding the distance to the spine, the progress * along it, and the distance gradient. */export const BOX = {  left: START[0] - LENGTH * 0.02,  right: END_X + LENGTH * 0.1,  bottom: -0.3,  top: 0.65,  z: 0.25,};const BAKE = { width: 256, height: 128 };/** Finite-difference step for the baked gradient, in world units. */const GRADIENT_STEP = 0.03;/** Steps the spine takes before the first frame, so it starts at rest. */const SETTLE = 120;/** * Lays the spine out at rest, with its dragged end at `end` in world x, as an arc of its length * bulging up between its ends, so it starts untangled. */function layArc(position: Float32Array, previous: Float32Array, end: number) {  const run = [end - START[0], END_Y - BASE] as const;  const span = Math.hypot(run[0], run[1]);  // The arc's half-angle, where sin(half) / half = span / LENGTH  let [low, high] = [0, Math.PI];  for (let i = 0; i < 40; i++) {    const half = (low + high) / 2;    if (Math.sin(half) / half > span / LENGTH) low = half;    else high = half;  }  const half = (low + high) / 2;  const turn = Math.atan2(run[1], run[0]);  const radius = LENGTH / (2 * half);  const centre = [    START[0] + run[0] / 2 + Math.sin(turn) * radius * Math.cos(half),    BASE + run[1] / 2 - Math.cos(turn) * radius * Math.cos(half),  ];  for (let i = 0; i < POINTS; i++) {    const angle = turn + Math.PI / 2 + half - 2 * half * (i / (POINTS - 1));    position[i * 2] = previous[i * 2] = centre[0]! + radius * Math.cos(angle);    position[i * 2 + 1] = previous[i * 2 + 1] = Math.max(      BASE,      centre[1]! + radius * Math.sin(angle),    );  }}/** * A Verlet spine on the CPU, drawn as quadratic Bézier segments that a kernel bakes into a * distance field the march samples. Its dragged end starts at `start` in world x. */export function createSlider(root: TgpuRoot, start: number = END_X) {  const position = new Float32Array(POINTS * 2);  const previous = new Float32Array(POINTS * 2);  const normal = new Float32Array(POINTS * 2);  const control = new Float32Array((POINTS - 1) * 2);  const cap = new Float32Array(4);  const loads = new Float32Array(POINTS);  const point: V3 = [0, 0, 0];  let target = std.clamp(start, RANGE[0], RANGE[1]);  layArc(position, previous, target);  const points = root.createReadonly(d.arrayOf(d.vec2f, POINTS));  /** Where each point was when `track` was last called, and how far back that is from it now. */ const drawn =    new Float32Array(POINTS * 2);  const back = new Float32Array(POINTS * 2);  const moved = root.createReadonly(d.arrayOf(d.vec2f, POINTS));  const controls = root.createReadonly(d.arrayOf(d.vec2f, POINTS - 1));  /** The last segment, as its start in xy and its end in zw, for the rounded cap. */  const end = root.createUniform(d.vec4f);  const texture = root    .createTexture({ size: [BAKE.width, BAKE.height], format: 'rgba16float' })    .$usage('sampled', 'storage');  const baking = baker.create(root, texture, {    texel: (uv) => {      'use gpu';      const at = d.vec2f(        BOX.left + uv.x * (BOX.right - BOX.left),        BOX.top - uv.y * (BOX.top - BOX.bottom),      );      const dx = d.vec2f(GRADIENT_STEP, 0);      const dy = d.vec2f(0, GRADIENT_STEP);      let nearest = d.f32(1e10);      let along = d.f32(0);      let right = d.f32(1e10);      let left = d.f32(1e10);      let up = d.f32(1e10);      let down = d.f32(1e10);      for (let i = 0; i < POINTS - 1; i++) {        const a = points.$[i]!;        const b = points.$[i + 1]!;        const c = controls.$[i]!;        const distance = sdBezier(at, a, c, b);        if (distance < nearest) {          nearest = distance;          // Progress within the segment, projected onto its chord          const chord = b - a;          const t = std.saturate(std.dot(at - a, chord) / std.max(std.dot(chord, chord), 1e-12));          along = (d.f32(i) + t) / (POINTS - 1);        }        right = std.min(right, sdBezier(at + dx, a, c, b));        left = std.min(left, sdBezier(at - dx, a, c, b));        up = std.min(up, sdBezier(at + dy, a, c, b));        down = std.min(down, sdBezier(at - dy, a, c, b));      }      const gradient = d.vec2f(right - left, up - down) / (2 * GRADIENT_STEP);      return d.vec4f(nearest, along, gradient);    },  });  const pin = () => {    position[0] = previous[0] = START[0];    position[1] = previous[1] = BASE;    const last = (POINTS - 1) * 2;    position[last] = previous[last] = target;    position[last + 1] = previous[last + 1] = END_Y;  };  const project = (i: number, j: number, rest: number, stiffness: number) => {    const dx = position[j * 2]! - position[i * 2]!;    const dy = position[j * 2 + 1]! - position[i * 2 + 1]!;    const length = Math.hypot(dx, dy);    if (length < 1e-8) return;    // Pinned ends have no inverse mass and take none of the correction    const wi = i === 0 || i === POINTS - 1 ? 0 : 1;    const wj = j === 0 || j === POINTS - 1 ? 0 : 1;    if (wi + wj === 0) return;    const scale = ((length - rest) / length) * stiffness;    const ci = (wi / (wi + wj)) * scale;    const cj = (wj / (wi + wj)) * scale;    position[i * 2] += dx * ci;    position[i * 2 + 1] += dy * ci;    position[j * 2] -= dx * cj;    position[j * 2 + 1] -= dy * cj;  };  const substep = (h: number) => {    const compression = Math.max(0, 1 - Math.abs(target - START[0]) / LENGTH);    for (let i = 1; i < POINTS - 1; i++) {      const t = i / (POINTS - 1);      const edge = ARCH.deadzone;      const window = std.smoothstep(edge, 1 - edge, t) * std.smoothstep(edge, 1 - edge, 1 - t);      const arch = ARCH.strength * Math.sin(Math.PI * t) * window * compression;      const x = position[i * 2]!;      const y = position[i * 2 + 1]!;      position[i * 2] = x + (x - previous[i * 2]!) * (1 - DAMPING);      position[i * 2 + 1] = Math.max(        BASE,        y + (y - previous[i * 2 + 1]!) * (1 - DAMPING) + (arch - loads[i]! * LOAD) * h * h,      );      previous[i * 2] = x;      previous[i * 2 + 1] = y;    }    pin();    for (let it = 0; it < ITERATIONS; it++) {      for (let i = 0; i < POINTS - 1; i++) project(i, i + 1, REST, STRETCHING);      for (let i = 1; i < POINTS - 1; i++) {        const fromMiddle = Math.abs(i / (POINTS - 1) - 0.5) * 2;        const k = BENDING.strength * (0.05 + 0.95 * fromMiddle ** BENDING.exponent);        project(i - 1, i + 1, 2 * REST, k);      }      for (const i of [1, POINTS - 2]) {        position[i * 2 + 1] += (BASE - position[i * 2 + 1]!) * FLAT_ENDS;      }      pin();    }  };  /** Sets each segment's control point where the tangents at its ends meet. */  const shape = () => {    for (let i = 0; i < POINTS; i++) {      const a = Math.max(0, i - 1);      const b = Math.min(POINTS - 1, i + 1);      const dx = position[b * 2]! - position[a * 2]!;      const dy = position[b * 2 + 1]! - position[a * 2 + 1]!;      const length = Math.hypot(dx, dy);      if (length < 1e-6) {        normal[i * 2] = i > 0 ? normal[i * 2 - 2]! : 0;        normal[i * 2 + 1] = i > 0 ? normal[i * 2 - 1]! : 1;        continue;      }      normal[i * 2] = -dy / length;      normal[i * 2 + 1] = dx / length;    }    for (let i = 0; i < POINTS - 1; i++) {      const ax = position[i * 2]!;      const ay = position[i * 2 + 1]!;      const bx = position[i * 2 + 2]!;      const by = position[i * 2 + 3]!;      control[i * 2] = (ax + bx) * 0.5;      control[i * 2 + 1] = (ay + by) * 0.5;      if (i === 0 || i === POINTS - 2) continue;      const na = [normal[i * 2]!, normal[i * 2 + 1]!] as const;      const nb = [normal[i * 2 + 2]!, normal[i * 2 + 3]!] as const;      // Nearly parallel tangents meet far away or nowhere: meet them halfway along the chord      // instead, which keeps the bends at the points smooth      const cross = na[1] * -nb[0] - -na[0] * nb[1];      if (na[0] * nb[0] + na[1] * nb[1] > 0.99 || Math.abs(cross) <= 1e-6) {        const half = Math.hypot(bx - ax, by - ay) * 0.5;        control[i * 2] = (ax + na[1] * half + bx - nb[1] * half) * 0.5;        control[i * 2 + 1] = (ay - na[0] * half + by + nb[0] * half) * 0.5;        continue;      }      const t = ((bx - ax) * -nb[0] - (by - ay) * nb[1]) / cross;      control[i * 2] = ax + t * na[1];      control[i * 2 + 1] = ay - t * na[0];    }    const last = (POINTS - 2) * 2;    cap.set(position.subarray(last, last + 4));  };  const step = () => {    for (let s = 0; s < SUBSTEPS; s++) substep(STEP / SUBSTEPS);    shape();  };  for (let i = 0; i < SETTLE; i++) step();  drawn.set(position);  return {    /** The baked field, sampled by the march. */    field: texture.createView(),    end,    /** How far back each point of the spine was when the last frame was drawn, in x and y. */    moved,    /** Writes `moved` for the frame about to be drawn. */    track() {      for (let i = 0; i < POINTS * 2; i++) back[i] = drawn[i]! - position[i]!;      drawn.set(position);      moved.write(back);    },    /** Sets where the dragged end goes, clamped to `RANGE`. */    drag(x: number) {      target = std.clamp(x, RANGE[0], RANGE[1]);    },    get target() {      return target;    },    /** Whether a ray passes within `reach` of a point of the spine. */    passes(origin: V3, direction: V3, reach: number) {      for (let i = 0; i < POINTS; i++) {        point[0] = position[i * 2]!;        point[1] = position[i * 2 + 1]!;        if (missOf(origin, direction, point) < reach) return true;      }      return false;    },    /** The spine's points in x and y. */    spine: position,    /** Downward pull on each spine point, such as riders' weight. */    loads,    step,    /** Uploads the spine and bakes its field. */    bake(pass: TgpuComputePass) {      points.write(position);      controls.write(control);      end.write(cap);      baking.record(pass);    },    initAsync: () => baking.initAsync(),    destroy() {      texture.destroy();    },  };}

jelly.ts

import { cameras, history, owner, type CameraBase } from '@typegpu/kit';import { perlin2d, randf } from '@typegpu/noise';import * as sdf from '@typegpu/sdf';import {  common,  d,  std,  tgpu,  type TgpuReadonly,  type TgpuRenderPass,  type TgpuRoot,  type TgpuTextureView,  type TgpuUniform,} from 'typegpu';import { fog, sky } from './backdrop.ts';import { SOFTBOX } from './candy.ts';import { BOX, POINTS, SKIN } from './slider.ts';import { GROUND, SLOT } from './tray.ts';/** How the jelly and its light look, written when a setting changes. */export const Look = d.struct({  /** Jelly colour in display values. Its alpha is unused. */  jelly: d.vec4f,  /** Unit direction the light travels. */  light: d.vec3f,  /** 1 blurs the floor seen through the jelly. */  blur: d.f32,});/** What changes every frame. */export const Moment = d.struct({  /** World x of the jelly world's front, and the time in seconds. */  front: d.f32,  time: d.f32,  /** Changes every frame, so the blur's offsets differ between the frames TAA blends. */  seed: d.vec2f,});/** The nearest thing besides the jelly a ray meets, how far along it, and which, or index -1. */export const Found = d.struct({ t: d.f32, index: d.i32 });/** A point of the floor as the tray shades it, for scenery to cover. */export const Floor = d.struct({  /** Where the ray meets the floor, and where it is shaded, shaken by the blur. */  at: d.vec3f,  shaken: d.vec3f,  normal: d.vec3f,  direction: d.vec3f,  /** The tray's colour with the label printed on it. */  color: d.vec3f,  /** How much of the point the label covers. */  ink: d.f32,  /** The jelly's coloured shadow, and the share of open sky the tray leaves. */  shadow: d.vec3f,  open: d.f32,  /** Light the jelly bounces onto the floor. */  bounce: d.vec3f,  /** A pixel's width on the floor. */  soft: d.f32,});/** What the jelly's rays meet besides the jelly and its tray, as `'use gpu'` functions. */export interface Scenery {  /**   * The nearest thing a ray through `pixel` of the march meets before `limit`. The jelly marches   * only up to it.   */  readonly find: (    eye: d.v3f,    direction: d.v3f,    limit: number,    pixel: d.v2f,  ) => d.Infer<typeof Found>;  /** The colour at `p` of the thing `find` found. */  readonly shade: (p: d.v3f, direction: d.v3f, index: number, light: d.v3f) => d.v3f;  /** How far back the thing `find` found was when the last frame was drawn. */  readonly moved: (index: number) => d.v3f;  /**   * What the jelly reflects and shows through before `limit`, and how much of the ray it covers in   * w.   */  readonly glimpse: (eye: d.v3f, direction: d.v3f, limit: number, light: d.v3f) => d.v4f;  /** What covers the floor at a point, from the tray's colour there. */  readonly ground: (point: d.Infer<typeof Floor>, light: d.v3f) => d.v3f;}/** * What a ray of the march sees: its colour, how far along the surface is, or -1 for the sky, and * how far back the surface was when the last frame was drawn. */const Seen = d.struct({ color: d.vec3f, distance: d.f32, moved: d.vec3f });/** The motion target's format, and how far along a ray the sky's motion is taken. */export const MOTION = 'rg16float';const SKY_DISTANCE = 15;const clipOf = (world: d.v3f) => {  'use gpu';  return cameras.current.$.viewProjection * d.vec4f(world, 1);};const clipBefore = history.previously(clipOf);/** A clip position in UV units, y down, as `taa.motion` takes them. */const uvOf = (clip: d.v4f) => {  'use gpu';  return (clip.xy / clip.w) * d.vec2f(0.5, -0.5);};/** Ray-march steps and reach, and the distance that counts as a hit, in world units. */const MARCH = { steps: 64, far: 10, hit: 0.001 };/** How far the jelly's reflections reach for critters. */const REFLECTED = 3;/** Steps through the jelly to the floor behind it. */const THROUGH_STEPS = 6;/** The floor's ambient light, and its highlight's sharpness and strength. */const AMBIENT = 0.36;const SPECULAR = { power: 10, intensity: 0.6 };/** The jelly's index of refraction, and how much light it scatters along a refracted ray. */const IOR = 1.42;const SCATTER = 3;/** * Absorption per unit of the jelly's missing colour, the depth light crosses, the least of it at * the jelly's start, and how much deeper it runs through the jelly at a glancing view. */const ABSORPTION = { density: 20, depth: 0.08, least: 0.07, glancing: 1 };/** * The room the jelly mirrors, its brightness below and above. Its highlights: the candy's softbox, * the cosines its edge fades between and its brightness, and the light's glint, its sharpness and * brightness. */const ROOM = {  low: 0.3,  high: 0.55,  softbox: SOFTBOX,  edge: d.vec2f(0.7, 0.97),  bright: 0.4,  glint: d.vec2f(60, 0.9),};/** The most the light the jelly gathers brightens the floor, and how much it takes its colour. */const CAUSTIC = { most: 0.8, tint: 0.6 };/** Ambient occlusion on the floor: its steps, reach, strength and offset from the surface. */const AO = { steps: 3, radius: 0.1, intensity: 0.5, bias: MARCH.hit * 5 };/** The jelly's rounding radius and half its depth across the slider plane. */const TUBE = { radius: SKIN, half: 0.17 };/** The floor slab's half-thickness, its top at the ground, and its edges' rounding. */const SLAB = { thickness: GROUND, rounding: 0.02 };/** Where the progress along the spine hands over from the tube to the rounded end, and over how much. */const CAP = { from: 0.94, blend: 0.02 };/** Rays that would meet the tray farther than this see the sky instead. */const HORIZON = 14;/** A pixel's width on the floor over its distance, and at least, for soft-edged marks. */const FOOTPRINT = { slope: 0.0016, least: 0.0005 };/** Floor-label centre and size in world units. */const LABEL = { center: d.vec3f(0.72, 0, 0), width: 0.38, depth: 0.33 };/** Perlin cells cached per side, after which the lawn's and the sky's noise repeats. */const NOISE_CELLS = 64;/** The jelly's distance at a point, and the progress along the spine there. */const Hit = d.struct({ distance: d.f32, t: d.f32 });/** The baked field at a point: progress along the spine, distance to it and its gradient. */const Spine = d.struct({ t: d.f32, distance: d.f32, normal: d.vec2f });/** The slot the jelly lies in, seen from above. */const slot = (p: d.v2f) => {  'use gpu';  return sdf.sdRoundedBox2d(p, d.vec2f(SLOT.x, SLOT.z), SLOT.z);};/** The jelly's body: the spine's distance, pushed out across the slider plane and rounded. */const tube = (p: d.v3f, along: d.Infer<typeof Spine>) => {  'use gpu';  return sdf.opExtrudeZ(p, along.distance, TUBE.half) - TUBE.radius;};/** The normal of a distance field, from four samples `step` apart. */const gradientOf = (distance: (p: d.v3f) => number, step: number) => {  const fourTapGradient = (p: d.v3f) => {    'use gpu';    const k = d.vec3f(1, -1, 0) * step;    return std.normalize(      k.xyy * distance(p + k.xyy) +        k.yyx * distance(p + k.yyx) +        k.yxy * distance(p + k.yxy) +        k.xxx * distance(p + k.xxx),    );  };  return fourTapGradient;};/** The tray: a slab cut by the slot, over the slot's floor. */const floor = (p: d.v3f) => {  'use gpu';  const slab = sdf.opExtrudeY(p, -slot(p.xz), SLAB.thickness - SLAB.rounding) - SLAB.rounding;  return sdf.opUnion(sdf.sdPlane(p, d.vec3f(0, 1, 0), -SLOT.floor), slab);};const slabNormal = gradientOf(floor, 0.0001);/** The tray's normal, flat away from the slot. */const floorNormal = (p: d.v3f) => {  'use gpu';  if (std.abs(p.z) > SLOT.z || std.abs(p.x) > SLOT.x) return d.vec3f(0, 1, 0);  return slabNormal(p);};export interface JellyOptions {  readonly camera: CameraBase;  readonly look: TgpuUniform<typeof Look>;  /** The slider's baked field. */  readonly field: TgpuTextureView<d.WgslTexture2d<d.F32>>;  /** The slider's last segment, start in xy and end in zw. */  readonly end: TgpuUniform<d.Vec4f>;  /** How far back each point of the slider's spine was when the last frame was drawn. */  readonly moved: TgpuReadonly<d.WgslArray<d.Vec2f>>;  /** The floor label, white on clear. */  readonly label: TgpuTextureView<d.WgslTexture2d<d.F32>>;  /** Moves each frame's rays by a fraction of a pixel, as TAA's `offset`. */  readonly offset: TgpuUniform<d.Vec2f>;  readonly now: TgpuUniform<typeof Moment>;  /** What else the rays meet, each carrying its own fills. */  readonly scenery: Scenery;}/** The jelly, its slotted floor and the label under it, marched per pixel. */export function createJelly(root: TgpuRoot, options: JellyOptions) {  const { look, field, end, moved, label, offset, now, scenery } = options;  const sampler = root.createSampler({ magFilter: 'linear', minFilter: 'linear' });  const own = owner();  const noise = own(perlin2d.staticCache({ root, size: d.vec2u(NOISE_CELLS, NOISE_CELLS) }));  /** The baked field at `p` on the slider plane. */  const spine = (p: d.v2f) => {    'use gpu';    const uv = d.vec2f(      (p.x - BOX.left) / (BOX.right - BOX.left),      (BOX.top - p.y) / (BOX.top - BOX.bottom),    );    const baked = std.textureSampleLevel(field.$, sampler.$, std.saturate(uv), 0);    return Spine({ t: baked.y, distance: baked.x, normal: baked.zw });  };  /** How far back the jelly at progress `t` along its spine was when the last frame was drawn. */  const spineMoved = (t: number) => {    'use gpu';    const at = std.saturate(t) * (POINTS - 1);    const i = std.min(d.u32(at), POINTS - 2);    return std.mix(moved.$[i]!, moved.$[i + 1]!, at - d.f32(i));  };  /** The jelly's rounded end, around its last segment. */  const cap = (p: d.v3f) => {    'use gpu';    const from = end.$.xy;    const along = std.normalize(end.$.zw - from);    const turn = d.mat2x2f(along.x, -along.y, along.y, along.x);    const local = p - d.vec3f(from, 0);    const turned = d.vec3f(turn * local.xy, local.z);    const pie = sdf.sdPie(turned.zx, d.vec2f(1, 0), TUBE.half);    return sdf.opExtrudeY(turned, pie, 0.001) - TUBE.radius;  };  /** The jelly's distance at a point. The floor bounds the march instead of joining it. */  const scene = (p: d.v3f) => {    'use gpu';    const along = spine(p.xy);    if (along.t > CAP.from) return Hit({ distance: cap(p), t: along.t });    return Hit({ distance: tube(p, along), t: along.t });  };  /** What occludes the floor: the tray and the jelly's tube within its box, skipping the cap. */  const occluder = (p: d.v3f) => {    'use gpu';    let jelly = d.f32(1e9);    if (p.x >= BOX.left && p.x <= BOX.right && p.y >= BOX.bottom && p.y <= BOX.top) {      jelly = tube(p, spine(p.xy));    }    return std.min(floor(p), jelly);  };  const capNormal = gradientOf(cap, 0.01);  /** Blends the spine's 2D gradient into the flat faces, rounding the tube without a gradient. */  const tubeNormal = (p: d.v3f, t: number) => {    'use gpu';    const along = spine(p.xy);    const threshold = TUBE.half * 0.85;    const intoEdge = std.max(0, ((std.abs(p.z) - threshold) * TUBE.half) / (TUBE.half - threshold));    const edge = 0.9;    const blend = std.smoothstep(      edge * TUBE.radius + (1 - edge) * TUBE.half,      0,      intoEdge * (1 - edge) + (TUBE.radius - along.distance) * edge,    );    const face = d.vec3f(0, 0, std.sign(p.z));    let normal = std.normalize(std.mix(face, d.vec3f(along.normal, 0), blend * 0.5 + 0.5));    if (t > CAP.from) {      normal = std.normalize(std.mix(normal, capNormal(p), (t - CAP.from) / CAP.blend));    }    return normal;  };  const normalAt = (p: d.v3f, hit: d.Infer<typeof Hit>) => {    'use gpu';    if (hit.t < CAP.from + CAP.blend) return tubeNormal(p, hit.t);    return capNormal(p);  };  /** The jelly's coloured shadow, from the baked field along the light, and the slot's lip. */  const shadowAt = (p: d.v3f, toLight: d.v3f) => {    'use gpu';    if (p.y < -SLAB.thickness) {      const lip = std.saturate(1 - (slot(p.xz) + 0.02) * 30);      const gradient = std.saturate(-p.z * 4 * toLight.z + 1);      return d.vec3f(lip * gradient * 0.5);    }    const uv = d.vec2f(      (p.x - p.z * toLight.x * std.sign(toLight.z)) * 0.5 + 0.5,      1 - (-p.z / toLight.z) * 0.5 - 0.2,    );    const baked = std.textureSampleLevel(field.$, sampler.$, uv, 0);    // Fades into a position-based tint where a bunched-up jelly would switch it too suddenly    const saturation = std.mix(0, baked.y, std.saturate(p.x * 1.5 + 1.1));    const tint = std.mix(d.vec3f(), look.$.jelly.rgb, saturation);    const contrast = 20 * std.saturate(uv.y) * (0.8 + end.$.x * 0.2);    const feather =      std.saturate(uv.x * 10) *      std.saturate((1 - uv.x) * 10) *      std.saturate((1 - uv.y) * 10) *      std.saturate(uv.y);    const reach = std.saturate((1 - toLight.y) * 2) * feather;    const shade = std.mix(tint, d.vec3f(1), std.saturate(baked.x * contrast - 0.3));    return std.mix(d.vec3f(1), shade, reach);  };  const occlusion = (p: d.v3f, normal: d.v3f) => {    'use gpu';    let total = d.f32(0);    let weight = d.f32(1);    for (let i = 1; i <= AO.steps; i++) {      const height = (AO.radius / AO.steps) * d.f32(i);      total += std.max(0, height - (occluder(p + normal * height) - AO.bias)) * weight;      weight *= 0.5;      if (total > AO.radius / AO.intensity) break;    }    return std.saturate(1 - (AO.intensity * total) / AO.radius);  };  const lit = (p: d.v3f, normal: d.v3f, eye: d.v3f, shadow: d.v3f) => {    'use gpu';    const toLight = look.$.light * -1;    const diffuse = std.max(std.dot(normal, toLight), 0);    const mirrored = std.reflect(look.$.light, normal);    const specular =      std.max(std.dot(std.normalize(eye - p), mirrored), 0) ** SPECULAR.power * SPECULAR.intensity;    return std.saturate(d.vec3f(0.9) * (diffuse * shadow + AMBIENT) + shadow * specular);  };  const labelAt = (p: d.v3f) => {    'use gpu';    const local = p - LABEL.center;    const uv = d.vec2f(local.x / LABEL.width, local.z / LABEL.depth) + 0.5;    if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) return d.f32(0);    return std.textureSampleLevel(label.$, sampler.$, uv, 0).w;  };  /** A bright streak where light gathers through the jelly onto the floor. */  const caustic = (p: d.v3f) => {    'use gpu';    const light = look.$.light;    const shift = d.vec2f(-light.x * 0.2, 0.05 + light.z * 0.2);    const at = p.xz + shift;    if (std.abs(at.x) >= 1 || std.abs(at.y) >= 0.2) return d.f32(0);    const centered = d.vec2f((at.x + 2) * 0.5 - 0.5, (at.y + 0.4) / 0.4 - 0.5);    const uv = d.vec2f(centered.x, 1 - (std.abs(centered.y - 0.5) * 2) ** 2 * 0.3);    const baked = std.textureSampleLevel(field.$, sampler.$, uv, 0).x;    const density = std.max(0, (baked - 0.25) * 8);    const stretch = (end.$.x + 1) * 0.5;    const fade =      std.saturate(std.smoothstep(0, -0.2, p.x - end.$.x)) *      std.saturate(1 - (std.abs(centered.y - 0.5) * 2) ** 3) *      std.saturate(1 - stretch);    return (density ** 3 * fade * 3 * (1 + light.z)) / 1.5;  };  const floorColor = (eye: d.v3f, direction: d.v3f, distance: number, blur: number) => {    'use gpu';    const p = eye + direction * distance;    const height = std.saturate(eye.y + 0.01);    const shaken = p + d.vec3f(0, blur * (height / (1 + height)) * (1 + randf.sample() / 2), 0);    const normal = floorNormal(shaken);    // Light the jelly bounces onto the floor around its end    const near = p - d.vec3f(end.$.x, 0, 0);    const near2 = std.dot(near, near);    const bounce =      look.$.jelly.rgb * (0.4 / (near2 * 15 + 1) + (0.3 / (near2 * 40 + 1)) * std.abs(normal.z));    const shadow = shadowAt(shaken, look.$.light * -1);    const open = occlusion(shaken, normal);    const color = lit(shaken, normal, eye, shadow) * open + bounce;    const ink = labelAt(p);    // The gathered light rolls off below a bound and takes on the jelly's colour    const gathered = caustic(p);    const glow = (gathered / (1 + gathered)) * CAUSTIC.most;    const printed =      std.mix(color, std.saturate(color * 0.5), ink) *      (1 + std.mix(d.vec3f(1), look.$.jelly.rgb, CAUSTIC.tint) * glow);    const point = Floor({      at: p,      shaken,      normal,      direction,      color: printed,      ink,      shadow,      open,      bounce,      soft: distance * FOOTPRINT.slope + FOOTPRINT.least,    });    return scenery.ground(point, look.$.light);  };  const floorThrough = (eye: d.v3f, direction: d.v3f) => {    'use gpu';    let travelled = d.f32(0);    for (let i = 0; i < THROUGH_STEPS; i++) {      const step = floor(eye + direction * travelled);      travelled += step;      if (travelled > MARCH.far || step < MARCH.hit * 10) break;    }    const critter = scenery.glimpse(eye, direction, std.min(travelled, MARCH.far), look.$.light);    if (critter.w > 0.99) return critter.xyz;    let behind = d.vec3f();    if (travelled >= MARCH.far) behind = sky(direction, look.$.light * -1, now.$.time);    else behind = floorColor(eye, direction, travelled, d.f32(0.87) * look.$.blur);    return std.mix(behind, critter.xyz, critter.w);  };  const jellyColor = (p: d.v3f, direction: d.v3f, hit: d.Infer<typeof Hit>) => {    'use gpu';    const normal = normalAt(p, hit);    const cosine = std.saturate(std.dot(direction * -1, normal));    const r0 = ((1 - IOR) / (1 + IOR)) ** 2;    const fresnel = r0 + (1 - r0) * (1 - cosine) ** 5;    const mirrored = std.reflect(direction, normal);    const critter = scenery.glimpse(      p + mirrored * (MARCH.hit * 4),      mirrored,      REFLECTED,      look.$.light,    );    const room = std.mix(ROOM.low, ROOM.high, std.smoothstep(-0.2, 0.8, mirrored.y));    const reflection = std.mix(d.vec3f(room), critter.xyz, critter.w);    // Bounded highlights that roll off at their edges, over the colour the jelly lets through    const softbox = std.smoothstep(      ROOM.edge.x,      ROOM.edge.y,      std.dot(mirrored, std.normalize(ROOM.softbox)),    );    const glint = std.saturate(std.dot(mirrored, look.$.light * -1)) ** ROOM.glint.x;    // Squared, the softbox fades in slowly, so the faceted bends of the spine don't show in it    const shine = (softbox * softbox * ROOM.bright + glint * ROOM.glint.y) * (1 - critter.w);    const eta = 1 / IOR;    const k = 1 - eta * eta * (1 - cosine * cosine);    let refracted = d.vec3f();    if (k > 0) {      const bent = std.normalize(direction * eta + normal * (eta * cosine - std.sqrt(k)));      const behind = floorThrough(p + bent * (MARCH.hit * 4), bent);      const tint = look.$.jelly.rgb;      const depth =        (ABSORPTION.least + hit.t ** 2) *        ABSORPTION.depth *        (1 + ABSORPTION.glancing * (1 - cosine));      const absorbed = std.exp((tint - 1) * ABSORPTION.density * depth);      const forward = std.max(0, std.dot(look.$.light * -1, bent));      refracted = behind * absorbed + tint * (1.5 * SCATTER * forward * hit.t ** 3);    }    return std.mix(refracted, reflection, fresnel) + d.vec3f(shine);  };  /** The jelly's colour where a ray meets it before `limit` and how far along, or -1 in w. */  const jellyAlong = (eye: d.v3f, direction: d.v3f, limit: number) => {    'use gpu';    // The jelly lives inside its baked box, so rays that miss the box skip it    const inverse = 1 / direction;    const low = (d.vec3f(BOX.left, BOX.bottom, -BOX.z) - eye) * inverse;    const high = (d.vec3f(BOX.right, BOX.top, BOX.z) - eye) * inverse;    const near = std.min(low, high);    const far = std.max(low, high);    const enter = std.max(near.x, near.y, near.z);    const leave = std.min(far.x, far.y, far.z);    // Past the box the field clamps to its edge, and past `limit` the jelly is hidden    const stop = std.min(leave, limit);    if (stop < enter || leave < 0)      return Seen({ color: d.vec3f(), distance: -1, moved: d.vec3f() });    let travelled = std.max(0, enter);    for (let i = 0; i < MARCH.steps; i++) {      const p = eye + direction * travelled;      const hit = scene(p);      travelled += hit.distance;      if (travelled > stop) break;      if (hit.distance < MARCH.hit) {        return Seen({          color: jellyColor(eye + direction * travelled, direction, hit),          distance: travelled,          moved: d.vec3f(spineMoved(hit.t), 0),        });      }    }    return Seen({ color: d.vec3f(), distance: -1, moved: d.vec3f() });  };  /**   * The colour a ray sees, how far along it the surface is, or -1 for the sky, and how far back   * the surface was when the last frame was drawn.   */  const march = (eye: d.v3f, direction: d.v3f, pixel: d.v2f) => {    'use gpu';    const open = direction.y > -1e-3 || (eye.y - SLAB.thickness) / -direction.y > HORIZON;    let toFloor = d.f32(MARCH.far);    if (!open) {      // Nothing of the floor stands above its top      toFloor = (eye.y - SLAB.thickness) / -direction.y;      for (let i = 0; i < MARCH.steps; i++) {        const step = floor(eye + direction * toFloor);        toFloor += step;        if (step < MARCH.hit) break;      }    }    const found = scenery.find(eye, direction, toFloor, pixel);    let limit = toFloor;    if (found.index >= 0) limit = found.t;    // The jelly goes first, so what it hides is never shaded    const jelly = jellyAlong(eye, direction, limit);    if (jelly.distance >= 0) return jelly;    if (found.index >= 0) {      const color = scenery.shade(eye + direction * found.t, direction, found.index, look.$.light);      return Seen({        color: fog(color, eye, direction, limit),        distance: limit,        moved: scenery.moved(found.index),      });    }    if (open) {      const color = sky(direction, look.$.light * -1, now.$.time);      return Seen({ color, distance: -1, moved: d.vec3f() });    }    const ground = floorColor(eye, direction, toFloor, d.f32(0));    return Seen({ color: fog(ground, eye, direction, limit), distance: limit, moved: d.vec3f() });  };  const fragment = tgpu.fragmentFn({    in: { position: d.builtin.position, uv: d.vec2f },    out: { color: d.vec4f, motion: d.vec4f, depth: d.builtin.fragDepth },  })(({ position, uv }) => {    'use gpu';    randf.seed2(now.$.seed * uv);    const ndc = d.vec2f(uv.x, 1 - uv.y) * 2 - 1 + offset.$;    const ray = cameras.rayOf(cameras.current.$, ndc);    const seen = march(ray.origin, ray.direction, position.xy);    // Depth and motion let TAA follow the camera, the jelly and the critters    let depth = d.f32(1);    let distance = d.f32(SKY_DISTANCE);    if (seen.distance >= 0) {      distance = seen.distance;      const clip = clipOf(ray.origin + ray.direction * distance);      depth = std.saturate(clip.z / clip.w);    }    const at = ray.origin + ray.direction * distance;    const motion = uvOf(clipBefore(at + seen.moved)) - uvOf(clipOf(at));    return { color: d.vec4f(seen.color, 1), motion: d.vec4f(motion, 0, 0), depth };  });  const pipeline = root    .pipe(options.camera.inject())    .pipe(noise.inject())    .createRenderPipeline({      vertex: common.fullScreenTriangle,      fragment,      targets: { color: { format: 'rgba16float' }, motion: { format: MOTION } },      depthStencil: { format: 'depth24plus', depthWriteEnabled: true, depthCompare: 'always' },    });  return {    initAsync: () => pipeline.initAsync(),    draw(pass: TgpuRenderPass) {      pass.setPipeline(pipeline);      pass.draw(3);    },    destroy: own.destroy,  };}

label.ts

import { owner, passes, targets, text, type Font, type Frame } from '@typegpu/kit';import type { TgpuRoot } from 'typegpu';const SIZE = { width: 512, height: 256 };/** Digits and the percent sign, in pixels of the label texture. */const DIGITS = { em: 180, x: 330, y: 128 };const PERCENT = { em: 140, x: 340, y: 138 };/** The slider's value as text, drawn into a texture only when it changes. */export function createLabel(root: TgpuRoot, font: Font) {  const own = owner();  const page = own(targets.create(root, SIZE, { ink: { format: 'rgba8unorm', sampled: true } }));  const tags = own(text.create(root, font, { capacity: 2, length: 3, place: text.screen }));  const paint = passes.create(root, page, { ink: passes.color });  const drawn = paint.add(tags.drawing);  tags.write(1, '%', { em: PERCENT.em, align: 'left' });  tags.labels.patch({    0: { position: [DIGITS.x, DIGITS.y, 0.5], color: [1, 1, 1, 1] },    1: { position: [PERCENT.x, PERCENT.y, 0.5], color: [1, 1, 1, 1] },  });  let shown = -1;  return {    view: page.textures.ink.createView(),    initAsync: () => paint.initAsync(),    /** Draws `value` into the texture unless it already shows it. */    show(frame: Frame, value: number) {      if (value === shown) return;      shown = value;      tags.write(0, String(value), { em: DIGITS.em, align: 'right' });      paint.record(frame, drawn);    },    destroy: own.destroy,  };}

land.ts

import { baker, cameras, type CameraBase, type Injectable } from '@typegpu/kit';import { perlin2d, randf } from '@typegpu/noise';import { d, std, tgpu, type TgpuComputePass, type TgpuRoot, type TgpuUniform } from 'typegpu';import { CRITTERS } from './body.ts';import { jelly, spanOf } from './candy.ts';import { dropAt, flowerAt, stoneAt } from './decals.ts';import { Critter, glimpse, marchCritter, shadeCritter, underCritters } from './figures.ts';import { Floor, Found, type Look, type Moment, type Scenery } from './jelly.ts';import { boundOf, castAt, marchProp, Prop, shadeProp, shownProp } from './props.ts';import { GROUND } from './tray.ts';/** * The lawn's colours, how far its edge reaches with noise, and farther than which from the front * the noise changes nothing, how much its ripples and swells tilt it, its gloss and glow, and how * far behind the front its fresh glow fades. */const LAWN = {  color: d.vec3f(0.58, 0.88, 0.56),  meadow: d.vec3f(0.86, 0.94, 0.55),  slot: d.vec3f(0.98, 0.66, 0.78),  ragged: 0.22,  settled: 1.2,  wobble: 0.035,  swell: 0.6,  finish: d.vec2f(0.15, 0.25),  fresh: 0.15,  glow: d.vec3f(0.85, 1, 0.62),};/** * The jelly lip at the front: how far in it rises, how steeply its edge falls to the paper, how * clear its thin edge is, and how far out it focuses a bright line onto the paper. */const LIP = { width: 0.08, tilt: 2.6, clear: 0.6, caustic: 0.02 };/** Bubbles in the young jelly behind the front, and sparkles on its lip. */const BUBBLE = { cell: 0.05, share: 0.3, size: 0.006, reach: 0.2 };const SPARKLE = { cell: 0.08, size: 0.022 };/** The floor that props' shadows are baked over, in world x and z, and its texels. */const SHADED = { left: -3, right: 3, back: -2.2, front: 1.6, width: 512, height: 320 };/** * Pixels on a side of the screen tiles that list the props and critters they may show before the * march, and the tiles the list holds, enough for a march 4096 by 2304 pixels. Pixels of tiles past * them try every prop and critter. */const TILE = 8;const TILES = (4096 / TILE) * (2304 / TILE);const CULL_GROUP = 8;/** Indices `find` returns for critters start here, after the props. */const FIRST_CRITTER = 32;/** * Marches the shapes whose bits are set in `passed`, nearest entry first, until the next enters * past the hit in `before`. `bound` gives shape `i`'s bounding sphere and `march` where a ray * first meets it; a hit's index is `first + i`. */const nearestOf = (  bound: (i: number) => d.v4f,  march: (eye: d.v3f, direction: d.v3f, i: number, start: number, stop: number) => number,  first: number,) => {  const nearestHit = (    eye: d.v3f,    direction: d.v3f,    count: number,    passed: number,    before: d.Infer<typeof Found>,  ) => {    'use gpu';    let mask = d.u32(passed);    let found = Found({ t: before.t, index: before.index });    for (let n = 0; n < count; n++) {      if (mask === 0) break;      let next = d.i32(0);      let span = d.vec2f(1e9, 0);      for (let i = 0; i < count; i++) {        if (((mask >>> d.u32(i)) & d.u32(1)) === 0) continue;        const at = spanOf(eye, direction, bound(i));        if (at.x < span.x) {          span = d.vec2f(at);          next = i;        }      }      if (span.x >= found.t) break;      mask = mask ^ (d.u32(1) << d.u32(next));      const t = march(eye, direction, next, span.x, std.min(span.y, found.t));      if (t >= 0) found = Found({ t, index: first + next });    }    return found;  };  return nearestHit;};const nearestProp = nearestOf(boundOf, marchProp, 0);const nearestCritter = nearestOf(Critter.bound, marchCritter, FIRST_CRITTER);/** The march's size in pixels and in tiles. */const Grid = d.struct({ size: d.vec2f, tiles: d.vec2u });/** Whether a sphere may show in the cone round `axis` from `eye`, `spread` radians wide. */const inCone = (sphere: d.v4f, eye: d.v3f, axis: d.v3f, spread: number) => {  'use gpu';  const to = sphere.xyz - eye;  const distance = std.length(to);  if (distance <= sphere.w) return true;  const angle = std.acos(std.clamp(std.dot(to, axis) / distance, -1, 1));  return angle <= spread + std.asin(sphere.w / distance);};/** The direction of the ray through `pixel` of a march `size` pixels wide. */const rayAt = (pixel: d.v2f, size: d.v2f) => {  'use gpu';  const uv = pixel / size;  return cameras.rayOf(cameras.current.$, d.vec2f(uv.x, 1 - uv.y) * 2 - 1).direction;};/** * The nearest of the props and critters set in `masks`, x for props and y for critters, along a * ray before `limit`, or index -1. Each one the ray passes marches alone within its bounding * sphere, nearest entry first, until the next enters past a hit. */const nearestIn = (eye: d.v3f, direction: d.v3f, limit: number, masks: d.v2u) => {  'use gpu';  const count = d.i32(Prop.span());  let shown = d.u32(masks.x);  let props = d.u32(0);  while (shown !== 0) {    const k = d.i32(std.countTrailingZeros(shown));    shown = shown & (shown - 1);    if (k >= count || !shownProp(k)) continue;    const span = spanOf(eye, direction, boundOf(k));    if (span.y <= 0 || span.x > limit) continue;    props = props | (d.u32(1) << d.u32(k));  }  let near = d.u32(masks.y);  let crowd = d.u32(0);  while (near !== 0) {    const c = d.i32(std.countTrailingZeros(near));    near = near & (near - 1);    if (c >= CRITTERS) continue;    const bound = Critter.bound(c);    if (bound.w < 0.001) continue;    const span = spanOf(eye, direction, bound);    if (span.y <= 0 || span.x > limit) continue;    crowd = crowd | (d.u32(1) << d.u32(c));  }  const nearest = nearestProp(eye, direction, count, props, Found({ t: limit, index: -1 }));  return nearestCritter(eye, direction, d.i32(CRITTERS), crowd, nearest);};/** * Bubbles in the young jelly `depth` behind the front and stars twinkling on its `fresh` lip, as * brightness at `p`, with `soft` a pixel's width on the floor. */const glintsAt = (p: d.v3f, depth: number, soft: number, fresh: number, time: number) => {  'use gpu';  if (depth >= BUBBLE.reach) return d.f32(0);  let sparkle = d.f32(0);  const fade = 1 - depth / BUBBLE.reach;  const cell = std.floor(p.xz / BUBBLE.cell);  randf.seed2(cell + 3.3);  if (randf.sample() < BUBBLE.share) {    const jitter = d.vec2f(randf.sample(), randf.sample()) - 0.5;    const local = p.xz - (cell + 0.5 + jitter * 0.5) * BUBBLE.cell;    const size = BUBBLE.size * (0.5 + randf.sample());    const ring = std.smoothstep(soft, -soft, std.abs(std.length(local) - size) - size * 0.2);    const glint = std.smoothstep(size * 0.45, 0, std.length(local + size * 0.4));    sparkle += (ring * 0.3 + glint * 0.7) * fade;  }  // Sparkles twinkle on the fresh jelly, each in its own time  const spot = std.floor(p.xz / SPARKLE.cell);  randf.seed2(spot + 5.9);  const phase = randf.sample() * 6.28;  const jitter = d.vec2f(randf.sample(), randf.sample()) - 0.5;  const local = std.abs(p.xz - (spot + 0.5 + jitter * 0.6) * SPARKLE.cell);  const twinkle = std.max(0, std.sin(time * 2.2 + phase)) ** 6 * fresh;  const size = SPARKLE.size * twinkle + 1e-5;  const star = std.max(    1 - local.x / size - local.y / (size * 0.25),    1 - local.y / size - local.x / (size * 0.25),  );  sparkle += std.saturate(star) * 1.2 + std.smoothstep(size * 0.3, 0, std.length(local));  // Later random draws in this pixel stay different every frame  randf.seed3(d.vec3f(p.xz, time));  return sparkle;};export interface LandOptions {  /** The camera the march sees through, whose tiles list what they may show. */  readonly camera: CameraBase;  /** The jelly world's props, from `createWorld`, whose shadows the land bakes. */  readonly world: Injectable;  /** The critters, from `createCritters`, which stand on it. */  readonly critters: Injectable;  readonly now: TgpuUniform<typeof Moment>;  /** The light, whose direction the props' shadows are baked along. */  readonly look: TgpuUniform<typeof Look>;}/** How far back whatever `find` found was when the last frame was drawn. Props stand still. */const moved = (index: number) => {  'use gpu';  if (index >= FIRST_CRITTER) return Critter.moved(index - FIRST_CRITTER).xyz;  return d.vec3f();};/** * The jelly world on the tray: finding and shading its props and critters, and the lawn they stand * on, as the jelly's scenery. Pipelines that draw it fill perlin2d's cache. */export function createLand(root: TgpuRoot, options: LandOptions) {  const { camera, world, critters, now, look } = options;  const shadows = root    .createTexture({ size: [SHADED.width, SHADED.height], format: 'rgba8unorm' })    .$usage('storage', 'sampled');  const shadowsRead = shadows.createView(d.texture2d(d.f32));  const sampler = root.createSampler({ magFilter: 'linear', minFilter: 'linear' });  const tiles = root.createBuffer(d.arrayOf(d.vec2u, TILES)).$usage('storage');  const tilesWritten = tiles.as('mutable');  const tilesRead = tiles.as('readonly');  const grid = root.createUniform(Grid);  const measured = { width: 0, height: 0 };  /** The nearest prop or critter along a ray through `pixel` before `limit`, or index -1. */  const find = (eye: d.v3f, direction: d.v3f, limit: number, pixel: d.v2f) => {    'use gpu';    const tile = d.vec2u(pixel) / TILE;    const index = tile.y * grid.$.tiles.x + tile.x;    let masks = d.vec2u(0xffffffff);    if (tile.x < grid.$.tiles.x && index < TILES) masks = d.vec2u(tilesRead.$[index]!);    return nearestIn(eye, direction, limit, masks);  };  /** The colour of whatever `find` found. */  const shade = (p: d.v3f, direction: d.v3f, index: number, light: d.v3f) => {    'use gpu';    if (index >= FIRST_CRITTER) {      return shadeCritter(p, direction, index - FIRST_CRITTER, light, now.$.time);    }    return shadeProp(p, direction, index, light);  };  /** How far into the jelly world a point is: positive behind the front. */  const behind = (p: d.v3f) => {    'use gpu';    const ahead = now.$.front - p.x;    if (std.abs(ahead) > LAWN.settled) return ahead;    const at = p.xz * 1.8 + d.vec2f(0, now.$.time * 0.15);    const ragged = perlin2d.sample(at) + perlin2d.sample(at * 2.3 + 5.1) * 0.3;    return ahead + ragged * LAWN.ragged;  };  /** The props' baked shadows on the floor under `p`, as `castAt` gives them. */  const shadowAt = (p: d.v3f) => {    'use gpu';    const uv = d.vec2f(      (p.x - SHADED.left) / (SHADED.right - SHADED.left),      (p.z - SHADED.back) / (SHADED.front - SHADED.back),    );    if (std.any(std.lt(uv, d.vec2f())) || std.any(std.gt(uv, d.vec2f(1)))) return d.vec4f(1);    return std.textureSampleLevel(shadowsRead.$, sampler.$, uv, 0);  };  /**   * The floor at a point near or behind the front: the jelly lawn with its flowers and gumdrops,   * the lip where it meets the paper with bubbles and sparkles in it, and the bright line it   * focuses onto `printed`. `soft` is a pixel's width on the floor, `ink` the label's cover and   * `sunlit` the slider's shadow.   */  const lawn = (    p: d.v3f,    normal: d.v3f,    direction: d.v3f,    light: d.v3f,    depth: number,    printed: d.v3f,    ink: number,    soft: number,    sunlit: d.v3f,  ) => {    'use gpu';    const time = now.$.time;    let outward = d.vec2f(-1, 0);    if (std.abs(depth) < LIP.width) {      const slope =        d.vec2f(behind(p + d.vec3f(0.01, 0, 0)), behind(p + d.vec3f(0, 0, 0.01))) - depth;      outward = std.normalize(slope * -1 + d.vec2f(-1e-6, 0));    }    const lens = std.saturate(std.dot(outward, std.normalize(light.xz)) * 0.6 + 0.4);    // `**` compiles to `pow`, which is NaN for a negative base on most GPUs    const off = (depth + LIP.caustic) / (LIP.caustic * 0.55);    const line = std.exp(-off * off);    const near = depth / 0.007;    const pinch = std.exp(-near * near);    const paper =      printed * std.mix(d.vec3f(1), LAWN.color * 0.7, pinch * 0.45) +      LAWN.color * LAWN.color * (line * lens * 0.9);    if (depth < -soft) return paper;    const at = p.xz * 5 + d.vec2f(time * 0.4, time * 0.3);    const here = perlin2d.sample(at);    const wobble = d.vec2f(      here - perlin2d.sample(at + d.vec2f(0.15, 0)),      here - perlin2d.sample(at + d.vec2f(0, 0.15)),    );    const lip = (1 - std.saturate(depth / LIP.width)) ** 2;    // Light bends away inside the edge, which shows as a deeper band    const inside = (depth - LIP.width * 0.3) / (LIP.width * 0.22);    const band = std.exp(-inside * inside);    // Broad swells that roll the light across the lawn, and lighter meadows on their tops    const broad = p.xz * 1.1 + 11.7;    const swell = perlin2d.sample(broad);    const roll = d.vec2f(      swell - perlin2d.sample(broad + d.vec2f(0.2, 0)),      swell - perlin2d.sample(broad + d.vec2f(0, 0.2)),    );    let base = std.mix(LAWN.color, LAWN.meadow, std.smoothstep(0.35, 0.85, swell * 0.5 + 0.5));    // Fresh jelly just behind the front is brighter, and fades to the lawn's own colour    const fresh = std.exp(-depth / LAWN.fresh);    base = std.mix(base, LAWN.glow, fresh * 0.5);    let tilt = wobble * LAWN.wobble + roll * LAWN.swell + outward * (LIP.tilt * lip);    let dimmed = d.f32(1);    if (p.y < 0) {      base = d.vec3f(LAWN.slot);    } else if (normal.y > 0.95) {      const flower = flowerAt(p.xz, soft, depth);      const drop = dropAt(p.xz, soft, depth);      const stone = stoneAt(p.xz, soft, depth);      base = std.mix(base, flower.color, flower.cover);      base = std.mix(base, drop.color, drop.cover);      base = std.mix(base, stone.color, stone.cover);      tilt = std.mix(tilt + flower.tilt + drop.tilt, stone.tilt, stone.cover);      dimmed = flower.shade * drop.shade * stone.shade;    }    const sparkle = glintsAt(p, depth, soft, fresh, time);    const bent = std.normalize(normal + d.vec3f(tilt.x, 0, tilt.y));    const cover = shadowAt(p);    const top = p.y > 0;    const thick = std.select(0.25, 0.2 + band * 0.6, top);    // The slot's floor sits in the slider's shade already, so only the tray's top takes it    const sun = cover.xyz * std.select(d.vec3f(1), std.mix(d.vec3f(1), sunlit, 0.5), top);    // The lip is fresh and wet, glossier than the lawn behind it    const finish = std.mix(LAWN.finish, d.vec2f(1, LAWN.finish.y), lip);    const lit = jelly(base, bent, direction, light, thick, sun, finish) * (cover.w * dimmed);    const printedOn = std.mix(lit, lit * 0.45, ink) + d.vec3f(1, 0.97, 0.9) * sparkle;    // Paper shows through where the young jelly is thinnest    const seen = printed * std.mix(d.vec3f(1), base * 1.1, 0.6);    const thin = 1 - std.saturate(depth / (LIP.width * 0.2));    const grown = std.mix(printedOn, seen, LIP.clear * thin);    return std.mix(paper, grown, std.smoothstep(-soft, soft, depth));  };  /** The lawn behind the front, and the critters' shadows on the floor. */  const ground = (point: d.Infer<typeof Floor>, light: d.v3f) => {    'use gpu';    const depth = behind(point.at);    const under = underCritters(point.at);    if (depth < -0.04) {      // The bare tray takes the shadows of what stands on it, such as the lever      const cover = shadowAt(point.at);      return point.color * cover.xyz * (cover.w * under);    }    const covered = lawn(      point.shaken,      point.normal,      point.direction,      light,      depth,      point.color,      point.ink,      point.soft,      point.shadow,    );    const sheltered = std.select(1, std.mix(1, point.open, 0.6), point.shaken.y > 0);    return (covered * sheltered + point.bounce * (0.5 * std.smoothstep(0, 0.02, depth))) * under;  };  const scenery: Scenery = {    find: tgpu.fn(find).pipe(world.inject()).pipe(critters.inject()),    shade: tgpu.fn(shade).pipe(world.inject()).pipe(critters.inject()),    moved: tgpu.fn(moved).pipe(critters.inject()),    glimpse: tgpu.fn(glimpse).pipe(critters.inject()),    ground: tgpu.fn(ground).pipe(critters.inject()),  };  const cast = (uv: d.v2f) => {    'use gpu';    const p = d.vec3f(      std.mix(SHADED.left, SHADED.right, uv.x),      GROUND,      std.mix(SHADED.back, SHADED.front, uv.y),    );    return castAt(p, look.$.light);  };  const baking = baker.create(root, shadows, {    texel: tgpu.fn(cast).pipe(world.inject()),  });  const cullTiles = tgpu.computeFn({    workgroupSize: [CULL_GROUP, CULL_GROUP],    in: { gid: d.builtin.globalInvocationId },  })(({ gid }) => {    'use gpu';    const across = grid.$.tiles;    if (gid.x >= across.x || gid.y >= across.y) return;    const index = gid.y * across.x + gid.x;    if (index >= TILES) return;    const size = grid.$.size;    // A pixel's margin round the tile covers TAA's jitter    const low = d.vec2f(gid.xy) * TILE - 1;    const high = d.vec2f(gid.xy + 1) * TILE + 1;    const a = rayAt(low, size);    const b = rayAt(d.vec2f(high.x, low.y), size);    const c = rayAt(d.vec2f(low.x, high.y), size);    const e = rayAt(high, size);    const axis = std.normalize(a + b + c + e);    const nearest = std.min(      std.min(std.dot(axis, a), std.dot(axis, b)),      std.min(std.dot(axis, c), std.dot(axis, e)),    );    const spread = std.acos(std.clamp(nearest, -1, 1));    const eye = cameras.rayOf(cameras.current.$, d.vec2f()).origin;    let props = d.u32(0);    const count = d.i32(Prop.span());    for (let k = 0; k < count; k++) {      if (!shownProp(k)) continue;      if (inCone(boundOf(k), eye, axis, spread)) props = props | (d.u32(1) << d.u32(k));    }    let crowd = d.u32(0);    for (let k = 0; k < CRITTERS; k++) {      const bound = Critter.bound(k);      if (bound.w < 0.001) continue;      if (inCone(bound, eye, axis, spread)) crowd = crowd | (d.u32(1) << d.u32(k));    }    tilesWritten.$[index] = d.vec2u(props, crowd);  });  const culling = root    .pipe(camera.inject())    .pipe(world.inject())    .pipe(critters.inject())    .createComputePipeline({ compute: cullTiles });  return {    scenery,    initAsync: () => Promise.all([baking.initAsync(), culling.initAsync()]),    /** Bakes the props' shadows, after they grow or the light turns. */    bake(pass: TgpuComputePass) {      baking.record(pass);    },    /**     * Lists what each tile of a march `width` by `height` pixels may show, after the props and     * critters move.     */    cull(pass: TgpuComputePass, width: number, height: number) {      if (width !== measured.width || height !== measured.height) {        measured.width = width;        measured.height = height;        grid.write({          size: d.vec2f(width, height),          tiles: d.vec2u(Math.ceil(width / TILE), Math.ceil(height / TILE)),        });      }      pass.setPipeline(culling);      pass.dispatchWorkgroups(        Math.ceil(width / TILE / CULL_GROUP),        Math.ceil(height / TILE / CULL_GROUP),      );    },    destroy() {      shadows.destroy();      tiles.destroy();    },  };}

world.ts

import { clock, table } from '@typegpu/kit';import { std, type TgpuRoot } from 'typegpu';import {  ARM,  BUSH,  GUMDROP,  HOUSE,  LEVER,  LOLLY,  MUSHROOM,  PIVOT,  Prop,  PUFF,  TREE,} from './props.ts';import { missOf } from './slider.ts';import { GROUND, type V3 } from './tray.ts';/** Pastel jelly colours in display values. */const PALETTE = [  [1, 0.5, 0.66],  [0.5, 0.8, 1],  [1, 0.82, 0.36],  [0.72, 0.58, 1],  [0.3, 0.86, 0.6],  [1, 0.6, 0.42],] as const;/** Trees as x, z, size, palette index and what they grow into, set back from the slot and beside it. */const TREES = [  [-1.45, -0.6, 1.1, 4, GUMDROP],  [-0.95, -0.85, 0.9, 0, LOLLY],  [-0.2, -1.25, 1.3, 4, PUFF],  [0.5, -0.75, 0.85, 3, MUSHROOM],  [1.3, -1.0, 1.2, 4, PUFF],  [1.9, -0.5, 0.9, 5, BUSH],  [-2.0, -1.15, 1.25, 2, PUFF],  [2.3, -1.4, 1.3, 3, GUMDROP],  [-1.5, 0.55, 0.75, 4, BUSH],  [1.6, 0.62, 0.8, 0, MUSHROOM],] as const;/** Over a tree's size, how far critters keep from its middle by what it grew into. */const FOOTPRINT = [0.03, 0.02, 0.05, 0.05, 0.12] as const;/** Houses as x, z, heading, size and palette index. */const HOUSES = [  [-0.5, -0.62, 0.35, 1, 0],  [1.0, -0.55, -0.4, 0.95, 2],  [-1.6, -1.35, 0.2, 1.1, 1],  [0.65, -1.4, -0.1, 1.2, 5],  [1.75, 0.05, -0.9, 0.85, 3],] as const;/** * The lever by the slot's free end: where it stands, its colour, the angles of its arm at rest, * pulled all the way and where it sends the jelly over, how far back it must fall before it sends * it again and how far past rest it swings, how near a pointer ray must pass the knob to grab it, * nearer while a critter hangs there, how fast it follows the pointer, the spring that throws it * back, and how fast a critter's weight drags it down. */const PULL = {  at: [1.08, 0.5],  palette: 1,  rest: -0.2,  full: 1.3,  flips: 1.05,  rearm: 0.5,  overswing: 0.4,  grip: [0.06, 0.035],  follow: 25,  stiffness: 110,  damping: 5,  hung: 3.4,} as const;/** The lever's block squashing as it sends the jelly over: the kick, its spring and the most. */const KICK = { push: 4, stiffness: 200, damping: 9, most: 0.3 };/** Each kind's size over its modelled size. */const SCALE = { tree: 1.7, house: 1.6 };/** * Spring stiffness and damping of growing, which overshoots into a wobble, the most the front * passes a prop by in world x before it grows, so neighbours grow apart, and how much a prop * squashes by its growth's speed, at most `most`. */const GROW = { stiffness: 140, damping: 7, stagger: 0.12, squash: 0.08, most: 0.4 };/** How far tree tops sway, and how fast in radians a second. */const SWAY = { reach: 0.06, rate: 2 };/** Trees and houses as they stand, each with the radius critters keep from its middle. */const PLANTED = [  ...TREES.map(([x, z, size, colour, variant], i) => ({    x,    z,    heading: i * 1.7,    size: size * SCALE.tree,    kind: TREE,    variant,    colour,    radius: FOOTPRINT[variant] * size * SCALE.tree,  })),  ...HOUSES.map(([x, z, heading, size, colour]) => ({    x,    z,    heading,    size: size * SCALE.house,    kind: HOUSE,    variant: PUFF,    colour,    radius: 0.12 * size * SCALE.house,  })),];/** The jelly world's trees and houses, grown by a front sweeping along x. */export function createWorld(root: TgpuRoot) {  const props = table.create(root, Prop, { capacity: PLANTED.length + 1 });  const fields = props.fields;  /** What grows behind the front, each a little after the last, on a spring. */  const growing = PLANTED.map(({ x, z, heading, size, kind, variant, colour }, i) => ({    id: props.add({ at: [x, z], heading, size, kind, variant, color: PALETTE[colour] }),    x,    heading,    after: ((i * 0.618) % 1) * GROW.stagger,    grow: clock.spring(GROW),  }));  const [leverX, leverZ] = PULL.at;  const lever = props.add({    at: [leverX, leverZ],    size: 1,    kind: LEVER,    grown: 1,    color: PALETTE[PULL.palette],  });  const obstacles = [...PLANTED, { x: leverX, z: leverZ, radius: 0.08 }];  /** The lever's arm angle on its spring, and where the pointer pulls it. */  const arm = clock.spring({ ...PULL, value: PULL.rest });  let wanted: number = PULL.rest;  let grabbed = false;  let hung = false;  /** Whether the lever sends the jelly over the next time it is pulled, and its block's squash. */  let armed = true;  const block = clock.spring(KICK);  let moving = true;  let elapsed = 0;  const at: V3 = [0, 0, 0];  const knob = () => {    at[0] = leverX;    at[1] = GROUND + PIVOT + Math.cos(arm.value) * ARM;    at[2] = leverZ + Math.sin(arm.value) * ARM;    return at;  };  return {    /** Where trees, houses and the lever stand, for critters to walk around. */    obstacles,    /** Takes hold of the lever if a pointer ray passes its knob. */    grab(origin: V3, direction: V3) {      grabbed = missOf(origin, direction, knob()) < PULL.grip[hung ? 1 : 0];      return grabbed;    },    /** Pulls the held lever towards where a pointer ray crosses the plane it swings in. */    pull(origin: V3, direction: V3) {      if (!grabbed || Math.abs(direction[0]) < 1e-4) return;      const t = (leverX - origin[0]) / direction[0];      const y = origin[1] + direction[1] * t - GROUND - PIVOT;      const z = origin[2] + direction[2] * t - leverZ;      wanted = std.clamp(Math.atan2(z, y), PULL.rest, PULL.full);    },    release() {      grabbed = false;    },    /** Whether the pointer holds the lever. */    get holding() {      return grabbed;    },    /** Where the lever's knob is, for a critter to hang on. */    get knob() {      return knob();    },    /** How far the lever is pulled, from 0 at rest to 1 all the way. */    get pulled() {      return (arm.value - PULL.rest) / (PULL.full - PULL.rest);    },    /** Hangs a critter's weight on the knob, which drags the arm down unless the pointer holds it. */    hang(on: boolean) {      hung = on;    },    /** Whether a prop grew or squashed in the last `update`. */    get moving() {      return moving;    },    /**     * Grows what the front at `front` in world x has passed and swings the lever. Returns true on     * the frame the lever is pulled far enough to send the jelly over.     */    update(dt: number, front: number) {      const step = Math.min(dt, 1 / 30);      moving = false;      // The first frame's shadows are baked from the table, so it is written even without time      if (step <= 0) {        props.flush();        return false;      }      // The pointer or a hanging critter takes the arm where it goes, else the spring throws it back      if (grabbed || hung) {        const before = arm.value;        const to = grabbed ? wanted : PULL.full;        arm.value += (to - arm.value) * clock.ease(step, grabbed ? PULL.follow : PULL.hung);        arm.velocity = (arm.value - before) / step;      } else {        arm.value = std.clamp(arm.step(PULL.rest, step), PULL.rest - PULL.overswing, PULL.full);      }      const flipped = armed && arm.value > PULL.flips;      if (flipped) {        armed = false;        block.velocity -= KICK.push;      }      if (arm.value < PULL.flips - PULL.rearm) armed = true;      const squash = block.step(0, step);      moving = Math.abs(squash) + Math.abs(block.velocity) > 1e-4;      fields.squash.set(lever, std.clamp(squash, -KICK.most, KICK.most));      fields.angle.set(lever, arm.value);      elapsed += step;      for (const prop of growing) {        const target = prop.x + prop.after < front ? 1 : 0;        const { grow } = prop;        grow.value = Math.max(0, grow.step(target, step));        if (Math.abs(grow.velocity) + Math.abs(target - grow.value) > 1e-4) moving = true;        fields.grown.set(prop.id, grow.value);        fields.squash.set(prop.id, std.clamp(grow.velocity * GROW.squash, -GROW.most, GROW.most));        fields.sway.set(prop.id, Math.sin(elapsed * SWAY.rate + prop.heading) * SWAY.reach);      }      props.flush();      return flipped;    },    /** Fills the props' table for the pipelines that draw them. */    inject: () => props.inject(),    destroy() {      props.destroy();    },  };}export type World = ReturnType<typeof createWorld>;

critters.ts

import { table, type Inject } from '@typegpu/kit';import { d, type TgpuRoot } from 'typegpu';import { vec3n } from 'wgpu-matrix';import {  CRITTERS,  FALL,  createBody,  ease,  HERALD,  jump,  LAND,  R,  random,  STANDING,  UP,  type Body,  type Crowd,  type Ground,} from './body.ts';import { Critter, nearJelly } from './figures.ts';import { carry, collide, fly, hang, perch, ride } from './moves.ts';import {  blinks,  createFigure,  figureOf,  gait,  moods,  rollUpright,  springs,  type Figure,  type Sights,} from './poses.ts';import { BOX, POINTS, STEP } from './slider.ts';import { spineLength } from './spine.ts';import { GROUND, type V3 } from './tray.ts';import { walk } from './walking.ts';/** How near the jelly's bounds a critter's bounding sphere comes for the jelly to show it. */const SHOWN = 0.35;/** The lowest a held critter goes over the ground, and how far picking lifts it to the camera. */const HELD = { floor: 0.1, lift: 0.06 };/** The longest a tap may last and the slowest it may move a critter, to make it flip. */const TAP = { time: 0.25, speed: 0.4 };/** Fixed steps of the jelly's motion riders move with, a sixtieth of a second. */const FELT = 2;/** Whether a bounding sphere comes near enough the jelly for it to show in or through it. */const nearJellyBox = (bound: Figure['bound']) => {  const reach = bound[3] + SHOWN;  return (    bound[0] > BOX.left - reach &&    bound[0] < BOX.right + reach &&    bound[1] < BOX.top + reach &&    Math.abs(bound[2]) < BOX.z + reach  );};// Scratch a critter's step writes its vectors intoconst was: V3 = [0, 0, 0];const accel: V3 = [0, 0, 0];const ahead: V3 = [0, 0, 0];const side: V3 = [0, 0, 0];const toward: V3 = [0, 0, 0];/** * Critters walking the jelly world, sliding down the jelly and carried by the pointer. Before the * world is revealed, one of them pops in and pulls the lever that reveals it. */export function createCritters(root: TgpuRoot, world: Ground) {  const rows = table.create(root, Critter, { capacity: CRITTERS });  const near = root.createUniform(d.u32);  let nearMask = 0;  const bodies = Array.from({ length: CRITTERS }, (_, i) => createBody(i));  const ids = bodies.map(() => rows.add());  const figures = bodies.map(createFigure);  const herald = bodies[0]!;  /** The point the pointer hovers, while `watching`. */  const watched: V3 = [0, 0, 0];  let watching = false;  let watchedFor = 0;  let pointerMoved = 0;  let pointerSpeed = 0;  let held: Body | undefined;  const holdAt: V3 = [0, GROUND, 0];  let depth = 0;  /** The spine after each of its last `FELT` fixed steps and the one before, oldest first. */  const felt: Float32Array[] = [];  /** Seconds before the herald pops in, and whether it still has the lever to pull. */  let heraldIn: number = HERALD.after;  let heralding = true;  const crowd: Crowd = {    bodies,    world,    herald,    errand: undefined,    pointer: undefined,    pointerSpeed: 0,    front: 0,    spine: new Float32Array(),  };  const sights: Sights = { camera: [0, 0, 0], pointer: undefined, knob: [0, 0, 0] };  /** One critter's step: grown in, moved as its mode has it, its body sprung and its face eased. */  const move = (body: Body, step: number, before: Float32Array) => {    const { front, spine } = crowd;    vec3n.copy(body.velocity, was);    // Falling bends nothing: the body falls as a whole, so its bend leaves gravity out    if (body.mode === 'fly') was[1] -= FALL * step;    const shown = body === herald && heraldIn <= 0;    const target = shown || body.at[0] < front + 0.05 || body.mode === 'held' ? 1 : 0;    body.grown = Math.max(0, body.growth.step(target, step));    body.dizzy -= step;    // Thrown past the lawn and the front, it shrinks away there and pops back in on the lawn    const lost = body.at[0] > Math.max(front, LAND.x[1]) + 0.05;    if (body.mode === 'walk' && lost && body.grown < 0.05) {      body.at[0] = LAND.x[1];      jump(body, 0.7, false);    }    if (body.mode === 'walk') walk(body, crowd, step);    else if (body.mode === 'ride') ride(body, crowd, step, spine, before, FELT * STEP);    else if (body.mode === 'fly') fly(body, crowd, step, spine);    else if (body.mode === 'perch') perch(body, step);    else if (body.mode === 'pull') {      if (hang(body, crowd, step)) heralding = false;    } else carry(body, holdAt, step, spine);    if (body.mode !== 'fly' && body.spin) rollUpright(body, step);    vec3n.set(Math.sin(body.heading), 0, Math.cos(body.heading), ahead);    vec3n.normalize(vec3n.cross(UP, ahead, side), side);    if (body.mode === 'walk' && body.asleep <= 0) gait(body, step, side);    vec3n.scale(vec3n.sub(body.velocity, was, accel), 1 / step, accel);    springs(body, step, accel, side, ahead, watching ? watched : undefined);    moods(body, step);    blinks(body, step);    body.phase += step;  };  return {    /** The critters as last posed, for tests and tools that read them on the CPU. */    rows,    /** Fills the critters' table, and which are near the jelly, for pipelines that draw them. */    inject: (): Inject => (cfg) => rows.inject()(cfg).with(nearJelly, near),    /** The point on the ground the pointer hovers, which critters look at and come to see. */    watch(point: V3 | undefined) {      if (point && watching) {        pointerMoved += Math.hypot(point[0] - watched[0], point[2] - watched[2]);      }      if (point) vec3n.copy(point, watched);      watching = point !== undefined;      watchedFor = point ? 2.5 : 0;    },    /** Picks up the critter nearest a pointer ray, if any is under it. */    pick(origin: V3, direction: V3) {      let best = Infinity;      for (const body of bodies) {        if (body.grown < 0.5) continue;        const centre = body.drawn;        const along = vec3n.dot(vec3n.sub(centre, origin, toward), direction);        const miss = vec3n.distance(vec3n.addScaled(origin, direction, along, toward), centre);        if (miss < R * 2.2 && along < best) {          best = along;          held = body;        }      }      depth = best - HELD.lift;      if (held) {        held.mode = 'held';        held.base = undefined;        // Set down elsewhere, it no longer follows a line        held.leader = undefined;        held.asleep = 0;        held.heldFor = 0;        held.spinning = 0;        held.knocked = false;        vec3n.addScaled(held.drawn, UP, -STANDING, holdAt);      }      return held !== undefined;    },    /** Moves the held critter along a pointer ray, as far from the camera as it was picked. */    hold(origin: V3, direction: V3) {      if (!held) return;      const centre = vec3n.addScaled(origin, direction, depth, toward);      if (centre[1] < GROUND + HELD.floor && direction[1] < -1e-4) {        vec3n.addScaled(          origin,          direction,          (GROUND + HELD.floor - origin[1]) / direction[1],          centre,        );      }      vec3n.addScaled(centre, UP, -STANDING, holdAt);    },    /** Lets go of the held critter with the speed it was moving at; a quick tap makes it flip. */    drop() {      if (!held) return;      if (held.heldFor < TAP.time && vec3n.length(held.velocity) < TAP.speed) {        jump(held, 0.95, true);        held.joy = 1.2;      } else {        held.mode = 'fly';        vec3n.scale(held.velocity, 0.8, held.velocity);      }      held = undefined;    },    get holding() {      return held !== undefined;    },    /** Makes every critter on the ground jump for joy, waking any that nap. */    hop() {      for (const body of bodies) {        if (body.mode !== 'walk' || body.grown < 0.5) continue;        if (body.asleep > 0) body.shock = 1;        else body.joy = 2;        jump(body, random(0.6, 0.85), Math.random() < 0.3);      }    },    /**     * Notes the spine after one of its fixed steps. Riders move with how it moved over the last     * `FELT` steps, measured on its own clock, so the frame rate doesn't change how hard they're     * thrown.     */    feel(spine: Float32Array) {      while (felt.length <= FELT) felt.push(spine.slice());      const oldest = felt.shift()!;      oldest.set(spine);      felt.push(oldest);    },    update(dt: number, time: number, front: number, spine: Float32Array, camera: V3) {      const step = Math.min(dt, 1 / 30);      if (step <= 0) return;      watchedFor -= step;      pointerSpeed = ease(pointerSpeed, pointerMoved / step, 12, step);      pointerMoved = 0;      for (const body of bodies) body.load = 0;      for (const body of bodies) if (body.mode === 'perch' && body.base) body.base.load += 1;      // Pops in with a hop, and is done once the world is revealed past the lever      heraldIn -= step;      if (heraldIn <= 0 && heraldIn + step > 0) {        jump(herald, 0.7, false);        herald.joy = 1.5;      }      if (front > world.knob[0]) heralding = false;      const pointer = watching && watchedFor > 0 ? watched : undefined;      crowd.errand = heralding && heraldIn <= 0 ? herald : undefined;      crowd.pointer = pointer;      crowd.pointerSpeed = pointerSpeed;      crowd.front = front;      crowd.spine = spine;      const before = felt[0] ?? spine;      for (const body of bodies) move(body, step, before);      collide(bodies, step);      // Its weight drags the lever down only while it hangs there, however it stopped      world.hang(herald.mode === 'pull' && herald.pulling >= HERALD.leap);      // Critters near the jelly, which it shows in and through it      vec3n.copy(camera, sights.camera);      vec3n.copy(world.knob, sights.knob);      sights.pointer = pointer;      let mask = 0;      for (let i = 0; i < CRITTERS; i++) {        const { body, moved } = figures[i]!;        // A critter that was not drawn last frame has nowhere to move from        const shown = body[3] > 0;        vec3n.copy(body, moved);        const figure = figureOf(bodies[i]!, i, time, step, spine, sights, figures[i]!);        if (shown) vec3n.sub(moved, body, moved);        else vec3n.zero(moved);        rows.write(ids[i]!, figure);        if (nearJellyBox(figure.bound)) mask |= 1 << i;      }      rows.flush();      if (mask !== nearMask) near.write(mask);      nearMask = mask;    },    /** How much each spine point is pressed down by riders, for the slider. */    weigh(loads: Float32Array, spine: Float32Array) {      loads.fill(0);      const total = spineLength(spine);      for (const body of bodies) {        if (body.mode !== 'ride') continue;        const at = (body.s / total) * (POINTS - 1);        const i = Math.floor(at);        const t = at - i;        if (i > 0 && i < POINTS - 1) loads[i]! += 1 - t;        if (i + 1 > 0 && i + 1 < POINTS - 1) loads[i + 1]! += t;      }    },    destroy() {      rows.destroy();    },  };}

camera.ts

import { cameras, type Surface } from '@typegpu/kit';import { std, type TgpuRoot } from 'typegpu';import { MIDDLE } from './tray.ts';/** * The camera's poses over the bare tray and in the revealed world: its pitch and its target's * offset in x and z from the tray's middle, as pairs for views `aspects[1]` wide or wider and * `aspects[0]` or narrower, blended between. A pose backs off to fit `across` to the width, no * closer than `nearest`. The host's orbit turns the view within `pitches` and zooms it within * `distances`, or out to the bare tray's distance where that is farther. */const VIEW = {  bare: { pitch: [0.958, 0.958], x: [0, 0.08], z: [0, 0], across: 3.2, nearest: 2.9 },  world: { pitch: [0.55, 0.9], x: [0, 0], z: [0, -0.5], across: 2.7, nearest: 1.5 },  aspects: [0.5, 1.3],  fov: Math.PI / 4,  pitches: [0.08, 1.45],  distances: [0.8, 6],} as const;/** Seconds the view takes to lower into the revealed world. */const DESCENT = 2.4;/** A camera pose: its pitch, distance and its target's offset from the tray's middle. */interface Pose {  pitch: number;  distance: number;  x: number;  z: number;}const createPose = (): Pose => ({ pitch: 0, distance: 0, x: 0, z: 0 });const mix = (from: number, to: number, t: number) => from + (to - from) * t;/** Writes `pose` as fitted to a view `aspect` wide to 1 high, `portrait` of the way upright. */function fit(pose: (typeof VIEW)['bare' | 'world'], aspect: number, portrait: number, out: Pose) {  out.pitch = mix(pose.pitch[0], pose.pitch[1], portrait);  out.distance = Math.max(pose.nearest, pose.across / aspect);  out.x = mix(pose.x[0], pose.x[1], portrait);  out.z = mix(pose.z[0], pose.z[1], portrait);}/** Writes the poses over the bare tray and in the revealed world for a view `aspect` wide. */function framing(aspect: number, out: { bare: Pose; world: Pose }) {  const portrait = 1 - std.smoothstep(VIEW.aspects[0], VIEW.aspects[1], aspect);  fit(VIEW.bare, aspect, portrait, out.bare);  fit(VIEW.world, aspect, portrait, out.world);  return out;}/** * The orbit camera over the tray. Until the host's orbit turns or zooms it, it follows the framing, * so it refits as the surface turns or resizes, and lowers into the jelly world once that is * revealed. */export function createCamera(root: TgpuRoot, surface: Surface) {  const aspect = () => surface.width / surface.height;  const poses = { bare: createPose(), world: createPose() };  const opening = framing(aspect(), poses).bare;  const view = cameras.orbit(root, {    size: surface,    target: [MIDDLE[0] + opening.x, MIDDLE[1], MIDDLE[2] + opening.z],    distance: opening.distance,    pitch: opening.pitch,    fov: VIEW.fov,    near: 0.1,    far: 20,    pitchRange: VIEW.pitches,    distanceRange: [VIEW.distances[0], Infinity],  });  /** Whether the view still follows the framing, and how far it has lowered into the world. */  let framed = true;  let descent = 0;  /** The goal as the framing last left it, to tell when the host's orbit moved it. */  const left = { yaw: view.goal.yaw, pitch: view.goal.pitch, distance: view.goal.distance };  return {    view,    /**     * Follows the framing, lowering into the world while it is `revealed`, until the host's orbit     * moves the goal. Then keeps its distance within `distances` or the bare framing.     */    follow(dt: number, revealed: boolean) {      const pose = view.goal;      framed &&=        pose.yaw === left.yaw && pose.pitch === left.pitch && pose.distance === left.distance;      if (!framed) {        const farthest = Math.max(VIEW.distances[1], framing(aspect(), poses).bare.distance);        pose.distance = Math.min(pose.distance, farthest);        return;      }      if (revealed) descent = Math.min(1, descent + dt / DESCENT);      const k = std.smoothstep(0, 1, descent);      const { bare, world } = framing(aspect(), poses);      pose.pitch = left.pitch = mix(bare.pitch, world.pitch, k);      pose.distance = left.distance = mix(bare.distance, world.distance, k);      pose.target[0] = MIDDLE[0] + mix(bare.x, world.x, k);      pose.target[2] = MIDDLE[2] + mix(bare.z, world.z, k);    },  };}

tray.ts

/** Height of the tray's top, where the jelly world stands. */export const GROUND = 0.03;/** Half the length and width of the slot the jelly lies in, and the height of its floor. */export const SLOT = { x: 1.02, z: 0.22, floor: -0.06 } as const;/** The middle of the tray, which the view turns round. */export const MIDDLE = [0, 0.05, 0] as const;/** A point or direction in world units, which the CPU's vector math writes in place. */export type V3 = [number, number, number];

backdrop.ts

import { perlin2d } from '@typegpu/noise';import { d, std } from 'typegpu';import { MIDDLE } from './tray.ts';/** * The sky's colour overhead and at the horizon, past white so the tone curve keeps it bright, and * how much of it the far tray's haze and glossy surfaces take. */const SKY = {  zenith: d.vec3f(0.55, 0.78, 1.4),  horizon: d.vec3f(1.3, 1.16, 1.05),  glow: d.vec3f(1, 0.72, 0.5),  haze: 0.85,  mirrored: 0.72,};/** Distant gumdrop hills: their heights over the horizon, as sines of elevation, and colours. */const HILLS = {  far: d.vec2f(0.035, 0.09),  near: d.vec2f(0.01, 0.07),  farColor: d.vec3f(0.78, 0.7, 1.25),  nearColor: d.vec3f(0.52, 0.95, 0.66),};/** * Where the fog starts, in world units along a ray past the eye's distance to the tray's middle, * and how fast it thickens. */const FOG = { start: 1.68, density: 0.32 };/** The sky's gradient by how far up a direction points. */const skyTone = (up: number) => {  'use gpu';  return std.mix(SKY.horizon, SKY.zenith, std.saturate(up) ** 0.5);};/** The sky as glossy surfaces mirror it, kept warm near the horizon so grazing gloss stays clean. */export const mirrored = (up: number) => {  'use gpu';  return std.mix(SKY.horizon, SKY.zenith, std.saturate(up)) * SKY.mirrored;};/** The pastel sky seen along `direction`: clouds, a warm glow round the sun and gumdrop hills. */export const sky = (direction: d.v3f, toLight: d.v3f, time: number) => {  'use gpu';  const up = direction.y;  let color = skyTone(up);  const sunward = std.saturate(std.dot(direction, toLight));  color += SKY.glow * (sunward ** 6 * 0.35 + sunward ** 64 * 0.6);  const flat = direction.xz / (up + 0.15);  const at = flat * 0.9 + d.vec2f(time * 0.02, 0);  const puff = perlin2d.sample(at) + perlin2d.sample(at * 2.3 + 3.7) * 0.45;  const cloud = std.smoothstep(0.1, 0.4, puff) * std.smoothstep(0.01, 0.12, up);  const lit = std.mix(d.vec3f(1.05, 1, 1.3), d.vec3f(1.6, 1.55, 1.5), std.saturate(puff * 1.6));  color = std.mix(color, lit, cloud * 0.85);  // Heights from the direction round the horizon, so no seam shows behind the view; folding the  // noise at zero rounds the crests and creases the dips between them  const round = std.normalize(direction.xz);  const far = HILLS.far.x + HILLS.far.y * std.abs(perlin2d.sample(round * 4 + 9.1));  const near = HILLS.near.x + HILLS.near.y * std.abs(perlin2d.sample(round * 6.5 + 1.3));  // Each range is clearest at its crest and sinks into the haze at the horizon  if (up < far) color = std.mix(SKY.horizon, HILLS.farColor, std.saturate(up / far) * 0.9);  if (up < near) color = std.mix(SKY.horizon, HILLS.nearColor, std.saturate(up / near) * 0.95);  return color;};/** Fades a colour seen `distance` away from `eye` along `direction` into the horizon's haze. */export const fog = (color: d.v3f, eye: d.v3f, direction: d.v3f, distance: number) => {  'use gpu';  const past = distance - std.length(eye - d.vec3f(MIDDLE[0], MIDDLE[1], MIDDLE[2]));  const amount = 1 - std.exp(-std.max(past - FOG.start, 0) * FOG.density);  return std.mix(color, skyTone(direction.y * 0.5) * SKY.haze, amount);};

candy.ts

import { d, std } from 'typegpu';import { mirrored as mirror } from './backdrop.ts';/** * Ray-march steps through each prop and critter, the distance that counts as a hit, and how much * that distance grows per unit along the ray. */export const MARCH = { steps: 56, hit: 0.0003, cone: 0.0005 };/** Sunlight, the sky's cool fill and the warm bounce off the lawn, in display values. */const LIGHT = {  sun: d.vec3f(1.2, 1.04, 0.86),  fill: d.vec3f(0.7, 0.86, 1.05),  bounce: d.vec3f(0.85, 0.9, 0.6),};/** A soft window light high behind the view, for the broad highlight on round candy and the jelly. */export const SOFTBOX = d.vec3f(-0.3, 0.85, 0.43);/** How fast jelly deepens to its own colour with thickness, and how much sun shows through. */const GUMMY = { density: 2.4, through: 1.6 };/** Candy colours for icing, flowers and gumdrops. */export const CANDY = {  white: d.vec3f(1, 0.97, 0.94),  pink: d.vec3f(1, 0.5, 0.68),  lilac: d.vec3f(0.75, 0.6, 1),  butter: d.vec3f(1, 0.86, 0.35),  sky: d.vec3f(0.45, 0.78, 1),  middle: d.vec3f(1, 0.75, 0.2),  cherry: d.vec3f(1, 0.25, 0.38),};export const CREAM = d.vec3f(1, 0.93, 0.82);export const INK = d.vec3f(0.03, 0.02, 0.05);/** What a point of a prop or critter is made of. */export const BODY = 0;export const TRUNK = 1;export const LINE = 2;export const EYE = 3;export const ROOF = 4;/** An antenna's ball and a sprout's leaf. */export const TIP = 10;export const LEAF = 11;export const Surface = d.struct({ distance: d.f32, material: d.i32 });/** Where a ray enters and leaves a sphere in xyz with radius w, or a leave at or below 0. */export const spanOf = (eye: d.v3f, direction: d.v3f, sphere: d.v4f) => {  'use gpu';  const from = eye - sphere.xyz;  const b = std.dot(from, direction);  const disc = b * b - std.dot(from, from) + sphere.w * sphere.w;  if (disc <= 0) return d.vec2f(0, -1);  const half = std.sqrt(disc);  return d.vec2f(std.max(-b - half, 0), -b + half);};/** The normal of shape `i` of an indexed distance field, from four samples `step` apart. */export const shapeNormal = (distance: (p: d.v3f, i: number) => number, step: number) => {  const fourTapNormal = (p: d.v3f, i: number) => {    'use gpu';    const k = d.vec3f(1, -1, 0) * step;    return std.normalize(      k.xyy * distance(p + k.xyy, i) +        k.yyx * distance(p + k.yyx, i) +        k.yxy * distance(p + k.yxy, i) +        k.xxx * distance(p + k.xxx, i),    );  };  return fourTapNormal;};/** * Pastel jelly lit by `light` travelling that way and seen along `direction`. It deepens to a * saturated core where `thick` nears 1, glows where the sun shines through thin parts, and * `sunlit` tints the sun where something shades it. `finish` scales its gloss in x and the sun * shining through it in y. */export const jelly = (  base: d.v3f,  normal: d.v3f,  direction: d.v3f,  light: d.v3f,  thick: number,  sunlit: d.v3f,  finish: d.v2f,) => {  'use gpu';  const toLight = light * -1;  const facing = std.saturate(-std.dot(normal, direction));  const deep = base * std.exp((base - 1) * (GUMMY.density * thick));  const wrap = std.saturate(std.dot(normal, toLight) * 0.55 + 0.45);  const ambient = std.mix(LIGHT.bounce, LIGHT.fill, normal.y * 0.5 + 0.5);  const scattered = std.saturate(std.dot(direction * -1, std.normalize(light - normal * 0.4))) ** 2;  const sun = LIGHT.sun * sunlit;  const through = base * base * (scattered * (1.2 - thick) * GUMMY.through * finish.y);  const body = deep * (ambient * 0.62 + sun * (wrap * 0.6)) + sun * through;  // Thin edges glow with the light scattered inside them  const edge = base * base * ((1 - facing) ** 2 * (1 - thick) * 0.8 * finish.y);  const mirrored = std.reflect(direction, normal);  const fresnel = (0.04 + 0.96 * (1 - facing) ** 5) * finish.x;  const sharp = std.saturate(std.dot(mirrored, toLight)) ** 80 * 2;  const soft = std.smoothstep(0.8, 0.95, std.dot(mirrored, std.normalize(SOFTBOX))) * 0.45;  return (    std.mix(body + edge, mirror(mirrored.y), fresnel * 0.5) +    (sun * sharp + d.vec3f(soft)) * finish.x  );};/** Jelly whose thickness follows how squarely it faces the view, as a round body's does. */export const jellyLit = (base: d.v3f, normal: d.v3f, direction: d.v3f, light: d.v3f) => {  'use gpu';  const facing = std.saturate(-std.dot(normal, direction));  return jelly(base, normal, direction, light, facing * 0.6, d.vec3f(1), d.vec2f(1));};

body.ts

import { clock, type VectorSpring } from '@typegpu/kit';import { vec3n } from 'wgpu-matrix';import { GROUND, SLOT, type V3 } from './tray.ts';import type { World } from './world.ts';/** How many critters there are, at most 32 for one bit each in the march's masks. */export const CRITTERS = 18;/** What grows on a critter's head. */export const ANTENNA = 0;export const SPROUT = 1;export const EARS = 2;/** Critter colours in display values. */const PALETTE: readonly (readonly [number, number, number])[] = [  [1, 0.55, 0.7],  [0.55, 0.85, 1],  [1, 0.85, 0.4],  [0.7, 0.6, 1],  [0.6, 0.95, 0.6],  [1, 0.65, 0.45],];/** Where critters climb onto the jelly, beside the slot's left end. */export const LADDER = -1.06;/** The lawn critters walk on, in world x and z. Wherever they head lies within it. */export const LAND = { x: [-2.1, 2.1], z: [-1.2, 0.75] } as const;/** * The springs a critter grows in and shrinks away on, its body bends and squashes on, and what * grows on its head sways on. */const SPRINGS = {  grow: { stiffness: 120, damping: 9 },  bend: { stiffness: 55, damping: 4.5 },  squash: { stiffness: 160, damping: 10 },  top: { stiffness: 170, damping: 6 },} as const;/** Adds `by` times `k` to a vector spring's velocity, as a knock does. */export const kick = (spring: VectorSpring<V3>, by: V3, k: number) => {  for (let i = 0; i < 3; i++) spring.velocity[i]! += by[i]! * k;};/** Hip, knee and foot of each leg, then shoulder and hand of each arm. */export const JOINTS = 10;/** Body radius, and the hips' height over the feet, in world units. */export const R = 0.04;export const HIP = 0.05;/** The body's centre over the ground it stands on. */export const STANDING = HIP + R * 0.5;/** Walking speed, step length and time, how high a foot lifts, and turns in radians a second. */export const WALK = { speed: 0.17, stride: 0.034, time: 0.14, lift: 0.022, turn: 4 };/** Gravity, in world units a second squared. */export const FALL = 3.2;/** Climbing the jelly and sliding down it. */export const CLIMB = {  speed: 0.15,  /** Slopes over which walking up turns into heaving. */  steep: [0.3, 0.5],  /** The steepest slope critters grip. */  steepest: 0.97,  /** The downhill slope past a top where they sit to slide. */  over: 0.3,  /** Friction while sliding. */  friction: 0.25,  /** Step length and how high a foot lifts walking up, and the share of a step a foot is planted. */  stride: 0.03,  lift: 0.012,  planted: 0.65,  /** How much harder a grip is to shake. */  grip: 2,} as const;/** Up steep jelly: seconds per heave, how far ahead hands grab, and how far each pull lifts. */export const HEAVE = { time: 0.75, reach: R * 1.7, pull: R * 1.1 };/** The landing speed that makes a critter dizzy, and for how long. */const DIZZY = { impact: 1.5, time: 2.5 };/** * Bumps: the closing speed that knocks critters over, how much of it bounces back, how much * becomes lift, and how fast a knocked critter tumbles. */export const KNOCK = { speed: 0.45, bounce: 0.4, lift: 0.5, tumble: 11 };/** Petting: how near the pointer rests, how slowly it moves, and how long before it counts. */export const PET = { reach: R * 1.8, still: 0.3, after: 0.35 };/** A pointer swept fast past critters startles them away for `flee` seconds. */export const SHOO = { speed: 1.5, reach: R * 3.5, flee: 1.2 };/** The herald, the first critter, which pulls the lever that reveals the jelly world. */export const HERALD = {  /** Seconds before it pops in, and where in x and z. */  after: 0.8,  at: [0.72, 0.72],  /** How much faster than a walk it trots to the lever. */  pace: 1.6,  /** Where it stands from the knob to leap. */  stand: 0.17,  /** Seconds its leap to the knob takes, and how high it arcs. */  leap: 0.45,  arc: 0.05,  /** Where it hangs below the knob. */  hang: [0, -R * 1.45, R * 0.4] as V3,  /** How far the lever goes before it lets go. */  done: 0.92,} as const;export const UP: V3 = [0, 1, 0];export type Mode = 'walk' | 'ride' | 'fly' | 'held' | 'perch' | 'pull';export interface Foot {  at: V3;  from: V3;  to: V3;  /** Swing progress, 1 when planted. */  t: number;}export interface Obstacle {  readonly x: number;  readonly z: number;  readonly radius: number;}/** What critters need of the jelly world: where not to walk, and the lever one of them pulls. */export type Ground = Pick<World, 'obstacles' | 'knob' | 'pulled' | 'holding' | 'hang'>;/** What a critter's step reads besides itself, which the critters update each step. */export interface Crowd {  readonly bodies: readonly Body[];  readonly world: Ground;  /** The herald, and the same while it still has the lever to pull. */  readonly herald: Body;  errand: Body | undefined;  /** The point the pointer hovers while it is fresh, and how fast it moves. */  pointer: V3 | undefined;  pointerSpeed: number;  /** World x of the jelly world's front, which critters keep behind. */  front: number;  /** The jelly's spine, which critters climb onto. */  spine: Float32Array;}/** * Critter `i` standing on the tray: the herald at its spot, the others anywhere off the slot. Its * times are seconds left unless they say otherwise. */export function createBody(i: number) {  const at: V3 = [random(-2, 1.6), GROUND, random(-1, 0.7)];  if (Math.abs(at[2]) < SLOT.z + 0.1) at[2] = SLOT.z + 0.15;  if (i === 0) [at[0], at[2]] = HERALD.at;  const foot = (side: number): Foot => {    const p: V3 = [at[0] + side * 0.02, GROUND, at[2]];    return { at: p, from: [...p], to: [...p], t: 1 };  };  const heading = random(0, Math.PI * 2);  const blinkIn = random(1, 4);  const phase = random(0, 10);  const nerve = random(0, 1);  return {    mode: 'walk' as Mode,    at,    velocity: [0, 0, 0] as V3,    heading,    /** How far it has grown in, from 0 to 1, and the spring it grows on. */    grown: 0,    growth: clock.spring(SPRINGS.grow),    /** Arc length and speed along the spine while riding, and walking strides taken up it. */    s: 0,    sliding: 0,    cycle: 0,    /** How steep the climb is from walking to heaving, the heave's progress, and where it started. */    steep: 0,    heave: 0,    from: 0,    /** Where the hands hold the jelly, and held before the last reach. */    grab: 0,    grabFrom: 0,    /** Climbing rather than sliding, how far into the sitting pose, and turned from +s towards -s. */    gripping: true,    seated: 0,    turn: 0,    feet: [foot(1), foot(-1)] as [Foot, Foot],    /** The lean of the body's top, and the squash along its up axis. */    bend: clock.spring({ ...SPRINGS.bend, value: [0, 0, 0] as V3 }),    squash: clock.spring(SPRINGS.squash),    /** A flip's or tumble's turn so far, how fast it turns, and the tumble's axis. */    spin: 0,    spinning: 0,    spinAxis: undefined as V3 | undefined,    /** Where a tumble keeps its axis. */    axis: [0, 0, 0] as V3,    /** What grows on its head: its tip, placed on the first pose, and which kind. */    tip: clock.spring({ ...SPRINGS.top, value: [0, 0, 0] as V3 }),    tipped: false,    kind: Math.floor(i / PALETTE.length) % 3,    /** Where it is going in x and z, until when, and whether there to climb onto the jelly. */    goal: [at[0], at[2]] as [number, number],    plan: 0,    wantsRide: false,    /**     * How bold it is, from 0 to 1: bolder critters want to ride the jelly more often, run up to it     * and hesitate less before climbing on. Seconds it has stood at the jelly's edge, and walking     * before it wants to ride again.     */    nerve,    brink: 0,    rideIn: 0,    /** Whether it stopped where it was going, until it is well away from there again. */    arrived: false,    /** The side it passes what blocks its way, -1 or 1, and for how long it keeps that side. */    dodge: 0,    dodgeFor: 0,    /** What it last went round, which it keeps going round while another is about as near. */    avoiding: undefined as Body | Obstacle | undefined,    /** Seconds its way has been blocked. */    blocked: 0,    /** Seconds into a shove at `victim`, 0 when not shoving, and before it may shove again. */    shove: 0,    victim: undefined as Body | undefined,    shoveIn: 2 + (i % 4),    /** How fast a shove sends it stumbling, and who shoved it, to shove back. */    stumble: [0, 0, 0] as V3,    grudge: undefined as Body | undefined,    /** Waving at `friend`, and before it waves again. */    wave: 0,    friend: undefined as Body | undefined,    greeted: random(2, 6),    /** Cheering for `fan`, a critter flying off the jelly's end. */    cheer: 0,    fan: undefined as Body | undefined,    /** The critter it follows in a line. */    leader: undefined as Body | undefined,    train: 0,    /** The critter whose head it sits on, and how many sit on its own. */    base: undefined as Body | undefined,    perchFor: 0,    load: 0,    asleep: 0,    flee: 0,    fleeFrom: [0, 0, 0] as V3,    /** Seconds under a resting pointer. */    petted: 0,    dizzy: 0,    /** Whether it flies knocked over, which leaves it dizzy. */    knocked: false,    /** Seconds held by the pointer. */    heldFor: 0,    /** Seconds in the leap to the lever's knob and hanging there, and where the leap started. */    pulling: 0,    leapFrom: [0, 0, 0] as V3,    /** Looking surprised and looking happy. */    shock: 0,    joy: 0,    /** How happy, surprised, sleepy and dizzy it looks, eased. */    mood: [0, 0, 0, 0] as [number, number, number, number],    blink: 0,    blinkIn,    mouth: 0,    /** Seconds since it was made, which phases its sways. */    phase,    color: PALETTE[i % PALETTE.length]!,    /**     * The body's centre then its joints from it as last computed, once posed, offsets easing each     * from where it was drawn after a jump, and the centre and up axis as drawn.     */    raw: Array.from({ length: JOINTS + 1 }, (): V3 => [0, 0, 0]),    posed: false,    settle: Array.from({ length: JOINTS + 1 }, (): V3 => [0, 0, 0]),    drawn: [at[0], GROUND + STANDING, at[2]] as V3,    upright: [0, 1, 0] as V3,  };}/** One critter's state on the CPU, as `createBody` sets it up. */export interface Body extends ReturnType<typeof createBody> {}/** A random number from `low` to `high`. */export const random = (low: number, high: number) => low + Math.random() * (high - low);export const ease = (from: number, to: number, rate: number, dt: number) =>  from + (to - from) * Math.min(1, dt * rate);/** The turn from heading `from` to `to`, the short way round. */export const turnBetween = (from: number, to: number) => {  const turn = to - from;  return turn - Math.round(turn / (Math.PI * 2)) * Math.PI * 2;};/** Whether a count that grew by `dt` this step reached `mark`. */export const crossed = (count: number, dt: number, mark: number) =>  count >= mark && count - dt < mark;/** Whether a resting pointer has petted it long enough to show. */export const petted = (body: Body) => body.petted > PET.after;/** Lands on the ground with `impact`, dizzy after a hard landing or a knock. */export function land(body: Body, impact: number) {  body.squash.velocity -= impact * 2.5;  kick(body.bend, body.velocity, 0.3);  vec3n.zero(body.velocity);  body.mode = 'walk';  body.spinning = 0;  body.blocked = 0;  if (body.knocked || impact > DIZZY.impact) body.dizzy = DIZZY.time;  body.knocked = false;  for (const foot of body.feet) {    foot.t = 1;    vec3n.set(body.at[0], GROUND, body.at[2], foot.at);  }}/** Jumps up at `speed`, turning a front flip on the way when `flip` is set. */export function jump(body: Body, speed: number, flip: boolean) {  body.mode = 'fly';  body.base = undefined;  body.asleep = 0;  vec3n.set(Math.sin(body.heading) * 0.05, speed, Math.cos(body.heading) * 0.05, body.velocity);  body.squash.velocity -= 2;  body.spin = 0;  body.spinAxis = undefined;  body.spinning = flip ? (Math.PI * FALL) / speed : 0;  body.shove = 0;}/** Knocks a critter over by `push` and up by `lift`, so it tumbles the way it was pushed. */export function topple(body: Body, push: V3, lift = KNOCK.lift * vec3n.length(push)) {  if (body.mode === 'held') return;  body.mode = 'fly';  body.base = undefined;  vec3n.add(body.velocity, push, body.velocity);  body.velocity[1] += lift;  // Tumbles about the horizontal axis across the push  const axis = vec3n.set(push[2], 0, -push[0], body.axis);  const flat = vec3n.length(axis);  body.spinAxis = flat > 1e-4 ? vec3n.normalize(axis, axis) : undefined;  body.spin = 0;  body.spinning = KNOCK.tumble * Math.min(1.5, vec3n.length(push) / 0.6 + 0.3);  body.knocked = true;  body.shock = 1.2;  body.asleep = 0;  body.wave = 0;  body.leader = undefined;  body.train = 0;  body.wantsRide = false;  body.shove = 0;  body.squash.velocity -= 2;}/** Jumps away from `from` and flees it for a while. */export function startle(body: Body, from: V3) {  jump(body, 0.7, false);  body.shock = 1;  body.flee = SHOO.flee;  vec3n.copy(from, body.fleeFrom);}

decals.ts

import { perlin2d, randf } from '@typegpu/noise';import * as sdf from '@typegpu/sdf';import { d, std } from 'typegpu';import { CANDY, CREAM } from './candy.ts';/** Flowers in meadows and gumdrop pebbles over the lawn: cell size, share of cells and size. */const FLOWER = { cell: 0.12, share: 0.75, size: 0.02, meadow: 0.9 };const DROP = { cell: 0.11, share: 0.08, size: 0.011 };/** Tufts of grass drawn as three strokes in some of the pebbles' cells: share and size. */const TUFT = { share: 0.3, size: 0.026, color: d.vec3f(0.4, 0.76, 0.5) };/** How far behind the front marks wait to pop up, how long they take, and how far they overshoot. */const BLOOM = { start: 0.03, stagger: 0.3, span: 0.12, overshoot: 0.35 };/** * Candy stepping stones from the lever across the front of the tray: where the path starts and * ends in x, its middle in z and how it winds, and the stones' spacing, size and stagger. */const PATH = {  from: -2.8,  to: 0.98,  line: 0.52,  sway: 0.1,  wave: 2.1,  phase: -2.27,  spacing: 0.1,  size: 0.03,  stagger: 0.016,};/** A mark on the lawn: its colour and coverage, how it tilts the lawn and the shade around it. */export const Decal = d.struct({ color: d.vec3f, cover: d.f32, tilt: d.vec2f, shade: d.f32 });const clear = () => {  'use gpu';  return Decal({ color: d.vec3f(), cover: 0, tilt: d.vec2f(), shade: 1 });};/** How far a mark has popped up `depth` behind the front, overshooting a little on the way. */const bloomOf = (depth: number, delay: number) => {  'use gpu';  const grown = std.saturate((depth - BLOOM.start - delay * BLOOM.stagger) / BLOOM.span);  return grown + std.sin(grown * Math.PI) * BLOOM.overshoot;};/** A five-petal flower in some cells of the lawn, raised a little off it. */export const flowerAt = (at: d.v2f, soft: number, depth: number) => {  'use gpu';  const cell = std.floor(at / FLOWER.cell);  // Flowers gather in meadows rather than spread evenly  const meadow = perlin2d.sample(cell * (FLOWER.cell * FLOWER.meadow) + 4.2);  randf.seed2(cell + 0.37);  if (randf.sample() > FLOWER.share * std.smoothstep(0.05, 0.45, meadow)) return clear();  const jitter = d.vec2f(randf.sample(), randf.sample()) - 0.5;  const centre = (cell + 0.5 + jitter * 0.45) * FLOWER.cell;  const scale = FLOWER.size * (0.75 + randf.sample() * 0.5);  const pick = randf.sample();  const spin = randf.sample() * 6.28;  const bloom = bloomOf(depth, randf.sample());  if (bloom < 0.02) return clear();  const size = scale * bloom;  const local = at - centre;  const r = std.length(local);  const angle = std.atan2(local.y, local.x) + spin + (1 - bloom) * 2;  const petals = r - size * (0.55 + 0.45 * std.abs(std.cos(angle * 2.5)));  const cover = std.smoothstep(soft, -soft, petals);  const middle = std.smoothstep(soft, -soft, r - size * 0.3);  let petal = d.vec3f(CANDY.white);  if (pick < 0.3) petal = CANDY.pink;  else if (pick < 0.5) petal = CANDY.lilac;  else if (pick < 0.65) petal = CANDY.butter;  return Decal({    color: std.mix(petal, CANDY.middle, middle),    cover,    tilt: (local / size) * (0.5 * cover),    shade: 1 - std.smoothstep(size * 1.4, size * 0.6, r) * (1 - cover) * 0.3,  });};/** A tuft of grass, three strokes fanning up the view from `local`'s origin. */const tuftAt = (local: d.v2f, size: number, soft: number) => {  'use gpu';  const q = local / size + d.vec2f(0, 0.45);  const blades = std.min(    std.min(      sdf.sdBezierApprox(q, d.vec2f(-0.1, 0), d.vec2f(-0.2, -0.55), d.vec2f(-0.7, -0.8)),      sdf.sdBezierApprox(q, d.vec2f(0, 0), d.vec2f(0, -0.6), d.vec2f(0.1, -1.15)),    ),    sdf.sdBezierApprox(q, d.vec2f(0.1, 0), d.vec2f(0.25, -0.5), d.vec2f(0.65, -0.7)),  );  return Decal({    color: TUFT.color,    cover: std.smoothstep(soft, -soft, (blades - 0.13) * size) * 0.8,    tilt: d.vec2f(),    shade: 1,  });};/** * A gumdrop pebble, a glossy dome with a soft shadow round it, or a tuft of grass in some cells * of the lawn. */export const dropAt = (at: d.v2f, soft: number, depth: number) => {  'use gpu';  const cell = std.floor(at / DROP.cell);  randf.seed2(cell + 7.1);  const chance = randf.sample();  if (chance > DROP.share + TUFT.share) return clear();  const jitter = d.vec2f(randf.sample(), randf.sample()) - 0.5;  const scale = 0.7 + randf.sample() * 0.6;  const bloom = bloomOf(depth, randf.sample());  if (bloom < 0.02) return clear();  if (chance > DROP.share) {    const size = TUFT.size * scale * bloom;    return tuftAt(at - (cell + 0.5 + jitter * 0.4) * DROP.cell, size, soft);  }  const centre = (cell + 0.5 + jitter * 0.5) * DROP.cell;  const size = DROP.size * scale * bloom;  const pick = randf.sample();  const local = at - centre;  const r = std.length(local);  const cover = std.smoothstep(soft, -soft, r - size);  let color = d.vec3f(CANDY.pink);  if (pick < 0.25) color = CANDY.sky;  else if (pick < 0.5) color = CANDY.butter;  else if (pick < 0.7) color = CANDY.lilac;  return Decal({    color,    cover,    tilt: (local / size) * (1.3 * cover),    shade: 1 - std.smoothstep(size * 1.8, size, r) * (1 - cover) * 0.4,  });};/** A stepping stone of the path where one lies, glossy and domed with a sugary pink rim. */export const stoneAt = (at: d.v2f, soft: number, depth: number) => {  'use gpu';  const i = std.floor(at.x / PATH.spacing);  const x = (i + 0.5) * PATH.spacing;  if (x < PATH.from || x > PATH.to) return clear();  const side = std.fract(i * 0.5) * 4 - 1;  const z = PATH.line + PATH.sway * std.sin(x * PATH.wave + PATH.phase) + side * PATH.stagger;  const local = (at - d.vec2f(x, z)) / d.vec2f(1.15, 1);  randf.seed(i + 2.3);  const bloom = bloomOf(depth, randf.sample());  if (bloom < 0.02) return clear();  const size = PATH.size * bloom * (0.85 + randf.sample() * 0.3);  const r = std.length(local);  const cover = std.smoothstep(soft, -soft, r - size);  const rim = std.smoothstep(size * 0.62, size * 0.72, r);  return Decal({    color: std.mix(CREAM, CANDY.pink, rim * 0.7),    cover,    tilt: (local / size) * (0.9 * cover),    shade: 1 - std.smoothstep(size * 1.5, size, r) * (1 - cover) * 0.4,  });};

figures.ts

import { table, unfilled } from '@typegpu/kit';import * as sdf from '@typegpu/sdf';import { d, std, tgpu } from 'typegpu';import { ANTENNA, CRITTERS, SPROUT } from './body.ts';import { BODY, EYE, INK, jellyLit, LEAF, LINE, MARCH, shapeNormal, Surface, TIP } from './candy.ts';/** One critter as the march draws it, in world units, which the CPU poses each frame. */export const Critter = table.define('Critter', {  host: {    /** Body centre and radius. */    body: d.vec4f,    /** How far back the body was when the last frame was drawn, in xyz. */    moved: d.vec4f,    /** The body's up axis, and how much it stretches along it. */    up: d.vec4f,    /** Which way it faces, and how far its eyelids are shut. */    ahead: d.vec4f,    /** What it looks at, and how wide its mouth is open. */    gaze: d.vec4f,    /** Hip, knee and foot of each leg, then shoulder and hand of each arm. */    joints: d.arrayOf(d.vec4f, 10),    /** Colour, and how far it has grown in. */    color: d.vec4f,    /** A sphere around all of it. */    bound: d.vec4f,    /** How far the top of the body leans over its bottom, across its up axis. */    bend: d.vec4f,    /** How happy, surprised, sleepy and dizzy it looks, from 0 to 1. */    face: d.vec4f,    /** The tip of what grows on its head, and which: an antenna, a sprout or ears. */    top: d.vec4f,  },});/** Bit `c` is set for each critter the jelly shows in or through it. */export const nearJelly = tgpu.accessor(  d.u32,  unfilled('the critters near the jelly', 'critters', 'the `createCritters` they belong to'),);/** Eye placement and size, and limb thickness, over a critter's body radius. */const FACE = { ahead: 0.72, up: 0.3, apart: 0.4, eye: 0.36 };const LIMB = { leg: 0.085, foot: 0.17, arm: 0.075, hand: 0.12 };/** A critter's sphere in the jelly's reflections over its body radius, and its soft edge. */const GLIMPSE = { size: 1.15, edge: 0.3 };/** A critter's side, up and ahead axes as columns. */const frameOf = (c: number) => {  'use gpu';  const up = Critter.up(c).xyz;  const ahead = Critter.ahead(c).xyz;  return d.mat3x3f(std.normalize(std.cross(up, ahead)), up, ahead);};/** Where a critter's eyes sit, between them. */const faceOf = (c: number, frame: d.m3x3f) => {  'use gpu';  const body = Critter.body(c);  return (    body.xyz + frame.columns[2] * (body.w * FACE.ahead) + frame.columns[1] * (body.w * FACE.up)  );};/** A point of the bent body moved back to where it would be upright, as poses.ts bends it. */const unbent = (p: d.v3f, c: number) => {  'use gpu';  const body = Critter.body(c);  const along = std.dot(p - body.xyz, Critter.up(c).xyz);  const share = std.saturate((along / body.w + 1) * 0.5);  return p - Critter.bend(c).xyz * (share * share);};/** A leg from the hip through the knee to its foot, for a critter of radius `r`. */const leg = (p: d.v3f, hip: d.v3f, knee: d.v3f, foot: d.v3f, r: number) => {  'use gpu';  const bones = std.min(    sdf.sdCapsule(p, hip, knee, r * LIMB.leg),    sdf.sdCapsule(p, knee, foot, r * LIMB.leg),  );  return std.min(bones, sdf.sdSphere(p - foot, r * LIMB.foot));};/** An arm from the shoulder to its hand, for a critter of radius `r`. */const arm = (p: d.v3f, shoulder: d.v3f, hand: d.v3f, r: number) => {  'use gpu';  return std.min(    sdf.sdCapsule(p, shoulder, hand, r * LIMB.arm),    sdf.sdSphere(p - hand, r * LIMB.hand),  );};/** Critter `c`'s distance and material at `p`: its body, eyes, limbs and what grows on its head. */const critterOf = (p: d.v3f, c: number) => {  'use gpu';  const critter = Critter.host.$[c]!;  const r = critter.body.w;  const frame = frameOf(c);  const q = unbent(p, c);  const rel = q - critter.body.xyz;  const along = std.dot(rel, frame.columns[1]);  const wide = 1 - critter.up.w * 0.5;  const tall = 1 + critter.up.w;  const across = std.length(rel - frame.columns[1] * along);  const body = (std.length(d.vec2f(across / wide, along / tall)) - r) * std.min(wide, tall) * 0.85;  const face = faceOf(c, frame);  const apart = frame.columns[0] * (r * FACE.apart);  const eye = r * FACE.eye * (1 + critter.face.y * 0.25);  const eyes = std.min(sdf.sdSphere(q - face - apart, eye), sdf.sdSphere(q - face + apart, eye));  const j = critter.joints;  const legs = std.min(    leg(p, j[0].xyz, j[1].xyz, j[2].xyz, r),    leg(p, j[3].xyz, j[4].xyz, j[5].xyz, r),  );  const arms = std.min(arm(p, j[6].xyz, j[7].xyz, r), arm(p, j[8].xyz, j[9].xyz, r));  const lines = std.min(legs, arms);  let found = Surface({ distance: body, material: BODY });  if (eyes < found.distance) found = Surface({ distance: eyes, material: EYE });  if (lines < found.distance) found = Surface({ distance: lines, material: LINE });  // What grows on its head, from the top of the bent body to the sprung tip  const tip = critter.top.xyz;  const crown = critter.body.xyz + frame.columns[1] * (r * 0.95 * tall) + critter.bend.xyz;  const kind = d.i32(critter.top.w);  if (kind === ANTENNA) {    const stalk = sdf.sdCapsule(p, crown, tip, r * 0.06);    const ball = sdf.sdSphere(p - tip, r * 0.2);    if (stalk < found.distance) found = Surface({ distance: stalk, material: LINE });    if (ball < found.distance) found = Surface({ distance: ball, material: TIP });  } else if (kind === SPROUT) {    const sprout = std.min(      sdf.sdCapsule(p, crown, std.mix(crown, tip, 0.5), r * 0.07),      sdf.sdCapsule(p, std.mix(crown, tip, 0.4), tip, r * 0.17),    );    if (sprout < found.distance) found = Surface({ distance: sprout, material: LEAF });  } else {    // Ears splay a little and round off at the tips    const reach = tip - crown;    const base = crown - frame.columns[1] * (r * 0.25);    const out = frame.columns[0] * (r * 0.45);    const ears = std.min(      sdf.sdCapsule(p, base + out, crown + out * 1.6 + reach, r * 0.2),      sdf.sdCapsule(p, base - out, crown - out * 1.6 + reach, r * 0.2),    );    if (ears < found.distance) found = Surface({ distance: ears, material: BODY });  }  return found;};const critterDistance = (p: d.v3f, c: number) => {  'use gpu';  return critterOf(p, c).distance;};/** Where a ray first meets critter `c` between `start` and `stop`, or -1. */export const marchCritter = (  eye: d.v3f,  direction: d.v3f,  c: number,  start: number,  stop: number,) => {  'use gpu';  let t = start;  for (let i = 0; i < MARCH.steps; i++) {    const distance = critterDistance(eye + direction * t, c);    if (distance < MARCH.hit + t * MARCH.cone) return t;    t += distance;    if (t > stop) break;  }  return d.f32(-1);};const critterNormal = shapeNormal(critterDistance, 0.0004);/** * A critter's colour where a ray found it: jelly body, googly eyes, face and ink limbs. Dizzy * pupils spiral round with `time`. */export const shadeCritter = (p: d.v3f, direction: d.v3f, c: number, light: d.v3f, time: number) => {  'use gpu';  const critter = Critter.host.$[c]!;  const color = critter.color.xyz * 0.82;  const material = critterOf(p, c).material;  const normal = critterNormal(p, c);  const frame = frameOf(c);  const r = critter.body.w;  const q = unbent(p, c);  const mood = critter.face;  if (material === LINE) return jellyLit(INK, normal, direction, light) * 0.6;  if (material === TIP) {    return jellyLit(std.mix(color, d.vec3f(1, 0.95, 0.75), 0.35), normal, direction, light);  }  if (material === LEAF) return jellyLit(d.vec3f(0.42, 0.85, 0.38), normal, direction, light);  if (material === EYE) {    const face = faceOf(c, frame);    const side = std.sign(std.dot(q - face, frame.columns[0]));    const eye = face + frame.columns[0] * (r * FACE.apart * side);    const out = std.normalize(q - eye);    const look = std.normalize(critter.gaze.xyz - eye);    // Surprise shrinks the pupils; dizziness turns them into spirals    const small = std.mix(0.72, 0.86, mood.y);    let pupil = std.smoothstep(small, small + 0.08, std.dot(out, look));    const off = out - look * std.dot(out, look);    const turn = std.atan2(std.dot(off, frame.columns[1]), std.dot(off, frame.columns[0]));    const coil = std.fract(turn / (Math.PI * 2) + std.length(off) * 2.5 - time * 1.5);    const spiral =      std.smoothstep(0.3, 0.2, std.abs(coil - 0.5)) * std.step(0.2, std.dot(out, look));    pupil = std.mix(pupil, spiral, mood.w);    const shine = std.normalize(look + frame.columns[1] * 0.6);    const glint = std.smoothstep(0.93, 0.96, std.dot(out, shine));    const white = std.mix(d.vec3f(1), INK, pupil * (1 - glint * (1 - mood.w)));    // Lids close from above in the body's colour, blinking or sleepy, and shut into a line    const u = std.dot(out, frame.columns[0]);    const v = std.dot(out, frame.columns[1]);    const shut = std.max(critter.ahead.w, mood.z * 0.95);    const lid = std.smoothstep(0.02, -0.02, 1 - shut * 2.2 - v);    const lash =      std.smoothstep(0.07, 0.04, std.abs(v + 0.1 - 0.6 * u * u)) * std.smoothstep(0.7, 0.95, shut);    let seen = std.mix(std.mix(white, color, lid), INK, lash);    // Happy eyes squeeze into arcs    const arc =      std.smoothstep(0.08, 0.05, std.abs(v - 0.12 + 1.6 * u * u)) * std.step(std.abs(u), 0.5);    seen = std.mix(seen, std.mix(color, INK, arc), mood.x);    return jellyLit(seen, normal, direction, light);  }  const v = std.normalize(q - critter.body.xyz);  const across = std.dot(v, frame.columns[0]);  const up = std.dot(v, frame.columns[1]);  const front = std.dot(v, frame.columns[2]);  let base = d.vec3f(color);  const blush = std.smoothstep(0.12, 0.06, std.length(d.vec2f(std.abs(across) - 0.52, up + 0.08)));  base = std.mix(base, d.vec3f(1, 0.42, 0.5), blush * std.step(0, front) * (0.7 + mood.x * 0.3));  if (front > 0.45) {    // Wider when happy, a little round 'o' when surprised    const open = std.max(critter.gaze.w, mood.y * 0.55);    const curve = 1.8 + mood.x * 0.8;    const smile =      std.smoothstep(0.035, 0.018, std.abs(up + 0.2 - curve * across * across)) *      std.step(std.abs(across), 0.26 + mood.x * 0.06);    const wide = std.mix(0.12 + open * 0.05, 0.075, mood.y);    const mouth = std.length(d.vec2f(across / wide, (up + 0.26) / (0.02 + open * 0.13)));    const inside = std.smoothstep(1.05, 0.95, mouth);    const tongue = std.smoothstep(0.2, 0.6, (up + 0.26) / -(0.02 + open * 0.13));    const opened = std.mix(INK, d.vec3f(1, 0.4, 0.5), tongue * 0.8);    if (open > 0.15) base = std.mix(base, opened, inside);    else base = std.mix(base, INK, smile);  }  return jellyLit(base, normal, direction, light);};/** * The critters near the jelly a ray meets before `limit`, as lit spheres in their colours with * their eyes, and how much of the ray they cover, for what the jelly reflects and shows through. */export const glimpse = (eye: d.v3f, direction: d.v3f, limit: number, light: d.v3f) => {  'use gpu';  let nearest = d.f32(limit);  let found = d.i32(-1);  let cover = d.f32(0);  for (let c = 0; c < CRITTERS; c++) {    if (((nearJelly.$ >>> d.u32(c)) & d.u32(1)) === 0) continue;    const body = Critter.body(c);    const from = eye - body.xyz;    const b = std.dot(from, direction);    const r = body.w * GLIMPSE.size;    const miss = std.dot(from, from) - b * b;    if (b > 0 || miss >= r * r) continue;    const t = -b - std.sqrt(r * r - miss);    if (t < 0 || t > nearest) continue;    nearest = t;    found = c;    cover = std.saturate((r - std.sqrt(miss)) / (r * GLIMPSE.edge));  }  if (found < 0) return d.vec4f();  const body = Critter.body(found);  const r = body.w;  const p = eye + direction * nearest;  const normal = std.normalize(p - body.xyz);  const frame = frameOf(found);  const face = faceOf(found, frame);  const apart = frame.columns[0] * (r * FACE.apart);  const eyeAt =    std.min(std.length(p - face - apart), std.length(p - face + apart)) / (r * FACE.eye);  const white = std.mix(INK, d.vec3f(1), std.smoothstep(0.4, 0.55, eyeAt));  const base = std.mix(Critter.color(found).xyz * 0.82, white, std.smoothstep(1.15, 1, eyeAt));  return d.vec4f(jellyLit(base, normal, direction, light), cover);};/** Soft shadows under the critters on a floor point, by their height over it. */export const underCritters = (p: d.v3f) => {  'use gpu';  let shaded = d.f32(1);  for (let c = 0; c < CRITTERS; c++) {    const body = Critter.body(c);    if (body.w < 0.001) continue;    const height = std.max(body.y - p.y, 0);    const spread = body.w * (1.1 + height * 4);    const under = std.smoothstep(spread, spread * 0.3, std.length(p.xz - body.xz));    shaded *= 1 - under * 0.45 * std.exp(-height * 5);  }  return shaded;};

props.ts

import { table } from '@typegpu/kit';import * as sdf from '@typegpu/sdf';import { d, std } from 'typegpu';import { BODY, CANDY, CREAM, jelly, MARCH, ROOF, shapeNormal, Surface, TRUNK } from './candy.ts';import { GROUND } from './tray.ts';export const TREE = 0;export const HOUSE = 1;export const LEVER = 2;/** What a tree grows into. */export const PUFF = 0;export const LOLLY = 1;export const GUMDROP = 2;export const MUSHROOM = 3;export const BUSH = 4;/** Height over the ground of the lever's pivot, and its arm's length to the knob's centre. */export const PIVOT = 0.075;export const ARM = 0.17;/** The jelly world's trees, houses and lever, which the CPU places, grows and swings. */export const Prop = table.define('Prop', {  host: {    /** Where it stands on the ground in x and z. */    at: d.vec2f,    /** Its heading in radians, and its size over its modelled size. */    heading: d.f32,    size: d.f32,    kind: d.u32,    /** What a tree grows into. */    variant: d.u32,    /** How far it has grown from 0 to 1, and its squash. */    grown: d.f32,    squash: d.f32,    /** The lever's arm, in radians from upright towards +z. */    angle: d.f32,    /** How far a tree's top sways along x. */    sway: d.f32,    color: d.vec3f,  },});/** * Each prop's bounding sphere before scaling, as its centre's height and its radius, and how wide * its shadow falls over that radius. Trees go by what they grow into. */const EXTENTS = {  puff: d.vec3f(0.17, 0.2, 0.6),  lolly: d.vec3f(0.19, 0.2, 0.38),  gumdrop: d.vec3f(0.15, 0.19, 0.55),  mushroom: d.vec3f(0.09, 0.14, 0.75),  bush: d.vec3f(0.05, 0.14, 0.9),  house: d.vec3f(0.12, 0.17, 0.75),  lever: d.vec3f(0.12, 0.24, 0.35),};/** Props cast shadows from a light this much steeper than the scene's, so they stay short. */const SHADOW = { steep: 0.9 };/** The houses' lit windows, past white. */const WINDOW = d.vec3f(1.5, 1.05, 0.5);const ellipsoid = (q: d.v3f, radii: d.v3f) => {  'use gpu';  const k0 = std.length(q / radii);  const k1 = std.length(q / (radii * radii));  return (k0 * (k0 - 1)) / k1;};const house = (q: d.v3f) => {  'use gpu';  const walls = sdf.sdRoundedBox3d(q - d.vec3f(0, 0.07, 0), d.vec3f(0.08, 0.07, 0.07), 0.018);  const raised = q - d.vec3f(0, 0.14, 0);  const turned = d.vec3f(    (raised.x - raised.y) * Math.SQRT1_2,    (raised.x + raised.y) * Math.SQRT1_2,    raised.z,  );  const ridge = sdf.sdRoundedBox3d(turned, d.vec3f(0.075, 0.075, 0.088), 0.014);  const chimney = sdf.sdRoundedBox3d(    q - d.vec3f(0.035, 0.2, -0.03),    d.vec3f(0.015, 0.035, 0.015),    0.006,  );  const roof = std.min(std.max(ridge, 0.13 - q.y), chimney);  if (roof < walls) return Surface({ distance: roof, material: ROOF });  return Surface({ distance: walls, material: BODY });};/** A slot machine's arm on a jelly block, swung `angle` radians from upright towards +z. */const lever = (q: d.v3f, angle: number) => {  'use gpu';  const block = sdf.opSmoothUnion(    sdf.sdRoundedBox3d(q - d.vec3f(0, 0.035, 0), d.vec3f(0.05, 0.035, 0.06), 0.03),    sdf.sdCapsule(q, d.vec3f(-0.05, PIVOT, 0), d.vec3f(0.05, PIVOT, 0), 0.022),    0.02,  );  const tip = d.vec3f(0, PIVOT + std.cos(angle) * ARM, std.sin(angle) * ARM);  const arm = sdf.sdCapsule(q, d.vec3f(0, PIVOT, 0), tip, 0.011);  const knob = sdf.sdSphere(q - tip, 0.032);  if (knob < block && knob < arm) return Surface({ distance: knob, material: ROOF });  if (arm < block) return Surface({ distance: arm, material: TRUNK });  return Surface({ distance: block, material: BODY });};const puff = (q: d.v3f) => {  'use gpu';  const crown = sdf.opSmoothUnion(    sdf.opSmoothUnion(      sdf.sdSphere(q - d.vec3f(0, 0.25, 0), 0.1),      sdf.sdSphere(q - d.vec3f(0.07, 0.2, 0.03), 0.07),      0.04,    ),    sdf.sdSphere(q - d.vec3f(-0.06, 0.21, -0.04), 0.075),    0.04,  );  const trunk = sdf.sdCapsule(q, d.vec3f(), d.vec3f(0, 0.2, 0), 0.022);  if (crown < trunk) return Surface({ distance: crown, material: BODY });  return Surface({ distance: trunk, material: TRUNK });};/** A round candy facing local +z on a stick. */const lolly = (q: d.v3f) => {  'use gpu';  const c = q - d.vec3f(0, 0.25, 0);  const candy = sdf.opExtrudeZ(c, sdf.sdDisk(c.xy, 0.08), 0.008) - 0.02;  const stick = sdf.sdCapsule(q, d.vec3f(), d.vec3f(0, 0.24, 0), 0.011);  if (candy < stick) return Surface({ distance: candy, material: BODY });  return Surface({ distance: stick, material: TRUNK });};/** Three gumdrops stacked on a stub, with a bead on top. */const gumdrop = (q: d.v3f) => {  'use gpu';  const tiers = sdf.opSmoothUnion(    sdf.opSmoothUnion(      ellipsoid(q - d.vec3f(0, 0.1, 0), d.vec3f(0.1, 0.06, 0.1)),      ellipsoid(q - d.vec3f(0, 0.175, 0), d.vec3f(0.078, 0.05, 0.078)),      0.012,    ),    ellipsoid(q - d.vec3f(0, 0.24, 0), d.vec3f(0.055, 0.042, 0.055)),    0.012,  );  const bead = sdf.sdSphere(q - d.vec3f(0, 0.292, 0), 0.02);  const trunk = sdf.sdCapsule(q, d.vec3f(), d.vec3f(0, 0.06, 0), 0.022);  if (bead < tiers && bead < trunk) return Surface({ distance: bead, material: ROOF });  if (tiers < trunk) return Surface({ distance: tiers, material: BODY });  return Surface({ distance: trunk, material: TRUNK });};const mushroom = (q: d.v3f) => {  'use gpu';  const cap = std.max(sdf.sdSphere(q - d.vec3f(0, 0.06, 0), 0.088), 0.1 - q.y) - 0.012;  const stem = sdf.sdCapsule(q, d.vec3f(), d.vec3f(0, 0.085, 0), 0.03);  if (cap < stem) return Surface({ distance: cap, material: BODY });  return Surface({ distance: stem, material: TRUNK });};const bush = (q: d.v3f) => {  'use gpu';  const left = sdf.opSmoothUnion(    sdf.sdSphere(q - d.vec3f(0, 0.06, 0), 0.075),    sdf.sdSphere(q - d.vec3f(0.075, 0.04, 0.02), 0.055),    0.03,  );  const right = sdf.opSmoothUnion(    sdf.sdSphere(q - d.vec3f(-0.07, 0.045, -0.015), 0.06),    sdf.sdSphere(q - d.vec3f(0.01, 0.04, 0.07), 0.05),    0.03,  );  return Surface({ distance: sdf.opSmoothUnion(left, right, 0.03), material: BODY });};/** * A prop's shape at `q` in its own frame, by its kind and what a tree grows into, how far a tree * sways and the lever's angle. */const shapeAt = (q: d.v3f, kind: number, variant: number, sway: number, angle: number) => {  'use gpu';  if (kind === HOUSE) return house(q);  if (kind === LEVER) return lever(q, angle);  const swayed = q + d.vec3f(sway * q.y * q.y, 0, 0);  if (variant === LOLLY) return lolly(swayed);  if (variant === GUMDROP) return gumdrop(swayed);  if (variant === MUSHROOM) return mushroom(q);  if (variant === BUSH) return bush(q);  return puff(swayed);};/** A prop's size across and up, grown and stretched by its squash. */const scaleOf = (k: number) => {  'use gpu';  const size = Prop.size(k) * Prop.grown(k);  const squash = Prop.squash(k);  return d.vec2f(size * (1 - squash * 0.5), size * (1 + squash));};/** Turns a world direction, or a point from the prop's foot, by the prop's heading. */const unturn = (v: d.v3f, k: number) => {  'use gpu';  const c = std.cos(Prop.heading(k));  const s = std.sin(Prop.heading(k));  return d.vec3f(c * v.x - s * v.z, v.y, s * v.x + c * v.z);};/** Into a prop's frame: heading turned away and size divided out, y stretched by its squash. */const toLocal = (p: d.v3f, k: number) => {  'use gpu';  const at = Prop.at(k);  return unturn(p - d.vec3f(at.x, GROUND, at.y), k) / scaleOf(k).xyx;};const surfaceOf = (p: d.v3f, k: number) => {  'use gpu';  const scale = scaleOf(k);  const local = toLocal(p, k);  const found = shapeAt(local, Prop.kind(k), Prop.variant(k), Prop.sway(k), Prop.angle(k));  return Surface({    distance: found.distance * std.min(scale.x, scale.y),    material: found.material,  });};const distanceOf = (p: d.v3f, k: number) => {  'use gpu';  return surfaceOf(p, k).distance;};const normalOf = shapeNormal(distanceOf, 0.0008);/** Prop `k`'s entry in `EXTENTS`. */const extentOf = (k: number) => {  'use gpu';  const kind = Prop.kind(k);  const variant = Prop.variant(k);  if (kind === HOUSE) return EXTENTS.house;  if (kind === LEVER) return EXTENTS.lever;  if (variant === LOLLY) return EXTENTS.lolly;  if (variant === GUMDROP) return EXTENTS.gumdrop;  if (variant === MUSHROOM) return EXTENTS.mushroom;  if (variant === BUSH) return EXTENTS.bush;  return EXTENTS.puff;};/** Whether prop `k` has grown enough to show. */export const shownProp = (k: number) => {  'use gpu';  return Prop.grown(k) >= 0.01;};/** A sphere around prop `k`. */export const boundOf = (k: number) => {  'use gpu';  const scale = scaleOf(k);  const bound = extentOf(k);  const at = Prop.at(k);  return d.vec4f(at.x, GROUND + bound.x * scale.y, at.y, bound.y * std.max(scale.x, scale.y));};/** Where a ray first meets prop `k` between `start` and `stop`, or -1. */export const marchProp = (eye: d.v3f, direction: d.v3f, k: number, start: number, stop: number) => {  'use gpu';  // The ray moves into the prop's frame once, rather than each point along it  const scale = scaleOf(k);  const at = Prop.at(k);  const origin = unturn(eye - d.vec3f(at.x, GROUND, at.y), k) / scale.xyx;  const along = unturn(direction, k) / scale.xyx;  const unit = std.min(scale.x, scale.y);  const kind = Prop.kind(k);  const variant = Prop.variant(k);  const sway = Prop.sway(k);  const angle = Prop.angle(k);  let t = start;  for (let i = 0; i < MARCH.steps; i++) {    const q = origin + along * t;    const distance = shapeAt(q, kind, variant, sway, angle).distance * unit;    if (distance < MARCH.hit + t * MARCH.cone) return t;    t += distance;    if (t > stop) break;  }  return d.f32(-1);};/** The rest of a ray's way through jelly, from 0 at its surface to 1 deep inside. */const depthOf = (inside: number, probe: number) => {  'use gpu';  return std.saturate(-inside / probe);};/** * A tree's body colour at `q` in its frame, with the swirl of a lolly, the cherries of a puff or * the spots of a mushroom. */const decorated = (base: d.v3f, q: d.v3f, variant: number) => {  'use gpu';  if (variant === LOLLY) {    const c = q.xy - d.vec2f(0, 0.25);    const turns = (std.atan2(c.y, c.x) / Math.PI) * 1.5 + std.length(c) * 16;    const stripe = std.fract(turns);    const swirl = std.smoothstep(0.25, 0.32, stripe) * std.smoothstep(0.75, 0.68, stripe);    return std.mix(base, CANDY.white, swirl);  }  if (variant === PUFF) {    const v = std.normalize(q - d.vec3f(0, 0.22, 0));    const fruit = std.max(      std.max(        std.max(std.dot(v, d.vec3f(0.81, 0.3, 0.51)), std.dot(v, d.vec3f(-0.7, 0.2, 0.68))),        std.dot(v, d.vec3f(0.2, 0.35, 0.92)),      ),      std.max(        std.max(std.dot(v, d.vec3f(-0.3, 0.4, -0.86)), std.dot(v, d.vec3f(0.84, 0.2, -0.5))),        std.dot(v, d.vec3f(-0.91, 0.35, -0.2)),      ),    );    return std.mix(base, CANDY.cherry, std.smoothstep(0.984, 0.99, fruit));  }  if (variant === MUSHROOM) {    const v = std.normalize(q - d.vec3f(0, 0.06, 0));    const spots = std.max(      std.max(std.dot(v, d.vec3f(0, 1, 0)), std.dot(v, d.vec3f(0.72, 0.6, 0.35))),      std.max(        std.max(std.dot(v, d.vec3f(-0.66, 0.55, 0.5)), std.dot(v, d.vec3f(0.1, 0.55, -0.83))),        std.dot(v, d.vec3f(-0.6, 0.62, -0.5)),      ),    );    return std.mix(base, CANDY.white, std.smoothstep(0.945, 0.96, spots));  }  return d.vec3f(base);};/** A prop's colour where a ray found it. */export const shadeProp = (p: d.v3f, direction: d.v3f, k: number, light: d.v3f) => {  'use gpu';  const color = Prop.color(k);  const material = surfaceOf(p, k).material;  const normal = normalOf(p, k);  const q = toLocal(p, k);  const kind = Prop.kind(k);  const variant = Prop.variant(k);  // How deep the jelly runs on behind this point, from two samples along the ray  const thick =    (depthOf(distanceOf(p + direction * 0.03, k), 0.03) +      depthOf(distanceOf(p + direction * 0.09, k), 0.09)) *    0.5;  let base = d.vec3f(color);  let shine = d.vec3f();  if (material === TRUNK) base = std.mix(color, CREAM, 0.75);  if (material === ROOF) base = std.mix(color, d.vec3f(1, 0.35, 0.45), 0.55);  if (kind === TREE && material === BODY) base = decorated(base, q, variant);  if (kind === HOUSE && material === BODY) {    const front = q.z > 0.06;    const side = std.abs(q.x) > 0.07;    const pane = std.select(q.zy - d.vec2f(0, 0.08), q.xy - d.vec2f(0.035, 0.08), front);    const round = std.length(pane);    if ((front || side) && round < 0.028) {      base = d.vec3f(CREAM);      const bar = std.min(std.abs(pane.x), std.abs(pane.y));      if (round < 0.021 && bar > 0.0035) {        base = WINDOW * 0.3;        shine = WINDOW * (0.6 + 0.4 * std.smoothstep(0.021, 0, round));      }    }    const door = d.vec2f(q.x + 0.03, q.y);    const arch = std.length(door - d.vec2f(0, 0.068));    if (front && ((std.abs(door.x) < 0.02 && door.y < 0.068) || arch < 0.02)) {      base = color * color * 0.6;      if (std.length(door - d.vec2f(0.011, 0.04)) < 0.004) base = d.vec3f(CANDY.butter);    }  }  // Icing along the eaves, dripping a little  if (kind === HOUSE && material === ROOF && q.y < 0.147 + 0.007 * std.cos((q.x + q.z) * 70)) {    base = d.vec3f(CANDY.white);  }  // Darker where it meets the lawn, so it sits on it  const grounded = std.mix(0.6, 1, std.smoothstep(0, 0.06, p.y - GROUND));  return jelly(base, normal, direction, light, thick, d.vec3f(1), d.vec2f(1)) * grounded + shine;};/** * Short coloured shadows the props cast on a floor point along the light, in rgb, and a soft * contact under each in w. */export const castAt = (p: d.v3f, sun: d.v3f) => {  'use gpu';  let tint = d.vec3f(1);  let open = d.f32(1);  const light = std.normalize(sun + d.vec3f(0, -SHADOW.steep, 0));  const count = d.i32(Prop.span());  for (let k = 0; k < count; k++) {    if (!shownProp(k)) continue;    const bound = boundOf(k);    // From the prop's foot out to where its middle falls, widening along the way    const base = bound.xz;    const tip = bound.xz + light.xz * ((bound.y - p.y) / -light.y);    const span = tip - base;    const share = std.saturate(std.dot(p.xz - base, span) / std.max(std.dot(span, span), 1e-6));    const gap = std.length(p.xz - base - span * share);    const spread = bound.w * extentOf(k).z;    const radius = spread * std.mix(0.6, 1, share);    const cast = std.smoothstep(radius, radius * 0.5, gap);    const contact = std.smoothstep(spread, 0, std.length(p.xz - bound.xz)) * 0.4;    // Jelly passes on some of the sun, in its own colour    const color = Prop.color(k);    tint *= std.mix(d.vec3f(1), color * color * 0.6 + 0.15, cast * 0.8);    open *= 1 - contact;  }  return d.vec4f(tint, open);};

moves.ts

import { clock } from '@typegpu/kit';import { std } from 'typegpu';import { vec3n } from 'wgpu-matrix';import {  CLIMB,  CRITTERS,  ease,  FALL,  HEAVE,  HERALD,  jump,  KNOCK,  land,  R,  random,  STANDING,  topple,  turnBetween,  UP,  type Body,  type Crowd,} from './body.ts';import { createSeat, nearest, seat, spineLength } from './spine.ts';import { GROUND, SLOT, type V3 } from './tray.ts';import { STEER } from './steering.ts';/** The half-width critters ride the jelly within. */const RIDE_Z = 0.12;/** How far up the jelly faces where critters land on it, and below which riders fall off. */const FACING = { land: 0, leave: -0.2 };/** * The jelly throws a rider when it drops away under it faster than `away`, or pushes it up faster * than `toward` while moving faster than the square root of `fast`, each times the rider's grip. * Riders thrown or off an end hop up as they leave, at `hop.thrown` and `hop.end`. */const THROW = { away: 1.2, toward: 0.9, fast: 1.4, hop: { end: 0.6, thrown: 0.2 } };/** Seconds a critter that climbed on walks before it wants to ride again. */const RIDDEN = { shortest: 5, longest: 12 };/** How long a critter sits on another's head. */const PERCH = { shortest: 4, longest: 9 };/** Walkers this near cheer one flying off the jelly's end, this long, and again this long after. */const CHEER = { reach: 1, time: 1.6, again: 6 };// Scratch the steps below write their vectors intoconst here = createSeat();const there = createSeat();const then = createSeat();const found = { s: 0, above: 0 };const carried: V3 = [0, 0, 0];const along: V3 = [0, 0, 0];const out: V3 = [0, 0, 0];const top: V3 = [0, 0, 0];const before: V3 = [0, 0, 0];const hanging: V3 = [0, 0, 0];const centre: V3 = [0, 0, 0];const apart: V3 = [0, 0, 0];const direction: V3 = [0, 0, 0];const relative: V3 = [0, 0, 0];const push: V3 = [0, 0, 0];/** * Climbs the jelly or slides down it, carried as it moves, until it goes off an end, falls off * where it overhangs or is thrown. Critters near the end it flies off cheer. The jelly moved from * `previous` to `spine` over the last `over` seconds of its fixed steps. */export function ride(  body: Body,  crowd: Crowd,  dt: number,  spine: Float32Array,  previous: Float32Array,  over: number,) {  const total = spineLength(spine);  const { tangent, normal } = seat(spine, body.s, body.at[2], here);  const slope = tangent[1];  if (body.gripping) {    climb(body, slope, dt);    if (slope > CLIMB.steepest || slope < -CLIMB.over) body.gripping = false;  } else {    body.sliding += -FALL * slope * dt - body.sliding * CLIMB.friction * dt;    body.s += body.sliding * dt;    if (Math.abs(body.sliding) < 0.05 && slope > -0.1 && slope < 0.5) {      body.gripping = true;      body.steep = 0;    }  }  body.seated = ease(body.seated, body.gripping ? 0 : 1, 8, dt);  body.turn = ease(body.turn, !body.gripping && body.sliding < 0 ? Math.PI : 0, 6, dt);  const effort = body.gripping && body.steep > 0.5 && body.heave > 0.35 && body.heave < 0.8;  let mouth = body.gripping ? 0.1 : Math.min(1, Math.abs(body.sliding) * 1.5);  if (effort) mouth = 0.25;  body.mouth = ease(body.mouth, mouth, 8, dt);  // The jelly carries them, and throws them when it moves away faster than they hold on  const { surface } = seat(spine, body.s, body.at[2], there);  vec3n.sub(surface, seat(previous, body.s, body.at[2], then).surface, carried);  vec3n.scale(carried, 1 / over, carried);  const lift = vec3n.dot(carried, normal);  const hold = body.gripping ? CLIMB.grip : 1;  const end = body.s >= total - 0.02;  const launched = end || body.s <= 0;  const overhang = normal[1] < FACING.leave;  vec3n.scale(tangent, body.sliding, along);  const thrown =    lift < -THROW.away * hold ||    (lift > THROW.toward * hold && vec3n.dot(carried, carried) > THROW.fast * hold);  if (launched || overhang || thrown) {    const hop = launched ? THROW.hop.end : thrown ? THROW.hop.thrown : 0;    body.mode = 'fly';    vec3n.add(along, carried, body.velocity);    body.velocity[1] += hop;    body.heading = Math.atan2(body.velocity[0], body.velocity[2]);    vec3n.copy(surface, body.at);    if (end) cheered(body, crowd.bodies);    return;  }  vec3n.add(along, carried, body.velocity);  vec3n.copy(surface, body.at);}/** Flies off the jelly's end for joy, cheered by walkers near enough that haven't cheered lately. */function cheered(body: Body, bodies: readonly Body[]) {  body.joy = 1.5;  for (const fan of bodies) {    // Walking counts a cheer on down past 0, to the seconds since it ended    if (fan.mode !== 'walk' || fan.asleep > 0 || fan.cheer > -CHEER.again) continue;    if (Math.hypot(fan.at[0] - body.at[0], fan.at[2] - body.at[2]) > CHEER.reach) continue;    fan.cheer = CHEER.time;    fan.fan = body;  }}/** Walks up the jelly leaning into the slope, or where steep reaches, grabs and heaves itself. */function climb(body: Body, slope: number, dt: number) {  body.steep = ease(body.steep, std.smoothstep(CLIMB.steep[0], CLIMB.steep[1], slope), 5, dt);  let moved: number;  if (body.steep < 0.5) {    moved = CLIMB.speed * (1 - 0.4 * Math.max(slope, 0)) * dt;    body.cycle += (moved * CLIMB.planted) / CLIMB.stride;    body.heave = 0;    body.from = body.s + moved;    body.grabFrom = body.s + R * 0.6;    body.grab = body.s + HEAVE.reach;  } else {    body.heave += dt / HEAVE.time;    if (body.heave >= 1) {      body.heave -= 1;      body.from = body.s;      body.grabFrom = body.grab;      body.grab = body.s + HEAVE.reach;    }    moved = body.from + HEAVE.pull * std.smoothstep(0.35, 0.75, body.heave) - body.s;  }  body.sliding = moved / dt;  body.s += moved;}/** * Falls, off props, onto a head, onto the jelly where it faces up, or onto the ground. A critter * still wanting to ride, hopping on, missing the jelly, tries again from the ground. */export function fly(body: Body, crowd: Crowd, dt: number, spine: Float32Array) {  body.velocity[1] -= FALL * dt;  vec3n.addScaled(body.at, body.velocity, dt, body.at);  body.mouth = Math.min(1, body.mouth + dt * 4);  if (body.spinning) {    body.spin += body.spinning * dt;    if (!body.spinAxis && body.spin >= Math.PI * 2) {      body.spin = 0;      body.spinning = 0;    }  }  const x = body.at[0];  const y = body.at[1];  const z = body.at[2];  for (const o of crowd.world.obstacles) {    const gap = Math.hypot(x - o.x, z - o.z);    if (y > GROUND + 0.25 || gap > o.radius + R || gap < 1e-4) continue;    vec3n.set((x - o.x) / gap, 0, (z - o.z) / gap, out);    vec3n.addScaled(body.at, out, o.radius + R - gap, body.at);    const into = vec3n.dot(body.velocity, out);    if (into < 0) vec3n.addScaled(body.velocity, out, -into * 1.5, body.velocity);    body.squash.velocity -= 1;  }  const falling = body.velocity[1] < 0;  if (falling && (onHead(body, crowd.bodies, x, y, z) || onJelly(body, spine, x, y, z))) return;  const inSlot = Math.abs(x) < SLOT.x && Math.abs(z) < SLOT.z;  const floor = inSlot ? SLOT.floor : GROUND;  if (body.at[1] > floor) return;  body.at[1] = floor;  const impact = Math.abs(body.velocity[1]);  if (inSlot) {    // Out of the slot with a hop towards the nearer side    vec3n.set(0, 1.1, Math.sign(z || 1) * 0.6, body.velocity);    body.squash.velocity -= impact * 2;    return;  }  body.at[1] = GROUND;  land(body, impact);}/** Sits on the head of a critter under `x, y, z` it falls onto. Returns whether it did. */function onHead(body: Body, bodies: readonly Body[], x: number, y: number, z: number) {  for (const other of bodies) {    if (other === body || !other.posed || other.grown < 0.5 || under(other, body)) continue;    if (other.mode === 'fly') continue;    vec3n.addScaled(other.drawn, other.upright, R, top);    const gap = Math.hypot(x - top[0], z - top[2]);    if (gap < R * 0.9 && y < top[1] + R * 0.5 && y > top[1] - R * 0.8) {      body.mode = 'perch';      body.base = other;      body.perchFor = random(PERCH.shortest, PERCH.longest);      body.spinning = 0;      body.knocked = false;      body.squash.velocity -= 1.5;      return true;    }  }  return false;}/** * Lands at `x, y, z` on the jelly where it faces up. One that hopped on to ride grips and climbs; * any other lands sliding. Returns whether it landed. */function onJelly(body: Body, spine: Float32Array, x: number, y: number, z: number) {  if (Math.abs(z) >= RIDE_Z || x <= -1.02 || x >= 1.0) return false;  nearest(spine, x, y, found);  const short = found.s < spineLength(spine) - 0.06;  const facing = seat(spine, found.s, z, here).normal[1];  if (found.above >= 0.01 || found.above <= -0.06 || !short || facing <= FACING.land) return false;  const clambering = body.wantsRide;  body.mode = 'ride';  body.s = found.s;  body.sliding = clambering ? 0 : std.clamp(body.velocity[0], -0.6, 0.6);  body.gripping = clambering;  body.steep = 0;  body.turn = body.sliding < 0 ? Math.PI : 0;  body.wantsRide = false;  body.plan = 0;  if (clambering) body.rideIn = random(RIDDEN.shortest, RIDDEN.longest);  body.squash.velocity -= Math.abs(body.velocity[1]) * 2;  body.spinning = 0;  body.knocked = false;  return true;}/** Whether `body` stands anywhere under `other` in a stack. */function under(body: Body, other: Body) {  for (let b: Body | undefined = body; b; b = b.base) if (b === other) return true;  return false;}/** Sits on the head of its base until it jumps off, or falls when the base does. */export function perch(body: Body, dt: number) {  const base = body.base;  if (!base?.posed || base.mode === 'fly') {    body.mode = 'fly';    body.base = undefined;    if (base) vec3n.copy(base.velocity, body.velocity);    else vec3n.zero(body.velocity);    body.velocity[1] += 0.3;    return;  }  body.perchFor -= dt;  if (body.perchFor <= 0) {    jump(body, 0.75, Math.random() < 0.6);    vec3n.add(body.velocity, base.velocity, body.velocity);    return;  }  vec3n.copy(body.at, before);  vec3n.addScaled(base.drawn, base.upright, R * 0.98, body.at);  vec3n.scale(vec3n.sub(body.at, before, body.velocity), 1 / dt, body.velocity);  body.heading += turnBetween(body.heading, base.heading) * Math.min(1, dt * 2);  body.mouth = ease(body.mouth, 0.2, 4, dt);}/** * Leaps to the lever's knob and hangs on it until the lever is down, then drops off. Returns * whether it is done with the lever. */export function hang(body: Body, crowd: Crowd, dt: number) {  const { world } = crowd;  vec3n.addScaled(body.at, UP, STANDING, before);  body.pulling += dt;  const u = Math.min(1, body.pulling / HERALD.leap);  vec3n.add(world.knob, HERALD.hang, hanging);  vec3n.copy(hanging, centre);  if (u < 1) {    vec3n.lerp(body.leapFrom, hanging, std.smoothstep(0, 1, u), centre);    vec3n.addScaled(centre, UP, Math.sin(Math.PI * u) * HERALD.arc, centre);  }  vec3n.addScaled(centre, UP, -STANDING, body.at);  vec3n.scale(vec3n.sub(centre, before, body.velocity), 1 / dt, body.velocity);  body.heading = 0;  body.mouth = ease(body.mouth, u < 1 ? 0.7 : 0.35, 8, dt);  if (world.holding) {    // Shaken off by the pointer taking the lever    body.mode = 'fly';    vec3n.set(0, 0.3, 0.4, body.velocity);    body.shock = 1;  } else if (crowd.errand !== body || (u >= 1 && world.pulled > HERALD.done)) {    // Drops off for joy, an ordinary critter from now on    body.mode = 'fly';    vec3n.scale(body.velocity, 0.5, body.velocity);    body.velocity[1] += 0.5;    body.velocity[2] += 0.3;    body.joy = 2;    body.plan = 0;    return true;  }  return false;}/** Follows the pointer to `to`, over the jelly rather than through it. */export function carry(body: Body, to: V3, dt: number, spine: Float32Array) {  vec3n.copy(body.at, before);  vec3n.addScaled(body.at, vec3n.sub(to, body.at, apart), clock.ease(dt, 18), body.at);  if (Math.abs(body.at[2]) < RIDE_Z + 0.05) {    nearest(spine, body.at[0], body.at[1], found);    if (found.above < 0.01 && found.above > -0.05) {      const on = seat(spine, found.s, body.at[2], here);      vec3n.addScaled(on.surface, on.normal, 0.01, body.at);    }  }  vec3n.sub(body.at, before, body.velocity);  vec3n.scale(body.velocity, 1 / Math.max(dt, 1e-4), body.velocity);  body.mouth = 1;  body.heldFor += dt;}/** Pushes overlapping critters apart, and knocks over those that meet fast. */export function collide(bodies: readonly Body[], dt: number) {  for (let i = 0; i < CRITTERS; i++) {    for (let j = i + 1; j < CRITTERS; j++) {      const a = bodies[i]!;      const b = bodies[j]!;      if (!a.posed || !b.posed || a.grown < 0.5 || b.grown < 0.5) continue;      if (a.base === b || b.base === a) continue;      // Walkers meet on the ground where they are now, not where they were last drawn      const walking = a.mode === 'walk' && b.mode === 'walk';      if (walking) vec3n.set(a.at[0] - b.at[0], 0, a.at[2] - b.at[2], apart);      else vec3n.sub(a.drawn, b.drawn, apart);      const distance = vec3n.length(apart);      if (distance >= R * 2 || distance < 1e-5) continue;      vec3n.scale(apart, 1 / distance, direction);      const closing = vec3n.dot(vec3n.sub(a.velocity, b.velocity, relative), direction);      if (closing < -KNOCK.speed) {        const shareA = shareOf(a, b);        const shareB = shareOf(b, a);        const impulse = -(1 + KNOCK.bounce) * closing;        if (shareA) topple(a, vec3n.scale(direction, impulse * shareA, push));        if (shareB) topple(b, vec3n.scale(direction, -impulse * shareB, push));        continue;      }      const overlap = Math.min((R * 2 - distance) * 0.5, STEER.apart * dt);      nudge(a, direction, overlap);      nudge(b, direction, -overlap);    }  }}/** The share of a knock `body` takes from `other`: none while held, all from a held one. */function shareOf(body: Body, other: Body) {  if (body.mode === 'held') return 0;  return other.mode === 'held' ? 1 : 0.5;}/** Moves a walker or a rider `by` along `n`. */function nudge(body: Body, n: V3, by: number) {  if (body.mode === 'walk') {    body.at[0] += n[0] * by;    body.at[2] += n[2] * by;  } else if (body.mode === 'ride') body.s += n[0] * by;}

poses.ts

import { std } from 'typegpu';import { vec3n } from 'wgpu-matrix';import {  CLIMB,  ease,  HERALD,  HIP,  JOINTS,  petted,  R,  random,  STANDING,  UP,  WALK,  type Body,  kick,} from './body.ts';import { createSeat, seat } from './spine.ts';import { GROUND, type V3 } from './tray.ts';import { SHOVE } from './walking.ts';/** Thigh and shin lengths. */const THIGH = 0.032;const SHIN = 0.03;/** * The body's bend: a spring that lags behind its motion by `lag`, leans at most `most` radii, * and sways by `sway` radii at rest. */const BEND = { lag: 0.55, most: 0.8, sway: 0.25 };/** The length of what grows on the head, for each kind. */const TOP = { lengths: [R * 1.05, R * 0.6, R * 0.6] };/** The fastest a joint moves from the body before its jump is eased, and how fast easing fades. */const JUMP = { speed: 1.2, fade: 12 };type V4 = [number, number, number, number];/** One critter as the march draws it, a row of the `Critter` table that `figureOf` fills. */export interface Figure {  readonly body: V4;  /** How far back the body was when the last frame was drawn, which the caller writes. */  readonly moved: V4;  readonly up: V4;  readonly ahead: V4;  readonly gaze: V4;  readonly joints: V4[];  readonly color: V4;  readonly bound: V4;  readonly bend: V4;  readonly face: V4;  readonly top: V4;}const v4 = (): V4 => [0, 0, 0, 0];export const createFigure = (): Figure => ({  body: v4(),  moved: v4(),  up: v4(),  ahead: v4(),  gaze: v4(),  joints: Array.from({ length: JOINTS }, v4),  color: v4(),  bound: v4(),  bend: v4(),  face: v4(),  top: v4(),});/** What a critter looks at and reaches for besides itself. */export interface Sights {  readonly camera: V3;  /** The point the pointer hovers while it is fresh. */  pointer: V3 | undefined;  readonly knob: V3;}const v3 = (): V3 => [0, 0, 0];const v3s = (count: number) => Array.from({ length: count }, v3);const AHEAD: V3 = [0, 0, 1];// Scratch the steps and poses write their vectors into, one critter at a timeconst home = v3();const lead = v3();const gap = v3();const rest = v3();const toward = v3();const settled = createSeat();const contact = createSeat();/** The body's centre and up axis, and which way it faces, as `frameOf` writes them. */const pose = { ahead: v3(), up: v3(), centre: v3() };const [low, high, between] = v3s(3);const [up, side, facing, bend, shown, face, look, aim, tip] = v3s(9);const [hips, shoulders, feet, hands, knees, steps, holds] = Array.from({ length: 7 }, () => v3s(2));const [walkingFoot, walkingHand, heavingFoot, heavingHand, sittingFoot, sittingHand] = v3s(6);const [forward, climb, pole] = v3s(3);const joints = v3s(JOINTS + 1);const placed = v3s(JOINTS + 1);const turned = v3();/** A walking foot's stride, as `climbStep` writes it. */const stride = { ahead: 0, lift: 0 };/** How much of the bend a body point takes, by its height along the up axis, as in figures.ts. */const bendAt = (along: number) => std.clamp((along / R + 1) * 0.5, 0, 1) ** 2;/** Rotates `v` round unit `axis` by `angle` radians into `out`, which may be `v`. */const rotate = (v: V3, axis: V3, angle: number, out: V3) => {  const c = Math.cos(angle);  const along = vec3n.dot(axis, v) * (1 - c);  vec3n.cross(axis, v, turned);  vec3n.scale(v, c, out);  vec3n.addScaled(out, turned, Math.sin(angle), out);  return vec3n.addScaled(out, axis, along, out);};/** * Writes into `out` a knee between hip and foot, bent towards `bendTo`, for two bones of `a` and `b`. */const knee = (hip: V3, foot: V3, a: number, b: number, bendTo: V3, out: V3) => {  vec3n.sub(foot, hip, toward);  const span = Math.min(vec3n.length(toward), a + b - 1e-4);  vec3n.normalize(toward, toward);  const along = (span * span + a * a - b * b) / (2 * span);  const reach = Math.sqrt(Math.max(a * a - along * along, 0));  vec3n.addScaled(bendTo, toward, -vec3n.dot(bendTo, toward), between);  vec3n.normalize(between, between);  vec3n.addScaled(hip, toward, along, out);  return vec3n.addScaled(out, between, reach, out);};/** How far a walking foot sits ahead of the body along the jelly, and how high it lifts. */const climbStep = (cycle: number) => {  const u = cycle - Math.floor(cycle);  if (u < CLIMB.planted) {    stride.ahead = CLIMB.stride * (0.5 - u / CLIMB.planted);    stride.lift = 0;    return stride;  }  const w = (u - CLIMB.planted) / (1 - CLIMB.planted);  stride.ahead = CLIMB.stride * (w - 0.5);  stride.lift = Math.sin(Math.PI * w) * CLIMB.lift;  return stride;};/** Steps a foot towards where it belongs under the hips once the other is planted. */export function gait(body: Body, dt: number, across: V3) {  vec3n.scale(body.velocity, 0.12, lead);  for (let i = 0; i < 2; i++) {    const foot = body.feet[i]!;    vec3n.set(body.at[0], GROUND, body.at[2], home);    vec3n.addScaled(home, across, (i === 0 ? 1 : -1) * R * 0.45, home);    vec3n.add(home, lead, home);    if (foot.t < 1) {      foot.t = Math.min(1, foot.t + dt / WALK.time);      vec3n.lerp(foot.from, foot.to, foot.t, foot.at);      foot.at[1] += Math.sin(Math.PI * foot.t) * WALK.lift;      if (foot.t >= 1) body.squash.velocity -= 0.25;      continue;    }    const other = body.feet[1 - i]!;    if (other.t >= 1 && vec3n.distance(home, foot.at) > WALK.stride) {      vec3n.sub(home, foot.at, gap);      vec3n.copy(foot.at, foot.from);      vec3n.addScaled(home, gap, 0.3, foot.to);      foot.to[1] = GROUND;      foot.t = 0;    }  }}/** How far through its swing foot `i` is, as the height of its arc. */const swingOf = (body: Body, i: number) =>  body.feet[i]!.t < 1 ? Math.sin(Math.PI * body.feet[i]!.t) : 0;/** * The bend lags behind the body's `accel`, sways at rest, leans over the planted foot and into a * petting pointer at `watched`. The squash springs back to 0. */export function springs(  body: Body,  dt: number,  accel: V3,  across: V3,  ahead: V3,  watched: V3 | undefined,) {  const shift = body.mode === 'walk' ? swingOf(body, 1) - swingOf(body, 0) : 0;  const sway =    Math.sin(body.phase * 2.3) * BEND.sway +    shift * 0.55 +    (body.dizzy > 0 ? Math.sin(body.phase * 6) * 0.5 : 0) +    (petted(body) ? Math.sin(body.phase * 9) * 0.25 : 0);  const forth = Math.sin(body.phase * 1.6 + 1) * BEND.sway * 0.5;  vec3n.scale(across, sway * R, rest);  vec3n.addScaled(rest, ahead, forth * R, rest);  if (petted(body) && watched) {    vec3n.set(watched[0] - body.at[0], 0, watched[2] - body.at[2], gap);    vec3n.addScaled(rest, vec3n.normalize(gap, gap), R * 0.5, rest);  }  kick(body.bend, accel, -dt * BEND.lag);  body.bend.step(rest, dt);  body.squash.step(0, dt);}/** Eases how happy, surprised, sleepy and dizzy it looks towards what it is doing. */export function moods(body: Body, dt: number) {  body.shock -= dt;  body.joy -= dt;  const climbing = body.mode === 'ride' && body.gripping;  const effort = climbing && body.steep > 0.5 && body.heave > 0.35 && body.heave < 0.8;  const happy =    body.mode === 'perch' ||    body.mode === 'pull' ||    body.cheer > 0 ||    petted(body) ||    body.joy > 0 ||    body.wave > 0 ||    (body.mode === 'ride' && !body.gripping);  let surprised = 0;  if (body.shock > 0) surprised = 1;  else if (body.mode === 'held') surprised = 0.5;  let sleepy = 0;  if (body.asleep > 0) sleepy = 1;  else if (effort) sleepy = 0.45;  body.mood[0] = ease(body.mood[0], happy && body.shock <= 0 ? 1 : 0, 8, dt);  body.mood[1] = ease(body.mood[1], surprised, 10, dt);  body.mood[2] = ease(body.mood[2], sleepy, body.asleep > 0 ? 2 : 8, dt);  body.mood[3] = ease(body.mood[3], body.dizzy > 0 ? 1 : 0, 6, dt);}/** Rolls on upright out of a flip or tumble that ended early. */export function rollUpright(body: Body, dt: number) {  const upright = Math.round(body.spin / (Math.PI * 2)) * Math.PI * 2;  body.spin = ease(body.spin, upright, 10, dt);  if (Math.abs(upright - body.spin) < 0.02) {    body.spin = 0;    body.spinAxis = undefined;  }}/** Blinks now and then, the lids shutting at once and opening over an eighth of a second. */export function blinks(body: Body, dt: number) {  body.blinkIn -= dt;  if (body.blinkIn <= 0) {    body.blink = 1;    body.blinkIn = random(1.5, 5);  }  body.blink = Math.max(0, body.blink - dt * 8);}/** * Writes into `out` the point on the jelly at arc length `s`, `across` times `out` to the side and * `lift` off it. */const onJelly = (  body: Body,  spine: Float32Array,  s: number,  out: number,  across: number,  lift: number,  into: V3,) => {  const { surface, normal } = seat(spine, s, body.at[2], contact);  vec3n.addScaled(surface, side, out * across, into);  return vec3n.addScaled(into, normal, lift, into);};/** * Hands and feet on the jelly, walking up it, heaving up steep jelly or sitting to slide, into * `steps` and `holds`. */function ridingPose(body: Body, spine: Float32Array, time: number) {  const u = body.heave;  for (let k = 0; k < 2; k++) {    const out = k ? -R : R;    // Walking up: stepping feet and pumping arms    const step = climbStep(body.cycle + k * 0.5);    const pump = Math.sin((body.cycle + k * 0.5) * Math.PI * 2);    onJelly(body, spine, body.s + step.ahead, out, 0.45, R * 0.17 + step.lift, walkingFoot);    vec3n.addScaled(shoulders[k]!, up, -R * 0.45, walkingHand);    vec3n.addScaled(walkingHand, side, out * 0.35, walkingHand);    vec3n.addScaled(walkingHand, facing, pump * R * 0.45, walkingHand);    // Heaving: hands reach to a new hold, then the feet scrabble as the body pulls up    const reaching = u < 0.35;    const handAt = reaching      ? body.grabFrom + (body.grab - body.grabFrom) * std.smoothstep(0, 0.35, u)      : body.grab;    const handLift = reaching ? Math.sin((Math.PI * u) / 0.35) * R * 0.5 : 0;    const pulling = u >= 0.35 && u < 0.8;    const scrabble = pulling ? Math.sin(time * 30 + k * Math.PI) : 0;    onJelly(body, spine, handAt, out, 0.55, R * 0.12 + handLift, heavingHand);    const footAt = body.s - R * 0.45 + scrabble * R * 0.2;    const footLift = R * 0.17 + Math.max(0, scrabble) * R * 0.25;    onJelly(body, spine, footAt, out, 0.45, footLift, heavingFoot);    vec3n.addScaled(hips[k]!, facing, HIP * 0.85, sittingFoot);    vec3n.addScaled(sittingFoot, up, -R * 0.2, sittingFoot);    vec3n.addScaled(shoulders[k]!, side, out * 0.35, sittingHand);    vec3n.addScaled(sittingHand, up, R * 0.95 + Math.sin(time * 18 + k) * 0.006, sittingHand);    vec3n.lerp(walkingFoot, heavingFoot, body.steep, steps[k]!);    vec3n.lerp(steps[k]!, sittingFoot, body.seated, steps[k]!);    vec3n.lerp(walkingHand, heavingHand, body.steep, holds[k]!);    vec3n.lerp(holds[k]!, sittingHand, body.seated, holds[k]!);  }}/** * Eases any joint that jumps from where it was drawn, such as on grabbing the jelly or waving. * Takes the body's centre then its joints in `joints`, and writes them as drawn into `placed`. */function smooth(body: Body, dt: number) {  const [centre] = joints as [V3];  // Joints relative to the centre  for (let j = 1; j <= JOINTS; j++) vec3n.sub(joints[j]!, centre, joints[j]!);  const most = JUMP.speed * dt;  const fade = Math.exp(-dt * JUMP.fade);  for (let j = 0; j <= JOINTS; j++) {    const now = joints[j]!;    const settle = body.settle[j]!;    if (body.posed) {      const expected = j ? body.raw[j]! : vec3n.addScaled(body.raw[0]!, body.velocity, dt, aim);      if (vec3n.distance(now, expected) > most) {        vec3n.add(settle, vec3n.sub(expected, now, gap), settle);      }    }    vec3n.scale(settle, fade, settle);  }  for (let j = 0; j <= JOINTS; j++) vec3n.copy(joints[j]!, body.raw[j]!);  body.posed = true;  vec3n.add(centre, body.settle[0]!, body.drawn);  vec3n.copy(body.drawn, placed[0]!);  for (let j = 1; j <= JOINTS; j++) {    vec3n.add(body.drawn, joints[j]!, placed[j]!);    vec3n.add(placed[j]!, body.settle[j]!, placed[j]!);  }}/** What grows on the head, on a spring from the top of the body at `base`, into `tip`. */function crown(body: Body, base: V3, dt: number) {  const length = TOP.lengths[body.kind]!;  vec3n.addScaled(base, up, length, rest);  const sprung = body.tip.value;  if (!body.tipped) {    vec3n.copy(rest, sprung);    body.tipped = true;  }  body.tip.step(rest, dt);  vec3n.normalize(vec3n.sub(sprung, base, gap), gap);  vec3n.addScaled(base, gap, length, sprung);  return vec3n.copy(sprung, tip);}/** Writes the body's centre and up axis, and which way it faces, for what it is doing into `pose`. */function frameOf(body: Body, spine: Float32Array, knob: V3) {  const { ahead, up: tilt, centre } = pose;  vec3n.set(Math.sin(body.heading), 0, Math.cos(body.heading), ahead);  if (body.mode === 'ride') {    const { normal, tangent, surface } = seat(spine, body.s, body.at[2], settled);    vec3n.scale(tangent, Math.cos(body.turn), ahead);    vec3n.normalize(vec3n.addScaled(ahead, AHEAD, Math.sin(body.turn), ahead), ahead);    const bob = Math.abs(Math.sin(body.cycle * Math.PI * 2)) * 0.004;    // Walking tilt, heaving tilt steep jelly, or sitting    vec3n.normalize(vec3n.addScaled(UP, tangent, 0.25, low), low);    vec3n.normalize(vec3n.addScaled(normal, UP, 0.4, high), high);    vec3n.lerp(low, high, body.steep, tilt);    vec3n.normalize(vec3n.lerp(tilt, normal, body.seated, tilt), tilt);    vec3n.addScaled(surface, UP, STANDING * 0.95 + bob, low);    vec3n.addScaled(surface, normal, R * 1.1, high);    vec3n.lerp(low, high, body.steep, centre);    vec3n.lerp(centre, vec3n.addScaled(surface, normal, R * 0.95, low), body.seated, centre);    return pose;  }  if (body.mode === 'perch' && body.base) {    vec3n.normalize(vec3n.addScaled(body.base.upright, body.base.bend.value, 4, tilt), tilt);    vec3n.addScaled(body.at, tilt, R * 0.9, centre);    return pose;  }  if (body.mode === 'walk' && body.asleep > 0) {    vec3n.copy(UP, tilt);    vec3n.addScaled(body.at, UP, R * 0.92, centre);    return pose;  }  if (body.mode === 'pull') {    // Upright through the leap, then hanging from the knob, facing away from the lever    vec3n.addScaled(body.at, UP, STANDING, centre);    const hanging = std.smoothstep(0.5, 1, body.pulling / HERALD.leap);    vec3n.copy(AHEAD, ahead);    vec3n.normalize(vec3n.sub(knob, centre, high), high);    vec3n.normalize(vec3n.lerp(UP, high, hanging, tilt), tilt);    return pose;  }  vec3n.set(body.velocity[0] * 0.8, 1, body.velocity[2] * 0.8, tilt);  vec3n.normalize(tilt, tilt);  let bob = 0;  if (body.mode === 'walk') {    bob = Math.max(body.feet[0].at[1] - GROUND, body.feet[1].at[1] - GROUND);  }  vec3n.copy(body.at, centre);  centre[1] += bob * 0.5;  vec3n.addScaled(centre, tilt, STANDING, centre);  return pose;}/** * Where the feet are off the jelly, into `feet`: out in front sitting, walking, tucked into a flip, * or dangling and kicking, flailing while tumbling. */function feetOf(body: Body, time: number) {  for (let k = 0; k < 2; k++) {    const hip = hips[k]!;    const foot = feet[k]!;    if ((body.mode === 'walk' && body.asleep > 0) || body.mode === 'perch') {      // Swinging a little on a friend's head      const swing = body.mode === 'perch' ? 1 : 0;      vec3n.addScaled(hip, facing, HIP * 0.85, foot);      vec3n.addScaled(foot, up, -R * 0.25 + Math.sin(time * 4 + k * 2) * swing * 0.006, foot);    } else if (body.mode === 'walk') {      vec3n.copy(body.feet[k]!.at, foot);    } else if (body.spinning && !body.spinAxis) {      vec3n.addScaled(hip, up, -HIP * 0.45, foot);      vec3n.addScaled(foot, facing, HIP * 0.3, foot);    } else {      const pace = body.spinAxis ? 30 : 16;      vec3n.addScaled(hip, up, -HIP * 0.85, foot);      vec3n.addScaled(foot, facing, Math.sin(time * pace + k * 2) * 0.012, foot);    }  }}/** Writes into `out` shoulder `k` reaching out `reach` radii to its side. */const outward = (k: number, reach: number, out: V3) =>  vec3n.addScaled(shoulders[k]!, side, (k ? -1 : 1) * reach * R, out);/** Where hand `k` goes for what the critter is doing, into `hands`. */function handOf(body: Body, i: number, k: number, time: number, knob: V3) {  const hand = hands[k]!;  const riding = body.mode === 'ride';  const asleep = body.mode === 'walk' && body.asleep > 0;  // Cheering, which pulling and flying start from  outward(k, 0.35, aim);  vec3n.addScaled(aim, up, R * 0.95 + Math.sin(time * 18 + k * 1.3) * 0.006, aim);  if (riding) {    vec3n.copy(holds[k]!, hand);  } else if (asleep) {    vec3n.addScaled(outward(k, -0.5, hand), facing, R * 0.8, hand);    vec3n.addScaled(hand, up, -R * 0.3, hand);  } else if (body.mode === 'pull') {    vec3n.addScaled(knob, side, (k ? -1 : 1) * R * 0.3, hand);    vec3n.lerp(aim, hand, std.smoothstep(0.3, 0.9, body.pulling / HERALD.leap), hand);  } else if (body.mode === 'perch') {    // Holding on to the head below, waving now and then    const wave = Math.max(0, Math.sin(time * 1.3 + i)) * (k ? 0 : 1);    vec3n.addScaled(outward(k, 0.2, hand), up, -R * 0.9 + wave * R * 2.1, hand);    vec3n.addScaled(hand, facing, R * 0.3, hand);  } else if (body.mode !== 'walk') {    if (body.spinAxis) vec3n.addScaled(aim, side, Math.sin(time * 25 + k * 3) * R * 0.5, hand);    else vec3n.copy(aim, hand);  } else if (body.shove > 0) {    vec3n.addScaled(outward(k, 0.45, hand), up, R * 0.05, hand);    vec3n.addScaled(hand, facing, body.shove < SHOVE.windup ? 0 : R, hand);  } else if (body.wave > 0 && k === 0) {    vec3n.addScaled(outward(k, 0.3 + Math.sin(time * 16) * 0.3, hand), up, R * 1.3, hand);  } else if (body.load > 0) {    vec3n.addScaled(outward(k, 0.4, hand), up, R, hand);  } else if (body.cheer > 0) {    const lift = R * (1.1 + Math.sin(time * 14 + k * 2) * 0.25);    vec3n.addScaled(outward(k, 0.4, hand), up, lift, hand);  } else if (body.dizzy > 0) {    vec3n.addScaled(outward(k, 0.9, hand), up, Math.sin(time * 6 + k * 2) * R * 0.4, hand);  } else if (body.flee > 0) {    const lift = R * 1.2 + Math.sin(time * 20 + k * 3) * R * 0.2;    vec3n.addScaled(outward(k, 0.4, hand), up, lift, hand);  } else {    // Arms swing against the legs    const swing = vec3n.dot(vec3n.sub(feet[1 - k]!, hips[1 - k]!, gap), facing);    vec3n.addScaled(outward(k, 0.45, hand), up, -R * 0.55, hand);    vec3n.addScaled(hand, facing, swing * 0.6 + 0.004, hand);  }}/** Bends `p` in place by its height along the body from `centre`. */const bendIn = (p: V3, centre: V3) =>  vec3n.addScaled(p, bend, bendAt(vec3n.dot(vec3n.sub(p, centre, gap), up)), p);/** Writes `v` grown in from the ground under the body by `grown`, and `w`, into `out`. */const put = (body: Body, v: V3, w: number, out: V4) => {  vec3n.sub(v, body.at, gap);  vec3n.addScaled(body.at, gap, body.grown, out);  out[3] = w;};/** * Poses critter `i` for drawing into its `figure`: the body bent and squashed by its springs, its * legs and arms placed for what it is doing, and its eyes on what it looks at. */export function figureOf(  body: Body,  i: number,  time: number,  dt: number,  spine: Float32Array,  sights: Sights,  figure: Figure,) {  const { knob } = sights;  const g = body.grown;  const riding = body.mode === 'ride';  const asleep = body.mode === 'walk' && body.asleep > 0;  const { centre } = frameOf(body, spine, knob);  vec3n.copy(pose.up, up);  vec3n.normalize(vec3n.cross(up, pose.ahead, side), side);  vec3n.normalize(vec3n.cross(side, up, facing), facing);  if (body.spin) {    const axis = body.spinAxis ?? side;    rotate(up, axis, body.spin, up);    rotate(facing, axis, body.spin, facing);    vec3n.normalize(vec3n.cross(up, facing, side), side);  }  vec3n.copy(up, body.upright);  const bent = body.bend.value;  vec3n.addScaled(bent, up, -vec3n.dot(bent, up), bend);  if (vec3n.length(bend) > BEND.most * R) {    vec3n.scale(vec3n.normalize(bend, bend), BEND.most * R, bend);  }  for (let k = 0; k < 2; k++) {    const out = k ? -1 : 1;    vec3n.addScaled(centre, side, out * R * 0.45, hips[k]!);    bendIn(vec3n.addScaled(hips[k]!, up, -R * 0.55, hips[k]!), centre);    bendIn(vec3n.addScaled(centre, side, out * R * 0.85, shoulders[k]!), centre);  }  if (riding) {    ridingPose(body, spine, time);    for (let k = 0; k < 2; k++) vec3n.copy(steps[k]!, feet[k]!);  } else {    feetOf(body, time);  }  for (let k = 0; k < 2; k++) {    const out = k ? -1 : 1;    vec3n.addScaled(facing, side, out * 0.3, forward);    vec3n.addScaled(up, side, out * 0.7, climb);    vec3n.lerp(forward, climb, body.steep, climb);    if (riding) vec3n.lerp(climb, forward, body.seated, pole);    else vec3n.copy(forward, pole);    knee(hips[k]!, feet[k]!, THIGH, SHIN, pole, knees[k]!);  }  for (let k = 0; k < 2; k++) handOf(body, i, k, time, knob);  gazeOf(body, centre, time, sights);  vec3n.copy(centre, joints[0]!);  for (let k = 0; k < 2; k++) {    vec3n.copy(hips[k]!, joints[1 + k * 3]!);    vec3n.copy(knees[k]!, joints[2 + k * 3]!);    vec3n.copy(feet[k]!, joints[3 + k * 3]!);    vec3n.copy(shoulders[k]!, joints[7 + k * 2]!);    vec3n.copy(hands[k]!, joints[8 + k * 2]!);  }  smooth(body, dt);  vec3n.copy(placed[0]!, shown);  vec3n.addScaled(shown, facing, R * 0.72, face);  vec3n.addScaled(face, up, R * 0.3, face);  // Stretching to reach and squashing at the end of each heave, and breathing in a nap  let squash = body.squash.value;  if (riding && body.gripping && body.steep > 0.5) {    const u = body.heave;    squash +=      u < 0.35 ? Math.sin((Math.PI * u) / 0.35) * 0.15 : -std.smoothstep(0.35, 0.8, u) * 0.12;  }  if (asleep) squash += Math.sin(time * 2.4 + i) * 0.06;  squash = std.clamp(squash, -0.35, 0.35);  vec3n.addScaled(shown, up, R * 0.95 * (1 + squash), low);  crown(body, vec3n.add(low, bend, low), dt);  // The bound wraps the bent, squashed body with its eyes, each joint with its foot or hand,  // and the tip with its ball or splayed ears  let reach = R * (1 + Math.abs(squash)) * 1.3 + vec3n.length(bend);  for (let j = 1; j <= JOINTS; j++) {    reach = Math.max(reach, vec3n.distance(placed[j]!, shown) + R * 0.2);  }  reach = Math.max(reach, vec3n.distance(tip, shown) + R);  put(body, shown, R * g, figure.body);  vec3n.copy(up, figure.up);  figure.up[3] = squash;  vec3n.copy(facing, figure.ahead);  figure.ahead[3] = body.blink;  put(body, vec3n.add(face, look, face), body.mouth, figure.gaze);  for (let j = 0; j < JOINTS; j++) put(body, placed[j + 1]!, 0, figure.joints[j]!);  vec3n.copy(body.color, figure.color);  figure.color[3] = g;  put(body, shown, reach * g, figure.bound);  vec3n.scale(bend, g, figure.bend);  for (let m = 0; m < 4; m++) figure.face[m] = body.mood[m]!;  put(body, tip, body.kind, figure.top);  return figure;}/** * Which way the pupils look, into `look`: along its way while riding or flying, else at what holds * its eye. */function gazeOf(body: Body, centre: V3, time: number, sights: Sights) {  const eyes = vec3n.addScaled(centre, facing, R * 0.72, high);  vec3n.addScaled(eyes, up, R * 0.3, eyes);  const riding = body.mode === 'ride';  let target: V3 = sights.camera;  if (riding && body.gripping) {    vec3n.addScaled(centre, vec3n.addScaled(facing, up, 0.3, aim), 2, aim);    target = aim;  } else if (riding || body.mode === 'fly') {    vec3n.copy(facing, aim);    aim[1] -= 0.2;    vec3n.addScaled(centre, aim, 2, aim);    target = aim;  } else if (body.wave > 0 && body.friend) {    vec3n.copy(body.friend.at, aim);    aim[1] += HIP + R;    target = aim;  } else if (body.cheer > 0 && body.fan?.posed) target = body.fan.drawn;  else if (body.victim?.posed) target = body.victim.drawn;  else if (body.grudge?.posed) target = body.grudge.drawn;  else if (sights.pointer) target = sights.pointer;  vec3n.normalize(vec3n.sub(target, eyes, look), look);  if (body.dizzy > 0) {    vec3n.scale(side, Math.cos(time * 9), aim);    vec3n.addScaled(aim, up, Math.sin(time * 9), aim);    vec3n.normalize(vec3n.addScaled(facing, aim, 0.8, look), look);  }  // Pupils stay on the front of the eyes, however far behind the target is  const ahead = vec3n.dot(look, facing);  if (ahead < 0.4) {    vec3n.normalize(vec3n.addScaled(look, facing, -ahead, look), look);    vec3n.scale(look, 0.92, look);    vec3n.addScaled(look, facing, 0.4, look);  }  return look;}

spine.ts

import { std } from 'typegpu';import { POINTS, SKIN } from './slider.ts';import type { V3 } from './tray.ts';/** A point along the spine and its tangent and normal, as `sample` writes them. */const along = { x: 0, y: 0, tx: 1, ty: 0, nx: 0, ny: 1 };/** The normals of a segment and of those before and after it. */const normals = { here: [0, 0], before: [0, 0], after: [0, 0] };/** Writes the unit normal of segment `i`, clamped to the spine's first and last, into `out`. */const segmentNormal = (spine: Float32Array, i: number, out: number[]) => {  const k = std.clamp(i, 0, POINTS - 2);  const dx = spine[k * 2 + 2]! - spine[k * 2]!;  const dy = spine[k * 2 + 3]! - spine[k * 2 + 1]!;  const segment = Math.hypot(dx, dy) || 1e-6;  out[0] = -dy / segment;  out[1] = dx / segment;};/** * Point, tangent and normal at arc length `s` along a spine of xy points, written into `along`. The * normal blends between the segments' normals at each point, so it turns smoothly along the spine. */const sample = (spine: Float32Array, s: number) => {  let left = Math.max(s, 0);  for (let i = 0; i < POINTS - 1; i++) {    const ax = spine[i * 2]!;    const ay = spine[i * 2 + 1]!;    const dx = spine[i * 2 + 2]! - ax;    const dy = spine[i * 2 + 3]! - ay;    const segment = Math.hypot(dx, dy) || 1e-6;    if (left <= segment || i === POINTS - 2) {      const t = Math.min(left / segment, 1);      const { here, before, after } = normals;      segmentNormal(spine, i, here);      segmentNormal(spine, i - 1, before);      segmentNormal(spine, i + 1, after);      const fromX = (here[0]! + before[0]!) * (1 - t) + (here[0]! + after[0]!) * t;      const fromY = (here[1]! + before[1]!) * (1 - t) + (here[1]! + after[1]!) * t;      const n = Math.hypot(fromX, fromY) || 1e-6;      along.nx = fromX / n;      along.ny = fromY / n;      along.x = ax + dx * t;      along.y = ay + dy * t;      along.tx = along.ny;      along.ty = -along.nx;      return;    }    left -= segment;  }  along.x = along.y = along.ty = along.nx = 0;  along.tx = along.ny = 1;};export const spineLength = (spine: Float32Array) => {  let total = 0;  for (let i = 0; i < POINTS - 1; i++) {    total += Math.hypot(spine[i * 2 + 2]! - spine[i * 2]!, spine[i * 2 + 3]! - spine[i * 2 + 1]!);  }  return total;};/** The spine point nearest a point, as `nearest` finds it. */export interface Nearest {  /** Its arc length along the spine. */  s: number;  /** How far above the jelly's top the point is. */  above: number;}/** Finds the spine point nearest `x, y`, into `out`, and returns `out`. */export const nearest = (spine: Float32Array, x: number, y: number, out: Nearest) => {  let best = Infinity;  let at = 0;  let above = 0;  let travelled = 0;  for (let i = 0; i < POINTS - 1; i++) {    const ax = spine[i * 2]!;    const ay = spine[i * 2 + 1]!;    const dx = spine[i * 2 + 2]! - ax;    const dy = spine[i * 2 + 3]! - ay;    const segment = Math.hypot(dx, dy) || 1e-6;    const t = std.clamp(((x - ax) * dx + (y - ay) * dy) / (segment * segment), 0, 1);    const px = ax + dx * t;    const py = ay + dy * t;    const gap = Math.hypot(x - px, y - py);    if (gap < best) {      best = gap;      at = travelled + segment * t;      above = ((x - px) * -dy + (y - py) * dx) / segment;    }    travelled += segment;  }  out.s = at;  out.above = above - SKIN;  return out;};/** Where a critter's body sits on the jelly's top, as `seat` writes it. */export interface Seat {  readonly surface: V3;  readonly normal: V3;  readonly tangent: V3;}export const createSeat = (): Seat => ({  surface: [0, 0, 0],  normal: [0, 0, 0],  tangent: [0, 0, 0],});/** * Writes where a critter's body sits on the jelly's top, which is the spine's left side, at arc * length `s` and `z` across into `out`, and returns `out`. */export const seat = (spine: Float32Array, s: number, z: number, out: Seat) => {  sample(spine, s);  const { surface, normal, tangent } = out;  surface[0] = along.x + along.nx * SKIN;  surface[1] = along.y + along.ny * SKIN;  surface[2] = z;  normal[0] = along.nx;  normal[1] = along.ny;  normal[2] = 0;  tangent[0] = along.tx;  tangent[1] = along.ty;  tangent[2] = 0;  return out;};

walking.ts

import { std } from 'typegpu';import { vec3n } from 'wgpu-matrix';import {  crossed,  ease,  FALL,  HERALD,  LADDER,  LAND,  PET,  petted as isPetted,  R,  SHOO,  STANDING,  startle,  topple,  UP,  WALK,  jump,  type Body,  type Crowd,  random,  kick,} from './body.ts';import { createSeat, nearest, seat } from './spine.ts';import { asideOf, blockOf, keepOut, passing, routeOf, STEER, stride, turnTo } from './steering.ts';import { GROUND, type V3 } from './tray.ts';/** How far critters come to look at the pointer, how close they stop, and bounces per second. */const CURIOUS = { reach: 0.55, near: 0.13, bounce: 0.35 };/** How close two critters come before they wave, for how long, and the least time between. */const GREET = { reach: R * 5, time: 1.4, again: 9 };/** Naps: chance per second while idle, and how long they last. */const SLEEP = { chance: 0.04, shortest: 6, longest: 12 };/** Follow the leader: chance per plan, followers, how far they look, spacing and duration. */const TRAIN = { chance: 0.18, size: 3, reach: 1, gap: R * 3.2, time: 14 };/** Shoves at a critter in the way. */export const SHOVE = {  /** Seconds blocked before one, and its chance then. */  after: 0.7,  chance: 0.4,  /** Seconds of its wind-up, and of all of it. */  windup: 0.35,  time: 0.7,  /** How fast the shover lunges and the victim stumbles. */  lunge: 0.2,  push: 0.25,  /** The chance it knocks the victim over, and how hard it tips and lifts it then. */  knock: 0.25,  tip: 0.15,  lift: 0.5,  /** Seconds between shoves, and the chance of a shove back. */  again: [10, 20],  back: 0.35,} as const;/** * Climbing onto the jelly, only near its fixed end, never near the free end where it rears up and * throws riders. */const BOARD = {  /** The chance a plan is to ride it, from the timidest critter to the boldest. */  chance: [0.35, 0.8],  /** The chance it goes up the approach at the fixed end, rather than from beside the jelly. */  approach: 0.3,  /** The world x beside the jelly it climbs on from, nearest where it is, and z off its middle. */  beside: [-0.9, -0.45],  edge: 0.29,  /** Near it, it hesitates up to `hesitate` seconds, then dares at `dare`, up to 1 by nerve. */  near: 0.3,  hesitate: 1.2,  dare: 0.6,  /** Seconds a hop takes, onto a top at most `reach` along, `step` up and facing up `flat`. */  hop: 0.5,  reach: 0.55,  step: 0.07,  flat: 0.3,  /** The most it lands off the jelly's middle. */  lands: 0.09,  /** Bold critters run the last `runUp` metres faster. */  runUp: 0.35,} as const;/** * Where a walker heads this step, within the lawn, how near it stops and how fast it goes over a * walk, whether it comes to look at the pointer, and the critter it follows. */const intent = {  goal: [0, 0] as [number, number],  stop: 0,  pace: 1,  curious: false,  leader: undefined as Body | undefined,};/** The heading a walker steers in, and whether it stops there or queues. */const steered = { want: 0, arrived: false, waiting: false };// Scratch the steps below write their vectors intoconst forward: V3 = [0, 0, 0];const push: V3 = [0, 0, 0];const found = { s: 0, above: 0 };const top = createSeat();/** The heading from `from` towards `x, z`. */const facingTo = (from: V3, x: number, z: number) => Math.atan2(x - from[0], z - from[2]);/** One step of a critter on the ground. */export function walk(body: Body, crowd: Crowd, dt: number) {  countDown(body, dt);  const x = body.at[0];  const z = body.at[2];  const due = crowd.errand === body;  const { pointer, pointerSpeed } = crowd;  const near = pointer ? Math.hypot(pointer[0] - x, pointer[2] - z) : Infinity;  if (reacts(body, crowd, near, dt)) return;  body.petted = pointer && near < PET.reach && pointerSpeed < PET.still ? body.petted + dt : 0;  const petted = isPetted(body);  if (body.plan <= 0 && !due) plan(body, crowd);  intentOf(body, crowd, near, due, petted);  const { curious, leader } = intent;  const sociable = !body.wantsRide && !curious && !leader && !due;  if (body.wave <= 0 && body.greeted <= 0 && sociable && body.grown > 0.5) greet(body, crowd);  const { way, distance, toGoal } = routeOf(body, intent.goal, crowd.front, due);  const { stop } = intent;  if (toGoal < stop) body.arrived = true;  else if (toGoal > stop + Math.max(0.04, stop * 0.5)) body.arrived = false;  if (!steer(body, crowd, way, distance, toGoal, due, dt)) return;  const { arrived, waiting } = steered;  const aside = asideOf(body, crowd.bodies, leader);  const turn = turnTo(body, headingOf(body, steered.want, arrived, due, petted, pointer), dt);  let speed =    arrived || waiting || body.wave > 0 || petted ? 0 : WALK.speed * body.grown * intent.pace;  if (body.wantsRide && toGoal < BOARD.runUp) speed *= 1 + body.nerve * 0.6;  if (body.load > 0) speed *= 0.75;  if (body.dizzy > 0) {    speed *= 0.4;    body.heading += Math.sin(body.phase * 6) * 3 * dt;  }  stride(body, crowd, aside, speed * Math.max(0, Math.cos(turn)), dt);  let mouth = 0;  if (body.flee > 0) mouth = 0.9;  else if (body.cheer > 0) mouth = 0.8;  else if (body.wave > 0) mouth = 0.5;  else if (curious && arrived) mouth = 0.3;  body.mouth = ease(body.mouth, mouth, 6, dt);  const settled =    arrived &&    vec3n.length(aside) < 0.01 &&    body.wave <= 0 &&    body.dizzy <= 0 &&    !petted &&    !leader &&    !due &&    body.cheer <= 0 &&    body.load === 0 &&    body.grown > 0.9;  // Beside its spot or the critters there, it climbs on rather than waits  if (body.wantsRide && arrived && toGoal < BOARD.near) brink(body, crowd, turn, dt);  else if (settled && idles(body, curious, dt)) return;  if (due && arrived && Math.abs(turn) < 0.3 && !crowd.world.holding) leap(body);}function countDown(body: Body, dt: number) {  body.plan -= dt;  body.greeted -= dt;  body.wave -= dt;  body.flee -= dt;  body.cheer -= dt;  body.train -= dt;  body.shoveIn -= dt;  body.rideIn -= dt;  // A line breaks up when it ends, or when its leader stops walking other than onto the jelly  const mode = body.leader?.mode;  if (body.train <= 0 || (mode !== 'walk' && mode !== 'ride')) body.leader = undefined;}/** Naps, startles and shoves, which take over from walking. Returns whether one did. */function reacts(body: Body, crowd: Crowd, near: number, dt: number) {  const { pointer, pointerSpeed } = crowd;  if (body.asleep > 0) {    body.asleep -= dt;    vec3n.zero(body.velocity);    body.mouth = ease(body.mouth, 0, 4, dt);    if (pointer && near < SHOO.reach && pointerSpeed > PET.still) startle(body, pointer);    return true;  }  if (pointer && pointerSpeed > SHOO.speed && near < SHOO.reach && body.flee <= 0) {    startle(body, pointer);    return true;  }  if (body.shove > 0) {    shoving(body, crowd, dt);    return true;  }  // Shoves back once it has caught its balance  const grudge = body.grudge;  if (grudge && vec3n.length(body.stumble) < 0.03) {    body.grudge = undefined;    const gap = Math.hypot(grudge.at[0] - body.at[0], grudge.at[2] - body.at[2]);    if (grudge.mode === 'walk' && gap < STEER.spacing + R * 2) startShove(body, grudge);  }  return false;}/** Starts a shove at `victim`: a lean back, then a lunge with both hands. */function startShove(body: Body, victim: Body) {  body.shove = 1e-3;  body.victim = victim;  body.blocked = 0;  body.joy = SHOVE.time + 0.6;  kick(body.bend, vec3n.set(Math.sin(body.heading), 0, Math.cos(body.heading), forward), -0.3);  body.squash.velocity -= 1;}/** Turns to the victim, winds up and lunges, then waits a while before it shoves again. */function shoving(body: Body, crowd: Crowd, dt: number) {  const victim = body.victim;  body.shove += dt;  if (victim) turnTo(body, facingTo(body.at, victim.at[0], victim.at[2]), dt, WALK.turn * 2);  vec3n.set(Math.sin(body.heading), 0, Math.cos(body.heading), forward);  const lunge = body.shove > SHOVE.windup ? Math.max(0, 1 - (body.shove - SHOVE.windup) / 0.2) : 0;  vec3n.scale(forward, lunge * SHOVE.lunge, push);  vec3n.lerp(body.velocity, push, Math.min(1, dt * 20), body.velocity);  vec3n.addScaled(body.at, body.velocity, dt, body.at);  body.at[1] = GROUND;  keepOut(body, crowd.world.obstacles, dt);  body.mouth = ease(body.mouth, 0.6, 8, dt);  if (crossed(body.shove, dt, SHOVE.windup)) {    kick(body.bend, forward, 0.7);    if (victim) shoved(victim, body);  }  if (body.shove >= SHOVE.time) {    body.shove = 0;    body.victim = undefined;    body.shoveIn = random(SHOVE.again[0], SHOVE.again[1]);  }}/** Stumbles away from a shove by `shover`, or now and then topples, and may come back to shove. */function shoved(victim: Body, shover: Body) {  const gap = Math.hypot(victim.at[0] - shover.at[0], victim.at[2] - shover.at[2]);  // A critter still surprised by the last shove or bump takes no other  if (victim.mode !== 'walk' || victim.shock > 0 || gap >= STEER.spacing + R) return;  vec3n.set(victim.at[0] - shover.at[0], 0, victim.at[2] - shover.at[2], push);  vec3n.normalize(push, push);  if (Math.random() < SHOVE.knock) {    topple(victim, vec3n.scale(push, SHOVE.tip, push), SHOVE.lift);    return;  }  vec3n.scale(push, SHOVE.push, victim.stumble);  victim.shock = 1.2;  victim.asleep = 0;  victim.wave = 0;  victim.squash.velocity -= 1.5;  // Only one free to shove shoves back, so a shove back draws none  const back = victim.shoveIn <= 0 && Math.random() < SHOVE.back;  victim.grudge = back ? shover : undefined;}/** Picks somewhere to go: onto the jelly, more often the bolder it is, or somewhere on the lawn. */function plan(body: Body, crowd: Crowd) {  if (body.leader) {    body.plan = 1;    return;  }  const { front } = crowd;  const chance = BOARD.chance[0] + (BOARD.chance[1] - BOARD.chance[0]) * body.nerve;  body.wantsRide = false;  if (front > LADDER + 0.4 && body.rideIn <= 0 && Math.random() < chance) wantRide(body);  else lawnSpot(front, body.goal);  // Long enough to get there, so riders reach the ladder  const distance = Math.hypot(body.goal[0] - body.at[0], body.goal[1] - body.at[2]);  body.plan = (body.wantsRide ? 2 : 1) * (distance / WALK.speed) + random(1, 3);  if (Math.random() < TRAIN.chance) startTrain(body, crowd);}/** Sets out to ride the jelly, up the approach or from beside it nearest where it is. */function wantRide(body: Body) {  body.wantsRide = true;  body.brink = 0;  const along = std.clamp(body.at[0] + random(-0.15, 0.15), BOARD.beside[0], BOARD.beside[1]);  if (Math.random() < BOARD.approach) approach(body.goal);  else {    body.goal[0] = along;    body.goal[1] = Math.sign(body.at[2] || 1) * BOARD.edge;  }}/** Writes where critters climb on at the jelly's fixed end into `goal`. */const approach = (goal: [number, number]) => {  goal[0] = LADDER;  goal[1] = random(-0.1, 0.1);};/** Writes somewhere on the lawn behind the front into `goal`. */function lawnSpot(front: number, goal: [number, number]) {  const right = std.clamp(front - 0.3, LAND.x[0], LAND.x[1]);  goal[0] = random(LAND.x[0], right);  goal[1] = random(LAND.z[0], LAND.z[1]);}/** Whether `b` is free to follow `leader` in a line. */const followsFreely = (b: Body, leader: Body, crowd: Crowd) =>  b !== leader &&  b !== crowd.herald &&  b.mode === 'walk' &&  !b.leader &&  b.asleep <= 0 &&  !b.wantsRide &&  b.grown > 0.5 &&  Math.hypot(b.at[0] - leader.at[0], b.at[2] - leader.at[2]) < TRAIN.reach;/** Lines up to `TRAIN.size` free critters behind `leader`, nearest first. */function startTrain(leader: Body, crowd: Crowd) {  let ahead = leader;  for (let n = 0; n < TRAIN.size; n++) {    let next: Body | undefined;    let least = Infinity;    for (const b of crowd.bodies) {      if (!followsFreely(b, leader, crowd)) continue;      const gap = Math.hypot(b.at[0] - leader.at[0], b.at[2] - leader.at[2]);      if (gap < least) {        least = gap;        next = b;      }    }    if (!next) break;    next.leader = ahead;    next.train = TRAIN.time;    ahead = next;  }  if (ahead !== leader) leader.plan = Math.max(leader.plan, 6);}/** Works out the walker's `intent` for this step. */function intentOf(body: Body, crowd: Crowd, near: number, due: boolean, petted: boolean) {  const { pointer } = crowd;  const leader = body.leader;  const curious =    pointer !== undefined &&    !body.wantsRide &&    body.dizzy <= 0 &&    !leader &&    !due &&    near < CURIOUS.reach;  aimOf(body, crowd, due, petted, curious ? pointer : undefined);  // Whatever it heads for lies on the lawn, so it turns back short of the lawn's edge  const { goal } = intent;  goal[0] = std.clamp(goal[0], LAND.x[0], LAND.x[1]);  goal[1] = std.clamp(goal[1], LAND.z[0], LAND.z[1]);  intent.curious = curious;  intent.leader = leader;}/** Sets where the walker heads in `intent`, how near it stops there and how fast it goes. */const aim = (x: number, z: number, stop: number, pace: number) => {  intent.goal[0] = x;  intent.goal[1] = z;  intent.stop = stop;  intent.pace = pace;};/** * Where it heads, first of: away from what startled it, to the lever, staying put while petted or * cheering, after its leader, to the pointer it came to see, or where it planned. */function aimOf(body: Body, crowd: Crowd, due: boolean, petted: boolean, seen: V3 | undefined) {  const x = body.at[0];  const z = body.at[2];  const leader = body.leader;  if (body.flee > 0) {    vec3n.set(x - body.fleeFrom[0], 0, z - body.fleeFrom[2], push);    vec3n.normalize(push, push);    aim(x + push[0] * 0.5, z + push[2] * 0.5, 0.05, 1.8);  } else if (due) {    const knob = crowd.world.knob;    aim(knob[0], knob[2] + HERALD.stand, R, HERALD.pace);  } else if (petted || (body.cheer > 0 && body.fan)) {    aim(x, z, 0.05, 1);  } else if (leader?.mode === 'ride' && !body.wantsRide) {    // Following the leader up the jelly, setting out once this step is done    aim(body.goal[0], body.goal[1], 0.05, 1);    wantRide(body);    body.plan = 30;    body.leader = undefined;  } else if (leader) {    // On the line from the leader, so the train trails it however it turns    vec3n.set(x - leader.at[0], 0, z - leader.at[2], push);    vec3n.normalize(push, push);    aim(leader.at[0] + push[0] * TRAIN.gap, leader.at[2] + push[2] * TRAIN.gap, R * 0.6, 1.15);  } else if (seen) {    aim(seen[0], seen[2], CURIOUS.near, 1);  } else {    aim(body.goal[0], body.goal[1], 0.05, 1);  }}/** Waves with the first free critter near enough, which waves back. */function greet(body: Body, crowd: Crowd) {  for (const other of crowd.bodies) {    if (other === body || other.mode !== 'walk' || other.grown < 0.5) continue;    if (other.wave > 0 || other.greeted > 0 || other.wantsRide || other.asleep > 0) continue;    if (Math.hypot(other.at[0] - body.at[0], other.at[2] - body.at[2]) > GREET.reach) continue;    waveAt(body, other);    waveAt(other, body);    return;  }}const waveAt = (body: Body, friend: Body) => {  body.wave = GREET.time;  body.friend = friend;  body.greeted = GREET.again + random(0, 5);};/** * Writes into `steered` the heading to walk in round what blocks the way, and whether to stop * there or queue. It stops where its goal is taken or at the edge of a crowd round it, queues * behind a still critter nearer the goal, and passes on a side it keeps. Blocked for long, it tries * the other side, shoves, and at last goes elsewhere. Returns false when it starts a shove. */function steer(  body: Body,  crowd: Crowd,  way: readonly [number, number],  distance: number,  toGoal: number,  due: boolean,  dt: number,) {  const { goal, stop, curious, leader } = intent;  const block = blockOf(body, crowd, way, distance, leader);  steered.want = Math.atan2(way[0], way[1]);  steered.arrived = body.arrived;  steered.waiting = false;  body.dodgeFor -= dt;  if (!block) {    body.blocked = Math.max(0, body.blocked - dt);    return true;  }  const { other, room } = block;  const gap = Math.hypot(block.x - body.at[0], block.z - body.at[2]);  const close = gap < room + R;  const taken = Math.hypot(goal[0] - block.x, goal[1] - block.z) < room;  const ahead = other && Math.hypot(goal[0] - other.at[0], goal[1] - other.at[2]) < toGoal;  if (close && (taken || (ahead && toGoal < stop + STEER.spacing))) steered.arrived = true;  else if (close && ahead && vec3n.length(other.velocity) < 0.08 && toGoal < stop + 0.3) {    steered.waiting = true;  }  const { arrived, waiting } = steered;  // Blocked counts while it waits or barely moves  const stuck = !arrived && (waiting || vec3n.length(body.velocity) < 0.05);  body.blocked = stuck ? body.blocked + dt : Math.max(0, body.blocked - dt);  if (!arrived && !waiting) steered.want = passing(body, block, steered.want, gap, dt);  if (other && close && !arrived && crossed(body.blocked, dt, SHOVE.after) && body.shoveIn <= 0) {    if (due || Math.random() < SHOVE.chance) {      startShove(body, other);      return false;    }  }  if (body.blocked > STEER.give && !curious && !due && body.flee <= 0) {    body.blocked = 0;    body.plan = 0;    body.leader = undefined;  }  return true;}/** * Which way it faces, first of: away from the lever once there, at the jelly once beside it to * climb on, at a friend it waves to, at a critter flying off the jelly, at the pointer petting it * or once it arrives, on as it was once it arrives, or the way it walks. */function headingOf(  body: Body,  way: number,  arrived: boolean,  due: boolean,  petted: boolean,  pointer: V3 | undefined,) {  if (due && arrived) return 0;  if (body.wantsRide && arrived) {    return body.goal[0] === LADDER ? Math.PI / 2 : facingTo(body.at, body.at[0], 0);  }  if (body.wave > 0 && body.friend) return facingTo(body.at, body.friend.at[0], body.friend.at[2]);  if (body.cheer > 0 && body.fan) return facingTo(body.at, body.fan.at[0], body.fan.at[2]);  if ((petted || arrived) && pointer) return facingTo(body.at, pointer[0], pointer[2]);  if (arrived) return body.heading;  return way;}/** * Now and then naps, or hops or flips for nothing, and bounces at a pointer it came to see. * Returns whether it did. */function idles(body: Body, curious: boolean, dt: number) {  if (!curious && Math.random() < dt * SLEEP.chance) {    body.asleep = random(SLEEP.shortest, SLEEP.longest);    return true;  }  if (Math.random() < dt * (curious ? CURIOUS.bounce : 0.05)) {    jump(body, curious ? 0.5 : 0.65, !curious && Math.random() < 0.5);    return true;  }  return false;}/** * By the jelly, facing it: hesitates as long as its nerve lets it, then beside the jelly may give * up, and otherwise hops onto the jelly's top. Where that top is too high or steep it tries the * approach, and at the approach it gives up. */function brink(body: Body, crowd: Crowd, turn: number, dt: number) {  body.brink += dt;  if (Math.abs(turn) > 0.3 || body.brink < BOARD.hesitate * (1 - body.nerve)) return;  body.brink = 0;  const atApproach = body.goal[0] === LADDER;  const surface = topNear(crowd.spine, body.at[0], body.at[2]);  const dares = atApproach || Math.random() < BOARD.dare + (1 - BOARD.dare) * body.nerve;  if (surface && dares) hop(body, surface);  else if (surface || atApproach) giveUp(body, crowd.front);  else approach(body.goal);}/** * The jelly's top nearest world x, on the side of `z`, if it lies within `BOARD.reach` of the * fixed end, is low enough to hop onto and faces up. */function topNear(spine: Float32Array, x: number, z: number) {  const s = Math.max(nearest(spine, x, GROUND, found).s, R);  if (s > BOARD.reach) return undefined;  const { surface, normal } = seat(    spine,    s,    Math.sign(z) * Math.min(Math.abs(z), BOARD.lands),    top,  );  if (surface[1] > GROUND + BOARD.step || normal[1] < BOARD.flat) return undefined;  return surface;}/** Hops onto `surface` in `BOARD.hop` seconds, to land there and climb on. */function hop(body: Body, surface: V3) {  jump(body, 0, false);  const t = BOARD.hop;  const { at } = body;  vec3n.set(    (surface[0] - at[0]) / t,    (surface[1] - at[1]) / t + (FALL * t) / 2,    (surface[2] - at[2]) / t,    body.velocity,  );  body.joy = 1;}/** Thinks better of riding and wanders off. */function giveUp(body: Body, front: number) {  body.wantsRide = false;  lawnSpot(front, body.goal);  body.squash.velocity -= 1;}/** Leaps up to the lever's knob. */function leap(body: Body) {  body.mode = 'pull';  body.pulling = 0;  vec3n.addScaled(body.at, UP, STANDING, body.leapFrom);  body.squash.velocity -= 1.5;}

steering.ts

import { std } from 'typegpu';import { vec3n } from 'wgpu-matrix';import {  crossed,  LADDER,  R,  turnBetween,  WALK,  type Body,  type Crowd,  type Obstacle,} from './body.ts';import { GROUND, SLOT, type V3 } from './tray.ts';/** How critters steer round props and each other. */export const STEER = {  /** Metres a second at most that props, the slot and each other move them apart. */  apart: 0.5,  /** How far ahead they look for what blocks their way. */  ahead: 0.35,  /** The room they leave around props, and around each other. */  room: R * 1.3,  spacing: R * 2.4,  /** How fast they step aside from a critter too near, and how fast they reach a new speed. */  aside: 0.12,  speedUp: 10,  /** Seconds they keep the side they pass on. */  keep: 1,  /** Going round something, they keep at it while another is under `stick` nearer, `wide` wider. */  stick: 0.03,  wide: 1.2,  /** Seconds blocked before they try the other side, and before they go somewhere else. */  flip: 2.5,  give: 4,  /** Radians at most they turn from their way passing a critter, so two never walk off together. */  swerve: 1.25,};/** What blocks a walker's way: a prop or a critter, and the room it gives it. */export interface Block {  x: number;  z: number;  room: number;  other: Body | undefined;}/** The way towards a goal in x and z, how far the walker heads along it, and how far the goal is. */export interface Route {  readonly way: [number, number];  distance: number;  toGoal: number;}// What the steps below write, one walker at a timeconst route: Route = { way: [0, 0], distance: 0, toGoal: 0 };const blocking: Block = { x: 0, z: 0, room: 0, other: undefined };const sidestep: V3 = [0, 0, 0];const going: V3 = [0, 0, 0];const moved: V3 = [0, 0, 0];/** * The way towards the goal: past the slot's nearer end, halfway in towards it, when the goal lies * across it; out to the goal's side first when in line with the slot past an end and the goal lies * along it; and back from the front. */export function routeOf(body: Body, goal: readonly number[], front: number, due: boolean) {  const x = body.at[0];  const z = body.at[2];  const gx = goal[0]!;  const gz = goal[1]!;  let towardX = gx - x;  let towardZ = gz - z;  const steppingIn = byLadder(body);  const across = Math.sign(z) !== Math.sign(gz) || Math.abs(gz) < SLOT.z;  const pastEnd = Math.abs(x) > SLOT.x && Math.abs(z) < SLOT.z + 0.1;  if (!steppingIn && across && Math.abs(x) < SLOT.x + 0.1) {    const end = x + gx < 0 ? -1 : 1;    towardX = end * (SLOT.x + 0.2) - x;    towardZ = z * 0.5 - z;  } else if (!steppingIn && pastEnd && Math.sign(gx - x) !== Math.sign(x)) {    towardX = Math.sign(gx - x) * 0.1;    towardZ = Math.sign(gz || 1) * (SLOT.z + 0.2) - z;  }  if (x > front - 0.15 && !due) {    towardX = -1;    towardZ = 0;  }  const distance = Math.hypot(towardX, towardZ);  const { way } = route;  if (distance > 1e-6) {    way[0] = towardX / distance;    way[1] = towardZ / distance;  } else {    way[0] = Math.sin(body.heading);    way[1] = Math.cos(body.heading);  }  route.distance = distance;  route.toGoal = Math.hypot(gx - x, gz - z);  return route;}/** Whether it is near enough the jelly's fixed end, on its way onto it, to step into the slot. */const byLadder = (body: Body) => body.wantsRide && body.at[0] < LADDER + 0.08;/** The walker `blockOf` looks ahead for, its way, the nearest entry so far and what it avoided. */const search = {  body: undefined as Body | undefined,  way: [0, 0] as readonly [number, number],  entry: 0,  avoided: undefined as Body | Obstacle | undefined,  found: false,};/** Takes what lies at `bx, bz` with `room` round it as the block, if the walker enters it first. */function inWay(bx: number, bz: number, room: number, what: Body | Obstacle) {  const { body, way } = search;  const x = body!.at[0];  const z = body!.at[2];  const along = (bx - x) * way[0] + (bz - z) * way[1];  const off = (bx - x) * way[1] - (bz - z) * way[0];  const kept = what === search.avoided;  const wide = kept ? room * STEER.wide : room;  if (along <= 0 || Math.abs(off) >= wide) return;  const enters = along - Math.sqrt(wide * wide - off * off);  if (enters - (kept ? STEER.stick : 0) > search.entry) return;  search.entry = Math.max(enters, 0);  search.found = true;  blocking.x = bx;  blocking.z = bz;  blocking.room = room;  blocking.other = 'at' in what ? what : undefined;  body!.avoiding = what;}/** * The nearest prop or walker in the way, entered within `STEER.ahead`. What it went round last * step keeps a wider room and wins ties by `STEER.stick`, so it doesn't swap between two. */export function blockOf(  body: Body,  crowd: Crowd,  way: readonly [number, number],  distance: number,  leader: Body | undefined,) {  search.body = body;  search.way = way;  search.entry = Math.min(distance, STEER.ahead);  search.avoided = body.avoiding;  search.found = false;  body.avoiding = undefined;  for (const o of crowd.world.obstacles) inWay(o.x, o.z, o.radius + STEER.room, o);  for (const other of crowd.bodies) {    if (other === body || other.mode !== 'walk' || other === leader || other.grown < 0.5) continue;    inWay(other.at[0], other.at[2], STEER.spacing, other);  }  return search.found ? blocking : undefined;}/** * Turns `want` to pass `block`, `gap` away, on the side it keeps, which it flips after * `STEER.flip` seconds blocked. */export function passing(body: Body, block: Block, want: number, gap: number, dt: number) {  const bearing = Math.atan2(block.x - body.at[0], block.z - body.at[2]);  const off = turnBetween(want, bearing);  if (body.dodgeFor <= 0) {    const lean = turnBetween(want, body.heading);    body.dodge = Math.abs(off) > 0.05 ? -Math.sign(off) : lean >= 0 ? 1 : -1;  }  if (crossed(body.blocked, dt, STEER.flip)) body.dodge *= -1;  body.dodgeFor = STEER.keep;  const pass =    bearing + body.dodge * (Math.asin(Math.min(1, block.room / Math.max(gap, 1e-6))) + 0.15);  const most = block.other ? STEER.swerve : Math.PI;  return want + std.clamp(turnBetween(want, pass), -most, most);}/** A step aside from walkers too near, without turning to them. */export function asideOf(body: Body, bodies: readonly Body[], leader: Body | undefined) {  const x = body.at[0];  const z = body.at[2];  vec3n.zero(sidestep);  for (const other of bodies) {    if (other === body || other.mode !== 'walk' || other === leader || other.grown < 0.5) continue;    const gap = Math.hypot(x - other.at[0], z - other.at[2]);    if (gap < STEER.spacing && gap > 1e-4) {      const push = 1 - gap / STEER.spacing;      sidestep[0] += ((x - other.at[0]) / gap) * push;      sidestep[2] += ((z - other.at[2]) / gap) * push;    }  }  if (vec3n.length(sidestep) > 1) vec3n.normalize(sidestep, sidestep);  return vec3n.scale(sidestep, STEER.aside, sidestep);}/** Turns towards heading `want` at most `WALK.turn` radians a second. Returns the turn left. */export function turnTo(body: Body, want: number, dt: number, rate: number = WALK.turn) {  const turn = turnBetween(body.heading, want);  body.heading += std.clamp(turn, -1, 1) * rate * dt;  return turn;}/** * Eases its velocity towards walking `speed` along its heading plus `aside` and any stumble, moves, * and slides along what it walks into, never faster than it walks. */export function stride(body: Body, crowd: Crowd, aside: V3, speed: number, dt: number) {  const x = body.at[0];  const z = body.at[2];  vec3n.add(aside, body.stumble, going);  going[0] += Math.sin(body.heading) * speed;  going[2] += Math.cos(body.heading) * speed;  vec3n.lerp(body.velocity, going, Math.min(1, dt * STEER.speedUp), body.velocity);  vec3n.scale(body.stumble, Math.exp(-dt * 5), body.stumble);  vec3n.addScaled(body.at, body.velocity, dt, body.at);  body.at[1] = GROUND;  keepOut(body, crowd.world.obstacles, dt);  vec3n.set((body.at[0] - x) / dt, 0, (body.at[2] - z) / dt, moved);  const most = vec3n.length(body.velocity);  if (vec3n.length(moved) > most) vec3n.scale(vec3n.normalize(moved, moved), most, moved);  vec3n.copy(moved, body.velocity);}/** * Moves a walking critter out of props and the slot, unless it is climbing onto the jelly, at * most `STEER.apart` a second, so one landing deep inside walks out rather than jumps. */export function keepOut(body: Body, obstacles: readonly Obstacle[], dt: number) {  const at = body.at;  const most = STEER.apart * dt;  for (const o of obstacles) {    const gap = Math.hypot(at[0] - o.x, at[2] - o.z);    const deep = Math.min(o.radius + R - gap, most);    if (deep > 0 && gap > 1e-4) {      at[0] += ((at[0] - o.x) / gap) * deep;      at[2] += ((at[2] - o.z) / gap) * deep;    }  }  if (byLadder(body)) return;  const inX = SLOT.x + 0.06 - Math.abs(at[0]);  const inZ = SLOT.z + 0.06 - Math.abs(at[2]);  if (inX <= 0 || inZ <= 0) return;  if (inZ < inX) at[2] += Math.sign(at[2] || 1) * Math.min(inZ, most);  else at[0] += Math.sign(at[0] || 1) * Math.min(inX, most);}

assets.ts

import { font } from '../assets.ts';/** Inter, baked and shipped with `@typegpu/kit`. */export const assets = { font: font('inter') };