World overlays and the pointer
Current contract for 3D overlays on the client (boxes, lines, the cursor
highlight, chunk borders, debug layers) and for the platform’s cursor and
pointer surfaces. Design:
Enki B §5 and §6 (docs/superpowers/specs/2026-09-27-enki-b-mod-platform-contract-design.md)
(and its “As built” issue 38). Source:
crates/manifold-client-state/src/overlay/,
crates/manifold-overlay-protocol/, crates/manifold-render/src/overlay.rs,
crates/manifold-client-state/src/pointer.rs,
crates/manifold-client-state/src/bridged_specs.rs,
crates/manifold-client-api/.
The rule
Every 3D overlay is content in one client scene, OverlayScene, owned by
either a mod or a platform layer, and the renderer draws it in one pass.
Nothing calls the renderer to draw lines of its own, so turning one overlay on
never overwrites another. Overlays are presentation: client only, never
replicated, never on the wire. Showing another player’s selection is the mod’s
decision, carried on its own channel.
| Owner | Content | Budget | Written through |
|---|---|---|---|
Mod(id) | Retained box and lines nodes | 4,096 nodes and 262,144 line points per mod | Op batches (OverlayScene::apply, apply_encoded); manifold:render/world-overlay once it publishes |
Platform(layer) | Retained nodes (native tools), or immediate content rebuilt every frame | Exempt | The Rust API |
The immediate platform layers are manifold:cursor_highlight (from
CursorTarget), manifold:chunk_borders (F3), manifold:anim_hitboxes
(/anim debug hitboxes), manifold:telegraphs (Talos B telegraphs) and
manifold:ai_overlay (Talos G paths, sight, hearing and arcs). Each domain
still generates its own lines; the overlay draws them as its layer. A new
native layer adds a PlatformLayer constant and an arm in
overlay::platform::emit_layer.
Ops and budgets
A batch is Vec<OverlayOp>: Create, SetTransform, SetShape,
SetStyle, SetVisible, Remove, Clear. It applies entirely or not at
all. A malformed op (duplicate or unknown id, non-finite or inverted
geometry, a width outside 0.5–64 px, a segment list with an odd point count)
refuses the batch with Invalid; a batch that would leave a mod over its
budget is refused with BudgetExceeded. Nothing is dropped silently.
Every node has a transform (translation in f64, rotation, scale), a style
(edge colour, optional fill, width in pixels, depth mode), a visible flag, a
pickable flag and a draw order. Colours are a literal (Srgba8, Linear) or
a theme token of the Manifold look (docs/manifold-look.md) (Brass, Error,
…), resolved through the OverlayTheme client resource.
Resolution and the pass
Once per frame, after model poses, each host calls
overlay::resolve_frame(world, &PlatformFrame { chunk_borders }, &mut list)
and Renderer::update_overlay(&list). Resolution lowers nodes in draw order
(order, then owner, then id; the immediate layers draw at order 0, ahead of
retained order-0 nodes) into an OverlayDrawList: line segments and fill
triangles, bucketed by depth mode.
The renderer’s overlay pass runs after post and before egui, on the presentation (unjittered) camera, exactly where the old line pass ran, on native wgpu and browser WebGPU alike:
- Lines are instanced screen-space quads
width_pxwide with square caps, clipped at the near plane before expansion. WebGPU has no line width, so this is the only line path. - Depth modes:
Occludedtests against scene depth (reversed-ZGreater);Alwaysskips the test;XRaydraws normally and then where hidden (Less) at 35% alpha. Nothing writes depth. - Fills blend with straight alpha before the edges of the same bucket.
Picking
overlay::raycast(scene, owner, chunks, registry, Ray { origin, dir, max }, RayOptions { include_overlays }) returns the first RayHit: a block (its
position, face normal and value) within max blocks (up to 4,096, not limited
to gameplay reach), or, with include_overlays, one of the caller’s own
pickable, visible boxes. The nearer wins and an overlay wins a tie. A ray that
starts inside a box hits where it leaves. Lines are never hit.
Platform cursor surfaces
These bridged resources are registered by the platform
(manifold_client_state::bridged_specs::client_bridged_specs), never by a
game, so any mod reads them whichever game is loaded. Their guest views are
public in manifold-client-api (catalogs in manifold-server-api):
| Id | View | Source |
|---|---|---|
manifold:cursor_target_view | CursorTargetView | The crosshair’s block within reach (CursorTarget.hit): what break and place act on |
manifold:water_target_view | WaterTargetView | The first fluid cell along the crosshair |
manifold:block_action_handles | BlockActionHandlesView | Resolved break, place and interact handles |
voxel_factory:block_catalog | BlockCatalogView | Block names and basic properties by raw id |
manifold:fluid_catalog | FluidCatalogView | Each block’s fluid source |
manifold:pointer_view | PointerView | The pointer ray, the block under it, and the pointer’s state |
A guard test fails if the flagship registers any of them.
Pointer view. CursorTargetSystem casts the pointer ray every frame
beside the crosshair: through the cursor while it is free and inside the
window (inverting the renderer’s projection), else through the screen
centre, up to POINTER_PICK_DISTANCE (256 blocks). It records the hit’s
position, face normal and value in PointerTarget. pointer::pointer_view
is the one consumer API: it combines the target with the tick’s wheel and
cursor position, the physical buttons (PointerButtons) and the input
stack’s PointerGate (over_viewport, buttons_to_mods). The input runtime
writes both on both hosts. evaluate_input_frame writes the gate after every
evaluation: the frontend base, or an effective engine-band context that
sinks the pointer everywhere (manifold:ui for menus and BUI windows, the
console, engine tools, rebind capture), closes it; text entry sinks only the
keyboard and mods’ Game and Tool contexts never close it. Without an input
runtime there is no gate and the pointer reads closed. InputCollectionSystem
folds each frame’s raw button edges and TickInputBuilderSystem publishes
PointerButtons when the tick closes, with the evaluator’s held buttons and
modifiers.
Not built yet
manifold:render/world-overlayis an unpublished WIT draft (crates/manifold-wasm-abi/wit-drafts/render/world-overlay.wit). It publishes whole, at a lockstep bump after the foundation gate, once every item below is built. Guests cannot create overlays until then.- Engine hover and click picking (
take-events), gizmos, voxel outlines, ghost blocks, text, markers, images andinclude-entities(Enki B §5.2, §5.4, §5.6). PointerView.wheelis still the camera-zoom action’s notches ([0, dy]), not the raw wheel. The exclusiveenki:editorcontext and capture are Enki’s, on I0b’s mod contexts (input spec §10.8).