Files
stickman/docs/specs/phase_19_plain_background.md
T
ryan c6f3930dcb Implement Phase 19 updates and user/editor verification
- Updated architectural specifications for Phase 19 scenery, backgrounds, and surface finishes to reflect implementation status and user verification results.
- Revised UI/UX specifications for background ground and wall color, plain background, and starter scene composition to indicate successful implementation and verification.
- Enhanced scenery controller and sandbox stage scripts to improve eraser functionality and user interactions.
- Adjusted test cases to align with the latest changes in scenery and background implementations, ensuring all checks pass successfully.
- Added new buttons for wall area erasure in the scenery capture bar and updated layout handling for better user experience.
2026-10-22 17:02:39 -04:00

19 KiB
Raw Blame History

Phase 19 — Plain Background: architecture specification

Status (2026-10-22): Implemented and user/editor verified. The user explicitly verified Plain Background save/reopen; the Phase 19 scenery suite passes 706 checks with 0 failures. This specification implements the Plain Background brief and UI/UX specification. The color-choices and custom-picker PNGs reflect the current-color chip as the sole custom-picker entry and the simple Preview label; the solid fill and exact labels in the UI/UX specification govern implementation.

Scope and decisions

Plain Background is a scene-wide, opaque backdrop. It is not a Stage object, a wall panel, or Background ground. The initial color is #F5EFE0. File → New adds a truly empty Plain Background starter and relabels the existing neutral-Sky starter Open Sky. EDIT → Scenery → Backgrounds offers Plain Background beside the four existing Sky cards. A scene remembers its confirmed plain color while a Sky is active.

The approved design uses a small shared view composed of the existing StageSkyView and a full-rect solid ColorRect. SandboxStage owns the confirmed scene values and the short-lived picker draft. No Resource, global signal bus, or extra selectable node is needed for three scalar document values. The Stage inspector kit (StageInspectorPanel, StagePanelChoiceGroup, StagePanelToggleRow) remains for selected objects; this is a modal chooser and uses AssetSelector, AssetCard, the existing color controls, and their theme tokens.

When Sandbox Stage launches as the active scene without a requested scene, SandboxStage opens the starter chooser once after startup recovery handling. A restored autosave remains the active scene without a chooser; discarding recovery opens the chooser. CLI scene requests and load errors keep their existing flows. Closing the startup chooser leaves the empty EDIT Stage open and does not reopen the chooser for that session.

Node hierarchy and rendering

SandboxStage : Node2D
├─ StageBackgroundLayer : CanvasLayer (layer -1)
│  └─ StageBackgroundView : Control (full viewport; ignores mouse)
│     ├─ StageSkyView : Control (camera-bound only on the live Stage)
│     └─ PlainFill : ColorRect (full rect, opaque, ignores mouse)
├─ StageGrid : Node2D (EDIT overlay)
└─ World : Node2D (existing placed objects)

StageStarterCardPreview : SubViewportContainer
└─ PreviewViewport : SubViewport
   ├─ PreviewBackgroundLayer : CanvasLayer (layer -1)
   │  └─ StageBackgroundView (unbound still)
   └─ PreviewWorld : Node2D

AssetSelector scene-wide card
└─ StageBackgroundCardPreview : Control
   └─ StageBackgroundView (unbound still, clipped to card)

StageBackgroundView toggles its Sky and Plain children exclusively. A Plain view is a single opaque viewport-sized rect, with no gradient, horizon, motif, parallax, or world transform. Its anchors respond to viewport resize. StageSkyView continues to own all five existing Sky IDs and camera response; when hidden for Plain it should not spend frames updating Sky motifs. The view and both children ignore mouse input and never enter Stage selection or draw-order lists. The starter preview must use the same background-kind and color interpretation as the live Stage. The gallery card receives the remembered plain color even when the active kind is Sky. Card frames and labels are UI overlays outside the fill, not part of its sample.

Typed contracts and ownership

