How text finds a panel
Lives in: story nodes, character packs, and the panel command
Each dialogue moment draws text on one panel — the routed panel. Routing picks the id and the preset.
Which panel id gets the text
First match wins:
- Node field
panel_id - Pack
ui.default_panel_idfor the first speaker on the line (talking[0]) - Otherwise
main
{
"id": "side_note",
"panel_id": "side",
"talking": ["hana"],
"text": "She muttered aside."
}
With several speakers and no panel_id, only the first speaker drives routing (and panel speaker art). Joint lines: Lines and speakers.
If the id is not main and that panel does not exist yet, the player creates it (there is a small cap on extra panels). main is never destroyed.
Which preset (look) it uses
First match wins:
- Node field
panel(preset name) preseton apanelcommand aimed at that panel- Character
ui.default_panel_preset, if that preset exists in the theme - A preset whose name matches the panel id (e.g. id
side→panel.side) default
Unknown names fall back to default.
{
"id": "soft",
"panel": "whisper",
"talking": ["hana"],
"text": "…"
}
Character pack defaults:
"ui": {
"default_panel_id": "main",
"default_panel_preset": "whisper"
}
Sticky vs one-shot
| Surface | Effect |
|---|---|
Node panel / panel_id / panel_* policy fields | This moment only |
{ "type": "panel", "preset": "whisper" } | Stays until you change it (or "apply": "node_local") |
Pack ui.default_* | Whenever that character is first speaker and the node omits overrides |
Script sugar [panel whisper] sets the preset on main. Direction files can set panel_id and policy fields when the script is not enough — Adding direction.
Usually you need
| Want | Do |
|---|---|
Force this line to side | Node "panel_id": "side" |
| Always whisper for Hana | Pack ui.default_panel_preset: "whisper" |
| Change look for a stretch of scenes | panel command with "apply": "persistent" |