<!-- Generated by `npm run generate:docs`; do not edit by hand. -->

# Functor API reference

This reference is generated from the Functor Lang sources embedded in Functor: the host runtime's `.funi` prelude and the language's own standard library.

## Engine modules

Provided by the game runner. These resolve when Functor runs a game — natively, on the web, or in a headless test — and not under the plain `functor-lang` CLI.

### Scene & rendering

#### Scene

Declarative 3D scene nodes, materials, models, animation, and transforms.

Constructors produce immutable scene values. Most modifiers take the scene
last so a node can be built as a readable pipeline.

Coordinates are Y-up and right-handed: +Y is up, +X is right, and the
ground is the XZ plane.

Transforms wrap outward, so the OUTER call applies last in world space —
`s |> Scene.rotateY(r) |> Scene.translate(v)` rotates in place and then
moves, which is the order the source reads. The primitives take no size
arguments, so a box is `Scene.cube() |> Scene.scaleXYZ(w, h, d)`.

##### `Scene.t`

```functor
type t = host
```

An opaque scene node.

##### `Scene.cube`

```functor
let cube : () => t
```

Create a unit cube centered at the origin.

##### `Scene.sphere`

```functor
let sphere : () => t
```

Create a unit sphere centered at the origin.

##### `Scene.cylinder`

```functor
let cylinder : () => t
```

Create a unit cylinder aligned to the Y axis.

##### `Scene.quad`

```functor
let quad : () => t
```

Create a unit quad in the XY plane, facing `+Z`.

##### `Scene.plane`

```functor
let plane : () => t
```

Create a unit plane in the XZ ground plane.

##### `Scene.model`

```functor
let model : (Asset.Model) => t
```

Create a scene node from a model asset.

A locator whose file is missing logs an error and renders the empty
fallback asset, so a mistyped or unfetched model shows as nothing rather
than failing the frame.

##### `Scene.heightmap`

```functor
let heightmap : (List<List<float>>) => t
```

Create terrain from a rectangular grid of height values.

##### `Scene.terrain`

```functor
let terrain : (Terrain.t) => t
```

Create a scene node from an asset-backed terrain descriptor.

##### `Scene.group`

```functor
let group : (List<t>) => t
```

Group scene nodes under one transform.

##### `Scene.color`

```functor
let color : (Color.t, t) => t
```

Apply an unlit solid color; the scene is last for piping.

##### `Scene.lit`

```functor
let lit : (Color.t, t) => t
```

Apply a lit solid-color material; the scene is last for piping. It needs
lights to be visible — under a plain `Frame.create` it renders black.

##### `Scene.emissive`

```functor
let emissive : (Color.t, t) => t
```

Apply an emissive solid-color material; the scene is last for piping.

##### `Scene.litTexture`

```functor
let litTexture : ('texture, t) => t
```

Apply a lit texture from `Texture.t` or `Asset.Texture`.

##### `Scene.emissiveTexture`

```functor
let emissiveTexture : ('texture, t) => t
```

Apply an emissive texture from `Texture.t` or `Asset.Texture`.

##### `Scene.litNormalMapped`

```functor
let litNormalMapped : (Color.t, 'texture, t) => t
```

Apply a lit color and a TANGENT-SPACE normal-map texture.

The map perturbs the surface normal used for lighting, so its bumps catch
the scene's diffuse and specular response without changing the geometry.

##### `Scene.screen`

```functor
let screen : (RenderTarget.t, t) => t
```

Display a render target on this surface.

The surface is emissive, so the feed is shown unlit. A target no frame
writes renders magenta with one warning. A quad's front face is `+Z`, so a
monitor built from `Scene.quad` has to be rotated to face the viewer or the
feed reads mirrored.

##### `Scene.opacity`

```functor
let opacity : (float, t) => t
```

Make a subtree TRANSLUCENT — alpha from 0 (invisible) to 1 (unchanged); the
scene is last for piping.

The alpha applies uniformly to everything below it, whatever material each
node uses — solid colors, textures, lit models, terrain — so a ghost copy of
a craft is `craftScene(...) |> Scene.opacity(0.35)`. Nested opacities
multiply. An alpha outside `0..1` is an error, not a clamp.

HOW IT RENDERS, so the caveats are predictable:

- Translucent subtrees draw in a pass AFTER all opaque geometry, with depth
  testing on and depth WRITING off — opaque things in front hide them; they
  never hide each other.
- They are sorted back-to-front by the VIEW-SPACE depth of the average world
  position of the leaves under each `Scene.opacity` node. That is the whole
  sorting granularity:
  there is no per-triangle sort, so within ONE translucent subtree
  overlapping surfaces (including the back faces of a closed mesh) read
  denser where they overlap, and two INTERPENETRATING translucent objects
  sort by their averages, which is wrong for the overlapping sliver.
- A translucent subtree casts no shadow.
- `Scene.opacity(1.0, scene)` is exactly `scene` — the identity. Nothing
  that never calls this changes in cost or appearance. The flip side is a
  STEP at that boundary: the instant alpha drops below 1 the subtree leaves
  the opaque pass, stops writing depth AND stops casting a shadow, so a fade
  beginning at exactly 1.0 pops on its first frame rather than easing.
- An alpha of 0 draws nothing at all — the subtree is skipped rather than
  rasterized — so fading fully out costs nothing.

##### `Scene.instanced`

```functor
let instanced : (List<Instance.t>, t) => t
```

Stamp a scene once per instance — one node for thousands of copies.

`scene |> Scene.instanced(instances)` is semantically a group holding one
transformed copy of the template per `Instance.t`, with each copy's
`Instance.tint` multiplied into the template's material colors. Materials
come FROM the template: `Scene.cube() |> Scene.lit(color)` instanced is
lit and shadowed exactly like its copies would be.

The renderer draws recognized templates with hardware instancing — a
single cube/sphere/cylinder/quad/plane leaf under any transforms and at
most one solid `Scene.color` / `Scene.lit` / `Scene.emissive` material
(one draw call), or a `Scene.model` leaf under transforms (one draw call
per mesh primitive, textured exactly like the ordinary model draw). A
SKINNED model template — with an attached `Scene.animate` pose or the
zero-config first-clip autoplay — instances at the SHARED pose: the pose
is sampled and uploaded once, and every copy skins from it (a crowd in
step; per-instance playheads are planned, not yet available). Any other
template still renders correctly, expanded copy-by-copy on the CPU with
a once-per-topology `[functor]` perf note — comparable to writing the
group by hand, but not faster.

An empty list draws nothing. `Scene.opacity` inside the template is a
teaching error — wrap the whole instanced node instead
(`… |> Scene.instanced(xs) |> Scene.opacity(a)`).

##### `Scene.animate`

```functor
let animate : (Anim.t, t) => t
```

Attach an animation pose to model nodes; the scene is last for piping.

Without an attached pose, a skinned model plays its FIRST clip on the game
clock — attaching one is what puts the playhead under the game's control.

##### `Scene.translate`

```functor
let translate : (Vec3.t, t) => t
```

Translate a scene node; the scene is last for piping.

##### `Scene.scale`

```functor
let scale : (float, t) => t
```

Scale a scene node uniformly; the scene is last for piping.

##### `Scene.scaleXYZ`

```functor
let scaleXYZ : (float, float, float, t) => t
```

Scale a scene node independently on each axis.

##### `Scene.rotateX`

```functor
let rotateX : (Angle.t, t) => t
```

Rotate a scene node around X.

##### `Scene.rotateY`

```functor
let rotateY : (Angle.t, t) => t
```

Rotate a scene node around Y.

##### `Scene.rotateZ`

```functor
let rotateZ : (Angle.t, t) => t
```

Rotate a scene node around Z.

##### `Scene.equals`

```functor
let equals : (t, t) => bool
```

Compare two scene nodes structurally — the escape hatch for `Scene.t`,
which is opaque and therefore supports no `==`.

Intended for inline `expect` tests over `draw` output, NOT for per-frame
logic: the walk is O(scene size), which is why it is an explicit call
rather than an operator.

Three things it is literal about:

- Floats compare EXACTLY — transforms, colors, and playheads. There is no
  epsilon, so build both sides of a test from the same arithmetic. (The
  comparison happens after the engine boundary's narrowing to 32-bit, so
  two numbers that narrow to the same float are equal.)
- Assets compare by LOCATOR, not by loaded content: the same path or URL
  (and the same `Asset.whilePending` chain) is equal, whether or not either
  has finished loading.
- Animation compares as DECLARED — the clip name and playhead seconds in
  the attached pose, never a sampled skeleton.

Children are ordered, so two groups holding the same nodes in a different
order are not equal.

The answer is a bare `bool`, so a failing `expect` reports only that the
two scenes differ — scene values are opaque and cannot be printed. Write
the assertion over the smallest node that makes the point, or bisect by
comparing sub-scenes, rather than one `expect` over a whole frame.

#### Instance

Per-copy placement for `Scene.instanced`.

Build one `Instance.t` per copy — where it sits, how it is scaled and
rotated, and an optional color tint — then hand the list to
`Scene.instanced`, which stamps its template scene once per instance.

Instances are CHANNELS, not free-form transform chains: the copy's
transform always applies scale, then rotation, then translation, whatever
order the combinators were piped in. Combinators compose within their own
channel — rotations multiply (the outer pipe applies last, like
`Scene.rotate*`), scales multiply componentwise, and tints multiply
componentwise.

##### `Instance.t`

```functor
type t = host
```

One copy's placement: position, rotation, per-axis scale, and tint.

##### `Instance.at`

```functor
let at : (Vec3.t) => t
```

Start an instance at `position`, with no rotation, unit scale, and no
tint.

##### `Instance.scale`

```functor
let scale : (float, t) => t
```

Multiply the instance's scale uniformly.

##### `Instance.scaleXYZ`

```functor
let scaleXYZ : (float, float, float, t) => t
```

Multiply the instance's per-axis scale componentwise.

##### `Instance.rotateX`

```functor
let rotateX : (Angle.t, t) => t
```

Rotate the instance about the X axis; piped later means applied later.

##### `Instance.rotateY`

```functor
let rotateY : (Angle.t, t) => t
```

Rotate the instance about the Y axis; piped later means applied later.

##### `Instance.rotateZ`

```functor
let rotateZ : (Angle.t, t) => t
```

Rotate the instance about the Z axis; piped later means applied later.

##### `Instance.trs`

```functor
let trs : (Vec3.t, Angle.t, float, float, float) => t
```

The flat fast path: position, yaw, and per-axis scale in ONE call.

Exactly `Instance.at(position) |> Instance.scaleXYZ(sx, sy, sz)
|> Instance.rotateY(rotationY)`, minus the per-copy builder-call overhead —
reach for it when a large per-frame field makes construction cost visible.
Compose further channels (another rotation axis, `Instance.tint`) on top.

##### `Instance.tint`

```functor
let tint : (Color.t, t) => t
```

Multiply the copy's material colors by `color` — a per-instance tint over
the template's material, not a second material system. Tints compose by
multiplying; white is the identity. The tint is RGB only — a copy's alpha
always comes from the template's material. A bare `Scene.model` template
has no material colors to multiply, so tint has no effect there (exactly
as in the stamped group).

#### Frame

Complete frame descriptions returned by a game's `draw` function.

##### `Frame.t`

```functor
type t = host
```

An opaque frame description.

##### `Frame.create`

```functor
let create : (Camera3D.t, Scene.t) => t
```

Create an unlit frame from a camera and scene.

##### `Frame.createLit`

```functor
let createLit : (Camera3D.t, Scene.t, List<Light.t>) => t
```

Create a lit frame from a camera, scene, and lights.

##### `Frame.create2D`

```functor
let create2D : (Camera2D.t, Sprite.t) => t
```

Create a standalone 2D frame from a camera and sprite tree.

##### `Frame.withRenderTarget`

```functor
let withRenderTarget : (RenderTarget.t, t, t) => t
```

Render another frame into a target before rendering the main frame.

The target frame is a complete `Frame.create` / `Frame.createLit` with its
OWN lights, so a lit or shadowed feed needs `createLit` plus
`Light.castShadows` there. A scene that samples the very target it is drawn
into sees the previous frame's image. The main frame is last for piping.

##### `Frame.withUiTarget`

```functor
let withUiTarget : (RenderTarget.t, Ui.view, t) => t
```

Paint a UI view into a target before rendering the main frame.

The view is a `Ui.*` tree painted at the target's declared size and read
back like any render target (`Scene.quad() |> Scene.screen(target)`) — a
monitor mesh, a cockpit panel. Views on targets are display-only for now:
interactive widgets (buttons, sliders, text inputs) render, but their
handlers are ignored. The screen clears to the engine's default
background. Like `withRenderTarget`, the FIRST declaration of a target id
wins; one id must not be declared by both writers. Runs on every shell —
native, web, and VR. The main frame is last for piping.

##### `Frame.withFog`

```functor
let withFog : (Fog.t, t) => t
```

Attach fog to a frame; the frame is last for piping.

##### `Frame.withSkybox`

