Files
stickman/docs/architecture/SANDBOX_THEME.md
T

16 KiB

Sandbox theme reference

res://sandbox_theme.json is the hand-editable presentation configuration for the Sandbox Stage (res://scenes/stage/sandbox_stage.tscn). It is loaded by SandboxStage during _ready(), before the code-built UI is created. The loader is res://scripts/stage/sandbox_theme.gd (class_name SandboxTheme).

The committed file uses schema "2.0". The theme controls presentation defaults only; it does not save a scene, placement, or action data.

Load, merge, and validation rules

The load path is:

res://sandbox_theme.json
        │
        ▼
SandboxTheme.DEFAULT_CONFIG
        │ deep-merge file values
        ▼
SandboxStage._build_ui() and visual overlays
  • Missing, unreadable, malformed, or non-object JSON uses the built-in default configuration and emits one warning. The stage continues to run.
  • Missing nested keys inherit the built-in value. Unknown keys are retained in the raw dictionary but have no effect until a consumer is added.
  • stylebox(component) resolves styles in this order: global → <section>.style_defaults → the component object. For example, palette is resolved from global, then palette.style_defaults, then palette.
  • Colors must be strings in #RRGGBB or #RRGGBBAA form. Invalid colors use the requested fallback and emit a warning.
  • Numeric accessors accept finite integers/floats and clamp them to the range requested by the consumer. Invalid or non-finite numbers use that consumer's fallback. Size arrays are accepted when they contain two numeric values; individual controls apply their own minimum sizes.
  • Font paths are res:// resource paths. A missing resource, or a resource that is not a Font, uses ThemeDB.fallback_font and emits a warning.
  • The theme is loaded once per Sandbox Stage instance. Restart the scene (F6) to see edits; there is no live file watcher or reload button.

The live grid size, snap toggle, and grid visibility are stored in user://sandbox_settings.json. grid.snap_size supplies the initial grid-size default; it does not overwrite a value already saved in user settings.

Schema overview

Every current top-level block is listed below. The values shown are the defaults in the committed sandbox_theme.json (not necessarily the fallback used by an older caller).

Current consumer status

The schema intentionally includes forward-looking values for the reusable widget and speech work. The current Sandbox runtime applies these groups:

  • global style values used by resolved styleboxes and the selector control theme (background/border/radius/padding); global.text_color and global.muted_text_color are available presentation values but are not applied to every existing label automatically.
  • fonts.ui_font, emoji_font, bold/italic variants, the nested sizes entries for action popups, tooltips, status pills, queue/rule/action editors, panel rows/titles, file_menu, file_menu_popup, and mode_switcher, plus the related flags.
  • mode_colors.edit, direct, play, and guide; grid.snap_size as the first-run grid default; dialog sizes/fonts; chrome.mode_switcher width; palette width/card size/drag threshold/style; selector cell and thumbnail sizes; and all overlays values.

These declared values currently remain reserved or require a legacy adapter:

  • chrome.top_bar.height, status_bar.height, mode_frame, mode_badge, and tooltip describe the intended chrome contract, but the corresponding layout/style constants are still authored in SandboxStage.
  • popups.action_menu.font_size and popups.asset_selector.backdrop_alpha are reserved component overrides. The active action-menu size comes from fonts.sizes.action_popup; selector cell_size and thumbnail_size are active.
  • palette.icon_size and palette.selected_border_width are reserved for the palette card renderer; the current card has its own glyph/border values.
  • fonts.sizes.assignment_badge, rule_label, asset_label, and asset_badge are part of the resolved schema, but the current direct call into StageDirectorVisuals still reads the legacy flat badge/label names. Use the legacy names documented below when changing those director visuals.
  • fonts.presets and every widgets list are exposed through reusable controls, but the current Sandbox Speak action does not instantiate those controls yet. They are future speech-customization data.

version

Key Format Current value Notes
version string "2.0" Use "2.0" when using the nested schema below.

global

Global style values are inherited by SandboxTheme.stylebox() and the default control theme.

Key Format Default Purpose
font_preset string "ui" Named base-font selection for future/general UI use.
font_size integer 18 Default Control font size.
text_color, muted_text_color color string #F5F7FA, #AAB2C0 Primary and secondary text colors.
bg_color, surface_color color string #171B24F2, #252B38FA Global and raised-surface backgrounds.
border_color color string #FFFFFF33 Default border color.
border_width integer 1 Default border width.
corner_radius integer 10 Default corner radius.
padding, spacing integer 8, 8 Default style padding and layout spacing.

fonts

Font paths may be empty ("") to use the engine fallback. presets is an object of arbitrary preset IDs; each entry has a display label and a path. The committed file provides ui, comic, pixel, and scary entries.