Target Contract and change Owner responsibility
scripts/stage/scenery/stage_background_view.gd, class_name StageBackgroundView extends Control func set_background(kind: StringName, sky_id: StringName, plain_color: Color) -> void; func bind_camera(camera: Camera2D) -> void; func apply_theme(stage_theme: SandboxTheme) -> void Only renders a validated Sky or opaque Plain fill. Reuses StageSkyView; has no document, history, or picker state. bind_camera(null) yields still previews.
scripts/editors/stage/sandbox_stage.gd, class_name SandboxStage extends Node2D Confirmed _background_kind: StringName, _sky_id: StringName, _plain_color: Color; transient _plain_color_draft_active: bool, _plain_color_draft: Color. Private typed helpers such as _commit_background_kind(kind: StringName, sky_id: StringName = &"") -> void, _commit_plain_color(color: Color) -> void, _preview_plain_color(color: Color) -> void, _cancel_plain_color_preview() -> void, _refresh_background_view() -> void. Sole authority for current scene state, dirty state, save snapshot, recent color history, and draft rollback. Switching to Plain changes the kind immediately and opens choices; switching Sky retains _plain_color.
scripts/stage/stage_scene_io.gd, class_name StageSceneIO extends RefCounted FILE_VERSION := "1.7"; typed validation/migration of background_kind, sky_id, and plain_color. Reject malformed new backdrop fields before applying a document; migrate older versions without changing their visible Sky.
scripts/editors/stage/stage_scene_adapter.gd, class_name StageSceneAdapter extends RefCounted Pass all three fields through collect(...) -> Dictionary and apply(...) -> Dictionary. Keep background data at the top level; no object entry is created.
scripts/editors/stage/stage_asset_browser.gd and scripts/shared/ui/asset_selector.gd, class_name AssetSelector extends PopupPanel Supply four starter cards and five scene-wide background cards; pass {background_kind, sky_id, plain_color} for active state and previews. Add typed signal plain_color_preview_requested(color: Color), signal plain_color_commit_requested(color: Color), and signal plain_color_preview_cancel_requested through the browser facade. The chooser owns view navigation, focus, current/selected card display, and picker controls; it emits intents. It never marks the scene dirty or writes scene data. Keep existing surface_color_confirmed for wall/Ground setup history; do not route Plain through it and double-record a color.
scripts/shared/stage_ui/stage_surface_color_controls.gd, class_name StageSurfaceColorControls extends VBoxContainer; scripts/shared/ui/color_swatch_selector.gd, class_name ColorSwatchSelector extends FlowContainer Retain color_selected(color: Color) and custom_color_requested; activating the current-color chip is the sole entry to the full picker. Ensure compact swatches are at least 44 × 44 and stack sections when a horizontal row will clip. Show current, recent, and preset colors with separate selected and focus treatment. The Stage's existing eight-color recent list is shared.
scripts/shared/stage_ui/stage_surface_color_picker_view.gd, class_name StageSurfaceColorPickerView extends VBoxContainer Extend configure(...), or add a typed background-specific configuration method, so preview_color_changed(color: Color) uses an opaque flat sample for Plain. Native ColorPicker supplies controls. The view displays a draft but does not commit it. Surface motif previews keep their current path.
scripts/stage/scenery/stage_starter_card_preview.gd and scripts/stage/scenery/stage_background_card_preview.gd Use StageBackgroundView for starter and background card imagery. Show a true solid fill for Plain and preserve existing Sky rendering in still previews.
scripts/stage/stage_grid.gd, class_name StageGrid extends Node2D; scripts/stage/stage_gizmos.gd, class_name StageGizmos extends Node2D; scripts/stage/sandbox_theme.gd, class_name SandboxTheme extends RefCounted; sandbox_theme.json Add theme-backed light/dark grid colors and two-tone editor outline colors. Add typed setters on drawing nodes if needed to pass the current backdrop treatment. Adapt editor overlays to the background while leaving authored art, speech, and physics unchanged.

The signal names above are the desired public interface between chooser, browser, and Stage. Keep the existing item_selected(entry: Dictionary) path for Sky and Plain card taps. A Plain card tap commits the active kind before the chooser opens its color-choices view. A Sky card tap commits the new Sky immediately and keeps the chooser open. The current-card indicator uses a stable Current background badge and selected border; keyboard focus is separate. The Plain color choices and its custom picker keep the Stage at full brightness while the modal input blocker remains active and transparent. Other chooser views retain the usual visual dimming.

Saved scene format and migration

Save top-level fields in .scene v1.7:

{
  "version": "1.7",
  "background_kind": "plain",
  "sky_id": "sky_neutral",
  "plain_color": "#F5EFE0",
  "camera": {"position": {"x": 0.0, "y": -400.0}, "zoom": 0.5},
  "objects": [],
  "event_rules": []
}