```functor
let withSkybox : (Skybox.t, t) => t
```

Attach a cubemap skybox to a frame; the frame is last for piping.

##### `Frame.withClearColor`

```functor
let withClearColor : (Color.t, t) => t
```

Override a frame's background clear color.

This overrides the default of clearing to the fog color, and paints the
background only — it does not change how fog blends over geometry.

##### `Frame.with2D`

```functor
let with2D : (Camera2D.t, Sprite.t, t) => t
```

Add an ordered sprite pass above the frame's 3D scene.

Layers render in call order, so a second `with2D` draws above the first.
The main frame is last for piping.

##### `Frame.equals`

```functor
let equals : (t, t) => bool
```

Compare two frames structurally — the escape hatch for `Frame.t`, which is
opaque and therefore supports no `==`.

Compares every part of the frame: camera, scene, lights, render-target
passes, fog, skybox, clear color, and 2D layers (all ordered). It also
distinguishes HOW the frame was built — a `Frame.create2D` frame is never
equal to a 3D frame carrying the same layer through `with2D`. Intended for
inline `expect` tests over `draw` output, NOT for per-frame logic — the
walk is O(frame size).

It inherits `Scene.equals`'s rules: floats compare exactly, assets compare
by locator rather than by loaded content, animation compares as declared,
and a failing `expect` can only report THAT the two frames differ — assert
over the smallest piece that makes the point.

#### Camera3D

Cameras for viewing a Y-up, right-handed 3D scene.

##### `Camera3D.t`

```functor
type t = host
```

An opaque camera description.

##### `Camera3D.lookAt`

```functor
let lookAt : (Vec3.t, Vec3.t) => t
```

Create a camera from an eye position and target with a fixed 45° field of view.

##### `Camera3D.firstPerson`

```functor
let firstPerson : (Vec3.t, Angle.t, Angle.t, Angle.t) => t
```

Create a first-person camera from position, yaw, pitch, and field of view.

Zero yaw and pitch look down `+Z`.

On XR this camera is the authored reference center-eye rig: live head and
eye deltas compose in its local basis. Position, orientation, and the near/
far planes stay game-owned, while OpenXR owns IPD and per-eye optical FOV.

##### `Camera3D.ray`

```functor
type ray = { origin: Vec3.t, direction: Vec3.t }
```

A world-space ray from the camera eye through a logical surface point.

##### `Camera3D.toWorldRay`

```functor
let toWorldRay : (Input.mouse, t) => Option.t<ray>
```

Map a sampled mouse position through the authored perspective camera.

Mouse position and extent share one top-left-origin logical coordinate
space, so the result stays stable across resize and Retina/device-pixel
ratio changes. The direction is normalized and both fields feed directly
into `Physics.cast` / `Physics.raycast`. Returns `Option.None` while the
pointer is outside the surface or the camera is degenerate.

##### `Camera3D.mappedPose`

```functor
type mappedPose = { position: Input.point3, forward: Input.point3, up: Input.point3 }
```

A tracked pose mapped into world space through an authored camera.

##### `Camera3D.mapTrackedPose`

```functor
let mapTrackedPose : (t, Input.pose) => mappedPose
```

Map a rig-local tracked pose through the authored camera.

The returned position, forward, and up vectors are suitable for placing a
controller representation or aiming a ray in the authored world.

##### `Camera3D.clip`

```functor
let clip : (float, float, t) => t
```

Set near and far clipping distances; the camera is last for piping.

Both distances must be finite with `0 < near < far`; anything else is a
teaching error rather than a degenerate projection. Large outdoor worlds
should set the far plane explicitly. Keep the near plane as large as
gameplay permits to preserve depth-buffer precision.

#### Camera2D

Center-origin, Y-up cameras for `Sprite` scenes.

##### `Camera2D.t`

```functor
type t = host
```

An opaque 2D camera description.

##### `Camera2D.create`

```functor
let create : (float, float) => t
```

Create a camera with the visible world width and height at zoom 1.

The renderer preserves this aspect ratio and letterboxes rather than
stretching. Width and height must be positive.

##### `Camera2D.at`

```functor
let at : (float, float, t) => t
```

Center a camera at the given world position; the camera is last for piping.

##### `Camera2D.zoom`

```functor
let zoom : (float, t) => t
```

Set a positive camera zoom; the camera is last for piping.

LARGER is closer: the zoom divides the visible world extent, so `2.0` shows
half as much world at twice the size.

##### `Camera2D.toWorld`

```functor
let toWorld : (Input.mouse, t) => Option.t<Input.point2>
```

Map a sampled mouse position through the camera's fitted viewport.

Returns `Option.None` while the pointer is in a letterbox/pillarbox bar.
The mouse carries its logical surface extent, so this remains correct
across window resizes and Retina/device-pixel-ratio changes.

#### Sprite

Pure, inspectable 2D picture values.

Unlike `Scene.t` and `Frame.t`, `Sprite.t` is represented at runtime as
ordinary Functor Lang data. It can be compared, inspected, serialized,
stored in a model, and carried through time travel while its internal
rendering schema stays private.

##### `Sprite.t`

```functor
type t
```

An abstract plain-data 2D picture.

##### `Sprite.region`

```functor
type region
```

A rectangular section of an image in whole source pixels. Coordinates use
the conventional image origin: x grows right and y grows down from the
top-left corner.

##### `Sprite.metrics`

```functor
type metrics = {
  width: float,
  height: float
}
```

The size of a laid-out run of text, in the same world units as the sprite
itself.

##### `Sprite.blank`

```functor
let blank : () => t
```

Create an empty picture.

##### `Sprite.rectangle`

```functor
let rectangle : (Color.t, float, float) => t
```

Create a centered rectangle with positive width and height.

##### `Sprite.square`

```functor
let square : (Color.t, float) => t
```

Create a centered square with a positive side length.

##### `Sprite.text`

```functor
let text : (Color.t, float, string) => t
```

Draw text in the built-in font, centered on its own box like every other
primitive, with `size` the height of one line in world units. Needs no
asset: the font is compiled into the runtime. The text comes last, so a
formatted value pipes straight in:

    Text.fixed(model.score, 0.0) |> Sprite.text(Color.rgb(0.0, 1.0, 1.0), 1.2)

The font is a monospace 8x8 bitmap, so each character advances by exactly
`size` — ask `measure` for a run's size rather than assuming it. `\n` starts
a new line, stacked at exactly one `size` of line height, and each line is
centered within the block. That stride is the glyph cell, so lines never
overlap but sit tight — a descender nearly meets the next line's capitals,
as in a terminal. For airier text, draw the lines yourself and space them by
more than `size` with `group` and `moveY`. Characters outside printable ASCII
occupy their cell but draw nothing, so unsupported text leaves gaps instead
of shifting the rest of the line.

Text is centered, so align it by shifting half its measured width —
`Sprite.moveX(Sprite.measure(size, s).width * 0.5, …)` puts its LEFT edge at
the origin, and negating that puts its right edge there. That aligns the
BLOCK: with several lines only the widest reaches the edge, since each line
stays centered until a left-aligning text block exists.

Glyphs are sampled like any other sprite image, so `nearest` gives crisp
pixel edges and the default `linear` gives smoother ones at large sizes.

##### `Sprite.measure`

```functor
let measure : (float, string) => metrics
```

Measure what `text` at `size` would occupy, without rendering it, so labels
and columns can be laid out in game logic. The width is the widest line's;
the height is `size` per line, counting a trailing newline as a line, so
stacking blocks by their measured height never overlaps them.

##### `Sprite.circle`

```functor
let circle : (Color.t, float) => t
```

Create a filled circle of the given radius, centered on the origin like
`square` — so it spans `2 * radius` across. Approximated by a 32-sided
polygon, which is under a pixel from true at any size that reads as a circle.

##### `Sprite.polygon`

```functor
let polygon : (Color.t, List<Input.point2>) => t
```

Fill a CONVEX polygon through the given points, in order.

Unlike every other primitive, a polygon is NOT re-centered: the points are the
geometry, in the sprite's own coordinate space, so an outline computed in game
logic lands where it was computed. Either winding works (clockwise or
counter-clockwise).

The fill is a triangle fan, which is only correct for a convex outline, so
anything else is REJECTED with an error rather than filled wrongly — draw it
as a group of convex pieces instead. That covers a CONCAVE outline, a
self-intersecting STAR (which turns consistently but winds around more than
once), fewer than 3 points, and points that are all on one line.

##### `Sprite.line`

```functor
let line : (Color.t, float, Input.point2, Input.point2) => t
```

Draw a straight line of the given thickness between two points, in the
sprite's own coordinate space (not re-centered, like `polygon`).

Thickness is measured across the line and is exact at every angle. There are
no caps and no joins: the line stops flat at each endpoint, so two lines
meeting at an angle leave a notch at the corner — a jointed `polyline` is not
part of this surface yet. A zero-length line draws nothing.

Thickness is geometry, not a screen-space stroke: `scale` multiplies it along
with the length, and `scaleXY` with unequal factors distorts it for any line
that is not axis-aligned.

##### `Sprite.image`

```functor
let image : (float, float, Asset.Texture) => t
```

Create a centered, textured rectangle with positive width and height.

##### `Sprite.imageRegion`

```functor
let imageRegion : (float, float, region, Asset.Texture) => t
```

Select a source rectangle without requiring the image's full dimensions.

##### `Sprite.region`

```functor
let region : (float, float, float, float) => region
```

Construct a top-left-origin source rectangle as x, y, width, height.

##### `Sprite.group`

```functor
let group : (List<t>) => t
```

Group pictures in painter's order, with earlier items behind later items.

##### `Sprite.move`

```functor
let move : (float, float, t) => t
```

Move a picture by the given X and Y offsets; the picture is last for piping.

##### `Sprite.moveX`

```functor
let moveX : (float, t) => t
```

Move a picture along X; the picture is last for piping.

##### `Sprite.moveY`

```functor
let moveY : (float, t) => t
```

Move a picture along Y; the picture is last for piping.

##### `Sprite.rotate`

```functor
let rotate : (Angle.t, t) => t
```

Rotate a picture around its center; the picture is last for piping.

A positive angle rotates COUNTER-CLOCKWISE, matching the Y-up 2D camera.

##### `Sprite.scale`

```functor
let scale : (float, t) => t
```

Scale a picture uniformly; the picture is last for piping.

##### `Sprite.scaleXY`

```functor
let scaleXY : (float, float, t) => t
```

Scale a picture independently along X and Y; the picture is last for piping.

##### `Sprite.fade`

```functor
let fade : (float, t) => t
```

Multiply a picture's opacity by an alpha from 0 to 1.

##### `Sprite.tint`

```functor
let tint : (Color.t, t) => t
```

Multiply a picture's color by a tint; the picture is last for piping.

##### `Sprite.nearest`

```functor
let nearest : (t) => t
```

Use crisp nearest-neighbor sampling for every image in the subtree.

##### `Sprite.linear`

```functor
let linear : (t) => t
```

Use smooth linear sampling for every image in the subtree (the default).

#### Light

Lights used by `Frame.createLit`.

##### `Light.t`

```functor
type t = host
```

An opaque light description.

##### `Light.ambient`

```functor
let ambient : (Color.t) => t
```

Create uniform ambient light.

##### `Light.directional`

```functor
let directional : (Vec3.t, Color.t, float) => t
```

Create directional light from direction, color, and intensity.

##### `Light.point`

```functor
let point : (Vec3.t, Color.t, float, float) => t
```

Create point light from position, color, intensity, and range.

##### `Light.spot`

```functor
let spot : (Vec3.t, Vec3.t, Color.t, float, float, Angle.t) => t
```

Create a spot light from position, direction, color, intensity, range, and cone angle.

##### `Light.castShadows`

```functor
let castShadows : (t) => t
```

Enable shadow casting; the light is first for piping.

#### Skybox

Cubemap skyboxes attached to frames with `Frame.withSkybox`.

##### `Skybox.t`

```functor
type t = host
```

An opaque cubemap skybox.

##### `Skybox.files`

```functor
let files : (string, string, string, string, string, string) => t
```

Load six cubemap faces in `+X, -X, +Y, -Y, +Z, -Z` order.

Faces are ordinary fetched image files resolved from the game directory.
While they load the frame's clear color shows through, and a face that
fails to load warns once and leaves the frame with no sky.

#### Texture

Texture values accepted by scene material functions.

##### `Texture.t`

```functor
type t = host
```

An opaque texture value.

##### `Texture.file`

```functor
let file : (string) => t
```

Load an image texture from a path relative to the game directory.

#### Fog

Distance fog attached to a frame with `Frame.withFog`.

Fog applies to every forward material, emissive included, and its color
also becomes the frame's clear color unless `Frame.withClearColor` says
otherwise. A skybox is never fogged.

##### `Fog.t`

```functor
type t = host
```

An opaque fog description.

##### `Fog.linear`

```functor
let linear : (float, float, Color.t) => t
```

Create linear fog between near and far distances.

`near` must be at least 0 and `far` must exceed `near`; anything else is a
teaching error rather than a silently degenerate ramp.

