---
title: "renderer"
description: "The renderer namespace — the engine's Luau API reference for renderer."
section: "API Reference"
slug: "api-renderer"
canonical: "https://origozero.ai/docs/api-renderer"
updated: "2026-09-06T10:26:32.910958072+00:00"
tags: ["api", "reference"]
---

# renderer

The `renderer` namespace — 462 functions.

## globals/renderer/anisotropy {#globals-renderer-anisotropy}

```lua
renderer.anisotropy() -> number
```

The maximum anisotropy material textures are sampled with right now —
the requested level clamped to what this device honours.

**Returns** `number` — The effective level, 1 through 16.

```lua
if renderer.anisotropy() < 4 then ... end
```

## globals/renderer/atmospherics/held {#globals-renderer-atmospherics-held}

```lua
renderer.atmospherics.held() -> boolean
```

Whether a hold is standing on the air right now.

**Returns** `boolean` — True while at least one `renderer.atmospherics.hold` stands.

```lua
if renderer.atmospherics.held() then print("clear air") end
```

## globals/renderer/atmospherics/hold {#globals-renderer-atmospherics-hold}

```lua
renderer.atmospherics.hold(share: number?) -> () -> ()
```

Hold the air between the camera and every surface at a stated share of
what the scene authored, and return the release. At the default 0 the
media contribute nothing and a surface renders in its own colour, which is
what lets a reader judge an albedo, a tint or a material while another
slice of a shared world drives the weather. The share reaches aerial
perspective, height fog and volumetric light scattering; the sky, the sun
and the light they put on a surface are untouched, because those are what
the surface's colour is made of. Holds nest: the innermost names the
share, and the authored air is back once the last release is called. Each
release ends its own hold whatever order the releases come in, so two
callers holding at once each end their own.

**Parameters**

- `share` `number` _(optional)_ — How much of the authored air reaches the image, in [0, 1].
Defaults to 0 — no air at all.

**Returns** `() -> ()` — A function that releases this hold. Calling it twice releases once.

```lua
local release = renderer.atmospherics.hold() ; local png = tools.use("capture", "fromPosition", { position = { 0, 2, 8 } }) ; release()
```

## globals/renderer/atmospherics/onChange {#globals-renderer-atmospherics-onchange}

```lua
renderer.atmospherics.onChange(listener: (number) -> ()) -> () -> ()
```

Register a listener called with the share now in force whenever it
changes — a hold taken, a hold released — and return the unsubscribe. A
system that packs a medium into a GPU buffer registers here and re-packs
what it has already pushed, so the buffer carries the share before the
frame the hold was taken on is drawn rather than a frame later.

**Parameters**

- `listener` `(number) -> ()` — Called with the share now in force, in [0, 1].

**Returns** `() -> ()` — A function that removes this listener.

```lua
local stop = renderer.atmospherics.onChange(function(share) pushParams() end)
```

## globals/renderer/atmospherics/share {#globals-renderer-atmospherics-share}

```lua
renderer.atmospherics.share() -> number
```

The share of the authored air that reaches the image: the innermost
hold's share while one stands, and 1 otherwise. A system that packs a
medium multiplies its extinction — `aerial`, a fog `density` — by this,
and a hold then reaches that medium however it is being driven.

**Returns** `number` — A number in [0, 1]. 1 when nothing holds.

```lua
local density = state.density * renderer.atmospherics.share()
```

## globals/renderer/blendedBatching {#globals-renderer-blendedbatching}

```lua
renderer.blendedBatching() -> boolean
```

Whether blended neighbours sharing a draw key draw together.

**Returns** `boolean`

## globals/renderer/bounds/clear {#globals-renderer-bounds-clear}

```lua
renderer.bounds.clear(id: string) -> boolean
```

Withdraw the box an entity published, so it stops contributing to the
entity's reported extent.

**Parameters**

- `id` `string` — Entity id.

**Returns** `boolean` — True when there was a published box to withdraw.

```lua
renderer.bounds.clear(id)
```

## globals/renderer/bounds/set {#globals-renderer-bounds-set}

```lua
renderer.bounds.set(id: string, min: any?, max: any?) -> boolean
```

Publish the local-space box an entity's content-drawn geometry occupies.
`entity:bounds()` and `entity:hierarchyBounds()` union it with whatever
mesh geometry the entity has, each carried out of its own local space, so
framing a camera on the entity frames what a feature actually draws.

**Parameters**

- `id` `string` — Entity id.
- `min` `any` _(optional)_ — Local-space minimum corner — a Vec3 table or a 3-element array.
- `max` `any` _(optional)_ — Local-space maximum corner, same shape as `min`.

**Returns** `boolean` — True when the box was stored; false for a non-finite or inverted box.

```lua
renderer.bounds.set(id, cloud.boundsMin, cloud.boundsMax)
```

## globals/renderer/captureView/channelId {#globals-renderer-captureview-channelid}

```lua
renderer.captureView.channelId(name: string) -> number?
```

The debug channel a registered view draws on — what a feature passes as
its pass `debugChannel`. Nil when no view is registered under `name`.

**Parameters**

- `name` `string` — The view name.

**Returns** `number?` — The channel number or nil.

```lua
ctx.enqueue { ..., debugChannel = renderer.captureView.channelId("lightmap") }
```

## globals/renderer/captureView/list {#globals-renderer-captureview-list}

```lua
renderer.captureView.list() -> { any }
```

Every registered capture view as `{ name, channel, description }` records
— what backs the discoverability of `capture pass=<name>` and the
unknown-view error's suggestion list.

**Returns** `{ any }` — An array of view records.

```lua
for _, v in ipairs(renderer.captureView.list()) do ... end
```

## globals/renderer/captureView/ready {#globals-renderer-captureview-ready}

```lua
renderer.captureView.ready(name: string) -> boolean
```

Whether a registered view can draw yet. A view's passes are enqueued
from the moment its render feature first runs, but they are skipped while
the materials they name have no pipeline — their shader is still compiling —
so for the first frames of a session a camera bound to the view renders the
ORDINARY view into its target, and the image gives no sign of it. This
reports the difference, and reports it before any camera is on the view, so
it is answerable for the first camera bound to one. False for an
unregistered name.

**Parameters**

- `name` `string` — The view name.

**Returns** `boolean` — Whether this view's passes have resolved everything drawing needs.

```lua
repeat task.wait() until renderer.captureView.ready("zfighting")
```

## globals/renderer/captureView/register {#globals-renderer-captureview-register}

```lua
renderer.captureView.register(name: string, config: any?) -> number
```

Register (or update) a content capture view under `name` and return the
debug CHANNEL number assigned to it. A render feature gates its pass to this
channel (`debugChannel = channel`) so the pass draws only when a capture
selects the view. Idempotent: re-registering the same name keeps its channel.

**Parameters**

- `name` `string` — The view name, selected via `capture pass=<name>`.
- `config` `any` _(optional)_ — `{ description?, ensure?, warmup?, renderLayers? }`. `ensure` is
called before a capture of this view so the feature that draws it is live
(e.g. create it on demand). `warmup` is how many present frames a capture
lets the view accumulate before it reads — set it when the feature retains
prior-frame state (a temporal diff) so the first capture reads a warm
result. `renderLayers` is the layer spec a capture of this view uses when
the caller named none — a view that draws its own geometry and wants the
scene's kept out of the frame (and out of the depth buffer it tests
against) names only its own layer.

**Returns** `number` — The channel number assigned to the view.

```lua
local ch = renderer.captureView.register("lightmap", { description = "...", ensure = fn })
```

## globals/renderer/captureView/resolve {#globals-renderer-captureview-resolve}

```lua
renderer.captureView.resolve(name: string) -> any
```

Resolve a capture view by name to its `{ channel, ensure, description,
warmup }` record, or nil when no view is registered under `name`.

**Parameters**

- `name` `string` — The view name.

**Returns** `any` — The view record or nil.

```lua
local v = renderer.captureView.resolve("lightmap")
```

## globals/renderer/captureView/unregister {#globals-renderer-captureview-unregister}

```lua
renderer.captureView.unregister(name: string) -> boolean
```

Withdraw a capture view. A subsequent `capture pass=<name>` no longer
resolves to it (falls through to the unknown-view error).

**Parameters**

- `name` `string` — The view name.

**Returns** `boolean` — True when a view was registered under `name`.

```lua
renderer.captureView.unregister("lightmap")
```

## globals/renderer/clearShadowHero {#globals-renderer-clearshadowhero}

```lua
renderer.clearShadowHero() -> boolean
```

Release the hero caster, so the directional shadow is the cascades'
alone again and the layer the hero view rendered into is given back.

**Returns** `boolean` — Whether a caster was registered.

```lua
renderer.clearShadowHero()
```

## globals/renderer/clearShadowProxy {#globals-renderer-clearshadowproxy}

```lua
renderer.clearShadowProxy(mesh: string?) -> number
```

Stop proxying `mesh`, so it rasterizes its own geometry into shadow
views again. Called with no argument, drops every registration.

**Parameters**

- `mesh` `string` _(optional)_ — The mesh to stop proxying. Omit to clear all of them.

**Returns** `number` — How many registrations were removed.

```lua
renderer.clearShadowProxy(statueMesh)
print(renderer.clearShadowProxy(), "proxies dropped")
```

## globals/renderer/collect {#globals-renderer-collect}

```lua
renderer.collect() -> RuntimeCollection
```

Release every runtime texture, material, mesh and render feature nothing
holds: no handle a script still reaches, no live owner, no reference from
live engine state, no asset backing it, no hold. A root scene load runs this
once the new scene stands, so what the previous scene's content created and
nothing still wears goes with that scene; calling it directly collects at
any other moment. A session material's handle counts as reached while the
entity it was keyed for stands, and stops counting once that entity is
gone.
It reaches the GPU textures the device holds beside the registry's own: a
texture the cache loaded for an asset goes once nothing live names it and
is read back from that asset the next time something asks for it, while one
no asset answers for stays, there being nothing to read it back from — a
render pass's own target, a colour swatch, an atlas the engine built. A
texture the ASSET path uploaded and whose asset has since been removed has
nothing to come back from either, and the collection decides about it from
its holders the way it does about every other resource: a handle a script
still reaches, a live owner, a reference from live engine state, a hold.
Features go first, then materials, then meshes, then textures, so a texture
only a released material named goes with the material. Runs a full garbage
collection first, so a handle nothing reaches counts as let go, and yields
for the frame the census runs on. A handle the calling function still has
in a variable — or in a temporary it has not overwritten — is one a script
reaches, so a resource created in the function that collects is let go by
the next collection rather than this one.

**Returns** `RuntimeCollection` — `{ released = { texture, material, mesh, feature }, kept, entries }` — the counts released per kind, how many stayed, and every resource's status with `action = "released" | "kept"`.

```lua
local c = renderer.collect() print(c.released.texture, c.kept)
```

## globals/renderer/compiledShaders {#globals-renderer-compiledshaders}

```lua
renderer.compiledShaders() -> { string }
```

Every name `renderer.compiledSource` answers for — one per name a
shader compile has run under this session, whether it succeeded or failed.
What makes the composed-source surface enumerable rather than something to
guess a key for.

**Returns** `{ string }` — An array of shader names, sorted.

```lua
for _, name in renderer.compiledShaders() do print(name) end
```

## globals/renderer/compiledSource {#globals-renderer-compiledsource}

```lua
renderer.compiledSource(shader: string) -> string?
```

The WGSL the shader compiler received under one name, exactly as it
received it — the composed module, which is what a compile error's line
numbers and handle indices are positions in. Answers under any name a
compile ran under (identity, guid, alias, or a `program` from
`renderer.shaderVariants()`), for a shader that declares no features, and
for a shader whose compile FAILED, which is the case it exists for: a
message about a function body carries a position and nothing else, and the
text that position is in is this. The failed text stands for as long as
`shaderRef:compileStatus()` reports that failure under the same name.

**Parameters**

- `shader` `string` — Any name a shader compiled under — identity, guid, alias, or a
`shaderVariants()` program name.

**Returns** `string?` — The composed WGSL, or nil for a name no compile has run under.

```lua
local status = asset.resolve("myShader", "shader"):compileStatus()
if status.status == "failed" then
print(status.error)
print(renderer.compiledSource("myShader"))
end
```

## globals/renderer/compositeSize {#globals-renderer-compositesize}

```lua
renderer.compositeSize() -> { width: number, height: number }
```

The size of the image the post-scene phases worked on in the last
presented frame — the target the UI composites onto, which every pass
after the scene reads as `@scene.color` and writes into, and which a
`screenSpace = "composite"` render target follows. While the renderer
presents the viewport itself that is the display's own size, whatever
fraction of it the scene rasterized at; while a UI viewport panel owns
the presentation it is the size the scene rasterized at, since the panel
draws the scene target at its own rect and nothing upscales before the
composite. Both read `0` before a frame has drawn.

**Returns** `{ width: number, height: number }` in pixels.

```lua
local c = renderer.compositeSize()
```

## globals/renderer/cullStats {#globals-renderer-cullstats}

```lua
renderer.cullStats() -> {
```

What the last completed frame decided to draw. `total` renderables went
into the frustum test, `culled` fell outside it and `visible` survived. Of
those, occlusion culling measured `occlusionTested` against the depth
pyramid and proved `occlusionCulled` were entirely behind other geometry —
both 0 while `renderer.occlusionCulling()` is false. A renderable the
pyramid has no say over — one that laid no depth in the pre-pass, one whose
bounds were never recorded, one straddling the near plane — is measured
against nothing and counted in neither, so the gap between `visible` and
`occlusionTested` reads how much of the frame the test could speak for.

This answers for the main camera. What a shadow view's own volume did with
the frame's casters is on that view's row in `renderer.shadowViews()`.

**Returns** `{ total: number, culled: number, visible: number, occlusionTested: number, occlusionCulled: number }`

```lua
local s = renderer.cullStats(); print(s.visible - s.occlusionCulled, "drawn")
```

## globals/renderer/debugPass/builtins {#globals-renderer-debugpass-builtins}

```lua
renderer.debugPass.builtins() -> { string }
```

The built-in debug-pass names, one per channel in channel order — the
engine's built-in pass vocabulary (final, albedo, normal, depth, …).

**Returns** `{ string }` — An array of built-in pass names.

```lua
for _, n in ipairs(renderer.debugPass.builtins()) do ... end
```

## globals/renderer/debugPass/channel {#globals-renderer-debugpass-channel}

```lua
renderer.debugPass.channel(name: string) -> number?
```

The channel a debug-pass NAME renders on: a built-in pass, else a content
capture view registered via `renderer.captureView`. Nil when the name is
neither — the signal a selector uses to reject an unknown pass.

**Parameters**

- `name` `string` — A debug-pass name (e.g. "normal", "depth", "lightmap").

**Returns** `number?` — The channel number, or nil for an unknown name.

```lua
local ch = renderer.debugPass.channel("normal")   -- 7
```

## globals/renderer/debugPass/list {#globals-renderer-debugpass-list}

```lua
renderer.debugPass.list() -> { string }
```

Every selectable debug-pass name: the built-in passes plus every
registered content capture view. What a debug-pass selector offers.

**Returns** `{ string }` — An array of pass names.

```lua
local passes = renderer.debugPass.list()
```

## globals/renderer/debugPass/name {#globals-renderer-debugpass-name}

```lua
renderer.debugPass.name(channel: number) -> string?
```

The canonical NAME for a debug channel: a built-in pass name for a
built-in channel, else a registered capture view's name. Channel 0 is
"final" (the lit image). Nil when no pass owns the channel.

**Parameters**

- `channel` `number` — The channel number.

**Returns** `string?` — The pass name, or nil.

```lua
local name = renderer.debugPass.name(7)   -- "normal"
```

## globals/renderer/depthPrepass {#globals-renderer-depthprepass}

```lua
renderer.depthPrepass() -> boolean
```

Whether the opaque depth pre-pass is currently enabled.

**Returns** `boolean`

## globals/renderer/depthPrepassOrder {#globals-renderer-depthprepassorder}

```lua
renderer.depthPrepassOrder() -> { runs: number, reordered: number }
```

What the last frame's depth pre-passes planned, and how far their
sequences were from near-to-far before they ordered. `runs` counts the
instanced draws planned; `reordered` counts the adjacent pairs the sort
moved past each other, taken before it ran. Both are summed over every
pre-pass the frame ran — the window plus each render-target camera, each
ordering against its own camera. Both read `0` while the pre-pass or the
ordering is off, and `reordered` reads `0` for a frame that already stood
in order. The ordering leaves no other trace — the draws, the depth and the
image are the same either way.

**Returns** `{ runs: number, reordered: number }`

```lua
local o = renderer.depthPrepassOrder()  -- o.reordered > 0 → it sorted
```

## globals/renderer/depthPrepassOrdering {#globals-renderer-depthprepassordering}

```lua
renderer.depthPrepassOrdering() -> boolean
```

Whether the depth pre-pass is submitted nearest-first.

**Returns** `boolean`

## globals/renderer/destroy {#globals-renderer-destroy}

```lua
renderer.destroy(handleOrKind: any?, id: string?) -> boolean
```

Free the GPU resource a renderer resource holds (the GPU-destroy verb).
Takes any of the forms that name it: the handle a create returned, routed
by its `category` so one call releases a mixed set of handles; the id a
listing hands out, whose kind is read back off what the renderer holds
under it — the runtime registry, the material definitions, the live
features, and the device itself for an asset's own texture or mesh; or the
kind with the id beside it, the shape `renderer.hold` and
`renderer.references` take, which is what names the kind for an id two of
them answer to. An id nothing holds anything under releases nothing and
answers false. The on-disk asset, if any, is untouched. A CPU handle's
`:unload()` frees the CPU copy separately.

**Parameters**

- `handleOrKind` `any` _(optional)_ — A `MeshHandle`, `TextureHandle`, `MaterialHandle` or feature
handle; the id itself; or the kind (`"texture"`, `"material"`, `"mesh"`,
`"feature"`) with the id as the second argument.
- `id` `string` _(optional)_ — The guid or registry key, when the first argument is a kind.

**Returns** `boolean` true if a GPU resource was known under the id.

```lua
renderer.destroy(meshHandle); renderer.destroy(materialHandle)
renderer.destroy(renderer.texture.list()[1].guid)
renderer.destroy("mesh", guid)
```

## globals/renderer/deviceGeneration {#globals-renderer-devicegeneration}

```lua
renderer.deviceGeneration() -> number
```

Which render device this process is on, counted from the first.

A render device is lost when a driver resets, when the GPU is taken away,
or when a browser reclaims a WebGPU context. The engine answers by building
another device and re-deriving this session's resources onto it, and this
number moves by one each time it does. Anything held across frames that was
built from a GPU resource records this beside it and remakes it when the two
differ; `engine.onDeviceRebuilt` is the hook that fires when it moves.

**Returns** `number` — The current device generation, counting from 1.

```lua
local generation = renderer.deviceGeneration()
engine.onDeviceRebuilt(function(g) print("device " .. g) end)
```

## globals/renderer/deviceState {#globals-renderer-devicestate}

```lua
renderer.deviceState() -> string
```

Whether the render device this process draws through is the one it is
using, one it is replacing, or one it has stopped trying to replace.

`"ready"` is a live device. `"rebuilding"` is the window between a device
reporting itself lost and another being in place: every GPU resource built
from the old one is invalid, the frames in that window draw nothing, and
anything reaching the GPU refuses. `"abandoned"` is after the engine gave
up — the adapter refused every attempt, so this session draws no more
frames.

Work that spans the device — build a render target, draw into it, read it
back — reads this to tell an operation that failed because the device went
out from under it, which is worth doing again once
`renderer.deviceGeneration()` moves, from one that failed on its own terms.
The loss is reported before the next device exists, so the two readings
answer different halves: this one says a replacement is coming, the
generation says it arrived.

**Returns** `string` — `"ready"` | `"rebuilding"` | `"abandoned"`.

```lua
if renderer.deviceState() == "rebuilding" then return end
```

## globals/renderer/drawDiagnostics {#globals-renderer-drawdiagnostics}

```lua
renderer.drawDiagnostics() -> { DrawDiagnostic }
```

Every renderable that is NOT drawing what its material says — the one
call for "why does this surface look wrong". Three states land here: a
surface rendering as the magenta placeholder (`substituted`), one the
renderer could bind nothing for at all (`outcome = "skipped"`), and one
drawing a program whose most recent compile FAILED (`stale`), which is what
a shader edited into brokenness looks like — the pipeline its last good
compile built keeps drawing, so the picture is intact and answers to none of
the edits since. Each row names the entity, the program asked for, the
program bound, `programStatus` — the compile gate's word about the program
the material NAMED — and the one cause
from `shaderCompileFailed` / `shaderNotRegistered` / `shaderNotCompiledYet`
/ `noGbufferEntry` / `renderStateKeyNotBuilt` / `noPipelineForTarget` /
`unshaded`, with the compiler's own message in `detail` or `programError`.
Covers every renderable the renderer holds, whether or not a camera reached
it: a row with `observed = false` and `outcome = "notDrawn"` carries the
renderer's own resolution for one this frame drew nowhere, so a broken
surface off-screen is reported the same as one in frame. An empty result
means every renderable the renderer holds is drawing the program its
material named and that program compiles. Answers on the deferred path as
well as forward, and in edit mode as well as play.

**Returns** `{ DrawDiagnostic }`

```lua
for _, d in renderer.drawDiagnostics() do print(d.entity, d.reason, d.detail) end
if #renderer.drawDiagnostics() == 0 then print("every surface is drawing what it says") end
```

## globals/renderer/drawStats {#globals-renderer-drawstats}

```lua
renderer.drawStats() -> {
```

What the last completed frame actually submitted. `draws` counts every
geometry draw call the frame issued — the camera's passes, each shadow
view a shadow-casting light adds, and whatever a render feature draws —
and `instances` counts the instances those draws covered. The pair is what
separates one draw carrying five hundred instances from five hundred draws
carrying one each, so it reads how well the scene batches rather than how
many objects are in it.

`compacted` is how many of those instances the frame planned through draws
whose instance count the GPU decides: the culler's own per-object answers
packed into a dense run, so an object it rejects is absent from the draw
instead of collapsing to nothing in the vertex stage. `compactedDrawn` is
how many of them survived, counted on the GPU as it packed them — a pass
that then skips a whole draw over its own layer or visibility answer
leaves that draw's instances in both numbers.

The plan is made over the populations the frame draws, and the tests
answer which of their instances the packing keeps. That packing runs
before any pass has resolved the depth occlusion culling is tested
against, so on its own it reads the frustum and screen-size answers
alone. With `setOcclusionCulling` armed the frame packs the same plan a
second time once the test has answered, and `compactedDrawn` then counts
what came through occlusion as well.

`compactedDrawn` comes back from the buffer the GPU wrote, so it describes
a frame that has finished while `compacted` describes the most recent
plan, and it holds the last count the GPU wrote until another arrives — a
frame that compacts nothing reads `compacted` 0 beside the count from the
last frame that did. In a scene standing still the gap between the two is
the front-end work culling removed.

`materialBinds` is how many times the frame's geometry passes set a
material's parameter group, and `materialBindsElided` how many times a
pass reached that decision and found the group already bound. Their sum
is how many times the decision was reached — once per unit of geometry
submitted, which sits at or below `draws`, since a mesh of several
primitives draws once per primitive under one set of binds. The ratio
inside the pair is what material binding costs the frame: the batched
opaque geometry is gathered into runs sharing a material, so a frame of
many such draws over few materials binds about once per material rather
than once per unit. `materialExtraBinds` and `materialExtraBindsElided`
are the same pair for the second group, the storage bindings a shader
declares for itself, which only the shaders that have them ever bind.

`pipelineBinds` and `pipelineBindsElided` are the same pair for the
pipeline itself: how many times the frame's geometry passes set one, and
how many times a pass reached that decision and found the pipeline it
wanted already bound. Which pipeline a unit needs follows its shader, its
material's render state and its mesh's vertex layout together, so a scene
whose units share all three costs one set for the run of them, while units
differing in any one of the three each pay their own. Their sum is how
many units reached the pipeline decision, which sits at or above what the
material pair reports: a unit the pass settles a pipeline for and then
abandons — one whose material group resolved to nothing — counts here and
never reaches the material decision.

Every figure here is the whole frame's, the main camera's draws and every
shadow view's summed together. `renderer.shadowViews()` splits `compacted`
and `compactedDrawn` across the views that made them, and carries the
camera's own share beside them.

**Returns** `{ draws: number, instances: number, compacted: number, compactedDrawn: number, materialBinds: number, materialBindsElided: number, materialExtraBinds: number, materialExtraBindsElided: number, pipelineBinds: number, pipelineBindsElided: number }`

```lua
local d = renderer.drawStats(); print(d.instances / math.max(d.draws, 1), "instances per draw")
print(renderer.drawStats().compactedDrawn, "of", renderer.drawStats().compacted, "survived the cull")
local d = renderer.drawStats(); print(d.materialBinds, "material binds over", d.materialBinds + d.materialBindsElided, "units")
local d = renderer.drawStats(); print(d.pipelineBinds, "pipeline sets over", d.pipelineBinds + d.pipelineBindsElided, "units")
```

## globals/renderer/feature/create {#globals-renderer-feature-create}

```lua
renderer.feature.create(ref: any?, guid: string?) -> any
```

Instantiate a render feature so the engine calls its `render(ctx)` hook
every frame. `ref` is an `AssetRef<renderFeature>` whose `init.luau` returns
`{ setup?, render, teardown? }`. Returns a live `RenderFeatureHandle` (its
`guid` is the stable id, same as mesh/texture handles); tear it down with
`renderer:destroy(handle)`. Pass `guid` to assign a specific id.

**Parameters**

- `ref` `any` _(optional)_ — An `AssetRef<renderFeature>`, or a string identity/guid resolved via
`asset.resolve(ref, "renderFeature")`.
- `guid` `string` _(optional)_ — Optional explicit handle guid (minted when omitted).

**Returns** `any` — A `RenderFeatureHandle`.

```lua
local h = renderer.feature.create(asset.resolve("acrylic", "renderFeature"))
local h = renderer.feature.create("acrylic")
```

## globals/renderer/feature/destroy {#globals-renderer-feature-destroy}

```lua
renderer.feature.destroy(handleOrGuid: any?) -> boolean
```

Tear down a live render feature by its `RenderFeatureHandle` OR its guid
string — the by-id path for when the handle was lost (e.g. across `execute`
calls). Same effect as `renderer.destroy(handle)`. Returns true if a feature
was live under that id.

**Parameters**

- `handleOrGuid` `any` _(optional)_ — A `RenderFeatureHandle` or its `guid` string.

**Returns** `boolean`

```lua
renderer.feature.destroy("eb623975-d8bd-47a1-a0a4-5c0e56238e2f")
```

## globals/renderer/feature/list {#globals-renderer-feature-list}

```lua
renderer.feature.list() -> { { guid: string, identity: string } }
```

List every render feature currently live (running its `render(ctx)` each
frame). Each entry is `{ guid, identity }` — the `guid` is the same id a
`RenderFeatureHandle` carries, so you can tear a feature down by guid even
after losing its handle (e.g. across separate `execute` calls).

**Returns** `{ { guid: string, identity: string } }`

```lua
for _, f in renderer.feature.list() do print(f.identity, f.guid) end
```

## globals/renderer/feature/shaded {#globals-renderer-feature-shaded}

```lua
renderer.feature.shaded() -> { [string]: number }
```

How many pixels each fragment pass a render feature enqueued shaded on
the last drawn frame, keyed by the pass's shader/effect name. A fragment
pass draws one triangle over its target, so it shades the whole screen
whatever its effect actually reaches — unless it declares `bounds` on the
pass spec, the world-space box its effect stays inside, in which case it
shades the rectangle that box projects into for the camera drawing it and
is skipped for a camera that cannot see the box at all. This is the reading
that says which of the two a pass is: it moves when the effect moves, and a
pass absent from it shaded nothing. Summed over every camera the frame drew.

**Returns** `{ [string]: number }` — `{ [shader: string]: number }` — pixels shaded, last drawn frame.

```lua
local px = renderer.feature.shaded(); for name, n in pairs(px) do print(name, n) end
```

## globals/renderer/featureTexture/configure {#globals-renderer-featuretexture-configure}

```lua
renderer.featureTexture.configure(width: number, height: number, layers: number)
```

Size the shared feature-texture array — the layers a surface shader reads
through `zero_feature_texture(uv, layer)`, and the layers a `SpotLight`
projects through its cone via `cookieLayer`. Layers are `rgba16f`.
A call for the size the array already has is left alone. One that changes
the size reallocates, and the replacement is zeroed — so it empties every
layer in the array, including the layers other features and other cookies
own. `renderer.featureTexture.state()` reports the extent and the layers
holding content, which is how a feature re-fills the layer a resize took
from it.

**Parameters**

- `width` `number` — Layer width in pixels.
- `height` `number` — Layer height in pixels.
- `layers` `number` — How many layers the array holds.

```lua
renderer.featureTexture.configure(512, 512, 4)
```

## globals/renderer/featureTexture/setLayer {#globals-renderer-featuretexture-setlayer}

```lua
renderer.featureTexture.setLayer(layer: number, textureKey: string, x: number, y: number)
```

Copy a texture already on the GPU into one layer of the shared array,
its top-left corner at `(x, y)` — GPU to GPU, with no readback. Several
small images pack into one layer by calling this once per image at
different offsets. The source must be `rgba16f` and fit at that offset.

**Parameters**

- `layer` `number` — Which layer of the array to write into.
- `textureKey` `string` — The source texture's name — the one it was created under.
A `compute.createStorageTexture2D` target, a `compute.createTextureHistory`
pair (its current side), and a texture a `compute.copyBufferToTexture`
wrote all answer to the name they were given.
- `x` `number` — Left edge of the destination rectangle, in pixels.
- `y` `number` — Top edge of the destination rectangle, in pixels.

```lua
compute.createStorageTexture2D("gobo", { width = 256, height = 256, format = "rgba16f" })
renderer.featureTexture.setLayer(0, "gobo", 0, 0)
```

## globals/renderer/featureTexture/state {#globals-renderer-featuretexture-state}

```lua
renderer.featureTexture.state() -> {
```

What the shared feature-texture array is right now: the extent every
layer carries, and `filled`, the ascending 0-based indices of the layers a
`setLayer` has landed in since the array was last sized. One array is
shared by every feature and every light cookie in the scene, and it has no
allocator, so this is the call that tells a feature whether the array it
sized and filled is still the array it is writing into — a `configure` that
changed the size reallocates and zeroes every layer, and the layer it
emptied leaves `filled` without it. Measured off the renderer at the end of
the last rendered frame, so a `configure` or `setLayer` issued this frame
reads back on a later one.

What this describes is the array a shader samples. The source texture a
`setLayer` copied FROM is a GPU resource of its own and keeps the bytes it
was written with for as long as it lives, so `filled` is the reading that
answers whether the layer behind a `cookieLayer` is live right now.

**Returns** `{ width, height, layers, filled }`

```lua
local ft = renderer.featureTexture.state()
print(("feature textures: %dx%d over %d layers"):format(ft.width, ft.height, ft.layers))
-- Re-fill the cookie layer this module owns if anything emptied it.
if ft.width ~= myWidth or table.find(ft.filled, myLayer) == nil then
refillMyCookie()
end
```

## globals/renderer/framePacing {#globals-renderer-framepacing}

```lua
renderer.framePacing() -> FramePacing?
```

How far the CPU is allowed to run ahead of the GPU, and what holding it
there cost the frame just finished. Submitting work to the GPU returns
before the GPU has done it, and everything that submission holds — its
staging allocations, its bind groups, its command buffer — stays alive
until it completes. A frame that asks for more work than the GPU finishes
in a frame's time therefore leaves that behind it, and unbounded that is
memory growth rather than a lower frame rate.

`framesInFlight` is how many submitted frames have not reported done
through the queue's completion signal, held under `maxFramesInFlight`: a
device that keeps up reads under the bound, one that is behind reads at it.
It counts submissions, which is its own quantity — how many presented
images the swapchain permits in flight is a separate setting.
`mechanism` names how that bound is enforced
here: `submission-wait` waits for the frame that many frames back and
reports the wait in `waitMs`, so a paced frame costs latency and still
draws; `submitted-work-done` counts outstanding frames off the queue's
completion signal and declines to start a frame while the bound is met,
counting those in `pacedFrames` and leaving the last presented image up.
`submittedFrames` counts the frames that were admitted and submitted, so it
rises for as long as the renderer is producing frames — which is what tells
a renderer running slowly under a tight bound from one that has stopped.
`stalled` reads true while that completion signal has stopped arriving and
the pacer stood down rather than hold the image indefinitely; it clears on
the first frame that finds the count back under the bound.

`producing` is whether the renderer is drawing frames at all. A headless
renderer draws into an offscreen framebuffer that nothing presents, so its
image reaches a reader only through something that copies it out: it draws
while a consumer is asking — an MCP call in flight, a queued texture
readback, a recording, a frame-egress session — and declines the frames
between two asks, counting them in `idleSkippedFrames`. Every other
renderer stat answers with the last frame that drew, so `producing` is what
separates a live reading from a frozen one. A windowed renderer presents
every frame it draws and reads `producing = true` throughout.

`presentMode` is what the surface presents with and `presentModes` what it
offers; both are empty of meaning on a headless renderer, which never
presents.

**Returns** `FramePacing?` — `{ framesInFlight, maxFramesInFlight, pacedFrames, submittedFrames, waitMs, mechanism, stalled, producing, idleSkippedFrames, presentMode, presentModes }`, or nil before the renderer has drawn a frame

```lua
local p = renderer.framePacing()
print(("%d of %d frames in flight, paced by %s"):format(p.framesInFlight, p.maxFramesInFlight, p.mechanism))
```

## globals/renderer/getRaytrace {#globals-renderer-getraytrace}

```lua
renderer.getRaytrace() -> boolean
```

Whether ray tracing is currently enabled.

**Returns** `boolean`

## globals/renderer/gpuMemory {#globals-renderer-gpumemory}

```lua
renderer.gpuMemory() -> GpuMemory
```

Where the renderer's GPU memory went at the last completed frame — the
call to reach for when something is holding memory and you do not know
what.

Three figures answer three different questions, and they are meant to be
read against each other:

* The categories — `shadow`, `textures`, `meshes`, `instances`, `compute`,
summing to `categorised` — are the renderer's own accounting of what it
asked for on purpose. Always present, on every backend.
* `allocator` is the device allocator's ledger, with a row per creation
label largest first, which is what names an allocation no category
claims. It exceeds `categorised` by the per-frame render targets and the
scratch nothing categorises. The allocator hands memory out from blocks
it reserves whole from the device and returns a block only once nothing
is left in it, so `reservedBytes` runs above `allocatedBytes` by what
those blocks hold unused; `blocks` lists them emptiest first with the
labels that keep each one alive, and `emptyBytes` plus `slackBytes` is
that distance exactly — the pool held in empty blocks, and the room
pinned inside blocks something still sits in.
* `driver.deviceLocalBytes` is what the graphics driver charges this
process, out of the kernel's own accounting. It is the biggest of the
three and the one that fills a card, because it also holds the
swapchain, the images the driver keeps on the renderer's behalf, and the
rounding to whole pages and heap blocks that neither figure above sees.
Read it when the question is how much of the machine's GPU this engine
is using; read the two above when the question is what the engine spent
it on. A platform with no per-process accounting reports
`available = false` and the reason.
* `driver.outsideAllocatorBytes` is that charge less everything the
allocator reserved — what the driver holds on its own account, and the
one figure here nothing releases: a dropped pipeline, another scene and
`renderer.collect()` all leave it where it is, and it falls when the
device is destroyed. Read it when a session's device memory has grown
and no ledger row accounts for the growth.

`compute` is what the compute subsystem holds; `compute.observe()` names
each of those resources and what it costs. `renderTargets` counts the
offscreen render targets the renderer holds at that frame, which is what
says a `renderer.destroy` has been applied rather than queued.

**Returns** `GpuMemory` — The accounting — see `GpuMemory`. The category figures are zeroed until the renderer has published its first frame; `driver` is read as the call runs and answers from the first.

```lua
local m = renderer.gpuMemory()
print(("shadows %.1f MiB"):format(m.shadow.total / 1024 / 1024))
-- how much of the card this engine is holding:
if m.driver.available then
print(("driver %.1f MiB"):format(m.driver.deviceLocalBytes / 1024 / 1024))
else
print("no driver figure: " .. m.driver.reason)
end
-- the biggest allocation in the engine:
local top = m.allocator and m.allocator.byLabel[1]
print(top and top.label, top and top.bytes)
-- the block held for the least, and what pins it:
local block = m.allocator and m.allocator.blocks[1]
if block and block.allocationCount > 0 then
print(block.size, block.allocatedBytes, block.byLabel[1].label)
end
```

## globals/renderer/hold {#globals-renderer-hold}

```lua
renderer.hold(handleOrKind: any?, id: string?) -> boolean
```

Pin a runtime resource for the session. A held texture, material, mesh
or render feature survives every collection — the one a root scene load
runs and a direct `renderer.collect()` alike — until `renderer.release`
lets it go or its destroy frees it. It is the way to keep an ad-hoc
resource across the scenes that come and go under it. A hold keeps the
resource in the registry; a mesh's GPU buffers are governed by what draws
it, parked as a CPU definition when the last instance naming it goes and
brought back when one names it again, so `renderer.mesh.isResident(guid)`
is the separate question about the buffers.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind (`"texture"`, `"material"`,
`"mesh"`, `"feature"`) with the guid or key as the second argument.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `boolean` true when the registry knows the resource.

```lua
renderer.hold(tex)
renderer.hold("material", "swatch")
```

## globals/renderer/instanceData/clear {#globals-renderer-instancedata-clear}

```lua
renderer.instanceData.clear(target: string | entityRef)
```

Drop every lane of an entity's per-instance shader data, so its draws
read zero again — how a feature releases a subject it is still holding.
Despawning an entity releases its block too, so this is for a subject that
stays. It takes an entity that has already gone, which is when a feature
releasing its subjects often runs, and does nothing for an entity holding
no block.

**Parameters**

- `target` `string | entityRef` — The entity — a proxy from `entity(...)` / `entity.spawn(...)`, or
an entity-id string.

```lua
renderer.instanceData.clear(subject)
```

## globals/renderer/instanceData/laneCount {#globals-renderer-instancedata-lanecount}

```lua
renderer.instanceData.laneCount() -> number
```

How many `vec4` lanes each entity's per-instance block holds, so a lane
index runs `0 .. laneCount() - 1`. The same count a surface shader indexes
`input.shader_data` against.

**Returns** `number` — lanes per entity.

```lua
for lane = 0, renderer.instanceData.laneCount() - 1 do
renderer.instanceData.set(subject, lane, 0)
end
```

## globals/renderer/instanceData/set {#globals-renderer-instancedata-set}

```lua
renderer.instanceData.set(target: string | entityRef, lane: number, x: number, y: number?, z: number?, w: number?)
```

Write one `vec4` lane of an entity's per-instance shader data — the
channel that lets ONE material serve many entities that differ in a value.
A surface shader reads the lane back as `input.shader_data[lane]`, so a
dissolve at its own progress per subject, an effect at its own age per
firing, or a per-entity mask costs one material rather than one material
per entity.

The engine attaches no meaning to a lane: a feature picks the lane indices
it owns and packs whatever its shader agrees they carry. Name those indices
in the module that writes them, so the writer and the shader read the block
the same way.

The write reaches the block where it is called, so the entity it names is
the one holding that id at that point in the tick, and the value is on the
draw from the next frame. It is held until the lane is written again, the
entity's block is cleared, or the entity is despawned — a despawned entity
releases its whole block. A lane an entity was never given reads zero.

**Parameters**

- `target` `string | entityRef` — The entity — a proxy from `entity(...)` / `entity.spawn(...)`, or
an entity-id string. It must name a live entity.
- `lane` `number` — Which `vec4` lane to write, `0 .. laneCount() - 1`.
- `x` `number` — The lane's `.x`.
- `y` `number` _(optional)_ — The lane's `.y`. Defaults to 0.
- `z` `number` _(optional)_ — The lane's `.z`. Defaults to 0.
- `w` `number` _(optional)_ — The lane's `.w`. Defaults to 0.

```lua
-- One dissolve material, each subject at its own progress.
local DISSOLVE_LANE = 0
for _, subject in ipairs(dying) do
renderer.instanceData.set(subject.entity, DISSOLVE_LANE, subject.progress)
end
```

## globals/renderer/loseDevice {#globals-renderer-losedevice}

```lua
renderer.loseDevice()
```

Destroy the render device on the next frame, so the engine meets a real
device loss.

This is the one loss that can be caused on purpose, and it travels the same
path a driver reset does: frames draw nothing until the rebuild lands,
`GET /engine/status` reports the renderer as `recovering` while it does,
`engine.onDeviceRebuilt` fires afterwards, and `renderer.deviceGeneration()`
moves. Use it to prove that a world's content survives a device loss —
anything it holds only on the GPU has to be remade from the rebuild hook, and
this is how you find out whether it is.

```lua
renderer.loseDevice()
-- some frames later:
print(renderer.deviceGeneration()) -- one higher than before
```

## globals/renderer/mainCameraView {#globals-renderer-maincameraview}

```lua
renderer.mainCameraView() -> { number }?
```

The main camera's inverse view-projection (column-major, 16 numbers)
followed by its world position (3 numbers) — `{m0..m15, px,py,pz}` — for
reconstructing world positions from the depth buffer in a ray-tracing pass.
Nil before the first render.

**Returns** `{ number }?` 19 numbers, or nil.

## globals/renderer/material/animatedTexture {#globals-renderer-material-animatedtexture}

```lua
renderer.material.animatedTexture(texture: string | AssetRef, opts: { [string]: any }?) -> MaterialHandle
```

Build a material that PLAYS a layered texture: its layers bound as the
frames, its timing bound beside them, and the engine's `animatedTexture`
shader turning the clock into the layer showing now. One call from an
imported animated image to a material an entity can wear.

The layer showing is resolved per pixel against the texture's own schedule,
so frames of unequal length are shown for the lengths they were authored
with, and the sequence loops. `speed` scales the clock (2 plays twice as
fast, 0 holds the frame `startTime` lands in) and `startTime` offsets into
the sequence, so two surfaces sharing one texture can run out of phase.

The clock is the engine's, and it runs in edit mode as much as in play and
through a pause, so two screenshots of one surface taken moments apart are
two different frames of it. `speed = 0` holds one frame for as long as it
is set, which is the state to compare two screenshots in.

The returned handle is what a surface wears — `Model:applySessionMaterial`
takes it, and so does a Model's `material` field. The handle's `guid` is
this material's REGISTRY KEY, the currency of `setProperty`, `describe` and
`destroy`; a component field resolves an asset, so a bare key in one leaves
the component waiting for an asset to register under that name.

The builtin `plane` mesh emits `uv = (u, v)` with `v` along its own +Z, so
a quad pitched +90° about X (`Transform.eulerToQuat(0, math.pi / 2)`) shows
the image upright to a camera on +Z, and -90° shows it first-row-last.

A texture whose layers carry no timing is rejected — there is nothing to
play. `renderer.texture.info(bytes).animated` is the test.

**Parameters**

- `texture` `string | AssetRef` — The texture — a guid, an identity, a name, a path, or a texture `AssetRef`.
- `opts` `{ [string]: any }` _(optional)_ — `{ key?, speed?, startTime?, alphaCutoff?, baseColor?, uvScale?, uvOffset? }`.

**Returns** `MaterialHandle`

```lua
local mat = renderer.material.animatedTexture("banner.texture")
local id = entity.spawn("billboard", { rotation = { Transform.eulerToQuat(0, math.pi / 2) } })
entity(id).component.add("Model", { model = "plane" })
entity(id).component.get("Model"):applySessionMaterial(mat)
renderer.material.setProperty(mat.guid, "speed", 2)
```

## globals/renderer/material/create {#globals-renderer-material-create}

```lua
renderer.material.create(content: MaterialContent, key: string) -> MaterialHandle
```

**Parameters**

- `content` `MaterialContent`
- `key` `string`

**Returns** `MaterialHandle`

## globals/renderer/material/describe {#globals-renderer-material-describe}

```lua
renderer.material.describe(key: string | { [string]: any } | AssetRef) -> any
```

The recoverable definition (`{ shader, properties, textures, name }`)
this module registered under `key` via `renderer.material.create`, or nil
for keys registered elsewhere (e.g. material assets resolved by the
assetType). `properties` and `textures` carry the material's current values:
each `setProperty` / `setTexture` write lands on this record, a texture slot
under the GPU key the slot binds by — these are the WRITES, held here
whether or not the renderer took them up. `renderer` beside them is what the
renderer holds for the same key: the program its prepared bind group was
built against, the render state its draws are looked up under, whether a
pipeline exists for that key, and how many draws the observed frame gave
it. `renderer` is nil when the renderer holds no material under this key at
all, and `resident` states the same fact as a boolean. Writes reach the
screen through both halves: `resident = false` says the renderer holds
nothing to put them in, and `renderer.draws = 0` on a resident material
says it holds them and no renderable is drawing with it. For a material
that is resident AND drawn and still looks wrong,
`renderer.drawDiagnostics()` names the renderable and the cause.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.

**Returns** `any` — `MaterialContent?` with `resident: boolean` and `renderer: MaterialObservation?` fields

## globals/renderer/material/destroy {#globals-renderer-material-destroy}

```lua
renderer.material.destroy(key: string | { [string]: any } | AssetRef) -> boolean
```

Drop a runtime material registered via `renderer.material.create`: clears
its recoverable definition, unregisters its runtime-resource stamp so it is no
longer swept into the material freeze/save flow, and frees the GPU record. Use
for transient materials (e.g. a preview swatch) that must not outlive their use.
The on-disk asset, if any, is untouched.

The reach is the registry: after this, `describe` and `list` stop answering
for the key. A surface already wearing the handle goes on drawing what it
was given — `Model:restoreSessionMaterial` is what puts a Model back on its
authored material.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key (the one passed to `create`), the
`MaterialHandle` `create` returned, or an `AssetRef` from `asset.resolve`.

**Returns** `boolean` true when a definition was known under `key`.

```lua
renderer.material.destroy("__preview_swatch_" .. texGuid)
```

## globals/renderer/material/list {#globals-renderer-material-list}

```lua
renderer.material.list() -> { any }
```

Every runtime material currently registered, ordered by registry key.
Each entry carries the key, where it came from, and the shader it binds.
`renderer.references("material", key)` says what is still holding a row,
and `renderer.collect()` releases the rows nothing holds.

**Returns** `{ any }` — Array of `{ guid, origin, owner?, shader? }`.

```lua
for _, m in ipairs(renderer.material.list()) do print(m.guid, m.shader) end
```

## globals/renderer/material/renderState {#globals-renderer-material-renderstate}

```lua
renderer.material.renderState(key: string | { [string]: any } | AssetRef) -> MaterialObservation?
```

What the renderer holds for a material, which is a different document
from the values written to it. `shader` is the program its prepared bind
group was built against, `renderState` the blend / cull / topology / queue /
depth key its draws are looked up under, `keyBuilt` whether a pipeline
exists for that key, and `draws` / `instances` / `placeholderDraws` /
`binds` / `bindsElided` what it cost in the frame the renderer last
observed — those five read 0 until something arms per-draw recording, which
`renderer.materialCost()` and
`renderer.drawDiagnostics()` do. `nil` means the renderer holds no material
under this key at all — the writes landed on a record nothing is drawing
with.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.

**Returns** `MaterialObservation?`

```lua
local r = renderer.material.renderState("water"); print(r.renderState.blend, r.keyBuilt)
```

## globals/renderer/material/sessionKeyFor {#globals-renderer-material-sessionkeyfor}

```lua
renderer.material.sessionKeyFor(entityId: string) -> string
```

The canonical registry key for an entity's SESSION material — the
runtime material a system (e.g. GI baking) shows on an entity in place
of its authored material for the lifetime of the engine session. One
session material per entity: create it under this key, hand the handle
to `Model:applySessionMaterial`, and the component re-adopts it across
VM reloads by probing this key with `describe`. The key names the entity
for as long as the entity stands: once it is gone the session store lets
the handle go, and a collection releases the material and whatever its
bindings were the last to hold.

**Parameters**

- `entityId` `string` — The entity carrying the material.

**Returns** `string` — The registry key string.

```lua
local key = renderer.material.sessionKeyFor(entityId)
```

## globals/renderer/material/setProperty {#globals-renderer-material-setproperty}

```lua
renderer.material.setProperty(key: string | { [string]: any } | AssetRef, name: string, value: any?) -> ()
```

Push one changed uniform property to a registered material's GPU record
(frame-fast incremental update; no re-register). Keyed by the material's
registry key. The value written becomes the material's current one: it is
what `describe` reports, and — for a property the material's shader
declares, which is what the uniform buffer is packed by — what a material
`AssetRef` reads back through `getProperty` / `getProperties` and what the
surface is drawn with. A write under any other name reaches the record
`describe` reports, which is where it reads back.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.
- `name` `string` — Property name.
- `value` `any` _(optional)_ — New value.

**Returns** `()`

```lua
renderer.material.setProperty(asset.resolve("wall", "material"), "roughness", 0.2)
```

## globals/renderer/material/setTexture {#globals-renderer-material-settexture}

```lua
renderer.material.setTexture(key: string | { [string]: any } | AssetRef, slot: string, ref: string | { [string]: any } | AssetRef) -> ()
```

Push one changed texture slot to a registered material's GPU record.
Keyed by the material's registry key.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.
- `slot` `string` — Texture slot name (`"base_color_texture"`, …).
- `ref` `string | { [string]: any } | AssetRef` — Texture reference — a `.texture` guid / identity / name / path, the
image path it was imported from, a `color:` / `default:` form, a live GPU
handle, or a texture `AssetRef` carrying one. An asset reference is
materialised (Disk→CPU→GPU) and bound by the key the upload lands under.

**Returns** `()`

```lua
renderer.material.setTexture("sky", "sky_texture", "panorama.texture")
```

## globals/renderer/materialCost {#globals-renderer-materialcost}

```lua
renderer.materialCost() -> { MaterialObservation }
```

What each material cost the frame the renderer last drew, and the state
it holds each one under. One row per material the renderer holds a prepared
bind group for — a material an author wrote and the renderer never prepared
is absent, which is itself the answer to "why is nothing I set reaching the
screen". `draws` and `instances` cover that one frame; `placeholderDraws`
is how many of those draws bound the magenta placeholder instead of this
material's own program; `binds` is how many material-owned bind groups the
frame's passes SET for it and `bindsElided` how many of its draws wanted a
group the pass already held, which is what draw-key sorting buys; a draw
that fell back to the placeholder bound the placeholder's group, so it
counts in `placeholderDraws` and in neither bind count. `uniformBytes` is
the GPU uniform buffer's own size,
which is the reflected property block raised to the 16-byte floor and
rounded up to the copy alignment. `renderer.drawDiagnostics()` names WHICH
renderable is not drawing what its material says, and why.

**Returns** `{ MaterialObservation }`

```lua
for _, m in renderer.materialCost() do print(m.material, m.draws, m.binds, m.bindsElided) end
```

## globals/renderer/materialIdentity {#globals-renderer-materialidentity}

```lua
renderer.materialIdentity() -> MaterialIdentity
```

Which material each renderable draws with, as a number a shader can
carry. A material is authored and bound by name, and no shader can read a
string — so every renderable's per-instance record holds a material index
instead. `slots` is the name → index table those indices are drawn from: an
index is assigned the first time the renderer draws with that material and
does not move afterwards, so two renderables that differ only in material
read different indices, and one renderable reads the same index frame after
frame. It follows that the table keeps a row for every material name drawn
this session, whether or not anything still draws with it. `renderables` is
a row per renderable that owns a GPU slot — the entity it belongs to, that
slot, and the index the record at it carries; `populations` is the same for
an instanced draw, whose whole reserved run of slots carries the one
material its registration named. That index is what a shader reads as
`instance_data[slot].material_index`, and the row a ray hit resolves
through `zeroMaterial()`. A renderable draws with the material its entity
references, so one whose entity names none carries index 0.

**Returns** `MaterialIdentity` — `{ slots: { [string]: number }, renderables: { { entity: string, slot: number, index: number, material: string } }, populations: { { slot: number, count: number, index: number, material: string } } }`

```lua
local id = renderer.materialIdentity()
for _, r in id.renderables do print(r.entity, r.slot, r.index, r.material) end
```

## globals/renderer/materialIndex {#globals-renderer-materialindex}

```lua
renderer.materialIndex(name: string) -> number?
```

The index standing for a material, or `nil` for one the renderer has not
drawn with yet. Pass it to a shader (or compare it against what a shader
read out of `instance_data[slot].material_index`) to tell which material a
drawing instance carries.

**Parameters**

- `name` `string` — `string` Material name, as `renderer.material.create` filed it.

**Returns** `number?`

```lua
local red = renderer.materialIndex("brick_red")
```

## globals/renderer/maxAnisotropy {#globals-renderer-maxanisotropy}

```lua
renderer.maxAnisotropy() -> number
```

The highest anisotropy this device honours: 16 on hardware that filters
anisotropically, 1 on hardware that does not, where a higher request would
be downgraded to trilinear regardless. Read it to report quality honestly —
`renderer.setAnisotropy` clamps for you, so a request never needs guarding.

**Returns** `number` — The device ceiling, 1 or 16.

```lua
local best = renderer.maxAnisotropy()
```

## globals/renderer/mesh/boundsSource {#globals-renderer-mesh-boundssource}

```lua
renderer.mesh.boundsSource(mesh: string | { [string]: any } | AssetRef) -> string
```

Where this mesh's culling bounds come from. `"compute"` once a compute
pass has written its vertices: the engine reduces those vertices to an AABB
every frame, so the mesh is culled against the geometry the pass produced
wherever it puts it. `"geometry"` otherwise: the AABB of the geometry the
mesh was created with.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `string` — `"compute"` or `"geometry"`.

```lua
print(renderer.mesh.boundsSource(mesh))
```

## globals/renderer/mesh/buildClusters {#globals-renderer-mesh-buildclusters}

```lua
renderer.mesh.buildClusters(mesh: string | { [string]: any } | AssetRef) -> string?
```

Build a cluster-LOD DAG (Nanite-style virtualized geometry) for the
static CPU mesh held under `guid` and return its serialized `data.clusters`
bytes. Returns nil when the mesh is degenerate, and on an engine whose
`renderer.mesh.canBuildClusters` reports false. Pair with
`renderer.mesh.uploadClusters`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — A mesh loaded into the CPU store (`renderer.mesh.loadCpu`) — the
`MeshCpuHandle`, a `MeshHandle`, a guid, or a mesh `AssetRef`.

**Returns** `string?` — Serialized cluster bytes, or nil.

```lua
local cb = renderer.mesh.buildClusters(cpu)
```

## globals/renderer/mesh/canBuildClusters {#globals-renderer-mesh-canbuildclusters}

```lua
renderer.mesh.canBuildClusters() -> boolean
```

Whether this engine bakes cluster-LOD hierarchies. It reads the binding
the running engine registered: every target the engine ships on carries the
builder, so a mesh loaded in a browser bakes its own clusters the same way
one loaded natively does, and an engine built without it reports false and
answers nil from `renderer.mesh.buildClusters`.

**Returns** `boolean` — True if `renderer.mesh.buildClusters` can bake on this platform.

```lua
if renderer.mesh.canBuildClusters() then ... end
```

## globals/renderer/mesh/clusterBakeBudget {#globals-renderer-mesh-clusterbakebudget}

```lua
renderer.mesh.clusterBakeBudget(ms: number?) -> number
```

The wall time one frame may spend advancing scheduled cluster bakes, in
milliseconds — set first when `ms` is given. A slice always runs at least
one unit of the build, so the budget bounds what a frame spends by choice
and the largest single unit a mesh imposes sets the floor under it.

**Parameters**

- `ms` `number` _(optional)_ — New per-frame budget in milliseconds, capped at 1000. A value that is
not a positive, finite number raises.

**Returns** `number` — The budget in force after the call.

```lua
renderer.mesh.clusterBakeBudget(2)
```

## globals/renderer/mesh/clusterBakes {#globals-renderer-mesh-clusterbakes}

```lua
renderer.mesh.clusterBakes() -> { [string]: any }
```

What the scheduled cluster bakes are costing. `budgetMs` is the slice a
frame may spend, `pending` how many bakes are queued, `completed` how many
have finished since the engine started, `dropped` how many left the queue
because the geometry they were scheduled over stopped being readable, and
`heldBytes` the source geometry the queue is holding across all of them —
the vertex pool and index run the bake at the head is reading, plus a copy
for each queued mesh the engine holds no definition for.
`inFlight` is one row per queued bake —
`{ guid, cpuMs, frames, slices, bytes, state }`: the wall time spent
advancing it, the frames it has been queued for, the slices it has been
advanced by, the geometry it is holding, and `"baking"` for the one being
advanced against `"queued"` for the ones waiting their turn.

**Returns** `{ [string]: any }` — `{ budgetMs, pending, completed, dropped, heldBytes, inFlight }`.

```lua
print(renderer.mesh.clusterBakes().heldBytes)
```

## globals/renderer/mesh/clusterComponents {#globals-renderer-mesh-clustercomponents}

```lua
renderer.mesh.clusterComponents(clusterBytes: buffer | string) -> (ClusterComponents?, string?)
```

Split a cluster blob (from `renderer.mesh.buildClusters`) into its
GPU-ready component byte pools — the cluster vertex pool, the
geometry-addressing pool (every cluster's local→global vertex map, then
every cluster's triangle bytes), and the per-cluster record array — plus
their counts. A cluster's triangles address positions inside its own vertex
map one byte at a time, and a record's `vertexOffset` indexes the geometry
pool in `u32` elements while its `indexOffset` indexes it in bytes, so ONE
binding resolves a corner. A pure decode (no GPU work): upload the pools
into buffers a compute shader owns (`shaderRef:createBuffer` +
`buf:writeBytes`) to drive a cluster draw from Luau.

**Parameters**

- `clusterBytes` `buffer | string` — Serialized cluster bytes (binary-safe).

**Returns** `(ClusterComponents?, string?)` — `{ vertices, geometry, records, vertexCount, vertexRefCount, triangleBytes, indexCount, clusterCount }`, or (nil, err).

```lua
local c = renderer.mesh.clusterComponents(cb)
```

## globals/renderer/mesh/clusters {#globals-renderer-mesh-clusters}

```lua
renderer.mesh.clusters(mesh: string | { [string]: any } | AssetRef) -> { [string]: any }?
```

The shape of the cluster-LOD hierarchy the renderer holds for a mesh:
`clusterCount` across every level, `levelCount` with the finest counted as
one, and `triangleCount` across every cluster. The renderer keys one entry
per mesh that carries a hierarchy, so this answers whether the mesh has
clusters as well as what they are — nil for a mesh that carries none.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to read — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `{ [string]: any }?` — `{ clusterCount: number, levelCount: number, triangleCount: number }?`

```lua
local c = renderer.mesh.clusters(gpu) ; if c then print(c.levelCount) end
```

## globals/renderer/mesh/create {#globals-renderer-mesh-create}

```lua
renderer.mesh.create(src: any?, guid: string?) -> MeshHandle
```

Create (or fetch) a GPU mesh resource and return its `MeshHandle`. `src`:
a `MeshCpuHandle` from `meshRef:load()` (CPU→GPU upload under the asset's
guid, idempotent — returns the resident handle if already uploaded); raw
geometry `{positions, indices, normals?, uvs?, colors?, uvs1?, unwrapUvs?,
tangents?, skinning?, skins?}` (a new runtime mesh — `uvs1` is the lightmap
UV set, `unwrapUvs` generates one, `skinning`/`skins` bind a skeleton); GPU
compute buffers `{vertexBuffer, indexBuffer, vertexCount, indexCount,
aabbMin?, aabbMax?, prevVertexBuffer?}` (size the vertex buffer at
`vertexCount * engine.vertexStride` bytes, the engine's standard Vertex
layout); or a `MeshHandle` (returned as-is). NEVER takes an AssetRef —
load the CPU first.

`prevVertexBuffer` is a second buffer of the same size and layout holding
those vertices as they stood on the previous frame. Naming it is what makes
geometry a compute pass moves report a motion vector: the surface
differences the two streams, so every consumer of screen-space velocity —
motion blur, temporal reprojection — sees the movement. The engine fills it
from the current vertices once per frame, ahead of that frame's compute
dispatches, so a frame in which the pass does not run leaves the two
streams equal and the geometry reports standing still.

`morphTargets` are the shapes the mesh can blend towards: a list of
`{ name?, positions, normals? }` records, each holding one offset per vertex
from the base geometry, in the mesh's own vertex order. An entity blends
them with `ecs.MorphWeights`, weight `i` scaling target `i`. A `name` makes
the shape addressable as itself — `renderer.mesh.morphTargets` reads the
names back and `renderer.mesh.morphWeights` drives them by name.

Raw geometry is read against the mesh type's conventions: `indices` count
vertices from 0, and a triangle's FRONT face is the one whose vertices turn
counter-clockwise as the viewer sees them — `cross(v1 - v0, v2 - v0)` points
out of it. A material culls its back faces by default, so a triangle wound
the other way draws nothing where it stands; reverse the index triple, or
give the material `render = { cull = "none" }`, to draw that side. `normals`
give the surface its outward direction and shade the face; the side that
draws comes from the index order alone. `uvs` sample `(0,0)` at the image's
top-left. Model space carries the world's basis: +X right, +Y up, -Z the
direction `transform.forward` points. `guides { path = "types/mesh" }` has
the whole table.
A geometry src carrying `keepCpu = true` also keeps its geometry in the
guid-keyed CPU store, so `renderer.mesh.getVertices` reads it and
`renderer.mesh.setVertices` rewrites its positions in place — the per-frame
deformation path, which sends positions alone where `renderer.mesh.update`
re-sends the whole geometry. `renderer.mesh.unloadCpu(mesh)` releases that
copy. Without it the geometry lives on the GPU alone and
`renderer.mesh.readback(mesh)` is what brings it back.

**Parameters**

- `src` `any` _(optional)_ — A MeshCpuHandle, geometry, compute buffers, or a MeshHandle.
- `guid` `string` _(optional)_ — Optional v4 guid for a NEW runtime mesh (minted Luau-side when
absent). Ignored for the CPU-handle path (the asset's guid is used).

**Returns** `MeshHandle`

```lua
local gpu = renderer.mesh.create(meshRef:load())
local gpu = renderer.mesh.create({ positions = {...}, indices = {...} })
-- a runtime mesh whose positions are rewritten in place each frame
local m = renderer.mesh.create({ positions = P, indices = I, keepCpu = true })
renderer.mesh.setVertices(m, P)
-- a runtime mesh carrying a lightmap UV set (unwrapped at creation)
local gpu = renderer.mesh.create({ positions = {...}, indices = {...}, unwrapUvs = true })
-- a mesh with one shape to blend towards, driven by ecs.MorphWeights
local gpu = renderer.mesh.create({ positions = P, indices = I, morphTargets = { { positions = D } } })
```

## globals/renderer/mesh/decode {#globals-renderer-mesh-decode}

```lua
renderer.mesh.decode(zmsh: buffer | string) -> (MeshGeometry?, string?)
```

Decode engine-native `ZMSH` bytes back into a `MeshGeometry`. Inverse of
`renderer.mesh.encode`; each optional stream is present only when the blob
carries it. Takes the bytes themselves — the geometry of a mesh the engine
is holding comes from `renderer.mesh.geometry(mesh)`.

**Parameters**

- `zmsh` `buffer | string` — Engine-native ZMSH bytes (binary-safe).

**Returns** `(MeshGeometry?, string?)` the geometry, or (nil, errmsg).

```lua
local geom = renderer.mesh.decode(meshRef:getBytes())
```

## globals/renderer/mesh/destroy {#globals-renderer-mesh-destroy}

```lua
renderer.mesh.destroy(mesh: string | { [string]: any } | AssetRef) -> boolean
```

Release the GPU mesh `mesh` names, the release that pairs with
`renderer.mesh.create`. Takes every form that names a mesh — the
`MeshHandle` `create` returned, the guid `renderer.mesh.list` hands out, a
`MeshCpuHandle` or a mesh `AssetRef` — and routes through
`renderer.destroy`, the verb that releases any renderer resource by its
kind. The CPU copy, if one was loaded, is freed separately by the CPU
handle's `:unload()`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to release — a `MeshHandle`, a guid, a `MeshCpuHandle` or a
mesh `AssetRef`.

**Returns** `boolean` true if a GPU mesh was known under the guid.

```lua
local m = renderer.mesh.create(cpu); renderer.mesh.destroy(m)
renderer.mesh.destroy(renderer.mesh.list()[1].guid)
```

## globals/renderer/mesh/drawInstanced {#globals-renderer-mesh-drawinstanced}

```lua
renderer.mesh.drawInstanced(mesh: string | { [string]: any } | AssetRef, opts: any?) -> InstancedDraw
```

Draw one mesh `instanceCount` times in a single call, each copy placed by
a world matrix read from a GPU buffer. The population is a renderable in its
own right — it goes through the mesh's ordinary pipeline and the material's
ordinary bind groups, so it appears in the deferred pass, the forward passes
and the shadow maps exactly as an entity-backed draw of that mesh does.

The buffer holds `instanceCount` **column-major** 4x4 matrices, 64 bytes
each, tightly packed — the layout a vertex shader reads as
`array<mat4x4<f32>>`, which puts each matrix's translation in its LAST four
floats (Lua indices 13/14/15 for x/y/z). Packing row-major transposes every
instance.

The matrices are COPIED into the engine's transform slots once per frame,
which is what buys that full-pass parity. Rewrite the buffer between frames
and the instances move — no re-registration, no re-upload.

`material` is what the population draws with, and it is required: a
`MaterialHandle` (`matRef:handle()`), an `AssetRef`, or a registry key.

`instanceDataBuffer` names a second buffer, holding 64 bytes per instance —
four `vec4` lanes, tightly packed, in instance order. Those lanes arrive in
the fragment stage as `zero_object_data(in.instance_id, lane)`, the same
read a per-entity `__instancedata` block answers, so the members of one
population can differ in whatever their material's shader agrees the lanes
carry. Copied every frame like the transforms, from a buffer a compute pass
writes: the values never touch the CPU. Omit it and the lanes read zero.

`reserveCount` sizes the reservation above `instanceCount` so
`renderer.mesh.setInstanceCount` can raise the drawn count later without
re-registering; both buffers must back the reservation, not just the count.

`mobility` states whether the copies stand still — `"static"`, or
`"movable"` when it is left out. It is what a scene gather collecting
geometry for precomputed lighting admits a population on, the same
declaration `Model.mobility` makes for an entity: the transforms live in a
buffer anything may rewrite between frames, so a population that says
nothing is taken as one that moves.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh the population draws — a `MeshHandle`, the guid
`renderer.mesh.list` hands out, a `MeshCpuHandle` or a mesh `AssetRef`. A
registration holds the mesh on the device for as long as it lives, and takes
a mesh that is currently held off the device — one nothing displays — back
onto it.
- `opts` `any` _(optional)_ — `{ transformBuffer, instanceCount, material, instanceDataBuffer?, reserveCount?, renderLayer?, castsShadows?, mobility? }`.

**Returns** `InstancedDraw` — An `InstancedDraw` handle for `instanceInfo` / `setInstanceCount` / `dropInstanced`.

```lua
local m = renderer.mesh.create({ positions = ..., indices = ... })
local buf = substrate.createBuffer({
name = "crowd.xf", type = "mat4", len = 64, kind = "gpu",
})
-- Column-major: translation lives at indices 13/14/15.
local xf = {}
for i = 0, 63 do
local m4 = { 1,0,0,0, 0,1,0,0, 0,0,1,0, i * 2, 0, 0, 1 }
for _, v in ipairs(m4) do xf[#xf + 1] = v end
end
buf:write(xf)
local rock = asset.resolve("rock", "material"):handle()
local draw = renderer.mesh.drawInstanced(m, { transformBuffer = "crowd.xf", instanceCount = 64, material = rock })
```

## globals/renderer/mesh/dropClusters {#globals-renderer-mesh-dropclusters}

```lua
renderer.mesh.dropClusters(mesh: string | { [string]: any } | AssetRef) -> boolean
```

Detach a mesh's cluster-LOD hierarchy and cancel a bake still in flight
for it, so the renderer holds none for it. The inverse of
`renderer.mesh.uploadClusters`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to detach — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `boolean` — True if a hierarchy was attached or a bake was in flight.

```lua
renderer.mesh.dropClusters(gpu)
```

## globals/renderer/mesh/dropInstanced {#globals-renderer-mesh-dropinstanced}

```lua
renderer.mesh.dropInstanced(draw: InstancedDraw) -> boolean
```

Release an instanced-draw registration and the transform slots it
reserved. The mesh and the transform buffer outlive it — destroy those
through `renderer.destroy` and the buffer handle's `:destroy()`.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to release.

**Returns** `boolean` — True if a registration was live under the handle.

```lua
renderer.mesh.dropInstanced(draw)
```

## globals/renderer/mesh/encode {#globals-renderer-mesh-encode}

```lua
renderer.mesh.encode(geom: MeshGeometry) -> (string?, string?)
```

Encode raw geometry into engine-native `ZMSH` bytes (the on-disk mesh
payload). The CPU codec behind the mesh assetType's `onCreate`. Every stream
the format carries — including tangents, per-vertex skinning, and the
skeleton — round-trips back through `renderer.mesh.decode`. This pair moves
DATA the caller is holding; the geometry of a mesh the ENGINE is holding
comes from `renderer.mesh.geometry(mesh)`.

**Parameters**

- `geom` `MeshGeometry` — `MeshGeometry` — flat per-vertex float / u32 arrays plus optional `skinning` and `skins`.

**Returns** `(string?, string?)` engine-native ZMSH bytes (binary-safe), or (nil, errmsg) naming what the geometry could not describe.

```lua
local bytes = renderer.mesh.encode({ positions = {...}, indices = {...} })
local bytes = renderer.mesh.encode(renderer.mesh.geometry(meshHandle))
```

## globals/renderer/mesh/encodeCpu {#globals-renderer-mesh-encodecpu}

```lua
renderer.mesh.encodeCpu(mesh: string | { [string]: any } | AssetRef) -> string
```

Encode a mesh's resident CPU copy into `ZMSH` bytes. Reads the ONE
guid-keyed CPU store — `meshRef:load()` populates it for assets, and
`renderer.mesh.readback(mesh)` populates it for a runtime mesh. Errors
loudly when the mesh has no resident CPU copy.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `string` ZMSH bytes.

```lua
local bytes = renderer.mesh.encodeCpu(handle)
```

## globals/renderer/mesh/geometry {#globals-renderer-mesh-geometry}

```lua
renderer.mesh.geometry(mesh: string | { [string]: any } | AssetRef) -> MeshGeometry
```

The complete geometry of a mesh the engine is holding, as a
`MeshGeometry` — the same shape `renderer.mesh.create` and
`renderer.mesh.encode` take, carrying every stream the mesh has
(`positions`, `indices`, and whichever of `normals`, `uvs`, `colors`,
`uvs1`, `tangents`, `skinning`, `skins` it was built with). The read that
pairs with `create`: hand it the `MeshHandle` `create` returned and get the
vertex data back. Reads the resident CPU copy when there is one; for a
runtime mesh that lives only on the GPU it reads the geometry back off the
GPU first (yielding a frame or two) and leaves CPU residency as it found it.
An optional stream is present only when the mesh carries one, so `uvs1 ==
nil` is the answer to whether it has a second UV set. The drawable mesh
the renderer holds carries the tangent basis its positions, uvs and normals
determine — supplied by the caller, or derived at the ingest that made it
drawable — and that is what the GPU read gives back. The CPU store answers
with the streams the bytes it decoded hold, so a `.mesh` written without a
tangent stream reads back `tangents == nil` for as long as a CPU copy of it
is resident.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `MeshGeometry`

```lua
local geom = renderer.mesh.geometry(renderer.mesh.create({ positions = P, indices = I }))
local tangents = renderer.mesh.geometry(handle).tangents
```

## globals/renderer/mesh/getVertices {#globals-renderer-mesh-getvertices}

```lua
renderer.mesh.getVertices(mesh: string | { [string]: any } | AssetRef) -> { any }
```

Read the vertices of a mesh's resident CPU copy — one entry per vertex,
`{ pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }`. Reads the resident CPU
store directly (no re-decode). Errors when the mesh has no resident CPU copy
— `renderer.mesh.geometry(mesh)` is the read that works wherever the mesh
lives, and returns the tangent, colour and skinning streams too.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `{ any }` — `{ { pos: {x,y,z}, normal: {x,y,z}, uv: {u,v} }, ... }` Each record carries `pos`, `normal`, `uv` and `uv1` as named channels (`{ x = , y = , z = }`). Geometry going the other way — into `renderer.mesh.create` — is parallel flat arrays (`positions`, `normals`, `uvs`), and `create` accepts this record list under `vertices` so a mesh read back here can go straight into a new one.

## globals/renderer/mesh/instanceInfo {#globals-renderer-mesh-instanceinfo}

```lua
renderer.mesh.instanceInfo(draw: InstancedDraw) -> InstancedDrawInfo?
```

What a live instanced-draw registration is drawing: which mesh, which
transform buffer, which per-instance data buffer if it named one, how many
instances, and how many slots it reserved. Returns nil once the
registration has been dropped.

`status` is what the renderer did with it. The fields above it are the
request, made a stage before the renderer sees it; `status` is the answer:
`"drawing"` for a registration the renderer is drawing, `"refused"` for one
it turned away — `error` carries its reason — and `"pending"` for the frame
between the call and the renderer answering. So a registration whose copies
are not being drawn says so here.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to report on.

**Returns** `InstancedDrawInfo?` — The registration record, or nil.

```lua
print(renderer.mesh.instanceInfo(draw).instanceCount)
local info = renderer.mesh.instanceInfo(draw)
if info.status == "refused" then error(info.error) end
```

## globals/renderer/mesh/instanceTransforms {#globals-renderer-mesh-instancetransforms}

```lua
renderer.mesh.instanceTransforms(draw: InstancedDraw) -> any
```

Read back the world matrices a registration's drawn copies are placed
by: `instanceCount` matrices of 16 floats, column-major and tightly
packed, in the layout the transform buffer holds them. The read is of the
buffer as it stands when it runs, so a population a compute pass rewrites
every frame answers with the placement of the frame the read lands in.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` whose copies to locate.

**Returns** `any` — A `Readback` to poll — `:ready()` then `:result()` — or nil for a registration that is no longer live.

```lua
local pending = renderer.mesh.instanceTransforms(draw)
while not pending:ready() do task.wait() end
local floats = pending:result()
```

## globals/renderer/mesh/isCpuResident {#globals-renderer-mesh-iscpuresident}

```lua
renderer.mesh.isCpuResident(mesh: string | { [string]: any } | AssetRef) -> boolean
```

True if this mesh has a resident CPU copy in the guid-keyed CPU store.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `boolean`

```lua
if renderer.mesh.isCpuResident(handle) then ... end
```

## globals/renderer/mesh/isResident {#globals-renderer-mesh-isresident}

```lua
renderer.mesh.isResident(mesh: string | { [string]: any } | AssetRef) -> boolean
```

True if a GPU mesh is resident under this mesh's guid — the device
holds its buffers, or the upload pass is still going to hand them over.
This is the store the draw paths are gated on, so a mesh this reports
resident is one `renderer.mesh.drawInstanced` and a `Model` can draw.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `boolean`

```lua
print(renderer.mesh.isResident(handle))
```

## globals/renderer/mesh/list {#globals-renderer-mesh-list}

```lua
renderer.mesh.list() -> { any }
```

Every mesh currently registered, ordered by guid — the ones a script
created and the ones that reached the device through an asset alike.
Answers "which mesh is this?" when all that is known is a size: each entry
carries the guid, the vertex/index counts it was created with, where it came
from (`origin` is `"asset"` for a mesh the asset path uploaded), whether the
GPU still holds it, and where its culling bounds come from (`boundsFrom` is
`"compute"` for a mesh a compute pass writes). A resident entry also carries
the bytes its buffers cost. `bytes` is the mesh's whole VRAM footprint and
is the sum of the THREE buffer columns beside it — `vertexBytes +
vertexStorageBytes + indexBytes`, where the storage column is the same
vertices bound as a storage buffer for the passes that read them that way.
Summing only the vertex and index columns understates a mesh by its vertex
size. The `bytes` column is what sums to the `meshes` category of
`renderer.gpuMemory()`.
`renderer.references("mesh", guid)` says what is still holding a row, and
`renderer.collect()` releases the rows nothing holds.

**Returns** `{ any }` — Array of `{ guid, vertexCount?, indexCount?, origin, owner?, resident, boundsFrom, bytes?, vertexBytes?, vertexStorageBytes?, indexBytes?, primitives?, revision? }`.

```lua
for _, m in ipairs(renderer.mesh.list()) do print(m.guid, m.bytes) end
```

## globals/renderer/mesh/listInstanced {#globals-renderer-mesh-listinstanced}

```lua
renderer.mesh.listInstanced() -> { InstancedDrawInfo }
```

Every instanced-draw registration this engine is drawing, in
registration order. Each record is what `instanceInfo` answers with, and
carries a `draw` handle of its own — so a population whose handle its
caller no longer holds is reached here and released, resized or read like
any other.

**Returns** `{ InstancedDrawInfo }` — An array of registration records; empty when nothing is registered.

```lua
for _, pop in ipairs(renderer.mesh.listInstanced()) do
renderer.mesh.dropInstanced(pop.draw)
end
```

## globals/renderer/mesh/loadCpu {#globals-renderer-mesh-loadcpu}

```lua
renderer.mesh.loadCpu(ref: string | AssetRef) -> MeshCpuHandle
```

Load a `.mesh` asset's geometry into the ONE guid-keyed CPU store (the
Disk→CPU step) and return a CPU handle. The handle holds NO geometry — only
the guid plus counts and the per-handle read/encode/unload ops (which read
the Rust-side store). Called by `meshRef:load()`. DEFAULT lifecycle: upload
to the GPU then `handle:unload()`; the store is populated only by this call.

**Parameters**

- `ref` `string | AssetRef` — A mesh `AssetRef` (carries `.guid` and reads its primary via getBytes),
or any string `asset.ref` resolves to one — the guid `encodeCpu` takes, an
identity, a name or a source path.

**Returns** `MeshCpuHandle`

```lua
local cpu = meshRef:load(); local gpu = renderer.mesh.create(cpu); cpu:unload()
local cpu = renderer.mesh.loadCpu(gpuMesh.guid)
```

## globals/renderer/mesh/morphTargets {#globals-renderer-mesh-morphtargets}

```lua
renderer.mesh.morphTargets(mesh: string | { [string]: any } | AssetRef) -> { string }
```

The names of the shapes this mesh blends towards, in the order an
entity's `ecs.MorphWeights` addresses them — weight `i` drives the target
named at `i`. An imported model carries the names its source file gave its
blend shapes, so content drives a face by the shape it means rather than by
the ordinal that shape happened to import at (which moves when the model is
re-exported). A target the source never named reads as an empty string.

Empty for a mesh with no morph targets. Errors when the mesh is neither
GPU- nor CPU-resident — materialise it first (`meshRef:handle()`).

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

**Returns** `{ string }` one name per morph target, in target order.

```lua
for i, name in renderer.mesh.morphTargets(meshRef) do print(i, name) end
```

## globals/renderer/mesh/morphWeights {#globals-renderer-mesh-morphweights}

```lua
renderer.mesh.morphWeights(mesh: string | { [string]: any } | AssetRef, weights: { [string]: number }) -> { number }
```

Turn weights named by shape into the ordered weight array
`ecs.MorphWeights` takes — the drive-a-face-by-name call. Every target the
mesh carries gets a slot; the ones `weights` names take their value and the
rest are 0, so the returned array always describes the whole mesh and a
shape left out is a shape at rest.

A name the mesh does not carry is an error listing the names it does: a
mistyped viseme that silently moved nothing would be indistinguishable from
a rig that never had it.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.
- `weights` `{ [string]: number }` — `{ [string]: number }` — how strongly to blend each named shape.

**Returns** `{ number }` one weight per morph target, in target order.

```lua
local w = renderer.mesh.morphWeights(meshRef, { Eyes_Blink = 1, Mouth_Smile = 0.4 })
ecs.set(face, ecs.MorphWeights { weights = w })
```

## globals/renderer/mesh/readback {#globals-renderer-mesh-readback}

```lua
renderer.mesh.readback(mesh: string | { [string]: any } | AssetRef) -> MeshCpuHandle
```

Read a runtime GPU mesh's geometry back to CPU and return a
`MeshCpuHandle` for it — the GPU→CPU half of the runtime-mesh freeze path. A
mesh made with `renderer.mesh.create` keeps no CPU copy, so persisting it
(`:encode()` → `asset.create("mesh", …)`) reads it back here first. Yields
until the readback completes (a frame or two). After it returns the geometry
is resident in the guid-keyed CPU store: `:getTriangles`, `:getVertices`,
`:getBounds`, `:geometry`, `:encode`, `:unload` all work. Errors if the mesh
never becomes resident in the vertex pool.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — the `MeshHandle` `renderer.mesh.create` returned, a guid, or a mesh `AssetRef`.

**Returns** `MeshCpuHandle`

```lua
local cpu = renderer.mesh.readback(handle); local zmsh = cpu:encode(); cpu:unload()
```

## globals/renderer/mesh/readbackPosed {#globals-renderer-mesh-readbackposed}

```lua
renderer.mesh.readbackPosed(requests: { { entity: string, mesh: any } }) -> { [string]: MeshCpuHandle }
```

Read the POSED geometry of skinned entities back to CPU: for each
request, the vertices the skinning pass wrote for that entity this frame,
joined by the indices of the mesh it is posed from. A skinned surface's
world-space triangles are produced on the GPU from the entity's joint
matrices, so the mesh asset holds the bind pose and only this reads where
the surface actually is. The posed vertices are in model space, so the
entity's own world transform still places them — the same transform the
raster draw uses.

Takes a LIST and answers a map, because the readbacks are queued together
and polled together: a scene's worth of characters costs the frames of one
readback rather than one entity's after another. Each posed mesh lands in
the CPU store under a guid of its own, derived from the entity, so
`compute.buildBvh`, `meshcpu.*` and every other guid-keyed reader takes it
like any other mesh. Call `handle:unload()` when done with it.

An entity the map omits holds no live pose — nothing skinned it this frame,
which is also what makes its draws read the source mesh, so its bind-pose
geometry is what stands for it.

**Parameters**

- `requests` `{ { entity: string, mesh: any } }` — `{ { entity = <id>, mesh = <mesh> } }` — the entity to read, and the mesh it is posed from (a guid, `MeshHandle` or mesh `AssetRef`).

**Returns** `{ [string]: MeshCpuHandle }` — A map from entity id to the `MeshCpuHandle` holding that entity's posed geometry.

```lua
local posed = renderer.mesh.readbackPosed({ { entity = id, mesh = ecs.get(id, ecs.Mesh).mesh } })
local tris = posed[id]:getTriangles()
```

## globals/renderer/mesh/scheduleClusters {#globals-renderer-mesh-scheduleclusters}

```lua
renderer.mesh.scheduleClusters(mesh: string | { [string]: any } | AssetRef) -> boolean
```

Queue a cluster-LOD bake for the static CPU mesh held under `guid`, and
attach the DAG to the GPU mesh of that same guid on the frame it finishes.
The CPU mesh may be unloaded on the very next line; the DAG is then built
one bounded slice per frame, so a dense mesh virtualizes without the frame
loop stopping for the whole bake.

One bake is advanced per frame — the one at the head of the queue — and the
geometry is read on the frame a bake gets there, from the definition the
engine holds for the mesh. A queue of meshes the engine holds definitions
for therefore holds one mesh's geometry rather than one per mesh, whatever
its depth. A mesh the engine holds no definition for is copied into the
queue as it is scheduled, since the CPU store is then the only thing
holding it. `renderer.mesh.clusterBakes().heldBytes` reports what the queue
is holding, and its `inFlight` rows report which bakes it is holding for.
This is what the `.mesh` assetType materialisation path uses; reach for
`renderer.mesh.buildClusters` when you want the bytes in hand instead.
Scheduling the same mesh again replaces the bake already in flight for it.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — A mesh loaded into the CPU store (`renderer.mesh.loadCpu`) — the
`MeshCpuHandle`, a `MeshHandle`, a guid, or a mesh `AssetRef`.

**Returns** `boolean` — True if a bake was queued.

```lua
renderer.mesh.scheduleClusters(cpu) ; cpu:unload()
```

## globals/renderer/mesh/setInstanceCount {#globals-renderer-mesh-setinstancecount}

```lua
renderer.mesh.setInstanceCount(draw: InstancedDraw, count: number) -> InstancedDraw
```

Change how many of a registration's instances draw. Constant time — the
reservation, the transform buffer and the pipeline all stay put, so this is
the verb for a population whose size changes per frame. The new count must
fit the reservation `drawInstanced` was given.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to reconfigure.
- `count` `number` — Instances to draw, at least 1 and within the reservation.

**Returns** `InstancedDraw` — The same `InstancedDraw`.

```lua
renderer.mesh.setInstanceCount(draw, visibleCount)
```

## globals/renderer/mesh/setInstanceRenderLayer {#globals-renderer-mesh-setinstancerenderlayer}

```lua
renderer.mesh.setInstanceRenderLayer(draw: InstancedDraw, renderLayer: number) -> InstancedDraw
```

Change which render layers a registration's copies belong to. Constant
time — the reservation, the transform buffer and the pipeline all stay put,
and the next frame drawn tests the copies against the new membership. It is
the verb for a population that follows something whose membership moves: a
camera or a capture including the layer draws the copies, one excluding it
does not.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to reconfigure.
- `renderLayer` `number` — The membership bitmask, the same value `drawInstanced` takes
as `renderLayer`. At least one bit must be set.

**Returns** `InstancedDraw` — The same `InstancedDraw`.

```lua
renderer.mesh.setInstanceRenderLayer(draw, mask)
```

## globals/renderer/mesh/setVertices {#globals-renderer-mesh-setvertices}

```lua
renderer.mesh.setVertices(mesh: string | { [string]: any } | AssetRef, positions: { number })
```

Replace a mesh's resident CPU vertex positions (flat `{ x,y,z, ... }`)
IN PLACE — indices, normals/uvs, and skinning are preserved, the AABB
recomputes, and the GPU re-fetches the new geometry so it shows on screen.
The positions alone travel, so this is the per-frame deformation path where
`renderer.mesh.update` re-sends the whole geometry. The mesh must be
CPU-resident: `renderer.mesh.create({ ..., keepCpu = true })` keeps a copy
from the start, `renderer.mesh.readback(mesh)` recovers one from the GPU,
and `meshRef:load()` loads one for a `.mesh` asset. Errors with the reason
otherwise, or when the vertex count doesn't match.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.
- `positions` `{ number }` — Flat `{ x,y,z, ... }` — one xyz per vertex; count must match the mesh.

```lua
local m = renderer.mesh.create({ positions = P, indices = I, keepCpu = true })
renderer.mesh.setVertices(m, P) -- P mutated in place each frame
```

## globals/renderer/mesh/unloadCpu {#globals-renderer-mesh-unloadcpu}

```lua
renderer.mesh.unloadCpu(mesh: string | { [string]: any } | AssetRef)
```

Drop a mesh's resident CPU copy from the guid-keyed CPU store.
The explicit release for a runtime geometry mesh's recoverable definition.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

## globals/renderer/mesh/update {#globals-renderer-mesh-update}

```lua
renderer.mesh.update(mesh: string | { [string]: any } | AssetRef, src: any?) -> MeshHandle
```

Overwrite the GPU resource `mesh` names IN PLACE, under the same guid,
from new geometry or compute buffers. Never writes a `.mesh` file — the
play-mode mutate path. A Model bound to the guid reflects the change with no
re-bind. Takes every form that names a mesh — the `MeshHandle` `create`
returned, the guid `renderer.mesh.list` hands out, a `MeshCpuHandle` or a
mesh `AssetRef`. Returns a handle carrying the bounds the new geometry has:
the handle it was given, refreshed, and a handle over the guid otherwise.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to update — a `MeshHandle`, a guid, a `MeshCpuHandle` or a
mesh `AssetRef`.
- `src` `any` _(optional)_ — New geometry `{positions, indices, ...}` or compute buffers
`{vertexBuffer, indexBuffer, vertexCount, indexCount, prevVertexBuffer?}`.

**Returns** `MeshHandle` — A `MeshHandle` for the updated mesh.

## globals/renderer/mesh/uploadClusters {#globals-renderer-mesh-uploadclusters}

```lua
renderer.mesh.uploadClusters(mesh: string | { [string]: any } | AssetRef, clusters: string) -> boolean
```

Attach a cluster-LOD DAG (bytes from `renderer.mesh.buildClusters`) to
the GPU mesh keyed by `guid`, enabling the continuous-cut cluster draw path
for that mesh.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh the clusters belong to — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.
- `clusters` `string` — Serialized cluster bytes (binary-safe).

**Returns** `boolean` — True if the upload was queued.

```lua
renderer.mesh.uploadClusters(gpu, cb)
```

## globals/renderer/minScreenSize {#globals-renderer-minscreensize}

```lua
renderer.minScreenSize() -> number
```

The on-screen radius, in pixels, an object must reach to be drawn. `0`
while the cutoff is off.

**Returns** `number`

```lua
local px = renderer.minScreenSize()
```

## globals/renderer/morphStats {#globals-renderer-morphstats}

```lua
renderer.morphStats() -> {
```

The morph state the last frame drew with. A mesh carries the shapes it
can blend towards and an entity carries how strongly each is blended
(`ecs.MorphWeights`); where both are present, the vertex stage adds the
weighted deltas to the base geometry.

`instances` is how many render slots that happened at, and `blends` how
many single-target blends those slots carry between them: a slot
contributes one per target its weights move, or that they moved the frame
before, so the number of targets a mesh can be given is bounded by the
buffer the blends live in. `meshes` is how
many meshes hold a delta block and `targets` how many targets those blocks
cover between them; `deltaBytes` is what the shared buffer they are
appended into holds. A morph-target mesh whose weights are all zero reads a
`meshes` above zero beside an `instances` and `blends` of zero.

**Returns** `{ instances: number, blends: number, meshes: number, targets: number, deltaBytes: number }`

```lua
local m = renderer.morphStats()
print(("%d instances carrying %d blends over %d meshes"):format(m.instances, m.blends, m.meshes))
```

## globals/renderer/observe {#globals-renderer-observe}

```lua
renderer.observe() -> RenderObservation
```

Everything the renderer knows about the frame it last drew: what
program it bound for each renderable, the render state it holds each
material under, and what each program has cost in pipeline builds.
`renderables` is one row per renderable in the renderer's draw list,
carrying the program its material named (`requestedProgram`) beside the one
that was bound (`boundProgram`) — `__error__` wherever the lookup missed
and the draw went ahead on the magenta placeholder — plus `substituted`,
the `outcome` (`drew` / `drewPlaceholder` / `skipped` / `notDrawn`), the
`reason` that forced it and the compiler's own `detail` for a failed
compile. `observed` says which of two answers a row is: `true` for a
resolution a geometry pass took as it drew, `false` for the renderer's own
resolution of a renderable this frame drew nowhere, which is what a
renderable outside every camera's frustum or layer mask reports.
`materials` is one row per
material the renderer holds a prepared bind group for; `shaders` is one row
per program pipelines have been built for. `frame` names the frame every
per-frame count covers; `retainedFrames` how many frames a resolution a
pass took is kept for after the last frame that drew it; `window` and
`costWindow` state both in the document itself. Recording is armed by the
first read, so this waits for the frame that first records rather than
answering empty.

**Returns** `RenderObservation`

```lua
local o = renderer.observe(); print(o.placeholderDraws, "renderables drew the placeholder in frame", o.frame)
```

## globals/renderer/occlusionCulling {#globals-renderer-occlusionculling}

```lua
renderer.occlusionCulling() -> boolean
```

Whether occlusion culling is currently enabled.

**Returns** `boolean`

## globals/renderer/passSchedule {#globals-renderer-passschedule}

```lua
renderer.passSchedule() -> {
```

The schedule check over this frame's enqueued render passes. Passes
declare what they read (`inputs`) and what they write (`output` /
`outputs` / `storage`), and the frame runs them in phase order and, inside
a phase, in `order` order. `violations` holds every input bound to a
resource the frame produces LATER: that read samples the resource as it
stands ahead of that pass, which is the previous frame's contents for a
render target that persists, an empty target for one just created, and the
scene draw's own output for a `@scene.*` buffer — and the pass renders
either way. The frame's own buffers are checked on the same terms as a
render target: bind `@scene.motion` at a phase ahead of the pass that
writes it and the read is reported, naming the buffer and its writer.
A pass reading a resource ahead of that write on purpose declares that slot
in its enqueue's `readsPrevious` and drops out of the list;
`unboundPrevious` holds declared slots the pass binds no such resource to,
which cover nothing.
A resource no queued pass writes is not reported — a camera rendering to
texture and `compute.dispatch` both fill targets outside the pass queue,
and the scene draw fills the `@scene.*` buffers every frame.
A read the frame has only one order for is not reported either: where the
writing pass consumes something the reading pass produces, the reader runs
first or the writer has nothing to write, which is what a pass reading a
buffer into a target of its own and a second pass copying that target back
over the buffer forms.
`unreachable` holds passes at a phase that does not run their kind: every
phase drains its fragment and compute passes, while `afterLighting` is the
one that draws geometry, draw and splat passes, so one of those enqueued
elsewhere sits in the queue and never runs.
Each finding is also stated in the engine log the first time it appears.

**Returns** `{ violations, unboundPrevious, unreachable }`

```lua
local s = renderer.passSchedule()
for _, v in s.violations do print(v.message) end
```

## globals/renderer/pipelineCache {#globals-renderer-pipelinecache}

```lua
renderer.pipelineCache() -> PipelineCache?
```

What the driver's compiled-pipeline store held, built, and wrote back.
A pipeline is machine code the GPU driver compiles from the shader bound
into it, and that compile is what a launch pays before the first frame
drawing with each pipeline can appear. The store keeps that compiled code
across runs, so a launch whose shaders have not changed reads back what the
previous one compiled.

`restoredBytes` is what a previous run left for this GPU and this launch
read; `pipelinesBuilt` counts the pipelines built since startup and
`buildMs` is what they cost together, which is the number the store lowers.
`saves` and `savedBytes` describe writing it back — deferred until a burst
of builds settles, so one launch is one write — and `dirty` is true while
pipelines have been built that the file does not hold, including after a
write that failed, which `lastError` then names. `path` is the file, named
after the GPU it belongs to.

`supported` is false where the platform holds no store a program can carry:
a browser keeps its own and hands none out, and an adapter can lack the
capability. `reason` says which, and the build count and timing still read
true there. `lastError` names a read or write failure; a failed store costs
the saved compile and never the frame, since every pipeline is built from
its source either way.
`pipelinesBuilt` and `buildMs` are engine-wide totals; `renderer.shaderCost()`
is the same cost broken down per program, with each one's permutation count.

**Returns** `PipelineCache?` — `{ supported, reason, path, restoredBytes, pipelinesBuilt, buildMs, saves, savedBytes, dirty, lastError }`, or nil before the renderer has drawn a frame

```lua
local c = renderer.pipelineCache()
print(("%d pipelines in %.1f ms, %d bytes restored"):format(c.pipelinesBuilt, c.buildMs, c.restoredBytes))
```

## globals/renderer/pointShadowBudget {#globals-renderer-pointshadowbudget}

```lua
renderer.pointShadowBudget() -> PointShadowBudget
```

The point-light shadow pool now in force. A point light with
`castsShadows` renders an omnidirectional cube map, six faces of depth,
and `slots` is how many of them fit — a further caster is lit but throws
no shadow, and the engine log names how many were turned away. The slot
count is bought rather than authored: `megabytes` of VRAM at `resolution`
texels per face is what decides it.

**Returns** `PointShadowBudget` — The pool — see `PointShadowBudget`.

```lua
local p = renderer.pointShadowBudget()
print(("%d shadowed point lights, %.1f MiB"):format(p.slots, p.bytes / 1024 / 1024))
```

## globals/renderer/projectionOffset {#globals-renderer-projectionoffset}

```lua
renderer.projectionOffset() -> (number, number)
```

The sub-pixel projection offset in force for the main camera, in NDC.

**Returns** `(number, number)` — The x and y offset, both 0 when the projection samples pixel centres.

```lua
local ox, oy = renderer.projectionOffset()
```

## globals/renderer/raycast {#globals-renderer-raycast}

```lua
renderer.raycast(origin: vec3, direction: vec3, maxDistance: number?, exclude: (string | { string })?) -> RenderRayHit?
```

Cast a ray against the geometry the renderer DRAWS and return the
nearest surface it meets. Every visible mesh answers, whether or not
anything gave it a rigid body — so a terrain, a procedurally generated
mesh, or any plain `Model` reports the surface at a point, which is what a
camera station, a prop, a sound source or a scatter standing on the ground
needs to know. The answer is the nearest triangle of the mesh, so a sloped
or terraced surface reports its height where it was asked rather than the
extent of its bounding box.

`distance` is measured from `origin` along the direction given, so it is a
world-space distance whenever that direction is a unit vector, and it is
directly comparable to a `physics.raycast` distance along the same ray.
`normal` is a unit vector turned to face back along the ray. `exact` is
true when the answer is a triangle and false when it is the object's
bounding box, which is what a mesh whose vertices live only in GPU buffers
answers with. The triangles are the mesh's own, placed by the entity's
transform and by the mesh's bind pose, so a surface a skinning or morph
pass deforms on the GPU answers as the geometry the mesh holds.

EVERYTHING drawn is in scope — the ground you meant, and equally a
character standing on it, a prop, a placeholder floor. The hit names its
entity in `entityId`, `exclude` steps over the ones you do not want, and
`renderer.raycastAll` hands back the whole column so you can pick the
surface yourself. A height you did not expect is usually a nearer surface
you did not mean to ask about, so read `entityId` before trusting a number.

**Parameters**

- `origin` `vec3` — `vec3` ray start in world space
- `direction` `vec3` — `vec3` ray direction; any length, the engine normalises
- `maxDistance` `number` _(optional)_ — `number?` how far the ray reaches, in world units. Default 1000
- `exclude` `(string | { string })` _(optional)_ — `(string | { string })?` entity id, or ids, to step over

**Returns** `RenderRayHit?`

```lua
local hit = renderer.raycast({ x, 100, z }, { 0, -1, 0 }, 200)
if hit then camera.position = { x, hit.point.y + 1.7, z } end
```

## globals/renderer/raycastAll {#globals-renderer-raycastall}

```lua
renderer.raycastAll(origin: vec3, direction: vec3, maxDistance: number?, maxHits: number?, exclude: (string | { string })?) -> { RenderRayHit }
```

Cast a ray against the geometry the renderer draws and return every
surface along it, nearest first. One entry per renderable the ray crosses —
the nearest intersection with each — so a stack of surfaces reads as the
order they stand in, and a caller after one particular surface finds it by
`entityId` rather than hoping it is the nearest. Each entry carries the
fields `renderer.raycast` returns.

**Parameters**

- `origin` `vec3` — `vec3` ray start in world space
- `direction` `vec3` — `vec3` ray direction; any length, the engine normalises
- `maxDistance` `number` _(optional)_ — `number?` how far the ray reaches, in world units. Default 1000
- `maxHits` `number` _(optional)_ — `number?` how many surfaces to return. Default 32
- `exclude` `(string | { string })` _(optional)_ — `(string | { string })?` entity id, or ids, to step over

**Returns** `{ RenderRayHit }`

```lua
for _, hit in renderer.raycastAll(eye, look, 500) do print(hit.entityId, hit.distance) end
```

## globals/renderer/raytraceCapability {#globals-renderer-raytracecapability}

```lua
renderer.raytraceCapability() -> string
```

The active ray-tracing backend: `"hardware"` (GPU ray query) or
`"compute"` (software traversal — the path on devices without hardware ray
query, e.g. the web). The same ray-tracing features work on both.

**Returns** `string` "hardware" | "compute"

```lua
if renderer.raytraceCapability() == "hardware" then ... end
```

## globals/renderer/raytraceStats {#globals-renderer-raytracestats}

```lua
renderer.raytraceStats() -> { [string]: any }
```

What the ray-tracing acceleration structure holds, and what this
session's frames have spent building it. A ray walks a structure built over
the scene's geometry, and keeping it current is work a frame pays before it
traces anything. On the `"compute"` backend geometry that has stood still
long enough is filed under a static partition the frames after it leave
alone: `staticTriangles` + `dynamicTriangles` = `triangles`, `nodes` is the
hierarchy over them, `fullRebuilds` / `partialRebuilds` / `reusedFrames`
count what the session's frames did, and `trianglesRebuilt` is what those
rebuilds re-emitted, summed. On the `"hardware"` backend `blas` is the
bottom-level structures cached, `blasBuilt` how many the last frame built,
and `tlasInstances` what the top-level structure names. The counters are
cumulative — sample, run the scene, sample again.

**Returns** `{ [string]: any }` — `table` {backend, triangles, staticTriangles, dynamicTriangles, nodes, fullRebuilds, partialRebuilds, reusedFrames, trianglesRebuilt, blas, blasBuilt, tlasInstances}

```lua
local before = renderer.raytraceStats().trianglesRebuilt
```

## globals/renderer/references {#globals-renderer-references}

```lua
renderer.references(handleOrKind: any?, id: string?) -> RuntimeResourceStatus?
```

What holds a runtime resource right now — the answer a root scene load
reads before releasing it. `references` names each live consumer the engine
found: `{ by = "entity", id }` for an entity wearing the material or mesh,
`"material"` for a material whose slot names the texture, `"instancedDraw"`,
`"camera"`, `"sky"`, `"lightmap"`, `"ui"` (a screen drawing it) and
`"postProcess"` (an effect sampling it). `handleHeld` says whether a script
still reaches a handle to it, `assetBacked` whether an asset stands behind
it, `ownerLive` whether the component instance, scene load or feature that
created it still stands, and `held` whether a hold pins it. `origin` reads
`"device"` for a GPU texture the device holds that no script created — the
one the cache loaded for an asset, the atlas the engine built — whose
holders are the references, a handle and the asset. Runs a full garbage
collection first, the same one `renderer.collect` runs, so a handle nothing
reaches counts as let go and the row says what the next collection does
with the resource. Yields for the frame the census runs on.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `RuntimeResourceStatus?` — The resource's status, or nil for a key the registry does not record and the device holds no texture under.

```lua
local s = renderer.references(mat) for _, r in s.references do print(r.by, r.id) end
```

## globals/renderer/reflectionEnvironment {#globals-renderer-reflectionenvironment}

```lua
renderer.reflectionEnvironment() -> {
```

What a reflective surface is reflecting. `probes` is how many reflection
probes the shading blends; they are gathered highest `priority` first, each
rank taking the coverage the ranks above it left, so a small interior probe
ranked above the large exterior one it sits inside wins outright wherever it
reaches full weight. `ranks` is the priority each of those probe slots was
published with, in slot order. `sky` is whether the sky fallback is armed:
with it, coverage no probe claims reflects the captured sky, and without it
a surface outside every probe's radius falls back to the nearest probe
alone. `skyCaptured` is whether the sky slot holds a capture — arming is
refused until it does, since an uncaptured slot reflects black.
`slots` is how many cube slots the environment array holds right now: the
sky's alone, at index `skySlot`, until a probe is captured into it, then
that one plus one per probe. `maxProbes` is how many of them probes may
take, and `resident` whether the array has grown past the sky's single
slot. Capture the sky with `environment.captureSky()`.

**Returns** `{ probes, ranks, sky, skyCaptured, resident, slots, skySlot, maxProbes }`

```lua
local env = renderer.reflectionEnvironment()
print(("%d probes, sky fallback %s"):format(env.probes, tostring(env.sky)))
```

## globals/renderer/release {#globals-renderer-release}

```lua
renderer.release(handleOrKind: any?, id: string?) -> boolean
```

Let go of the hold `renderer.hold` placed. The resource stays until
nothing else holds it and a collection releases it — the one a root scene
load runs, or a direct `renderer.collect()`.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `boolean` true when the registry knows the resource.

```lua
renderer.release(tex)
```

## globals/renderer/renderTargetLimits {#globals-renderer-rendertargetlimits}

```lua
renderer.renderTargetLimits() -> {
```

The size a render target may be on this device. `maxDimension` is the
device's own maximum 2D texture dimension — the largest either side of a
render target may take. `maxPixels` is how many pixels one render target
may hold, so the RGBA8 image it reads back as fits in a single buffer on
every platform the engine runs on, and `maxSquare` is the largest square
that budget buys. A capture, a `renderer.texture.create({ width, height })`
or a render-to-texture camera past either bound is refused at the call with
the reason, so ask here for the size to request.

**Returns** `{ maxDimension, maxPixels, maxSquare }`

```lua
local lim = renderer.renderTargetLimits()
local w = math.min(want, lim.maxSquare)
```

## globals/renderer/renderTargets {#globals-renderer-rendertargets}

```lua
renderer.renderTargets() -> {
```

Every render target the renderer owns and what each one costs, measured
from the texture that is allocated. One row per target, each carrying its
`name`, whether it is `resident`, the `bytes` it holds while it is, its
`width`/`height`/`layers`/`mipLevels`, and `onDemand`.
An `onDemand` target exists only while something needs it: a target nothing
writes into reads `resident = false` and `bytes = 0` and appears again the
frame something writes it, and one sized by content — the reflection-probe
cube array — holds the slots content asked for. The scratch the draws into
a render target have needed is reported as `camera[<handle>].*` rows:
depth and motion vectors under any rasterized pass, and the occlusion
channel and G-buffer over them under a camera's scene render. A draw builds
what it needs, and the set goes once no live camera names the target and
sixty frames have passed without a draw, so a target nothing draws into
carries no such row; the colour image drawn into belongs to the texture
cache and outlives every one of those releases.
`totalBytes` is what the resident targets hold together. Measured at the
end of the last rendered frame.

**Returns** `{ targets, totalBytes, residentCount }`

```lua
local rt = renderer.renderTargets()
print(("render targets: %.1f MiB over %d resident"):format(rt.totalBytes / 1048576, rt.residentCount))
for _, t in rt.targets do
if t.onDemand then print(t.name, t.resident, t.bytes) end
end
```

## globals/renderer/resolutionScale {#globals-renderer-resolutionscale}

```lua
renderer.resolutionScale() -> number
```

The fraction of the display resolution the scene is currently rendered
at. `1` until something sets it.

**Returns** `number`

```lua
local s = renderer.resolutionScale()
```

## globals/renderer/setAnisotropy {#globals-renderer-setanisotropy}

```lua
renderer.setAnisotropy(level: number) -> number
```

Set the maximum anisotropy material textures are sampled with. Takes
effect on the next frame for content already on screen — no reload, no
texture re-upload. 1 is plain trilinear.

**Parameters**

- `level` `number` — One of 1, 2, 4, 8, 16. Any other value is an error.

**Returns** `number` — The EFFECTIVE level after clamping to `renderer.maxAnisotropy()`, so asking for more than the device offers reports what was actually applied.

```lua
renderer.setAnisotropy(16)
```

## globals/renderer/setBlendedBatching {#globals-renderer-setblendedbatching}

```lua
renderer.setBlendedBatching(enabled: boolean) -> ()
```

Whether neighbours in a view's back-to-front blended order draw
together. On by default: alpha-blended geometry is submitted farthest-first,
and a stretch of neighbours in that order sharing a mesh, a material, a
shader and a pose is submitted as one instanced draw over those neighbours,
which puts the same members on screen in the same order out of a single
submission. A run stops wherever a differently-drawn renderable sorts
between two of its members, and a mesh of several primitives keeps a draw
per renderable — both would otherwise move fragments through each other.
Off, every blended renderable draws on its own at its own slot, so a
transparent crowd costs a draw per member. The image is the same either way,
which is what makes this the comparison a frame suspected of being formed by
the batching is made against; `renderer.drawStats().draws` counts the
difference.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setBlendedBatching(false)  -- a draw per blended renderable
```

## globals/renderer/setDepthPrepass {#globals-renderer-setdepthprepass}

```lua
renderer.setDepthPrepass(enabled: boolean) -> ()
```

Enable or disable the opaque depth pre-pass. While enabled the renderer
resolves opaque depth in its own pass before shading, so each shaded pixel
runs its material once instead of once per surface stacked behind it, and
the resolved depth is what occlusion culling reads. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setDepthPrepass(false) -- shade every layer, for comparison
```

## globals/renderer/setDepthPrepassOrdering {#globals-renderer-setdepthprepassordering}

```lua
renderer.setDepthPrepassOrdering(enabled: boolean) -> ()
```

Submit the depth pre-pass nearest-first. Renderables reach the pre-pass
in the order they were registered, which stands in no relation to where the
camera is: a scene built back-to-front makes every layer write depth and be
overwritten by the layer in front of it. Ordered, the nearest surface
writes first and the surfaces behind it are rejected by the depth test
before they write. The same draws go out either way and the depth that
comes out is the same, so `scene.depth_prepass` in `profiler.gpuFrame()` is
what moves. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setDepthPrepassOrdering(false) -- submit in registration order
```

## globals/renderer/setGpuMemoryTracking {#globals-renderer-setgpumemorytracking}

```lua
renderer.setGpuMemoryTracking(frames: number?) -> number
```

Set how often the GPU allocator sampler reads — one reading every
`frames` frames — or turn it off with 0. It starts at 60, a reading a
second at 60 Hz, so `renderer.gpuMemory().allocator` answers without
anything arming it. Building the ledger walks every live allocation, which
is why it is sampled rather than read every frame; the category figures
cost nothing either way, and a reader between samples sees the most recent
ledger, so a slow interval still answers.

Called with no argument it reports the interval in force and changes
nothing, which is how something that retimes the sampler puts it back
afterwards instead of restoring a number it assumed was the default.

**Parameters**

- `frames` `number` _(optional)_ — `number?` Frames between readings; 0 turns the sampler off. Omit
to read the interval without changing it.

**Returns** `number` — The interval now in force.

```lua
renderer.setGpuMemoryTracking(60)
local was = renderer.setGpuMemoryTracking()
renderer.setGpuMemoryTracking(1)
-- ... take a tight reading ...
renderer.setGpuMemoryTracking(was)
```

## globals/renderer/setMaxFramesInFlight {#globals-renderer-setmaxframesinflight}

```lua
renderer.setMaxFramesInFlight(frames: number) -> number
```

Set how many frames of GPU work may be outstanding before the renderer
stops running ahead. One is the least overlap this can express — a frame's
work is waited for as soon as the next frame has been submitted — which is
the lowest latency and the lowest throughput; higher values let a slow
frame build a longer backlog, and that backlog is memory. Takes effect on
the next frame.

Answers the bound after clamping to [1, 8], so asking for more than the
renderer honours reports what you actually got.

**Parameters**

- `frames` `number` — number Frames of GPU work that may be outstanding, 1 through 8.

**Returns** `number` — The bound that took effect, after clamping.

```lua
renderer.setMaxFramesInFlight(1) -- lowest latency
print(renderer.setMaxFramesInFlight(99), "was clamped")
```

## globals/renderer/setMinScreenSize {#globals-renderer-setminscreensize}

```lua
renderer.setMinScreenSize(pixels: number) -> ()
```

Stop drawing an object once its on-screen radius falls below this many
pixels. A few pixels across, an object carries no detail a viewer can
resolve while still costing a full vertex and submission pass, and the
cutoff drops it from the camera's draws entirely — `0`, the default,
keeps every object however small it lands. Measured from the object's own
bounds against the camera's projection, so the same threshold means the
same apparent size at any distance or field of view. Shadow casters have
their own threshold in `renderer.setShadowCasterCutoff`.

**Parameters**

- `pixels` `number` — `number` — smallest on-screen radius still drawn; 0 disables.

**Returns** `()`

```lua
renderer.setMinScreenSize(4) -- drop anything under 4 px of radius
print(renderer.drawStats().compactedDrawn, "instances survived it")
```

## globals/renderer/setOcclusionCulling {#globals-renderer-setocclusionculling}

```lua
renderer.setOcclusionCulling(enabled: boolean) -> ()
```

Enable or disable occlusion culling. While enabled the renderer reduces
the pre-pass depth into a pyramid each frame and tests every renderable
that cleared the frustum against it, dropping the ones another surface
entirely covers before their geometry is submitted. The pyramid describes
the frame being drawn, so an object that becomes visible this frame is
never held back a frame. Requires the depth pre-pass.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setOcclusionCulling(true); print(renderer.cullStats().occlusionCulled)
```

## globals/renderer/setPointShadowBudget {#globals-renderer-setpointshadowbudget}

```lua
renderer.setPointShadowBudget(cfg: {
    megabytes: number?,
    resolution: number?,
}) -> number
```

Set how much VRAM the point-light shadow atlas may hold, and at what
per-face resolution. An omitted field keeps its current value. The atlas
is reallocated on the next frame, so `renderer.pointShadowBudget().slots`
reports the new pool one frame later; the returned number is what this
budget buys. Raising `resolution` sharpens every point shadow and spends
the same memory on fewer of them — doubling it quarters the slot count.
Values are clamped: megabytes [1, 1024], resolution [64, 4096], and the
pool never exceeds `renderer.pointShadowBudget().maxSlots`. One slot is
always granted, so a budget too small for a single cube shadows one light
and the pool costs what that slot costs rather than what was asked for —
`{ megabytes = 1, resolution = 4096 }` buys 384 MiB of ceiling. Read
`pointShadowBudget().bytes` back to see what a budget actually bought, and
`renderer.shadowMemory().point` to see what the scene has made resident.

**Parameters**

- `cfg` `{
    megabytes: number?,
    resolution: number?,
}` — The fields to change — `megabytes` and/or `resolution`.

**Returns** `number` — Cube slots this budget buys.

```lua
renderer.setPointShadowBudget({ megabytes = 96 })
renderer.setPointShadowBudget({ resolution = 1024, megabytes = 96 })
```

## globals/renderer/setPresentMode {#globals-renderer-setpresentmode}

```lua
renderer.setPresentMode(mode: string) -> string
```

Set how a presented frame reaches the display. `fifo` queues every frame
and shows it on a vertical blank, which never tears and never drops one;
`mailbox` replaces the queued frame with the newest, which does not tear
and does not hold the renderer to the refresh rate; `immediate` presents as
soon as a frame is ready and can tear; `fifo_relaxed` is `fifo` that tears
rather than stall when a frame misses its blank; `auto_vsync` and
`auto_no_vsync` leave the choice to the backend.

A surface that does not offer the mode presents `fifo` instead, so read
`renderer.framePacing().presentMode` for what took effect and
`.presentModes` for what this surface offers. Takes effect on the next
frame.

**Parameters**

- `mode` `string` — string One of "fifo", "fifo_relaxed", "mailbox", "immediate", "auto_vsync", "auto_no_vsync".

**Returns** `string` — The canonical spelling of the request — `renderer.framePacing().presentMode` is what the surface presents with, and differs when the surface does not offer the request.

```lua
renderer.setPresentMode("mailbox")
print(renderer.framePacing().presentMode, "is what the surface took")
```

## globals/renderer/setProjectionOffset {#globals-renderer-setprojectionoffset}

```lua
renderer.setProjectionOffset(x: number, y: number)
```

Offset the main camera's projection by a sub-pixel amount, in NDC, for
the frames until it is set again. The offset is in NDC because that is the
space it is constant in: one pixel is `2.0 / width` across, so half a pixel
is `1.0 / width`. Velocity (`@scene.motion`) is measured against the
offset-free projection, so a still scene reports no motion however the
samples are placed — and picking resolves a click to the same ray either
way. `(0, 0)` samples pixel centres.

**Parameters**

- `x` `number` — Horizontal offset in NDC. One pixel is `2.0 / width`.
- `y` `number` — Vertical offset in NDC. One pixel is `2.0 / height`.

```lua
renderer.setProjectionOffset(0.5 * 2 / w, -0.25 * 2 / h)
```

## globals/renderer/setRaytrace {#globals-renderer-setraytrace}

```lua
renderer.setRaytrace(enabled: boolean) -> ()
```

Enable or disable GPU ray tracing. While enabled the engine builds the
scene acceleration structure each frame so ray-tracing render features can
trace against it; disabling stops the build (so it costs nothing until a
ray-traced effect is active). Required before any ray-traced shadows / AO /
reflections render.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setRaytrace(true); renderer.feature.create("rt_shadows")
```

## globals/renderer/setResolutionScale {#globals-renderer-setresolutionscale}

```lua
renderer.setResolutionScale(scale: number) -> number
```

Render the scene at a fraction of the display's resolution and present
it at the display's own size. Shading cost scales with pixel count and with
nothing else, so this trades sharpness for frame time without taking
anything out of the scene: at `0.5` the scene rasterizes a quarter of the
pixels. UI and text are unaffected — they are drawn after the scene is
brought back up to size. The scene rows in `profiler.gpuFrame()` are what
move.

**Parameters**

- `scale` `number` — `number` — fraction of the display resolution, clamped to [0.25, 1].

**Returns** `number` — the scale in force after clamping.

```lua
renderer.setResolutionScale(0.7)
```

## globals/renderer/setShadowCaching {#globals-renderer-setshadowcaching}

```lua
renderer.setShadowCaching(enabled: boolean) -> ()
```

Whether a shadow map that nothing changed is kept rather than drawn
again. On by default: a shadow view — one directional cascade, one atlas
layer of spot tiles, one face of a point light's cube — is rasterized on
the frames its own inputs change and holds the depth it drew on the ones
they do not.
Off, every view is drawn on every pass, which is what a shadow suspected of
holding a stale image is compared against.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setShadowCaching(false)  -- draw every shadow view, every frame
```

## globals/renderer/setShadowCasterBatching {#globals-renderer-setshadowcasterbatching}

```lua
renderer.setShadowCasterBatching(enabled: boolean) -> ()
```

Whether a shadow view draws every caster of one mesh together. On by
default: a view — one directional cascade, one atlas layer of spot tiles,
one face of a point light's cube — submits one draw per geometry over every
caster of it the view admits, wherever those casters sit in render order
and whatever transform slots they hold. Off, a view draws the runs of render-order
neighbours that share a mesh AND hold consecutive slots, so a scene that has
spawned and despawned anything fragments into many more draws. The image is
the same either way, which is what makes this the comparison a shadow
suspected of being placed by the batching is made against.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setShadowCasterBatching(false)  -- draw the runs the scene presents
```

## globals/renderer/setShadowCasterCutoff {#globals-renderer-setshadowcastercutoff}

```lua
renderer.setShadowCasterCutoff(cfg: {
    minRadiusPx: number?,
    maxDistance: number?,
}) -> ShadowCasterCutoff
```

Set the shadow-caster cutoff. An omitted field keeps its current value,
so a call can adjust one threshold without restating the other. Both are
measured against the camera the frame draws from rather than against each
light, so one setting covers every cascade, spot and cube face, and a
caster that stops casting is one whose shadow the viewer could not have
resolved. `maxDistance` is measured to the near side of the caster's
bounding sphere, so a large object keeps casting while any part of it is in
range. 0 releases a threshold; releasing both draws the casters the frame
drew before either was set.

**Parameters**

- `cfg` `{
    minRadiusPx: number?,
    maxDistance: number?,
}` — The fields to change — `minRadiusPx` and/or `maxDistance`.

**Returns** `ShadowCasterCutoff` — The cutoff now in force.

```lua
renderer.setShadowCasterCutoff({ minRadiusPx = 2 })
renderer.setShadowCasterCutoff({ minRadiusPx = 1.5, maxDistance = 120 })
```

## globals/renderer/setShadowConfig {#globals-renderer-setshadowconfig}

```lua
renderer.setShadowConfig(cfg: {
    resolution: number?,
    cascades: number?,
    distance: number?,
    splitLambda: number?,
    fadeFraction: number?,
    softness: number?,
}) -> ShadowConfig
```

Set the directional shadow quality. Any omitted field keeps its current
value, so a call can adjust one knob without restating the rest. Values are
clamped: resolution [64, 8192], cascades [1, 4], distance >= 0, splitLambda
[0, 1], fadeFraction [0, 1], softness [0, 1]. Changing `resolution` or
`cascades` reallocates the depth array; the rest are per-frame values. A
`distance` of 0 hands the range to the frame — the splits are cut over the
depth its own shadow-taking renderables reach — and a positive one caps it,
which is what a scene bounding its shadow cost states.

**Parameters**

- `cfg` `{
    resolution: number?,
    cascades: number?,
    distance: number?,
    splitLambda: number?,
    fadeFraction: number?,
    softness: number?,
}` — The fields to change — see `ShadowConfig`.

**Returns** `ShadowConfig` — The full config now in force.

```lua
renderer.setShadowConfig({ softness = 0.65, fadeFraction = 0.28 })
renderer.setShadowConfig({ cascades = 4, resolution = 2048, distance = 240 })
```

## globals/renderer/setShadowHero {#globals-renderer-setshadowhero}

```lua
renderer.setShadowHero(entity: string, padding: number?) -> ()
```

Give one caster a directional shadow view of its own, fit to its world
bounds.

A cascade covers the slab of world the camera sees, so its texels are spread
over tens of metres and one character standing in the middle of it is
resolved by a handful of them. The hero view is the same light and the same
depth range zoomed onto that entity's bounds, so the whole map goes into the
shadow it and the ground under it carry — `renderer.shadowHero().zoom` is
the factor its texel density gains.

It renders beside the cascades, into a layer of the same texture allocated
while a hero is registered, and every surface inside it reads it in place of
the cascade, crossing back at its edge. Nothing else about the shadow
changes: the same casters reach it, at the same depth range, through the
same filter.

**Parameters**

- `entity` `string` — The entity whose renderables the view is fit around.
- `padding` `number` _(optional)_ — How much room the fit leaves around those bounds — for a pose that
leaves the bind-pose box and for the filter that samples outside a
silhouette. 1.0 fits them exactly.

**Returns** `()`

```lua
renderer.setShadowHero(player.id)
print(("hero shadow: %.1f texels/unit vs %.1f"):format(
renderer.shadowHero().texelsPerUnit, renderer.shadowHero().cascadeTexelsPerUnit))
```

## globals/renderer/setShadowProxy {#globals-renderer-setshadowproxy}

```lua
renderer.setShadowProxy(mesh: string, proxy: string) -> ()
```

Rasterize `proxy` in place of `mesh` in every shadow view. A shadow is a
silhouette resolved at the resolution of a shadow map, so the triangles that
carry a mesh's close-up detail write depth no reader can resolve — a
decimated version of the shape, a level of its own LOD chain, or a
hand-built hull casts the same shadow for a fraction of the geometry.

The registration is keyed by MESH, so one call covers every instance of it —
entities and GPU-driven populations alike — and a crowd sharing that mesh
stays one draw. The proxy is placed by whatever places the caster, its
instance's own transforms, so it stands where the caster stands, at the
caster's scale.

An entity caster keeps its own geometry where a stand-in could not be placed
or deformed correctly: it is skinned (it rasterizes the post-skinned
vertices written for its own mesh), it blends morph targets (whose deltas
describe its own mesh and are read by vertex id), or its proxy would be
placed by a different node of its model than the source mesh is. Either
caster keeps it where the renderer holds no geometry under the proxy's
guid. Each of those is counted in `renderer.shadowProxies()`.

Nothing else in the scene draws a proxy, so this call is what brings it onto
the GPU, and it raises where it cannot. A proxy already resident there is
registered as it stands.

**Parameters**

- `mesh` `string` — The mesh a caster draws, as a guid or any mesh reference.
- `proxy` `string` — The mesh it rasterizes into shadow views instead.

**Returns** `()`

```lua
renderer.setShadowProxy(statueMesh, statueHullMesh)
print(renderer.shadowProxies().triangles, "vs", renderer.shadowProxies().sourceTriangles)
```

## globals/renderer/setSkinnedBatching {#globals-renderer-setskinnedbatching}

```lua
renderer.setSkinnedBatching(enabled: boolean) -> ()
```

Whether skinned instances holding one pose draw together. On by default:
instances of one mesh wearing one material and posed alike read the same
post-skinned vertices, so the camera's colour passes submit them as a single
instanced draw, and so does each shadow view and the velocity pass while
`renderer.shadowCasterBatching()` is on — that switch is what makes a depth
view form its draws by geometry at all. The camera depth pre-pass submits
its casters nearest-first, which is a run per span of neighbours rather than
a draw per geometry, so a crowd costs a draw per member there. Off, each
skinned instance draws on its own at its own slot in every pass that
rasterizes it. The image is the same either way, which is what makes this
the comparison a frame suspected of being formed by the batching is made
against — `renderer.drawStats().draws` counts the difference and
`renderer.skinningStats().poses` says how many distinct poses it holds.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setSkinnedBatching(false)  -- a draw per skinned instance
```

## globals/renderer/setSkinningPoseHold {#globals-renderer-setskinningposehold}

```lua
renderer.setSkinningPoseHold(enabled: boolean) -> ()
```

Whether a pose the skinning pass already wrote is read as it stands. On
by default: the pass produces an instance's vertices from its joint
matrices, its node transforms, its blend weight and its blend model, so the
slice holding a pose already holds what running the pass over those same
inputs would write. A frame binding a pose whose slice still holds it reads
the slice and dispatches nothing, and skinning costs what the frame's poses
CHANGED — a paused clip, a skeleton nothing drives, a cast standing in one
pose, each cost compute the frame the pose arrived and nothing after it.
Off, every pose a frame binds is dispatched again, which is the comparison a
frame suspected of reading a slice that no longer holds its pose is made
against; the image is the same either way and
`renderer.skinningStats()` counts the difference as `dispatches` against
`held`. A mesh whose vertices a compute pass writes is dispatched every
frame however this stands.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setSkinningPoseHold(false)  -- dispatch every pose, every frame
```

## globals/renderer/setSpotShadowBudget {#globals-renderer-setspotshadowbudget}

```lua
renderer.setSpotShadowBudget(cfg: {
    megabytes: number?,
    resolution: number?,
}) -> number
```

Set how much VRAM the spot/area shadow atlas may hold, and the per-side
resolution of one layer. An omitted field keeps its current value. The
atlas is reallocated on the next frame, so `renderer.spotShadowBudget()`
reports it one frame later; the returned number is what this budget buys.
Raising `resolution` sharpens the lights that cover the most screen and
spends the same memory on fewer layers — doubling it quarters the layer
count. Raising `megabytes` buys layers, which is what lets several lights
hold a large tile at once. Values are clamped: megabytes [1, 1024],
resolution [64, 4096], and the atlas never exceeds
`spotShadowBudget().maxLayers`. One layer is always granted, so a budget
too small for one still shadows lights and the atlas costs what that layer
costs rather than what was asked for.

**Parameters**

- `cfg` `{
    megabytes: number?,
    resolution: number?,
}` — The fields to change — `megabytes` and/or `resolution`.

**Returns** `number` — Atlas layers this budget buys.

```lua
renderer.setSpotShadowBudget({ megabytes = 64 })
renderer.setSpotShadowBudget({ resolution = 2048, megabytes = 64 })
```

## globals/renderer/setTextureBudget {#globals-renderer-settexturebudget}

```lua
renderer.setTextureBudget(opts: TextureBudgetOpts) -> TextureBudget
```

Bound the VRAM a world's textures occupy, by keeping only the mip levels
the frame is actually sampling. Pass `{ megabytes = 256 }`; `0` — the
default — leaves texture residency alone and every texture stays fully
resident the way it uploaded.

With a budget armed, each frame measures how many screen pixels ONE
traversal of a texture's coordinate range covers on the surface that spans
it widest, and asks for the mip level that serves that span one texel per
pixel — the level the GPU picks from the fragment's own derivatives. A
material with `uvScale = 8` lays eight copies of its texture across a
surface, so each copy spans an eighth of the surface and asks for three
levels coarser than the surface's own size would. A shader that declares
`// @uv_space: world` advances its coordinate over world units rather than
over the mesh's UVs, so how many copies a surface carries follows how large
that surface is. The textures whose surfaces cover the fewest pixels give
up levels until the set fits. Detail climbs one level per frame, from the
image already on screen, so a surface the camera approaches sharpens rather
than popping, and no texture is taken below the level whose longest side is
64 texels.

`bias` shifts every measurement by whole mip levels either way — negative
for finer than the sampling implies, positive for coarser — over a world
whose look wants a different trade than one texel per pixel.

The plan moves a texture whose demand the frame can measure: one at least
256 texels on its narrowest side, worn by a surface an entity draws. A
texture a UI image, a post-process property or a render feature holds a
view of stays whole, because nothing measures how much of the screen those
cover.

Which textures the budget governs follows the surfaces the frame draws. A
texture whose asset still holds its bytes is enrolled the frame a measured
surface wears it — whenever it loaded, and whenever the budget was armed —
because a level change reads the levels it needs back from the asset; when
the last such surface goes it leaves the set whole, at the level it
uploaded at, and a surface reaching it again takes it back up. A texture a
script uploaded has its pixels nowhere else, so one enrolled while it is
resident holds them in system memory
(`renderer.textureMemory().streamSourceBytes`) from the upload until a
surface has worn it and gone, and releases them then, which is what keeps
it out for the rest of the session; one whose pixels were already released
when the budget was armed is out from the start.
`renderer.textureMemory().pinnedTextures` counts those, together with the
textures whose asset could not be read back and the ones a UI image, a
post-process property or a render feature holds a view of. Disarming
returns every texture to the level it uploaded at, and arming again governs
the textures the frame's surfaces are wearing then.

**Parameters**

- `opts` `TextureBudgetOpts` — `{ megabytes: number?, bias: number? }`

**Returns** `TextureBudget` — `{ megabytes, bias }` — the budget now in force

```lua
renderer.setTextureBudget({ megabytes = 128 })
renderer.setTextureBudget({ megabytes = 128, bias = -1 })  -- one level finer everywhere
renderer.setTextureBudget({ megabytes = 0 })               -- leave residency alone
```

## globals/renderer/setTransmissionShadows {#globals-renderer-settransmissionshadows}

```lua
renderer.setTransmissionShadows(enabled: boolean) -> ()
```

Let translucent casters tint the sunlight they block instead of blocking
it outright. A shadow map holds one depth per texel and is compared as a
yes-or-no test, so stained glass, water and thin fabric all project the same
black silhouette a wall does. With this on, a caster whose material declares
opacity (`base_color` alpha under a transparent blend) or `transmission`
also draws into a light-space transmittance map, and the colour it lets
through multiplies into the directional light reaching whatever stands
behind it. Stacked casters compose. Opaque casters are unaffected, and a
scene with no translucent caster allocates nothing and records no pass.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setTransmissionShadows(true)  -- stained glass tints the floor
```

## globals/renderer/shaderCache {#globals-renderer-shadercache}

```lua
renderer.shaderCache() -> ShaderCache?
```

What the shader compile gate's store of baked WGSL held, answered and
wrote back. Compiling a `.shader` wraps the author's body in its framework,
expands every `#include`, and hands the result to naga to parse and
validate — work that is a pure function of the text going in, and that a
launch would otherwise repeat for every shader it draws with. The store
keeps that baked text across launches.

`restoredEntries` and `restoredBytes` are what a previous launch left that
this one read back. `hits` counts the compiles answered out of the store
and `misses` those that ran in full; `savedMs` sums what each hit's own
recorded compile had cost, against `compileMs`, what the misses spent.
`stale` counts the misses whose key was held but whose `#include`d modules
had changed underneath — an entry records every module its expansion
consumed, so editing a module invalidates exactly the shaders that included
it and leaves the rest.

`entries` and `bytes` are what the store now holds, `evictions` how many a
write dropped to stay inside its bounds, and `saves` / `savedBytes` /
`dirty` describe writing it back, deferred until a burst of compiles
settles. `persistent` is false where a launch has nowhere to keep
artifacts and `reason` says why; `location` is the file, or the browser
store, they are kept in. `restoreState` is how the read of what a previous
launch left has gone — `pending` while it is still out (a browser answers
through a promise, so a launch reaches its first frames before it lands),
`restored` once entries came back, `empty` when there were none to come
back, `failed` when what was there could not be read, and `none` where a
launch keeps nothing. A cold, missing or corrupt store leaves every
shader compiling from source with identical output, and `lastError` then
names what went wrong.

**Returns** `ShaderCache?` — `{ persistent, reason, restoreState, location, restoredEntries, restoredBytes, hits, misses, stale, entries, bytes, compileMs, savedMs, saves, savedBytes, dirty, evictions, lastError }`, or nil on a build with no renderer

```lua
local c = renderer.shaderCache()
print(("%d hits, %d misses, %.1f ms saved"):format(c.hits, c.misses, c.savedMs))
```

## globals/renderer/shaderCost {#globals-renderer-shadercost}

```lua
renderer.shaderCost() -> { ShaderCost }
```

What each program has cost in pipeline builds, beside the compile
gate's most recent word about it. `variants` is how many pipelines this
engine has built for it — one per (target format, vertex layout,
render-state key) permutation reached — and `buildMs` what those builds
cost, both summed since engine start. A pipeline the driver's own store
restored is not built and so is not counted, so a second launch on the same
adapter reports less than the first. `status` is `compiled`, `failed` or
`pending`, and `error` carries the compiler's message for a failure.
Ordered by cost, most expensive first.

**Returns** `{ ShaderCost }`

```lua
local top = renderer.shaderCost()[1]; print(top.shader, top.variants, top.buildMs)
```

## globals/renderer/shaderVariants {#globals-renderer-shadervariants}

```lua
renderer.shaderVariants() -> { ShaderVariants }
```

Every shader that declares optional features, and the programs its
materials have made it compile. Each row carries the features the shader
declares, the base program it ships as, and one entry per variant with the
features that variant holds — so the permutation count a scene's materials
are spending is a number to read rather than something to infer from
compile time. A shader whose variants reach `budget` compiles no more; the
materials asking for further feature sets draw with the base program.

**Returns** `{ ShaderVariants }` — An array of `ShaderVariants`, one per feature-declaring shader.

```lua
for _, s in renderer.shaderVariants() do print(s.shader, #s.variants, s.budget) end
```

## globals/renderer/shadingOf {#globals-renderer-shadingof}

```lua
renderer.shadingOf(subject: string | { [string]: any }) -> ShadingReading
```

What the renderer is shading ONE subject with, taken from the document
the renderer publishes — the call a system holding a handle makes to find
out whether what reaches the screen is its own material or the magenta
placeholder standing in for it, without reading the engine log. `subject` is
an entity that draws or the registry key of a material. `state` reads
`itsMaterial` where the renderer bound the program the material names,
`errorMaterial` where it bound the placeholder instead, `stalePipeline`
where the pipeline drawing it was built before that program's most recent
compile, `nothingBound` where the renderer resolved no pipeline for it,
`pending` where this call is the one that armed per-draw recording and the
frame after it publishes, and `unknown` where the renderer holds a
resolution under no such subject. A fault state carries the renderer's own
`reason` from the closed set `renderer.drawDiagnostics()` names — plus
`materialNotPrepared`, which a material subject reads where the renderer
prepared nothing under that key — the compiler's `detail`, the `program`
the material asked for and the `bound` one; `means` states the reading in a
sentence. A material subject answers from the renderables drawing with it,
and from the renderer's record for the material itself where a draw
registered against the material carries no row of its own; a subject that
several renderables draw answers with a refused one wherever there is one.
The reading follows the renderer, so a program that compiles on a later
edit puts the subject back on `itsMaterial` from the frame the renderer
draws it with again.

**Parameters**

- `subject` `string | { [string]: any }` — The entity — a proxy from `entity(...)` or an entity-id string —
or the material, as its registry key or the `MaterialHandle`
`renderer.material.create` returned.

**Returns** `ShadingReading`

```lua
local s = renderer.shadingOf(emitter); if s.state ~= "itsMaterial" then print(s.means) end
```

## globals/renderer/shadowCacheStats {#globals-renderer-shadowcachestats}

```lua
renderer.shadowCacheStats() -> {
```

What the last frame did with the shadow maps it already had. A shadow
view — one directional cascade, one atlas layer of spot tiles, one face of
a point light's cube — is drawn again only when something it draws from
changed:
its light moved, a caster it can see moved or appeared or vanished, a
caster's geometry or material changed, a caster changed pose or moved the
nodes its parts are placed by, or the map it writes into was reallocated.
Anything else keeps the depth already in the texture, so a scene that stops
moving reads `rendered` 0 while `cached` keeps climbing. A mesh whose
vertices a compute pass writes — a population, or a mesh built from a
compute buffer — re-renders the views it stands in every frame. A shadowed
point light contributes six views, one per cube face, so a caster moving on
one side of it re-renders the face that can see it and leaves the other
five holding what they have. Counted per light kind, plus the totals across
all three.

These are totals over every view of a kind. `renderer.shadowViews()` is the
same frame one view at a time, each row naming the light that owns it and
what it drew.

**Returns** `{ directionalRendered, directionalCached, spotRendered, spotCached, pointRendered, pointCached, rendered, cached }`

```lua
local s = renderer.shadowCacheStats()
print(("shadow views: %d drawn, %d cached"):format(s.rendered, s.cached))
```

## globals/renderer/shadowCaching {#globals-renderer-shadowcaching}

```lua
renderer.shadowCaching() -> boolean
```

Whether a shadow view may keep the depth it already holds.

**Returns** `boolean`

## globals/renderer/shadowCasterBatching {#globals-renderer-shadowcasterbatching}

```lua
renderer.shadowCasterBatching() -> boolean
```

Whether a shadow view draws every caster of one mesh together.

**Returns** `boolean`

## globals/renderer/shadowCasterCutoff {#globals-renderer-shadowcastercutoff}

```lua
renderer.shadowCasterCutoff() -> ShadowCasterCutoff
```

How small, and how far away, a caster may get before it stops writing
depth into any shadow view. A shadow view rasterizes a caster's whole
triangle count whatever the shadow it produces ends up covering, so an
object the viewer resolves a fraction of a pixel of, and one past the range
the scene cares about, each cost a full depth pass per shadowed light for
detail nothing reads. Both thresholds are 0 — released — until something
sets them.

**Returns** `ShadowCasterCutoff` — The cutoff in force — see `ShadowCasterCutoff`.

```lua
local c = renderer.shadowCasterCutoff(); print(c.minRadiusPx, c.maxDistance)
```

## globals/renderer/shadowConfig {#globals-renderer-shadowconfig}

```lua
renderer.shadowConfig() -> ShadowConfig
```

The directional shadow quality now in force. `resolution` and `cascades`
size the cascade depth array; `distance` and `splitLambda` place the splits
along the view; `fadeFraction` and `softness` shape how the result is
sampled.

**Returns** `ShadowConfig` — The full config — see `ShadowConfig`.

```lua
print(renderer.shadowConfig().cascades)
```

## globals/renderer/shadowHero {#globals-renderer-shadowhero}

```lua
renderer.shadowHero() -> ShadowHeroReport
```

The registered hero caster and what the last frame's fit produced. A
frame that fit nothing says why in `decline`.

**Returns** `ShadowHeroReport` — See `ShadowHeroReport`.

```lua
local h = renderer.shadowHero()
print(h.active, h.zoom, h.decline)
```

## globals/renderer/shadowMemory {#globals-renderer-shadowmemory}

```lua
renderer.shadowMemory() -> {
```

How much GPU memory the shadow maps hold right now, in bytes, by the
light kind that owns them. The spot atlas and the point pool are sized to
the casters in the scene rather than to the budget, so `spot` and `point`
move as lights that cast shadows appear and leave, and a scene with one
shadowed light holds far less than one that fills every slot. A budget is
the ceiling they grow within — `renderer.spotShadowBudget().layers` and
`renderer.pointShadowBudget().slots` report that ceiling, unmoved by how
many casters exist. Raising shadow resolution costs the square of the
change across every cascade.

**Returns** `{ directional, spot, point, total }` in bytes

```lua
local m = renderer.shadowMemory()
print(("shadows: %.1f MiB"):format(m.total / 1024 / 1024))
```

## globals/renderer/shadowProxies {#globals-renderer-shadowproxies}

```lua
renderer.shadowProxies() -> ShadowProxyReport
```

The shadow proxies in force and what the last frame's shadow passes did
with them. `triangles` and `sourceTriangles` are what those passes
submitted and what they would have submitted from the source meshes — the
before/after of every registration, equal while nothing is proxied.

**Returns** `ShadowProxyReport` — See `ShadowProxyReport`.

```lua
local p = renderer.shadowProxies()
print(("shadow triangles: %d of %d"):format(p.triangles, p.sourceTriangles))
```

## globals/renderer/shadowViews {#globals-renderer-shadowviews}

```lua
renderer.shadowViews() -> ShadowViewReport?
```

Every shadow view the last rendered frame considered, and what each one
cost.

A frame rasterizes a depth view per directional cascade, one for the hero
caster, one per shadow-casting spot and six per shadow-casting point.
`renderer.shadowCacheStats()` counts those views by light kind,
`renderer.drawStats()` sums their draws with the camera's, and
`profiler.gpuFrame()` carries one `scene.shadow` span across all of them.
This is the same frame read one view at a time.

Each row names the view and the light that owns it, says whether it drew or
kept the depth it already held, and carries the draws, the instances and the
casters that went into it. `span` is the label the view's pass is timed
under, so its GPU time is a lookup in `profiler.gpuFrame()`; every one of
those labels is a variant of `scene.shadow`, which still carries their
total. `camera` carries the same instance counters for the main camera, so
the camera's share of a frame-wide total is a read rather than a measurement
taken by turning every light's shadow off.

A cascade's `near` and `far` are where the split scheme cut its slice, not
the world it covers: the fit takes the bounding sphere of that slice and
rasterizes the ortho box around it, and both reach past `far`. What the
cascade covers is `center` and `radius`, with `viewProj` the exact test;
`coversNear` and `coversFar` read that volume back along one ray, the
camera's view axis. `directional` states the axis reading for the set —
how far it reaches (`coversFar`), the range the splits were run over
(`distance`), how far the camera draws (`cameraFar`), and the
depth past the reach the camera still draws (`uncovered`). A receiver
further along the axis than `coversFar` has no directional depth map over
it and is shaded as if the sun reached it, so `uncovered` is the room a
missing shadow has and a surface standing in that room is what makes one;
`@builtin::systems.proxyOcclusion` occludes past the cascades. The box is
bounded in every direction, so a receiver standing wide of the axis leaves
it at its own distance even where `uncovered` is 0 — `viewProj` is what
answers for that receiver.

The list is rebuilt every frame: a view whose light stopped casting is
absent from the next report rather than standing at the numbers it last had,
and a frame that drew no shadow view answers a report whose `views` is
empty. `views` grouped the way the shadow cache decides — a row per cascade,
per spot atlas layer, per point cube — counts what
`renderer.shadowCacheStats()` reports as `rendered + cached`.

The frame names its views only while something is reading them, so this
call asks the frames after it to name theirs and waits out the first one.
Nil on an engine that renders no frame at all.

**Returns** `ShadowViewReport?` — See `ShadowViewReport`.

```lua
local report = renderer.shadowViews()
print(("camera submitted %d of %d instances"):format(
report.camera.submittedInstances, renderer.drawStats().compactedDrawn))
for _, view in report.views do
print(("%s %d (%s): %d draws, %d instances, %s"):format(
view.role, view.index, view.light, view.draws, view.instances,
view.rendered and "drew" or "cached"))
end
local d = report.directional
if d ~= nil and d.uncovered > 0 then
print(("sun shadows stop at %.0f; the camera draws to %.0f"):format(
d.coversFar, d.cameraFar))
end
```

## globals/renderer/skinnedBatching {#globals-renderer-skinnedbatching}

```lua
renderer.skinnedBatching() -> boolean
```

Whether skinned instances holding one pose draw together.

**Returns** `boolean`

## globals/renderer/skinningPoseHold {#globals-renderer-skinningposehold}

```lua
renderer.skinningPoseHold() -> boolean
```

Whether a pose already written into its slice skips its dispatch.

**Returns** `boolean`

## globals/renderer/skinningStats {#globals-renderer-skinningstats}

```lua
renderer.skinningStats() -> {
```

What the last frame's skinned instances cost. A skinned instance is
posed by a compute pass that writes its vertices into a shared pool, and
instances holding the same pose read one slice of that pool and the single
dispatch that fills it. `instances` is how many were posed, `poses` how
many distinct poses they held, and `dispatches` how many dispatches those
poses cost this frame — so a crowd whose members move together costs what
its poses cost rather than what its head count does, while members at
different animation times each hold their own pose and pay for it.

`held` is how many of the frame's poses cost no dispatch at all. The pass
produces a slice from what the pose is made of, so a slice an earlier frame
filled already holds what running it again would write, and a pose still
wearing that slice is read as it stands. Skinning is paid for by the poses
that CHANGED: a cast standing still reads `dispatches` 0 beside a `held`
equal to its `poses`, and the two add up to `poses` in any frame.

`reusedSlices` is how many of the frame's poses took a slice the pool
already held — one a retired pose gave back, or one a pose nothing has
asked for this frame was holding — rather than one cut from pool the
engine had never used. A scene whose poses keep changing reads a non-zero
count beside a `poolBytes` that stays where it was.

`liveBytes` is what the slices holding this frame's poses occupy, against
`unsharedBytes` — what the same instances would occupy with a slice each.
`poolBytes` is what the pool holds; a previous-position buffer of the same
size rides alongside it so skinned deformation reaches motion vectors.

**Returns** `{ instances: number, poses: number, dispatches: number, held: number, reusedSlices: number, liveBytes: number, unsharedBytes: number, poolBytes: number }`

```lua
local s = renderer.skinningStats()
print(("%d instances over %d poses"):format(s.instances, s.poses))
print(("%d poses dispatched, %d read as they stood"):format(s.dispatches, s.held))
print(("skinned vertex storage: %d B of an unshared %d B"):format(s.liveBytes, s.unsharedBytes))
```

## globals/renderer/splat/components {#globals-renderer-splat-components}

```lua
renderer.splat.components(bytes: any?, convention: string?) -> (SplatComponents?, string?)
```

Decode a Gaussian splat capture — a Niantic `.spz` (gzipped or raw) or a
3DGS `.ply` — into the GPU-ready byte pools a render feature uploads.
`records` is the packed splat array at `recordBytes` per splat (position,
log scale, quaternion, DC colour + opacity); `sh` is the quantized
higher-order spherical-harmonics pool at `shStrideWords` u32 words per
splat, empty at degree 0. A pure decode (no GPU work): upload the pools with
`shaderRef:createBuffer` + `buf:writeBytes` and draw them with a
`kind = "splat"`, `channel = "gaussian"` pass.

**Parameters**

- `bytes` `any` _(optional)_ — Capture bytes — `.spz` or `.ply`, as a `buffer` or a binary string.
- `convention` `string` _(optional)_ — Source axis convention: `"rightDownFront"` (the default, what
COLMAP-trained captures use) or `"engineNative"` for a capture already in
engine space.

**Returns** `(SplatComponents?, string?)` — `{ records, sh, count, shDegree, shStrideWords, recordBytes, boundsMin?, boundsMax?, antialiased, format }`, or (nil, err).

```lua
local c = renderer.splat.components(vfs.readBytes("captures/ceramic.spz"))
```

## globals/renderer/spotShadowBudget {#globals-renderer-spotshadowbudget}

```lua
renderer.spotShadowBudget() -> SpotShadowBudget
```

The spot and area-light shadow atlas now in force. Each shadow-casting
spot is given a tile of it every frame, sized to what the camera can
resolve: a light filling the view gets a whole layer at `resolution`, one
far away gets a `minResolution` tile, and the atlas holds `tiles` of the
smallest kind. That is what lets one budget serve a close hero light and a
street of distant ones without either the memory or the sharpness being set
for the worst case.

**Returns** `SpotShadowBudget` — The atlas — see `SpotShadowBudget`.

```lua
local s = renderer.spotShadowBudget()
print(("%d layers of %d, %.1f MiB"):format(s.layers, s.resolution, s.bytes / 1024 / 1024))
```

## globals/renderer/temporal/held {#globals-renderer-temporal-held}

```lua
renderer.temporal.held() -> boolean
```

Whether a hold is pinning the per-frame clock right now.

**Returns** `boolean` — True while at least one `renderer.temporal.hold` stands.

```lua
if renderer.temporal.held() then print("frame is pinned") end
```

## globals/renderer/temporal/hold {#globals-renderer-temporal-hold}

```lua
renderer.temporal.hold(at: number?, options: TemporalHoldOptions?) -> () -> ()
```

Pin the clock every per-frame effect draws itself against, and return
the release. While the hold stands, `renderer.temporal.now` answers `at`
instead of the running clock, so film grain and every other field redrawn
each frame is redrawn as the same field. Two renders taken under holds at
the same instant therefore agree pixel for pixel wherever the scene itself
has not moved, which is what makes one frame comparable with another.
Holds nest: the innermost names the instant, and the clock runs again once
the last release is called. Each release takes its own hold off the stack
whatever order the releases come in, so two callers holding at once — two
captures in flight together — each end their own hold and the clock runs
again when both have.
`exclusive` takes the clock for the `owner` key the call states: while
that hold stands, a hold is admitted only when it states the same key, and
every other one is refused with an error naming the key and the instant
holding it. That is what lets one caller wind the clock to the second it
means to photograph and keep it there while another agent drives the same
engine. The key is what an owner presents to take a nested hold of its
own, and what `renderer.temporal.release` hands the clock back by. A
capture taken while the hold stands renders at the held instant; a
`deterministic` capture takes a hold of its own that states no key, so it
runs once the clock is handed back.

**Parameters**

- `at` `number` _(optional)_ — The instant to pin the clock at, in seconds. Two holds that state the
same instant produce the same field; the default 0 is that shared instant.
- `options` `TemporalHoldOptions` _(optional)_ — `owner` is the key this hold is taken under, and an exclusive
hold states one. A hold that states no key is labelled with the agent the
call is attributed to, which is the account the caller presented a token
for and is shared by every session driving this engine under it.
`exclusive` takes the clock for the stated key until the hold is released.

**Returns** `() -> ()` — A function that releases this hold. Calling it twice releases once.

```lua
local release = renderer.temporal.hold() ; local png = tools.use("capture", "fromPosition", { position = { 0, 2, 8 } }) ; release()
local release = renderer.temporal.hold(46.0, { exclusive = true, owner = "stage-air" })
```

## globals/renderer/temporal/now {#globals-renderer-temporal-now}

```lua
renderer.temporal.now() -> number
```

The instant a per-frame effect should draw itself at: the innermost
hold's instant while one stands, and seconds since boot otherwise. A
system that redraws a field every frame reads this rather than the running
clock, and a capture asking for a repeatable frame then gets one.

**Returns** `number` — Seconds — pinned while a hold stands, running otherwise.

```lua
local params = { grainTime = renderer.temporal.now() }
```

## globals/renderer/temporal/onChange {#globals-renderer-temporal-onchange}

```lua
renderer.temporal.onChange(listener: (number) -> ()) -> () -> ()
```

Register a listener called with the pinned instant whenever it changes
— a hold taken, a hold released — and return the unsubscribe. A system
whose shader reads the clock out of a GPU buffer registers here, so the
buffer carries the pinned instant before the frame that hold was taken on
is drawn rather than a frame later.

**Parameters**

- `listener` `(number) -> ()` — Called with the instant now in force, in seconds.

**Returns** `() -> ()` — A function that removes this listener.

```lua
local stop = renderer.temporal.onChange(function(t) pushClock(t) end)
```

## globals/renderer/temporal/owner {#globals-renderer-temporal-owner}

```lua
renderer.temporal.owner() -> { id: string?, name: string?, at: number, exclusive: boolean }?
```

The hold naming the instant the clock answers right now: who took it,
what instant it pinned, and whether it took the clock exclusively. Several
agents drive one engine at once and a hold any of them takes moves the
clock every registered field is redrawn against, so this is how a caller
sees that another agent holds it before its own instant is quietly
replaced — and, when `exclusive` is true, `id` is the key a hold of its
own states to be admitted, and the key `renderer.temporal.release` hands
the clock back by. `id` and `name` are nil for a hold that stated no key
and that the engine attributes to no agent.

**Returns** `{ id: string?, name: string?, at: number, exclusive: boolean }?` — `{ id, name, at, exclusive }` for the standing hold, or nil when the clock is running.

```lua
local who = renderer.temporal.owner()
if who ~= nil and who.exclusive then print(who.name, "holds the clock at", who.at) end
```

## globals/renderer/temporal/release {#globals-renderer-temporal-release}

```lua
renderer.temporal.release(owner: string) -> number
```

Hand the clock back by the key its holds were taken under, and report
how many came off. A hold stands until its release is called, and the
release is a closure the call that took the hold holds: a caller that
takes a hold in one call and comes back in another, and a task that ends
between the two, both leave the clock pinned with nobody holding a release
for it. Naming the key is how the clock runs again, and how a caller
refused by an exclusive hold takes one over.

**Parameters**

- `owner` `string` — The key the holds to release were taken under — what `owner`
stated when they were taken, which `renderer.temporal.owner` reports.

**Returns** `number` — How many holds came off the stack.

```lua
renderer.temporal.release("stage-air")
```

## globals/renderer/texture/capture {#globals-renderer-texture-capture}

```lua
renderer.texture.capture(texture: string | { [string]: any } | AssetRef) -> string
```

Request a CPU readback of the GPU texture `texture` names (e.g. a
camera's rendered output). Returns a result key to pass to a
TextureCpuHandle's `:encode()` once the readback completes. Takes every form
that names a texture — the `TextureHandle` `create` returned, the guid
`renderer.texture.list` hands out, a `TextureCpuHandle` or a texture
`AssetRef`.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to read back — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.

**Returns** string The capture result key.

```lua
local key = renderer.texture.capture(cameraTarget)
```

## globals/renderer/texture/cpuCreate {#globals-renderer-texture-cpucreate}

```lua
renderer.texture.cpuCreate(width: number, height: number, fill: any?) -> TextureCpuHandle
```

Allocate a blank CPU image (RGBA8) filled with a solid colour and return a
`TextureCpuHandle`. Compose into it with `canvas:blit(src, x, y, w, h)`, then
`canvas:encodeJpeg()` / `:encodePng()` for the bytes; `:unload()` drops it.

**Parameters**

- `width` `number` — number Canvas width in pixels.
- `height` `number` — number Canvas height in pixels.
- `fill` `any` _(optional)_ — Optional `{ r, g, b, a }` (0-255) solid fill; defaults to opaque white.

**Returns** `TextureCpuHandle`

```lua
local c = renderer.texture.cpuCreate(1024, 576, { 255, 255, 255, 255 })
```

## globals/renderer/texture/cpuFromBytes {#globals-renderer-texture-cpufrombytes}

```lua
renderer.texture.cpuFromBytes(bytes: buffer | string, encodeOpts: any?) -> TextureCpuHandle
```

Load engine-native ZTEX bytes — or an encoded image (png / jpg / webp)
— into the CPU store under a fresh guid and answer the CPU handle, for
pixels that come from somewhere other than a texture asset: a `data.ztex`
read as a file, a payload held in memory. The pixels stay at the format
they were encoded in. DEFAULT: `handle:unload()` once done with them.

**Parameters**

- `bytes` `buffer | string` — The ZTEX or image bytes.
- `encodeOpts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }` applied
when the bytes are an encoded image and need the engine-native encode.

**Returns** `TextureCpuHandle`

```lua
local cpu = renderer.texture.cpuFromBytes(vfs.read(path)); local png = cpu:encodePng(); cpu:unload()
```

## globals/renderer/texture/create {#globals-renderer-texture-create}

```lua
renderer.texture.create(src: any?, guid: string?) -> TextureHandle
```

Create (or fetch) a GPU texture resource and return its `TextureHandle`.
`src`: a `TextureCpuHandle` from `texRef:load()` (CPU→GPU under the asset's
guid, idempotent); raw pixels `{rgba, width, height, srgb?, format?}` (a
flat width*height*4 byte payload, 0-255, row-major, top-to-bottom, RGBA —
a `buffer`, a binary string, or a number array; `format = "rgba16f"`
uploads an HDR texture instead, where `rgba` carries float channel
values); a `TextureHandle` (returned as-is);
or render-target dimensions `{width, height, name?, format?}` with no pixel
source — an empty GPU texture a render pass writes into (camera output,
UI surface) and that samples like any other texture. `format` names the
colour format the target is allocated in, and the passes drawing into it
are built for that format: `"rgba8unorm"` / `"bgra8unorm"` (the two
eight-bit channel orders, either of which a surface may carry),
`"rgba16f"` / `"rgba32f"`, `"rg16f"` / `"rg32f"`, `"r16f"` / `"r32f"`.
Each also answers to its spelled-out width (`"rgba16float"`, `"r32float"`,
and so on), in any case. Omit it to take the surface's own. A float format
carries what eight bits quantize — positions, velocities, HDR. Any other
`format` raises an error naming every name that works, so a target is
allocated in the format it was asked for or not at all. A render target
takes `filter` the way raw pixels do: `"nearest"` keeps its own pixels square
wherever something draws it larger than it is — a viewport widget, a
magnified capture — which is what an image whose pixels ARE the subject
needs, since a 64x32 panel holds no detail between its pixels to
interpolate; `"linear"` (the default) smooths between them. It also
takes `screen` (the engine keeps it the size of the image being drawn),
`screenScale` (the fraction of that size it takes) and `screenSpace`
(`"scene"`, the default, or `"composite"` — the image the post-scene
phases draw into, which is the display's own resolution while the renderer
presents the viewport itself and the scene's size while a UI viewport panel
owns the presentation). A scene-space target is resized for every render
target drawn and cleared before an offscreen one; a composite-space target
follows the presented frame alone, which is what lets a pass keep an
accumulation in it. One scene-space `screen` target is therefore one
resource every render target draws through in turn, so its guid holds the
last one's image at the last one's size, and a value read back from it
belongs to whichever render target was drawn last. A reading that has to
be the viewport's own comes from `screenSpace = "composite"`, or from a
target created without `screen`. NEVER takes an AssetRef — load the CPU
first.

**Parameters**

- `src` `any` _(optional)_ — A TextureCpuHandle, raw pixels, a TextureHandle, or render-target dimensions.
- `guid` `string` _(optional)_ — Optional v4 guid for a NEW runtime texture — the asset identity the
texture is filed under, which a material's texture slot resolves through.
Minted when absent. Ignored for the CPU-handle and render-target paths.

**Returns** `TextureHandle`

```lua
local gpu = renderer.texture.create(texRef:load())
local gpu = renderer.texture.create({ rgba = pixels, width = 16, height = 16 })
local px = buffer.create(16 * 16 * 4); local gpu = renderer.texture.create({ rgba = px, width = 16, height = 16 })
local rt = renderer.texture.create({ width = 512, height = 256, name = "panel_rt" })
local hdr = renderer.texture.create({ width = 512, height = 256, name = "cam_rt", format = "rgba16f" })
local led = renderer.texture.create({ width = 64, height = 32, name = "panel", filter = "nearest" })
```

## globals/renderer/texture/createFromAsset {#globals-renderer-texture-createfromasset}

```lua
renderer.texture.createFromAsset(ref: string | AssetRef, encodeOpts: any?, keepCpu: boolean?) -> TextureHandle
```

Put a `.texture` asset on the GPU under its own guid and answer its
handle at once. The asset's bytes are decoded off the frame and the
texture lands on the device when the decode finishes, a frame or more
later: a material naming the guid draws the shader's default for that
slot until then and rebinds when it arrives, and
`renderer.texture.isResident` reports the arrival. The decoded pixels are
dropped once uploaded unless `keepCpu` holds them in the CPU store for
`textureRef:load()`-style reads. An asset the device already holds is
answered from the shape the device reports, without reading the asset's
bytes and without a second decode.

**Parameters**

- `ref` `string | AssetRef` — A texture `AssetRef`, or a string naming one (guid, identity, name
or source path).
- `encodeOpts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension?, filter? }`
applied when the primary is an encoded source image and needs the
engine-native encode (a `.ztex` primary is decoded as-is).
- `keepCpu` `boolean` _(optional)_ — Keep the decoded pixels in the CPU store after the upload.

**Returns** `TextureHandle`

```lua
local h = renderer.texture.createFromAsset(texRef) -- bind h.guid on a material; it draws once resident
```

## globals/renderer/texture/decode {#globals-renderer-texture-decode}

```lua
renderer.texture.decode(bytes: buffer | string) -> (any, any, any, any)
```

Decode a texture payload to its pixel buffer. Takes the two shapes the
renderer's own texture loader takes, told apart by their leading bytes:

* an engine-native `ZTEX` payload — handed back at the texel format the
payload was written in, so a height field read back here keeps every bit
it was authored with. A `ZTEX` holding block-compressed or verbatim
source-image levels decodes to `"rgba8"`.
* source image bytes — png, jpeg, gif or webp, straight off disk or out of
a `capture` — decoded to `"rgba8"` at whatever colour type, bit depth or
interlacing the file was written with. This is the call that reads the
pixels of a screenshot.

The fourth return names the format the buffer came back in: `"rgba8"` (4
bytes/texel, channels 0-255), `"rgba16"` (8 bytes/texel, 16-bit unsigned
normalized channels 0-65535) or `"rgba32f"` (16 bytes/texel, float
channels).

**Parameters**

- `bytes` `buffer | string` — A `ZTEX` payload or source image bytes.

**Returns** `(any, any, any, any)` — `(string?, number?, number?, string?)` pixels, width, height, format — or (nil, errmsg) where errmsg is in the 2nd slot.

```lua
local pixels, w, h = renderer.texture.decode(vfs.read("/source/tmp/shot.png"))
```

## globals/renderer/texture/destroy {#globals-renderer-texture-destroy}

```lua
renderer.texture.destroy(texture: string | { [string]: any } | AssetRef) -> boolean
```

Release the GPU texture `texture` names. For an empty render-into texture
(camera output, UI surface) this also frees its render scratch; for an
uploaded runtime texture it drops the GPU resource (and any CPU shadow).
After this, `renderer.texture.list` stops answering for the guid. Takes
every form that names a texture — the `TextureHandle` `create` returned, the
guid the listing hands out, a `TextureCpuHandle` or a texture `AssetRef`.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to release — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.

**Returns** `boolean` true when a texture was known under the guid.

```lua
renderer.texture.destroy(rt)
renderer.texture.destroy(renderer.texture.list()[1].guid)
```

## globals/renderer/texture/encode {#globals-renderer-texture-encode}

```lua
renderer.texture.encode(rgba: any?, width: number, height: number, opts: any?) -> (string?, string?)
```

Encode raw pixels into an engine-native `ZTEX` payload (the on-disk
texture content). The CPU codec behind the texture assetType's `onCreate`.
`opts.format` selects the on-disk precision: `"rgba8"` / `"srgb"` (default,
8 bits/channel, `rgba` is width*height*4 bytes) or the high-precision data
formats `"rgba16"` (16-bit unsigned normalized, width*height*8 bytes) /
`"rgba32f"` (32-bit float, width*height*16 bytes) — for height/displacement
fields, baked lightmaps, and other data rasters an 8-bit format quantizes
visibly. The two high-precision formats store `rgba` verbatim and reject
`opts.generateMipmaps` / `opts.maxDimension`.

**Parameters**

- `rgba` `any` _(optional)_ — Pixel payload at `opts.format`'s native byte width — a `buffer`, a binary string, or a number array.
- `width` `number` — number
- `height` `number` — number
- `opts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }`

**Returns** `(string?, string?)` ZTEX bytes, or (nil, errmsg).

## globals/renderer/texture/encodeFromImage {#globals-renderer-texture-encodefromimage}

```lua
renderer.texture.encodeFromImage(bytes: buffer | string, opts: any?) -> (string?, string?)
```

Encode source image bytes (png/jpg/webp/…) into an engine-native `ZTEX`
payload. Used by the texture importer / assetType `onChange`.

**Parameters**

- `bytes` `buffer | string` — source image bytes.
- `opts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }`

**Returns** `(string?, string?)` ZTEX bytes, or (nil, errmsg).

## globals/renderer/texture/frameSchedule {#globals-renderer-texture-frameschedule}

```lua
renderer.texture.frameSchedule(texture: string | AssetRef) -> { number }?
```

The times at which each layer of a timed texture stops being shown,
in seconds from the start of the sequence — the running total of the layer
display times, so the last entry is the length of one pass.

This is the form a sampler reads a sequence through: a time is turned into
a layer by finding the first entry it has not passed, whatever the
individual layer times are. It is what the `schedule` slot of the builtin
`animatedTexture` shader holds, one entry per layer.

A texture whose layers carry no timing — a still image, a sprite sheet, a
LUT stack — has no schedule and answers nil.

**Parameters**

- `texture` `string | AssetRef` — The texture — a guid, an identity, a name, a path, or a texture `AssetRef`.

**Returns** `{ number }?` one cumulative end time per layer, or nil.

```lua
local ends = renderer.texture.frameSchedule(texRef) -- {0.1, 0.15, 0.35}
```

## globals/renderer/texture/info {#globals-renderer-texture-info}

```lua
renderer.texture.info(ztex: buffer | string) -> (any, any)
```

Read the header of an engine-native `ZTEX` payload without copying the
pixels. Returns its format, dimensions, mip count, `filter` ("nearest"
or "linear" — the sampler baked into the blob from the asset's
`settings.filter`), and the payload's layer shape.

`layers` counts the array layers the payload carries and `isArray` is true
past one — the answer to "am I about to sample a `texture_2d_array`?",
available before anything samples it. `animated` is true when those layers
are a sequence in time; then `frameDelaysMs` lists each layer's display
time in milliseconds in display order, and `durationMs` totals one pass.
An animated image imports as one layer per frame, so `layers` is its frame
count. A still texture reports `layers = 1`, `isArray = false`.

**Parameters**

- `ztex` `buffer | string` — ZTEX bytes.

**Returns** `(any, any)` — `(table?, string?)` `{ format, width, height, mipCount, filter, layers, isArray, animated, frameDelaysMs?, durationMs? }`, or (nil, errmsg).

```lua
local i = renderer.texture.info(bytes); if i.animated then print(i.layers, "frames", i.durationMs, "ms") end
```

## globals/renderer/texture/isResident {#globals-renderer-texture-isresident}

```lua
renderer.texture.isResident(texture: string | { [string]: any } | AssetRef) -> boolean
```

True if a GPU texture is resident under this texture's guid.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture — a `TextureHandle`, a `TextureCpuHandle`, a guid, or a texture `AssetRef`.

**Returns** `boolean`

```lua
print(renderer.texture.isResident(handle))
```

## globals/renderer/texture/list {#globals-renderer-texture-list}

```lua
renderer.texture.list() -> { any }
```

Every texture currently registered, ordered by guid — the ones a script
created and the ones that reached the device through an asset alike. Each
entry carries the guid, where it came from (`origin` is `"asset"` for a
texture the asset path uploaded), and whether the GPU still holds it. A
resident entry also carries the bytes it costs, its dimensions and its
texel format, so the listing sums to `renderer.textureMemory()`. A
streamable one carries `streamOrigin` — `"asset"` when a level change reads
the levels it needs back from the asset, `"retained"` when the cache holds
the pixels for it.
A script-created entry also carries `held` — whether `renderer.hold` pins
it for the session — and `scene`, the load that created it.
`renderer.references("texture", guid)` says what is still holding a row,
and `renderer.collect()` releases the rows nothing holds.

**Returns** `{ any }` — Array of `{ guid, origin, owner?, scene?, held?, resident, bytes?, width?, height?, format?, compressed?, streamable?, streamOrigin? }`.

```lua
for _, t in ipairs(renderer.texture.list()) do print(t.guid, t.bytes) end
```

## globals/renderer/texture/loadCpu {#globals-renderer-texture-loadcpu}

```lua
renderer.texture.loadCpu(ref: string | AssetRef, encodeOpts: any?) -> TextureCpuHandle
```

Load a `.texture` asset's pixels into the ONE guid-keyed CPU store (the
Disk→CPU step) and return a CPU handle for per-pixel access (no GPU
readback). The handle holds NO pixels — only the guid, dims and texel
format plus the read/write/encode/unload ops (which read the Rust store).
The pixels stay at the format they were authored in: `handle.format` is
`"rgba8"`, `"rgba16"` or `"rgba32f"`, and `:readPixel` reports channels in
that format's own units. Called by `texRef:load()`. DEFAULT: upload to the
GPU then `handle:unload()`.

**Parameters**

- `ref` `string | AssetRef` — A texture `AssetRef` (carries `.guid`, reads its primary via getBytes),
or any string `asset.ref` resolves to one — a guid, an identity, a name or a
source path.
- `encodeOpts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }` applied
when the primary is an encoded source image and needs the engine-native
encode (a `.ztex` primary is loaded as-is).

**Returns** `TextureCpuHandle`

```lua
local cpu = texRef:load(); local r,g,b,a = cpu:readPixel(3, 4); cpu:unload()
```

## globals/renderer/texture/readback {#globals-renderer-texture-readback}

```lua
renderer.texture.readback(texture: string | { [string]: any } | AssetRef) -> TextureCpuHandle
```

Read a runtime GPU texture's pixels back to CPU and return a
`TextureCpuHandle` for them — the GPU→CPU half of the runtime-texture freeze
path. A texture made with `renderer.texture.create` keeps no CPU copy, so
persisting it (`:encode()` → `asset.create("texture", …)`) reads it back
here first. Yields until the readback completes (a frame or two). After it
returns the pixels are resident in the guid-keyed CPU store: `:readPixel`,
`:writePixel`, `:getInfo`, `:encode`, `:unload` all work. Errors if the
texture never becomes GPU-resident.

A SCENE-space `screen`-sized render target is one resource shared by every
render target drawn — the viewport, an offscreen capture, a camera
rendering into a texture — resized and re-derived for each of them in
turn. The copy is taken ahead of all of them for the frame, so what a
readback of its guid answers is the content of the last frame the renderer
drew: the presented view's own image at the presented resolution, since
the presented view is the sink that draws last. A request made while the
renderer is holding frames back is carried to the next frame it draws
rather than being answered from a target another sink left standing, so a
readback can wait a frame longer than the copy itself takes.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture — the `TextureHandle` `renderer.texture.create` returned, a guid, or a texture `AssetRef`.

**Returns** `TextureCpuHandle`

```lua
local cpu = renderer.texture.readback(handle); local ztex = cpu:encode(); cpu:unload()
```

## globals/renderer/texture/tone {#globals-renderer-texture-tone}

```lua
renderer.texture.tone(histogram: any?) -> TextureTone
```

Reduce a histogram to what the picture's tone IS: where its darkest and
brightest pixels sit, where the body of it sits, and how much of it is
standing on the floor or the ceiling — all in code values on the 0-255
scale the pixels were delivered at.

`span` (`max - min`) is the whole range including a single stray pixel;
`spread` (`p95 - p5`) is the range the body of the picture occupies, which
is the reading that says whether a shot is legible. A frame whose subject is
modelled and shaded but delivered inside a few code values reads a large
`mean` and a tiny `spread`, and no mean alone can tell that apart from a
frame with a subject in it.

`crushed` and `clipped` are the shares of the picture at code 0 and at code
255, each 0..1 — what a shot loses to the floor and to the ceiling.

**Parameters**

- `histogram` `any` _(optional)_ — A histogram from `cpu:histogram()`.

**Returns** `TextureTone`

```lua
local t = cpu:tone(); if t.spread < 24 then error("the shot is flat") end
```

## globals/renderer/texture/update {#globals-renderer-texture-update}

```lua
renderer.texture.update(texture: string | { [string]: any } | AssetRef, src: any?) -> TextureHandle
```

Overwrite the GPU texture `texture` names IN PLACE, under the same guid,
from new raw pixels. Never writes a `.texture` file — the play-mode mutate
path. Takes every form that names a texture — the `TextureHandle` `create`
returned, the guid `renderer.texture.list` hands out, a `TextureCpuHandle`
or a texture `AssetRef`. Returns a handle carrying the new dimensions: the
handle it was given, refreshed, and a handle over the guid otherwise.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to update — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.
- `src` `any` _(optional)_ — New raw pixels `{rgba, width, height, srgb?, format?}` — `rgba` as a
`buffer`, a binary string, or a number array.

**Returns** `TextureHandle` — A `TextureHandle` for the updated texture.

## globals/renderer/textureMemory {#globals-renderer-texturememory}

```lua
renderer.textureMemory() -> {
```

What the GPU texture cache holds, split by whether the texture is
block-compressed. `compressedBytes` and `uncompressedBytes` are what those
textures cost in VRAM, measured from each texture's own format and mip
chain — so a `.texture` whose settings name `format = "bc7"` appears in the
compressed columns at a quarter of what the same image costs as RGBA8.
`blockCompressionSupported` is whether this adapter can hold
block-compressed textures at all; where it is false a BC7 payload is
uploaded decoded and lands in the uncompressed columns instead, so the
texture is present everywhere and compressed where the hardware allows it.
Measured at the end of the last rendered frame.
`streamableTextures` is how many of them a texture budget can move the
base mip level of, split by where a level change reads the levels it needs
from: `assetStreamedTextures` are read back from the asset they came from
and hold nothing in system memory, `retainedTextures` hold the payload
because a script uploaded their pixels and the GPU copy is the only other
one there is. `streamSourceBytes` is what those held payloads occupy in
system memory — bytes that are not VRAM — so it is a reading on the
retained half alone. `pinnedTextures` counts the textures big enough to
stream that stand at a level nothing can move: their pixels were released
and no asset holds them, the asset behind them could not be read back, or a
UI image, a post-process property or a render feature holds a view of them.
A texture out of the streamable set only because no measured surface wears
it stands in neither count: a surface reaching it takes it back up, so its
level moves again as soon as there is a footprint to move it by. It reads 0
while no budget is armed.

**Returns** `{ blockCompressionSupported, compressedTextures, compressedBytes, uncompressedTextures, uncompressedBytes, streamableTextures, assetStreamedTextures, retainedTextures, pinnedTextures, streamSourceBytes }`

```lua
local tm = renderer.textureMemory()
print(("%d compressed textures hold %.1f MiB"):format(tm.compressedTextures, tm.compressedBytes / 1048576))
print(("%d textures stream from their asset, %d hold %.1f MiB of pixels"):format(
tm.assetStreamedTextures, tm.retainedTextures, tm.streamSourceBytes / 1048576))
```

## globals/renderer/textureStreaming {#globals-renderer-texturestreaming}

```lua
renderer.textureStreaming() -> TextureStreaming
```

What the last frame's texture-residency plan decided. `budgetBytes` is
the armed budget, and `0` means residency is left alone. `streamable` is
how many textures the plan can move. `residentBytes` is what those textures
occupy now, measured from the textures that are allocated; `demandedBytes`
is what the frame's demand alone would have cost, so the two part exactly
where the budget is doing something. `starved` counts the textures left
coarser than the frame asked for, `promoted` the ones that climbed a level
this frame, and `changed` the ones whose GPU texture was replaced. A camera
approaching a surface reads `promoted` above zero for a few frames and then
zero once it settles.

`textures` is one row per streamable texture, ordered by key, carrying the
level each one was asked for and the measurement that asked. Two byte
totals can agree while a single texture sits several levels off what its
surface samples, so read the row when the question is which level a texture
holds and why.

With `budgetBytes` at 0 nothing holds a level back, so `residentBytes`,
`plannedBytes` and `demandedBytes` all read the whole chain of every
texture still enrolled and `textures` is empty — which is how a session
that armed a budget and dropped it reads back that the levels came home.

**Returns** `TextureStreaming` — `{ budgetBytes, streamable, residentBytes, plannedBytes, demandedBytes, starved, promoted, changed, textures }`

```lua
local ts = renderer.textureStreaming()
print(("textures: %.1f MiB resident of %.1f MiB demanded, %d starved"):format(
ts.residentBytes / 1048576, ts.demandedBytes / 1048576, ts.starved))
```

## globals/renderer/transmissionShadows {#globals-renderer-transmissionshadows}

```lua
renderer.transmissionShadows() -> boolean
```

Whether translucent casters tint the directional light they block.

**Returns** `boolean`

## globals/renderer/uploadStats {#globals-renderer-uploadstats}

```lua
renderer.uploadStats() -> {
```

What the last completed frame spent re-describing its renderables to the
GPU. Every renderable owns a slot in the per-instance data a draw reads —
its world matrix, the bounds the culler tests it by, and the flags that
decide which passes and which culling stages see it — and a frame uploads
only the slots whose contents changed. `bytes` is what those uploads
carried, `fullBytes` what re-sending every slot would have cost, and
`writes` how many buffer writes carried it. The three numbers cover that
per-renderable data alone, so a scene standing still reads `bytes = 0`
against a `fullBytes` that grows with the scene, and the ratio says how much
of it the scene's own churn — rather than its size — is paying for.

**Returns** `{ writes: number, bytes: number, fullBytes: number }`

```lua
local u = renderer.uploadStats()
print(("instance upload: %d B of %d B in %d writes"):format(u.bytes, u.fullBytes, u.writes))
```

## globals/renderer/variantSource {#globals-renderer-variantsource}

```lua
renderer.variantSource(program: string) -> string?
```

The WGSL one of the programs `renderer.shaderVariants()` lists holds,
exactly as the shader compiler received it. `program` is the `program`
field of a row's `base` or of one of its `variants`. Reading a base
alongside a variant shows what a feature set selected: each program's text
holds the code its own features guard. The variant-report spelling of
`renderer.compiledSource`, which answers the same for every other shader.

**Parameters**

- `program` `string` — A `program` name from `renderer.shaderVariants()`.

**Returns** `string?` — The compiled WGSL, or nil for a name no compile has run under.

```lua
local row = renderer.shaderVariants()[1]
local base = renderer.variantSource(row.base.program)
```

## modules/renderer/README {#modules-renderer-readme}

```lua
require("@builtin/modules/api/engine/renderer") -- renderer (also available as global 'renderer')
```

GPU-resource factory + CPU codec/store wrappers — the GPU/CPU half of the asset↔resource split. This module (under `modules/api/`) is the SOLE caller of the internal `__mesh` / `__meshcpu` / `__meshgpu` / `__splat` / `__texture` / `__texturecpu` / `__texturegpu` / `__instancedata` FFI; assetType behaviours, components, and every other module call `renderer.*`, never the `__` internals.

Usage: local renderer = require("@builtin/modules/api/engine/renderer")
Also available as global: renderer

## modules/renderer/anisotropy {#modules-renderer-anisotropy}

```lua
anisotropy(): number
```

The maximum anisotropy material textures are sampled with right now —
the requested level clamped to what this device honours.

```lua
if renderer.anisotropy() < 4 then ... end
```

## modules/renderer/atmospherics.held {#held}

```lua
atmospherics.held(): boolean
```

Whether a hold is standing on the air right now.

```lua
if renderer.atmospherics.held() then print("clear air") end
```

## modules/renderer/atmospherics.hold {#hold}

```lua
atmospherics.hold(share: number?): () -> ()
```

Hold the air between the camera and every surface at a stated share of
what the scene authored, and return the release. At the default 0 the
media contribute nothing and a surface renders in its own colour, which is
what lets a reader judge an albedo, a tint or a material while another
slice of a shared world drives the weather. The share reaches aerial
perspective, height fog and volumetric light scattering; the sky, the sun
and the light they put on a surface are untouched, because those are what
the surface's colour is made of. Holds nest: the innermost names the
share, and the authored air is back once the last release is called. Each
release ends its own hold whatever order the releases come in, so two
callers holding at once each end their own.

**Parameters**

- `share` `number?` _(optional)_ — How much of the authored air reaches the image, in [0, 1].
Defaults to 0 — no air at all.

**Returns** `()` — A function that releases this hold. Calling it twice releases once.

```lua
local release = renderer.atmospherics.hold() ; local png = tools.use("capture", "fromPosition", { position = { 0, 2, 8 } }) ; release()
```

## modules/renderer/atmospherics.onChange {#onchange}

```lua
atmospherics.onChange(listener: (number) -> ()): () -> ()
```

Register a listener called with the share now in force whenever it
changes — a hold taken, a hold released — and return the unsubscribe. A
system that packs a medium into a GPU buffer registers here and re-packs
what it has already pushed, so the buffer carries the share before the
frame the hold was taken on is drawn rather than a frame later.

**Parameters**

- `listener` `(number) -> ()` — Called with the share now in force, in [0, 1].

**Returns** `()` — A function that removes this listener.

```lua
local stop = renderer.atmospherics.onChange(function(share) pushParams() end)
```

## modules/renderer/atmospherics.share {#share}

```lua
atmospherics.share(): number
```

The share of the authored air that reaches the image: the innermost
hold's share while one stands, and 1 otherwise. A system that packs a
medium multiplies its extinction — `aerial`, a fog `density` — by this,
and a hold then reaches that medium however it is being driven.

```lua
local density = state.density * renderer.atmospherics.share()
```

## modules/renderer/blendedBatching {#modules-renderer-blendedbatching}

```lua
blendedBatching(): boolean
```

Whether blended neighbours sharing a draw key draw together.

## modules/renderer/bounds.clear {#clear}

```lua
bounds.clear(id: string): boolean
```

Withdraw the box an entity published, so it stops contributing to the
entity's reported extent.

**Parameters**

- `id` `string` — Entity id.

```lua
renderer.bounds.clear(id)
```

## modules/renderer/bounds.set {#set}

```lua
bounds.set(id: string, min: any, max: any): boolean
```

Publish the local-space box an entity's content-drawn geometry occupies.
`entity:bounds()` and `entity:hierarchyBounds()` union it with whatever
mesh geometry the entity has, each carried out of its own local space, so
framing a camera on the entity frames what a feature actually draws.

## modules/renderer/captureView.channelId {#channelid}

```lua
captureView.channelId(name: string): number?
```

The debug channel a registered view draws on — what a feature passes as
its pass `debugChannel`. Nil when no view is registered under `name`.

**Parameters**

- `name` `string` — The view name.

```lua
ctx.enqueue { ..., debugChannel = renderer.captureView.channelId("lightmap") }
```

## modules/renderer/captureView.list {#list}

```lua
captureView.list(): { any }
```

Every registered capture view as `{ name, channel, description }` records
— what backs the discoverability of `capture pass=<name>` and the
unknown-view error's suggestion list.

```lua
for _, v in ipairs(renderer.captureView.list()) do ... end
```

## modules/renderer/captureView.ready {#ready}

```lua
captureView.ready(name: string): boolean
```

Whether a registered view can draw yet. A view's passes are enqueued
from the moment its render feature first runs, but they are skipped while
the materials they name have no pipeline — their shader is still compiling —
so for the first frames of a session a camera bound to the view renders the
ORDINARY view into its target, and the image gives no sign of it. This
reports the difference, and reports it before any camera is on the view, so
it is answerable for the first camera bound to one. False for an
unregistered name.

**Parameters**

- `name` `string` — The view name.

```lua
repeat task.wait() until renderer.captureView.ready("zfighting")
```

## modules/renderer/captureView.register {#register}

```lua
captureView.register(name: string, config: any): number
```

Register (or update) a content capture view under `name` and return the
debug CHANNEL number assigned to it. A render feature gates its pass to this
channel (`debugChannel = channel`) so the pass draws only when a capture
selects the view. Idempotent: re-registering the same name keeps its channel.

**Parameters**

- `name` `string` — The view name, selected via `capture pass=<name>`.
- `config` `any` _(optional)_ — `{ description?, ensure?, warmup?, renderLayers? }`. `ensure` is
called before a capture of this view so the feature that draws it is live
(e.g. create it on demand). `warmup` is how many present frames a capture
lets the view accumulate before it reads — set it when the feature retains
prior-frame state (a temporal diff) so the first capture reads a warm
result. `renderLayers` is the layer spec a capture of this view uses when
the caller named none — a view that draws its own geometry and wants the
scene's kept out of the frame (and out of the depth buffer it tests
against) names only its own layer.

```lua
local ch = renderer.captureView.register("lightmap", { description = "...", ensure = fn })
```

## modules/renderer/captureView.resolve {#resolve}

```lua
captureView.resolve(name: string): any
```

Resolve a capture view by name to its `{ channel, ensure, description,
warmup }` record, or nil when no view is registered under `name`.

**Parameters**

- `name` `string` — The view name.

```lua
local v = renderer.captureView.resolve("lightmap")
```

## modules/renderer/captureView.unregister {#unregister}

```lua
captureView.unregister(name: string): boolean
```

Withdraw a capture view. A subsequent `capture pass=<name>` no longer
resolves to it (falls through to the unknown-view error).

**Parameters**

- `name` `string` — The view name.

```lua
renderer.captureView.unregister("lightmap")
```

## modules/renderer/clearShadowHero {#modules-renderer-clearshadowhero}

```lua
clearShadowHero(): boolean
```

Release the hero caster, so the directional shadow is the cascades'
alone again and the layer the hero view rendered into is given back.

```lua
renderer.clearShadowHero()
```

## modules/renderer/clearShadowProxy {#modules-renderer-clearshadowproxy}

```lua
clearShadowProxy(mesh: string?): number
```

Stop proxying `mesh`, so it rasterizes its own geometry into shadow
views again. Called with no argument, drops every registration.

**Parameters**

- `mesh` `string?` _(optional)_ — The mesh to stop proxying. Omit to clear all of them.

```lua
renderer.clearShadowProxy(statueMesh)
print(renderer.clearShadowProxy(), "proxies dropped")
```

## modules/renderer/collect {#modules-renderer-collect}

```lua
collect(): RuntimeCollection
```

Release every runtime texture, material, mesh and render feature nothing
holds: no handle a script still reaches, no live owner, no reference from
live engine state, no asset backing it, no hold. A root scene load runs this
once the new scene stands, so what the previous scene's content created and
nothing still wears goes with that scene; calling it directly collects at
any other moment. A session material's handle counts as reached while the
entity it was keyed for stands, and stops counting once that entity is
gone.
It reaches the GPU textures the device holds beside the registry's own: a
texture the cache loaded for an asset goes once nothing live names it and
is read back from that asset the next time something asks for it, while one
no asset answers for stays, there being nothing to read it back from — a
render pass's own target, a colour swatch, an atlas the engine built. A
texture the ASSET path uploaded and whose asset has since been removed has
nothing to come back from either, and the collection decides about it from
its holders the way it does about every other resource: a handle a script
still reaches, a live owner, a reference from live engine state, a hold.
Features go first, then materials, then meshes, then textures, so a texture
only a released material named goes with the material. Runs a full garbage
collection first, so a handle nothing reaches counts as let go, and yields
for the frame the census runs on. A handle the calling function still has
in a variable — or in a temporary it has not overwritten — is one a script
reaches, so a resource created in the function that collects is let go by
the next collection rather than this one.

```lua
local c = renderer.collect() print(c.released.texture, c.kept)
```

## modules/renderer/compiledShaders {#modules-renderer-compiledshaders}

```lua
compiledShaders(): { string }
```

Every name `renderer.compiledSource` answers for — one per name a
shader compile has run under this session, whether it succeeded or failed.
What makes the composed-source surface enumerable rather than something to
guess a key for.

```lua
for _, name in renderer.compiledShaders() do print(name) end
```

## modules/renderer/compiledSource {#modules-renderer-compiledsource}

```lua
compiledSource(shader: string): string?
```

The WGSL the shader compiler received under one name, exactly as it
received it — the composed module, which is what a compile error's line
numbers and handle indices are positions in. Answers under any name a
compile ran under (identity, guid, alias, or a `program` from
`renderer.shaderVariants()`), for a shader that declares no features, and
for a shader whose compile FAILED, which is the case it exists for: a
message about a function body carries a position and nothing else, and the
text that position is in is this. The failed text stands for as long as
`shaderRef:compileStatus()` reports that failure under the same name.

**Parameters**

- `shader` `string` — Any name a shader compiled under — identity, guid, alias, or a
`shaderVariants()` program name.

```lua
local status = asset.resolve("myShader", "shader"):compileStatus()
if status.status == "failed" then
print(status.error)
print(renderer.compiledSource("myShader"))
end
```

## modules/renderer/compositeSize {#modules-renderer-compositesize}

```lua
compositeSize(): { width: number, height: number }
```

The size of the image the post-scene phases worked on in the last
presented frame — the target the UI composites onto, which every pass
after the scene reads as `@scene.color` and writes into, and which a
`screenSpace = "composite"` render target follows. While the renderer
presents the viewport itself that is the display's own size, whatever
fraction of it the scene rasterized at; while a UI viewport panel owns
the presentation it is the size the scene rasterized at, since the panel
draws the scene target at its own rect and nothing upscales before the
composite. Both read `0` before a frame has drawn.

```lua
local c = renderer.compositeSize()
```

## modules/renderer/cullStats {#modules-renderer-cullstats}

```lua
cullStats(): {
```

What the last completed frame decided to draw. `total` renderables went
into the frustum test, `culled` fell outside it and `visible` survived. Of
those, occlusion culling measured `occlusionTested` against the depth
pyramid and proved `occlusionCulled` were entirely behind other geometry —
both 0 while `renderer.occlusionCulling()` is false. A renderable the
pyramid has no say over — one that laid no depth in the pre-pass, one whose
bounds were never recorded, one straddling the near plane — is measured
against nothing and counted in neither, so the gap between `visible` and
`occlusionTested` reads how much of the frame the test could speak for.

This answers for the main camera. What a shadow view's own volume did with
the frame's casters is on that view's row in `renderer.shadowViews()`.

```lua
local s = renderer.cullStats(); print(s.visible - s.occlusionCulled, "drawn")
```

## modules/renderer/debugPass.builtins {#builtins}

```lua
debugPass.builtins(): { string }
```

The built-in debug-pass names, one per channel in channel order — the
engine's built-in pass vocabulary (final, albedo, normal, depth, …).

```lua
for _, n in ipairs(renderer.debugPass.builtins()) do ... end
```

## modules/renderer/debugPass.channel {#channel}

```lua
debugPass.channel(name: string): number?
```

The channel a debug-pass NAME renders on: a built-in pass, else a content
capture view registered via `renderer.captureView`. Nil when the name is
neither — the signal a selector uses to reject an unknown pass.

**Parameters**

- `name` `string` — A debug-pass name (e.g. "normal", "depth", "lightmap").

```lua
local ch = renderer.debugPass.channel("normal")   -- 7
```

## modules/renderer/debugPass.list {#list}

```lua
debugPass.list(): { string }
```

Every selectable debug-pass name: the built-in passes plus every
registered content capture view. What a debug-pass selector offers.

```lua
local passes = renderer.debugPass.list()
```

## modules/renderer/debugPass.name {#name}

```lua
debugPass.name(channel: number): string?
```

The canonical NAME for a debug channel: a built-in pass name for a
built-in channel, else a registered capture view's name. Channel 0 is
"final" (the lit image). Nil when no pass owns the channel.

**Parameters**

- `channel` `number` — The channel number.

```lua
local name = renderer.debugPass.name(7)   -- "normal"
```

## modules/renderer/depthPrepass {#modules-renderer-depthprepass}

```lua
depthPrepass(): boolean
```

Whether the opaque depth pre-pass is currently enabled.

## modules/renderer/depthPrepassOrder {#modules-renderer-depthprepassorder}

```lua
depthPrepassOrder(): { runs: number, reordered: number }
```

What the last frame's depth pre-passes planned, and how far their
sequences were from near-to-far before they ordered. `runs` counts the
instanced draws planned; `reordered` counts the adjacent pairs the sort
moved past each other, taken before it ran. Both are summed over every
pre-pass the frame ran — the window plus each render-target camera, each
ordering against its own camera. Both read `0` while the pre-pass or the
ordering is off, and `reordered` reads `0` for a frame that already stood
in order. The ordering leaves no other trace — the draws, the depth and the
image are the same either way.

```lua
local o = renderer.depthPrepassOrder()  -- o.reordered > 0 → it sorted
```

## modules/renderer/depthPrepassOrdering {#modules-renderer-depthprepassordering}

```lua
depthPrepassOrdering(): boolean
```

Whether the depth pre-pass is submitted nearest-first.

## modules/renderer/destroy {#modules-renderer-destroy}

```lua
destroy(handleOrKind: any, id: string?): boolean
```

Free the GPU resource a renderer resource holds (the GPU-destroy verb).
Takes any of the forms that name it: the handle a create returned, routed
by its `category` so one call releases a mixed set of handles; the id a
listing hands out, whose kind is read back off what the renderer holds
under it — the runtime registry, the material definitions, the live
features, and the device itself for an asset's own texture or mesh; or the
kind with the id beside it, the shape `renderer.hold` and
`renderer.references` take, which is what names the kind for an id two of
them answer to. An id nothing holds anything under releases nothing and
answers false. The on-disk asset, if any, is untouched. A CPU handle's
`:unload()` frees the CPU copy separately.

**Parameters**

- `handleOrKind` `any` _(optional)_ — A `MeshHandle`, `TextureHandle`, `MaterialHandle` or feature
handle; the id itself; or the kind (`"texture"`, `"material"`, `"mesh"`,
`"feature"`) with the id as the second argument.
- `id` `string?` _(optional)_ — The guid or registry key, when the first argument is a kind.

```lua
renderer.destroy(meshHandle); renderer.destroy(materialHandle)
renderer.destroy(renderer.texture.list()[1].guid)
renderer.destroy("mesh", guid)
```

## modules/renderer/deviceGeneration {#modules-renderer-devicegeneration}

```lua
deviceGeneration(): number
```

Which render device this process is on, counted from the first.

A render device is lost when a driver resets, when the GPU is taken away,
or when a browser reclaims a WebGPU context. The engine answers by building
another device and re-deriving this session's resources onto it, and this
number moves by one each time it does. Anything held across frames that was
built from a GPU resource records this beside it and remakes it when the two
differ; `engine.onDeviceRebuilt` is the hook that fires when it moves.

```lua
local generation = renderer.deviceGeneration()
engine.onDeviceRebuilt(function(g) print("device " .. g) end)
```

## modules/renderer/deviceState {#modules-renderer-devicestate}

```lua
deviceState(): string
```

Whether the render device this process draws through is the one it is
using, one it is replacing, or one it has stopped trying to replace.

`"ready"` is a live device. `"rebuilding"` is the window between a device
reporting itself lost and another being in place: every GPU resource built
from the old one is invalid, the frames in that window draw nothing, and
anything reaching the GPU refuses. `"abandoned"` is after the engine gave
up — the adapter refused every attempt, so this session draws no more
frames.

Work that spans the device — build a render target, draw into it, read it
back — reads this to tell an operation that failed because the device went
out from under it, which is worth doing again once
`renderer.deviceGeneration()` moves, from one that failed on its own terms.
The loss is reported before the next device exists, so the two readings
answer different halves: this one says a replacement is coming, the
generation says it arrived.

```lua
if renderer.deviceState() == "rebuilding" then return end
```

## modules/renderer/drawDiagnostics {#modules-renderer-drawdiagnostics}

```lua
drawDiagnostics(): { DrawDiagnostic }
```

Every renderable that is NOT drawing what its material says — the one
call for "why does this surface look wrong". Three states land here: a
surface rendering as the magenta placeholder (`substituted`), one the
renderer could bind nothing for at all (`outcome = "skipped"`), and one
drawing a program whose most recent compile FAILED (`stale`), which is what
a shader edited into brokenness looks like — the pipeline its last good
compile built keeps drawing, so the picture is intact and answers to none of
the edits since. Each row names the entity, the program asked for, the
program bound, `programStatus` — the compile gate's word about the program
the material NAMED — and the one cause
from `shaderCompileFailed` / `shaderNotRegistered` / `shaderNotCompiledYet`
/ `noGbufferEntry` / `renderStateKeyNotBuilt` / `noPipelineForTarget` /
`unshaded`, with the compiler's own message in `detail` or `programError`.
Covers every renderable the renderer holds, whether or not a camera reached
it: a row with `observed = false` and `outcome = "notDrawn"` carries the
renderer's own resolution for one this frame drew nowhere, so a broken
surface off-screen is reported the same as one in frame. An empty result
means every renderable the renderer holds is drawing the program its
material named and that program compiles. Answers on the deferred path as
well as forward, and in edit mode as well as play.

```lua
for _, d in renderer.drawDiagnostics() do print(d.entity, d.reason, d.detail) end
if #renderer.drawDiagnostics() == 0 then print("every surface is drawing what it says") end
```

## modules/renderer/drawStats {#modules-renderer-drawstats}

```lua
drawStats(): {
```

What the last completed frame actually submitted. `draws` counts every
geometry draw call the frame issued — the camera's passes, each shadow
view a shadow-casting light adds, and whatever a render feature draws —
and `instances` counts the instances those draws covered. The pair is what
separates one draw carrying five hundred instances from five hundred draws
carrying one each, so it reads how well the scene batches rather than how
many objects are in it.

`compacted` is how many of those instances the frame planned through draws
whose instance count the GPU decides: the culler's own per-object answers
packed into a dense run, so an object it rejects is absent from the draw
instead of collapsing to nothing in the vertex stage. `compactedDrawn` is
how many of them survived, counted on the GPU as it packed them — a pass
that then skips a whole draw over its own layer or visibility answer
leaves that draw's instances in both numbers.

The plan is made over the populations the frame draws, and the tests
answer which of their instances the packing keeps. That packing runs
before any pass has resolved the depth occlusion culling is tested
against, so on its own it reads the frustum and screen-size answers
alone. With `setOcclusionCulling` armed the frame packs the same plan a
second time once the test has answered, and `compactedDrawn` then counts
what came through occlusion as well.

`compactedDrawn` comes back from the buffer the GPU wrote, so it describes
a frame that has finished while `compacted` describes the most recent
plan, and it holds the last count the GPU wrote until another arrives — a
frame that compacts nothing reads `compacted` 0 beside the count from the
last frame that did. In a scene standing still the gap between the two is
the front-end work culling removed.

`materialBinds` is how many times the frame's geometry passes set a
material's parameter group, and `materialBindsElided` how many times a
pass reached that decision and found the group already bound. Their sum
is how many times the decision was reached — once per unit of geometry
submitted, which sits at or below `draws`, since a mesh of several
primitives draws once per primitive under one set of binds. The ratio
inside the pair is what material binding costs the frame: the batched
opaque geometry is gathered into runs sharing a material, so a frame of
many such draws over few materials binds about once per material rather
than once per unit. `materialExtraBinds` and `materialExtraBindsElided`
are the same pair for the second group, the storage bindings a shader
declares for itself, which only the shaders that have them ever bind.

`pipelineBinds` and `pipelineBindsElided` are the same pair for the
pipeline itself: how many times the frame's geometry passes set one, and
how many times a pass reached that decision and found the pipeline it
wanted already bound. Which pipeline a unit needs follows its shader, its
material's render state and its mesh's vertex layout together, so a scene
whose units share all three costs one set for the run of them, while units
differing in any one of the three each pay their own. Their sum is how
many units reached the pipeline decision, which sits at or above what the
material pair reports: a unit the pass settles a pipeline for and then
abandons — one whose material group resolved to nothing — counts here and
never reaches the material decision.

Every figure here is the whole frame's, the main camera's draws and every
shadow view's summed together. `renderer.shadowViews()` splits `compacted`
and `compactedDrawn` across the views that made them, and carries the
camera's own share beside them.

```lua
local d = renderer.drawStats(); print(d.instances / math.max(d.draws, 1), "instances per draw")
print(renderer.drawStats().compactedDrawn, "of", renderer.drawStats().compacted, "survived the cull")
local d = renderer.drawStats(); print(d.materialBinds, "material binds over", d.materialBinds + d.materialBindsElided, "units")
local d = renderer.drawStats(); print(d.pipelineBinds, "pipeline sets over", d.pipelineBinds + d.pipelineBindsElided, "units")
```

## modules/renderer/feature.create {#create}

```lua
feature.create(ref: any, guid: string?): any
```

Instantiate a render feature so the engine calls its `render(ctx)` hook
every frame. `ref` is an `AssetRef<renderFeature>` whose `init.luau` returns
`{ setup?, render, teardown? }`. Returns a live `RenderFeatureHandle` (its
`guid` is the stable id, same as mesh/texture handles); tear it down with
`renderer:destroy(handle)`. Pass `guid` to assign a specific id.

**Parameters**

- `ref` `any` _(optional)_ — An `AssetRef<renderFeature>`, or a string identity/guid resolved via
`asset.resolve(ref, "renderFeature")`.
- `guid` `string?` _(optional)_ — Optional explicit handle guid (minted when omitted).

```lua
local h = renderer.feature.create(asset.resolve("acrylic", "renderFeature"))
local h = renderer.feature.create("acrylic")
```

## modules/renderer/feature.destroy {#destroy}

```lua
feature.destroy(handleOrGuid: any): boolean
```

Tear down a live render feature by its `RenderFeatureHandle` OR its guid
string — the by-id path for when the handle was lost (e.g. across `execute`
calls). Same effect as `renderer.destroy(handle)`. Returns true if a feature
was live under that id.

**Parameters**

- `handleOrGuid` `any` _(optional)_ — A `RenderFeatureHandle` or its `guid` string.

```lua
renderer.feature.destroy("eb623975-d8bd-47a1-a0a4-5c0e56238e2f")
```

## modules/renderer/feature.list {#list}

```lua
feature.list(): { { guid: string, identity: string } }
```

List every render feature currently live (running its `render(ctx)` each
frame). Each entry is `{ guid, identity }` — the `guid` is the same id a
`RenderFeatureHandle` carries, so you can tear a feature down by guid even
after losing its handle (e.g. across separate `execute` calls).

```lua
for _, f in renderer.feature.list() do print(f.identity, f.guid) end
```

## modules/renderer/feature.shaded {#shaded}

```lua
feature.shaded(): { [string]: number }
```

How many pixels each fragment pass a render feature enqueued shaded on
the last drawn frame, keyed by the pass's shader/effect name. A fragment
pass draws one triangle over its target, so it shades the whole screen
whatever its effect actually reaches — unless it declares `bounds` on the
pass spec, the world-space box its effect stays inside, in which case it
shades the rectangle that box projects into for the camera drawing it and
is skipped for a camera that cannot see the box at all. This is the reading
that says which of the two a pass is: it moves when the effect moves, and a
pass absent from it shaded nothing. Summed over every camera the frame drew.

```lua
local px = renderer.feature.shaded(); for name, n in pairs(px) do print(name, n) end
```

## modules/renderer/featureTexture.configure {#configure}

```lua
featureTexture.configure(width: number, height: number, layers: number)
```

Size the shared feature-texture array — the layers a surface shader reads
through `zero_feature_texture(uv, layer)`, and the layers a `SpotLight`
projects through its cone via `cookieLayer`. Layers are `rgba16f`.
A call for the size the array already has is left alone. One that changes
the size reallocates, and the replacement is zeroed — so it empties every
layer in the array, including the layers other features and other cookies
own. `renderer.featureTexture.state()` reports the extent and the layers
holding content, which is how a feature re-fills the layer a resize took
from it.

**Parameters**

- `width` `number` — Layer width in pixels.
- `height` `number` — Layer height in pixels.
- `layers` `number` — How many layers the array holds.

```lua
renderer.featureTexture.configure(512, 512, 4)
```

## modules/renderer/featureTexture.setLayer {#setlayer}

```lua
featureTexture.setLayer(layer: number, textureKey: string, x: number, y: number)
```

Copy a texture already on the GPU into one layer of the shared array,
its top-left corner at `(x, y)` — GPU to GPU, with no readback. Several
small images pack into one layer by calling this once per image at
different offsets. The source must be `rgba16f` and fit at that offset.

**Parameters**

- `layer` `number` — Which layer of the array to write into.
- `textureKey` `string` — The source texture's name — the one it was created under.
A `compute.createStorageTexture2D` target, a `compute.createTextureHistory`
pair (its current side), and a texture a `compute.copyBufferToTexture`
wrote all answer to the name they were given.
- `x` `number` — Left edge of the destination rectangle, in pixels.
- `y` `number` — Top edge of the destination rectangle, in pixels.

```lua
compute.createStorageTexture2D("gobo", { width = 256, height = 256, format = "rgba16f" })
renderer.featureTexture.setLayer(0, "gobo", 0, 0)
```

## modules/renderer/featureTexture.state {#state}

```lua
featureTexture.state(): {
```

What the shared feature-texture array is right now: the extent every
layer carries, and `filled`, the ascending 0-based indices of the layers a
`setLayer` has landed in since the array was last sized. One array is
shared by every feature and every light cookie in the scene, and it has no
allocator, so this is the call that tells a feature whether the array it
sized and filled is still the array it is writing into — a `configure` that
changed the size reallocates and zeroes every layer, and the layer it
emptied leaves `filled` without it. Measured off the renderer at the end of
the last rendered frame, so a `configure` or `setLayer` issued this frame
reads back on a later one.

What this describes is the array a shader samples. The source texture a
`setLayer` copied FROM is a GPU resource of its own and keeps the bytes it
was written with for as long as it lives, so `filled` is the reading that
answers whether the layer behind a `cookieLayer` is live right now.

```lua
local ft = renderer.featureTexture.state()
print(("feature textures: %dx%d over %d layers"):format(ft.width, ft.height, ft.layers))
-- Re-fill the cookie layer this module owns if anything emptied it.
if ft.width ~= myWidth or table.find(ft.filled, myLayer) == nil then
refillMyCookie()
end
```

## modules/renderer/framePacing {#modules-renderer-framepacing}

```lua
framePacing(): FramePacing?
```

How far the CPU is allowed to run ahead of the GPU, and what holding it
there cost the frame just finished. Submitting work to the GPU returns
before the GPU has done it, and everything that submission holds — its
staging allocations, its bind groups, its command buffer — stays alive
until it completes. A frame that asks for more work than the GPU finishes
in a frame's time therefore leaves that behind it, and unbounded that is
memory growth rather than a lower frame rate.

`framesInFlight` is how many submitted frames have not reported done
through the queue's completion signal, held under `maxFramesInFlight`: a
device that keeps up reads under the bound, one that is behind reads at it.
It counts submissions, which is its own quantity — how many presented
images the swapchain permits in flight is a separate setting.
`mechanism` names how that bound is enforced
here: `submission-wait` waits for the frame that many frames back and
reports the wait in `waitMs`, so a paced frame costs latency and still
draws; `submitted-work-done` counts outstanding frames off the queue's
completion signal and declines to start a frame while the bound is met,
counting those in `pacedFrames` and leaving the last presented image up.
`submittedFrames` counts the frames that were admitted and submitted, so it
rises for as long as the renderer is producing frames — which is what tells
a renderer running slowly under a tight bound from one that has stopped.
`stalled` reads true while that completion signal has stopped arriving and
the pacer stood down rather than hold the image indefinitely; it clears on
the first frame that finds the count back under the bound.

`producing` is whether the renderer is drawing frames at all. A headless
renderer draws into an offscreen framebuffer that nothing presents, so its
image reaches a reader only through something that copies it out: it draws
while a consumer is asking — an MCP call in flight, a queued texture
readback, a recording, a frame-egress session — and declines the frames
between two asks, counting them in `idleSkippedFrames`. Every other
renderer stat answers with the last frame that drew, so `producing` is what
separates a live reading from a frozen one. A windowed renderer presents
every frame it draws and reads `producing = true` throughout.

`presentMode` is what the surface presents with and `presentModes` what it
offers; both are empty of meaning on a headless renderer, which never
presents.

```lua
local p = renderer.framePacing()
print(("%d of %d frames in flight, paced by %s"):format(p.framesInFlight, p.maxFramesInFlight, p.mechanism))
```

## modules/renderer/getRaytrace {#modules-renderer-getraytrace}

```lua
getRaytrace(): boolean
```

Whether ray tracing is currently enabled.

## modules/renderer/gpuMemory {#modules-renderer-gpumemory}

```lua
gpuMemory(): GpuMemory
```

Where the renderer's GPU memory went at the last completed frame — the
call to reach for when something is holding memory and you do not know
what.

Three figures answer three different questions, and they are meant to be
read against each other:

* The categories — `shadow`, `textures`, `meshes`, `instances`, `compute`,
summing to `categorised` — are the renderer's own accounting of what it
asked for on purpose. Always present, on every backend.
* `allocator` is the device allocator's ledger, with a row per creation
label largest first, which is what names an allocation no category
claims. It exceeds `categorised` by the per-frame render targets and the
scratch nothing categorises. The allocator hands memory out from blocks
it reserves whole from the device and returns a block only once nothing
is left in it, so `reservedBytes` runs above `allocatedBytes` by what
those blocks hold unused; `blocks` lists them emptiest first with the
labels that keep each one alive, and `emptyBytes` plus `slackBytes` is
that distance exactly — the pool held in empty blocks, and the room
pinned inside blocks something still sits in.
* `driver.deviceLocalBytes` is what the graphics driver charges this
process, out of the kernel's own accounting. It is the biggest of the
three and the one that fills a card, because it also holds the
swapchain, the images the driver keeps on the renderer's behalf, and the
rounding to whole pages and heap blocks that neither figure above sees.
Read it when the question is how much of the machine's GPU this engine
is using; read the two above when the question is what the engine spent
it on. A platform with no per-process accounting reports
`available = false` and the reason.
* `driver.outsideAllocatorBytes` is that charge less everything the
allocator reserved — what the driver holds on its own account, and the
one figure here nothing releases: a dropped pipeline, another scene and
`renderer.collect()` all leave it where it is, and it falls when the
device is destroyed. Read it when a session's device memory has grown
and no ledger row accounts for the growth.

`compute` is what the compute subsystem holds; `compute.observe()` names
each of those resources and what it costs. `renderTargets` counts the
offscreen render targets the renderer holds at that frame, which is what
says a `renderer.destroy` has been applied rather than queued.

```lua
local m = renderer.gpuMemory()
print(("shadows %.1f MiB"):format(m.shadow.total / 1024 / 1024))
-- how much of the card this engine is holding:
if m.driver.available then
print(("driver %.1f MiB"):format(m.driver.deviceLocalBytes / 1024 / 1024))
else
print("no driver figure: " .. m.driver.reason)
end
-- the biggest allocation in the engine:
local top = m.allocator and m.allocator.byLabel[1]
print(top and top.label, top and top.bytes)
-- the block held for the least, and what pins it:
local block = m.allocator and m.allocator.blocks[1]
if block and block.allocationCount > 0 then
print(block.size, block.allocatedBytes, block.byLabel[1].label)
end
```

## modules/renderer/hold {#modules-renderer-hold}

```lua
hold(handleOrKind: any, id: string?): boolean
```

Pin a runtime resource for the session. A held texture, material, mesh
or render feature survives every collection — the one a root scene load
runs and a direct `renderer.collect()` alike — until `renderer.release`
lets it go or its destroy frees it. It is the way to keep an ad-hoc
resource across the scenes that come and go under it. A hold keeps the
resource in the registry; a mesh's GPU buffers are governed by what draws
it, parked as a CPU definition when the last instance naming it goes and
brought back when one names it again, so `renderer.mesh.isResident(guid)`
is the separate question about the buffers.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind (`"texture"`, `"material"`,
`"mesh"`, `"feature"`) with the guid or key as the second argument.
- `id` `string?` _(optional)_ — The guid or key, when the first argument is a kind.

```lua
renderer.hold(tex)
renderer.hold("material", "swatch")
```

## modules/renderer/instanceData.clear {#clear}

```lua
instanceData.clear(target: string | entityRef)
```

Drop every lane of an entity's per-instance shader data, so its draws
read zero again — how a feature releases a subject it is still holding.
Despawning an entity releases its block too, so this is for a subject that
stays. It takes an entity that has already gone, which is when a feature
releasing its subjects often runs, and does nothing for an entity holding
no block.

**Parameters**

- `target` `string | entityRef` — The entity — a proxy from `entity(...)` / `entity.spawn(...)`, or
an entity-id string.

```lua
renderer.instanceData.clear(subject)
```

## modules/renderer/instanceData.laneCount {#lanecount}

```lua
instanceData.laneCount(): number
```

How many `vec4` lanes each entity's per-instance block holds, so a lane
index runs `0 .. laneCount() - 1`. The same count a surface shader indexes
`input.shader_data` against.

```lua
for lane = 0, renderer.instanceData.laneCount() - 1 do
renderer.instanceData.set(subject, lane, 0)
end
```

## modules/renderer/instanceData.set {#set}

```lua
instanceData.set(
```

Write one `vec4` lane of an entity's per-instance shader data — the
channel that lets ONE material serve many entities that differ in a value.
A surface shader reads the lane back as `input.shader_data[lane]`, so a
dissolve at its own progress per subject, an effect at its own age per
firing, or a per-entity mask costs one material rather than one material
per entity.

The engine attaches no meaning to a lane: a feature picks the lane indices
it owns and packs whatever its shader agrees they carry. Name those indices
in the module that writes them, so the writer and the shader read the block
the same way.

The write reaches the block where it is called, so the entity it names is
the one holding that id at that point in the tick, and the value is on the
draw from the next frame. It is held until the lane is written again, the
entity's block is cleared, or the entity is despawned — a despawned entity
releases its whole block. A lane an entity was never given reads zero.

## modules/renderer/loseDevice {#modules-renderer-losedevice}

```lua
loseDevice()
```

Destroy the render device on the next frame, so the engine meets a real
device loss.

This is the one loss that can be caused on purpose, and it travels the same
path a driver reset does: frames draw nothing until the rebuild lands,
`GET /engine/status` reports the renderer as `recovering` while it does,
`engine.onDeviceRebuilt` fires afterwards, and `renderer.deviceGeneration()`
moves. Use it to prove that a world's content survives a device loss —
anything it holds only on the GPU has to be remade from the rebuild hook, and
this is how you find out whether it is.

```lua
renderer.loseDevice()
-- some frames later:
print(renderer.deviceGeneration()) -- one higher than before
```

## modules/renderer/mainCameraView {#modules-renderer-maincameraview}

```lua
mainCameraView(): { number }?
```

The main camera's inverse view-projection (column-major, 16 numbers)
followed by its world position (3 numbers) — `{m0..m15, px,py,pz}` — for
reconstructing world positions from the depth buffer in a ray-tracing pass.
Nil before the first render.

## modules/renderer/material.animatedTexture {#animatedtexture}

```lua
material.animatedTexture(
```

Build a material that PLAYS a layered texture: its layers bound as the
frames, its timing bound beside them, and the engine's `animatedTexture`
shader turning the clock into the layer showing now. One call from an
imported animated image to a material an entity can wear.

The layer showing is resolved per pixel against the texture's own schedule,
so frames of unequal length are shown for the lengths they were authored
with, and the sequence loops. `speed` scales the clock (2 plays twice as
fast, 0 holds the frame `startTime` lands in) and `startTime` offsets into
the sequence, so two surfaces sharing one texture can run out of phase.

The clock is the engine's, and it runs in edit mode as much as in play and
through a pause, so two screenshots of one surface taken moments apart are
two different frames of it. `speed = 0` holds one frame for as long as it
is set, which is the state to compare two screenshots in.

The returned handle is what a surface wears — `Model:applySessionMaterial`
takes it, and so does a Model's `material` field. The handle's `guid` is
this material's REGISTRY KEY, the currency of `setProperty`, `describe` and
`destroy`; a component field resolves an asset, so a bare key in one leaves
the component waiting for an asset to register under that name.

The builtin `plane` mesh emits `uv = (u, v)` with `v` along its own +Z, so
a quad pitched +90° about X (`Transform.eulerToQuat(0, math.pi / 2)`) shows
the image upright to a camera on +Z, and -90° shows it first-row-last.

A texture whose layers carry no timing is rejected — there is nothing to
play. `renderer.texture.info(bytes).animated` is the test.

```lua
local mat = renderer.material.animatedTexture("banner.texture")
local id = entity.spawn("billboard", { rotation = { Transform.eulerToQuat(0, math.pi / 2) } })
entity(id).component.add("Model", { model = "plane" })
entity(id).component.get("Model"):applySessionMaterial(mat)
renderer.material.setProperty(mat.guid, "speed", 2)
```

## modules/renderer/material.create {#create}

```lua
material.create(content: MaterialContent, key: string): MaterialHandle
```

**Parameters**

- `content` `MaterialContent`
- `key` `string`

## modules/renderer/material.describe {#describe}

```lua
material.describe(key: string | { [string]: any } | AssetRef): any
```

The recoverable definition (`{ shader, properties, textures, name }`)
this module registered under `key` via `renderer.material.create`, or nil
for keys registered elsewhere (e.g. material assets resolved by the
assetType). `properties` and `textures` carry the material's current values:
each `setProperty` / `setTexture` write lands on this record, a texture slot
under the GPU key the slot binds by — these are the WRITES, held here
whether or not the renderer took them up. `renderer` beside them is what the
renderer holds for the same key: the program its prepared bind group was
built against, the render state its draws are looked up under, whether a
pipeline exists for that key, and how many draws the observed frame gave
it. `renderer` is nil when the renderer holds no material under this key at
all, and `resident` states the same fact as a boolean. Writes reach the
screen through both halves: `resident = false` says the renderer holds
nothing to put them in, and `renderer.draws = 0` on a resident material
says it holds them and no renderable is drawing with it. For a material
that is resident AND drawn and still looks wrong,
`renderer.drawDiagnostics()` names the renderable and the cause.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.

## modules/renderer/material.destroy {#destroy}

```lua
material.destroy(key: string | { [string]: any } | AssetRef): boolean
```

Drop a runtime material registered via `renderer.material.create`: clears
its recoverable definition, unregisters its runtime-resource stamp so it is no
longer swept into the material freeze/save flow, and frees the GPU record. Use
for transient materials (e.g. a preview swatch) that must not outlive their use.
The on-disk asset, if any, is untouched.

The reach is the registry: after this, `describe` and `list` stop answering
for the key. A surface already wearing the handle goes on drawing what it
was given — `Model:restoreSessionMaterial` is what puts a Model back on its
authored material.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key (the one passed to `create`), the
`MaterialHandle` `create` returned, or an `AssetRef` from `asset.resolve`.

```lua
renderer.material.destroy("__preview_swatch_" .. texGuid)
```

## modules/renderer/material.list {#list}

```lua
material.list(): { any }
```

Every runtime material currently registered, ordered by registry key.
Each entry carries the key, where it came from, and the shader it binds.
`renderer.references("material", key)` says what is still holding a row,
and `renderer.collect()` releases the rows nothing holds.

```lua
for _, m in ipairs(renderer.material.list()) do print(m.guid, m.shader) end
```

## modules/renderer/material.renderState {#renderstate}

```lua
material.renderState(key: string | { [string]: any } | AssetRef): MaterialObservation?
```

What the renderer holds for a material, which is a different document
from the values written to it. `shader` is the program its prepared bind
group was built against, `renderState` the blend / cull / topology / queue /
depth key its draws are looked up under, `keyBuilt` whether a pipeline
exists for that key, and `draws` / `instances` / `placeholderDraws` /
`binds` / `bindsElided` what it cost in the frame the renderer last
observed — those five read 0 until something arms per-draw recording, which
`renderer.materialCost()` and
`renderer.drawDiagnostics()` do. `nil` means the renderer holds no material
under this key at all — the writes landed on a record nothing is drawing
with.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.

```lua
local r = renderer.material.renderState("water"); print(r.renderState.blend, r.keyBuilt)
```

## modules/renderer/material.sessionKeyFor {#sessionkeyfor}

```lua
material.sessionKeyFor(entityId: string): string
```

The canonical registry key for an entity's SESSION material — the
runtime material a system (e.g. GI baking) shows on an entity in place
of its authored material for the lifetime of the engine session. One
session material per entity: create it under this key, hand the handle
to `Model:applySessionMaterial`, and the component re-adopts it across
VM reloads by probing this key with `describe`. The key names the entity
for as long as the entity stands: once it is gone the session store lets
the handle go, and a collection releases the material and whatever its
bindings were the last to hold.

**Parameters**

- `entityId` `string` — The entity carrying the material.

```lua
local key = renderer.material.sessionKeyFor(entityId)
```

## modules/renderer/material.setProperty {#setproperty}

```lua
material.setProperty(key: string | { [string]: any } | AssetRef, name: string, value: any): ()
```

Push one changed uniform property to a registered material's GPU record
(frame-fast incremental update; no re-register). Keyed by the material's
registry key. The value written becomes the material's current one: it is
what `describe` reports, and — for a property the material's shader
declares, which is what the uniform buffer is packed by — what a material
`AssetRef` reads back through `getProperty` / `getProperties` and what the
surface is drawn with. A write under any other name reaches the record
`describe` reports, which is where it reads back.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.
- `name` `string` — Property name.
- `value` `any` _(optional)_ — New value.

```lua
renderer.material.setProperty(asset.resolve("wall", "material"), "roughness", 0.2)
```

## modules/renderer/material.setTexture {#settexture}

```lua
material.setTexture(key: string | { [string]: any } | AssetRef, slot: string, ref: string | { [string]: any } | AssetRef): ()
```

Push one changed texture slot to a registered material's GPU record.
Keyed by the material's registry key.

**Parameters**

- `key` `string | { [string]: any } | AssetRef` — The material's registry key, the `MaterialHandle` from
`renderer.material.create`, or an `AssetRef` from `asset.resolve`.
- `slot` `string` — Texture slot name (`"base_color_texture"`, …).
- `ref` `string | { [string]: any } | AssetRef` — Texture reference — a `.texture` guid / identity / name / path, the
image path it was imported from, a `color:` / `default:` form, a live GPU
handle, or a texture `AssetRef` carrying one. An asset reference is
materialised (Disk→CPU→GPU) and bound by the key the upload lands under.

```lua
renderer.material.setTexture("sky", "sky_texture", "panorama.texture")
```

## modules/renderer/materialCost {#modules-renderer-materialcost}

```lua
materialCost(): { MaterialObservation }
```

What each material cost the frame the renderer last drew, and the state
it holds each one under. One row per material the renderer holds a prepared
bind group for — a material an author wrote and the renderer never prepared
is absent, which is itself the answer to "why is nothing I set reaching the
screen". `draws` and `instances` cover that one frame; `placeholderDraws`
is how many of those draws bound the magenta placeholder instead of this
material's own program; `binds` is how many material-owned bind groups the
frame's passes SET for it and `bindsElided` how many of its draws wanted a
group the pass already held, which is what draw-key sorting buys; a draw
that fell back to the placeholder bound the placeholder's group, so it
counts in `placeholderDraws` and in neither bind count. `uniformBytes` is
the GPU uniform buffer's own size,
which is the reflected property block raised to the 16-byte floor and
rounded up to the copy alignment. `renderer.drawDiagnostics()` names WHICH
renderable is not drawing what its material says, and why.

```lua
for _, m in renderer.materialCost() do print(m.material, m.draws, m.binds, m.bindsElided) end
```

## modules/renderer/materialIdentity {#modules-renderer-materialidentity}

```lua
materialIdentity(): MaterialIdentity
```

Which material each renderable draws with, as a number a shader can
carry. A material is authored and bound by name, and no shader can read a
string — so every renderable's per-instance record holds a material index
instead. `slots` is the name → index table those indices are drawn from: an
index is assigned the first time the renderer draws with that material and
does not move afterwards, so two renderables that differ only in material
read different indices, and one renderable reads the same index frame after
frame. It follows that the table keeps a row for every material name drawn
this session, whether or not anything still draws with it. `renderables` is
a row per renderable that owns a GPU slot — the entity it belongs to, that
slot, and the index the record at it carries; `populations` is the same for
an instanced draw, whose whole reserved run of slots carries the one
material its registration named. That index is what a shader reads as
`instance_data[slot].material_index`, and the row a ray hit resolves
through `zeroMaterial()`. A renderable draws with the material its entity
references, so one whose entity names none carries index 0.

```lua
local id = renderer.materialIdentity()
for _, r in id.renderables do print(r.entity, r.slot, r.index, r.material) end
```

## modules/renderer/materialIndex {#modules-renderer-materialindex}

```lua
materialIndex(name: string): number?
```

The index standing for a material, or `nil` for one the renderer has not
drawn with yet. Pass it to a shader (or compare it against what a shader
read out of `instance_data[slot].material_index`) to tell which material a
drawing instance carries.

**Parameters**

- `name` `string` — `string` Material name, as `renderer.material.create` filed it.

```lua
local red = renderer.materialIndex("brick_red")
```

## modules/renderer/maxAnisotropy {#modules-renderer-maxanisotropy}

```lua
maxAnisotropy(): number
```

The highest anisotropy this device honours: 16 on hardware that filters
anisotropically, 1 on hardware that does not, where a higher request would
be downgraded to trilinear regardless. Read it to report quality honestly —
`renderer.setAnisotropy` clamps for you, so a request never needs guarding.

```lua
local best = renderer.maxAnisotropy()
```

## modules/renderer/mesh.boundsSource {#boundssource}

```lua
mesh.boundsSource(mesh: string | { [string]: any } | AssetRef): string
```

Where this mesh's culling bounds come from. `"compute"` once a compute
pass has written its vertices: the engine reduces those vertices to an AABB
every frame, so the mesh is culled against the geometry the pass produced
wherever it puts it. `"geometry"` otherwise: the AABB of the geometry the
mesh was created with.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
print(renderer.mesh.boundsSource(mesh))
```

## modules/renderer/mesh.buildClusters {#buildclusters}

```lua
mesh.buildClusters(mesh: string | { [string]: any } | AssetRef): string?
```

Build a cluster-LOD DAG (Nanite-style virtualized geometry) for the
static CPU mesh held under `guid` and return its serialized `data.clusters`
bytes. Returns nil when the mesh is degenerate, and on an engine whose
`renderer.mesh.canBuildClusters` reports false. Pair with
`renderer.mesh.uploadClusters`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — A mesh loaded into the CPU store (`renderer.mesh.loadCpu`) — the
`MeshCpuHandle`, a `MeshHandle`, a guid, or a mesh `AssetRef`.

```lua
local cb = renderer.mesh.buildClusters(cpu)
```

## modules/renderer/mesh.canBuildClusters {#canbuildclusters}

```lua
mesh.canBuildClusters(): boolean
```

Whether this engine bakes cluster-LOD hierarchies. It reads the binding
the running engine registered: every target the engine ships on carries the
builder, so a mesh loaded in a browser bakes its own clusters the same way
one loaded natively does, and an engine built without it reports false and
answers nil from `renderer.mesh.buildClusters`.

```lua
if renderer.mesh.canBuildClusters() then ... end
```

## modules/renderer/mesh.clusterBakeBudget {#clusterbakebudget}

```lua
mesh.clusterBakeBudget(ms: number?): number
```

The wall time one frame may spend advancing scheduled cluster bakes, in
milliseconds — set first when `ms` is given. A slice always runs at least
one unit of the build, so the budget bounds what a frame spends by choice
and the largest single unit a mesh imposes sets the floor under it.

**Parameters**

- `ms` `number?` _(optional)_ — New per-frame budget in milliseconds, capped at 1000. A value that is
not a positive, finite number raises.

```lua
renderer.mesh.clusterBakeBudget(2)
```

## modules/renderer/mesh.clusterBakes {#clusterbakes}

```lua
mesh.clusterBakes(): { [string]: any }
```

What the scheduled cluster bakes are costing. `budgetMs` is the slice a
frame may spend, `pending` how many bakes are queued, `completed` how many
have finished since the engine started, `dropped` how many left the queue
because the geometry they were scheduled over stopped being readable, and
`heldBytes` the source geometry the queue is holding across all of them —
the vertex pool and index run the bake at the head is reading, plus a copy
for each queued mesh the engine holds no definition for.
`inFlight` is one row per queued bake —
`{ guid, cpuMs, frames, slices, bytes, state }`: the wall time spent
advancing it, the frames it has been queued for, the slices it has been
advanced by, the geometry it is holding, and `"baking"` for the one being
advanced against `"queued"` for the ones waiting their turn.

```lua
print(renderer.mesh.clusterBakes().heldBytes)
```

## modules/renderer/mesh.clusterComponents {#clustercomponents}

```lua
mesh.clusterComponents(clusterBytes: buffer | string): (ClusterComponents?, string?)
```

Split a cluster blob (from `renderer.mesh.buildClusters`) into its
GPU-ready component byte pools — the cluster vertex pool, the
geometry-addressing pool (every cluster's local→global vertex map, then
every cluster's triangle bytes), and the per-cluster record array — plus
their counts. A cluster's triangles address positions inside its own vertex
map one byte at a time, and a record's `vertexOffset` indexes the geometry
pool in `u32` elements while its `indexOffset` indexes it in bytes, so ONE
binding resolves a corner. A pure decode (no GPU work): upload the pools
into buffers a compute shader owns (`shaderRef:createBuffer` +
`buf:writeBytes`) to drive a cluster draw from Luau.

**Parameters**

- `clusterBytes` `buffer | string` — Serialized cluster bytes (binary-safe).

```lua
local c = renderer.mesh.clusterComponents(cb)
```

## modules/renderer/mesh.clusters {#clusters}

```lua
mesh.clusters(mesh: string | { [string]: any } | AssetRef): { [string]: any }?
```

The shape of the cluster-LOD hierarchy the renderer holds for a mesh:
`clusterCount` across every level, `levelCount` with the finest counted as
one, and `triangleCount` across every cluster. The renderer keys one entry
per mesh that carries a hierarchy, so this answers whether the mesh has
clusters as well as what they are — nil for a mesh that carries none.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to read — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
local c = renderer.mesh.clusters(gpu) ; if c then print(c.levelCount) end
```

## modules/renderer/mesh.create {#create}

```lua
mesh.create(src: any, guid: string?): MeshHandle
```

Create (or fetch) a GPU mesh resource and return its `MeshHandle`. `src`:
a `MeshCpuHandle` from `meshRef:load()` (CPU→GPU upload under the asset's
guid, idempotent — returns the resident handle if already uploaded); raw
geometry `{positions, indices, normals?, uvs?, colors?, uvs1?, unwrapUvs?,
tangents?, skinning?, skins?}` (a new runtime mesh — `uvs1` is the lightmap
UV set, `unwrapUvs` generates one, `skinning`/`skins` bind a skeleton); GPU
compute buffers `{vertexBuffer, indexBuffer, vertexCount, indexCount,
aabbMin?, aabbMax?, prevVertexBuffer?}` (size the vertex buffer at
`vertexCount * engine.vertexStride` bytes, the engine's standard Vertex
layout); or a `MeshHandle` (returned as-is). NEVER takes an AssetRef —
load the CPU first.

`prevVertexBuffer` is a second buffer of the same size and layout holding
those vertices as they stood on the previous frame. Naming it is what makes
geometry a compute pass moves report a motion vector: the surface
differences the two streams, so every consumer of screen-space velocity —
motion blur, temporal reprojection — sees the movement. The engine fills it
from the current vertices once per frame, ahead of that frame's compute
dispatches, so a frame in which the pass does not run leaves the two
streams equal and the geometry reports standing still.

`morphTargets` are the shapes the mesh can blend towards: a list of
`{ name?, positions, normals? }` records, each holding one offset per vertex
from the base geometry, in the mesh's own vertex order. An entity blends
them with `ecs.MorphWeights`, weight `i` scaling target `i`. A `name` makes
the shape addressable as itself — `renderer.mesh.morphTargets` reads the
names back and `renderer.mesh.morphWeights` drives them by name.

Raw geometry is read against the mesh type's conventions: `indices` count
vertices from 0, and a triangle's FRONT face is the one whose vertices turn
counter-clockwise as the viewer sees them — `cross(v1 - v0, v2 - v0)` points
out of it. A material culls its back faces by default, so a triangle wound
the other way draws nothing where it stands; reverse the index triple, or
give the material `render = { cull = "none" }`, to draw that side. `normals`
give the surface its outward direction and shade the face; the side that
draws comes from the index order alone. `uvs` sample `(0,0)` at the image's
top-left. Model space carries the world's basis: +X right, +Y up, -Z the
direction `transform.forward` points. `guides { path = "types/mesh" }` has
the whole table.
A geometry src carrying `keepCpu = true` also keeps its geometry in the
guid-keyed CPU store, so `renderer.mesh.getVertices` reads it and
`renderer.mesh.setVertices` rewrites its positions in place — the per-frame
deformation path, which sends positions alone where `renderer.mesh.update`
re-sends the whole geometry. `renderer.mesh.unloadCpu(mesh)` releases that
copy. Without it the geometry lives on the GPU alone and
`renderer.mesh.readback(mesh)` is what brings it back.

**Parameters**

- `src` `any` _(optional)_ — A MeshCpuHandle, geometry, compute buffers, or a MeshHandle.
- `guid` `string?` _(optional)_ — Optional v4 guid for a NEW runtime mesh (minted Luau-side when
absent). Ignored for the CPU-handle path (the asset's guid is used).

```lua
local gpu = renderer.mesh.create(meshRef:load())
local gpu = renderer.mesh.create({ positions = {...}, indices = {...} })
-- a runtime mesh whose positions are rewritten in place each frame
local m = renderer.mesh.create({ positions = P, indices = I, keepCpu = true })
renderer.mesh.setVertices(m, P)
-- a runtime mesh carrying a lightmap UV set (unwrapped at creation)
local gpu = renderer.mesh.create({ positions = {...}, indices = {...}, unwrapUvs = true })
-- a mesh with one shape to blend towards, driven by ecs.MorphWeights
local gpu = renderer.mesh.create({ positions = P, indices = I, morphTargets = { { positions = D } } })
```

## modules/renderer/mesh.decode {#decode}

```lua
mesh.decode(zmsh: buffer | string): (MeshGeometry?, string?)
```

Decode engine-native `ZMSH` bytes back into a `MeshGeometry`. Inverse of
`renderer.mesh.encode`; each optional stream is present only when the blob
carries it. Takes the bytes themselves — the geometry of a mesh the engine
is holding comes from `renderer.mesh.geometry(mesh)`.

**Parameters**

- `zmsh` `buffer | string` — Engine-native ZMSH bytes (binary-safe).

```lua
local geom = renderer.mesh.decode(meshRef:getBytes())
```

## modules/renderer/mesh.destroy {#destroy}

```lua
mesh.destroy(mesh: string | { [string]: any } | AssetRef): boolean
```

Release the GPU mesh `mesh` names, the release that pairs with
`renderer.mesh.create`. Takes every form that names a mesh — the
`MeshHandle` `create` returned, the guid `renderer.mesh.list` hands out, a
`MeshCpuHandle` or a mesh `AssetRef` — and routes through
`renderer.destroy`, the verb that releases any renderer resource by its
kind. The CPU copy, if one was loaded, is freed separately by the CPU
handle's `:unload()`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to release — a `MeshHandle`, a guid, a `MeshCpuHandle` or a
mesh `AssetRef`.

```lua
local m = renderer.mesh.create(cpu); renderer.mesh.destroy(m)
renderer.mesh.destroy(renderer.mesh.list()[1].guid)
```

## modules/renderer/mesh.drawInstanced {#drawinstanced}

```lua
mesh.drawInstanced(mesh: string | { [string]: any } | AssetRef, opts: any): InstancedDraw
```

Draw one mesh `instanceCount` times in a single call, each copy placed by
a world matrix read from a GPU buffer. The population is a renderable in its
own right — it goes through the mesh's ordinary pipeline and the material's
ordinary bind groups, so it appears in the deferred pass, the forward passes
and the shadow maps exactly as an entity-backed draw of that mesh does.

The buffer holds `instanceCount` **column-major** 4x4 matrices, 64 bytes
each, tightly packed — the layout a vertex shader reads as
`array<mat4x4<f32>>`, which puts each matrix's translation in its LAST four
floats (Lua indices 13/14/15 for x/y/z). Packing row-major transposes every
instance.

The matrices are COPIED into the engine's transform slots once per frame,
which is what buys that full-pass parity. Rewrite the buffer between frames
and the instances move — no re-registration, no re-upload.

`material` is what the population draws with, and it is required: a
`MaterialHandle` (`matRef:handle()`), an `AssetRef`, or a registry key.

`instanceDataBuffer` names a second buffer, holding 64 bytes per instance —
four `vec4` lanes, tightly packed, in instance order. Those lanes arrive in
the fragment stage as `zero_object_data(in.instance_id, lane)`, the same
read a per-entity `__instancedata` block answers, so the members of one
population can differ in whatever their material's shader agrees the lanes
carry. Copied every frame like the transforms, from a buffer a compute pass
writes: the values never touch the CPU. Omit it and the lanes read zero.

`reserveCount` sizes the reservation above `instanceCount` so
`renderer.mesh.setInstanceCount` can raise the drawn count later without
re-registering; both buffers must back the reservation, not just the count.

`mobility` states whether the copies stand still — `"static"`, or
`"movable"` when it is left out. It is what a scene gather collecting
geometry for precomputed lighting admits a population on, the same
declaration `Model.mobility` makes for an entity: the transforms live in a
buffer anything may rewrite between frames, so a population that says
nothing is taken as one that moves.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh the population draws — a `MeshHandle`, the guid
`renderer.mesh.list` hands out, a `MeshCpuHandle` or a mesh `AssetRef`. A
registration holds the mesh on the device for as long as it lives, and takes
a mesh that is currently held off the device — one nothing displays — back
onto it.
- `opts` `any` _(optional)_ — `{ transformBuffer, instanceCount, material, instanceDataBuffer?, reserveCount?, renderLayer?, castsShadows?, mobility? }`.

```lua
local m = renderer.mesh.create({ positions = ..., indices = ... })
local buf = substrate.createBuffer({
name = "crowd.xf", type = "mat4", len = 64, kind = "gpu",
})
-- Column-major: translation lives at indices 13/14/15.
local xf = {}
for i = 0, 63 do
local m4 = { 1,0,0,0, 0,1,0,0, 0,0,1,0, i * 2, 0, 0, 1 }
for _, v in ipairs(m4) do xf[#xf + 1] = v end
end
buf:write(xf)
local rock = asset.resolve("rock", "material"):handle()
local draw = renderer.mesh.drawInstanced(m, { transformBuffer = "crowd.xf", instanceCount = 64, material = rock })
```

## modules/renderer/mesh.dropClusters {#dropclusters}

```lua
mesh.dropClusters(mesh: string | { [string]: any } | AssetRef): boolean
```

Detach a mesh's cluster-LOD hierarchy and cancel a bake still in flight
for it, so the renderer holds none for it. The inverse of
`renderer.mesh.uploadClusters`.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to detach — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
renderer.mesh.dropClusters(gpu)
```

## modules/renderer/mesh.dropInstanced {#dropinstanced}

```lua
mesh.dropInstanced(draw: InstancedDraw): boolean
```

Release an instanced-draw registration and the transform slots it
reserved. The mesh and the transform buffer outlive it — destroy those
through `renderer.destroy` and the buffer handle's `:destroy()`.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to release.

```lua
renderer.mesh.dropInstanced(draw)
```

## modules/renderer/mesh.encode {#encode}

```lua
mesh.encode(geom: MeshGeometry): (string?, string?)
```

Encode raw geometry into engine-native `ZMSH` bytes (the on-disk mesh
payload). The CPU codec behind the mesh assetType's `onCreate`. Every stream
the format carries — including tangents, per-vertex skinning, and the
skeleton — round-trips back through `renderer.mesh.decode`. This pair moves
DATA the caller is holding; the geometry of a mesh the ENGINE is holding
comes from `renderer.mesh.geometry(mesh)`.

**Parameters**

- `geom` `MeshGeometry` — `MeshGeometry` — flat per-vertex float / u32 arrays plus optional `skinning` and `skins`.

```lua
local bytes = renderer.mesh.encode({ positions = {...}, indices = {...} })
local bytes = renderer.mesh.encode(renderer.mesh.geometry(meshHandle))
```

## modules/renderer/mesh.encodeCpu {#encodecpu}

```lua
mesh.encodeCpu(mesh: string | { [string]: any } | AssetRef): string
```

Encode a mesh's resident CPU copy into `ZMSH` bytes. Reads the ONE
guid-keyed CPU store — `meshRef:load()` populates it for assets, and
`renderer.mesh.readback(mesh)` populates it for a runtime mesh. Errors
loudly when the mesh has no resident CPU copy.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
local bytes = renderer.mesh.encodeCpu(handle)
```

## modules/renderer/mesh.geometry {#geometry}

```lua
mesh.geometry(mesh: string | { [string]: any } | AssetRef): MeshGeometry
```

The complete geometry of a mesh the engine is holding, as a
`MeshGeometry` — the same shape `renderer.mesh.create` and
`renderer.mesh.encode` take, carrying every stream the mesh has
(`positions`, `indices`, and whichever of `normals`, `uvs`, `colors`,
`uvs1`, `tangents`, `skinning`, `skins` it was built with). The read that
pairs with `create`: hand it the `MeshHandle` `create` returned and get the
vertex data back. Reads the resident CPU copy when there is one; for a
runtime mesh that lives only on the GPU it reads the geometry back off the
GPU first (yielding a frame or two) and leaves CPU residency as it found it.
An optional stream is present only when the mesh carries one, so `uvs1 ==
nil` is the answer to whether it has a second UV set. The drawable mesh
the renderer holds carries the tangent basis its positions, uvs and normals
determine — supplied by the caller, or derived at the ingest that made it
drawable — and that is what the GPU read gives back. The CPU store answers
with the streams the bytes it decoded hold, so a `.mesh` written without a
tangent stream reads back `tangents == nil` for as long as a CPU copy of it
is resident.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
local geom = renderer.mesh.geometry(renderer.mesh.create({ positions = P, indices = I }))
local tangents = renderer.mesh.geometry(handle).tangents
```

## modules/renderer/mesh.getVertices {#getvertices}

```lua
mesh.getVertices(mesh: string | { [string]: any } | AssetRef): { any }
```

Read the vertices of a mesh's resident CPU copy — one entry per vertex,
`{ pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }`. Reads the resident CPU
store directly (no re-decode). Errors when the mesh has no resident CPU copy
— `renderer.mesh.geometry(mesh)` is the read that works wherever the mesh
lives, and returns the tangent, colour and skinning streams too.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

## modules/renderer/mesh.instanceInfo {#instanceinfo}

```lua
mesh.instanceInfo(draw: InstancedDraw): InstancedDrawInfo?
```

What a live instanced-draw registration is drawing: which mesh, which
transform buffer, which per-instance data buffer if it named one, how many
instances, and how many slots it reserved. Returns nil once the
registration has been dropped.

`status` is what the renderer did with it. The fields above it are the
request, made a stage before the renderer sees it; `status` is the answer:
`"drawing"` for a registration the renderer is drawing, `"refused"` for one
it turned away — `error` carries its reason — and `"pending"` for the frame
between the call and the renderer answering. So a registration whose copies
are not being drawn says so here.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to report on.

```lua
print(renderer.mesh.instanceInfo(draw).instanceCount)
local info = renderer.mesh.instanceInfo(draw)
if info.status == "refused" then error(info.error) end
```

## modules/renderer/mesh.instanceTransforms {#instancetransforms}

```lua
mesh.instanceTransforms(draw: InstancedDraw): any
```

Read back the world matrices a registration's drawn copies are placed
by: `instanceCount` matrices of 16 floats, column-major and tightly
packed, in the layout the transform buffer holds them. The read is of the
buffer as it stands when it runs, so a population a compute pass rewrites
every frame answers with the placement of the frame the read lands in.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` whose copies to locate.

```lua
local pending = renderer.mesh.instanceTransforms(draw)
while not pending:ready() do task.wait() end
local floats = pending:result()
```

## modules/renderer/mesh.isCpuResident {#iscpuresident}

```lua
mesh.isCpuResident(mesh: string | { [string]: any } | AssetRef): boolean
```

True if this mesh has a resident CPU copy in the guid-keyed CPU store.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
if renderer.mesh.isCpuResident(handle) then ... end
```

## modules/renderer/mesh.isResident {#isresident}

```lua
mesh.isResident(mesh: string | { [string]: any } | AssetRef): boolean
```

True if a GPU mesh is resident under this mesh's guid — the device
holds its buffers, or the upload pass is still going to hand them over.
This is the store the draw paths are gated on, so a mesh this reports
resident is one `renderer.mesh.drawInstanced` and a `Model` can draw.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
print(renderer.mesh.isResident(handle))
```

## modules/renderer/mesh.list {#list}

```lua
mesh.list(): { any }
```

Every mesh currently registered, ordered by guid — the ones a script
created and the ones that reached the device through an asset alike.
Answers "which mesh is this?" when all that is known is a size: each entry
carries the guid, the vertex/index counts it was created with, where it came
from (`origin` is `"asset"` for a mesh the asset path uploaded), whether the
GPU still holds it, and where its culling bounds come from (`boundsFrom` is
`"compute"` for a mesh a compute pass writes). A resident entry also carries
the bytes its buffers cost. `bytes` is the mesh's whole VRAM footprint and
is the sum of the THREE buffer columns beside it — `vertexBytes +
vertexStorageBytes + indexBytes`, where the storage column is the same
vertices bound as a storage buffer for the passes that read them that way.
Summing only the vertex and index columns understates a mesh by its vertex
size. The `bytes` column is what sums to the `meshes` category of
`renderer.gpuMemory()`.
`renderer.references("mesh", guid)` says what is still holding a row, and
`renderer.collect()` releases the rows nothing holds.

```lua
for _, m in ipairs(renderer.mesh.list()) do print(m.guid, m.bytes) end
```

## modules/renderer/mesh.listInstanced {#listinstanced}

```lua
mesh.listInstanced(): { InstancedDrawInfo }
```

Every instanced-draw registration this engine is drawing, in
registration order. Each record is what `instanceInfo` answers with, and
carries a `draw` handle of its own — so a population whose handle its
caller no longer holds is reached here and released, resized or read like
any other.

```lua
for _, pop in ipairs(renderer.mesh.listInstanced()) do
renderer.mesh.dropInstanced(pop.draw)
end
```

## modules/renderer/mesh.loadCpu {#loadcpu}

```lua
mesh.loadCpu(ref: string | AssetRef): MeshCpuHandle
```

Load a `.mesh` asset's geometry into the ONE guid-keyed CPU store (the
Disk→CPU step) and return a CPU handle. The handle holds NO geometry — only
the guid plus counts and the per-handle read/encode/unload ops (which read
the Rust-side store). Called by `meshRef:load()`. DEFAULT lifecycle: upload
to the GPU then `handle:unload()`; the store is populated only by this call.

**Parameters**

- `ref` `string | AssetRef` — A mesh `AssetRef` (carries `.guid` and reads its primary via getBytes),
or any string `asset.ref` resolves to one — the guid `encodeCpu` takes, an
identity, a name or a source path.

```lua
local cpu = meshRef:load(); local gpu = renderer.mesh.create(cpu); cpu:unload()
local cpu = renderer.mesh.loadCpu(gpuMesh.guid)
```

## modules/renderer/mesh.morphTargets {#morphtargets}

```lua
mesh.morphTargets(mesh: string | { [string]: any } | AssetRef): { string }
```

The names of the shapes this mesh blends towards, in the order an
entity's `ecs.MorphWeights` addresses them — weight `i` drives the target
named at `i`. An imported model carries the names its source file gave its
blend shapes, so content drives a face by the shape it means rather than by
the ordinal that shape happened to import at (which moves when the model is
re-exported). A target the source never named reads as an empty string.

Empty for a mesh with no morph targets. Errors when the mesh is neither
GPU- nor CPU-resident — materialise it first (`meshRef:handle()`).

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

```lua
for i, name in renderer.mesh.morphTargets(meshRef) do print(i, name) end
```

## modules/renderer/mesh.morphWeights {#morphweights}

```lua
mesh.morphWeights(
```

Turn weights named by shape into the ordered weight array
`ecs.MorphWeights` takes — the drive-a-face-by-name call. Every target the
mesh carries gets a slot; the ones `weights` names take their value and the
rest are 0, so the returned array always describes the whole mesh and a
shape left out is a shape at rest.

A name the mesh does not carry is an error listing the names it does: a
mistyped viseme that silently moved nothing would be indistinguishable from
a rig that never had it.

```lua
local w = renderer.mesh.morphWeights(meshRef, { Eyes_Blink = 1, Mouth_Smile = 0.4 })
ecs.set(face, ecs.MorphWeights { weights = w })
```

## modules/renderer/mesh.readback {#readback}

```lua
mesh.readback(mesh: string | { [string]: any } | AssetRef): MeshCpuHandle
```

Read a runtime GPU mesh's geometry back to CPU and return a
`MeshCpuHandle` for it — the GPU→CPU half of the runtime-mesh freeze path. A
mesh made with `renderer.mesh.create` keeps no CPU copy, so persisting it
(`:encode()` → `asset.create("mesh", …)`) reads it back here first. Yields
until the readback completes (a frame or two). After it returns the geometry
is resident in the guid-keyed CPU store: `:getTriangles`, `:getVertices`,
`:getBounds`, `:geometry`, `:encode`, `:unload` all work. Errors if the mesh
never becomes resident in the vertex pool.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — the `MeshHandle` `renderer.mesh.create` returned, a guid, or a mesh `AssetRef`.

```lua
local cpu = renderer.mesh.readback(handle); local zmsh = cpu:encode(); cpu:unload()
```

## modules/renderer/mesh.readbackPosed {#readbackposed}

```lua
mesh.readbackPosed(requests: { { entity: string, mesh: any } }): { [string]: MeshCpuHandle }
```

Read the POSED geometry of skinned entities back to CPU: for each
request, the vertices the skinning pass wrote for that entity this frame,
joined by the indices of the mesh it is posed from. A skinned surface's
world-space triangles are produced on the GPU from the entity's joint
matrices, so the mesh asset holds the bind pose and only this reads where
the surface actually is. The posed vertices are in model space, so the
entity's own world transform still places them — the same transform the
raster draw uses.

Takes a LIST and answers a map, because the readbacks are queued together
and polled together: a scene's worth of characters costs the frames of one
readback rather than one entity's after another. Each posed mesh lands in
the CPU store under a guid of its own, derived from the entity, so
`compute.buildBvh`, `meshcpu.*` and every other guid-keyed reader takes it
like any other mesh. Call `handle:unload()` when done with it.

An entity the map omits holds no live pose — nothing skinned it this frame,
which is also what makes its draws read the source mesh, so its bind-pose
geometry is what stands for it.

**Parameters**

- `requests` `{ { entity: string, mesh: any } }` — `{ { entity = <id>, mesh = <mesh> } }` — the entity to read, and the mesh it is posed from (a guid, `MeshHandle` or mesh `AssetRef`).

```lua
local posed = renderer.mesh.readbackPosed({ { entity = id, mesh = ecs.get(id, ecs.Mesh).mesh } })
local tris = posed[id]:getTriangles()
```

## modules/renderer/mesh.scheduleClusters {#scheduleclusters}

```lua
mesh.scheduleClusters(mesh: string | { [string]: any } | AssetRef): boolean
```

Queue a cluster-LOD bake for the static CPU mesh held under `guid`, and
attach the DAG to the GPU mesh of that same guid on the frame it finishes.
The CPU mesh may be unloaded on the very next line; the DAG is then built
one bounded slice per frame, so a dense mesh virtualizes without the frame
loop stopping for the whole bake.

One bake is advanced per frame — the one at the head of the queue — and the
geometry is read on the frame a bake gets there, from the definition the
engine holds for the mesh. A queue of meshes the engine holds definitions
for therefore holds one mesh's geometry rather than one per mesh, whatever
its depth. A mesh the engine holds no definition for is copied into the
queue as it is scheduled, since the CPU store is then the only thing
holding it. `renderer.mesh.clusterBakes().heldBytes` reports what the queue
is holding, and its `inFlight` rows report which bakes it is holding for.
This is what the `.mesh` assetType materialisation path uses; reach for
`renderer.mesh.buildClusters` when you want the bytes in hand instead.
Scheduling the same mesh again replaces the bake already in flight for it.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — A mesh loaded into the CPU store (`renderer.mesh.loadCpu`) — the
`MeshCpuHandle`, a `MeshHandle`, a guid, or a mesh `AssetRef`.

```lua
renderer.mesh.scheduleClusters(cpu) ; cpu:unload()
```

## modules/renderer/mesh.setInstanceCount {#setinstancecount}

```lua
mesh.setInstanceCount(draw: InstancedDraw, count: number): InstancedDraw
```

Change how many of a registration's instances draw. Constant time — the
reservation, the transform buffer and the pipeline all stay put, so this is
the verb for a population whose size changes per frame. The new count must
fit the reservation `drawInstanced` was given.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to reconfigure.
- `count` `number` — Instances to draw, at least 1 and within the reservation.

```lua
renderer.mesh.setInstanceCount(draw, visibleCount)
```

## modules/renderer/mesh.setInstanceRenderLayer {#setinstancerenderlayer}

```lua
mesh.setInstanceRenderLayer(draw: InstancedDraw, renderLayer: number): InstancedDraw
```

Change which render layers a registration's copies belong to. Constant
time — the reservation, the transform buffer and the pipeline all stay put,
and the next frame drawn tests the copies against the new membership. It is
the verb for a population that follows something whose membership moves: a
camera or a capture including the layer draws the copies, one excluding it
does not.

**Parameters**

- `draw` `InstancedDraw` — The `InstancedDraw` to reconfigure.
- `renderLayer` `number` — The membership bitmask, the same value `drawInstanced` takes
as `renderLayer`. At least one bit must be set.

```lua
renderer.mesh.setInstanceRenderLayer(draw, mask)
```

## modules/renderer/mesh.setVertices {#setvertices}

```lua
mesh.setVertices(mesh: string | { [string]: any } | AssetRef, positions: { number })
```

Replace a mesh's resident CPU vertex positions (flat `{ x,y,z, ... }`)
IN PLACE — indices, normals/uvs, and skinning are preserved, the AABB
recomputes, and the GPU re-fetches the new geometry so it shows on screen.
The positions alone travel, so this is the per-frame deformation path where
`renderer.mesh.update` re-sends the whole geometry. The mesh must be
CPU-resident: `renderer.mesh.create({ ..., keepCpu = true })` keeps a copy
from the start, `renderer.mesh.readback(mesh)` recovers one from the GPU,
and `meshRef:load()` loads one for a `.mesh` asset. Errors with the reason
otherwise, or when the vertex count doesn't match.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.
- `positions` `{ number }` — Flat `{ x,y,z, ... }` — one xyz per vertex; count must match the mesh.

```lua
local m = renderer.mesh.create({ positions = P, indices = I, keepCpu = true })
renderer.mesh.setVertices(m, P) -- P mutated in place each frame
```

## modules/renderer/mesh.unloadCpu {#unloadcpu}

```lua
mesh.unloadCpu(mesh: string | { [string]: any } | AssetRef)
```

Drop a mesh's resident CPU copy from the guid-keyed CPU store.
The explicit release for a runtime geometry mesh's recoverable definition.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.

## modules/renderer/mesh.update {#update}

```lua
mesh.update(mesh: string | { [string]: any } | AssetRef, src: any): MeshHandle
```

Overwrite the GPU resource `mesh` names IN PLACE, under the same guid,
from new geometry or compute buffers. Never writes a `.mesh` file — the
play-mode mutate path. A Model bound to the guid reflects the change with no
re-bind. Takes every form that names a mesh — the `MeshHandle` `create`
returned, the guid `renderer.mesh.list` hands out, a `MeshCpuHandle` or a
mesh `AssetRef`. Returns a handle carrying the bounds the new geometry has:
the handle it was given, refreshed, and a handle over the guid otherwise.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh to update — a `MeshHandle`, a guid, a `MeshCpuHandle` or a
mesh `AssetRef`.
- `src` `any` _(optional)_ — New geometry `{positions, indices, ...}` or compute buffers
`{vertexBuffer, indexBuffer, vertexCount, indexCount, prevVertexBuffer?}`.

## modules/renderer/mesh.uploadClusters {#uploadclusters}

```lua
mesh.uploadClusters(mesh: string | { [string]: any } | AssetRef, clusters: string): boolean
```

Attach a cluster-LOD DAG (bytes from `renderer.mesh.buildClusters`) to
the GPU mesh keyed by `guid`, enabling the continuous-cut cluster draw path
for that mesh.

**Parameters**

- `mesh` `string | { [string]: any } | AssetRef` — The mesh the clusters belong to — a `MeshHandle`, a `MeshCpuHandle`, a guid, or a mesh `AssetRef`.
- `clusters` `string` — Serialized cluster bytes (binary-safe).

```lua
renderer.mesh.uploadClusters(gpu, cb)
```

## modules/renderer/minScreenSize {#modules-renderer-minscreensize}

```lua
minScreenSize(): number
```

The on-screen radius, in pixels, an object must reach to be drawn. `0`
while the cutoff is off.

```lua
local px = renderer.minScreenSize()
```

## modules/renderer/morphStats {#modules-renderer-morphstats}

```lua
morphStats(): {
```

The morph state the last frame drew with. A mesh carries the shapes it
can blend towards and an entity carries how strongly each is blended
(`ecs.MorphWeights`); where both are present, the vertex stage adds the
weighted deltas to the base geometry.

`instances` is how many render slots that happened at, and `blends` how
many single-target blends those slots carry between them: a slot
contributes one per target its weights move, or that they moved the frame
before, so the number of targets a mesh can be given is bounded by the
buffer the blends live in. `meshes` is how
many meshes hold a delta block and `targets` how many targets those blocks
cover between them; `deltaBytes` is what the shared buffer they are
appended into holds. A morph-target mesh whose weights are all zero reads a
`meshes` above zero beside an `instances` and `blends` of zero.

```lua
local m = renderer.morphStats()
print(("%d instances carrying %d blends over %d meshes"):format(m.instances, m.blends, m.meshes))
```

## modules/renderer/observe {#modules-renderer-observe}

```lua
observe(): RenderObservation
```

Everything the renderer knows about the frame it last drew: what
program it bound for each renderable, the render state it holds each
material under, and what each program has cost in pipeline builds.
`renderables` is one row per renderable in the renderer's draw list,
carrying the program its material named (`requestedProgram`) beside the one
that was bound (`boundProgram`) — `__error__` wherever the lookup missed
and the draw went ahead on the magenta placeholder — plus `substituted`,
the `outcome` (`drew` / `drewPlaceholder` / `skipped` / `notDrawn`), the
`reason` that forced it and the compiler's own `detail` for a failed
compile. `observed` says which of two answers a row is: `true` for a
resolution a geometry pass took as it drew, `false` for the renderer's own
resolution of a renderable this frame drew nowhere, which is what a
renderable outside every camera's frustum or layer mask reports.
`materials` is one row per
material the renderer holds a prepared bind group for; `shaders` is one row
per program pipelines have been built for. `frame` names the frame every
per-frame count covers; `retainedFrames` how many frames a resolution a
pass took is kept for after the last frame that drew it; `window` and
`costWindow` state both in the document itself. Recording is armed by the
first read, so this waits for the frame that first records rather than
answering empty.

```lua
local o = renderer.observe(); print(o.placeholderDraws, "renderables drew the placeholder in frame", o.frame)
```

## modules/renderer/occlusionCulling {#modules-renderer-occlusionculling}

```lua
occlusionCulling(): boolean
```

Whether occlusion culling is currently enabled.

## modules/renderer/passSchedule {#modules-renderer-passschedule}

```lua
passSchedule(): {
```

The schedule check over this frame's enqueued render passes. Passes
declare what they read (`inputs`) and what they write (`output` /
`outputs` / `storage`), and the frame runs them in phase order and, inside
a phase, in `order` order. `violations` holds every input bound to a
resource the frame produces LATER: that read samples the resource as it
stands ahead of that pass, which is the previous frame's contents for a
render target that persists, an empty target for one just created, and the
scene draw's own output for a `@scene.*` buffer — and the pass renders
either way. The frame's own buffers are checked on the same terms as a
render target: bind `@scene.motion` at a phase ahead of the pass that
writes it and the read is reported, naming the buffer and its writer.
A pass reading a resource ahead of that write on purpose declares that slot
in its enqueue's `readsPrevious` and drops out of the list;
`unboundPrevious` holds declared slots the pass binds no such resource to,
which cover nothing.
A resource no queued pass writes is not reported — a camera rendering to
texture and `compute.dispatch` both fill targets outside the pass queue,
and the scene draw fills the `@scene.*` buffers every frame.
A read the frame has only one order for is not reported either: where the
writing pass consumes something the reading pass produces, the reader runs
first or the writer has nothing to write, which is what a pass reading a
buffer into a target of its own and a second pass copying that target back
over the buffer forms.
`unreachable` holds passes at a phase that does not run their kind: every
phase drains its fragment and compute passes, while `afterLighting` is the
one that draws geometry, draw and splat passes, so one of those enqueued
elsewhere sits in the queue and never runs.
Each finding is also stated in the engine log the first time it appears.

```lua
local s = renderer.passSchedule()
for _, v in s.violations do print(v.message) end
```

## modules/renderer/pipelineCache {#modules-renderer-pipelinecache}

```lua
pipelineCache(): PipelineCache?
```

What the driver's compiled-pipeline store held, built, and wrote back.
A pipeline is machine code the GPU driver compiles from the shader bound
into it, and that compile is what a launch pays before the first frame
drawing with each pipeline can appear. The store keeps that compiled code
across runs, so a launch whose shaders have not changed reads back what the
previous one compiled.

`restoredBytes` is what a previous run left for this GPU and this launch
read; `pipelinesBuilt` counts the pipelines built since startup and
`buildMs` is what they cost together, which is the number the store lowers.
`saves` and `savedBytes` describe writing it back — deferred until a burst
of builds settles, so one launch is one write — and `dirty` is true while
pipelines have been built that the file does not hold, including after a
write that failed, which `lastError` then names. `path` is the file, named
after the GPU it belongs to.

`supported` is false where the platform holds no store a program can carry:
a browser keeps its own and hands none out, and an adapter can lack the
capability. `reason` says which, and the build count and timing still read
true there. `lastError` names a read or write failure; a failed store costs
the saved compile and never the frame, since every pipeline is built from
its source either way.
`pipelinesBuilt` and `buildMs` are engine-wide totals; `renderer.shaderCost()`
is the same cost broken down per program, with each one's permutation count.

```lua
local c = renderer.pipelineCache()
print(("%d pipelines in %.1f ms, %d bytes restored"):format(c.pipelinesBuilt, c.buildMs, c.restoredBytes))
```

## modules/renderer/pointShadowBudget {#modules-renderer-pointshadowbudget}

```lua
pointShadowBudget(): PointShadowBudget
```

The point-light shadow pool now in force. A point light with
`castsShadows` renders an omnidirectional cube map, six faces of depth,
and `slots` is how many of them fit — a further caster is lit but throws
no shadow, and the engine log names how many were turned away. The slot
count is bought rather than authored: `megabytes` of VRAM at `resolution`
texels per face is what decides it.

```lua
local p = renderer.pointShadowBudget()
print(("%d shadowed point lights, %.1f MiB"):format(p.slots, p.bytes / 1024 / 1024))
```

## modules/renderer/projectionOffset {#modules-renderer-projectionoffset}

```lua
projectionOffset(): (number, number)
```

The sub-pixel projection offset in force for the main camera, in NDC.

```lua
local ox, oy = renderer.projectionOffset()
```

## modules/renderer/raycast {#modules-renderer-raycast}

```lua
raycast(
```

Cast a ray against the geometry the renderer DRAWS and return the
nearest surface it meets. Every visible mesh answers, whether or not
anything gave it a rigid body — so a terrain, a procedurally generated
mesh, or any plain `Model` reports the surface at a point, which is what a
camera station, a prop, a sound source or a scatter standing on the ground
needs to know. The answer is the nearest triangle of the mesh, so a sloped
or terraced surface reports its height where it was asked rather than the
extent of its bounding box.

`distance` is measured from `origin` along the direction given, so it is a
world-space distance whenever that direction is a unit vector, and it is
directly comparable to a `physics.raycast` distance along the same ray.
`normal` is a unit vector turned to face back along the ray. `exact` is
true when the answer is a triangle and false when it is the object's
bounding box, which is what a mesh whose vertices live only in GPU buffers
answers with. The triangles are the mesh's own, placed by the entity's
transform and by the mesh's bind pose, so a surface a skinning or morph
pass deforms on the GPU answers as the geometry the mesh holds.

EVERYTHING drawn is in scope — the ground you meant, and equally a
character standing on it, a prop, a placeholder floor. The hit names its
entity in `entityId`, `exclude` steps over the ones you do not want, and
`renderer.raycastAll` hands back the whole column so you can pick the
surface yourself. A height you did not expect is usually a nearer surface
you did not mean to ask about, so read `entityId` before trusting a number.

```lua
local hit = renderer.raycast({ x, 100, z }, { 0, -1, 0 }, 200)
if hit then camera.position = { x, hit.point.y + 1.7, z } end
```

## modules/renderer/raycastAll {#modules-renderer-raycastall}

```lua
raycastAll(
```

Cast a ray against the geometry the renderer draws and return every
surface along it, nearest first. One entry per renderable the ray crosses —
the nearest intersection with each — so a stack of surfaces reads as the
order they stand in, and a caller after one particular surface finds it by
`entityId` rather than hoping it is the nearest. Each entry carries the
fields `renderer.raycast` returns.

```lua
for _, hit in renderer.raycastAll(eye, look, 500) do print(hit.entityId, hit.distance) end
```

## modules/renderer/raytraceCapability {#modules-renderer-raytracecapability}

```lua
raytraceCapability(): string
```

The active ray-tracing backend: `"hardware"` (GPU ray query) or
`"compute"` (software traversal — the path on devices without hardware ray
query, e.g. the web). The same ray-tracing features work on both.

```lua
if renderer.raytraceCapability() == "hardware" then ... end
```

## modules/renderer/raytraceStats {#modules-renderer-raytracestats}

```lua
raytraceStats(): { [string]: any }
```

What the ray-tracing acceleration structure holds, and what this
session's frames have spent building it. A ray walks a structure built over
the scene's geometry, and keeping it current is work a frame pays before it
traces anything. On the `"compute"` backend geometry that has stood still
long enough is filed under a static partition the frames after it leave
alone: `staticTriangles` + `dynamicTriangles` = `triangles`, `nodes` is the
hierarchy over them, `fullRebuilds` / `partialRebuilds` / `reusedFrames`
count what the session's frames did, and `trianglesRebuilt` is what those
rebuilds re-emitted, summed. On the `"hardware"` backend `blas` is the
bottom-level structures cached, `blasBuilt` how many the last frame built,
and `tlasInstances` what the top-level structure names. The counters are
cumulative — sample, run the scene, sample again.

```lua
local before = renderer.raytraceStats().trianglesRebuilt
```

## modules/renderer/references {#modules-renderer-references}

```lua
references(handleOrKind: any, id: string?): RuntimeResourceStatus?
```

What holds a runtime resource right now — the answer a root scene load
reads before releasing it. `references` names each live consumer the engine
found: `{ by = "entity", id }` for an entity wearing the material or mesh,
`"material"` for a material whose slot names the texture, `"instancedDraw"`,
`"camera"`, `"sky"`, `"lightmap"`, `"ui"` (a screen drawing it) and
`"postProcess"` (an effect sampling it). `handleHeld` says whether a script
still reaches a handle to it, `assetBacked` whether an asset stands behind
it, `ownerLive` whether the component instance, scene load or feature that
created it still stands, and `held` whether a hold pins it. `origin` reads
`"device"` for a GPU texture the device holds that no script created — the
one the cache loaded for an asset, the atlas the engine built — whose
holders are the references, a handle and the asset. Runs a full garbage
collection first, the same one `renderer.collect` runs, so a handle nothing
reaches counts as let go and the row says what the next collection does
with the resource. Yields for the frame the census runs on.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string?` _(optional)_ — The guid or key, when the first argument is a kind.

```lua
local s = renderer.references(mat) for _, r in s.references do print(r.by, r.id) end
```

## modules/renderer/reflectionEnvironment {#modules-renderer-reflectionenvironment}

```lua
reflectionEnvironment(): {
```

What a reflective surface is reflecting. `probes` is how many reflection
probes the shading blends; they are gathered highest `priority` first, each
rank taking the coverage the ranks above it left, so a small interior probe
ranked above the large exterior one it sits inside wins outright wherever it
reaches full weight. `ranks` is the priority each of those probe slots was
published with, in slot order. `sky` is whether the sky fallback is armed:
with it, coverage no probe claims reflects the captured sky, and without it
a surface outside every probe's radius falls back to the nearest probe
alone. `skyCaptured` is whether the sky slot holds a capture — arming is
refused until it does, since an uncaptured slot reflects black.
`slots` is how many cube slots the environment array holds right now: the
sky's alone, at index `skySlot`, until a probe is captured into it, then
that one plus one per probe. `maxProbes` is how many of them probes may
take, and `resident` whether the array has grown past the sky's single
slot. Capture the sky with `environment.captureSky()`.

```lua
local env = renderer.reflectionEnvironment()
print(("%d probes, sky fallback %s"):format(env.probes, tostring(env.sky)))
```

## modules/renderer/release {#modules-renderer-release}

```lua
release(handleOrKind: any, id: string?): boolean
```

Let go of the hold `renderer.hold` placed. The resource stays until
nothing else holds it and a collection releases it — the one a root scene
load runs, or a direct `renderer.collect()`.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string?` _(optional)_ — The guid or key, when the first argument is a kind.

```lua
renderer.release(tex)
```

## modules/renderer/renderTargetLimits {#modules-renderer-rendertargetlimits}

```lua
renderTargetLimits(): {
```

The size a render target may be on this device. `maxDimension` is the
device's own maximum 2D texture dimension — the largest either side of a
render target may take. `maxPixels` is how many pixels one render target
may hold, so the RGBA8 image it reads back as fits in a single buffer on
every platform the engine runs on, and `maxSquare` is the largest square
that budget buys. A capture, a `renderer.texture.create({ width, height })`
or a render-to-texture camera past either bound is refused at the call with
the reason, so ask here for the size to request.

```lua
local lim = renderer.renderTargetLimits()
local w = math.min(want, lim.maxSquare)
```

## modules/renderer/renderTargets {#modules-renderer-rendertargets}

```lua
renderTargets(): {
```

Every render target the renderer owns and what each one costs, measured
from the texture that is allocated. One row per target, each carrying its
`name`, whether it is `resident`, the `bytes` it holds while it is, its
`width`/`height`/`layers`/`mipLevels`, and `onDemand`.
An `onDemand` target exists only while something needs it: a target nothing
writes into reads `resident = false` and `bytes = 0` and appears again the
frame something writes it, and one sized by content — the reflection-probe
cube array — holds the slots content asked for. The scratch the draws into
a render target have needed is reported as `camera[<handle>].*` rows:
depth and motion vectors under any rasterized pass, and the occlusion
channel and G-buffer over them under a camera's scene render. A draw builds
what it needs, and the set goes once no live camera names the target and
sixty frames have passed without a draw, so a target nothing draws into
carries no such row; the colour image drawn into belongs to the texture
cache and outlives every one of those releases.
`totalBytes` is what the resident targets hold together. Measured at the
end of the last rendered frame.

```lua
local rt = renderer.renderTargets()
print(("render targets: %.1f MiB over %d resident"):format(rt.totalBytes / 1048576, rt.residentCount))
for _, t in rt.targets do
if t.onDemand then print(t.name, t.resident, t.bytes) end
end
```

## modules/renderer/resolutionScale {#modules-renderer-resolutionscale}

```lua
resolutionScale(): number
```

The fraction of the display resolution the scene is currently rendered
at. `1` until something sets it.

```lua
local s = renderer.resolutionScale()
```

## modules/renderer/setAnisotropy {#modules-renderer-setanisotropy}

```lua
setAnisotropy(level: number): number
```

Set the maximum anisotropy material textures are sampled with. Takes
effect on the next frame for content already on screen — no reload, no
texture re-upload. 1 is plain trilinear.

**Parameters**

- `level` `number` — One of 1, 2, 4, 8, 16. Any other value is an error.

```lua
renderer.setAnisotropy(16)
```

## modules/renderer/setBlendedBatching {#modules-renderer-setblendedbatching}

```lua
setBlendedBatching(enabled: boolean): ()
```

Whether neighbours in a view's back-to-front blended order draw
together. On by default: alpha-blended geometry is submitted farthest-first,
and a stretch of neighbours in that order sharing a mesh, a material, a
shader and a pose is submitted as one instanced draw over those neighbours,
which puts the same members on screen in the same order out of a single
submission. A run stops wherever a differently-drawn renderable sorts
between two of its members, and a mesh of several primitives keeps a draw
per renderable — both would otherwise move fragments through each other.
Off, every blended renderable draws on its own at its own slot, so a
transparent crowd costs a draw per member. The image is the same either way,
which is what makes this the comparison a frame suspected of being formed by
the batching is made against; `renderer.drawStats().draws` counts the
difference.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setBlendedBatching(false)  -- a draw per blended renderable
```

## modules/renderer/setDepthPrepass {#modules-renderer-setdepthprepass}

```lua
setDepthPrepass(enabled: boolean): ()
```

Enable or disable the opaque depth pre-pass. While enabled the renderer
resolves opaque depth in its own pass before shading, so each shaded pixel
runs its material once instead of once per surface stacked behind it, and
the resolved depth is what occlusion culling reads. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setDepthPrepass(false) -- shade every layer, for comparison
```

## modules/renderer/setDepthPrepassOrdering {#modules-renderer-setdepthprepassordering}

```lua
setDepthPrepassOrdering(enabled: boolean): ()
```

Submit the depth pre-pass nearest-first. Renderables reach the pre-pass
in the order they were registered, which stands in no relation to where the
camera is: a scene built back-to-front makes every layer write depth and be
overwritten by the layer in front of it. Ordered, the nearest surface
writes first and the surfaces behind it are rejected by the depth test
before they write. The same draws go out either way and the depth that
comes out is the same, so `scene.depth_prepass` in `profiler.gpuFrame()` is
what moves. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setDepthPrepassOrdering(false) -- submit in registration order
```

## modules/renderer/setGpuMemoryTracking {#modules-renderer-setgpumemorytracking}

```lua
setGpuMemoryTracking(frames: number?): number
```

Set how often the GPU allocator sampler reads — one reading every
`frames` frames — or turn it off with 0. It starts at 60, a reading a
second at 60 Hz, so `renderer.gpuMemory().allocator` answers without
anything arming it. Building the ledger walks every live allocation, which
is why it is sampled rather than read every frame; the category figures
cost nothing either way, and a reader between samples sees the most recent
ledger, so a slow interval still answers.

Called with no argument it reports the interval in force and changes
nothing, which is how something that retimes the sampler puts it back
afterwards instead of restoring a number it assumed was the default.

**Parameters**

- `frames` `number?` _(optional)_ — `number?` Frames between readings; 0 turns the sampler off. Omit
to read the interval without changing it.

```lua
renderer.setGpuMemoryTracking(60)
local was = renderer.setGpuMemoryTracking()
renderer.setGpuMemoryTracking(1)
-- ... take a tight reading ...
renderer.setGpuMemoryTracking(was)
```

## modules/renderer/setMaxFramesInFlight {#modules-renderer-setmaxframesinflight}

```lua
setMaxFramesInFlight(frames: number): number
```

Set how many frames of GPU work may be outstanding before the renderer
stops running ahead. One is the least overlap this can express — a frame's
work is waited for as soon as the next frame has been submitted — which is
the lowest latency and the lowest throughput; higher values let a slow
frame build a longer backlog, and that backlog is memory. Takes effect on
the next frame.

Answers the bound after clamping to [1, 8], so asking for more than the
renderer honours reports what you actually got.

**Parameters**

- `frames` `number` — number Frames of GPU work that may be outstanding, 1 through 8.

```lua
renderer.setMaxFramesInFlight(1) -- lowest latency
print(renderer.setMaxFramesInFlight(99), "was clamped")
```

## modules/renderer/setMinScreenSize {#modules-renderer-setminscreensize}

```lua
setMinScreenSize(pixels: number): ()
```

Stop drawing an object once its on-screen radius falls below this many
pixels. A few pixels across, an object carries no detail a viewer can
resolve while still costing a full vertex and submission pass, and the
cutoff drops it from the camera's draws entirely — `0`, the default,
keeps every object however small it lands. Measured from the object's own
bounds against the camera's projection, so the same threshold means the
same apparent size at any distance or field of view. Shadow casters have
their own threshold in `renderer.setShadowCasterCutoff`.

**Parameters**

- `pixels` `number` — `number` — smallest on-screen radius still drawn; 0 disables.

```lua
renderer.setMinScreenSize(4) -- drop anything under 4 px of radius
print(renderer.drawStats().compactedDrawn, "instances survived it")
```

## modules/renderer/setOcclusionCulling {#modules-renderer-setocclusionculling}

```lua
setOcclusionCulling(enabled: boolean): ()
```

Enable or disable occlusion culling. While enabled the renderer reduces
the pre-pass depth into a pyramid each frame and tests every renderable
that cleared the frustum against it, dropping the ones another surface
entirely covers before their geometry is submitted. The pyramid describes
the frame being drawn, so an object that becomes visible this frame is
never held back a frame. Requires the depth pre-pass.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setOcclusionCulling(true); print(renderer.cullStats().occlusionCulled)
```

## modules/renderer/setPointShadowBudget {#modules-renderer-setpointshadowbudget}

```lua
setPointShadowBudget(cfg: {
```

Set how much VRAM the point-light shadow atlas may hold, and at what
per-face resolution. An omitted field keeps its current value. The atlas
is reallocated on the next frame, so `renderer.pointShadowBudget().slots`
reports the new pool one frame later; the returned number is what this
budget buys. Raising `resolution` sharpens every point shadow and spends
the same memory on fewer of them — doubling it quarters the slot count.
Values are clamped: megabytes [1, 1024], resolution [64, 4096], and the
pool never exceeds `renderer.pointShadowBudget().maxSlots`. One slot is
always granted, so a budget too small for a single cube shadows one light
and the pool costs what that slot costs rather than what was asked for —
`{ megabytes = 1, resolution = 4096 }` buys 384 MiB of ceiling. Read
`pointShadowBudget().bytes` back to see what a budget actually bought, and
`renderer.shadowMemory().point` to see what the scene has made resident.

```lua
renderer.setPointShadowBudget({ megabytes = 96 })
renderer.setPointShadowBudget({ resolution = 1024, megabytes = 96 })
```

## modules/renderer/setPresentMode {#modules-renderer-setpresentmode}

```lua
setPresentMode(mode: string): string
```

Set how a presented frame reaches the display. `fifo` queues every frame
and shows it on a vertical blank, which never tears and never drops one;
`mailbox` replaces the queued frame with the newest, which does not tear
and does not hold the renderer to the refresh rate; `immediate` presents as
soon as a frame is ready and can tear; `fifo_relaxed` is `fifo` that tears
rather than stall when a frame misses its blank; `auto_vsync` and
`auto_no_vsync` leave the choice to the backend.

A surface that does not offer the mode presents `fifo` instead, so read
`renderer.framePacing().presentMode` for what took effect and
`.presentModes` for what this surface offers. Takes effect on the next
frame.

**Parameters**

- `mode` `string` — string One of "fifo", "fifo_relaxed", "mailbox", "immediate", "auto_vsync", "auto_no_vsync".

```lua
renderer.setPresentMode("mailbox")
print(renderer.framePacing().presentMode, "is what the surface took")
```

## modules/renderer/setProjectionOffset {#modules-renderer-setprojectionoffset}

```lua
setProjectionOffset(x: number, y: number)
```

Offset the main camera's projection by a sub-pixel amount, in NDC, for
the frames until it is set again. The offset is in NDC because that is the
space it is constant in: one pixel is `2.0 / width` across, so half a pixel
is `1.0 / width`. Velocity (`@scene.motion`) is measured against the
offset-free projection, so a still scene reports no motion however the
samples are placed — and picking resolves a click to the same ray either
way. `(0, 0)` samples pixel centres.

**Parameters**

- `x` `number` — Horizontal offset in NDC. One pixel is `2.0 / width`.
- `y` `number` — Vertical offset in NDC. One pixel is `2.0 / height`.

```lua
renderer.setProjectionOffset(0.5 * 2 / w, -0.25 * 2 / h)
```

## modules/renderer/setRaytrace {#modules-renderer-setraytrace}

```lua
setRaytrace(enabled: boolean): ()
```

Enable or disable GPU ray tracing. While enabled the engine builds the
scene acceleration structure each frame so ray-tracing render features can
trace against it; disabling stops the build (so it costs nothing until a
ray-traced effect is active). Required before any ray-traced shadows / AO /
reflections render.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setRaytrace(true); renderer.feature.create("rt_shadows")
```

## modules/renderer/setResolutionScale {#modules-renderer-setresolutionscale}

```lua
setResolutionScale(scale: number): number
```

Render the scene at a fraction of the display's resolution and present
it at the display's own size. Shading cost scales with pixel count and with
nothing else, so this trades sharpness for frame time without taking
anything out of the scene: at `0.5` the scene rasterizes a quarter of the
pixels. UI and text are unaffected — they are drawn after the scene is
brought back up to size. The scene rows in `profiler.gpuFrame()` are what
move.

**Parameters**

- `scale` `number` — `number` — fraction of the display resolution, clamped to [0.25, 1].

```lua
renderer.setResolutionScale(0.7)
```

## modules/renderer/setShadowCaching {#modules-renderer-setshadowcaching}

```lua
setShadowCaching(enabled: boolean): ()
```

Whether a shadow map that nothing changed is kept rather than drawn
again. On by default: a shadow view — one directional cascade, one atlas
layer of spot tiles, one face of a point light's cube — is rasterized on
the frames its own inputs change and holds the depth it drew on the ones
they do not.
Off, every view is drawn on every pass, which is what a shadow suspected of
holding a stale image is compared against.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setShadowCaching(false)  -- draw every shadow view, every frame
```

## modules/renderer/setShadowCasterBatching {#modules-renderer-setshadowcasterbatching}

```lua
setShadowCasterBatching(enabled: boolean): ()
```

Whether a shadow view draws every caster of one mesh together. On by
default: a view — one directional cascade, one atlas layer of spot tiles,
one face of a point light's cube — submits one draw per geometry over every
caster of it the view admits, wherever those casters sit in render order
and whatever transform slots they hold. Off, a view draws the runs of render-order
neighbours that share a mesh AND hold consecutive slots, so a scene that has
spawned and despawned anything fragments into many more draws. The image is
the same either way, which is what makes this the comparison a shadow
suspected of being placed by the batching is made against.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setShadowCasterBatching(false)  -- draw the runs the scene presents
```

## modules/renderer/setShadowCasterCutoff {#modules-renderer-setshadowcastercutoff}

```lua
setShadowCasterCutoff(cfg: {
```

Set the shadow-caster cutoff. An omitted field keeps its current value,
so a call can adjust one threshold without restating the other. Both are
measured against the camera the frame draws from rather than against each
light, so one setting covers every cascade, spot and cube face, and a
caster that stops casting is one whose shadow the viewer could not have
resolved. `maxDistance` is measured to the near side of the caster's
bounding sphere, so a large object keeps casting while any part of it is in
range. 0 releases a threshold; releasing both draws the casters the frame
drew before either was set.

```lua
renderer.setShadowCasterCutoff({ minRadiusPx = 2 })
renderer.setShadowCasterCutoff({ minRadiusPx = 1.5, maxDistance = 120 })
```

## modules/renderer/setShadowConfig {#modules-renderer-setshadowconfig}

```lua
setShadowConfig(cfg: {
```

Set the directional shadow quality. Any omitted field keeps its current
value, so a call can adjust one knob without restating the rest. Values are
clamped: resolution [64, 8192], cascades [1, 4], distance >= 0, splitLambda
[0, 1], fadeFraction [0, 1], softness [0, 1]. Changing `resolution` or
`cascades` reallocates the depth array; the rest are per-frame values. A
`distance` of 0 hands the range to the frame — the splits are cut over the
depth its own shadow-taking renderables reach — and a positive one caps it,
which is what a scene bounding its shadow cost states.

```lua
renderer.setShadowConfig({ softness = 0.65, fadeFraction = 0.28 })
renderer.setShadowConfig({ cascades = 4, resolution = 2048, distance = 240 })
```

## modules/renderer/setShadowHero {#modules-renderer-setshadowhero}

```lua
setShadowHero(entity: string, padding: number?): ()
```

Give one caster a directional shadow view of its own, fit to its world
bounds.

A cascade covers the slab of world the camera sees, so its texels are spread
over tens of metres and one character standing in the middle of it is
resolved by a handful of them. The hero view is the same light and the same
depth range zoomed onto that entity's bounds, so the whole map goes into the
shadow it and the ground under it carry — `renderer.shadowHero().zoom` is
the factor its texel density gains.

It renders beside the cascades, into a layer of the same texture allocated
while a hero is registered, and every surface inside it reads it in place of
the cascade, crossing back at its edge. Nothing else about the shadow
changes: the same casters reach it, at the same depth range, through the
same filter.

**Parameters**

- `entity` `string` — The entity whose renderables the view is fit around.
- `padding` `number?` _(optional)_ — How much room the fit leaves around those bounds — for a pose that
leaves the bind-pose box and for the filter that samples outside a
silhouette. 1.0 fits them exactly.

```lua
renderer.setShadowHero(player.id)
print(("hero shadow: %.1f texels/unit vs %.1f"):format(
renderer.shadowHero().texelsPerUnit, renderer.shadowHero().cascadeTexelsPerUnit))
```

## modules/renderer/setShadowProxy {#modules-renderer-setshadowproxy}

```lua
setShadowProxy(mesh: string, proxy: string): ()
```

Rasterize `proxy` in place of `mesh` in every shadow view. A shadow is a
silhouette resolved at the resolution of a shadow map, so the triangles that
carry a mesh's close-up detail write depth no reader can resolve — a
decimated version of the shape, a level of its own LOD chain, or a
hand-built hull casts the same shadow for a fraction of the geometry.

The registration is keyed by MESH, so one call covers every instance of it —
entities and GPU-driven populations alike — and a crowd sharing that mesh
stays one draw. The proxy is placed by whatever places the caster, its
instance's own transforms, so it stands where the caster stands, at the
caster's scale.

An entity caster keeps its own geometry where a stand-in could not be placed
or deformed correctly: it is skinned (it rasterizes the post-skinned
vertices written for its own mesh), it blends morph targets (whose deltas
describe its own mesh and are read by vertex id), or its proxy would be
placed by a different node of its model than the source mesh is. Either
caster keeps it where the renderer holds no geometry under the proxy's
guid. Each of those is counted in `renderer.shadowProxies()`.

Nothing else in the scene draws a proxy, so this call is what brings it onto
the GPU, and it raises where it cannot. A proxy already resident there is
registered as it stands.

**Parameters**

- `mesh` `string` — The mesh a caster draws, as a guid or any mesh reference.
- `proxy` `string` — The mesh it rasterizes into shadow views instead.

```lua
renderer.setShadowProxy(statueMesh, statueHullMesh)
print(renderer.shadowProxies().triangles, "vs", renderer.shadowProxies().sourceTriangles)
```

## modules/renderer/setSkinnedBatching {#modules-renderer-setskinnedbatching}

```lua
setSkinnedBatching(enabled: boolean): ()
```

Whether skinned instances holding one pose draw together. On by default:
instances of one mesh wearing one material and posed alike read the same
post-skinned vertices, so the camera's colour passes submit them as a single
instanced draw, and so does each shadow view and the velocity pass while
`renderer.shadowCasterBatching()` is on — that switch is what makes a depth
view form its draws by geometry at all. The camera depth pre-pass submits
its casters nearest-first, which is a run per span of neighbours rather than
a draw per geometry, so a crowd costs a draw per member there. Off, each
skinned instance draws on its own at its own slot in every pass that
rasterizes it. The image is the same either way, which is what makes this
the comparison a frame suspected of being formed by the batching is made
against — `renderer.drawStats().draws` counts the difference and
`renderer.skinningStats().poses` says how many distinct poses it holds.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setSkinnedBatching(false)  -- a draw per skinned instance
```

## modules/renderer/setSkinningPoseHold {#modules-renderer-setskinningposehold}

```lua
setSkinningPoseHold(enabled: boolean): ()
```

Whether a pose the skinning pass already wrote is read as it stands. On
by default: the pass produces an instance's vertices from its joint
matrices, its node transforms, its blend weight and its blend model, so the
slice holding a pose already holds what running the pass over those same
inputs would write. A frame binding a pose whose slice still holds it reads
the slice and dispatches nothing, and skinning costs what the frame's poses
CHANGED — a paused clip, a skeleton nothing drives, a cast standing in one
pose, each cost compute the frame the pose arrived and nothing after it.
Off, every pose a frame binds is dispatched again, which is the comparison a
frame suspected of reading a slice that no longer holds its pose is made
against; the image is the same either way and
`renderer.skinningStats()` counts the difference as `dispatches` against
`held`. A mesh whose vertices a compute pass writes is dispatched every
frame however this stands.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setSkinningPoseHold(false)  -- dispatch every pose, every frame
```

## modules/renderer/setSpotShadowBudget {#modules-renderer-setspotshadowbudget}

```lua
setSpotShadowBudget(cfg: {
```

Set how much VRAM the spot/area shadow atlas may hold, and the per-side
resolution of one layer. An omitted field keeps its current value. The
atlas is reallocated on the next frame, so `renderer.spotShadowBudget()`
reports it one frame later; the returned number is what this budget buys.
Raising `resolution` sharpens the lights that cover the most screen and
spends the same memory on fewer layers — doubling it quarters the layer
count. Raising `megabytes` buys layers, which is what lets several lights
hold a large tile at once. Values are clamped: megabytes [1, 1024],
resolution [64, 4096], and the atlas never exceeds
`spotShadowBudget().maxLayers`. One layer is always granted, so a budget
too small for one still shadows lights and the atlas costs what that layer
costs rather than what was asked for.

```lua
renderer.setSpotShadowBudget({ megabytes = 64 })
renderer.setSpotShadowBudget({ resolution = 2048, megabytes = 64 })
```

## modules/renderer/setTextureBudget {#modules-renderer-settexturebudget}

```lua
setTextureBudget(opts: TextureBudgetOpts): TextureBudget
```

Bound the VRAM a world's textures occupy, by keeping only the mip levels
the frame is actually sampling. Pass `{ megabytes = 256 }`; `0` — the
default — leaves texture residency alone and every texture stays fully
resident the way it uploaded.

With a budget armed, each frame measures how many screen pixels ONE
traversal of a texture's coordinate range covers on the surface that spans
it widest, and asks for the mip level that serves that span one texel per
pixel — the level the GPU picks from the fragment's own derivatives. A
material with `uvScale = 8` lays eight copies of its texture across a
surface, so each copy spans an eighth of the surface and asks for three
levels coarser than the surface's own size would. A shader that declares
`// @uv_space: world` advances its coordinate over world units rather than
over the mesh's UVs, so how many copies a surface carries follows how large
that surface is. The textures whose surfaces cover the fewest pixels give
up levels until the set fits. Detail climbs one level per frame, from the
image already on screen, so a surface the camera approaches sharpens rather
than popping, and no texture is taken below the level whose longest side is
64 texels.

`bias` shifts every measurement by whole mip levels either way — negative
for finer than the sampling implies, positive for coarser — over a world
whose look wants a different trade than one texel per pixel.

The plan moves a texture whose demand the frame can measure: one at least
256 texels on its narrowest side, worn by a surface an entity draws. A
texture a UI image, a post-process property or a render feature holds a
view of stays whole, because nothing measures how much of the screen those
cover.

Which textures the budget governs follows the surfaces the frame draws. A
texture whose asset still holds its bytes is enrolled the frame a measured
surface wears it — whenever it loaded, and whenever the budget was armed —
because a level change reads the levels it needs back from the asset; when
the last such surface goes it leaves the set whole, at the level it
uploaded at, and a surface reaching it again takes it back up. A texture a
script uploaded has its pixels nowhere else, so one enrolled while it is
resident holds them in system memory
(`renderer.textureMemory().streamSourceBytes`) from the upload until a
surface has worn it and gone, and releases them then, which is what keeps
it out for the rest of the session; one whose pixels were already released
when the budget was armed is out from the start.
`renderer.textureMemory().pinnedTextures` counts those, together with the
textures whose asset could not be read back and the ones a UI image, a
post-process property or a render feature holds a view of. Disarming
returns every texture to the level it uploaded at, and arming again governs
the textures the frame's surfaces are wearing then.

**Parameters**

- `opts` `TextureBudgetOpts` — `{ megabytes: number?, bias: number? }`

```lua
renderer.setTextureBudget({ megabytes = 128 })
renderer.setTextureBudget({ megabytes = 128, bias = -1 })  -- one level finer everywhere
renderer.setTextureBudget({ megabytes = 0 })               -- leave residency alone
```

## modules/renderer/setTransmissionShadows {#modules-renderer-settransmissionshadows}

```lua
setTransmissionShadows(enabled: boolean): ()
```

Let translucent casters tint the sunlight they block instead of blocking
it outright. A shadow map holds one depth per texel and is compared as a
yes-or-no test, so stained glass, water and thin fabric all project the same
black silhouette a wall does. With this on, a caster whose material declares
opacity (`base_color` alpha under a transparent blend) or `transmission`
also draws into a light-space transmittance map, and the colour it lets
through multiplies into the directional light reaching whatever stands
behind it. Stacked casters compose. Opaque casters are unaffected, and a
scene with no translucent caster allocates nothing and records no pass.

**Parameters**

- `enabled` `boolean` — `boolean`

```lua
renderer.setTransmissionShadows(true)  -- stained glass tints the floor
```

## modules/renderer/shaderCache {#modules-renderer-shadercache}

```lua
shaderCache(): ShaderCache?
```

What the shader compile gate's store of baked WGSL held, answered and
wrote back. Compiling a `.shader` wraps the author's body in its framework,
expands every `#include`, and hands the result to naga to parse and
validate — work that is a pure function of the text going in, and that a
launch would otherwise repeat for every shader it draws with. The store
keeps that baked text across launches.

`restoredEntries` and `restoredBytes` are what a previous launch left that
this one read back. `hits` counts the compiles answered out of the store
and `misses` those that ran in full; `savedMs` sums what each hit's own
recorded compile had cost, against `compileMs`, what the misses spent.
`stale` counts the misses whose key was held but whose `#include`d modules
had changed underneath — an entry records every module its expansion
consumed, so editing a module invalidates exactly the shaders that included
it and leaves the rest.

`entries` and `bytes` are what the store now holds, `evictions` how many a
write dropped to stay inside its bounds, and `saves` / `savedBytes` /
`dirty` describe writing it back, deferred until a burst of compiles
settles. `persistent` is false where a launch has nowhere to keep
artifacts and `reason` says why; `location` is the file, or the browser
store, they are kept in. `restoreState` is how the read of what a previous
launch left has gone — `pending` while it is still out (a browser answers
through a promise, so a launch reaches its first frames before it lands),
`restored` once entries came back, `empty` when there were none to come
back, `failed` when what was there could not be read, and `none` where a
launch keeps nothing. A cold, missing or corrupt store leaves every
shader compiling from source with identical output, and `lastError` then
names what went wrong.

```lua
local c = renderer.shaderCache()
print(("%d hits, %d misses, %.1f ms saved"):format(c.hits, c.misses, c.savedMs))
```

## modules/renderer/shaderCost {#modules-renderer-shadercost}

```lua
shaderCost(): { ShaderCost }
```

What each program has cost in pipeline builds, beside the compile
gate's most recent word about it. `variants` is how many pipelines this
engine has built for it — one per (target format, vertex layout,
render-state key) permutation reached — and `buildMs` what those builds
cost, both summed since engine start. A pipeline the driver's own store
restored is not built and so is not counted, so a second launch on the same
adapter reports less than the first. `status` is `compiled`, `failed` or
`pending`, and `error` carries the compiler's message for a failure.
Ordered by cost, most expensive first.

```lua
local top = renderer.shaderCost()[1]; print(top.shader, top.variants, top.buildMs)
```

## modules/renderer/shaderVariants {#modules-renderer-shadervariants}

```lua
shaderVariants(): { ShaderVariants }
```

Every shader that declares optional features, and the programs its
materials have made it compile. Each row carries the features the shader
declares, the base program it ships as, and one entry per variant with the
features that variant holds — so the permutation count a scene's materials
are spending is a number to read rather than something to infer from
compile time. A shader whose variants reach `budget` compiles no more; the
materials asking for further feature sets draw with the base program.

```lua
for _, s in renderer.shaderVariants() do print(s.shader, #s.variants, s.budget) end
```

## modules/renderer/shadingOf {#modules-renderer-shadingof}

```lua
shadingOf(subject: string | { [string]: any }): ShadingReading
```

What the renderer is shading ONE subject with, taken from the document
the renderer publishes — the call a system holding a handle makes to find
out whether what reaches the screen is its own material or the magenta
placeholder standing in for it, without reading the engine log. `subject` is
an entity that draws or the registry key of a material. `state` reads
`itsMaterial` where the renderer bound the program the material names,
`errorMaterial` where it bound the placeholder instead, `stalePipeline`
where the pipeline drawing it was built before that program's most recent
compile, `nothingBound` where the renderer resolved no pipeline for it,
`pending` where this call is the one that armed per-draw recording and the
frame after it publishes, and `unknown` where the renderer holds a
resolution under no such subject. A fault state carries the renderer's own
`reason` from the closed set `renderer.drawDiagnostics()` names — plus
`materialNotPrepared`, which a material subject reads where the renderer
prepared nothing under that key — the compiler's `detail`, the `program`
the material asked for and the `bound` one; `means` states the reading in a
sentence. A material subject answers from the renderables drawing with it,
and from the renderer's record for the material itself where a draw
registered against the material carries no row of its own; a subject that
several renderables draw answers with a refused one wherever there is one.
The reading follows the renderer, so a program that compiles on a later
edit puts the subject back on `itsMaterial` from the frame the renderer
draws it with again.

**Parameters**

- `subject` `string | { [string]: any }` — The entity — a proxy from `entity(...)` or an entity-id string —
or the material, as its registry key or the `MaterialHandle`
`renderer.material.create` returned.

```lua
local s = renderer.shadingOf(emitter); if s.state ~= "itsMaterial" then print(s.means) end
```

## modules/renderer/shadowCacheStats {#modules-renderer-shadowcachestats}

```lua
shadowCacheStats(): {
```

What the last frame did with the shadow maps it already had. A shadow
view — one directional cascade, one atlas layer of spot tiles, one face of
a point light's cube — is drawn again only when something it draws from
changed:
its light moved, a caster it can see moved or appeared or vanished, a
caster's geometry or material changed, a caster changed pose or moved the
nodes its parts are placed by, or the map it writes into was reallocated.
Anything else keeps the depth already in the texture, so a scene that stops
moving reads `rendered` 0 while `cached` keeps climbing. A mesh whose
vertices a compute pass writes — a population, or a mesh built from a
compute buffer — re-renders the views it stands in every frame. A shadowed
point light contributes six views, one per cube face, so a caster moving on
one side of it re-renders the face that can see it and leaves the other
five holding what they have. Counted per light kind, plus the totals across
all three.

These are totals over every view of a kind. `renderer.shadowViews()` is the
same frame one view at a time, each row naming the light that owns it and
what it drew.

```lua
local s = renderer.shadowCacheStats()
print(("shadow views: %d drawn, %d cached"):format(s.rendered, s.cached))
```

## modules/renderer/shadowCaching {#modules-renderer-shadowcaching}

```lua
shadowCaching(): boolean
```

Whether a shadow view may keep the depth it already holds.

## modules/renderer/shadowCasterBatching {#modules-renderer-shadowcasterbatching}

```lua
shadowCasterBatching(): boolean
```

Whether a shadow view draws every caster of one mesh together.

## modules/renderer/shadowCasterCutoff {#modules-renderer-shadowcastercutoff}

```lua
shadowCasterCutoff(): ShadowCasterCutoff
```

How small, and how far away, a caster may get before it stops writing
depth into any shadow view. A shadow view rasterizes a caster's whole
triangle count whatever the shadow it produces ends up covering, so an
object the viewer resolves a fraction of a pixel of, and one past the range
the scene cares about, each cost a full depth pass per shadowed light for
detail nothing reads. Both thresholds are 0 — released — until something
sets them.

```lua
local c = renderer.shadowCasterCutoff(); print(c.minRadiusPx, c.maxDistance)
```

## modules/renderer/shadowConfig {#modules-renderer-shadowconfig}

```lua
shadowConfig(): ShadowConfig
```

The directional shadow quality now in force. `resolution` and `cascades`
size the cascade depth array; `distance` and `splitLambda` place the splits
along the view; `fadeFraction` and `softness` shape how the result is
sampled.

```lua
print(renderer.shadowConfig().cascades)
```

## modules/renderer/shadowHero {#modules-renderer-shadowhero}

```lua
shadowHero(): ShadowHeroReport
```

The registered hero caster and what the last frame's fit produced. A
frame that fit nothing says why in `decline`.

```lua
local h = renderer.shadowHero()
print(h.active, h.zoom, h.decline)
```

## modules/renderer/shadowMemory {#modules-renderer-shadowmemory}

```lua
shadowMemory(): {
```

How much GPU memory the shadow maps hold right now, in bytes, by the
light kind that owns them. The spot atlas and the point pool are sized to
the casters in the scene rather than to the budget, so `spot` and `point`
move as lights that cast shadows appear and leave, and a scene with one
shadowed light holds far less than one that fills every slot. A budget is
the ceiling they grow within — `renderer.spotShadowBudget().layers` and
`renderer.pointShadowBudget().slots` report that ceiling, unmoved by how
many casters exist. Raising shadow resolution costs the square of the
change across every cascade.

```lua
local m = renderer.shadowMemory()
print(("shadows: %.1f MiB"):format(m.total / 1024 / 1024))
```

## modules/renderer/shadowProxies {#modules-renderer-shadowproxies}

```lua
shadowProxies(): ShadowProxyReport
```

The shadow proxies in force and what the last frame's shadow passes did
with them. `triangles` and `sourceTriangles` are what those passes
submitted and what they would have submitted from the source meshes — the
before/after of every registration, equal while nothing is proxied.

```lua
local p = renderer.shadowProxies()
print(("shadow triangles: %d of %d"):format(p.triangles, p.sourceTriangles))
```

## modules/renderer/shadowViews {#modules-renderer-shadowviews}

```lua
shadowViews(): ShadowViewReport?
```

Every shadow view the last rendered frame considered, and what each one
cost.

A frame rasterizes a depth view per directional cascade, one for the hero
caster, one per shadow-casting spot and six per shadow-casting point.
`renderer.shadowCacheStats()` counts those views by light kind,
`renderer.drawStats()` sums their draws with the camera's, and
`profiler.gpuFrame()` carries one `scene.shadow` span across all of them.
This is the same frame read one view at a time.

Each row names the view and the light that owns it, says whether it drew or
kept the depth it already held, and carries the draws, the instances and the
casters that went into it. `span` is the label the view's pass is timed
under, so its GPU time is a lookup in `profiler.gpuFrame()`; every one of
those labels is a variant of `scene.shadow`, which still carries their
total. `camera` carries the same instance counters for the main camera, so
the camera's share of a frame-wide total is a read rather than a measurement
taken by turning every light's shadow off.

A cascade's `near` and `far` are where the split scheme cut its slice, not
the world it covers: the fit takes the bounding sphere of that slice and
rasterizes the ortho box around it, and both reach past `far`. What the
cascade covers is `center` and `radius`, with `viewProj` the exact test;
`coversNear` and `coversFar` read that volume back along one ray, the
camera's view axis. `directional` states the axis reading for the set —
how far it reaches (`coversFar`), the range the splits were run over
(`distance`), how far the camera draws (`cameraFar`), and the
depth past the reach the camera still draws (`uncovered`). A receiver
further along the axis than `coversFar` has no directional depth map over
it and is shaded as if the sun reached it, so `uncovered` is the room a
missing shadow has and a surface standing in that room is what makes one;
`@builtin::systems.proxyOcclusion` occludes past the cascades. The box is
bounded in every direction, so a receiver standing wide of the axis leaves
it at its own distance even where `uncovered` is 0 — `viewProj` is what
answers for that receiver.

The list is rebuilt every frame: a view whose light stopped casting is
absent from the next report rather than standing at the numbers it last had,
and a frame that drew no shadow view answers a report whose `views` is
empty. `views` grouped the way the shadow cache decides — a row per cascade,
per spot atlas layer, per point cube — counts what
`renderer.shadowCacheStats()` reports as `rendered + cached`.

The frame names its views only while something is reading them, so this
call asks the frames after it to name theirs and waits out the first one.
Nil on an engine that renders no frame at all.

```lua
local report = renderer.shadowViews()
print(("camera submitted %d of %d instances"):format(
report.camera.submittedInstances, renderer.drawStats().compactedDrawn))
for _, view in report.views do
print(("%s %d (%s): %d draws, %d instances, %s"):format(
view.role, view.index, view.light, view.draws, view.instances,
view.rendered and "drew" or "cached"))
end
local d = report.directional
if d ~= nil and d.uncovered > 0 then
print(("sun shadows stop at %.0f; the camera draws to %.0f"):format(
d.coversFar, d.cameraFar))
end
```

## modules/renderer/skinnedBatching {#modules-renderer-skinnedbatching}

```lua
skinnedBatching(): boolean
```

Whether skinned instances holding one pose draw together.

## modules/renderer/skinningPoseHold {#modules-renderer-skinningposehold}

```lua
skinningPoseHold(): boolean
```

Whether a pose already written into its slice skips its dispatch.

## modules/renderer/skinningStats {#modules-renderer-skinningstats}

```lua
skinningStats(): {
```

What the last frame's skinned instances cost. A skinned instance is
posed by a compute pass that writes its vertices into a shared pool, and
instances holding the same pose read one slice of that pool and the single
dispatch that fills it. `instances` is how many were posed, `poses` how
many distinct poses they held, and `dispatches` how many dispatches those
poses cost this frame — so a crowd whose members move together costs what
its poses cost rather than what its head count does, while members at
different animation times each hold their own pose and pay for it.

`held` is how many of the frame's poses cost no dispatch at all. The pass
produces a slice from what the pose is made of, so a slice an earlier frame
filled already holds what running it again would write, and a pose still
wearing that slice is read as it stands. Skinning is paid for by the poses
that CHANGED: a cast standing still reads `dispatches` 0 beside a `held`
equal to its `poses`, and the two add up to `poses` in any frame.

`reusedSlices` is how many of the frame's poses took a slice the pool
already held — one a retired pose gave back, or one a pose nothing has
asked for this frame was holding — rather than one cut from pool the
engine had never used. A scene whose poses keep changing reads a non-zero
count beside a `poolBytes` that stays where it was.

`liveBytes` is what the slices holding this frame's poses occupy, against
`unsharedBytes` — what the same instances would occupy with a slice each.
`poolBytes` is what the pool holds; a previous-position buffer of the same
size rides alongside it so skinned deformation reaches motion vectors.

```lua
local s = renderer.skinningStats()
print(("%d instances over %d poses"):format(s.instances, s.poses))
print(("%d poses dispatched, %d read as they stood"):format(s.dispatches, s.held))
print(("skinned vertex storage: %d B of an unshared %d B"):format(s.liveBytes, s.unsharedBytes))
```

## modules/renderer/splat.components {#components}

```lua
splat.components(bytes: any, convention: string?): (SplatComponents?, string?)
```

Decode a Gaussian splat capture — a Niantic `.spz` (gzipped or raw) or a
3DGS `.ply` — into the GPU-ready byte pools a render feature uploads.
`records` is the packed splat array at `recordBytes` per splat (position,
log scale, quaternion, DC colour + opacity); `sh` is the quantized
higher-order spherical-harmonics pool at `shStrideWords` u32 words per
splat, empty at degree 0. A pure decode (no GPU work): upload the pools with
`shaderRef:createBuffer` + `buf:writeBytes` and draw them with a
`kind = "splat"`, `channel = "gaussian"` pass.

**Parameters**

- `bytes` `any` _(optional)_ — Capture bytes — `.spz` or `.ply`, as a `buffer` or a binary string.
- `convention` `string?` _(optional)_ — Source axis convention: `"rightDownFront"` (the default, what
COLMAP-trained captures use) or `"engineNative"` for a capture already in
engine space.

```lua
local c = renderer.splat.components(vfs.readBytes("captures/ceramic.spz"))
```

## modules/renderer/spotShadowBudget {#modules-renderer-spotshadowbudget}

```lua
spotShadowBudget(): SpotShadowBudget
```

The spot and area-light shadow atlas now in force. Each shadow-casting
spot is given a tile of it every frame, sized to what the camera can
resolve: a light filling the view gets a whole layer at `resolution`, one
far away gets a `minResolution` tile, and the atlas holds `tiles` of the
smallest kind. That is what lets one budget serve a close hero light and a
street of distant ones without either the memory or the sharpness being set
for the worst case.

```lua
local s = renderer.spotShadowBudget()
print(("%d layers of %d, %.1f MiB"):format(s.layers, s.resolution, s.bytes / 1024 / 1024))
```

## modules/renderer/temporal.held {#held}

```lua
temporal.held(): boolean
```

Whether a hold is pinning the per-frame clock right now.

```lua
if renderer.temporal.held() then print("frame is pinned") end
```

## modules/renderer/temporal.hold {#hold}

```lua
temporal.hold(at: number?, options: TemporalHoldOptions?): () -> ()
```

Pin the clock every per-frame effect draws itself against, and return
the release. While the hold stands, `renderer.temporal.now` answers `at`
instead of the running clock, so film grain and every other field redrawn
each frame is redrawn as the same field. Two renders taken under holds at
the same instant therefore agree pixel for pixel wherever the scene itself
has not moved, which is what makes one frame comparable with another.
Holds nest: the innermost names the instant, and the clock runs again once
the last release is called. Each release takes its own hold off the stack
whatever order the releases come in, so two callers holding at once — two
captures in flight together — each end their own hold and the clock runs
again when both have.
`exclusive` takes the clock for the `owner` key the call states: while
that hold stands, a hold is admitted only when it states the same key, and
every other one is refused with an error naming the key and the instant
holding it. That is what lets one caller wind the clock to the second it
means to photograph and keep it there while another agent drives the same
engine. The key is what an owner presents to take a nested hold of its
own, and what `renderer.temporal.release` hands the clock back by. A
capture taken while the hold stands renders at the held instant; a
`deterministic` capture takes a hold of its own that states no key, so it
runs once the clock is handed back.

**Parameters**

- `at` `number?` _(optional)_ — The instant to pin the clock at, in seconds. Two holds that state the
same instant produce the same field; the default 0 is that shared instant.
- `options` `TemporalHoldOptions?` _(optional)_ — `owner` is the key this hold is taken under, and an exclusive
hold states one. A hold that states no key is labelled with the agent the
call is attributed to, which is the account the caller presented a token
for and is shared by every session driving this engine under it.
`exclusive` takes the clock for the stated key until the hold is released.

**Returns** `()` — A function that releases this hold. Calling it twice releases once.

```lua
local release = renderer.temporal.hold() ; local png = tools.use("capture", "fromPosition", { position = { 0, 2, 8 } }) ; release()
local release = renderer.temporal.hold(46.0, { exclusive = true, owner = "stage-air" })
```

## modules/renderer/temporal.now {#now}

```lua
temporal.now(): number
```

The instant a per-frame effect should draw itself at: the innermost
hold's instant while one stands, and seconds since boot otherwise. A
system that redraws a field every frame reads this rather than the running
clock, and a capture asking for a repeatable frame then gets one.

```lua
local params = { grainTime = renderer.temporal.now() }
```

## modules/renderer/temporal.onChange {#onchange}

```lua
temporal.onChange(listener: (number) -> ()): () -> ()
```

Register a listener called with the pinned instant whenever it changes
— a hold taken, a hold released — and return the unsubscribe. A system
whose shader reads the clock out of a GPU buffer registers here, so the
buffer carries the pinned instant before the frame that hold was taken on
is drawn rather than a frame later.

**Parameters**

- `listener` `(number) -> ()` — Called with the instant now in force, in seconds.

**Returns** `()` — A function that removes this listener.

```lua
local stop = renderer.temporal.onChange(function(t) pushClock(t) end)
```

## modules/renderer/temporal.owner {#owner}

```lua
temporal.owner(): { id: string?, name: string?, at: number, exclusive: boolean }?
```

The hold naming the instant the clock answers right now: who took it,
what instant it pinned, and whether it took the clock exclusively. Several
agents drive one engine at once and a hold any of them takes moves the
clock every registered field is redrawn against, so this is how a caller
sees that another agent holds it before its own instant is quietly
replaced — and, when `exclusive` is true, `id` is the key a hold of its
own states to be admitted, and the key `renderer.temporal.release` hands
the clock back by. `id` and `name` are nil for a hold that stated no key
and that the engine attributes to no agent.

```lua
local who = renderer.temporal.owner()
if who ~= nil and who.exclusive then print(who.name, "holds the clock at", who.at) end
```

## modules/renderer/temporal.release {#release}

```lua
temporal.release(owner: string): number
```

Hand the clock back by the key its holds were taken under, and report
how many came off. A hold stands until its release is called, and the
release is a closure the call that took the hold holds: a caller that
takes a hold in one call and comes back in another, and a task that ends
between the two, both leave the clock pinned with nobody holding a release
for it. Naming the key is how the clock runs again, and how a caller
refused by an exclusive hold takes one over.

**Parameters**

- `owner` `string` — The key the holds to release were taken under — what `owner`
stated when they were taken, which `renderer.temporal.owner` reports.

```lua
renderer.temporal.release("stage-air")
```

## modules/renderer/texture.capture {#capture}

```lua
texture.capture(texture: string | { [string]: any } | AssetRef): string
```

Request a CPU readback of the GPU texture `texture` names (e.g. a
camera's rendered output). Returns a result key to pass to a
TextureCpuHandle's `:encode()` once the readback completes. Takes every form
that names a texture — the `TextureHandle` `create` returned, the guid
`renderer.texture.list` hands out, a `TextureCpuHandle` or a texture
`AssetRef`.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to read back — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.

```lua
local key = renderer.texture.capture(cameraTarget)
```

## modules/renderer/texture.cpuCreate {#cpucreate}

```lua
texture.cpuCreate(width: number, height: number, fill: any?): TextureCpuHandle
```

Allocate a blank CPU image (RGBA8) filled with a solid colour and return a
`TextureCpuHandle`. Compose into it with `canvas:blit(src, x, y, w, h)`, then
`canvas:encodeJpeg()` / `:encodePng()` for the bytes; `:unload()` drops it.

**Parameters**

- `width` `number` — number Canvas width in pixels.
- `height` `number` — number Canvas height in pixels.
- `fill` `any?` _(optional)_ — Optional `{ r, g, b, a }` (0-255) solid fill; defaults to opaque white.

```lua
local c = renderer.texture.cpuCreate(1024, 576, { 255, 255, 255, 255 })
```

## modules/renderer/texture.cpuFromBytes {#cpufrombytes}

```lua
texture.cpuFromBytes(bytes: buffer | string, encodeOpts: any?): TextureCpuHandle
```

Load engine-native ZTEX bytes — or an encoded image (png / jpg / webp)
— into the CPU store under a fresh guid and answer the CPU handle, for
pixels that come from somewhere other than a texture asset: a `data.ztex`
read as a file, a payload held in memory. The pixels stay at the format
they were encoded in. DEFAULT: `handle:unload()` once done with them.

**Parameters**

- `bytes` `buffer | string` — The ZTEX or image bytes.
- `encodeOpts` `any?` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }` applied
when the bytes are an encoded image and need the engine-native encode.

```lua
local cpu = renderer.texture.cpuFromBytes(vfs.read(path)); local png = cpu:encodePng(); cpu:unload()
```

## modules/renderer/texture.create {#create}

```lua
texture.create(src: any, guid: string?): TextureHandle
```

Create (or fetch) a GPU texture resource and return its `TextureHandle`.
`src`: a `TextureCpuHandle` from `texRef:load()` (CPU→GPU under the asset's
guid, idempotent); raw pixels `{rgba, width, height, srgb?, format?}` (a
flat width*height*4 byte payload, 0-255, row-major, top-to-bottom, RGBA —
a `buffer`, a binary string, or a number array; `format = "rgba16f"`
uploads an HDR texture instead, where `rgba` carries float channel
values); a `TextureHandle` (returned as-is);
or render-target dimensions `{width, height, name?, format?}` with no pixel
source — an empty GPU texture a render pass writes into (camera output,
UI surface) and that samples like any other texture. `format` names the
colour format the target is allocated in, and the passes drawing into it
are built for that format: `"rgba8unorm"` / `"bgra8unorm"` (the two
eight-bit channel orders, either of which a surface may carry),
`"rgba16f"` / `"rgba32f"`, `"rg16f"` / `"rg32f"`, `"r16f"` / `"r32f"`.
Each also answers to its spelled-out width (`"rgba16float"`, `"r32float"`,
and so on), in any case. Omit it to take the surface's own. A float format
carries what eight bits quantize — positions, velocities, HDR. Any other
`format` raises an error naming every name that works, so a target is
allocated in the format it was asked for or not at all. A render target
takes `filter` the way raw pixels do: `"nearest"` keeps its own pixels square
wherever something draws it larger than it is — a viewport widget, a
magnified capture — which is what an image whose pixels ARE the subject
needs, since a 64x32 panel holds no detail between its pixels to
interpolate; `"linear"` (the default) smooths between them. It also
takes `screen` (the engine keeps it the size of the image being drawn),
`screenScale` (the fraction of that size it takes) and `screenSpace`
(`"scene"`, the default, or `"composite"` — the image the post-scene
phases draw into, which is the display's own resolution while the renderer
presents the viewport itself and the scene's size while a UI viewport panel
owns the presentation). A scene-space target is resized for every render
target drawn and cleared before an offscreen one; a composite-space target
follows the presented frame alone, which is what lets a pass keep an
accumulation in it. One scene-space `screen` target is therefore one
resource every render target draws through in turn, so its guid holds the
last one's image at the last one's size, and a value read back from it
belongs to whichever render target was drawn last. A reading that has to
be the viewport's own comes from `screenSpace = "composite"`, or from a
target created without `screen`. NEVER takes an AssetRef — load the CPU
first.

**Parameters**

- `src` `any` _(optional)_ — A TextureCpuHandle, raw pixels, a TextureHandle, or render-target dimensions.
- `guid` `string?` _(optional)_ — Optional v4 guid for a NEW runtime texture — the asset identity the
texture is filed under, which a material's texture slot resolves through.
Minted when absent. Ignored for the CPU-handle and render-target paths.

```lua
local gpu = renderer.texture.create(texRef:load())
local gpu = renderer.texture.create({ rgba = pixels, width = 16, height = 16 })
local px = buffer.create(16 * 16 * 4); local gpu = renderer.texture.create({ rgba = px, width = 16, height = 16 })
local rt = renderer.texture.create({ width = 512, height = 256, name = "panel_rt" })
local hdr = renderer.texture.create({ width = 512, height = 256, name = "cam_rt", format = "rgba16f" })
local led = renderer.texture.create({ width = 64, height = 32, name = "panel", filter = "nearest" })
```

## modules/renderer/texture.createFromAsset {#createfromasset}

```lua
texture.createFromAsset(
```

Put a `.texture` asset on the GPU under its own guid and answer its
handle at once. The asset's bytes are decoded off the frame and the
texture lands on the device when the decode finishes, a frame or more
later: a material naming the guid draws the shader's default for that
slot until then and rebinds when it arrives, and
`renderer.texture.isResident` reports the arrival. The decoded pixels are
dropped once uploaded unless `keepCpu` holds them in the CPU store for
`textureRef:load()`-style reads. An asset the device already holds is
answered from the shape the device reports, without reading the asset's
bytes and without a second decode.

```lua
local h = renderer.texture.createFromAsset(texRef) -- bind h.guid on a material; it draws once resident
```

## modules/renderer/texture.decode {#decode}

```lua
texture.decode(bytes: buffer | string): (any, any, any, any)
```

Decode a texture payload to its pixel buffer. Takes the two shapes the
renderer's own texture loader takes, told apart by their leading bytes:

* an engine-native `ZTEX` payload — handed back at the texel format the
payload was written in, so a height field read back here keeps every bit
it was authored with. A `ZTEX` holding block-compressed or verbatim
source-image levels decodes to `"rgba8"`.
* source image bytes — png, jpeg, gif or webp, straight off disk or out of
a `capture` — decoded to `"rgba8"` at whatever colour type, bit depth or
interlacing the file was written with. This is the call that reads the
pixels of a screenshot.

The fourth return names the format the buffer came back in: `"rgba8"` (4
bytes/texel, channels 0-255), `"rgba16"` (8 bytes/texel, 16-bit unsigned
normalized channels 0-65535) or `"rgba32f"` (16 bytes/texel, float
channels).

**Parameters**

- `bytes` `buffer | string` — A `ZTEX` payload or source image bytes.

```lua
local pixels, w, h = renderer.texture.decode(vfs.read("/source/tmp/shot.png"))
```

## modules/renderer/texture.destroy {#destroy}

```lua
texture.destroy(texture: string | { [string]: any } | AssetRef): boolean
```

Release the GPU texture `texture` names. For an empty render-into texture
(camera output, UI surface) this also frees its render scratch; for an
uploaded runtime texture it drops the GPU resource (and any CPU shadow).
After this, `renderer.texture.list` stops answering for the guid. Takes
every form that names a texture — the `TextureHandle` `create` returned, the
guid the listing hands out, a `TextureCpuHandle` or a texture `AssetRef`.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to release — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.

```lua
renderer.texture.destroy(rt)
renderer.texture.destroy(renderer.texture.list()[1].guid)
```

## modules/renderer/texture.encode {#encode}

```lua
texture.encode(rgba: any, width: number, height: number, opts: any): (string?, string?)
```

Encode raw pixels into an engine-native `ZTEX` payload (the on-disk
texture content). The CPU codec behind the texture assetType's `onCreate`.
`opts.format` selects the on-disk precision: `"rgba8"` / `"srgb"` (default,
8 bits/channel, `rgba` is width*height*4 bytes) or the high-precision data
formats `"rgba16"` (16-bit unsigned normalized, width*height*8 bytes) /
`"rgba32f"` (32-bit float, width*height*16 bytes) — for height/displacement
fields, baked lightmaps, and other data rasters an 8-bit format quantizes
visibly. The two high-precision formats store `rgba` verbatim and reject
`opts.generateMipmaps` / `opts.maxDimension`.

**Parameters**

- `rgba` `any` _(optional)_ — Pixel payload at `opts.format`'s native byte width — a `buffer`, a binary string, or a number array.
- `width` `number` — number
- `height` `number` — number
- `opts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }`

## modules/renderer/texture.encodeFromImage {#encodefromimage}

```lua
texture.encodeFromImage(bytes: buffer | string, opts: any): (string?, string?)
```

Encode source image bytes (png/jpg/webp/…) into an engine-native `ZTEX`
payload. Used by the texture importer / assetType `onChange`.

**Parameters**

- `bytes` `buffer | string` — source image bytes.
- `opts` `any` _(optional)_ — `{ format?, srgb?, generateMipmaps?, maxDimension? }`

## modules/renderer/texture.frameSchedule {#frameschedule}

```lua
texture.frameSchedule(texture: string | AssetRef): { number }?
```

The times at which each layer of a timed texture stops being shown,
in seconds from the start of the sequence — the running total of the layer
display times, so the last entry is the length of one pass.

This is the form a sampler reads a sequence through: a time is turned into
a layer by finding the first entry it has not passed, whatever the
individual layer times are. It is what the `schedule` slot of the builtin
`animatedTexture` shader holds, one entry per layer.

A texture whose layers carry no timing — a still image, a sprite sheet, a
LUT stack — has no schedule and answers nil.

**Parameters**

- `texture` `string | AssetRef` — The texture — a guid, an identity, a name, a path, or a texture `AssetRef`.

```lua
local ends = renderer.texture.frameSchedule(texRef) -- {0.1, 0.15, 0.35}
```

## modules/renderer/texture.info {#info}

```lua
texture.info(ztex: buffer | string): (any, any)
```

Read the header of an engine-native `ZTEX` payload without copying the
pixels. Returns its format, dimensions, mip count, `filter` ("nearest"
or "linear" — the sampler baked into the blob from the asset's
`settings.filter`), and the payload's layer shape.

`layers` counts the array layers the payload carries and `isArray` is true
past one — the answer to "am I about to sample a `texture_2d_array`?",
available before anything samples it. `animated` is true when those layers
are a sequence in time; then `frameDelaysMs` lists each layer's display
time in milliseconds in display order, and `durationMs` totals one pass.
An animated image imports as one layer per frame, so `layers` is its frame
count. A still texture reports `layers = 1`, `isArray = false`.

**Parameters**

- `ztex` `buffer | string` — ZTEX bytes.

```lua
local i = renderer.texture.info(bytes); if i.animated then print(i.layers, "frames", i.durationMs, "ms") end
```

## modules/renderer/texture.isResident {#isresident}

```lua
texture.isResident(texture: string | { [string]: any } | AssetRef): boolean
```

True if a GPU texture is resident under this texture's guid.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture — a `TextureHandle`, a `TextureCpuHandle`, a guid, or a texture `AssetRef`.

```lua
print(renderer.texture.isResident(handle))
```

## modules/renderer/texture.list {#list}

```lua
texture.list(): { any }
```

Every texture currently registered, ordered by guid — the ones a script
created and the ones that reached the device through an asset alike. Each
entry carries the guid, where it came from (`origin` is `"asset"` for a
texture the asset path uploaded), and whether the GPU still holds it. A
resident entry also carries the bytes it costs, its dimensions and its
texel format, so the listing sums to `renderer.textureMemory()`. A
streamable one carries `streamOrigin` — `"asset"` when a level change reads
the levels it needs back from the asset, `"retained"` when the cache holds
the pixels for it.
A script-created entry also carries `held` — whether `renderer.hold` pins
it for the session — and `scene`, the load that created it.
`renderer.references("texture", guid)` says what is still holding a row,
and `renderer.collect()` releases the rows nothing holds.

```lua
for _, t in ipairs(renderer.texture.list()) do print(t.guid, t.bytes) end
```

## modules/renderer/texture.loadCpu {#loadcpu}

```lua
texture.loadCpu(
```

Load a `.texture` asset's pixels into the ONE guid-keyed CPU store (the
Disk→CPU step) and return a CPU handle for per-pixel access (no GPU
readback). The handle holds NO pixels — only the guid, dims and texel
format plus the read/write/encode/unload ops (which read the Rust store).
The pixels stay at the format they were authored in: `handle.format` is
`"rgba8"`, `"rgba16"` or `"rgba32f"`, and `:readPixel` reports channels in
that format's own units. Called by `texRef:load()`. DEFAULT: upload to the
GPU then `handle:unload()`.

```lua
local cpu = texRef:load(); local r,g,b,a = cpu:readPixel(3, 4); cpu:unload()
```

## modules/renderer/texture.readback {#readback}

```lua
texture.readback(texture: string | { [string]: any } | AssetRef): TextureCpuHandle
```

Read a runtime GPU texture's pixels back to CPU and return a
`TextureCpuHandle` for them — the GPU→CPU half of the runtime-texture freeze
path. A texture made with `renderer.texture.create` keeps no CPU copy, so
persisting it (`:encode()` → `asset.create("texture", …)`) reads it back
here first. Yields until the readback completes (a frame or two). After it
returns the pixels are resident in the guid-keyed CPU store: `:readPixel`,
`:writePixel`, `:getInfo`, `:encode`, `:unload` all work. Errors if the
texture never becomes GPU-resident.

A SCENE-space `screen`-sized render target is one resource shared by every
render target drawn — the viewport, an offscreen capture, a camera
rendering into a texture — resized and re-derived for each of them in
turn. The copy is taken ahead of all of them for the frame, so what a
readback of its guid answers is the content of the last frame the renderer
drew: the presented view's own image at the presented resolution, since
the presented view is the sink that draws last. A request made while the
renderer is holding frames back is carried to the next frame it draws
rather than being answered from a target another sink left standing, so a
readback can wait a frame longer than the copy itself takes.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture — the `TextureHandle` `renderer.texture.create` returned, a guid, or a texture `AssetRef`.

```lua
local cpu = renderer.texture.readback(handle); local ztex = cpu:encode(); cpu:unload()
```

## modules/renderer/texture.tone {#tone}

```lua
texture.tone(histogram: any): TextureTone
```

Reduce a histogram to what the picture's tone IS: where its darkest and
brightest pixels sit, where the body of it sits, and how much of it is
standing on the floor or the ceiling — all in code values on the 0-255
scale the pixels were delivered at.

`span` (`max - min`) is the whole range including a single stray pixel;
`spread` (`p95 - p5`) is the range the body of the picture occupies, which
is the reading that says whether a shot is legible. A frame whose subject is
modelled and shaded but delivered inside a few code values reads a large
`mean` and a tiny `spread`, and no mean alone can tell that apart from a
frame with a subject in it.

`crushed` and `clipped` are the shares of the picture at code 0 and at code
255, each 0..1 — what a shot loses to the floor and to the ceiling.

**Parameters**

- `histogram` `any` _(optional)_ — A histogram from `cpu:histogram()`.

```lua
local t = cpu:tone(); if t.spread < 24 then error("the shot is flat") end
```

## modules/renderer/texture.update {#update}

```lua
texture.update(texture: string | { [string]: any } | AssetRef, src: any): TextureHandle
```

Overwrite the GPU texture `texture` names IN PLACE, under the same guid,
from new raw pixels. Never writes a `.texture` file — the play-mode mutate
path. Takes every form that names a texture — the `TextureHandle` `create`
returned, the guid `renderer.texture.list` hands out, a `TextureCpuHandle`
or a texture `AssetRef`. Returns a handle carrying the new dimensions: the
handle it was given, refreshed, and a handle over the guid otherwise.

**Parameters**

- `texture` `string | { [string]: any } | AssetRef` — The texture to update — a `TextureHandle`, a guid, a
`TextureCpuHandle` or a texture `AssetRef`.
- `src` `any` _(optional)_ — New raw pixels `{rgba, width, height, srgb?, format?}` — `rgba` as a
`buffer`, a binary string, or a number array.

## modules/renderer/textureMemory {#modules-renderer-texturememory}

```lua
textureMemory(): {
```

What the GPU texture cache holds, split by whether the texture is
block-compressed. `compressedBytes` and `uncompressedBytes` are what those
textures cost in VRAM, measured from each texture's own format and mip
chain — so a `.texture` whose settings name `format = "bc7"` appears in the
compressed columns at a quarter of what the same image costs as RGBA8.
`blockCompressionSupported` is whether this adapter can hold
block-compressed textures at all; where it is false a BC7 payload is
uploaded decoded and lands in the uncompressed columns instead, so the
texture is present everywhere and compressed where the hardware allows it.
Measured at the end of the last rendered frame.
`streamableTextures` is how many of them a texture budget can move the
base mip level of, split by where a level change reads the levels it needs
from: `assetStreamedTextures` are read back from the asset they came from
and hold nothing in system memory, `retainedTextures` hold the payload
because a script uploaded their pixels and the GPU copy is the only other
one there is. `streamSourceBytes` is what those held payloads occupy in
system memory — bytes that are not VRAM — so it is a reading on the
retained half alone. `pinnedTextures` counts the textures big enough to
stream that stand at a level nothing can move: their pixels were released
and no asset holds them, the asset behind them could not be read back, or a
UI image, a post-process property or a render feature holds a view of them.
A texture out of the streamable set only because no measured surface wears
it stands in neither count: a surface reaching it takes it back up, so its
level moves again as soon as there is a footprint to move it by. It reads 0
while no budget is armed.

```lua
local tm = renderer.textureMemory()
print(("%d compressed textures hold %.1f MiB"):format(tm.compressedTextures, tm.compressedBytes / 1048576))
print(("%d textures stream from their asset, %d hold %.1f MiB of pixels"):format(
tm.assetStreamedTextures, tm.retainedTextures, tm.streamSourceBytes / 1048576))
```

## modules/renderer/textureStreaming {#modules-renderer-texturestreaming}

```lua
textureStreaming(): TextureStreaming
```

What the last frame's texture-residency plan decided. `budgetBytes` is
the armed budget, and `0` means residency is left alone. `streamable` is
how many textures the plan can move. `residentBytes` is what those textures
occupy now, measured from the textures that are allocated; `demandedBytes`
is what the frame's demand alone would have cost, so the two part exactly
where the budget is doing something. `starved` counts the textures left
coarser than the frame asked for, `promoted` the ones that climbed a level
this frame, and `changed` the ones whose GPU texture was replaced. A camera
approaching a surface reads `promoted` above zero for a few frames and then
zero once it settles.

`textures` is one row per streamable texture, ordered by key, carrying the
level each one was asked for and the measurement that asked. Two byte
totals can agree while a single texture sits several levels off what its
surface samples, so read the row when the question is which level a texture
holds and why.

With `budgetBytes` at 0 nothing holds a level back, so `residentBytes`,
`plannedBytes` and `demandedBytes` all read the whole chain of every
texture still enrolled and `textures` is empty — which is how a session
that armed a budget and dropped it reads back that the levels came home.

```lua
local ts = renderer.textureStreaming()
print(("textures: %.1f MiB resident of %.1f MiB demanded, %d starved"):format(
ts.residentBytes / 1048576, ts.demandedBytes / 1048576, ts.starved))
```

## modules/renderer/transmissionShadows {#modules-renderer-transmissionshadows}

```lua
transmissionShadows(): boolean
```

Whether translucent casters tint the directional light they block.

## modules/renderer/uploadStats {#modules-renderer-uploadstats}

```lua
uploadStats(): {
```

What the last completed frame spent re-describing its renderables to the
GPU. Every renderable owns a slot in the per-instance data a draw reads —
its world matrix, the bounds the culler tests it by, and the flags that
decide which passes and which culling stages see it — and a frame uploads
only the slots whose contents changed. `bytes` is what those uploads
carried, `fullBytes` what re-sending every slot would have cost, and
`writes` how many buffer writes carried it. The three numbers cover that
per-renderable data alone, so a scene standing still reads `bytes = 0`
against a `fullBytes` that grows with the scene, and the ratio says how much
of it the scene's own churn — rather than its size — is paying for.

```lua
local u = renderer.uploadStats()
print(("instance upload: %d B of %d B in %d writes"):format(u.bytes, u.fullBytes, u.writes))
```

## modules/renderer/variantSource {#modules-renderer-variantsource}

```lua
variantSource(program: string): string?
```

The WGSL one of the programs `renderer.shaderVariants()` lists holds,
exactly as the shader compiler received it. `program` is the `program`
field of a row's `base` or of one of its `variants`. Reading a base
alongside a variant shows what a feature set selected: each program's text
holds the code its own features guard. The variant-report spelling of
`renderer.compiledSource`, which answers the same for every other shader.

**Parameters**

- `program` `string` — A `program` name from `renderer.shaderVariants()`.

```lua
local row = renderer.shaderVariants()[1]
local base = renderer.variantSource(row.base.program)
```

## typed/builtin//modules/api/engine/renderer/renderer/anisotropy {#typed-builtin-modules-api-engine-renderer-renderer-anisotropy}

```lua
renderer.anisotropy() -> number
```

The maximum anisotropy material textures are sampled with right now —
the requested level clamped to what this device honours.

**Returns** `number` — The effective level, 1 through 16.

```lua
if renderer.anisotropy() < 4 then ... end
```

## typed/builtin//modules/api/engine/renderer/renderer/blendedBatching {#typed-builtin-modules-api-engine-renderer-renderer-blendedbatching}

```lua
renderer.blendedBatching() -> boolean
```

Whether blended neighbours sharing a draw key draw together.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/clearShadowHero {#typed-builtin-modules-api-engine-renderer-renderer-clearshadowhero}

```lua
renderer.clearShadowHero() -> boolean
```

Release the hero caster, so the directional shadow is the cascades'
alone again and the layer the hero view rendered into is given back.

**Returns** `boolean` — Whether a caster was registered.

```lua
renderer.clearShadowHero()
```

## typed/builtin//modules/api/engine/renderer/renderer/clearShadowProxy {#typed-builtin-modules-api-engine-renderer-renderer-clearshadowproxy}

```lua
renderer.clearShadowProxy(mesh: string?) -> number
```

Stop proxying `mesh`, so it rasterizes its own geometry into shadow
views again. Called with no argument, drops every registration.

**Parameters**

- `mesh` `string` _(optional)_ — The mesh to stop proxying. Omit to clear all of them.

**Returns** `number` — How many registrations were removed.

```lua
renderer.clearShadowProxy(statueMesh)
print(renderer.clearShadowProxy(), "proxies dropped")
```

## typed/builtin//modules/api/engine/renderer/renderer/collect {#typed-builtin-modules-api-engine-renderer-renderer-collect}

```lua
renderer.collect() -> RuntimeCollection
```

Release every runtime texture, material, mesh and render feature nothing
holds: no handle a script still reaches, no live owner, no reference from
live engine state, no asset backing it, no hold. A root scene load runs this
once the new scene stands, so what the previous scene's content created and
nothing still wears goes with that scene; calling it directly collects at
any other moment. A session material's handle counts as reached while the
entity it was keyed for stands, and stops counting once that entity is
gone.
It reaches the GPU textures the device holds beside the registry's own: a
texture the cache loaded for an asset goes once nothing live names it and
is read back from that asset the next time something asks for it, while one
no asset answers for stays, there being nothing to read it back from — a
render pass's own target, a colour swatch, an atlas the engine built. A
texture the ASSET path uploaded and whose asset has since been removed has
nothing to come back from either, and the collection decides about it from
its holders the way it does about every other resource: a handle a script
still reaches, a live owner, a reference from live engine state, a hold.
Features go first, then materials, then meshes, then textures, so a texture
only a released material named goes with the material. Runs a full garbage
collection first, so a handle nothing reaches counts as let go, and yields
for the frame the census runs on. A handle the calling function still has
in a variable — or in a temporary it has not overwritten — is one a script
reaches, so a resource created in the function that collects is let go by
the next collection rather than this one.

**Returns** `RuntimeCollection` — `{ released = { texture, material, mesh, feature }, kept, entries }` — the counts released per kind, how many stayed, and every resource's status with `action = "released" | "kept"`.

```lua
local c = renderer.collect() print(c.released.texture, c.kept)
```

## typed/builtin//modules/api/engine/renderer/renderer/compiledShaders {#typed-builtin-modules-api-engine-renderer-renderer-compiledshaders}

```lua
renderer.compiledShaders() -> { string }
```

Every name `renderer.compiledSource` answers for — one per name a
shader compile has run under this session, whether it succeeded or failed.
What makes the composed-source surface enumerable rather than something to
guess a key for.

**Returns** `{ string }` — An array of shader names, sorted.

```lua
for _, name in renderer.compiledShaders() do print(name) end
```

## typed/builtin//modules/api/engine/renderer/renderer/compiledSource {#typed-builtin-modules-api-engine-renderer-renderer-compiledsource}

```lua
renderer.compiledSource(shader: string) -> string?
```

The WGSL the shader compiler received under one name, exactly as it
received it — the composed module, which is what a compile error's line
numbers and handle indices are positions in. Answers under any name a
compile ran under (identity, guid, alias, or a `program` from
`renderer.shaderVariants()`), for a shader that declares no features, and
for a shader whose compile FAILED, which is the case it exists for: a
message about a function body carries a position and nothing else, and the
text that position is in is this. The failed text stands for as long as
`shaderRef:compileStatus()` reports that failure under the same name.

**Parameters**

- `shader` `string` — Any name a shader compiled under — identity, guid, alias, or a
`shaderVariants()` program name.

**Returns** `string?` — The composed WGSL, or nil for a name no compile has run under.

```lua
local status = asset.resolve("myShader", "shader"):compileStatus()
if status.status == "failed" then
print(status.error)
print(renderer.compiledSource("myShader"))
end
```

## typed/builtin//modules/api/engine/renderer/renderer/compositeSize {#typed-builtin-modules-api-engine-renderer-renderer-compositesize}

```lua
renderer.compositeSize() -> { width: number, height: number }
```

The size of the image the post-scene phases worked on in the last
presented frame — the target the UI composites onto, which every pass
after the scene reads as `@scene.color` and writes into, and which a
`screenSpace = "composite"` render target follows. While the renderer
presents the viewport itself that is the display's own size, whatever
fraction of it the scene rasterized at; while a UI viewport panel owns
the presentation it is the size the scene rasterized at, since the panel
draws the scene target at its own rect and nothing upscales before the
composite. Both read `0` before a frame has drawn.

**Returns** `{ width: number, height: number }` in pixels.

```lua
local c = renderer.compositeSize()
```

## typed/builtin//modules/api/engine/renderer/renderer/cullStats {#typed-builtin-modules-api-engine-renderer-renderer-cullstats}

```lua
renderer.cullStats() -> {
```

What the last completed frame decided to draw. `total` renderables went
into the frustum test, `culled` fell outside it and `visible` survived. Of
those, occlusion culling measured `occlusionTested` against the depth
pyramid and proved `occlusionCulled` were entirely behind other geometry —
both 0 while `renderer.occlusionCulling()` is false. A renderable the
pyramid has no say over — one that laid no depth in the pre-pass, one whose
bounds were never recorded, one straddling the near plane — is measured
against nothing and counted in neither, so the gap between `visible` and
`occlusionTested` reads how much of the frame the test could speak for.

This answers for the main camera. What a shadow view's own volume did with
the frame's casters is on that view's row in `renderer.shadowViews()`.

**Returns** `{ total: number, culled: number, visible: number, occlusionTested: number, occlusionCulled: number }`

```lua
local s = renderer.cullStats(); print(s.visible - s.occlusionCulled, "drawn")
```

## typed/builtin//modules/api/engine/renderer/renderer/depthPrepass {#typed-builtin-modules-api-engine-renderer-renderer-depthprepass}

```lua
renderer.depthPrepass() -> boolean
```

Whether the opaque depth pre-pass is currently enabled.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/depthPrepassOrder {#typed-builtin-modules-api-engine-renderer-renderer-depthprepassorder}

```lua
renderer.depthPrepassOrder() -> { runs: number, reordered: number }
```

What the last frame's depth pre-passes planned, and how far their
sequences were from near-to-far before they ordered. `runs` counts the
instanced draws planned; `reordered` counts the adjacent pairs the sort
moved past each other, taken before it ran. Both are summed over every
pre-pass the frame ran — the window plus each render-target camera, each
ordering against its own camera. Both read `0` while the pre-pass or the
ordering is off, and `reordered` reads `0` for a frame that already stood
in order. The ordering leaves no other trace — the draws, the depth and the
image are the same either way.

**Returns** `{ runs: number, reordered: number }`

```lua
local o = renderer.depthPrepassOrder()  -- o.reordered > 0 → it sorted
```

## typed/builtin//modules/api/engine/renderer/renderer/depthPrepassOrdering {#typed-builtin-modules-api-engine-renderer-renderer-depthprepassordering}

```lua
renderer.depthPrepassOrdering() -> boolean
```

Whether the depth pre-pass is submitted nearest-first.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/destroy {#typed-builtin-modules-api-engine-renderer-renderer-destroy}

```lua
renderer.destroy(handleOrKind: any?, id: string?) -> boolean
```

Free the GPU resource a renderer resource holds (the GPU-destroy verb).
Takes any of the forms that name it: the handle a create returned, routed
by its `category` so one call releases a mixed set of handles; the id a
listing hands out, whose kind is read back off what the renderer holds
under it — the runtime registry, the material definitions, the live
features, and the device itself for an asset's own texture or mesh; or the
kind with the id beside it, the shape `renderer.hold` and
`renderer.references` take, which is what names the kind for an id two of
them answer to. An id nothing holds anything under releases nothing and
answers false. The on-disk asset, if any, is untouched. A CPU handle's
`:unload()` frees the CPU copy separately.

**Parameters**

- `handleOrKind` `any` _(optional)_ — A `MeshHandle`, `TextureHandle`, `MaterialHandle` or feature
handle; the id itself; or the kind (`"texture"`, `"material"`, `"mesh"`,
`"feature"`) with the id as the second argument.
- `id` `string` _(optional)_ — The guid or registry key, when the first argument is a kind.

**Returns** `boolean` true if a GPU resource was known under the id.

```lua
renderer.destroy(meshHandle); renderer.destroy(materialHandle)
renderer.destroy(renderer.texture.list()[1].guid)
renderer.destroy("mesh", guid)
```

## typed/builtin//modules/api/engine/renderer/renderer/deviceGeneration {#typed-builtin-modules-api-engine-renderer-renderer-devicegeneration}

```lua
renderer.deviceGeneration() -> number
```

Which render device this process is on, counted from the first.

A render device is lost when a driver resets, when the GPU is taken away,
or when a browser reclaims a WebGPU context. The engine answers by building
another device and re-deriving this session's resources onto it, and this
number moves by one each time it does. Anything held across frames that was
built from a GPU resource records this beside it and remakes it when the two
differ; `engine.onDeviceRebuilt` is the hook that fires when it moves.

**Returns** `number` — The current device generation, counting from 1.

```lua
local generation = renderer.deviceGeneration()
engine.onDeviceRebuilt(function(g) print("device " .. g) end)
```

## typed/builtin//modules/api/engine/renderer/renderer/deviceState {#typed-builtin-modules-api-engine-renderer-renderer-devicestate}

```lua
renderer.deviceState() -> string
```

Whether the render device this process draws through is the one it is
using, one it is replacing, or one it has stopped trying to replace.

`"ready"` is a live device. `"rebuilding"` is the window between a device
reporting itself lost and another being in place: every GPU resource built
from the old one is invalid, the frames in that window draw nothing, and
anything reaching the GPU refuses. `"abandoned"` is after the engine gave
up — the adapter refused every attempt, so this session draws no more
frames.

Work that spans the device — build a render target, draw into it, read it
back — reads this to tell an operation that failed because the device went
out from under it, which is worth doing again once
`renderer.deviceGeneration()` moves, from one that failed on its own terms.
The loss is reported before the next device exists, so the two readings
answer different halves: this one says a replacement is coming, the
generation says it arrived.

**Returns** `string` — `"ready"` | `"rebuilding"` | `"abandoned"`.

```lua
if renderer.deviceState() == "rebuilding" then return end
```

## typed/builtin//modules/api/engine/renderer/renderer/drawDiagnostics {#typed-builtin-modules-api-engine-renderer-renderer-drawdiagnostics}

```lua
renderer.drawDiagnostics() -> { DrawDiagnostic }
```

Every renderable that is NOT drawing what its material says — the one
call for "why does this surface look wrong". Three states land here: a
surface rendering as the magenta placeholder (`substituted`), one the
renderer could bind nothing for at all (`outcome = "skipped"`), and one
drawing a program whose most recent compile FAILED (`stale`), which is what
a shader edited into brokenness looks like — the pipeline its last good
compile built keeps drawing, so the picture is intact and answers to none of
the edits since. Each row names the entity, the program asked for, the
program bound, `programStatus` — the compile gate's word about the program
the material NAMED — and the one cause
from `shaderCompileFailed` / `shaderNotRegistered` / `shaderNotCompiledYet`
/ `noGbufferEntry` / `renderStateKeyNotBuilt` / `noPipelineForTarget` /
`unshaded`, with the compiler's own message in `detail` or `programError`.
Covers every renderable the renderer holds, whether or not a camera reached
it: a row with `observed = false` and `outcome = "notDrawn"` carries the
renderer's own resolution for one this frame drew nowhere, so a broken
surface off-screen is reported the same as one in frame. An empty result
means every renderable the renderer holds is drawing the program its
material named and that program compiles. Answers on the deferred path as
well as forward, and in edit mode as well as play.

**Returns** `{ DrawDiagnostic }`

```lua
for _, d in renderer.drawDiagnostics() do print(d.entity, d.reason, d.detail) end
if #renderer.drawDiagnostics() == 0 then print("every surface is drawing what it says") end
```

## typed/builtin//modules/api/engine/renderer/renderer/drawStats {#typed-builtin-modules-api-engine-renderer-renderer-drawstats}

```lua
renderer.drawStats() -> {
```

What the last completed frame actually submitted. `draws` counts every
geometry draw call the frame issued — the camera's passes, each shadow
view a shadow-casting light adds, and whatever a render feature draws —
and `instances` counts the instances those draws covered. The pair is what
separates one draw carrying five hundred instances from five hundred draws
carrying one each, so it reads how well the scene batches rather than how
many objects are in it.

`compacted` is how many of those instances the frame planned through draws
whose instance count the GPU decides: the culler's own per-object answers
packed into a dense run, so an object it rejects is absent from the draw
instead of collapsing to nothing in the vertex stage. `compactedDrawn` is
how many of them survived, counted on the GPU as it packed them — a pass
that then skips a whole draw over its own layer or visibility answer
leaves that draw's instances in both numbers.

The plan is made over the populations the frame draws, and the tests
answer which of their instances the packing keeps. That packing runs
before any pass has resolved the depth occlusion culling is tested
against, so on its own it reads the frustum and screen-size answers
alone. With `setOcclusionCulling` armed the frame packs the same plan a
second time once the test has answered, and `compactedDrawn` then counts
what came through occlusion as well.

`compactedDrawn` comes back from the buffer the GPU wrote, so it describes
a frame that has finished while `compacted` describes the most recent
plan, and it holds the last count the GPU wrote until another arrives — a
frame that compacts nothing reads `compacted` 0 beside the count from the
last frame that did. In a scene standing still the gap between the two is
the front-end work culling removed.

`materialBinds` is how many times the frame's geometry passes set a
material's parameter group, and `materialBindsElided` how many times a
pass reached that decision and found the group already bound. Their sum
is how many times the decision was reached — once per unit of geometry
submitted, which sits at or below `draws`, since a mesh of several
primitives draws once per primitive under one set of binds. The ratio
inside the pair is what material binding costs the frame: the batched
opaque geometry is gathered into runs sharing a material, so a frame of
many such draws over few materials binds about once per material rather
than once per unit. `materialExtraBinds` and `materialExtraBindsElided`
are the same pair for the second group, the storage bindings a shader
declares for itself, which only the shaders that have them ever bind.

`pipelineBinds` and `pipelineBindsElided` are the same pair for the
pipeline itself: how many times the frame's geometry passes set one, and
how many times a pass reached that decision and found the pipeline it
wanted already bound. Which pipeline a unit needs follows its shader, its
material's render state and its mesh's vertex layout together, so a scene
whose units share all three costs one set for the run of them, while units
differing in any one of the three each pay their own. Their sum is how
many units reached the pipeline decision, which sits at or above what the
material pair reports: a unit the pass settles a pipeline for and then
abandons — one whose material group resolved to nothing — counts here and
never reaches the material decision.

Every figure here is the whole frame's, the main camera's draws and every
shadow view's summed together. `renderer.shadowViews()` splits `compacted`
and `compactedDrawn` across the views that made them, and carries the
camera's own share beside them.

**Returns** `{ draws: number, instances: number, compacted: number, compactedDrawn: number, materialBinds: number, materialBindsElided: number, materialExtraBinds: number, materialExtraBindsElided: number, pipelineBinds: number, pipelineBindsElided: number }`

```lua
local d = renderer.drawStats(); print(d.instances / math.max(d.draws, 1), "instances per draw")
print(renderer.drawStats().compactedDrawn, "of", renderer.drawStats().compacted, "survived the cull")
local d = renderer.drawStats(); print(d.materialBinds, "material binds over", d.materialBinds + d.materialBindsElided, "units")
local d = renderer.drawStats(); print(d.pipelineBinds, "pipeline sets over", d.pipelineBinds + d.pipelineBindsElided, "units")
```

## typed/builtin//modules/api/engine/renderer/renderer/framePacing {#typed-builtin-modules-api-engine-renderer-renderer-framepacing}

```lua
renderer.framePacing() -> FramePacing?
```

How far the CPU is allowed to run ahead of the GPU, and what holding it
there cost the frame just finished. Submitting work to the GPU returns
before the GPU has done it, and everything that submission holds — its
staging allocations, its bind groups, its command buffer — stays alive
until it completes. A frame that asks for more work than the GPU finishes
in a frame's time therefore leaves that behind it, and unbounded that is
memory growth rather than a lower frame rate.

`framesInFlight` is how many submitted frames have not reported done
through the queue's completion signal, held under `maxFramesInFlight`: a
device that keeps up reads under the bound, one that is behind reads at it.
It counts submissions, which is its own quantity — how many presented
images the swapchain permits in flight is a separate setting.
`mechanism` names how that bound is enforced
here: `submission-wait` waits for the frame that many frames back and
reports the wait in `waitMs`, so a paced frame costs latency and still
draws; `submitted-work-done` counts outstanding frames off the queue's
completion signal and declines to start a frame while the bound is met,
counting those in `pacedFrames` and leaving the last presented image up.
`submittedFrames` counts the frames that were admitted and submitted, so it
rises for as long as the renderer is producing frames — which is what tells
a renderer running slowly under a tight bound from one that has stopped.
`stalled` reads true while that completion signal has stopped arriving and
the pacer stood down rather than hold the image indefinitely; it clears on
the first frame that finds the count back under the bound.

`producing` is whether the renderer is drawing frames at all. A headless
renderer draws into an offscreen framebuffer that nothing presents, so its
image reaches a reader only through something that copies it out: it draws
while a consumer is asking — an MCP call in flight, a queued texture
readback, a recording, a frame-egress session — and declines the frames
between two asks, counting them in `idleSkippedFrames`. Every other
renderer stat answers with the last frame that drew, so `producing` is what
separates a live reading from a frozen one. A windowed renderer presents
every frame it draws and reads `producing = true` throughout.

`presentMode` is what the surface presents with and `presentModes` what it
offers; both are empty of meaning on a headless renderer, which never
presents.

**Returns** `FramePacing?` — `{ framesInFlight, maxFramesInFlight, pacedFrames, submittedFrames, waitMs, mechanism, stalled, producing, idleSkippedFrames, presentMode, presentModes }`, or nil before the renderer has drawn a frame

```lua
local p = renderer.framePacing()
print(("%d of %d frames in flight, paced by %s"):format(p.framesInFlight, p.maxFramesInFlight, p.mechanism))
```

## typed/builtin//modules/api/engine/renderer/renderer/getRaytrace {#typed-builtin-modules-api-engine-renderer-renderer-getraytrace}

```lua
renderer.getRaytrace() -> boolean
```

Whether ray tracing is currently enabled.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/gpuMemory {#typed-builtin-modules-api-engine-renderer-renderer-gpumemory}

```lua
renderer.gpuMemory() -> GpuMemory
```

Where the renderer's GPU memory went at the last completed frame — the
call to reach for when something is holding memory and you do not know
what.

Three figures answer three different questions, and they are meant to be
read against each other:

* The categories — `shadow`, `textures`, `meshes`, `instances`, `compute`,
summing to `categorised` — are the renderer's own accounting of what it
asked for on purpose. Always present, on every backend.
* `allocator` is the device allocator's ledger, with a row per creation
label largest first, which is what names an allocation no category
claims. It exceeds `categorised` by the per-frame render targets and the
scratch nothing categorises. The allocator hands memory out from blocks
it reserves whole from the device and returns a block only once nothing
is left in it, so `reservedBytes` runs above `allocatedBytes` by what
those blocks hold unused; `blocks` lists them emptiest first with the
labels that keep each one alive, and `emptyBytes` plus `slackBytes` is
that distance exactly — the pool held in empty blocks, and the room
pinned inside blocks something still sits in.
* `driver.deviceLocalBytes` is what the graphics driver charges this
process, out of the kernel's own accounting. It is the biggest of the
three and the one that fills a card, because it also holds the
swapchain, the images the driver keeps on the renderer's behalf, and the
rounding to whole pages and heap blocks that neither figure above sees.
Read it when the question is how much of the machine's GPU this engine
is using; read the two above when the question is what the engine spent
it on. A platform with no per-process accounting reports
`available = false` and the reason.
* `driver.outsideAllocatorBytes` is that charge less everything the
allocator reserved — what the driver holds on its own account, and the
one figure here nothing releases: a dropped pipeline, another scene and
`renderer.collect()` all leave it where it is, and it falls when the
device is destroyed. Read it when a session's device memory has grown
and no ledger row accounts for the growth.

`compute` is what the compute subsystem holds; `compute.observe()` names
each of those resources and what it costs. `renderTargets` counts the
offscreen render targets the renderer holds at that frame, which is what
says a `renderer.destroy` has been applied rather than queued.

**Returns** `GpuMemory` — The accounting — see `GpuMemory`. The category figures are zeroed until the renderer has published its first frame; `driver` is read as the call runs and answers from the first.

```lua
local m = renderer.gpuMemory()
print(("shadows %.1f MiB"):format(m.shadow.total / 1024 / 1024))
-- how much of the card this engine is holding:
if m.driver.available then
print(("driver %.1f MiB"):format(m.driver.deviceLocalBytes / 1024 / 1024))
else
print("no driver figure: " .. m.driver.reason)
end
-- the biggest allocation in the engine:
local top = m.allocator and m.allocator.byLabel[1]
print(top and top.label, top and top.bytes)
-- the block held for the least, and what pins it:
local block = m.allocator and m.allocator.blocks[1]
if block and block.allocationCount > 0 then
print(block.size, block.allocatedBytes, block.byLabel[1].label)
end
```

## typed/builtin//modules/api/engine/renderer/renderer/hold {#typed-builtin-modules-api-engine-renderer-renderer-hold}

```lua
renderer.hold(handleOrKind: any?, id: string?) -> boolean
```

Pin a runtime resource for the session. A held texture, material, mesh
or render feature survives every collection — the one a root scene load
runs and a direct `renderer.collect()` alike — until `renderer.release`
lets it go or its destroy frees it. It is the way to keep an ad-hoc
resource across the scenes that come and go under it. A hold keeps the
resource in the registry; a mesh's GPU buffers are governed by what draws
it, parked as a CPU definition when the last instance naming it goes and
brought back when one names it again, so `renderer.mesh.isResident(guid)`
is the separate question about the buffers.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind (`"texture"`, `"material"`,
`"mesh"`, `"feature"`) with the guid or key as the second argument.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `boolean` true when the registry knows the resource.

```lua
renderer.hold(tex)
renderer.hold("material", "swatch")
```

## typed/builtin//modules/api/engine/renderer/renderer/loseDevice {#typed-builtin-modules-api-engine-renderer-renderer-losedevice}

```lua
renderer.loseDevice()
```

Destroy the render device on the next frame, so the engine meets a real
device loss.

This is the one loss that can be caused on purpose, and it travels the same
path a driver reset does: frames draw nothing until the rebuild lands,
`GET /engine/status` reports the renderer as `recovering` while it does,
`engine.onDeviceRebuilt` fires afterwards, and `renderer.deviceGeneration()`
moves. Use it to prove that a world's content survives a device loss —
anything it holds only on the GPU has to be remade from the rebuild hook, and
this is how you find out whether it is.

```lua
renderer.loseDevice()
-- some frames later:
print(renderer.deviceGeneration()) -- one higher than before
```

## typed/builtin//modules/api/engine/renderer/renderer/mainCameraView {#typed-builtin-modules-api-engine-renderer-renderer-maincameraview}

```lua
renderer.mainCameraView() -> { number }?
```

The main camera's inverse view-projection (column-major, 16 numbers)
followed by its world position (3 numbers) — `{m0..m15, px,py,pz}` — for
reconstructing world positions from the depth buffer in a ray-tracing pass.
Nil before the first render.

**Returns** `{ number }?` 19 numbers, or nil.

## typed/builtin//modules/api/engine/renderer/renderer/materialCost {#typed-builtin-modules-api-engine-renderer-renderer-materialcost}

```lua
renderer.materialCost() -> { MaterialObservation }
```

What each material cost the frame the renderer last drew, and the state
it holds each one under. One row per material the renderer holds a prepared
bind group for — a material an author wrote and the renderer never prepared
is absent, which is itself the answer to "why is nothing I set reaching the
screen". `draws` and `instances` cover that one frame; `placeholderDraws`
is how many of those draws bound the magenta placeholder instead of this
material's own program; `binds` is how many material-owned bind groups the
frame's passes SET for it and `bindsElided` how many of its draws wanted a
group the pass already held, which is what draw-key sorting buys; a draw
that fell back to the placeholder bound the placeholder's group, so it
counts in `placeholderDraws` and in neither bind count. `uniformBytes` is
the GPU uniform buffer's own size,
which is the reflected property block raised to the 16-byte floor and
rounded up to the copy alignment. `renderer.drawDiagnostics()` names WHICH
renderable is not drawing what its material says, and why.

**Returns** `{ MaterialObservation }`

```lua
for _, m in renderer.materialCost() do print(m.material, m.draws, m.binds, m.bindsElided) end
```

## typed/builtin//modules/api/engine/renderer/renderer/materialIdentity {#typed-builtin-modules-api-engine-renderer-renderer-materialidentity}

```lua
renderer.materialIdentity() -> MaterialIdentity
```

Which material each renderable draws with, as a number a shader can
carry. A material is authored and bound by name, and no shader can read a
string — so every renderable's per-instance record holds a material index
instead. `slots` is the name → index table those indices are drawn from: an
index is assigned the first time the renderer draws with that material and
does not move afterwards, so two renderables that differ only in material
read different indices, and one renderable reads the same index frame after
frame. It follows that the table keeps a row for every material name drawn
this session, whether or not anything still draws with it. `renderables` is
a row per renderable that owns a GPU slot — the entity it belongs to, that
slot, and the index the record at it carries; `populations` is the same for
an instanced draw, whose whole reserved run of slots carries the one
material its registration named. That index is what a shader reads as
`instance_data[slot].material_index`, and the row a ray hit resolves
through `zeroMaterial()`. A renderable draws with the material its entity
references, so one whose entity names none carries index 0.

**Returns** `MaterialIdentity` — `{ slots: { [string]: number }, renderables: { { entity: string, slot: number, index: number, material: string } }, populations: { { slot: number, count: number, index: number, material: string } } }`

```lua
local id = renderer.materialIdentity()
for _, r in id.renderables do print(r.entity, r.slot, r.index, r.material) end
```

## typed/builtin//modules/api/engine/renderer/renderer/materialIndex {#typed-builtin-modules-api-engine-renderer-renderer-materialindex}

```lua
renderer.materialIndex(name: string) -> number?
```

The index standing for a material, or `nil` for one the renderer has not
drawn with yet. Pass it to a shader (or compare it against what a shader
read out of `instance_data[slot].material_index`) to tell which material a
drawing instance carries.

**Parameters**

- `name` `string` — `string` Material name, as `renderer.material.create` filed it.

**Returns** `number?`

```lua
local red = renderer.materialIndex("brick_red")
```

## typed/builtin//modules/api/engine/renderer/renderer/maxAnisotropy {#typed-builtin-modules-api-engine-renderer-renderer-maxanisotropy}

```lua
renderer.maxAnisotropy() -> number
```

The highest anisotropy this device honours: 16 on hardware that filters
anisotropically, 1 on hardware that does not, where a higher request would
be downgraded to trilinear regardless. Read it to report quality honestly —
`renderer.setAnisotropy` clamps for you, so a request never needs guarding.

**Returns** `number` — The device ceiling, 1 or 16.

```lua
local best = renderer.maxAnisotropy()
```

## typed/builtin//modules/api/engine/renderer/renderer/minScreenSize {#typed-builtin-modules-api-engine-renderer-renderer-minscreensize}

```lua
renderer.minScreenSize() -> number
```

The on-screen radius, in pixels, an object must reach to be drawn. `0`
while the cutoff is off.

**Returns** `number`

```lua
local px = renderer.minScreenSize()
```

## typed/builtin//modules/api/engine/renderer/renderer/morphStats {#typed-builtin-modules-api-engine-renderer-renderer-morphstats}

```lua
renderer.morphStats() -> {
```

The morph state the last frame drew with. A mesh carries the shapes it
can blend towards and an entity carries how strongly each is blended
(`ecs.MorphWeights`); where both are present, the vertex stage adds the
weighted deltas to the base geometry.

`instances` is how many render slots that happened at, and `blends` how
many single-target blends those slots carry between them: a slot
contributes one per target its weights move, or that they moved the frame
before, so the number of targets a mesh can be given is bounded by the
buffer the blends live in. `meshes` is how
many meshes hold a delta block and `targets` how many targets those blocks
cover between them; `deltaBytes` is what the shared buffer they are
appended into holds. A morph-target mesh whose weights are all zero reads a
`meshes` above zero beside an `instances` and `blends` of zero.

**Returns** `{ instances: number, blends: number, meshes: number, targets: number, deltaBytes: number }`

```lua
local m = renderer.morphStats()
print(("%d instances carrying %d blends over %d meshes"):format(m.instances, m.blends, m.meshes))
```

## typed/builtin//modules/api/engine/renderer/renderer/observe {#typed-builtin-modules-api-engine-renderer-renderer-observe}

```lua
renderer.observe() -> RenderObservation
```

Everything the renderer knows about the frame it last drew: what
program it bound for each renderable, the render state it holds each
material under, and what each program has cost in pipeline builds.
`renderables` is one row per renderable in the renderer's draw list,
carrying the program its material named (`requestedProgram`) beside the one
that was bound (`boundProgram`) — `__error__` wherever the lookup missed
and the draw went ahead on the magenta placeholder — plus `substituted`,
the `outcome` (`drew` / `drewPlaceholder` / `skipped` / `notDrawn`), the
`reason` that forced it and the compiler's own `detail` for a failed
compile. `observed` says which of two answers a row is: `true` for a
resolution a geometry pass took as it drew, `false` for the renderer's own
resolution of a renderable this frame drew nowhere, which is what a
renderable outside every camera's frustum or layer mask reports.
`materials` is one row per
material the renderer holds a prepared bind group for; `shaders` is one row
per program pipelines have been built for. `frame` names the frame every
per-frame count covers; `retainedFrames` how many frames a resolution a
pass took is kept for after the last frame that drew it; `window` and
`costWindow` state both in the document itself. Recording is armed by the
first read, so this waits for the frame that first records rather than
answering empty.

**Returns** `RenderObservation`

```lua
local o = renderer.observe(); print(o.placeholderDraws, "renderables drew the placeholder in frame", o.frame)
```

## typed/builtin//modules/api/engine/renderer/renderer/occlusionCulling {#typed-builtin-modules-api-engine-renderer-renderer-occlusionculling}

```lua
renderer.occlusionCulling() -> boolean
```

Whether occlusion culling is currently enabled.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/passSchedule {#typed-builtin-modules-api-engine-renderer-renderer-passschedule}

```lua
renderer.passSchedule() -> {
```

The schedule check over this frame's enqueued render passes. Passes
declare what they read (`inputs`) and what they write (`output` /
`outputs` / `storage`), and the frame runs them in phase order and, inside
a phase, in `order` order. `violations` holds every input bound to a
resource the frame produces LATER: that read samples the resource as it
stands ahead of that pass, which is the previous frame's contents for a
render target that persists, an empty target for one just created, and the
scene draw's own output for a `@scene.*` buffer — and the pass renders
either way. The frame's own buffers are checked on the same terms as a
render target: bind `@scene.motion` at a phase ahead of the pass that
writes it and the read is reported, naming the buffer and its writer.
A pass reading a resource ahead of that write on purpose declares that slot
in its enqueue's `readsPrevious` and drops out of the list;
`unboundPrevious` holds declared slots the pass binds no such resource to,
which cover nothing.
A resource no queued pass writes is not reported — a camera rendering to
texture and `compute.dispatch` both fill targets outside the pass queue,
and the scene draw fills the `@scene.*` buffers every frame.
A read the frame has only one order for is not reported either: where the
writing pass consumes something the reading pass produces, the reader runs
first or the writer has nothing to write, which is what a pass reading a
buffer into a target of its own and a second pass copying that target back
over the buffer forms.
`unreachable` holds passes at a phase that does not run their kind: every
phase drains its fragment and compute passes, while `afterLighting` is the
one that draws geometry, draw and splat passes, so one of those enqueued
elsewhere sits in the queue and never runs.
Each finding is also stated in the engine log the first time it appears.

**Returns** `{ violations, unboundPrevious, unreachable }`

```lua
local s = renderer.passSchedule()
for _, v in s.violations do print(v.message) end
```

## typed/builtin//modules/api/engine/renderer/renderer/pipelineCache {#typed-builtin-modules-api-engine-renderer-renderer-pipelinecache}

```lua
renderer.pipelineCache() -> PipelineCache?
```

What the driver's compiled-pipeline store held, built, and wrote back.
A pipeline is machine code the GPU driver compiles from the shader bound
into it, and that compile is what a launch pays before the first frame
drawing with each pipeline can appear. The store keeps that compiled code
across runs, so a launch whose shaders have not changed reads back what the
previous one compiled.

`restoredBytes` is what a previous run left for this GPU and this launch
read; `pipelinesBuilt` counts the pipelines built since startup and
`buildMs` is what they cost together, which is the number the store lowers.
`saves` and `savedBytes` describe writing it back — deferred until a burst
of builds settles, so one launch is one write — and `dirty` is true while
pipelines have been built that the file does not hold, including after a
write that failed, which `lastError` then names. `path` is the file, named
after the GPU it belongs to.

`supported` is false where the platform holds no store a program can carry:
a browser keeps its own and hands none out, and an adapter can lack the
capability. `reason` says which, and the build count and timing still read
true there. `lastError` names a read or write failure; a failed store costs
the saved compile and never the frame, since every pipeline is built from
its source either way.
`pipelinesBuilt` and `buildMs` are engine-wide totals; `renderer.shaderCost()`
is the same cost broken down per program, with each one's permutation count.

**Returns** `PipelineCache?` — `{ supported, reason, path, restoredBytes, pipelinesBuilt, buildMs, saves, savedBytes, dirty, lastError }`, or nil before the renderer has drawn a frame

```lua
local c = renderer.pipelineCache()
print(("%d pipelines in %.1f ms, %d bytes restored"):format(c.pipelinesBuilt, c.buildMs, c.restoredBytes))
```

## typed/builtin//modules/api/engine/renderer/renderer/pointShadowBudget {#typed-builtin-modules-api-engine-renderer-renderer-pointshadowbudget}

```lua
renderer.pointShadowBudget() -> PointShadowBudget
```

The point-light shadow pool now in force. A point light with
`castsShadows` renders an omnidirectional cube map, six faces of depth,
and `slots` is how many of them fit — a further caster is lit but throws
no shadow, and the engine log names how many were turned away. The slot
count is bought rather than authored: `megabytes` of VRAM at `resolution`
texels per face is what decides it.

**Returns** `PointShadowBudget` — The pool — see `PointShadowBudget`.

```lua
local p = renderer.pointShadowBudget()
print(("%d shadowed point lights, %.1f MiB"):format(p.slots, p.bytes / 1024 / 1024))
```

## typed/builtin//modules/api/engine/renderer/renderer/projectionOffset {#typed-builtin-modules-api-engine-renderer-renderer-projectionoffset}

```lua
renderer.projectionOffset() -> (number, number)
```

The sub-pixel projection offset in force for the main camera, in NDC.

**Returns** `(number, number)` — The x and y offset, both 0 when the projection samples pixel centres.

```lua
local ox, oy = renderer.projectionOffset()
```

## typed/builtin//modules/api/engine/renderer/renderer/raycast {#typed-builtin-modules-api-engine-renderer-renderer-raycast}

```lua
renderer.raycast(origin: vec3, direction: vec3, maxDistance: number?, exclude: (string | { string })?) -> RenderRayHit?
```

Cast a ray against the geometry the renderer DRAWS and return the
nearest surface it meets. Every visible mesh answers, whether or not
anything gave it a rigid body — so a terrain, a procedurally generated
mesh, or any plain `Model` reports the surface at a point, which is what a
camera station, a prop, a sound source or a scatter standing on the ground
needs to know. The answer is the nearest triangle of the mesh, so a sloped
or terraced surface reports its height where it was asked rather than the
extent of its bounding box.

`distance` is measured from `origin` along the direction given, so it is a
world-space distance whenever that direction is a unit vector, and it is
directly comparable to a `physics.raycast` distance along the same ray.
`normal` is a unit vector turned to face back along the ray. `exact` is
true when the answer is a triangle and false when it is the object's
bounding box, which is what a mesh whose vertices live only in GPU buffers
answers with. The triangles are the mesh's own, placed by the entity's
transform and by the mesh's bind pose, so a surface a skinning or morph
pass deforms on the GPU answers as the geometry the mesh holds.

EVERYTHING drawn is in scope — the ground you meant, and equally a
character standing on it, a prop, a placeholder floor. The hit names its
entity in `entityId`, `exclude` steps over the ones you do not want, and
`renderer.raycastAll` hands back the whole column so you can pick the
surface yourself. A height you did not expect is usually a nearer surface
you did not mean to ask about, so read `entityId` before trusting a number.

**Parameters**

- `origin` `vec3` — `vec3` ray start in world space
- `direction` `vec3` — `vec3` ray direction; any length, the engine normalises
- `maxDistance` `number` _(optional)_ — `number?` how far the ray reaches, in world units. Default 1000
- `exclude` `(string | { string })` _(optional)_ — `(string | { string })?` entity id, or ids, to step over

**Returns** `RenderRayHit?`

```lua
local hit = renderer.raycast({ x, 100, z }, { 0, -1, 0 }, 200)
if hit then camera.position = { x, hit.point.y + 1.7, z } end
```

## typed/builtin//modules/api/engine/renderer/renderer/raycastAll {#typed-builtin-modules-api-engine-renderer-renderer-raycastall}

```lua
renderer.raycastAll(origin: vec3, direction: vec3, maxDistance: number?, maxHits: number?, exclude: (string | { string })?) -> { RenderRayHit }
```

Cast a ray against the geometry the renderer draws and return every
surface along it, nearest first. One entry per renderable the ray crosses —
the nearest intersection with each — so a stack of surfaces reads as the
order they stand in, and a caller after one particular surface finds it by
`entityId` rather than hoping it is the nearest. Each entry carries the
fields `renderer.raycast` returns.

**Parameters**

- `origin` `vec3` — `vec3` ray start in world space
- `direction` `vec3` — `vec3` ray direction; any length, the engine normalises
- `maxDistance` `number` _(optional)_ — `number?` how far the ray reaches, in world units. Default 1000
- `maxHits` `number` _(optional)_ — `number?` how many surfaces to return. Default 32
- `exclude` `(string | { string })` _(optional)_ — `(string | { string })?` entity id, or ids, to step over

**Returns** `{ RenderRayHit }`

```lua
for _, hit in renderer.raycastAll(eye, look, 500) do print(hit.entityId, hit.distance) end
```

## typed/builtin//modules/api/engine/renderer/renderer/raytraceCapability {#typed-builtin-modules-api-engine-renderer-renderer-raytracecapability}

```lua
renderer.raytraceCapability() -> string
```

The active ray-tracing backend: `"hardware"` (GPU ray query) or
`"compute"` (software traversal — the path on devices without hardware ray
query, e.g. the web). The same ray-tracing features work on both.

**Returns** `string` "hardware" | "compute"

```lua
if renderer.raytraceCapability() == "hardware" then ... end
```

## typed/builtin//modules/api/engine/renderer/renderer/raytraceStats {#typed-builtin-modules-api-engine-renderer-renderer-raytracestats}

```lua
renderer.raytraceStats() -> { [string]: any }
```

What the ray-tracing acceleration structure holds, and what this
session's frames have spent building it. A ray walks a structure built over
the scene's geometry, and keeping it current is work a frame pays before it
traces anything. On the `"compute"` backend geometry that has stood still
long enough is filed under a static partition the frames after it leave
alone: `staticTriangles` + `dynamicTriangles` = `triangles`, `nodes` is the
hierarchy over them, `fullRebuilds` / `partialRebuilds` / `reusedFrames`
count what the session's frames did, and `trianglesRebuilt` is what those
rebuilds re-emitted, summed. On the `"hardware"` backend `blas` is the
bottom-level structures cached, `blasBuilt` how many the last frame built,
and `tlasInstances` what the top-level structure names. The counters are
cumulative — sample, run the scene, sample again.

**Returns** `{ [string]: any }` — `table` {backend, triangles, staticTriangles, dynamicTriangles, nodes, fullRebuilds, partialRebuilds, reusedFrames, trianglesRebuilt, blas, blasBuilt, tlasInstances}

```lua
local before = renderer.raytraceStats().trianglesRebuilt
```

## typed/builtin//modules/api/engine/renderer/renderer/references {#typed-builtin-modules-api-engine-renderer-renderer-references}

```lua
renderer.references(handleOrKind: any?, id: string?) -> RuntimeResourceStatus?
```

What holds a runtime resource right now — the answer a root scene load
reads before releasing it. `references` names each live consumer the engine
found: `{ by = "entity", id }` for an entity wearing the material or mesh,
`"material"` for a material whose slot names the texture, `"instancedDraw"`,
`"camera"`, `"sky"`, `"lightmap"`, `"ui"` (a screen drawing it) and
`"postProcess"` (an effect sampling it). `handleHeld` says whether a script
still reaches a handle to it, `assetBacked` whether an asset stands behind
it, `ownerLive` whether the component instance, scene load or feature that
created it still stands, and `held` whether a hold pins it. `origin` reads
`"device"` for a GPU texture the device holds that no script created — the
one the cache loaded for an asset, the atlas the engine built — whose
holders are the references, a handle and the asset. Runs a full garbage
collection first, the same one `renderer.collect` runs, so a handle nothing
reaches counts as let go and the row says what the next collection does
with the resource. Yields for the frame the census runs on.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `RuntimeResourceStatus?` — The resource's status, or nil for a key the registry does not record and the device holds no texture under.

```lua
local s = renderer.references(mat) for _, r in s.references do print(r.by, r.id) end
```

## typed/builtin//modules/api/engine/renderer/renderer/reflectionEnvironment {#typed-builtin-modules-api-engine-renderer-renderer-reflectionenvironment}

```lua
renderer.reflectionEnvironment() -> {
```

What a reflective surface is reflecting. `probes` is how many reflection
probes the shading blends; they are gathered highest `priority` first, each
rank taking the coverage the ranks above it left, so a small interior probe
ranked above the large exterior one it sits inside wins outright wherever it
reaches full weight. `ranks` is the priority each of those probe slots was
published with, in slot order. `sky` is whether the sky fallback is armed:
with it, coverage no probe claims reflects the captured sky, and without it
a surface outside every probe's radius falls back to the nearest probe
alone. `skyCaptured` is whether the sky slot holds a capture — arming is
refused until it does, since an uncaptured slot reflects black.
`slots` is how many cube slots the environment array holds right now: the
sky's alone, at index `skySlot`, until a probe is captured into it, then
that one plus one per probe. `maxProbes` is how many of them probes may
take, and `resident` whether the array has grown past the sky's single
slot. Capture the sky with `environment.captureSky()`.

**Returns** `{ probes, ranks, sky, skyCaptured, resident, slots, skySlot, maxProbes }`

```lua
local env = renderer.reflectionEnvironment()
print(("%d probes, sky fallback %s"):format(env.probes, tostring(env.sky)))
```

## typed/builtin//modules/api/engine/renderer/renderer/release {#typed-builtin-modules-api-engine-renderer-renderer-release}

```lua
renderer.release(handleOrKind: any?, id: string?) -> boolean
```

Let go of the hold `renderer.hold` placed. The resource stays until
nothing else holds it and a collection releases it — the one a root scene
load runs, or a direct `renderer.collect()`.

**Parameters**

- `handleOrKind` `any` _(optional)_ — The resource's handle, or its kind with the id second.
- `id` `string` _(optional)_ — The guid or key, when the first argument is a kind.

**Returns** `boolean` true when the registry knows the resource.

```lua
renderer.release(tex)
```

## typed/builtin//modules/api/engine/renderer/renderer/renderTargetLimits {#typed-builtin-modules-api-engine-renderer-renderer-rendertargetlimits}

```lua
renderer.renderTargetLimits() -> {
```

The size a render target may be on this device. `maxDimension` is the
device's own maximum 2D texture dimension — the largest either side of a
render target may take. `maxPixels` is how many pixels one render target
may hold, so the RGBA8 image it reads back as fits in a single buffer on
every platform the engine runs on, and `maxSquare` is the largest square
that budget buys. A capture, a `renderer.texture.create({ width, height })`
or a render-to-texture camera past either bound is refused at the call with
the reason, so ask here for the size to request.

**Returns** `{ maxDimension, maxPixels, maxSquare }`

```lua
local lim = renderer.renderTargetLimits()
local w = math.min(want, lim.maxSquare)
```

## typed/builtin//modules/api/engine/renderer/renderer/renderTargets {#typed-builtin-modules-api-engine-renderer-renderer-rendertargets}

```lua
renderer.renderTargets() -> {
```

Every render target the renderer owns and what each one costs, measured
from the texture that is allocated. One row per target, each carrying its
`name`, whether it is `resident`, the `bytes` it holds while it is, its
`width`/`height`/`layers`/`mipLevels`, and `onDemand`.
An `onDemand` target exists only while something needs it: a target nothing
writes into reads `resident = false` and `bytes = 0` and appears again the
frame something writes it, and one sized by content — the reflection-probe
cube array — holds the slots content asked for. The scratch the draws into
a render target have needed is reported as `camera[<handle>].*` rows:
depth and motion vectors under any rasterized pass, and the occlusion
channel and G-buffer over them under a camera's scene render. A draw builds
what it needs, and the set goes once no live camera names the target and
sixty frames have passed without a draw, so a target nothing draws into
carries no such row; the colour image drawn into belongs to the texture
cache and outlives every one of those releases.
`totalBytes` is what the resident targets hold together. Measured at the
end of the last rendered frame.

**Returns** `{ targets, totalBytes, residentCount }`

```lua
local rt = renderer.renderTargets()
print(("render targets: %.1f MiB over %d resident"):format(rt.totalBytes / 1048576, rt.residentCount))
for _, t in rt.targets do
if t.onDemand then print(t.name, t.resident, t.bytes) end
end
```

## typed/builtin//modules/api/engine/renderer/renderer/resolutionScale {#typed-builtin-modules-api-engine-renderer-renderer-resolutionscale}

```lua
renderer.resolutionScale() -> number
```

The fraction of the display resolution the scene is currently rendered
at. `1` until something sets it.

**Returns** `number`

```lua
local s = renderer.resolutionScale()
```

## typed/builtin//modules/api/engine/renderer/renderer/setAnisotropy {#typed-builtin-modules-api-engine-renderer-renderer-setanisotropy}

```lua
renderer.setAnisotropy(level: number) -> number
```

Set the maximum anisotropy material textures are sampled with. Takes
effect on the next frame for content already on screen — no reload, no
texture re-upload. 1 is plain trilinear.

**Parameters**

- `level` `number` — One of 1, 2, 4, 8, 16. Any other value is an error.

**Returns** `number` — The EFFECTIVE level after clamping to `renderer.maxAnisotropy()`, so asking for more than the device offers reports what was actually applied.

```lua
renderer.setAnisotropy(16)
```

## typed/builtin//modules/api/engine/renderer/renderer/setBlendedBatching {#typed-builtin-modules-api-engine-renderer-renderer-setblendedbatching}

```lua
renderer.setBlendedBatching(enabled: boolean) -> ()
```

Whether neighbours in a view's back-to-front blended order draw
together. On by default: alpha-blended geometry is submitted farthest-first,
and a stretch of neighbours in that order sharing a mesh, a material, a
shader and a pose is submitted as one instanced draw over those neighbours,
which puts the same members on screen in the same order out of a single
submission. A run stops wherever a differently-drawn renderable sorts
between two of its members, and a mesh of several primitives keeps a draw
per renderable — both would otherwise move fragments through each other.
Off, every blended renderable draws on its own at its own slot, so a
transparent crowd costs a draw per member. The image is the same either way,
which is what makes this the comparison a frame suspected of being formed by
the batching is made against; `renderer.drawStats().draws` counts the
difference.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setBlendedBatching(false)  -- a draw per blended renderable
```

## typed/builtin//modules/api/engine/renderer/renderer/setDepthPrepass {#typed-builtin-modules-api-engine-renderer-renderer-setdepthprepass}

```lua
renderer.setDepthPrepass(enabled: boolean) -> ()
```

Enable or disable the opaque depth pre-pass. While enabled the renderer
resolves opaque depth in its own pass before shading, so each shaded pixel
runs its material once instead of once per surface stacked behind it, and
the resolved depth is what occlusion culling reads. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setDepthPrepass(false) -- shade every layer, for comparison
```

## typed/builtin//modules/api/engine/renderer/renderer/setDepthPrepassOrdering {#typed-builtin-modules-api-engine-renderer-renderer-setdepthprepassordering}

```lua
renderer.setDepthPrepassOrdering(enabled: boolean) -> ()
```

Submit the depth pre-pass nearest-first. Renderables reach the pre-pass
in the order they were registered, which stands in no relation to where the
camera is: a scene built back-to-front makes every layer write depth and be
overwritten by the layer in front of it. Ordered, the nearest surface
writes first and the surfaces behind it are rejected by the depth test
before they write. The same draws go out either way and the depth that
comes out is the same, so `scene.depth_prepass` in `profiler.gpuFrame()` is
what moves. Enabled by default.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setDepthPrepassOrdering(false) -- submit in registration order
```

## typed/builtin//modules/api/engine/renderer/renderer/setGpuMemoryTracking {#typed-builtin-modules-api-engine-renderer-renderer-setgpumemorytracking}

```lua
renderer.setGpuMemoryTracking(frames: number?) -> number
```

Set how often the GPU allocator sampler reads — one reading every
`frames` frames — or turn it off with 0. It starts at 60, a reading a
second at 60 Hz, so `renderer.gpuMemory().allocator` answers without
anything arming it. Building the ledger walks every live allocation, which
is why it is sampled rather than read every frame; the category figures
cost nothing either way, and a reader between samples sees the most recent
ledger, so a slow interval still answers.

Called with no argument it reports the interval in force and changes
nothing, which is how something that retimes the sampler puts it back
afterwards instead of restoring a number it assumed was the default.

**Parameters**

- `frames` `number` _(optional)_ — `number?` Frames between readings; 0 turns the sampler off. Omit
to read the interval without changing it.

**Returns** `number` — The interval now in force.

```lua
renderer.setGpuMemoryTracking(60)
local was = renderer.setGpuMemoryTracking()
renderer.setGpuMemoryTracking(1)
-- ... take a tight reading ...
renderer.setGpuMemoryTracking(was)
```

## typed/builtin//modules/api/engine/renderer/renderer/setMaxFramesInFlight {#typed-builtin-modules-api-engine-renderer-renderer-setmaxframesinflight}

```lua
renderer.setMaxFramesInFlight(frames: number) -> number
```

Set how many frames of GPU work may be outstanding before the renderer
stops running ahead. One is the least overlap this can express — a frame's
work is waited for as soon as the next frame has been submitted — which is
the lowest latency and the lowest throughput; higher values let a slow
frame build a longer backlog, and that backlog is memory. Takes effect on
the next frame.

Answers the bound after clamping to [1, 8], so asking for more than the
renderer honours reports what you actually got.

**Parameters**

- `frames` `number` — number Frames of GPU work that may be outstanding, 1 through 8.

**Returns** `number` — The bound that took effect, after clamping.

```lua
renderer.setMaxFramesInFlight(1) -- lowest latency
print(renderer.setMaxFramesInFlight(99), "was clamped")
```

## typed/builtin//modules/api/engine/renderer/renderer/setMinScreenSize {#typed-builtin-modules-api-engine-renderer-renderer-setminscreensize}

```lua
renderer.setMinScreenSize(pixels: number) -> ()
```

Stop drawing an object once its on-screen radius falls below this many
pixels. A few pixels across, an object carries no detail a viewer can
resolve while still costing a full vertex and submission pass, and the
cutoff drops it from the camera's draws entirely — `0`, the default,
keeps every object however small it lands. Measured from the object's own
bounds against the camera's projection, so the same threshold means the
same apparent size at any distance or field of view. Shadow casters have
their own threshold in `renderer.setShadowCasterCutoff`.

**Parameters**

- `pixels` `number` — `number` — smallest on-screen radius still drawn; 0 disables.

**Returns** `()`

```lua
renderer.setMinScreenSize(4) -- drop anything under 4 px of radius
print(renderer.drawStats().compactedDrawn, "instances survived it")
```

## typed/builtin//modules/api/engine/renderer/renderer/setOcclusionCulling {#typed-builtin-modules-api-engine-renderer-renderer-setocclusionculling}

```lua
renderer.setOcclusionCulling(enabled: boolean) -> ()
```

Enable or disable occlusion culling. While enabled the renderer reduces
the pre-pass depth into a pyramid each frame and tests every renderable
that cleared the frustum against it, dropping the ones another surface
entirely covers before their geometry is submitted. The pyramid describes
the frame being drawn, so an object that becomes visible this frame is
never held back a frame. Requires the depth pre-pass.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setOcclusionCulling(true); print(renderer.cullStats().occlusionCulled)
```

## typed/builtin//modules/api/engine/renderer/renderer/setPointShadowBudget {#typed-builtin-modules-api-engine-renderer-renderer-setpointshadowbudget}

```lua
renderer.setPointShadowBudget(cfg: {
    megabytes: number?,
    resolution: number?,
}) -> number
```

Set how much VRAM the point-light shadow atlas may hold, and at what
per-face resolution. An omitted field keeps its current value. The atlas
is reallocated on the next frame, so `renderer.pointShadowBudget().slots`
reports the new pool one frame later; the returned number is what this
budget buys. Raising `resolution` sharpens every point shadow and spends
the same memory on fewer of them — doubling it quarters the slot count.
Values are clamped: megabytes [1, 1024], resolution [64, 4096], and the
pool never exceeds `renderer.pointShadowBudget().maxSlots`. One slot is
always granted, so a budget too small for a single cube shadows one light
and the pool costs what that slot costs rather than what was asked for —
`{ megabytes = 1, resolution = 4096 }` buys 384 MiB of ceiling. Read
`pointShadowBudget().bytes` back to see what a budget actually bought, and
`renderer.shadowMemory().point` to see what the scene has made resident.

**Parameters**

- `cfg` `{
    megabytes: number?,
    resolution: number?,
}` — The fields to change — `megabytes` and/or `resolution`.

**Returns** `number` — Cube slots this budget buys.

```lua
renderer.setPointShadowBudget({ megabytes = 96 })
renderer.setPointShadowBudget({ resolution = 1024, megabytes = 96 })
```

## typed/builtin//modules/api/engine/renderer/renderer/setPresentMode {#typed-builtin-modules-api-engine-renderer-renderer-setpresentmode}

```lua
renderer.setPresentMode(mode: string) -> string
```

Set how a presented frame reaches the display. `fifo` queues every frame
and shows it on a vertical blank, which never tears and never drops one;
`mailbox` replaces the queued frame with the newest, which does not tear
and does not hold the renderer to the refresh rate; `immediate` presents as
soon as a frame is ready and can tear; `fifo_relaxed` is `fifo` that tears
rather than stall when a frame misses its blank; `auto_vsync` and
`auto_no_vsync` leave the choice to the backend.

A surface that does not offer the mode presents `fifo` instead, so read
`renderer.framePacing().presentMode` for what took effect and
`.presentModes` for what this surface offers. Takes effect on the next
frame.

**Parameters**

- `mode` `string` — string One of "fifo", "fifo_relaxed", "mailbox", "immediate", "auto_vsync", "auto_no_vsync".

**Returns** `string` — The canonical spelling of the request — `renderer.framePacing().presentMode` is what the surface presents with, and differs when the surface does not offer the request.

```lua
renderer.setPresentMode("mailbox")
print(renderer.framePacing().presentMode, "is what the surface took")
```

## typed/builtin//modules/api/engine/renderer/renderer/setProjectionOffset {#typed-builtin-modules-api-engine-renderer-renderer-setprojectionoffset}

```lua
renderer.setProjectionOffset(x: number, y: number)
```

Offset the main camera's projection by a sub-pixel amount, in NDC, for
the frames until it is set again. The offset is in NDC because that is the
space it is constant in: one pixel is `2.0 / width` across, so half a pixel
is `1.0 / width`. Velocity (`@scene.motion`) is measured against the
offset-free projection, so a still scene reports no motion however the
samples are placed — and picking resolves a click to the same ray either
way. `(0, 0)` samples pixel centres.

**Parameters**

- `x` `number` — Horizontal offset in NDC. One pixel is `2.0 / width`.
- `y` `number` — Vertical offset in NDC. One pixel is `2.0 / height`.

```lua
renderer.setProjectionOffset(0.5 * 2 / w, -0.25 * 2 / h)
```

## typed/builtin//modules/api/engine/renderer/renderer/setRaytrace {#typed-builtin-modules-api-engine-renderer-renderer-setraytrace}

```lua
renderer.setRaytrace(enabled: boolean) -> ()
```

Enable or disable GPU ray tracing. While enabled the engine builds the
scene acceleration structure each frame so ray-tracing render features can
trace against it; disabling stops the build (so it costs nothing until a
ray-traced effect is active). Required before any ray-traced shadows / AO /
reflections render.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setRaytrace(true); renderer.feature.create("rt_shadows")
```

## typed/builtin//modules/api/engine/renderer/renderer/setResolutionScale {#typed-builtin-modules-api-engine-renderer-renderer-setresolutionscale}

```lua
renderer.setResolutionScale(scale: number) -> number
```

Render the scene at a fraction of the display's resolution and present
it at the display's own size. Shading cost scales with pixel count and with
nothing else, so this trades sharpness for frame time without taking
anything out of the scene: at `0.5` the scene rasterizes a quarter of the
pixels. UI and text are unaffected — they are drawn after the scene is
brought back up to size. The scene rows in `profiler.gpuFrame()` are what
move.

**Parameters**

- `scale` `number` — `number` — fraction of the display resolution, clamped to [0.25, 1].

**Returns** `number` — the scale in force after clamping.

```lua
renderer.setResolutionScale(0.7)
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowCaching {#typed-builtin-modules-api-engine-renderer-renderer-setshadowcaching}

```lua
renderer.setShadowCaching(enabled: boolean) -> ()
```

Whether a shadow map that nothing changed is kept rather than drawn
again. On by default: a shadow view — one directional cascade, one atlas
layer of spot tiles, one face of a point light's cube — is rasterized on
the frames its own inputs change and holds the depth it drew on the ones
they do not.
Off, every view is drawn on every pass, which is what a shadow suspected of
holding a stale image is compared against.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setShadowCaching(false)  -- draw every shadow view, every frame
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowCasterBatching {#typed-builtin-modules-api-engine-renderer-renderer-setshadowcasterbatching}

```lua
renderer.setShadowCasterBatching(enabled: boolean) -> ()
```

Whether a shadow view draws every caster of one mesh together. On by
default: a view — one directional cascade, one atlas layer of spot tiles,
one face of a point light's cube — submits one draw per geometry over every
caster of it the view admits, wherever those casters sit in render order
and whatever transform slots they hold. Off, a view draws the runs of render-order
neighbours that share a mesh AND hold consecutive slots, so a scene that has
spawned and despawned anything fragments into many more draws. The image is
the same either way, which is what makes this the comparison a shadow
suspected of being placed by the batching is made against.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setShadowCasterBatching(false)  -- draw the runs the scene presents
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowCasterCutoff {#typed-builtin-modules-api-engine-renderer-renderer-setshadowcastercutoff}

```lua
renderer.setShadowCasterCutoff(cfg: {
    minRadiusPx: number?,
    maxDistance: number?,
}) -> ShadowCasterCutoff
```

Set the shadow-caster cutoff. An omitted field keeps its current value,
so a call can adjust one threshold without restating the other. Both are
measured against the camera the frame draws from rather than against each
light, so one setting covers every cascade, spot and cube face, and a
caster that stops casting is one whose shadow the viewer could not have
resolved. `maxDistance` is measured to the near side of the caster's
bounding sphere, so a large object keeps casting while any part of it is in
range. 0 releases a threshold; releasing both draws the casters the frame
drew before either was set.

**Parameters**

- `cfg` `{
    minRadiusPx: number?,
    maxDistance: number?,
}` — The fields to change — `minRadiusPx` and/or `maxDistance`.

**Returns** `ShadowCasterCutoff` — The cutoff now in force.

```lua
renderer.setShadowCasterCutoff({ minRadiusPx = 2 })
renderer.setShadowCasterCutoff({ minRadiusPx = 1.5, maxDistance = 120 })
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowConfig {#typed-builtin-modules-api-engine-renderer-renderer-setshadowconfig}

```lua
renderer.setShadowConfig(cfg: {
    resolution: number?,
    cascades: number?,
    distance: number?,
    splitLambda: number?,
    fadeFraction: number?,
    softness: number?,
}) -> ShadowConfig
```

Set the directional shadow quality. Any omitted field keeps its current
value, so a call can adjust one knob without restating the rest. Values are
clamped: resolution [64, 8192], cascades [1, 4], distance >= 0, splitLambda
[0, 1], fadeFraction [0, 1], softness [0, 1]. Changing `resolution` or
`cascades` reallocates the depth array; the rest are per-frame values. A
`distance` of 0 hands the range to the frame — the splits are cut over the
depth its own shadow-taking renderables reach — and a positive one caps it,
which is what a scene bounding its shadow cost states.

**Parameters**

- `cfg` `{
    resolution: number?,
    cascades: number?,
    distance: number?,
    splitLambda: number?,
    fadeFraction: number?,
    softness: number?,
}` — The fields to change — see `ShadowConfig`.

**Returns** `ShadowConfig` — The full config now in force.

```lua
renderer.setShadowConfig({ softness = 0.65, fadeFraction = 0.28 })
renderer.setShadowConfig({ cascades = 4, resolution = 2048, distance = 240 })
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowHero {#typed-builtin-modules-api-engine-renderer-renderer-setshadowhero}

```lua
renderer.setShadowHero(entity: string, padding: number?) -> ()
```

Give one caster a directional shadow view of its own, fit to its world
bounds.

A cascade covers the slab of world the camera sees, so its texels are spread
over tens of metres and one character standing in the middle of it is
resolved by a handful of them. The hero view is the same light and the same
depth range zoomed onto that entity's bounds, so the whole map goes into the
shadow it and the ground under it carry — `renderer.shadowHero().zoom` is
the factor its texel density gains.

It renders beside the cascades, into a layer of the same texture allocated
while a hero is registered, and every surface inside it reads it in place of
the cascade, crossing back at its edge. Nothing else about the shadow
changes: the same casters reach it, at the same depth range, through the
same filter.

**Parameters**

- `entity` `string` — The entity whose renderables the view is fit around.
- `padding` `number` _(optional)_ — How much room the fit leaves around those bounds — for a pose that
leaves the bind-pose box and for the filter that samples outside a
silhouette. 1.0 fits them exactly.

**Returns** `()`

```lua
renderer.setShadowHero(player.id)
print(("hero shadow: %.1f texels/unit vs %.1f"):format(
renderer.shadowHero().texelsPerUnit, renderer.shadowHero().cascadeTexelsPerUnit))
```

## typed/builtin//modules/api/engine/renderer/renderer/setShadowProxy {#typed-builtin-modules-api-engine-renderer-renderer-setshadowproxy}

```lua
renderer.setShadowProxy(mesh: string, proxy: string) -> ()
```

Rasterize `proxy` in place of `mesh` in every shadow view. A shadow is a
silhouette resolved at the resolution of a shadow map, so the triangles that
carry a mesh's close-up detail write depth no reader can resolve — a
decimated version of the shape, a level of its own LOD chain, or a
hand-built hull casts the same shadow for a fraction of the geometry.

The registration is keyed by MESH, so one call covers every instance of it —
entities and GPU-driven populations alike — and a crowd sharing that mesh
stays one draw. The proxy is placed by whatever places the caster, its
instance's own transforms, so it stands where the caster stands, at the
caster's scale.

An entity caster keeps its own geometry where a stand-in could not be placed
or deformed correctly: it is skinned (it rasterizes the post-skinned
vertices written for its own mesh), it blends morph targets (whose deltas
describe its own mesh and are read by vertex id), or its proxy would be
placed by a different node of its model than the source mesh is. Either
caster keeps it where the renderer holds no geometry under the proxy's
guid. Each of those is counted in `renderer.shadowProxies()`.

Nothing else in the scene draws a proxy, so this call is what brings it onto
the GPU, and it raises where it cannot. A proxy already resident there is
registered as it stands.

**Parameters**

- `mesh` `string` — The mesh a caster draws, as a guid or any mesh reference.
- `proxy` `string` — The mesh it rasterizes into shadow views instead.

**Returns** `()`

```lua
renderer.setShadowProxy(statueMesh, statueHullMesh)
print(renderer.shadowProxies().triangles, "vs", renderer.shadowProxies().sourceTriangles)
```

## typed/builtin//modules/api/engine/renderer/renderer/setSkinnedBatching {#typed-builtin-modules-api-engine-renderer-renderer-setskinnedbatching}

```lua
renderer.setSkinnedBatching(enabled: boolean) -> ()
```

Whether skinned instances holding one pose draw together. On by default:
instances of one mesh wearing one material and posed alike read the same
post-skinned vertices, so the camera's colour passes submit them as a single
instanced draw, and so does each shadow view and the velocity pass while
`renderer.shadowCasterBatching()` is on — that switch is what makes a depth
view form its draws by geometry at all. The camera depth pre-pass submits
its casters nearest-first, which is a run per span of neighbours rather than
a draw per geometry, so a crowd costs a draw per member there. Off, each
skinned instance draws on its own at its own slot in every pass that
rasterizes it. The image is the same either way, which is what makes this
the comparison a frame suspected of being formed by the batching is made
against — `renderer.drawStats().draws` counts the difference and
`renderer.skinningStats().poses` says how many distinct poses it holds.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setSkinnedBatching(false)  -- a draw per skinned instance
```

## typed/builtin//modules/api/engine/renderer/renderer/setSkinningPoseHold {#typed-builtin-modules-api-engine-renderer-renderer-setskinningposehold}

```lua
renderer.setSkinningPoseHold(enabled: boolean) -> ()
```

Whether a pose the skinning pass already wrote is read as it stands. On
by default: the pass produces an instance's vertices from its joint
matrices, its node transforms, its blend weight and its blend model, so the
slice holding a pose already holds what running the pass over those same
inputs would write. A frame binding a pose whose slice still holds it reads
the slice and dispatches nothing, and skinning costs what the frame's poses
CHANGED — a paused clip, a skeleton nothing drives, a cast standing in one
pose, each cost compute the frame the pose arrived and nothing after it.
Off, every pose a frame binds is dispatched again, which is the comparison a
frame suspected of reading a slice that no longer holds its pose is made
against; the image is the same either way and
`renderer.skinningStats()` counts the difference as `dispatches` against
`held`. A mesh whose vertices a compute pass writes is dispatched every
frame however this stands.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setSkinningPoseHold(false)  -- dispatch every pose, every frame
```

## typed/builtin//modules/api/engine/renderer/renderer/setSpotShadowBudget {#typed-builtin-modules-api-engine-renderer-renderer-setspotshadowbudget}

```lua
renderer.setSpotShadowBudget(cfg: {
    megabytes: number?,
    resolution: number?,
}) -> number
```

Set how much VRAM the spot/area shadow atlas may hold, and the per-side
resolution of one layer. An omitted field keeps its current value. The
atlas is reallocated on the next frame, so `renderer.spotShadowBudget()`
reports it one frame later; the returned number is what this budget buys.
Raising `resolution` sharpens the lights that cover the most screen and
spends the same memory on fewer layers — doubling it quarters the layer
count. Raising `megabytes` buys layers, which is what lets several lights
hold a large tile at once. Values are clamped: megabytes [1, 1024],
resolution [64, 4096], and the atlas never exceeds
`spotShadowBudget().maxLayers`. One layer is always granted, so a budget
too small for one still shadows lights and the atlas costs what that layer
costs rather than what was asked for.

**Parameters**

- `cfg` `{
    megabytes: number?,
    resolution: number?,
}` — The fields to change — `megabytes` and/or `resolution`.

**Returns** `number` — Atlas layers this budget buys.

```lua
renderer.setSpotShadowBudget({ megabytes = 64 })
renderer.setSpotShadowBudget({ resolution = 2048, megabytes = 64 })
```

## typed/builtin//modules/api/engine/renderer/renderer/setTextureBudget {#typed-builtin-modules-api-engine-renderer-renderer-settexturebudget}

```lua
renderer.setTextureBudget(opts: TextureBudgetOpts) -> TextureBudget
```

Bound the VRAM a world's textures occupy, by keeping only the mip levels
the frame is actually sampling. Pass `{ megabytes = 256 }`; `0` — the
default — leaves texture residency alone and every texture stays fully
resident the way it uploaded.

With a budget armed, each frame measures how many screen pixels ONE
traversal of a texture's coordinate range covers on the surface that spans
it widest, and asks for the mip level that serves that span one texel per
pixel — the level the GPU picks from the fragment's own derivatives. A
material with `uvScale = 8` lays eight copies of its texture across a
surface, so each copy spans an eighth of the surface and asks for three
levels coarser than the surface's own size would. A shader that declares
`// @uv_space: world` advances its coordinate over world units rather than
over the mesh's UVs, so how many copies a surface carries follows how large
that surface is. The textures whose surfaces cover the fewest pixels give
up levels until the set fits. Detail climbs one level per frame, from the
image already on screen, so a surface the camera approaches sharpens rather
than popping, and no texture is taken below the level whose longest side is
64 texels.

`bias` shifts every measurement by whole mip levels either way — negative
for finer than the sampling implies, positive for coarser — over a world
whose look wants a different trade than one texel per pixel.

The plan moves a texture whose demand the frame can measure: one at least
256 texels on its narrowest side, worn by a surface an entity draws. A
texture a UI image, a post-process property or a render feature holds a
view of stays whole, because nothing measures how much of the screen those
cover.

Which textures the budget governs follows the surfaces the frame draws. A
texture whose asset still holds its bytes is enrolled the frame a measured
surface wears it — whenever it loaded, and whenever the budget was armed —
because a level change reads the levels it needs back from the asset; when
the last such surface goes it leaves the set whole, at the level it
uploaded at, and a surface reaching it again takes it back up. A texture a
script uploaded has its pixels nowhere else, so one enrolled while it is
resident holds them in system memory
(`renderer.textureMemory().streamSourceBytes`) from the upload until a
surface has worn it and gone, and releases them then, which is what keeps
it out for the rest of the session; one whose pixels were already released
when the budget was armed is out from the start.
`renderer.textureMemory().pinnedTextures` counts those, together with the
textures whose asset could not be read back and the ones a UI image, a
post-process property or a render feature holds a view of. Disarming
returns every texture to the level it uploaded at, and arming again governs
the textures the frame's surfaces are wearing then.

**Parameters**

- `opts` `TextureBudgetOpts` — `{ megabytes: number?, bias: number? }`

**Returns** `TextureBudget` — `{ megabytes, bias }` — the budget now in force

```lua
renderer.setTextureBudget({ megabytes = 128 })
renderer.setTextureBudget({ megabytes = 128, bias = -1 })  -- one level finer everywhere
renderer.setTextureBudget({ megabytes = 0 })               -- leave residency alone
```

## typed/builtin//modules/api/engine/renderer/renderer/setTransmissionShadows {#typed-builtin-modules-api-engine-renderer-renderer-settransmissionshadows}

```lua
renderer.setTransmissionShadows(enabled: boolean) -> ()
```

Let translucent casters tint the sunlight they block instead of blocking
it outright. A shadow map holds one depth per texel and is compared as a
yes-or-no test, so stained glass, water and thin fabric all project the same
black silhouette a wall does. With this on, a caster whose material declares
opacity (`base_color` alpha under a transparent blend) or `transmission`
also draws into a light-space transmittance map, and the colour it lets
through multiplies into the directional light reaching whatever stands
behind it. Stacked casters compose. Opaque casters are unaffected, and a
scene with no translucent caster allocates nothing and records no pass.

**Parameters**

- `enabled` `boolean` — `boolean`

**Returns** `()`

```lua
renderer.setTransmissionShadows(true)  -- stained glass tints the floor
```

## typed/builtin//modules/api/engine/renderer/renderer/shaderCache {#typed-builtin-modules-api-engine-renderer-renderer-shadercache}

```lua
renderer.shaderCache() -> ShaderCache?
```

What the shader compile gate's store of baked WGSL held, answered and
wrote back. Compiling a `.shader` wraps the author's body in its framework,
expands every `#include`, and hands the result to naga to parse and
validate — work that is a pure function of the text going in, and that a
launch would otherwise repeat for every shader it draws with. The store
keeps that baked text across launches.

`restoredEntries` and `restoredBytes` are what a previous launch left that
this one read back. `hits` counts the compiles answered out of the store
and `misses` those that ran in full; `savedMs` sums what each hit's own
recorded compile had cost, against `compileMs`, what the misses spent.
`stale` counts the misses whose key was held but whose `#include`d modules
had changed underneath — an entry records every module its expansion
consumed, so editing a module invalidates exactly the shaders that included
it and leaves the rest.

`entries` and `bytes` are what the store now holds, `evictions` how many a
write dropped to stay inside its bounds, and `saves` / `savedBytes` /
`dirty` describe writing it back, deferred until a burst of compiles
settles. `persistent` is false where a launch has nowhere to keep
artifacts and `reason` says why; `location` is the file, or the browser
store, they are kept in. `restoreState` is how the read of what a previous
launch left has gone — `pending` while it is still out (a browser answers
through a promise, so a launch reaches its first frames before it lands),
`restored` once entries came back, `empty` when there were none to come
back, `failed` when what was there could not be read, and `none` where a
launch keeps nothing. A cold, missing or corrupt store leaves every
shader compiling from source with identical output, and `lastError` then
names what went wrong.

**Returns** `ShaderCache?` — `{ persistent, reason, restoreState, location, restoredEntries, restoredBytes, hits, misses, stale, entries, bytes, compileMs, savedMs, saves, savedBytes, dirty, evictions, lastError }`, or nil on a build with no renderer

```lua
local c = renderer.shaderCache()
print(("%d hits, %d misses, %.1f ms saved"):format(c.hits, c.misses, c.savedMs))
```

## typed/builtin//modules/api/engine/renderer/renderer/shaderCost {#typed-builtin-modules-api-engine-renderer-renderer-shadercost}

```lua
renderer.shaderCost() -> { ShaderCost }
```

What each program has cost in pipeline builds, beside the compile
gate's most recent word about it. `variants` is how many pipelines this
engine has built for it — one per (target format, vertex layout,
render-state key) permutation reached — and `buildMs` what those builds
cost, both summed since engine start. A pipeline the driver's own store
restored is not built and so is not counted, so a second launch on the same
adapter reports less than the first. `status` is `compiled`, `failed` or
`pending`, and `error` carries the compiler's message for a failure.
Ordered by cost, most expensive first.

**Returns** `{ ShaderCost }`

```lua
local top = renderer.shaderCost()[1]; print(top.shader, top.variants, top.buildMs)
```

## typed/builtin//modules/api/engine/renderer/renderer/shaderVariants {#typed-builtin-modules-api-engine-renderer-renderer-shadervariants}

```lua
renderer.shaderVariants() -> { ShaderVariants }
```

Every shader that declares optional features, and the programs its
materials have made it compile. Each row carries the features the shader
declares, the base program it ships as, and one entry per variant with the
features that variant holds — so the permutation count a scene's materials
are spending is a number to read rather than something to infer from
compile time. A shader whose variants reach `budget` compiles no more; the
materials asking for further feature sets draw with the base program.

**Returns** `{ ShaderVariants }` — An array of `ShaderVariants`, one per feature-declaring shader.

```lua
for _, s in renderer.shaderVariants() do print(s.shader, #s.variants, s.budget) end
```

## typed/builtin//modules/api/engine/renderer/renderer/shadingOf {#typed-builtin-modules-api-engine-renderer-renderer-shadingof}

```lua
renderer.shadingOf(subject: string | { [string]: any }) -> ShadingReading
```

What the renderer is shading ONE subject with, taken from the document
the renderer publishes — the call a system holding a handle makes to find
out whether what reaches the screen is its own material or the magenta
placeholder standing in for it, without reading the engine log. `subject` is
an entity that draws or the registry key of a material. `state` reads
`itsMaterial` where the renderer bound the program the material names,
`errorMaterial` where it bound the placeholder instead, `stalePipeline`
where the pipeline drawing it was built before that program's most recent
compile, `nothingBound` where the renderer resolved no pipeline for it,
`pending` where this call is the one that armed per-draw recording and the
frame after it publishes, and `unknown` where the renderer holds a
resolution under no such subject. A fault state carries the renderer's own
`reason` from the closed set `renderer.drawDiagnostics()` names — plus
`materialNotPrepared`, which a material subject reads where the renderer
prepared nothing under that key — the compiler's `detail`, the `program`
the material asked for and the `bound` one; `means` states the reading in a
sentence. A material subject answers from the renderables drawing with it,
and from the renderer's record for the material itself where a draw
registered against the material carries no row of its own; a subject that
several renderables draw answers with a refused one wherever there is one.
The reading follows the renderer, so a program that compiles on a later
edit puts the subject back on `itsMaterial` from the frame the renderer
draws it with again.

**Parameters**

- `subject` `string | { [string]: any }` — The entity — a proxy from `entity(...)` or an entity-id string —
or the material, as its registry key or the `MaterialHandle`
`renderer.material.create` returned.

**Returns** `ShadingReading`

```lua
local s = renderer.shadingOf(emitter); if s.state ~= "itsMaterial" then print(s.means) end
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowCacheStats {#typed-builtin-modules-api-engine-renderer-renderer-shadowcachestats}

```lua
renderer.shadowCacheStats() -> {
```

What the last frame did with the shadow maps it already had. A shadow
view — one directional cascade, one atlas layer of spot tiles, one face of
a point light's cube — is drawn again only when something it draws from
changed:
its light moved, a caster it can see moved or appeared or vanished, a
caster's geometry or material changed, a caster changed pose or moved the
nodes its parts are placed by, or the map it writes into was reallocated.
Anything else keeps the depth already in the texture, so a scene that stops
moving reads `rendered` 0 while `cached` keeps climbing. A mesh whose
vertices a compute pass writes — a population, or a mesh built from a
compute buffer — re-renders the views it stands in every frame. A shadowed
point light contributes six views, one per cube face, so a caster moving on
one side of it re-renders the face that can see it and leaves the other
five holding what they have. Counted per light kind, plus the totals across
all three.

These are totals over every view of a kind. `renderer.shadowViews()` is the
same frame one view at a time, each row naming the light that owns it and
what it drew.

**Returns** `{ directionalRendered, directionalCached, spotRendered, spotCached, pointRendered, pointCached, rendered, cached }`

```lua
local s = renderer.shadowCacheStats()
print(("shadow views: %d drawn, %d cached"):format(s.rendered, s.cached))
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowCaching {#typed-builtin-modules-api-engine-renderer-renderer-shadowcaching}

```lua
renderer.shadowCaching() -> boolean
```

Whether a shadow view may keep the depth it already holds.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/shadowCasterBatching {#typed-builtin-modules-api-engine-renderer-renderer-shadowcasterbatching}

```lua
renderer.shadowCasterBatching() -> boolean
```

Whether a shadow view draws every caster of one mesh together.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/shadowCasterCutoff {#typed-builtin-modules-api-engine-renderer-renderer-shadowcastercutoff}

```lua
renderer.shadowCasterCutoff() -> ShadowCasterCutoff
```

How small, and how far away, a caster may get before it stops writing
depth into any shadow view. A shadow view rasterizes a caster's whole
triangle count whatever the shadow it produces ends up covering, so an
object the viewer resolves a fraction of a pixel of, and one past the range
the scene cares about, each cost a full depth pass per shadowed light for
detail nothing reads. Both thresholds are 0 — released — until something
sets them.

**Returns** `ShadowCasterCutoff` — The cutoff in force — see `ShadowCasterCutoff`.

```lua
local c = renderer.shadowCasterCutoff(); print(c.minRadiusPx, c.maxDistance)
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowConfig {#typed-builtin-modules-api-engine-renderer-renderer-shadowconfig}

```lua
renderer.shadowConfig() -> ShadowConfig
```

The directional shadow quality now in force. `resolution` and `cascades`
size the cascade depth array; `distance` and `splitLambda` place the splits
along the view; `fadeFraction` and `softness` shape how the result is
sampled.

**Returns** `ShadowConfig` — The full config — see `ShadowConfig`.

```lua
print(renderer.shadowConfig().cascades)
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowHero {#typed-builtin-modules-api-engine-renderer-renderer-shadowhero}

```lua
renderer.shadowHero() -> ShadowHeroReport
```

The registered hero caster and what the last frame's fit produced. A
frame that fit nothing says why in `decline`.

**Returns** `ShadowHeroReport` — See `ShadowHeroReport`.

```lua
local h = renderer.shadowHero()
print(h.active, h.zoom, h.decline)
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowMemory {#typed-builtin-modules-api-engine-renderer-renderer-shadowmemory}

```lua
renderer.shadowMemory() -> {
```

How much GPU memory the shadow maps hold right now, in bytes, by the
light kind that owns them. The spot atlas and the point pool are sized to
the casters in the scene rather than to the budget, so `spot` and `point`
move as lights that cast shadows appear and leave, and a scene with one
shadowed light holds far less than one that fills every slot. A budget is
the ceiling they grow within — `renderer.spotShadowBudget().layers` and
`renderer.pointShadowBudget().slots` report that ceiling, unmoved by how
many casters exist. Raising shadow resolution costs the square of the
change across every cascade.

**Returns** `{ directional, spot, point, total }` in bytes

```lua
local m = renderer.shadowMemory()
print(("shadows: %.1f MiB"):format(m.total / 1024 / 1024))
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowProxies {#typed-builtin-modules-api-engine-renderer-renderer-shadowproxies}

```lua
renderer.shadowProxies() -> ShadowProxyReport
```

The shadow proxies in force and what the last frame's shadow passes did
with them. `triangles` and `sourceTriangles` are what those passes
submitted and what they would have submitted from the source meshes — the
before/after of every registration, equal while nothing is proxied.

**Returns** `ShadowProxyReport` — See `ShadowProxyReport`.

```lua
local p = renderer.shadowProxies()
print(("shadow triangles: %d of %d"):format(p.triangles, p.sourceTriangles))
```

## typed/builtin//modules/api/engine/renderer/renderer/shadowViews {#typed-builtin-modules-api-engine-renderer-renderer-shadowviews}

```lua
renderer.shadowViews() -> ShadowViewReport?
```

Every shadow view the last rendered frame considered, and what each one
cost.

A frame rasterizes a depth view per directional cascade, one for the hero
caster, one per shadow-casting spot and six per shadow-casting point.
`renderer.shadowCacheStats()` counts those views by light kind,
`renderer.drawStats()` sums their draws with the camera's, and
`profiler.gpuFrame()` carries one `scene.shadow` span across all of them.
This is the same frame read one view at a time.

Each row names the view and the light that owns it, says whether it drew or
kept the depth it already held, and carries the draws, the instances and the
casters that went into it. `span` is the label the view's pass is timed
under, so its GPU time is a lookup in `profiler.gpuFrame()`; every one of
those labels is a variant of `scene.shadow`, which still carries their
total. `camera` carries the same instance counters for the main camera, so
the camera's share of a frame-wide total is a read rather than a measurement
taken by turning every light's shadow off.

A cascade's `near` and `far` are where the split scheme cut its slice, not
the world it covers: the fit takes the bounding sphere of that slice and
rasterizes the ortho box around it, and both reach past `far`. What the
cascade covers is `center` and `radius`, with `viewProj` the exact test;
`coversNear` and `coversFar` read that volume back along one ray, the
camera's view axis. `directional` states the axis reading for the set —
how far it reaches (`coversFar`), the range the splits were run over
(`distance`), how far the camera draws (`cameraFar`), and the
depth past the reach the camera still draws (`uncovered`). A receiver
further along the axis than `coversFar` has no directional depth map over
it and is shaded as if the sun reached it, so `uncovered` is the room a
missing shadow has and a surface standing in that room is what makes one;
`@builtin::systems.proxyOcclusion` occludes past the cascades. The box is
bounded in every direction, so a receiver standing wide of the axis leaves
it at its own distance even where `uncovered` is 0 — `viewProj` is what
answers for that receiver.

The list is rebuilt every frame: a view whose light stopped casting is
absent from the next report rather than standing at the numbers it last had,
and a frame that drew no shadow view answers a report whose `views` is
empty. `views` grouped the way the shadow cache decides — a row per cascade,
per spot atlas layer, per point cube — counts what
`renderer.shadowCacheStats()` reports as `rendered + cached`.

The frame names its views only while something is reading them, so this
call asks the frames after it to name theirs and waits out the first one.
Nil on an engine that renders no frame at all.

**Returns** `ShadowViewReport?` — See `ShadowViewReport`.

```lua
local report = renderer.shadowViews()
print(("camera submitted %d of %d instances"):format(
report.camera.submittedInstances, renderer.drawStats().compactedDrawn))
for _, view in report.views do
print(("%s %d (%s): %d draws, %d instances, %s"):format(
view.role, view.index, view.light, view.draws, view.instances,
view.rendered and "drew" or "cached"))
end
local d = report.directional
if d ~= nil and d.uncovered > 0 then
print(("sun shadows stop at %.0f; the camera draws to %.0f"):format(
d.coversFar, d.cameraFar))
end
```

## typed/builtin//modules/api/engine/renderer/renderer/skinnedBatching {#typed-builtin-modules-api-engine-renderer-renderer-skinnedbatching}

```lua
renderer.skinnedBatching() -> boolean
```

Whether skinned instances holding one pose draw together.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/skinningPoseHold {#typed-builtin-modules-api-engine-renderer-renderer-skinningposehold}

```lua
renderer.skinningPoseHold() -> boolean
```

Whether a pose already written into its slice skips its dispatch.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/skinningStats {#typed-builtin-modules-api-engine-renderer-renderer-skinningstats}

```lua
renderer.skinningStats() -> {
```

What the last frame's skinned instances cost. A skinned instance is
posed by a compute pass that writes its vertices into a shared pool, and
instances holding the same pose read one slice of that pool and the single
dispatch that fills it. `instances` is how many were posed, `poses` how
many distinct poses they held, and `dispatches` how many dispatches those
poses cost this frame — so a crowd whose members move together costs what
its poses cost rather than what its head count does, while members at
different animation times each hold their own pose and pay for it.

`held` is how many of the frame's poses cost no dispatch at all. The pass
produces a slice from what the pose is made of, so a slice an earlier frame
filled already holds what running it again would write, and a pose still
wearing that slice is read as it stands. Skinning is paid for by the poses
that CHANGED: a cast standing still reads `dispatches` 0 beside a `held`
equal to its `poses`, and the two add up to `poses` in any frame.

`reusedSlices` is how many of the frame's poses took a slice the pool
already held — one a retired pose gave back, or one a pose nothing has
asked for this frame was holding — rather than one cut from pool the
engine had never used. A scene whose poses keep changing reads a non-zero
count beside a `poolBytes` that stays where it was.

`liveBytes` is what the slices holding this frame's poses occupy, against
`unsharedBytes` — what the same instances would occupy with a slice each.
`poolBytes` is what the pool holds; a previous-position buffer of the same
size rides alongside it so skinned deformation reaches motion vectors.

**Returns** `{ instances: number, poses: number, dispatches: number, held: number, reusedSlices: number, liveBytes: number, unsharedBytes: number, poolBytes: number }`

```lua
local s = renderer.skinningStats()
print(("%d instances over %d poses"):format(s.instances, s.poses))
print(("%d poses dispatched, %d read as they stood"):format(s.dispatches, s.held))
print(("skinned vertex storage: %d B of an unshared %d B"):format(s.liveBytes, s.unsharedBytes))
```

## typed/builtin//modules/api/engine/renderer/renderer/spotShadowBudget {#typed-builtin-modules-api-engine-renderer-renderer-spotshadowbudget}

```lua
renderer.spotShadowBudget() -> SpotShadowBudget
```

The spot and area-light shadow atlas now in force. Each shadow-casting
spot is given a tile of it every frame, sized to what the camera can
resolve: a light filling the view gets a whole layer at `resolution`, one
far away gets a `minResolution` tile, and the atlas holds `tiles` of the
smallest kind. That is what lets one budget serve a close hero light and a
street of distant ones without either the memory or the sharpness being set
for the worst case.

**Returns** `SpotShadowBudget` — The atlas — see `SpotShadowBudget`.

```lua
local s = renderer.spotShadowBudget()
print(("%d layers of %d, %.1f MiB"):format(s.layers, s.resolution, s.bytes / 1024 / 1024))
```

## typed/builtin//modules/api/engine/renderer/renderer/textureMemory {#typed-builtin-modules-api-engine-renderer-renderer-texturememory}

```lua
renderer.textureMemory() -> {
```

What the GPU texture cache holds, split by whether the texture is
block-compressed. `compressedBytes` and `uncompressedBytes` are what those
textures cost in VRAM, measured from each texture's own format and mip
chain — so a `.texture` whose settings name `format = "bc7"` appears in the
compressed columns at a quarter of what the same image costs as RGBA8.
`blockCompressionSupported` is whether this adapter can hold
block-compressed textures at all; where it is false a BC7 payload is
uploaded decoded and lands in the uncompressed columns instead, so the
texture is present everywhere and compressed where the hardware allows it.
Measured at the end of the last rendered frame.
`streamableTextures` is how many of them a texture budget can move the
base mip level of, split by where a level change reads the levels it needs
from: `assetStreamedTextures` are read back from the asset they came from
and hold nothing in system memory, `retainedTextures` hold the payload
because a script uploaded their pixels and the GPU copy is the only other
one there is. `streamSourceBytes` is what those held payloads occupy in
system memory — bytes that are not VRAM — so it is a reading on the
retained half alone. `pinnedTextures` counts the textures big enough to
stream that stand at a level nothing can move: their pixels were released
and no asset holds them, the asset behind them could not be read back, or a
UI image, a post-process property or a render feature holds a view of them.
A texture out of the streamable set only because no measured surface wears
it stands in neither count: a surface reaching it takes it back up, so its
level moves again as soon as there is a footprint to move it by. It reads 0
while no budget is armed.

**Returns** `{ blockCompressionSupported, compressedTextures, compressedBytes, uncompressedTextures, uncompressedBytes, streamableTextures, assetStreamedTextures, retainedTextures, pinnedTextures, streamSourceBytes }`

```lua
local tm = renderer.textureMemory()
print(("%d compressed textures hold %.1f MiB"):format(tm.compressedTextures, tm.compressedBytes / 1048576))
print(("%d textures stream from their asset, %d hold %.1f MiB of pixels"):format(
tm.assetStreamedTextures, tm.retainedTextures, tm.streamSourceBytes / 1048576))
```

## typed/builtin//modules/api/engine/renderer/renderer/textureStreaming {#typed-builtin-modules-api-engine-renderer-renderer-texturestreaming}

```lua
renderer.textureStreaming() -> TextureStreaming
```

What the last frame's texture-residency plan decided. `budgetBytes` is
the armed budget, and `0` means residency is left alone. `streamable` is
how many textures the plan can move. `residentBytes` is what those textures
occupy now, measured from the textures that are allocated; `demandedBytes`
is what the frame's demand alone would have cost, so the two part exactly
where the budget is doing something. `starved` counts the textures left
coarser than the frame asked for, `promoted` the ones that climbed a level
this frame, and `changed` the ones whose GPU texture was replaced. A camera
approaching a surface reads `promoted` above zero for a few frames and then
zero once it settles.

`textures` is one row per streamable texture, ordered by key, carrying the
level each one was asked for and the measurement that asked. Two byte
totals can agree while a single texture sits several levels off what its
surface samples, so read the row when the question is which level a texture
holds and why.

With `budgetBytes` at 0 nothing holds a level back, so `residentBytes`,
`plannedBytes` and `demandedBytes` all read the whole chain of every
texture still enrolled and `textures` is empty — which is how a session
that armed a budget and dropped it reads back that the levels came home.

**Returns** `TextureStreaming` — `{ budgetBytes, streamable, residentBytes, plannedBytes, demandedBytes, starved, promoted, changed, textures }`

```lua
local ts = renderer.textureStreaming()
print(("textures: %.1f MiB resident of %.1f MiB demanded, %d starved"):format(
ts.residentBytes / 1048576, ts.demandedBytes / 1048576, ts.starved))
```

## typed/builtin//modules/api/engine/renderer/renderer/transmissionShadows {#typed-builtin-modules-api-engine-renderer-renderer-transmissionshadows}

```lua
renderer.transmissionShadows() -> boolean
```

Whether translucent casters tint the directional light they block.

**Returns** `boolean`

## typed/builtin//modules/api/engine/renderer/renderer/uploadStats {#typed-builtin-modules-api-engine-renderer-renderer-uploadstats}

```lua
renderer.uploadStats() -> {
```

What the last completed frame spent re-describing its renderables to the
GPU. Every renderable owns a slot in the per-instance data a draw reads —
its world matrix, the bounds the culler tests it by, and the flags that
decide which passes and which culling stages see it — and a frame uploads
only the slots whose contents changed. `bytes` is what those uploads
carried, `fullBytes` what re-sending every slot would have cost, and
`writes` how many buffer writes carried it. The three numbers cover that
per-renderable data alone, so a scene standing still reads `bytes = 0`
against a `fullBytes` that grows with the scene, and the ratio says how much
of it the scene's own churn — rather than its size — is paying for.

**Returns** `{ writes: number, bytes: number, fullBytes: number }`

```lua
local u = renderer.uploadStats()
print(("instance upload: %d B of %d B in %d writes"):format(u.bytes, u.fullBytes, u.writes))
```

## typed/builtin//modules/api/engine/renderer/renderer/variantSource {#typed-builtin-modules-api-engine-renderer-renderer-variantsource}

```lua
renderer.variantSource(program: string) -> string?
```

The WGSL one of the programs `renderer.shaderVariants()` lists holds,
exactly as the shader compiler received it. `program` is the `program`
field of a row's `base` or of one of its `variants`. Reading a base
alongside a variant shows what a feature set selected: each program's text
holds the code its own features guard. The variant-report spelling of
`renderer.compiledSource`, which answers the same for every other shader.

**Parameters**

- `program` `string` — A `program` name from `renderer.shaderVariants()`.

**Returns** `string?` — The compiled WGSL, or nil for a name no compile has run under.

```lua
local row = renderer.shaderVariants()[1]
local base = renderer.variantSource(row.base.program)
```