Key Format Default Consumer
ui_font, emoji_font res:// path or empty "" Base UI and emoji-capable stage/director controls.
ui_font_bold, ui_font_italic res:// path or empty "" Optional style variants; bold can be generated from the base font when no dedicated path is supplied.
presets object {id:{label,path}} see JSON font_presets() and future font selectors.
sizes.action_popup integer 24 Director/action popup text.
sizes.action_popup_emoji integer 22 Effective popup size when a popup font is active (Godot popup menus have one font size).
sizes.assignment_badge integer 20 Reserved resolved-theme value; current director visuals use legacy fonts.assignment_badge_font_size.
sizes.rule_label integer 18 Reserved resolved-theme value; current director visuals use legacy fonts.rule_label_font_size.
sizes.status_pill integer 18 Mode badge/pill.
sizes.tooltip integer 18 Cursor-attached targeting tooltip.
sizes.file_menu integer 22 File button in the top bar.
sizes.file_menu_popup integer 26 File menu and Open Recent popup rows.
sizes.mode_switcher integer 24 Edit/Direct/Play buttons.
sizes.queue_panel, sizes.rule_panel integer 22, 22 Queue and rule editor panels.
sizes.action_editor, sizes.rule_editor integer 22, 22 Action and rule editor controls.
sizes.panel_row, sizes.panel_title integer 20, 24 Editor row text and panel titles.
sizes.asset_label, sizes.asset_badge integer 18, 14 Reserved asset-card text sizing.
flags.panel_title_bold boolean true Bold queue/rule panel titles.
flags.rule_label_bold boolean false Bold director rule labels.
flags.badge_bold boolean true Bold director badges.
flags.action_popup_bold, flags.action_popup_italic boolean false, false Popup style flags.

mode_colors

Each value is a color string. These accents are used by the mode badge/frame, active mode controls, targeting feedback, and related stage chrome.

Key Default Purpose
edit #22C6FF Edit-mode accent.
direct #FFB300 Direct-mode accent.
play #33DD77 Play-mode accent.
guide #22C6FF Terrain painting guide line.

grid

Key Format Default Purpose
snap_size finite number 15.0 Initial grid size, clamped to the stage's supported range.

chrome

style_defaults is the optional common style object for chrome components. Its style keys follow the global style formats (bg_color, border_color, border_width, corner_radius, padding).

Key Format Default Purpose
style_defaults style object { "bg_color":"#171B24F2", "corner_radius":10 } Shared chrome style base.
top_bar.height number 64 Top navigation bar height.
status_bar.height number 38 Bottom status bar height.
mode_switcher.segment_min_width number 124 Minimum width of each Edit/Direct/Play segment.
mode_frame.border_width integer 3 Colored stage frame width.
mode_badge.corner_radius integer 12 Mode badge corner radius.
mode_badge.padding number 10 Mode badge padding.
tooltip.bg_color color #101010E6 Targeting tooltip background.
tooltip.border_width integer 1 Targeting tooltip border.
dialogs.queue_size [width,height] [680,520] Queue editor window minimum size.
dialogs.rule_size [width,height] [720,560] Rule editor window minimum size.
dialogs.action_size [width,height] [560,390] Action editor window minimum size.
dialogs.confirm_size [width,height] [620,300] Rename, unsaved, missing-assets, and other confirmation/error windows.
dialogs.font_size integer 22 Dialog controls/buttons.
dialogs.message_font_size integer 26 Centered AcceptDialog message text.

popups

style_defaults accepts the normal style keys and is inherited by popup components.

Key Format Default Purpose
style_defaults style object bg #202633FA, border #FFFFFF33, radius 12, padding 12 Common popup surface.
action_menu.font_size integer 24 Reserved action-menu component override; use fonts.sizes.action_popup for the active menu.
dialog object {} Reserved per-dialog popup overrides.
asset_selector.backdrop_alpha finite number 0.55 Reserved selector backdrop opacity.
asset_selector.cell_size [width,height] [190,220] Prop/stickman selector cell size.
asset_selector.thumbnail_size [width,height] [156,156] Prop/stickman selector thumbnail size.

palette

style_defaults uses normal style keys and styles the stage placement palette.

Key Format Default Purpose
style_defaults style object bg #252B38FA, radius 10, padding 6 Palette surface/card base.
dock_width number 116 Palette width in Edit mode and stage-frame inset.
card_size [width,height] [96,94] Placement-card minimum size; the card keeps room for its label.
icon_size number 30 Reserved palette icon/glyph size.
drag_threshold number 8 Pointer distance before a card becomes a drag.
selected_border_width integer 3 Reserved selected palette card border.