##### `Fog.exp`

```functor
let exp : (float, Color.t) => t
```

Create exponential fog with the given density.

The density must be positive.

#### RenderTarget

Named off-screen render targets for render-to-texture effects.

Declare a target ONCE and use that value at both sites — the
`Frame.withRenderTarget` writer and the `Scene.screen` reader — rather
than repeating a bare string, exactly as `Angle` and `Physics.tag` brand
their own identities. A scene that samples the very target it is being
rendered into shows the PREVIOUS frame's image.

##### `RenderTarget.t`

```functor
type t = host
```

An opaque render target identity and size.

##### `RenderTarget.named`

```functor
let named : (string) => t
```

Create a stable, named 512×512 render target.

##### `RenderTarget.sized`

```functor
let sized : (float, float, t) => t
```

Set target width and height; the target is last for piping.

### Math & geometry

#### Vec3

Branded 3D vectors used for positions, directions, velocities, and gravity.

Vectors are **opaque** — like `Angle` and `Color`, a `Vec3` is built once
(`Vec3.make`) and passed as a value, so three interleaved bare floats can
never be mistaken for a position. Read components back with `Vec3.x` /
`Vec3.y` / `Vec3.z`, and combine vectors with the arithmetic below rather
than unpacking to a record and rebuilding.

**Argument order is thread-last**, matching the rest of the prelude
(`Scene.translate(v, scene)`): the *subject* is the LAST parameter, so a
pipeline reads left-to-right as the subject being acted on.

```
// v - origin, scaled by 2, then normalized
let dir = v |> Vec3.sub(origin) |> Vec3.scale(2.0) |> Vec3.normalize()
```

For the non-commutative operations this means `Vec3.sub(b, a)` computes
`a - b`, so `v |> Vec3.sub(origin)` reads as "v minus origin"; likewise
`a |> Vec3.cross(b)` is `a × b`, and `from |> Vec3.lerp(target, t)` moves
`from` toward `target`.

Components are 32-bit floats and every vector is **finite**: an operation
whose result would overflow that range is an error naming the operation,
rather than an infinity that becomes a NaN and silently blanks the scene.

##### `Vec3.t`

```functor
type t = host
```

An opaque 3D vector.

##### `Vec3.make`

```functor
let make : (float, float, float) => t
```

Construct a vector from X, Y, and Z components.

##### `Vec3.x`

```functor
let x : (t) => float
```

The X component of a vector.

Components are stored as 32-bit floats, so a value that is not exactly
representable comes back rounded (`Vec3.x(Vec3.make(0.1, 0.0, 0.0))` is
`0.10000000149011612`). Compare components with a tolerance, not `==`.

##### `Vec3.y`

```functor
let y : (t) => float
```

The Y component of a vector. Rounded to 32-bit like `Vec3.x`.

##### `Vec3.z`

```functor
let z : (t) => float
```

The Z component of a vector. Rounded to 32-bit like `Vec3.x`.

##### `Vec3.add`

```functor
let add : (t, t) => t
```

Componentwise sum. `Vec3.add(b, a)` is `a + b`, so `a |> Vec3.add(b)`
reads as "a plus b" (addition is commutative, so the order is free).

##### `Vec3.sub`

```functor
let sub : (t, t) => t
```

Componentwise difference. `Vec3.sub(b, a)` is `a - b`, so
`v |> Vec3.sub(origin)` reads as "v minus origin".

##### `Vec3.scale`

```functor
let scale : (float, t) => t
```

Multiply every component by a scalar: `v |> Vec3.scale(2.0)` doubles `v`.
Scaling by a negative number negates: `v |> Vec3.scale(0.0 - 1.0)`.

##### `Vec3.dot`

```functor
let dot : (t, t) => float
```

The dot product `a · b` — commutative, so argument order does not matter.
Zero when the vectors are perpendicular.

##### `Vec3.cross`

```functor
let cross : (t, t) => t
```

The cross product. `Vec3.cross(b, a)` is `a × b`, so `a |> Vec3.cross(b)`
reads as "a cross b" — the result is perpendicular to both, right-handed:
`X × Y = Z`.

Mind the order for a strafe axis. In Functor's Y-up right-handed frame the
world-space "right" of a gaze is `up × forward`, which in this argument
order is `up |> Vec3.cross(forward)` — with `forward = +Z` and `up = +Y`
that is `+X`. The other order gives `-X`.

##### `Vec3.length`

```functor
let length : (t) => float
```

The Euclidean length (magnitude) of a vector.

##### `Vec3.normalize`

```functor
let normalize : (t) => t
```

A unit vector pointing the same way.

**A zero-length vector normalizes to zero**, not an error and not NaN —
`Vec3.normalize(Vec3.make(0.0, 0.0, 0.0))` is the zero vector. Per-frame
code normalizes a velocity or an input direction that is legitimately
zero all the time, so this must not fault the frame; test the length
first when you need a fallback direction. Zero is the ONLY input that
yields a non-unit result — the length is computed with enough range that
even the largest representable vector normalizes correctly.

##### `Vec3.distance`

```functor
let distance : (t, t) => float
```

The distance between two points — symmetric, so argument order is free.
`a |> Vec3.distance(b)` is the length of `a - b`.

##### `Vec3.lerp`

```functor
let lerp : (t, float, t) => t
```

Linear interpolation. `Vec3.lerp(target, t, from)` moves `from` toward
`target` by fraction `t`, so `from |> Vec3.lerp(target, 0.5)` is the
midpoint. `t` is NOT clamped: `0.0` yields `from`, `1.0` yields `target`,
and values outside `0..1` extrapolate.

#### Angle

Branded angles used by cameras, rotations, and spot lights.

Angle-taking APIs require an `Angle.t`, preventing radians and degrees from
being mixed accidentally. Construct one with `Angle.degrees` or
`Angle.radians`.

##### `Angle.t`

```functor
type t = host
```

An opaque angle value.

##### `Angle.degrees`

```functor
let degrees : (float) => t
```

Construct an angle from degrees.

##### `Angle.radians`

```functor
let radians : (float) => t
```

Construct an angle from radians.

##### `Angle.add`

```functor
let add : (t, t) => t
```

Add two angles. This is what `90deg + 45deg` calls.

##### `Angle.sub`

```functor
let sub : (t, t) => t
```

Subtract one angle from another. This is what `90deg - 45deg` calls.

##### `Angle.scale`

```functor
let scale : (t, float) => t
```

Scale an angle by a plain number. This is what `45deg * 2.0` calls.
It takes the angle FIRST — the shape every declared `*` has — so unlike
most of the prelude it is not written to be piped into.

##### `Angle.equals`

```functor
let equals : (t, t) => bool
```

Are two angles the same? This is what `90deg == 90deg` calls.

It is float equality on the underlying radians, with every consequence
that implies: `90deg == 90deg` is true because both sides build the same
number, but `90deg == 1.5708rad` is false, and an angle accumulated
through arithmetic may miss an exact literal by a rounding step. An angle
is one-way (there is no way back to a number), so where a tolerance
matters, keep the plain float you built it from and compare THAT.

##### `Angle.less`

```functor
let less : (t, t) => bool
```

Is the first angle smaller than the second? This is what `45deg < 90deg`
calls, and — swapped or negated — `>`, `<=`, and `>=` too.

Angles are ordered by their raw radians, so this is signed magnitude, NOT
a direction on the circle: `-270deg < 90deg` is true.

##### `Angle.deg`

```functor
unit deg = Angle.degrees
```

Degrees as a literal suffix: `90deg` is exactly `Angle.degrees(90.0)`.

##### `Angle.rad`

```functor
unit rad = Angle.radians
```

Radians as a literal suffix: `0.5rad` is exactly `Angle.radians(0.5)`.

##### `Angle.deg (+)`

```functor
unit deg (+) = Angle.add
```

`+` on angles: `90deg + 45deg` is `Angle.add(90deg, 45deg)`. An operator
belongs to the BRAND, so it covers every angle suffix — `90deg + 1.5rad`
adds too.

##### `Angle.deg (-)`

```functor
unit deg (-) = Angle.sub
```

`-` on angles: `90deg - 45deg` is `Angle.sub(90deg, 45deg)`.

##### `Angle.deg (*)`

```functor
unit deg (*) = Angle.scale
```

`*` scales an angle by a number, on either side: `45deg * 2.0` and
`2.0 * 45deg` are both `Angle.scale(45deg, 2.0)`. (Multiplying two angles
would be a different kind of thing — Functor Lang does not model that.)

##### `Angle.deg (==)`

```functor
unit deg (==) = Angle.equals
```

`==` on angles: `90deg == 90deg` is `Angle.equals(90deg, 90deg)`, and
`!=` is its negation. Underneath it is FLOAT equality on radians — see
`Angle.equals`.

##### `Angle.deg (<)`

```functor
unit deg (<) = Angle.less
```

`<` on angles: `45deg < 90deg` is `Angle.less(45deg, 90deg)`. `>`, `<=`,
and `>=` derive from it (swapped and/or negated), so all four orderings
come from this one declaration.

#### Color

RGB colors shared by rendering, lighting, fog, and UI APIs.

##### `Color.t`

```functor
type t = host
```

An opaque RGB color.

Channels are normally in `0..1`; emissive and HDR uses may exceed `1`.

##### `Color.rgb`

```functor
let rgb : (float, float, float) => t
```

Construct a color from red, green, and blue channels.

### Simulation

#### Physics

Declarative rigid-body physics for the optional `physics` hook.

Shapes and bodies are values, body attributes pipe naturally, and stable
branded tags connect declarations, reads, commands, and collision events.

The hook DECLARES the world each frame and the runtime reconciles that
declaration against the live one. A tag is cross-frame identity: the same
tag is the same body, and a body is dropped by no longer declaring it.
Re-declaring an unchanged body leaves the simulation alone, while changing
its declared position or rotation drives that field — a dynamic or fixed
body teleports immediately, and a kinematic body takes the new pose as its
next-step target, so it carries velocity into contacts. Reading a tag that
is not in the live world is a runtime error rather than an empty answer, so
read only bodies your hook has declared. Like the model, the world survives
hot reload for as long as the hook does; deleting the hook drops it. An
error raised inside the hook does NOT stop the world: the previous frame's
declaration is kept and stepped, and the error is reported once.

Reads are SYNCHRONOUS and writes are QUEUED. `Physics.position`,
`Physics.linearVelocity`, and `Physics.cast` answer in place from the LAST
STEPPED world — in any entry point, the `physics` hook included. Everything
that runs before the step (`tick`, `input`, a pre-step `update`, the hook)
sees the previous step; only `draw` and the post-step `update`s see the
world this frame just stepped. The world is primed from `init` before the
first frame, so frame 1's reads answer with the initial declared poses.
Every mutation instead returns an `Effect.t` that applies at the next
physics step after it queues — after reconcile, on that step's first
substep. A command issued from `tick` therefore normally lands in time for
the same frame's `draw`; on a frame that takes no substep at all (normal
above 60fps) it waits for the next simulated frame.

##### `Physics.shape`

```functor
type shape = host
```

An opaque collision shape.

##### `Physics.body`

```functor
type body = host
```

An opaque rigid body description.

##### `Physics.world`

```functor
type world = host
```

An opaque physics world description.

##### `Physics.tag`

```functor
type tag
```

A stable, branded body identity used throughout the physics API.

##### `Physics.position`

```functor
type position = { x: float, y: float, z: float }
```

The live world-space position of a body.

##### `Physics.velocity`

```functor
type velocity = { x: float, y: float, z: float }
```

The live linear velocity of a body, in world units per second.

##### `Physics.rayHit`

```functor
type rayHit = {
  hit: bool,
  x: float, y: float, z: float,
  nx: float, ny: float, nz: float,
  distance: float,
  tag: tag
}
```

A raycast result with hit position, normal, distance, and body tag.

For a miss, `hit` is false and the remaining fields are zeroed.

##### `Physics.collisionEvent`

```functor
type collisionEvent = { started: bool, a: tag, b: tag, sensor: bool }
```

A contact-begin or contact-end event between two bodies.

##### `Physics.tag`

```functor
let tag : (string) => tag
```

Construct a stable body tag from a string.

The empty tag is reserved as the no-body sentinel in a raycast miss.

The brand is check-time only: declare a tag once and use that VALUE at
every site — a bare string where a tag is expected is a check error. At
runtime a tag simply IS its string, so comparing one against a collision
event's `a`/`b` with `==` works.

##### `Physics.box`

```functor
let box : (float, float, float) => shape
```

Create a local-axis box shape from its full width, height, and depth.

##### `Physics.sphere`

```functor
let sphere : (float) => shape
```

Create a sphere shape from its radius.

##### `Physics.capsule`

```functor
let capsule : (float, float) => shape
```

Create a capsule shape from half-height and radius.

##### `Physics.heightfield`

```functor
let heightfield : (tag, Terrain.t) => body
```

Create a fixed heightfield body from a shared terrain descriptor.

