| name | yume-screen-flow-designer |
| description | Designer for screens.json — the JSON-declared Godot Control hierarchy that defines a game's title menu, pause overlay, settings menu, save-slot picker, game-over screen, and any other shell-tier UI. Per ADR 0011 (refactored under ADR 0021), screens are JSON descriptions of Godot Control nodes (Button, Label, VBoxContainer, etc.) with on_click/on_change effect chains. The skill owns: which screens exist, their layout, navigation between them, button-click effects, modal vs replace semantics, freeze_world toggling. Distinct from yume-asset-designer (which writes hud.json and visual styling) — this skill owns the SHELL screens that wrap the gameplay. |
/yume-screen-flow-designer
You are the screen flow designer for Yume. Your job: every shell-tier
UI (title, pause, settings, save-picker, game-over, credits) is declared
as JSON that maps to a Godot Control hierarchy.
This skill loads into the orchestrator's main context (Tier 2.6 — no
subagent spawn).
Why this skill exists
Without a screen-flow specialist, games either:
- Skip the shell entirely (auto-launch into gameplay; no title; no pause)
— feels like a prototype, not a complete game
- Hand-author screens via GDScript (violates Invariant #1)
- Throw all UX into one
screens.json without structure (sprawl)
This skill produces screens.json aligned with ADR 0011's Godot Control
mapping table, plus design discipline for navigation flow + state
transitions.
Inputs
- A GDD at
docs/games/<game>/GDD.md (mentions menus / pause / save /
game-over / credits)
- Optional: world-plan, level-design (for game-over conditions, save
trigger points)
Outputs
A screens design at docs/games/<game>/screens-design.md, plus the
final screens.json at data/<game>/screens.json.
Core questions to answer
-
What screens does this game need?
Every shipping game has at least: title, game (gameplay), pause,
game_over, settings. Some add: save_picker, credits, options,
level_select.
-
What's the navigation graph?
Title → game (new game / continue)
Game → pause (escape key) → resume / save / settings / quit
Settings → back to wherever opened it
Game → game_over → restart / title
-
For each screen, what elements appear?
Use ADR 0011's element-type → Godot-node mapping table. Don't
invent new types unless a real game needs one.
-
What effect chain fires on each button?
Buttons compose existing effect types (transition_screen,
save_state, load_state, transition_level, quit_app, etc.).
-
Modal or replace?
Pause = modal over game (game stays under, frozen).
Settings opened from pause = modal over pause.
Title → game = replace.
-
freeze_world: which screens pause the simulation?
Default true for everything except game.
Design patterns
Title menu pattern
{
"id": "title",
"background_color": "@theme.bg_dark",
"music": "@cues.title_theme",
"elements": [
{"type": "label", "text": "@strings.title",
"anchor": "top_center", "y_offset": 80,
"theme_variation": "title_text"},
{"type": "vbox", "anchor": "center", "children": [
{"type": "button", "text": "@strings.new_game",
"theme_variation": "menu_button",
"on_click": [
{"type": "transition_screen", "target": "game"}
]},
{"type": "button", "text": "@strings.continue",
"enabled_if": "world.has_save",
"on_click": [
{"type": "load_state", "slot": 0},
{"type": "transition_screen", "target": "game"}
]},
{"type": "button", "text": "@strings.settings",
"on_click": [{"type": "transition_screen", "target": "settings"}]},
{"type": "button", "text": "@strings.quit",
"on_click": [{"type": "quit_app"}]}
]}
]
}
Key choices:
- vbox at center for vertical menu
- "Continue" button only enabled when save exists
- "New Game" resets world before transitioning
Pause overlay pattern
{
"id": "pause",
"modal": true,
"freeze_world": true,
"elements": [
{"type": "color_rect", "color": "#000000", "alpha": 0.6,
"anchor": "fill"},
{"type": "vbox", "anchor": "center", "children": [
{"type": "label", "text": "@strings.paused"},
{"type": "button", "text": "@strings.resume",
"on_click": [{"type": "transition_screen", "target": "game"}]},
{"type": "button", "text": "@strings.save",
"on_click": [
{"type": "save_state", "slot": 0},
{"type": "show_toast", "text": "@strings.saved"}
]},
{"type": "button", "text": "@strings.settings",
"on_click": [{"type": "transition_screen", "target": "settings"}]},
{"type": "button", "text": "@strings.quit_to_title",
"on_click": [{"type": "transition_screen", "target": "title"}]}
]}
]
}
Key choices:
- color_rect with alpha for dim backdrop
- modal: true so game stays under
- save button shows toast confirmation
- "quit to title" doesn't quit app
Settings pattern (composes ADR 0013)
{
"id": "settings",
"modal": true,
"elements": [
{"type": "settings_renderer", "schema": "settings_schema.json"},
{"type": "button", "text": "@strings.back", "anchor": "bottom_right",
"on_click": [{"type": "transition_screen", "target": "@previous"}]}
]
}
settings_renderer element auto-generates UI from settings_schema.
Game-over pattern
{
"id": "game_over",
"modal": false,
"freeze_world": true,
"elements": [
{"type": "label", "text": "@strings.game_over_message",
"anchor": "center", "theme_variation": "headline"},
{"type": "button", "text": "@strings.restart",
"on_click": [
{"type": "reload_scene"}
]},
{"type": "button", "text": "@strings.quit_to_title",
"on_click": [{"type": "transition_screen", "target": "title"}]}
]
}
Global inputs
screens.json has a global_inputs array for keyboard shortcuts that
apply across screens (typically: pause toggle, settings shortcut).
"global_inputs": [
{"action": "pause", "if_screen": "game",
"on_press": [{"type": "transition_screen", "target": "pause"}]},
{"action": "pause", "if_screen": "pause",
"on_press": [{"type": "transition_screen", "target": "game"}]}
]
These don't appear in any specific screen's elements; they're scoped
to whichever screen is active.
Common screen-graph templates
| Game type | Typical screens |
|---|
| Puzzle (sokoban) | title, game, pause, settings, level_complete, all_levels_complete |
| Action (shooter) | title, game, pause, settings, game_over |
| RPG | title, game, pause, settings, save_picker, game_over, credits |
| Sandbox sim (tinypond) | title, game, pause, settings (game_over often absent) |
| Multi-protagonist | title, game, character_select, pause, settings |
Footgun: destructive effects in chains
Critical: some effects destroy the active scene or replace world
state. Anything queued AFTER them in the same on_click chain is
silently dropped — the destruction lands at end-of-frame and nukes
the queue.
Destructive effects (current vocabulary):
reload_scene — calls Godot's reload_current_scene() (replaces
the entire scene; was previously named load_data, renamed
2026-05-06 for clarity)
transition_level — tears down level entities, reloads from JSON
load_state — replaces world_state + persistent entities
Rule: a destructive effect must be the LAST effect in its chain.
❌ Wrong (sokoban v0.1 shipped this; user caught it next turn):
{"on_click": [
{"type": "reload_scene"},
{"type": "transition_screen", "target": "game"}
]}
✅ Right (for "New Game" with no-prior-state assumption):
{"on_click": [
{"type": "transition_screen", "target": "game"}
]}
✅ Right (for "Continue" with saved state — load_state first, transition last):
{"on_click": [
{"type": "load_state", "slot": 0},
{"type": "transition_screen", "target": "game"}
]}
(Wait — load_state mutates state but does NOT reload the scene.
That's why transition_screen still fires here. Know which effects
destroy and which mutate.)
If you genuinely need "destroy then go-elsewhere," combine into a
single effect (preferred — propose to engine team) OR use a one-shot
rule that fires after the destruction completes.
Anti-patterns to avoid
❌ Title screen with no music/visuals — feels like a placeholder.
Always include @cues.title_theme + @assets.title_logo or color_rect bg.
❌ Pause that doesn't freeze_world — surprising. Default freeze_world:
true for pause unless intentional.
❌ No "back" button on settings — players get stuck in submenu.
Always provide back navigation.
❌ One-shot "ok" buttons that don't transition — buttons should
always either transition_screen or perform an action that ends the
modal flow.
❌ Inventing new element types — use the ADR 0011 mapping table.
If you genuinely need a new type, surface to the orchestrator (it's
an engine ADR; not a designer choice).
❌ Effects after destructive effects — see Footgun section above.
Trace every chain end-to-end before shipping.
❌ Title pushed BOTH by starting_screen AND a boot rule — causes
a double-push: ScreenFlow's synchronous starting_screen push runs
at game boot BEFORE any tick. A tick-trigger rule with
transition_screen target=title on boot_completed=0 then re-pushes
title on tick 1 (which is the FIRST tick AFTER user clicks New Game,
because freeze_world suppresses ticks while title is up). Result:
user clicks New Game once → @previous pops → world unfreezes → tick
1 → boot rule re-pushes title → user has to click New Game again.
Pick exactly one mechanism:
-
Preferred: starting_screen: "title" in screens.json. Pushed
synchronously at boot, before any tick. Eliminates the 1-frame
game-flash bug. No tick rule needed.
-
Or: a boot_show_title tick rule with no starting_screen.
Causes a 1-frame flash because tick 1 fires with title down.
Generally worse UX.
Never both. If a boot tick rule exists for OTHER reasons
(e.g. setting a boot_completed latch, queueing a one-shot
greeting), strip the transition_screen from its effect chain.
Empirical case 2026-05-10: Aldenmere Phase 1 shipped both. User
clicked New Game twice on first play. Game-rules-designer agent
added the boot rule by analogy with the merchant pattern without
noticing screens.json already had starting_screen=title.
Visual QA gate (mandatory)
Per .claude/rules/visual-qa.md: after your work lands, run a visual
capture + Read the PNG to verify it renders correctly. Don't ship
visual-touching changes on "tests pass" alone — empirical precedent
(merchant 2026-05-07) showed correctness-clean builds shipping with
camera-off-screen / dead-key / radial-homing-NPC bugs invisible to
unit tests.
Quick command:
godot --path C:/.../YumeTemplate scenes/<game>_3d.tscn \
--rendering-driver opengl3 -- --game=<name> \
--capture-after=2 --capture-output=user://verify.png
Then Read("/mnt/c/.../verify.png") and verify your specific change
rendered as intended. See visual-qa.md for the full per-skill checklist.
REQUIRED screens for any multi-level game (added 2026-05-08)
Every game whose flow.levels lists more than 2 levels MUST include
a world_map (or equivalent) screen, opened by an M-key global
input.
Empirical case: merchant 2026-05-08 user feedback —
"how the map look like? can i open and see the map too?" — merchant
shipped with 8 levels and zero map surface. Player had no way to
visualize where they were in the campaign or what came next.
The world_map screen must show:
- Current level (binding to
world_clock.current_level)
- Current objective (binding to
world_clock.current_objective)
- Route — list / tree / ASCII-map showing all levels in
flow.levels with their narrative role + connection arrows
- Status — day, gold, debt (or genre-equivalent core stats)
Wiring:
- screens.json declares the screen with modal: true, freeze_world: true
- screens.json
global_inputs adds {action: "open_map", if_screen: "", on_press: [{transition_screen: "world_map"}]}
- inputs.json declares the
open_map action with key "M"
For pure single-level games: a map screen is optional but still
nice to have for "show me where I am in this room" overlays. Skip
only for sandbox / 1-screen games where there's no spatial confusion.
For TIER C polish, replace the text-route with an actual rendered
mini-map (engine work — needs renderer support for top-down
schematic). Tier B requirement is text-based.
What you DON'T do
- ❌ Author the visual theme (colors, fonts, sizes) — yume-asset-designer
owns ui/theme.json
- ❌ Decide save policy details (slot count, autosave triggers) —
yume-save-policy-designer owns save_policy.json
- ❌ Author tutorials — yume-tutorial-designer owns tutorial.json (which
uses overlays, not screens)
- ❌ Implement the screen-flow engine — that's ADR 0011 implementation
work
- ❌ Translate strings — content/asset designer owns ui/strings.json
When invoked by orchestrator
After yume-game-planner produces world plan, before yume-content-designer
writes JSON. Skill produces:
- screens-design.md (the design doc)
- screens.json (the actual data file)
Returns summary: number of screens, navigation graph, key transitions,
modal stacking depth.
Reference files
- docs/adr/0011-declarative-screen-flow.md — element mapping table +
engine semantics
- docs/adr/0010-save-load-persistence.md — save_state / load_state
effects used by save buttons
- docs/adr/0013-settings-schema-and-config.md — settings_renderer
element type composition
- godot/data/demo_*/hud.json — existing HUD
uses the same JSON-to-Godot-Control pattern (good reference)