overlays

style_defaults controls dashed overlay geometry in world pixels; colors are RGBA strings.

Key Format Default Purpose
style_defaults.line_width number 2 Dashed trajectory/guide width.
style_defaults.dash_length number 8 Dash length.
style_defaults.dash_gap number 5 Gap length.
terrain_guide.color color #22C6FF Terrain paint guide.
valid.color color #4DE66680 Valid director target/placement feedback.
same_type.color color #FFFFFF26 Same-type placement feedback.
invalid.color color #FF4D4D80 Invalid target/placement feedback.

widgets

These lists feed reusable controls (EmojiQuickGrid, FontPresetPicker, and PresetSizeToggle). They are data sources, not speech-action persistence.

Key Format Default Purpose
swatches array of color strings white, black, cyan, amber, green, red Color selector entries.
size_presets array of {id,label,value} objects Small/Medium/HUGE (20/28/40) Size selector entries; value is an integer-like size.
emoji array of strings eight emoji entries Emoji quick-grid entries.

Practical edits

Make the top navigation easier to read:

{
  "fonts": {
    "sizes": {
      "file_menu": 26,
      "file_menu_popup": 30,
      "mode_switcher": 28
    }
  }
}

Change the mode frame and make the stage colors more distinct:

{
  "mode_colors": {
    "edit": "#4DD9FF",
    "direct": "#FF8A3D",
    "play": "#65E68A",
    "guide": "#4DD9FF"
  },
  "chrome": { "mode_frame": { "border_width": 4 } }
}

Give selectors larger cards and thumbnails:

{
  "popups": {
    "asset_selector": {
      "cell_size": [220, 260],
      "thumbnail_size": [190, 190]
    }
  }
}

Make kid-facing editors and confirmations larger:

{
  "chrome": {
    "dialogs": {
      "queue_size": [760, 600],
      "rule_size": [800, 640],
      "font_size": 26,
      "message_font_size": 30
    }
  },
  "fonts": {
    "sizes": {
      "queue_panel": 24,
      "rule_panel": 24,
      "action_editor": 24,
      "rule_editor": 24,
      "panel_row": 22,
      "panel_title": 28
    }
  }
}

Legacy v1 files

Older Phase 4b/3c theme files used flat keys such as fonts.action_popup_font_size, fonts.queue_panel_font_size, fonts.panel_title_font_size, and mode keys such as mode_colors.edit_accent and mode_colors.guide_line. They remain accepted. The loader aliases the legacy mode names and selected flat font-size names into the nested structure, while SandboxStage retains the old precedence for a file that does not declare version "2.0".

The complete legacy names still read by the current runtime are:

fonts.action_popup_font_size       fonts.action_popup_emoji_size
fonts.assignment_badge_font_size  fonts.assignment_badge_radius
fonts.rule_label_font_size        fonts.status_pill_font_size
fonts.tooltip_font_size           fonts.queue_panel_font_size
fonts.rule_panel_font_size        fonts.action_editor_font_size
fonts.rule_editor_font_size       fonts.panel_row_font_size
fonts.panel_title_font_size       fonts.panel_title_bold
fonts.rule_label_bold             fonts.badge_bold
fonts.action_popup = { size, bold, italic }

mode_colors.edit_accent           mode_colors.direct_accent
mode_colors.play_accent            mode_colors.guide_line

For a migration, copy the values into the nested keys in this document and set "version": "2.0". Keep only one spelling of each setting so the result is easy to maintain.

Fonts, emoji, and troubleshooting

  • Use project-relative paths such as res://fonts/kids_font.ttf; do not use a machine-specific absolute path.
  • If text falls back to the Godot default, check the path, import status, and that the resource is actually a Font/font file. Restart the Sandbox after correcting it.
  • Emoji availability depends on the selected font and platform glyph support. A missing emoji_font is non-fatal; the normal UI fallback remains usable.
  • If a color or number is ignored, check JSON commas/quotes, use a supported color form, and keep numbers finite. The warning identifies the affected key.
  • If a selector or dialog looks cramped, increase its size array and its related font size together; restart F6 to reload the file.

Speech customization status

fonts.presets (ui, comic, pixel, scary), widgets.size_presets, widgets.swatches, and widgets.emoji are the reusable data contract for a future speech-customization UI. They are intentionally available now so a future Speak dialog can consume the same theme.

The current Sandbox speech action still stores and renders text through the existing StickmanSpeechBubble; these theme lists do not yet change saved speech actions, bubble shape, bubble colors, or bubble font at runtime. Do not assume that changing a preset changes an existing speech action. Treat speech-specific fields under popups.dialog or future bubble sections as reserved until that feature is implemented.