The renderer and collider share dimensions, elevation range, asset
resolution, and pending-asset chain. Collision uses at most 1025 samples
per axis to bound frame-thread work; larger render sources are decimated.
The body supports translation via `Physics.at`; pair it only with an
unrotated, unscaled `Scene.terrain`, using the same translation.

##### `Physics.dynamic`

```functor
let dynamic : (tag, shape) => body
```

Create a dynamic body affected by forces and collisions.

##### `Physics.kinematic`

```functor
let kinematic : (tag, shape) => body
```

Create a kinematic body driven explicitly by the game.

##### `Physics.fixed`

```functor
let fixed : (tag, shape) => body
```

Create a fixed body that does not move.

##### `Physics.at`

```functor
let at : (Vec3.t, body) => body
```

Set a body's initial world position; the body is last for piping.

##### `Physics.rotateX`

```functor
let rotateX : (Angle.t, body) => body
```

Rotate a body about world X around its center; the body is last for piping.

An outer rotation applies last in world space, matching `Scene.rotateX`.
Heightfield bodies reject rotation because terrain rendering is
translation-only.

##### `Physics.rotateY`

```functor
let rotateY : (Angle.t, body) => body
```

Rotate a body about world Y around its center; the body is last for piping.

An outer rotation applies last in world space, matching `Scene.rotateY`.
Heightfield bodies reject rotation because terrain rendering is
translation-only.

##### `Physics.rotateZ`

```functor
let rotateZ : (Angle.t, body) => body
```

Rotate a body about world Z around its center; the body is last for piping.

An outer rotation applies last in world space, matching `Scene.rotateZ`.
Heightfield bodies reject rotation because terrain rendering is
translation-only.

##### `Physics.velocity`

```functor
let velocity : (Vec3.t, body) => body
```

Set a body's initial linear velocity; the body is last for piping.

##### `Physics.mass`

```functor
let mass : (float, body) => body
```

Set a body's mass; the body is last for piping.

##### `Physics.friction`

```functor
let friction : (float, body) => body
```

Set a body's friction coefficient; the body is last for piping.

##### `Physics.restitution`

```functor
let restitution : (float, body) => body
```

Set a body's restitution; the body is last for piping.

##### `Physics.linearDamping`

```functor
let linearDamping : (float, body) => body
```

Damp a body's LINEAR velocity — drag, per second; the body is last for piping.

The default is `0.0` (no drag), so a rolling sphere on a box coasts almost
forever: contact friction resists sliding, not rolling. A small value
(`0.3`–`0.8`) is what makes a marble, a puck, or a thrown prop actually
settle, and it belongs here rather than in a per-frame velocity command:
damping is a property of the body, so declaring it keeps `tick` pure.
Must not be negative. Only `dynamic` bodies integrate, so damping is inert
on a `kinematic` or `fixed` one. Changing the value writes it onto the live
body (the friction/restitution rule) — the new drag applies from the next
step, with the body's current pose and velocity untouched.

##### `Physics.angularDamping`

```functor
let angularDamping : (float, body) => body
```

Damp a body's ANGULAR velocity — spin resistance, per second; the body is
last for piping.

The default is `0.0`. Pair it with `linearDamping` for a ball that stops
rolling instead of creeping, or use it alone to bleed off spin while linear
motion is preserved. Must not be negative, `dynamic`-only, and reconciles
onto a live body exactly like `linearDamping`.

##### `Physics.sensor`

```functor
let sensor : (body) => body
```

Make a body a non-solid sensor; the body is last for piping.

##### `Physics.upright`

```functor
let upright : (body) => body
```

Lock a body's rotation so it translates but never tips.

The character-controller attribute: an upright capsule that lands, scuffs a
ledge, or leans on a wall would otherwise pick up angular velocity and
topple, which also invalidates any fixed standing-height assumption a
grounding probe makes. The body is last for piping.

##### `Physics.scene`

```functor
let scene : (Vec3.t, List<body>) => world
```

Declare a physics world from gravity and bodies.

##### `Physics.position`

```functor
let position : (tag) => position
```

Read a body's live, stepped world position.

Answers with the LAST stepped world, so a pre-step caller (`tick`, the
`physics` hook) sees the previous step and `draw` sees this frame's.

##### `Physics.linearVelocity`

```functor
let linearVelocity : (tag) => velocity
```

Read a body's live, stepped linear velocity.

The read counterpart of `Physics.setVelocity`. (`Physics.velocity` is the
body-builder attribute that sets an *initial* velocity.)

##### `Physics.cast`

```functor
let cast : (Vec3.t, Vec3.t, float) => rayHit
```

Cast a ray against the world and get the nearest hit immediately.

Unlike `Physics.raycast` — an effect whose answer arrives through `update`
after the step — this answers in place, so `tick` can branch on it while
deciding. It reads the world as of the last step, like `Physics.position`:
in `tick` that is the previous step, in `draw` this frame's. A miss is
`hit: false` with zeroed fields, not an error. `dir` need not be
normalized, so `maxDist` is in world units.

##### `Physics.castExcluding`

```functor
let castExcluding : (tag, Vec3.t, Vec3.t, float) => rayHit
```

`Physics.cast`, ignoring one body — the grounding probe.

A ray cast from inside a character's own capsule would otherwise hit that
capsule at distance 0 and report the character standing on itself. Excluding
a tag that isn't in the world excludes nothing.

##### `Physics.transformed`

```functor
let transformed : (tag, Scene.t) => Scene.t
```

Apply a body's live transform to a scene node.

The scene is last for piping.

##### `Physics.applyImpulse`

```functor
let applyImpulse : (tag, Vec3.t) => Effect.t
```

Apply an instantaneous impulse to a body.

##### `Physics.applyForce`

```functor
let applyForce : (tag, Vec3.t) => Effect.t
```

Apply a continuous force to a body for the next step.

##### `Physics.setVelocity`

```functor
let setVelocity : (tag, Vec3.t) => Effect.t
```

Replace a body's linear velocity — all three axes.

For a character controller prefer `Physics.setVelocityXZ`: writing the
vertical axis every frame fights the solver's own ground contact.

##### `Physics.setVelocityXZ`

```functor
let setVelocityXZ : (tag, float, float) => Effect.t
```

Replace a body's HORIZONTAL velocity, leaving the vertical axis alone.

The character-controller command. Steering owns x and z while the solver
keeps the y it is using to resolve the ground contact, so the game never
has to author a vertical velocity it has no opinion about — with
`Physics.setVelocity` it must write all three every frame, and the only
values available to it are a one-step-stale read or a guess.

The preserved axis is read from the live world when the command applies —
after reconcile, and after any command queued earlier the same frame. So
velocity commands in one frame compose as last-write-wins per axis, and an
axis nobody wrote is left exactly as the solver left it.

##### `Physics.setVelocityY`

```functor
let setVelocityY : (tag, float) => Effect.t
```

Replace a body's VERTICAL velocity, leaving the horizontal plane alone.

A jump that keeps the run: unlike `Physics.applyImpulse` the result does
not depend on the body's mass, and unlike `Physics.setVelocity` it does not
discard the horizontal momentum the character arrived with.

##### `Physics.teleport`

```functor
let teleport : (tag, Vec3.t) => Effect.t
```

Move a body immediately to a world position.

##### `Physics.raycast`

```functor
let raycast : (Vec3.t, Vec3.t, float, (rayHit) => 'msg) => Effect.t
```

Cast a ray and tag its `Physics.rayHit` result as a message.

##### `Physics.events`

```functor
let events : ((collisionEvent) => 'msg) => Sub.t
```

Subscribe to contact begin/end events and tag them as messages.

#### Anim

Declarative animation poses attached to scene models with `Scene.animate`.

Playheads and blend weights are values derived by the game, so the engine
owns no hidden animation clock and poses rewind and replay deterministically.

##### `Anim.t`

```functor
type t = host
```

An opaque animation pose expression.

##### `Anim.clip`

```functor
let clip : (string, float) => t
```

Sample a named glTF clip at a playhead in seconds.

The clip loops by its duration; negative playheads wrap backwards from the
end. A name the model does not define warns once and renders the bind pose
— `functor import`'s generated clip constants (`Assets.xbotClips.walk.name`)
turn that into a check-time error instead.

##### `Anim.blend`

```functor
let blend : (List<(t, float)>) => t
```

Blend a list of `(animation, weight)` pairs.

Weights are normalized and entries with non-positive weights are ignored.
Translation and scale are interpolated linearly and rotation as a
normalized quaternion mix. An entry may itself be a blend, so blends nest.

##### `Anim.rest`

```functor
let rest : () => t
```

Return the bind pose as a base for programmatic posing.

##### `Anim.add`

```functor
let add : (t, float, t) => t
```

Apply an additive animation layer to a base pose.

The base is last for piping. Weight is clamped to `0..1`, and the delta
applies only where the base has influence.

##### `Anim.mask`

```functor
let mask : (List<string>, t) => t
```

Restrict a pose to the subtrees rooted at the named joints.

Joints the mask does not cover fall out of this pose entirely — they take
the bind pose, or the other inputs of an enclosing blend. A joint name the
model does not define warns once.

##### `Anim.rotate`

```functor
let rotate : (string, Angle.t, Angle.t, Angle.t, t) => t
```

Add a local XYZ Euler rotation to one joint.

The joint counts as FULLY DRIVEN by this node, so a mask BENEATH it cannot
drop the joint; an enclosing mask — one applied to this node's result —
still can.

##### `Anim.lookAt`

```functor
let lookAt : (string, Vec3.t, Angle.t, float, t) => t
```

Aim one joint's local +Z axis at a model-space target after evaluating the
pose below it.

The target is baked into the animation value at draw time, and `Scene`
transforms sit deliberately outside the solver — so a world-space aim point
has to be inverted through the node's own transforms before it is passed
here. `maxDeflection` limits the shortest correction from the evaluated pose and
must be between 0 and 180 degrees. `weight` is clamped to `0..1`. The joint
is fully driven by this node, exactly as with `Anim.rotate`; an enclosing
mask can still exclude it.

##### `Anim.reach`

```functor
let reach : (string, string, string, Vec3.t, float, t) => t
```

Reach a model-space target with a direct two-bone joint chain.

`root`, `middle`, and `end` must name direct parent/child joints, such as an
upper arm, forearm, and hand. Unreachable targets clamp to the chain's
nearest extension. The evaluated pose below supplies the elbow bend side,
`root` must have uniform scale, and `weight` is clamped to `0..1`.

#### Terrain

Finite, asset-backed heightfield terrain shared by rendering and physics.

Heightmaps should be 16-bit grayscale PNGs. Black maps to the declared
minimum height and white maps to the maximum height.

##### `Terrain.t`

```functor
type t = host
```

An immutable terrain descriptor.

##### `Terrain.heightmap`

```functor
let heightmap : (Asset.Texture, float, float, float, float) => t
```

Create a terrain centered on the origin and spanning `width` by `depth` in XZ.

##### `Terrain.maxPixelError`

```functor
let maxPixelError : (float, t) => t
```

Set the maximum projected vertex spacing in pixels; lower is more detailed.

The default is 2 pixels. The terrain is last for piping.

##### `Terrain.color`

```functor
let color : (Color.t, t) => t
```

Set the basic lit terrain color; the terrain is last for piping.

This is the ALTERNATIVE to `Terrain.layered`, not a stage before it: it
clears any layers, so whichever of the two comes last in a pipeline wins
and the earlier one is simply dead. Pick one.

##### `Terrain.layered`

```functor
let layered : (Color.t, Color.t, Color.t, Color.t, float, t) => t
```

Blend lowland, highland, rock, and snow colors by height and slope.

`snowHeight` is in terrain-local world units. The terrain is last for
piping.

##### `Terrain.textured`

```functor
let textured : (Asset.Texture, Asset.Texture, Asset.Texture, Asset.Texture, float, t) => t
```

Dress the `layered` bands with detail maps.

Each map supplies STRUCTURE, not color: it is divided by its own average, so
it adds surface detail to the band's `layered` color without repainting it.
(A photographic ground albedo averages brown; used directly it would turn a
green hillside to dirt.) The maps blend by the same height and slope weights
as the colors, so texturing changes what a band looks like, not where it
falls. `tileSize` is the world-unit span of one tile; each map is sampled at
two scales to hide the repeat, and detail fades out with distance. Requires
`layered`. The terrain is last for piping.

##### `Terrain.grass`

```functor
let grass : (float, float, float, Color.t, t) => t
```

Add camera-local GPU-instanced grass clusters.

`spacing`, `distance`, and `bladeHeight` are terrain-local world units.
Grass is suppressed on steep, low-lying, and snowy samples. The terrain is
last for piping.

#### Time

Branded durations used by timing APIs such as `Sub.every`.

Constructing a `Time.t` explicitly prevents milliseconds and seconds from
being mixed accidentally.

##### `Time.t`

```functor
type t = host
```

An opaque duration.

##### `Time.seconds`

```functor
let seconds : (float) => t
```

Construct a duration from seconds.

##### `Time.millis`

```functor
let millis : (float) => t
```

Construct a duration from milliseconds.

