# 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: ```text 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` → `
.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: ```json { "fonts": { "sizes": { "file_menu": 26, "file_menu_popup": 30, "mode_switcher": 28 } } } ``` Change the mode frame and make the stage colors more distinct: ```json { "mode_colors": { "edit": "#4DD9FF", "direct": "#FF8A3D", "play": "#65E68A", "guide": "#4DD9FF" }, "chrome": { "mode_frame": { "border_width": 4 } } } ``` Give selectors larger cards and thumbnails: ```json { "popups": { "asset_selector": { "cell_size": [220, 260], "thumbnail_size": [190, 190] } } } ``` Make kid-facing editors and confirmations larger: ```json { "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: ```text 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.