Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

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.

OwnerContentBudgetWritten through
Mod(id)Retained box and lines nodes4,096 nodes and 262,144 line points per modOp batches (OverlayScene::apply, apply_encoded); manifold:render/world-overlay once it publishes
Platform(layer)Retained nodes (native tools), or immediate content rebuilt every frameExemptThe 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_px wide with square caps, clipped at the near plane before expansion. WebGPU has no line width, so this is the only line path.
  • Depth modes: Occluded tests against scene depth (reversed-Z Greater); Always skips the test; XRay draws 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):

IdViewSource
manifold:cursor_target_viewCursorTargetViewThe crosshair’s block within reach (CursorTarget.hit): what break and place act on
manifold:water_target_viewWaterTargetViewThe first fluid cell along the crosshair
manifold:block_action_handlesBlockActionHandlesViewResolved break, place and interact handles
voxel_factory:block_catalogBlockCatalogViewBlock names and basic properties by raw id
manifold:fluid_catalogFluidCatalogViewEach block’s fluid source
manifold:pointer_viewPointerViewThe 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-overlay is 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 and include-entities (Enki B §5.2, §5.4, §5.6).
  • PointerView.wheel is still the camera-zoom action’s notches ([0, dy]), not the raw wheel. The exclusive enki:editor context and capture are Enki’s, on I0b’s mod contexts (input spec §10.8).