##### `Time.micros`

```functor
let micros : (float) => t
```

Construct a duration from microseconds.

##### `Time.minutes`

```functor
let minutes : (float) => t
```

Construct a duration from minutes.

##### `Time.hours`

```functor
let hours : (float) => t
```

Construct a duration from hours.

##### `Time.add`

```functor
let add : (t, t) => t
```

Add two durations. This is what `1.5s + 200ms` calls.

##### `Time.sub`

```functor
let sub : (t, t) => t
```

Subtract one duration from another. This is what `1.5s - 200ms` calls.

##### `Time.scale`

```functor
let scale : (t, float) => t
```

Scale a duration by a plain number. This is what `0.5s * 2.0` calls.
It takes the duration FIRST — the shape every declared `*` has — so unlike
most of the prelude it is not written to be piped into.

##### `Time.equals`

```functor
let equals : (t, t) => bool
```

Are two durations the same? This is what `1s == 1000ms` calls.

Durations are stored in seconds, so this is FLOAT equality on that number:
`1s == 1000ms` is true, but a duration accumulated through arithmetic may
miss an exact literal by a rounding step. A duration is one-way (there is
no way back to a number), so where a tolerance matters, keep the plain
float you built it from and compare THAT.

##### `Time.less`

```functor
let less : (t, t) => bool
```

Is the first duration shorter than the second? This is what
`200ms < 1.5s` calls, and — swapped or negated — `>`, `<=`, and `>=` too.

##### `Time.s`

```functor
unit s = Time.seconds
```

Seconds as a literal suffix: `0.5s` is exactly `Time.seconds(0.5)`.

##### `Time.ms`

```functor
unit ms = Time.millis
```

Milliseconds as a literal suffix: `500ms` is exactly `Time.millis(500.0)`.

##### `Time.us`

```functor
unit us = Time.micros
```

Microseconds as a literal suffix: `250us` is exactly `Time.micros(250.0)`.

##### `Time.min`

```functor
unit min = Time.minutes
```

Minutes as a literal suffix: `2min` is exactly `Time.minutes(2.0)`.

##### `Time.hr`

```functor
unit hr = Time.hours
```

Hours as a literal suffix: `1hr` is exactly `Time.hours(1.0)`.

##### `Time.s (+)`

```functor
unit s (+) = Time.add
```

`+` on durations: `1.5s + 200ms` is `Time.add(1.5s, 200ms)`. An operator
belongs to the BRAND, so every duration suffix shares it — seconds and
milliseconds add directly.

##### `Time.s (-)`

```functor
unit s (-) = Time.sub
```

`-` on durations: `1.5s - 200ms` is `Time.sub(1.5s, 200ms)`.

##### `Time.s (*)`

```functor
unit s (*) = Time.scale
```

`*` scales a duration by a number, on either side: `0.5s * 2.0` and
`2.0 * 0.5s` are both `Time.scale(0.5s, 2.0)`.

##### `Time.s (==)`

```functor
unit s (==) = Time.equals
```

`==` on durations: `1s == 1000ms` is `Time.equals(1s, 1000ms)`, and `!=`
is its negation. Underneath it is FLOAT equality on seconds — see
`Time.equals`.

##### `Time.s (<)`

```functor
unit s (<) = Time.less
```

`<` on durations: `200ms < 1.5s` is `Time.less(200ms, 1.5s)`. `>`, `<=`,
and `>=` derive from it (swapped and/or negated), so all four orderings
come from this one declaration.

### Input

#### Input

Target-neutral continuously sampled input.

The optional `sampledInput(model, snapshot)` game hook receives one
`Input.snapshot` immediately before every fixed simulation step. XR,
gamepad, and touch are typed device domains; further devices belong as
siblings beside them on the snapshot.

##### `Input.point2`

```functor
type point2 = { x: float, y: float }
```

A plain two-dimensional point or axis pair.

##### `Input.point3`

```functor
type point3 = { x: float, y: float, z: float }
```

A plain three-dimensional point or direction.

##### `Input.quaternion`

```functor
type quaternion = { x: float, y: float, z: float, w: float }
```

A quaternion in `[x, y, z, w]` component order, matching OpenXR and glTF.

##### `Input.pose`

```functor
type pose = { position: point3, orientation: quaternion }
```

A rig-local pose where `+X` is right, `+Y` is up, and `-Z` is forward.

##### `Input.controller`

```functor
type controller = {
  active: bool,
  grip: Option.t<pose>,
  aim: Option.t<pose>,
  trigger: float,
  squeeze: float,
  thumbstick: point2,
  primaryPressed: bool,
  secondaryPressed: bool,
  thumbstickPressed: bool,
  menuPressed: bool
}
```

One XR controller's availability, poses, analog controls, and buttons.

##### `Input.xr`

```functor
type xr = {
  head: Option.t<pose>,
  left: controller,
  right: controller
}
```

Head and left/right controller state sampled from an XR runtime.

##### `Input.gamepad`

```functor
type gamepad = {
  leftStick: point2,
  rightStick: point2,
  leftTrigger: float,
  rightTrigger: float,
  south: bool,
  east: bool,
  west: bool,
  north: bool,
  leftBumper: bool,
  rightBumper: bool,
  leftStickPressed: bool,
  rightStickPressed: bool,
  dpadUp: bool,
  dpadDown: bool,
  dpadLeft: bool,
  dpadRight: bool,
  start: bool,
  select: bool
}
```

The primary connected gamepad's held state, aligned to the standard
mapping desktop and web pads share. Face buttons are POSITIONAL — `south`
is the bottom face button (A on Xbox, Cross on PlayStation, B on
Nintendo) — because letter names swap between vendors. Sticks are `-1..1`
with up-positive `y` (the XR thumbstick convention); triggers are `0..1`.
Values are raw — apply your own deadzone. Levels only: detect edges
against your model, as XR games do. Native's windowed runtime and the
web runtime both poll the first connected standard-mapping pad each
frame (on native, debug injection wins over the poll); while the
window/document is unfocused or the clock is pinned a connected pad
reads rest-level controls rather than `Option.None`. Browsers hide pads
until a button is first pressed, and headless polls nothing — there this
is `Option.None` unless injected.

##### `Input.touchPoint`

```functor
type touchPoint = { id: float, x: float, y: float }
```

One touch contact in the same top-left-origin logical coordinate space as
`mouse` (window points natively, CSS pixels on web). `id` is a small
ordinal stable for the contact's lifetime.

##### `Input.touch`

```functor
type touch = {
  touches: List<touchPoint>,
  pressed: List<touchPoint>,
  released: List<touchPoint>
}
```

Active touch contacts plus this step's transitions — the keyboard
contract for fingers: `touches` are held levels at current positions,
`pressed`/`released` are de-duplicated one-step edges (a quick tap can
appear in both while `touches` no longer carries it). A contact the
platform steals (gesture navigation) reports through `released`, never a
silently vanished touch. On the snapshot, `Option.Some` signals a touch
surface EXISTS (empty lists while idle — the cue to show touch UI);
`Option.None` means no touch input at all.

##### `Input.mouseButtons`

```functor
type mouseButtons = { left: bool, right: bool, middle: bool }
```

A fixed set of mouse buttons. `mouse.buttons` uses it for held levels;
`mouse.pressed` / `mouse.released` use it for one-step transitions.

##### `Input.mouse`

```functor
type mouse = {
  x: float,
  y: float,
  surfaceWidth: float,
  surfaceHeight: float,
  buttons: mouseButtons,
  pressed: mouseButtons,
  released: mouseButtons
}
```

Mouse position in top-left-origin logical surface coordinates, the matching
logical extent, plus held levels and transitions since the previous fixed
simulation step. Desktop uses window points; web uses CSS pixels, so this
stays stable across Retina/device-pixel-ratio changes. A quick click may be
both `pressed.left` and `released.left` in one sample.

##### `Input.snapshot`

```functor
type snapshot = {
  heldKeys: List<Key.t>,
  pressedKeys: List<Key.t>,
  releasedKeys: List<Key.t>,
  mouse: mouse,
  xr: Option.t<xr>,
  gamepad: Option.t<gamepad>,
  touch: Option.t<touch>
}
```

Keyboard/mouse levels and transitions for one fixed simulation step.

`pressedKeys` and `releasedKeys` are de-duplicated transition sets. They
survive render frames with no simulation step, are consumed by the first
catch-up step, and are empty on later steps. Native OS-repeat events still
reach the legacy `input` hook, but do not repeat `pressedKeys`.

### Effects & messaging

#### Effect

Commands returned beside a model and performed by the runtime.

Results are converted into game messages and folded back through `update`.
Effects remain outside the game's pure functional core.

##### `Effect.t`

```functor
type t = host
```

An opaque effect command.

##### `Effect.none`

```functor
let none : () => t
```

Produce no command.

##### `Effect.now`

```functor
let now : ((float) => 'msg) => t
```

Read the current Unix time in seconds and tag the result as a message.

##### `Effect.random`

```functor
let random : ((float) => 'msg) => t
```

Generate a random float in `0..1` and tag it as a message.

##### `Effect.batch`

```functor
let batch : (List<t>) => t
```

Combine effects to be performed together.

##### `Effect.send`

```functor
let send : (float, string) => t
```

Send text over a live connection by connection ID.

##### `Effect.sendMsg`

```functor
let sendMsg : (float, 'a) => t
```

Send a plain-data value over a live connection.

The peer receives `Net.Data(id, value)`. Functions and opaque host values
cannot be sent.

##### `Effect.httpGet`

```functor
let httpGet : (string, (Net.HttpResponse) => 'msg) => t
```

Perform an HTTP GET and tag its `Net.HttpResponse` as a message.

##### `Effect.httpPost`

```functor
let httpPost : (string, string, (Net.HttpResponse) => 'msg) => t
```

Perform an HTTP POST with a text body and tag its response as a message.

##### `Effect.play`

```functor
let play : (Asset.Sound) => t
```

Play a non-spatial sound once.

##### `Effect.playAt`

```functor
let playAt : (Asset.Sound, Vec3.t) => t
```

Play a spatial sound once at a world position.

##### `Effect.playThen`

```functor
let playThen : (Asset.Sound, 'msg) => t
```

Play a sound once and deliver a message when playback finishes.

The completion message is native-only; on wasm the sound plays but the
message is not delivered, so do not gate game progress on it.

##### `Effect.preload`

```functor
let preload : ('asset) => t
```

Begin loading a model or texture before it is referenced by `draw`.

The imperative prefetch; the declarative default is simply that `draw`
references the asset. The parameter is generic, but only an `Asset.Model`
or `Asset.Texture` is accepted: an `Asset.Sound` is a teaching error (a
sound decodes at play time and has no pending state), and a bare path
string is rejected toward `Asset.model` / `Asset.texture` like any other
asset consumer. `preloadThen` takes the same values.

##### `Effect.preloadThen`

```functor
let preloadThen : ('asset, 'msg) => t
```

Preload an asset and deliver a message when the load settles.

Settlement includes success and failure; `Sub.assets` reports which assets
failed. Preloads count toward `Sub.assets` totals, and unlike `playThen`'s
completion message this one is delivered on wasm too.

#### Sub

Declarative event sources returned by the optional `subscriptions` hook.

Events become game messages and are folded through `update` before `tick`.

##### `Sub.t`

```functor
type t = host
```

An opaque subscription description.

##### `Sub.none`

```functor
let none : () => t
```

Subscribe to no events.

##### `Sub.every`

```functor
let every : (Time.t, 'msg) => t
```

Deliver a message at the requested interval.

The timer is STATELESS: it fires when an integer multiple of its period
lies in the interval this frame covers, measured on the global time grid.
So a long frame that spans several boundaries fires ONCE (missed
boundaries collapse rather than queueing up), and timers keep their
phase across a hot reload.

##### `Sub.batch`

```functor
let batch : (List<t>) => t
```

Combine subscriptions.

##### `Sub.connect`

```functor
let connect : (string, (Net.NetEvent) => 'msg) => t
```

Maintain a client connection and tag its ordered `Net.NetEvent` values as messages.

A failed attempt delivers `Net.Error`, then retries forever on the game-time
clock after 250ms, doubling through 500ms, 1s, 2s, 4s, and at most 8s.
Every failed attempt remains observable as an `Error`; `Connected` resets
the next retry to 250ms. Dropping the subscription cancels the connection
and its pending retry.

##### `Sub.listen`

```functor
let listen : (string, (Net.NetEvent) => 'msg) => t
```

Maintain a server listener and tag its `Net.NetEvent` values as messages.

##### `Sub.AssetFailure`

```functor
type AssetFailure = { path: string, error: string }
```

One asset byte-load failure.

Only a failed BYTE load lands here — an asset whose bytes arrived but did
not decode counts as loaded (it renders its fallback).

##### `Sub.AssetProgress`

```functor
type AssetProgress = { loaded: float, total: float, failed: List<AssetFailure> }
```

A snapshot of loaded, total, and failed assets.

