Trusted packet-mod menus
A WASI preview 1 packet reactor may export menu(i32, i32) -> i32 alongside
native-init(i32). The host sets capability bit 2 only when the mod has the
controls grant. Native camera frames use bit 1 independently.
Reactors without optional callbacks retain their existing packet ABI.
Export discovery requests permissions but never grants them.
menu receives UTF-8 JSON bytes. Its return uses the same canonical
result<list<u8>, string> layout as other packet callbacks: a four-byte
discriminant, then the
pointer and length of the successful byte list or error string. Exporting
cabi_post_menu(i32) releases the returned allocation after copying it.
Input is bounded to 64 KiB:
{"version":1,"open":true,"viewport":[640,400],"events":[{"id":"enabled","value":1}],"keys":[{"key":"KeyA","text":"a"}]}
The optional viewport is the available authored GUI size before reference-size fitting; it follows window resizing and platform safe-area insets.
Version one starts with only these original fields, including for guests that
reject unknown JSON fields. To accept additional facts, return optional output
input_extensions:["pointer_state","shift_state","hud_geometry"]. The next
input includes only those groups: pointer_state adds pointer_held and
pointer_cancelled, shift_state adds shift_held, and hud_geometry adds its
map. Omission retains the accepted groups; an empty array clears them. Requests
must contain unique known groups. A new session starts with no extensions.
Each group’s field set is frozen for version one. Future input facts require a
new opt-in group so strict decoders that accepted an existing group remain valid.
The host delivers native control IDs and values. Focused menu keys may carry
composed text; native number and choice editors receive their keyboard input
first. capture_key lets the guest handle Escape and the panel’s toggle key while
open. The toggle key reopens the retained menu while closed. Other screens and
unfocused windows receive no guest menu keys.
Text controls are rejected before the panel is published. Version one delivers numeric control events; committed text values need a separate input contract.
Output is bounded to 512 KiB. JSON null retains the current menu. A changed
output contains open, optional panel using ui::mod_panel::Panel, and optional keys:
{"open":true,"panel":{"toggle_key":"ShiftRight","controls":[],"surface":{"screen":"example.root","document":"{\"namespace\":\"example\",\"root\":{\"type\":\"panel\"}}"}},"keys":["KeyR"]}
A panel always carries its JSON-UI surface; the mod draws every title, page and
preview itself. Optional widget_colors sets the RGBA background, surface,
text, accent and border of the popups the host draws for a choice dropdown and
the slider and text value editor; omitted colours keep the built-in palette. A
panel update may replace them with {"widget_colors":{...}}.
Panel motion and authored surface transitions belong to the mod and remain
independent of Cinnabar’s built-in Screen animations setting. Mods implement their
own motion preference by choosing motion and which transitions to declare.
Panel show_gameplay_hud: true keeps the resource-pack gameplay HUD visible behind
an open panel, for example for a placement editor. It defaults to false and can
change with full panel updates while retaining pointer capture. The player’s Hide
HUD setting and death screen still take priority; a focused panel always hides
the crosshair. Other extensions’ overlay cards remain suppressed during menus.
Omitting panel retains its previous document, allowing HUD-only updates before or
while the menu is closed. Optional hud_bindings supplies a bounded map of trusted
resource-pack HUD globals: names begin with #mod_, values are booleans, finite
numbers or numeric vectors of two to four components. An omitted map retains the
previous values; an empty map removes them. The host overlays these values without
mutating the shared server HUD data, and retires them with their packet session or
grants. HUD overrides do not require the menu to be visible.
Optional output hud_geometry:["example_clock"] requests the last painted bounds
of up to 128 unique inert resource-pack HUD input_panel instance names. Names
are opaque ASCII identifiers, at most 96 bytes; mapped controls are excluded.
Omitting the list retains it, and an empty list clears it. After opting into the
hud_geometry input extension, input returns a
hud_geometry map, for example:
{"example_clock":{"rect":[12,24,80,27],"size":[40,13.5]}}
rect contains x, y, width and height after composed ancestor paint transforms;
size contains the root’s layout width and height before paint transforms. Both
use the native panel GUI coordinate basis with safe-area origin removed. Missing,
hidden, ambiguous and stale-session roots are omitted. Geometry is a completed
frame observation and can lag one frame behind updated bindings.
Selectors match the engine’s resolved instance name. For an authored literal
name, underscores avoid JSON-UI interpreting a leading namespace. qualifier.
Trusted HUD catalogs support cinnabar_position:[x,y] or its
#cinnabar_position vector binding. Each normalized component selects the
remaining travel inside the parent after the widget’s transformed bounds, keeping
scaled widgets within the parent. Ordinary resource-pack catalogs ignore this
native extension. The host enables it for a HUD with active trusted mod bindings.
An authored surface.screen identifies a logical route. Catalog revisions of the
same screen retain binding tweens; a different screen starts its declared entry.
Closing animates the displayed surface before a retained future route is installed.
keys subscribes to physical key press edges while focused gameplay owns input.
Those keys are reserved from ordinary gameplay, never collected from chat or
another menu. Each native packet session has its own menu identity, input queue
and prepared catalog; reconnecting or revoking grants retires its pending input
and retained presentation. Calls run on the existing serial packet worker.
Consecutive held-pointer edits of declared sliders retain their latest values
while the worker is busy. Buttons, keys, cancellation, visibility changes and
newly delivered panels preserve their ordering boundaries.
Custom panels support surface bindings, eased transitions, glass and paint
transforms through the shared JSON-UI engine. The mod_panel_sans font alias selects the shipped Inter outline face. mod.control:N invokes declared
control N. mod.vector:X:Y maps pointer position within its hit region to two
slider controls. mod.drag:X:Y preserves the initial grab offset and maps pointer
deltas across the available authored travel (reference dimensions minus the
handle’s painted bounds) to the same bounded sliders. Capture freezes that travel
until release, so position and scale updates cannot change its gain.
These routes use the full authored root coordinate space; keep an interactive
placement canvas untransformed and size it to that reference. A widget’s own
paint scale is included in its available travel.
Relative routes emit their current numeric values on press, without rounding or
moving them, so a guest can begin selection and an exact gesture rollback snapshot.
mod.drag_axis:N:x or mod.drag_axis:N:y changes one slider relatively across the
full authored reference dimension, suitable for a scale handle without a grab jump.
An optional positive reference extent, as in mod.drag_axis:N:x:112, replaces
that dimension when an authored handle needs a specific sensitivity.
The extent may instead name a declared positive numeric surface binding, as in
mod.drag_axis:N:x:#resize_extent; its value is captured on press, so geometry
updates during the gesture keep the same sensitivity and stable document.
Input pointer_held reflects focused menu capture, including release and focus
loss without another control edit.
pointer_cancelled marks an interrupted accepted gesture, such as lost focus,
cursor loss or another screen taking input. It is delivered once, including when
several samples coalesce; normal release leaves it false. shift_held is the
current focused physical Shift state. Ordered visibility phases retain earlier
edits while rejecting their stale panel results, so closing before a worker poll
cannot silently lose a final HUD edit.
The host owns drawing, input routing and animation; the guest owns feature state.
The Mods library detects the native callback permissions of raw reactors when
no explicit cinnabar:grants section exists. A native menu requests Controls.
Requests still require user consent and apply to a new server session. An explicit
grant section stays authoritative, including when it omits native Controls.
For a window-free layout capture, run:
cargo run -p ui-snapshot -- --panel panel.json --out panel.png \
--width 1280 --height 720 --gui-scale 2 --time 1 --carriers /path/to/compiled
The CPU snapshot path uses the production panel painter and local validated carriers. A headless client capture verifies the GPU glass/backdrop appearance.
Packet mods have no HUD surface, so cinnabar_hud_control nodes in their panels
draw nothing; they draw their own previews in the panel surface.