background_kind is exactly "sky" or "plain". sky_id remains one of the existing five IDs even while Plain is active, preserving the last selected Sky for a later switch. plain_color is an opaque canonical six-digit #RRGGBB string. Runtime color values are clamped to opacity before commit; invalid file values are rejected at the JSON boundary. The plain starter is a separate .scene file under content/stages/starters/ with these values and empty objects/event_rules; the existing blank.scene can remain the Open Sky starter, with its UI label changed. Town Street and Living Room remain authored compositions and acquire migrated Sky-kind fields on their next save if their source version is older.

StageSceneIO._migrate_to_current() must explicitly handle 1.6 as well as 1.0–1.5. Every pre-1.7 file migrates to background_kind: "sky" and plain_color: "#F5EFE0"; it preserves its migrated sky_id (including sky_neutral). In particular, adding 1.6 to the old version != "1.5" branch would incorrectly overwrite its Sky choice: keep that earlier version-specific rule intact. Current-version normalization still runs for migrated data. Read and validate the complete document before replacing the live scene. Save and autosave both serialize confirmed fields only; a picker draft is never consulted. ContentPaths.canonicalize() continues to handle starter paths.

Color transaction and navigation

stateDiagram-v2
    [*] --> Backgrounds
    Backgrounds --> ColorChoices: tap Plain / commit kind
    Backgrounds --> Backgrounds: tap Sky / commit sky
    ColorChoices --> ColorChoices: tap swatch / commit color
    ColorChoices --> CustomPicker: activate current-color chip / snapshot confirmed color
    CustomPicker --> ColorChoices: Use color / commit once
    CustomPicker --> ColorChoices: Back or Esc / discard draft
    ColorChoices --> Backgrounds: Back / keep commits
    CustomPicker --> [*]: popup hide, mode switch, scene replacement / discard draft

_plain_color is the confirmed remembered value; _plain_color_draft exists only while the custom picker is open. Every native picker movement calls _preview_plain_color() to update the live StageBackgroundView and its flat chooser sample, without assigning _plain_color, changing recent history, or marking dirty. Use color clamps to opaque, commits the chosen color once, clears draft state, calls the existing _record_recent_surface_color() once, and returns to color choices. A preset/recent swatch uses the same commit method directly. An intentional selection can reorder recent history; mark the scene dirty only if the confirmed scene value actually changes. Opening choices at the untouched default does not add the default to recents.

Back, Cancel, Esc, popup hide, mode switch, and scene replacement must all clear the draft and restore the confirmed render. A replacement cancels the draft before resetting or loading scene state; popup-hide cleanup must be idempotent because a normal close can trigger it after a button callback. Back from color choices returns to Backgrounds without reverting confirmed card/swatches; Close retains confirmed choices. If Save/autosave occurs during a draft, the snapshot reads _plain_color, never the displayed draft. Focus returns to the Plain card from color choices and to the custom-picker opener from the picker; the popup keeps its screen anchor while switching views.

Chooser layout and contrast

Use the existing AssetSelector responsive grid and AssetCard controls. File → New presents Plain Background, Open Sky, Town Street, Living Room in that order, two columns on a normal desktop and one scrollable column when narrow. Keep fixed heading and reachable Close. Backgrounds presents the five scene-wide cards under Choose the scene background; Wall panel and Background ground stay in a separate More backgrounds row. The color-choices view has a fixed Choose background color heading, flat confirmed preview, a focusable current-color chip with readable hex, up to eight shared recents, and presets. Place ‹ Backgrounds at the left of the fixed footer and Close at the right; stack them as full-width actions on narrow layouts. Activating the current-color chip opens the custom picker; do not add a separate More colors button. The custom view has a flat draft preview and fixed Back/Use footer. Reuse popups.asset_selector, popups.dialog, stage_surface_colors, stage_panels.action, and mode_colors.edit tokens; add only narrowly needed flat-preview/backdrop-overlay tokens. Increase popups.asset_selector.compact_swatch_size from 42 to at least 44 in both theme sources, and clamp the actual ColorSwatchSelector target size to at least 44 even if a custom theme supplies a smaller number. Card, swatch, and action targets also meet 44 × 44. Scroll the content within viewport-safe bounds; do not horizontally clip compact color sections.

StageGrid currently uses translucent white lines. For Plain, choose theme-backed dark minor/major lines on a light fill and light minor/major lines on a dark fill using the displayed color's luminance, including a temporary draft. Minor lines stay subdued; major lines remain legible. On Sky, retain the established grid treatment. StageGizmos currently draws white selection and yellow hover outlines, which can disappear against light fills; add a dark backing stroke beneath a bright core for selection, hover, box selection, rotate ring, and scale handles. Keep screen-space widths and hit testing unchanged. This overlay treatment may be applied consistently for Sky and Plain. No authored object, speech bubble, placement material, or pixel color is rewritten. EDIT still owns grid/gizmos, DIRECT keeps its framing cues, and PLAY hides edit-only overlays. Review cursor pills and placement ghosts on light, dark, and saturated fills during rendered verification.