All assets are settled when `total > 0` and
`loaded + List.length(failed) == total`. Failures never join `loaded`, and
frame one can legitimately deliver `0 / 0`, so a loading screen must gate on
the whole expression rather than on `loaded == total`.

##### `Sub.assets`

```functor
let assets : ((AssetProgress) => 'msg) => t
```

Subscribe to asset-loading snapshots, including the initial state.

Delivery is driven by CHANGE, not by the time grid: the tagger fires
whenever the shell's snapshot differs from the last one it delivered
(including the first one on frame one), so this is not a per-frame poll.
Like every subscription it requires an `update` hook.

#### Persistence

Durable local state — the save slot that outlives the process.

The model is the game's live state and hot-reload preserves it, but
quitting does not: a slot is where progress goes to survive. Both
functions produce an ordinary `Effect.t`, performed by the same broker as
every other effect, so a save is deterministic under replay like anything
else.

This is the simple path for the common case — one autosave, saved whole and
read back at boot. It deliberately says nothing about where the bytes live;
a general file API over a virtual user namespace is planned, and these two
functions are meant to keep reading as the two-line convenience layer over
it.

Slots belong to the running project: natively `<project>/.functor/saves/`,
in the browser a page- and entry-scoped `localStorage` key. One process
runs one game, so the store root is process-global. The Quest/embedded
producer does not name a writable project root yet — there slots follow the
process working directory.

##### `Persistence.save`

```functor
let save : (string, 'a) => Effect.t
```

Persist a plain-data value in a local save slot.

The durable dual of the model: a slot survives quitting the game, where
hot-reload's preserved model does not. The value is carried by the same
codec as `Effect.sendMsg` — numbers, strings, bools, lists, maps, tuples,
records and variants of those — so functions and opaque host values are a
teaching error at the call site. Saving the whole model is the common
case; save what `init` can rebuild from.

The slot is a NAME, not a path: letters, digits, `_` and `-` (1–64
characters). Natively it lands in `<project>/.functor/saves/<slot>.json`,
written atomically; in the browser it is one project-scoped
`localStorage` key. Writing the same slot again replaces it.

##### `Persistence.load`

```functor
let load : (string, (Option.t<'a>) => 'msg) => Effect.t
```

Read a local save slot back and tag it as a message.

The tagger receives `Option.Some(value)` — the value exactly as saved —
or `Option.None` when the slot has never been written or what is there
cannot be read at all (a corrupt or hand-mangled file): an unreadable
save degrades to "no save", it never stops the game.

A save that reads back fine but was written by an OLDER MODEL SHAPE is
still `Option.Some` — nothing checks the shape for you. Handle that the
way you would any external data: change the slot name when the model
changes incompatibly, or save a record carrying your own version field
and match on it.

Usually issued once at boot from the first `tick`.

### Audio

#### AudioScene

Declarative audio scenes returned by the optional `soundScape` hook.

##### `AudioScene.t`

```functor
type t = host
```

An opaque audio scene.

##### `AudioScene.create`

```functor
let create : (List<AudioSource.t>) => t
```

Create an audio scene from continuous sources.

##### `AudioScene.empty`

```functor
let empty : () => t
```

Create an audio scene with no sources.

#### AudioSource

Continuous, declarative voices used in an `AudioScene`.

Sources are keyed for cross-frame identity so the runtime can reconcile a
live voice instead of restarting it every frame.

##### `AudioSource.t`

```functor
type t = host
```

An opaque continuous audio source.

##### `AudioSource.ambient`

```functor
let ambient : (string, Asset.Sound) => t
```

Create a non-spatial source identified by a stable key.

##### `AudioSource.at`

```functor
let at : (string, Asset.Sound, Vec3.t) => t
```

Create a spatial source at a world position, identified by a stable key.

##### `AudioSource.gain`

```functor
let gain : (float, t) => t
```

Set LINEAR source gain, where `1.0` is full volume; the source is last for
piping.

### UI

#### Ui

Lightweight game UI views returned by the optional `ui` hook.

Compose text and widgets into rows or columns, then pin them to a screen
anchor with `Ui.panel`. Text is 14pt monospace.

Like `draw`, the hook is a pure function of the model, and the widgets are
CONTROLLED: a view shows what the model says, and interacting with it sends
a message — the model stays the only state. Those messages (a button's, and
a slider's or text input's tagger result) are folded through `update`, so a
program with interactive UI must define that hook.

##### `Ui.view`

```functor
type view = host
```

An opaque UI view.

##### `Ui.anchor`

```functor
type anchor = host
```

An opaque screen anchor.

##### `Ui.text`

```functor
let text : (string) => view
```

Create a text view.

##### `Ui.textColor`

```functor
let textColor : (Color.t, string) => view
```

Create a colored text view.

##### `Ui.column`

```functor
let column : (List<view>) => view
```

Stack views vertically.

##### `Ui.row`

```functor
let row : (List<view>) => view
```

Arrange views horizontally.

##### `Ui.panel`

```functor
let panel : (anchor, view) => view
```

Pin a view to an anchor; the view is last for piping.

##### `Ui.topLeft`

```functor
let topLeft : () => anchor
```

Anchor a panel to the top-left corner.

##### `Ui.topRight`

```functor
let topRight : () => anchor
```

Anchor a panel to the top-right corner.

##### `Ui.bottomLeft`

```functor
let bottomLeft : () => anchor
```

Anchor a panel to the bottom-left corner.

##### `Ui.bottomRight`

```functor
let bottomRight : () => anchor
```

Anchor a panel to the bottom-right corner.

##### `Ui.center`

```functor
let center : () => anchor
```

Anchor a panel to the center of the screen.

##### `Ui.button`

```functor
let button : (string, 'msg) => view
```

Create a button that delivers a message through `update` when clicked.

The message is delivered VERBATIM, like `Sub.every`'s — so the link between
its type and `update`'s is a runtime check, not a static one.

##### `Ui.slider`

```functor
let slider : (float, float, float, 'tagger) => view
```

Create a controlled slider from minimum, maximum, value, and tagger.

A drag applies the tagger to the new value and folds the resulting message
through `update`. The maximum must exceed the minimum, and the tagger must
be a function or constructor.

##### `Ui.textInput`

```functor
let textInput : (string, 'tagger) => view
```

Create a controlled text input whose tagger receives edited text.

While a field is FOCUSED the game's `input` hook is suppressed, so keys
type into the field instead of driving the game; Escape defocuses the field
first and releases the cursor second. An `update` that transforms the text
resets the caret to the end.

#### Html

Elm-style HTML trees returned by the optional `webview` hook.

Native renders the tree over the 3D frame; wasm renders it as a DOM overlay
above the canvas.

##### `Html.node`

```functor
type node = host
```

An opaque HTML node.

##### `Html.text`

```functor
let text : (string) => node
```

Create an escaped text node.

##### `Html.element`

```functor
let element : (string, List<Attr.t>, List<node>) => node
```

Create an element from a safe tag name, attributes, and children.

Names must start with a letter and then contain only letters, digits, or
dashes. Script-capable tags such as `script` and `iframe` are refused.

##### `Html.div`

```functor
let div : (List<Attr.t>, List<node>) => node
```

Create a `div` element.

##### `Html.span`

```functor
let span : (List<Attr.t>, List<node>) => node
```

Create a `span` element.

##### `Html.button`

```functor
let button : (List<Attr.t>, List<node>) => node
```

Create a `button` element.

##### `Html.h1`

```functor
let h1 : (List<Attr.t>, List<node>) => node
```

Create an `h1` heading element.

##### `Html.h2`

```functor
let h2 : (List<Attr.t>, List<node>) => node
```

Create an `h2` heading element.

##### `Html.p`

```functor
let p : (List<Attr.t>, List<node>) => node
```

Create a paragraph element.

##### `Html.input`

```functor
let input : (List<Attr.t>) => node
```

Create a controlled, single-line text input.

Pair it with `Attr.value` and `Attr.onInput`.

##### `Html.style`

```functor
let style : (string) => node
```

Create a `style` element containing a raw CSS stylesheet.

#### Attr

Attributes and event handlers for `Html` elements.

Event attributes deliver messages through the game's `update` function.

##### `Attr.t`

```functor
type t = host
```

An opaque HTML attribute.

##### `Attr.class`

```functor
let class : (string) => t
```

Set the element's CSS class string.

##### `Attr.style`

```functor
let style : (string) => t
```

Set the element's raw inline `style` string.

##### `Attr.styles`

```functor
let styles : (List<Style.t>) => t
```

Combine typed `Style` values into one inline style attribute.

##### `Attr.id`

```functor
let id : (string) => t
```

Set the element's `id`.

##### `Attr.attr`

```functor
let attr : (string, string) => t
```

Set a safe attribute by name and value.

Names must start with a letter and then contain only letters, digits, or
dashes. Executable, navigating, document-embedding, and runtime-reserved
names (`on*`, `href`, `action`, `formaction`, `srcdoc`, and `data-fn-*`) are
refused.

##### `Attr.value`

```functor
let value : (string) => t
```

Set the controlled value of an `Html.input`.

##### `Attr.placeholder`

```functor
let placeholder : (string) => t
```

Set an input's placeholder text.

##### `Attr.onClick`

```functor
let onClick : ('msg) => t
```

Deliver a message through `update` when the element is clicked.

A click on a descendant counts too, through ordinary DOM bubbling. The
message is delivered verbatim, like `Ui.button`'s.

##### `Attr.onInput`

```functor
let onInput : ('tagger) => t
```

Apply a tagger to edited text and deliver its message through `update`.

Works on both shells. Clicking the input focuses it, after which keys type
into the field rather than reaching the game's `input` hook (Escape
defocuses first, releases the cursor second), and focus survives the
per-edit re-render. As with `Ui.textInput`, an `update` that transforms the
text resets the caret to the end. IME composition (CJK, dead keys) is
deferred on native.

#### Style

Typed inline CSS declarations combined with `Attr.styles`.

Constructors format their values immediately and share `Color.t` with the
3D APIs. Use `Html.style` for selectors, pseudo-classes, and keyframes.

##### `Style.t`

```functor
type t = host
```

An opaque inline CSS declaration.

##### `Style.flexRow`

```functor
let flexRow : () => t
```

Use horizontal flexbox layout.

##### `Style.flexColumn`

```functor
let flexColumn : () => t
```

Use vertical flexbox layout.

##### `Style.gapPx`

```functor
let gapPx : (float) => t
```

Set the flex/grid gap in pixels.

##### `Style.justifyStart`

```functor
let justifyStart : () => t
```

Align content to the start of the main axis.

##### `Style.justifyCenter`

```functor
let justifyCenter : () => t
```

Center content on the main axis.

##### `Style.justifyEnd`

```functor
let justifyEnd : () => t
```

Align content to the end of the main axis.

##### `Style.justifyBetween`

```functor
let justifyBetween : () => t
```

Distribute content with space between items.

##### `Style.alignStart`

```functor
let alignStart : () => t
```

Align items to the start of the cross axis.

##### `Style.alignCenter`

```functor
let alignCenter : () => t
```

Center items on the cross axis.

##### `Style.alignEnd`

```functor
let alignEnd : () => t
```

Align items to the end of the cross axis.

##### `Style.widthPx`

```functor
let widthPx : (float) => t
```

Set width in pixels.

##### `Style.widthPct`

```functor
let widthPct : (float) => t
```

Set width as a percentage of the parent.

##### `Style.heightPx`

```functor
let heightPx : (float) => t
```

Set height in pixels.

##### `Style.heightPct`

```functor
let heightPct : (float) => t
```

Set height as a percentage of the parent.

##### `Style.paddingPx`

```functor
let paddingPx : (float) => t
```

Set padding on all sides in pixels.

##### `Style.marginPx`

```functor
let marginPx : (float) => t
```

Set margin on all sides in pixels.

##### `Style.color`

```functor
let color : (Color.t) => t
```

Set the text color.

##### `Style.background`

```functor
let background : (Color.t) => t
```

Set the background color.

##### `Style.fontSizePx`

```functor
let fontSizePx : (float) => t
```

Set font size in pixels.

##### `Style.bold`

```functor
let bold : () => t
```

Use a bold font weight.

##### `Style.textCenter`

```functor
let textCenter : () => t
```

Center-align text.

##### `Style.borderPx`

```functor
let borderPx : (float, Color.t) => t
```

Add a solid border with pixel width and color.

##### `Style.roundedPx`

```functor
let roundedPx : (float) => t
```

Set border radius in pixels.

##### `Style.opacity`

```functor
let opacity : (float) => t
```

Set opacity in `0..1`. A value outside that range is an error, not a clamp.

##### `Style.raw`

```functor
let raw : (string, string) => t
```

Create an inline declaration for an arbitrary CSS property and value.

Property names must start with a letter and then contain only letters,
digits, or dashes.

### Assets

#### Asset

Typed locators for models, textures, and sounds.

Each asset kind has its own branded value, making wrong-kind uses a
type error. Prefer constants generated by `functor import`; constructors
remain available where a locator enters the program dynamically.

Asset consumers — `Scene.model`, `Sprite.image`/`imageRegion`,
`Terrain.heightmap`/`textured`, `Effect.play`/`playAt`/`playThen`/
`preload`/`preloadThen`, `AudioSource.ambient`/`at` — take these branded
values ONLY: a
bare path string there is a check error and, at runtime, a teaching error
pointing at the generated manifest, and an asset of the wrong kind names
the constructor that was wanted. (`Texture.file` paths, `Skybox.files`
faces, `Anim.clip` names, and `AudioSource` keys are not asset locators and
stay plain strings.)

##### `Asset.Model`

```functor
type Model = host
```

A model asset locator.

##### `Asset.Texture`

```functor
type Texture = host
```

A texture asset locator.

##### `Asset.Sound`

```functor
type Sound = host
```

A sound asset locator.

##### `Asset.model`

```functor
let model : (string) => Model
```

Construct a model locator from a relative path or URL.

##### `Asset.texture`

```functor
let texture : (string) => Texture
```

Construct a texture locator from a relative path or URL.

##### `Asset.sound`

```functor
let sound : (string) => Sound
```

Construct a sound locator from a relative path or URL.

##### `Asset.whilePending`

```functor
let whilePending : ('placeholder, 'asset) => 'asset
```

Use another asset of the same kind while a model or texture is loading.

Models and textures only — a sound decodes at play time and has no pending
state, so asking for one is a teaching error. The placeholder is just
another asset of the same kind, so placeholders chain. The requested asset
is last for piping. Failed loads use the normal fallback because failure is
no longer pending, and `Sub.assets` still reports the failure.

## Language standard library

Ships with the language. These are available in every Functor Lang program, with or without a game runner.

### Collections

#### List

Immutable list operations.

Functor Lang has no loops: iteration is `List.map`, `List.filter`, and
`List.fold`, which run iteratively in the interpreter and so consume no
evaluation depth (unlike a hand-rolled recursive walk, which trips the
recursion cap).

Every function takes its list LAST, so it threads through the thread-last
pipeline operator: `xs |> List.map(f)` is exactly `List.map(f, xs)`.

The partial accessors — `nth`, `head`, `last`, `find` — answer with
`Option.t`, never a sentinel, so the absent case has to be handled.

##### `List.map`

```functor
let map : (('a) => 'b, List<'a>) => List<'b>
```

Apply `fn` to every element, preserving order and length.

##### `List.indexedMap`

```functor
let indexedMap : ((float, 'a) => 'b, List<'a>) => List<'b>
```

Apply `fn` to every element with its 0-based index, as `fn(index, element)`
— index FIRST, unlike the subject-last list argument.

##### `List.filter`

```functor
let filter : (('a) => bool, List<'a>) => List<'a>
```

Keep the elements the predicate accepts, in order.

##### `List.fold`

```functor
let fold : (('b, 'a) => 'b, 'b, List<'a>) => 'b
```

Reduce left-to-right from an initial accumulator, calling `fn(acc, element)`.
This is the iteration primitive to reach for when `map` and `filter` do not
fit — a recursive walk hits the interpreter's recursion cap around 40–60
elements, while `fold` has no such limit.

##### `List.concatMap`

```functor
let concatMap : (('a) => List<'b>, List<'a>) => List<'b>
```

Map each element to a list and concatenate the results (one level).

##### `List.range`

```functor
let range : (float) => List<float>
```

`[0, 1, … n - 1]`. A non-positive `n` gives the empty list; a count that
is not finite, or above one million, is an error. A fractional count
truncates toward zero, so `List.range(3.7)` is `[0, 1, 2]`.

##### `List.grid`

```functor
let grid : ((float, float) => 'a, float, float) => List<List<'a>>
```

Build a `rows` x `cols` grid by calling `fn(row, col)` for every cell, both
0-based — the procedural-heightmap shape, e.g.
`Scene.heightmap(List.grid(height, rows, cols))`. Both counts must be
whole and non-negative, and the cells must total at most one million.

##### `List.maximum`

```functor
let maximum : (List<float>) => Option.t<float>
```

The largest number in the list as `Option.Some`, or `Option.None` for an
empty list — partial like `nth`/`head`/`last`/`find`. NaN elements are
ignored unless every element is NaN.

##### `List.minimum`

```functor
let minimum : (List<float>) => Option.t<float>
```

The smallest number in the list as `Option.Some`, or `Option.None` for an
empty list — `maximum`'s mirror, with the same NaN rule.

##### `List.sum`

```functor
let sum : (List<float>) => float
```

The sum of the numbers. The empty list sums to `0.0`.

##### `List.length`

```functor
let length : (List<'a>) => float
```

How many elements the list has, as a number.

##### `List.isEmpty`

```functor
let isEmpty : (List<'a>) => bool
```

Whether the list has no elements.

##### `List.reverse`

```functor
let reverse : (List<'a>) => List<'a>
```

The list in reverse order.

##### `List.append`

```functor
let append : (List<'a>, List<'a>) => List<'a>
```

The piped list followed by `other`: `xs |> List.append(ys)` is `xs` then
`ys`.

##### `List.flatten`

```functor
let flatten : (List<List<'a>>) => List<'a>
```

Concatenate a list of lists, one level deep.

##### `List.zip`

```functor
let zip : (List<'b>, List<'a>) => List<('a, 'b)>
```

Pair each element of the piped list with the element of `other` at the same
index — the PIPED list fills the first slot of every tuple. The result
truncates to the shorter of the two.

##### `List.sortBy`

```functor
let sortBy : (('a) => float, List<'a>) => List<'a>
```

Sort ascending by the number `fn` returns. The sort is STABLE and calls
`fn` exactly once per element. NaN keys sort last, tied with each other
regardless of sign, so the order is identical on every platform; `-0.0` and
`0.0` tie.

##### `List.take`

```functor
let take : (float, List<'a>) => List<'a>
```

The first `count` elements, saturating: past the end gives the whole list,
and a negative count behaves as `0`. A fractional count truncates toward
zero rather than raising — unlike `nth`, where a fractional index is a
caller bug.

##### `List.drop`

```functor
let drop : (float, List<'a>) => List<'a>
```

Everything after the first `count` elements, saturating: past the end gives
the empty list, and a negative count behaves as `0`. A fractional count
truncates, like `take`.

##### `List.any`

```functor
let any : (('a) => bool, List<'a>) => bool
```

Whether ANY element satisfies the predicate, short-circuiting at the first
one that does. The empty list is `false`.

##### `List.all`

```functor
let all : (('a) => bool, List<'a>) => bool
```

Whether EVERY element satisfies the predicate, short-circuiting at the
first one that does not. The empty list is `true`.

##### `List.nth`

```functor
let nth : (float, List<'a>) => Option.t<'a>
```

The element at a 0-based `index`, or `Option.None` when the index is out of
range. An index that is not a whole, finite number is an ERROR rather than
an absence — that is a caller bug, not a missing element.

##### `List.head`

```functor
let head : (List<'a>) => Option.t<'a>
```

The first element, or `Option.None` for an empty list.

##### `List.last`

```functor
let last : (List<'a>) => Option.t<'a>
```

The last element, or `Option.None` for an empty list.

##### `List.find`

```functor
let find : (('a) => bool, List<'a>) => Option.t<'a>
```

The first element satisfying the predicate, or `Option.None` when none
does. It stops at that first match rather than scanning the whole list.

#### Map

Immutable keyed collections.

A `Map` is plain data: it compares structurally, displays, snapshots, and
survives hot reload. Every operation returns a NEW map rather than mutating
the old one, and every function takes the map LAST so it threads through a
pipeline.

Keys are bounded to `bool`, FINITE `float`, and `string`. Inference keeps a
map homogeneous in ordinary code, and a generic or `unknown` seam is
checked again at runtime; NaN and the infinities are refused, while `-0.0`
and `0.0` are the same key.

Every map is stored in one canonical key order — bool before float before
string, then `false` before `true`, ascending numerically, and strings by
Unicode scalar value (not locale-aware). So `values`, `toList`, structural
equality, and display all agree byte-for-byte between native and wasm.

`get` and `member` are logarithmic; the immutable `insert` and `remove`
copy the ordered storage and are linear, as are `values` and `toList`;
`fromList` is O(n log n).

##### `Map.empty`

```functor
let empty : () => Map<'a, 'b>
```

The empty map.

##### `Map.get`

```functor
let get : ('a, Map<'a, 'b>) => Option.t<'b>
```

The value stored under `key`, or `Option.None` when the map has no such
key.

##### `Map.insert`

```functor
let insert : ('a, 'b, Map<'a, 'b>) => Map<'a, 'b>
```

The map with `key` bound to `value`, replacing any existing binding.

##### `Map.remove`

```functor
let remove : ('a, Map<'a, 'b>) => Map<'a, 'b>
```

The map without `key`. Removing an absent key is not an error.

##### `Map.member`

```functor
let member : ('a, Map<'a, 'b>) => bool
```

Whether the map holds a binding for `key`.

##### `Map.values`

```functor
let values : (Map<'a, 'b>) => List<'b>
```

Every value, in canonical key order.

##### `Map.toList`

```functor
let toList : (Map<'a, 'b>) => List<('a, 'b)>
```

Every `(key, value)` pair, in canonical key order.

##### `Map.fromList`

```functor
let fromList : (List<('a, 'b)>) => Map<'a, 'b>
```

Build a map from `(key, value)` pairs. For a repeated key the LAST pair
wins.

### Text

#### Text

String building, formatting, and inspection.

There is no string-concatenation operator and no character type:
interpolation (`$"score: {n}"`) covers most formatting, `Text.concat` joins
two strings, and single characters are one-character STRINGS.

Functions with a clear subject take the string LAST so they thread through
a pipeline: `s |> Text.contains("ab")` is `Text.contains("ab", s)`.

Lengths and character splits count **Unicode scalar values** — not bytes,
and not grapheme clusters. `Text.length(s)` always equals
`List.length(Text.chars(s))`.

##### `Text.concat`

```functor
let concat : (string, string) => string
```

`a` followed by `b`.

The subject is LAST here too, which means piping PREPENDS: the piped
string lands in `b`, so `"SUF" |> Text.concat("PRE")` is `"PRESUF"`.

##### `Text.fromFloat`

```functor
let fromFloat : (float) => string
```

A number rendered in Functor Lang's canonical display form — the same text
string interpolation produces. That is the shortest round-tripping form, so
`1.0` renders as `"1"`; use `Text.fixed` when a HUD needs a stable width.

##### `Text.fixed`

```functor
let fixed : (float, float) => string
```

A number rendered with exactly `decimals` digits after the point.
`Text.fixed(42.0, 0.0)` is `"42"`, the integer-formatting shape — the one
to reach for in a HUD. The digit count must be a whole number from 0 to
12; anything else is an error. Unlike the string functions, the NUMBER
comes first, so this cannot be piped on: write `Text.fixed(hp, 0.0)`, not
`hp |> Text.fixed(0.0)`.

##### `Text.toBullets`

```functor
let toBullets : (List<string>) => string
```

The strings as a bulleted block, one item per line.

##### `Text.split`

```functor
let split : (string, string) => List<string>
```

Split the subject on every occurrence of `sep`. Splitting the empty string
gives `[""]`; an empty separator is an error.

##### `Text.join`

```functor
let join : (string, List<string>) => string
```

Join the strings with `sep` between them. An empty separator is fine here —
unlike `Text.split` — and simply concatenates.

##### `Text.parseFloat`

```functor
let parseFloat : (string) => float
```

Parse a number, ignoring surrounding whitespace. Unparseable text answers
`0.0` rather than raising — validate the input yourself when the difference
matters. Text that parses to a non-finite number (`"inf"`, `"NaN"`, an
overflowing literal) degrades to `0.0` as well, so the result is always
finite.

##### `Text.length`

```functor
let length : (string) => float
```

How many Unicode scalar values the string contains.

##### `Text.chars`

```functor
let chars : (string) => List<string>
```

The string's Unicode scalar values, each as a one-character string.

##### `Text.toUpper`

```functor
let toUpper : (string) => string
```

The string uppercased, Unicode-aware — so the LENGTH may change (`"ß"`
uppercases to `"SS"`).

##### `Text.toLower`

```functor
let toLower : (string) => string
```

The string lowercased, Unicode-aware — so the length may change.

##### `Text.trim`

```functor
let trim : (string) => string
```

The string without leading or trailing whitespace.

##### `Text.contains`

```functor
let contains : (string, string) => bool
```

Whether the subject contains `needle`. The empty needle is contained in
every string.

##### `Text.replace`

```functor
let replace : (string, string, string) => string
```

Replace EVERY occurrence of `from` with `to`, never re-scanning what was
just written. An empty `from` is an error.

### Numbers & randomness

#### Math

Numeric functions and constants.

Functor Lang has one number type (`float`, an f64) and a deliberately small
operator set: `Math.mod` stands in for `%` and `Math.pow` for `^`.
Arithmetic is IEEE throughout, so `1.0 / 0.0` is infinity and NaN compares
false against everything, itself included.