Observable acceptance and verification route

User path / repeatable scenario Observable outcome
Fresh Sandbox Stage startup without a scene; close the chooser; compare with a CLI scene load and autosave restore/discard The chooser opens once over an empty EDIT Stage. Close leaves the blank Stage open without reopening the chooser. A CLI-loaded or restored scene suppresses it; discarding autosave recovery opens it only after the recovery dialog closes.
Dirty scene → File → New → cancel unsaved gate or cancel starter chooser; then choose Plain and Open Sky in separate runs Gates preserve the prior scene on cancel. Plain opens an unsaved empty scene with a uniform #F5EFE0 fill and zero placed objects; Open Sky keeps the former neutral gradient, sun, and clouds. Both card previews match.
EDIT → Scenery → Backgrounds → Plain → swatch → Sky → Plain Plain taps switch immediately, swatches update Stage and recent ordering, Sky remains selectable, and returning to Plain restores its confirmed color. Selected badge and focus are distinct. Wall/Ground remain separate.
Plain custom picker → drag RGB/HSV controls → Back; repeat with Esc, Close, mode change, popup dismissal, and scene replacement Draft appears in Stage/sample while editing; each cancel path restores the prior confirmed fill. Dirty state, recent list, saved file, and autosave content do not change from the draft. Replacement does not carry draft into the next scene.
Plain color choices → activate current-color chip → change custom picker controls → Use color → Save → reopen; compare color and recent list before/after The current-color chip opens the picker without a separate button. One confirmation stores an opaque color, records it once in shared history, and marks the scene dirty once. Reopen restores active kind and remembered color. A Sky-active save/reopen also retains the hidden remembered plain color.
Load representative v1.0–v1.6 files, especially a v1.6 sky_neutral scene and a v1.6 non-neutral Sky; save/reopen They remain visually Sky scenes with the same Sky ID and composition; migration adds default remembered Plain color and writes v1.7 without changing objects, camera, or Sky choice. Invalid v1.7 background data fails before live-scene replacement.
Plain Stage with no world objects → pan, zoom, viewport resize, EDIT → DIRECT → PLAY → stop; repeat after save/reopen Every frame stays an opaque edge-free flat fill. No gradient or motifs appear; backdrop never intercepts clicks. Mode transitions retain color; PLAY has no edit-only grid/gizmos.
Light #F5EFE0, dark, and saturated Plain colors with Ground, artwork, Props, character, speech, selection, and placement ghost Grid major/minor lines, selection/hover/gizmos, cursor pills, and ghosts remain usable. Foreground pixel colors and object behavior remain unchanged.
Narrow viewport → pointer/touch/keyboard through starters, cards, swatches, picker, Back/Use/Close Content scrolls without horizontal clipping; actions stay reachable; all targets meet 44 × 44; returning from subviews restores opener focus and popup anchor.

Use the real Stage chooser, stage scene load/save path, and render-capable Stage for the scenarios above. Inspect captured frames at the named colors, camera positions, zoom levels, modes, and viewport sizes; a headless parse or a screenshot command alone does not establish visual correctness. Focused automated checks should cover the v1.7 migration/validation boundary and the commit/cancel state transitions where they meaningfully protect behavior. No new graphical or animation asset is required: thumbnails and previews are rendered from existing Sky drawing and the new solid fill.

Trade-offs and implementation constraint

Composing a backdrop view adds one small node and typed facade but keeps Sky drawing focused and lets live Stage and previews share the same kind switch. Three scalar file fields are easier to validate and migrate than a custom Resource; the saved sky_id is deliberate remembered state while Plain is active. Color history remains a user setting shared with surface colors, while the confirmed plain color belongs to each scene. Theme-backed grid color can account for the flat background, but it cannot account for every placed object beneath a particular grid segment; the two-tone gizmo outline protects selection visibility across mixed art.

The existing chooser was designed around four Sky cards and a separate surface row. Its grid-row counting, active-card group, footer placement, and compact swatch width need targeted extension for five scene-wide choices and the new color views. Preserve its popup anchor and focus behavior while changing those branches; do not fork a second gallery or color picker.