Two behaviors differ from the usual defaults and are worth knowing: `mod`
is EUCLIDEAN (its result is never negative) and `round` goes half AWAY FROM
ZERO (not banker's rounding).

Unlike the collections, most of `Math` reads as ordinary notation rather
than as a pipeline: `pow`, `atan2`, `mod`, `min`, and `max` take their
NUMBER FIRST, so for the order-sensitive ones (`pow`, `atan2`, `mod`)
piping feeds the wrong slot. `clamp`, `clamp01`, `lerp`, and `smoothstep`
are the deliberate subject-last exceptions — their bounds and parameters
are configuration and their number is the subject, so they pipe:
`n |> Math.clamp(0.0, 10.0)`.

##### `Math.pi`

```functor
let pi : float
```

The ratio of a circle's circumference to its diameter — a constant VALUE,
not a function, so it is written `Math.pi` with no parentheses.

##### `Math.sin`

```functor
let sin : (float) => float
```

The sine of an angle in radians.

##### `Math.cos`

```functor
let cos : (float) => float
```

The cosine of an angle in radians.

##### `Math.tan`

```functor
let tan : (float) => float
```

The tangent of an angle in radians.

##### `Math.asin`

```functor
let asin : (float) => float
```

The arcsine, in radians. NaN outside `[-1, 1]`: it does NOT clamp, so a dot
product nudged past 1.0 by float error needs an explicit
`|> Math.clamp(-1.0, 1.0)` first.

##### `Math.acos`

```functor
let acos : (float) => float
```

The arccosine, in radians. NaN outside `[-1, 1]`, exactly like `asin`.

##### `Math.atan`

```functor
let atan : (float) => float
```

The arctangent, in radians.

##### `Math.atan2`

```functor
let atan2 : (float, float) => float
```

The angle in radians from the positive x-axis to the point `(x, y)`, using
the standard mathematical argument order with `y` FIRST.

##### `Math.sqrt`

```functor
let sqrt : (float) => float
```

The non-negative square root.

##### `Math.pow`

```functor
let pow : (float, float) => float
```

`base` raised to `exp` — the language has no `^` operator.

##### `Math.log`

```functor
let log : (float) => float
```

The natural logarithm, base e — the inverse of `Math.exp`.

##### `Math.exp`

```functor
let exp : (float) => float
```

e raised to the given power.

##### `Math.abs`

```functor
let abs : (float) => float
```

The magnitude, without sign.

##### `Math.sign`

```functor
let sign : (float) => float
```

`-1`, `0`, or `1` — and exactly `0` AT zero, so it is not a two-way branch.
NaN answers NaN.

##### `Math.floor`

```functor
let floor : (float) => float
```

The largest whole number that is not greater than `n`.

##### `Math.ceil`

```functor
let ceil : (float) => float
```

The smallest whole number that is not less than `n`.

##### `Math.round`

```functor
let round : (float) => float
```

The nearest whole number, rounding halves AWAY FROM ZERO rather than to
even: `0.5` rounds to `1`, `2.5` to `3`, and `-2.5` to `-3`.

##### `Math.mod`

```functor
let mod : (float, float) => float
```

The EUCLIDEAN remainder: the result always lands in `[0, abs(b))`, so
negatives wrap positively — `Math.mod(-1.0, 8.0)` is `7.0`, the wraparound
games want. A zero divisor answers NaN.

##### `Math.min`

```functor
let min : (float, float) => float
```

The smaller of two numbers.

##### `Math.max`

```functor
let max : (float, float) => float
```

The larger of two numbers.

##### `Math.clamp`

```functor
let clamp : (float, float, float) => float
```

`n` confined to `[low, high]`, subject-last so it pipes:
`n |> Math.clamp(0.0, 10.0)`. A `low` greater than `high` is an error, not
a silent swap.

##### `Math.clamp01`

```functor
let clamp01 : (float) => float
```

`n` confined to `[0, 1]` — the same as `Math.clamp(0.0, 1.0, n)`.

##### `Math.lerp`

```functor
let lerp : (float, float, float) => float
```

Linear interpolation from `from` toward `target` by `t` (unclamped):
`from + (target - from) * t`, and `t = 1.0` answers `target` exactly.
Mirrors `Vec3.lerp(target, t, from)` — the pipe subject is the start
value, so `x |> Math.lerp(target, t)` works like its vector sibling.
(Deliberately NOT GLSL's `mix(a, b, t)` order.)

##### `Math.smoothstep`

```functor
let smoothstep : (float, float, float) => float
```

Hermite smoothstep of `x` across `[edge0, edge1]`, clamped to `[0, 1]`.
The edges must be a finite ascending range (`edge0 < edge1`, both finite
and not overflow-wide) — anything else is an error, never a silent NaN.

#### Random

Pure, seeded pseudo-random numbers.

There is no hidden global generator: a draw takes a seed and hands back the
NEXT seed alongside its value, so randomness is an ordinary part of the
model. Thread the next seed through and a run is exactly reproducible —
which is what makes rewind, replay, and hot reload work.

Seed a stream once, at `init`: a fixed `Random.seed(42.0)` for a
reproducible run, or `Effect.random` / `Effect.now` for a different stream
each session. Distinct seeds produce DECORRELATED streams that share no
prefix, so per-entity streams do not visibly rhyme with each other.

##### `Random.Seed`

```functor
type Seed
```

An opaque PRNG seed.

The brand keeps seeds out of arithmetic: a bare number where a `Seed` is
expected, or `seed + 1.0` to derive a sibling stream, is a check-time error
— use `Random.fork` instead. At runtime a seed is still plain data, so it
snapshots, hot-reloads, and time-travels like any other model field.

##### `Random.seed`

```functor
let seed : (float) => Seed
```

Make a seed from any finite number.

The number's BITS are hashed, so fractional seeds are as usable as whole
ones — `Random.seed(0.42)` from an `Effect.random` result names a distinct
starting point just as `Random.seed(42.0)` does.

##### `Random.step`

```functor
let step : (Seed) => (float, Seed)
```

Draw the next value, as `(value, nextSeed)` with `value` in `[0, 1)`.

The same seed always yields the same pair. Bind both halves and carry the
next seed forward — `let (v, next) = Random.step(model.seed) in …` —
otherwise the stream never advances.

##### `Random.range`

```functor
let range : (float, float, Seed) => (float, Seed)
```

Draw one value rescaled into `[lo, hi)`, as `(value, nextSeed)` — one
`step` draw, interpolated between the bounds.

Only finiteness is checked, so the bounds are yours to get right: reversed
ones simply interpolate the other way (landing in `(hi, lo]`), and equal
ones always answer that value.

##### `Random.fork`

```functor
let fork : (float, Seed) => Seed
```

The seed of decorrelated child stream `i` — the typed replacement for
deriving sibling streams by arithmetic. Subject-last, so per-entity streams
read as `model.seed |> Random.fork(i)`, and any number may name a stream.

### Fallibility

#### Option

A value that may be absent.

`Option` is an ordinary generic variant bundled with the language, so it is
available in every project and under the plain `functor-lang` CLI. It is
what the partial accessors (`List.head`, `Map.get`, …) answer with instead
of a sentinel, which is what forces the absent case to be handled.

ALWAYS qualify the constructors: bare `Some` / `None` do not resolve — they
belong to this module, so write `Option.Some(x)` and `Option.None` in both
expressions and patterns (or `open Option` first).

Helpers take the option LAST, so they thread through a pipeline:
`Option.Some(41.0) |> Option.map((n) => n + 1.0) |> Option.defaultValue(0.0)`.

##### `Option.t`

```functor
type t<'value> =
  | Some(value: 'value)
  | None
```

A present value (`Option.Some`) or its absence (`Option.None`).

##### `Option.map`

```functor
let map : (('value) => 'mapped, t<'value>) => t<'mapped>
```

Transform a present value; leave `None` unchanged.

##### `Option.bind`

```functor
let bind : (('value) => t<'mapped>, t<'value>) => t<'mapped>
```

Continue with an optional computation when a value is present.

##### `Option.defaultValue`

```functor
let defaultValue : ('value, t<'value>) => 'value
```

Return the contained value, or an eager fallback for `None`.

##### `Option.defaultWith`

```functor
let defaultWith : (() => 'value, t<'value>) => 'value
```

Return the contained value, computing the fallback only for `None`.

##### `Option.isSome`

```functor
let isSome : (t<'value>) => bool
```

Whether the option contains a value.

##### `Option.isNone`

```functor
let isNone : (t<'value>) => bool
```

Whether the option is `None`.

##### `Option.filter`

```functor
let filter : (('value) => bool, t<'value>) => t<'value>
```

Keep a present value only when it satisfies the predicate.

##### `Option.toList`

```functor
let toList : (t<'value>) => List<'value>
```

Convert `Some(value)` to `[value]` and `None` to `[]`.

#### Result

A computation that either succeeded or carries an error.

`Result` is an ordinary generic variant bundled with the language, so it is
available in every project and under the plain `functor-lang` CLI. Use it
where a failure needs to explain itself; use `Option` where absence needs
no explanation.

ALWAYS qualify the constructors: bare `Ok` / `Error` do not resolve — write
`Result.Ok(x)` and `Result.Error(e)` in both expressions and patterns (or
`open Result` first).

Helpers take the result LAST, so they thread through a pipeline.

##### `Result.t`

```functor
type t<'value, 'error> =
  | Ok(value: 'value)
  | Error(error: 'error)
```

A success carrying a value (`Result.Ok`) or a failure carrying an error
(`Result.Error`).

##### `Result.map`

```functor
let map : (('value) => 'mapped, t<'value, 'error>) => t<'mapped, 'error>
```

Transform a successful value; leave an error unchanged.

##### `Result.mapError`

```functor
let mapError : (('error) => 'mapped, t<'value, 'error>) => t<'value, 'mapped>
```

Transform an error; leave a successful value unchanged.

##### `Result.bind`

```functor
let bind : (('value) => t<'mapped, 'error>, t<'value, 'error>) => t<'mapped, 'error>
```

Continue with a result-producing computation after success.

##### `Result.defaultValue`

```functor
let defaultValue : ('value, t<'value, 'error>) => 'value
```

Return the successful value, or an eager fallback for an error.

##### `Result.defaultWith`

```functor
let defaultWith : (('error) => 'value, t<'value, 'error>) => 'value
```

Return the successful value, or compute a fallback from the error.

##### `Result.isOk`

```functor
let isOk : (t<'value, 'error>) => bool
```

Whether the result is successful.

##### `Result.isError`

```functor
let isError : (t<'value, 'error>) => bool
```

Whether the result contains an error.

##### `Result.toOption`

```functor
let toOption : (t<'value, 'error>) => Option.t<'value>
```

Convert `Ok(value)` to `Option.Some(value)` and an error to `Option.None`.

### Input

#### Key

Keyboard keys, as a variant rather than strings.

`Key` is built in — no declaration and no import. The `input` hook's `key`
parameter and the `Input.snapshot` key sets all carry these constructors,
so a misspelling (`Key.Enterr`) is a load-time error instead of an arm that
silently never matches. Match them (`| Key.W =>`) or compare them
(`key == Key.Enter`).

##### `Key.t`

```functor
type t =
  | A | B | C | D | E | F | G | H | I | J | K | L | M
  | N | O | P | Q | R | S | T | U | V | W | X | Y | Z
  | Up | Down | Left | Right
  | Space | Enter | Escape
  | Num0 | Num1 | Num2 | Num3 | Num4 | Num5 | Num6 | Num7 | Num8 | Num9
```

A keyboard key.

The digit row is `Num0` … `Num9` — constructor names have to be
identifiers, so a bare digit is not one of them. Keys the platform reports
that Functor does not name are never delivered to game logic.

#### Mouse

Mouse buttons, as a variant rather than strings.

The mouse twin of `Key`, and built in the same way: the `mouseButton`
hook's `button` parameter carries these constructors, so a typo is caught
at load time. Match them (`| Mouse.Left =>`) or compare them
(`button == Mouse.Right`).

##### `Mouse.t`

```functor
type t =
  | Left | Right | Middle
```

A mouse button. Buttons beyond these three are never delivered to game
logic.

### Diagnostics

#### Debug

The observability escape hatch.

`Debug` is the one impure corner of the standard library, and it is impure
only in the direction of the terminal: it cannot influence the model or the
simulation, so a game with and without it evolves identically.

##### `Debug.log`

```functor
let log : (string, 'a) => 'a
```

Log `label: value` and return `value` UNCHANGED — an Elm-style trace.

The value is rendered exactly as `functor-lang run` displays it, whatever
its type. Because the label comes first and the subject last, it reads
standalone (`Debug.log("x", model.x)`) and threads through a pipeline
(`model.x |> Debug.log("x") |> Math.clamp01`) without changing what flows
through.

Under the plain `functor-lang` CLI the line goes to stdout; under the game
runner it goes to the CLI's log stream, or the browser console on wasm. It
is NOT rate-limited, so a call in `tick` or `draw` fires every frame —
prefer an event path such as `input` or `update`, or remove the call when
you are done with it.
