Add Phase 19 Starter Scene Composition UI/UX Specification and Tests
- Introduced a new UI/UX specification document for the Phase 19 Starter Scene Composition, detailing user paths, composition rules for Town Street and Living Room, camera framing, and observable acceptance outcomes. - Implemented a visual capture test for different sky states in the sandbox stage, ensuring proper rendering and visual consistency. - Created a comprehensive test suite for the Phase 19 starter scene composition, validating scene loading, geometry, navigation, and interaction behaviors for both Town Street and Living Room. - Added checks for visual alignment, object placements, and scene transitions to ensure adherence to the new UI/UX specifications.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# Phase 19 — Scenery, backgrounds, and surface finishes: architecture and implementation specification
|
||||
|
||||
> **Status:** Original Phase 19 scenery architecture and the approved Stage-relative sizing amendment are integrated; automated and render-capable checks pass. This specification follows the [accepted product brief](../plans/phase_19_scenery_backgrounds_surface_finishes.md), the [approved UI/UX specification](phase_19_scenery_backgrounds_ui_ux.md), and the [Stage panel UI system](stage_panel_ui_system.md). Full Phase 19 user/editor verification remains pending. The earlier sections describe the original implementation contract; the sizing amendment starts from the current `.scene` v1.5, `.prop` v1.1, Stage controller, and shared UI.
|
||||
> **Status:** Original Phase 19 scenery architecture and the approved Stage-relative sizing amendment are integrated; automated and render-capable checks pass. The camera-aware Sky architecture addendum below is specified for user sign-off and is not yet implemented. This specification follows the [accepted product brief](../plans/phase_19_scenery_backgrounds_surface_finishes.md), the [approved UI/UX specification](phase_19_scenery_backgrounds_ui_ux.md), and the [Stage panel UI system](stage_panel_ui_system.md). Full Phase 19 user/editor verification remains pending. The camera-aware Sky addendum supersedes the original static-detail statements in this document.
|
||||
|
||||
## Decisions and scope
|
||||
|
||||
1. Use independent, inert `Node2D` placements for wall panels and `.prop` Scenery artwork. The source `.prop` is a **live reference**: its current vector art is used on reopen. Missing art remains a saved, visible, repairable placement. A given `.prop` may be used as inert Scenery and, when it has valid collision geometry, as a functional Prop.
|
||||
2. Sky is one adaptive, viewport-filling, screen-space vector drawing behind the world. Its details stay fixed during camera movement; it does not tint other objects. Existing scenes and Blank Scene use an internal neutral Sky. The four child-facing Sky presets are Clear Day, Cloudy Day, Sunset, and Starry Night.
|
||||
2. Sky is one adaptive, viewport-filling, screen-space vector drawing behind the world. Its gradient stays fixed while its details respond gently to camera pan and zoom under the [camera-aware Sky addendum](#phase-19-addition-camera-aware-sky-details); it does not tint other objects. Existing scenes and Blank Scene use an internal neutral Sky. The four child-facing Sky presets are Clear Day, Cloudy Day, Sunset, and Starry Night.
|
||||
3. The existing Prop Editor opens **inside the live Stage session** for Make artwork. The Stage and its unsaved document remain in memory, with Stage input suspended until the editor returns. The standalone F6 route remains valid.
|
||||
4. Select several creates a temporary Scenery selection and snapshot-based transform preview. It does not create a persistent group or a parent that changes the saved hierarchy. Sky, Ground, characters, and functional Props are outside this transform.
|
||||
5. Phase 19 adds neither Stage Undo/Redo nor a whole-set transform. Group preview has its own exact Cancel/Keep boundary. Stage-wide Undo/Redo is a separately planned phase and is logged in [technical debt](../architecture/tech_debt_and_optimizations.md).
|
||||
@@ -31,7 +31,7 @@ flowchart TD
|
||||
| Owner / location | Typed contract or change | Boundary |
|
||||
| --- | --- | --- |
|
||||
| `scripts/stage/stage_draw_order.gd`, `class_name StageDrawOrder extends RefCounted` | Constants `WALL := -40`, `FAR := -30`, `NEAR := -20`, `TERRAIN := -10`, `FLOOR := -5`; `static func scenery_z(depth: StringName) -> int`; `static func apply_terrain(node: TerrainBlock) -> void` | One drawing policy. Leave the existing Prop `-1/0/1` and character relative order intact; rebase terrain drawing only. Never serialize raw `z_index` in place of a semantic depth/finish. |
|
||||
| `scripts/stage/scenery/stage_sky_view.gd`, `class_name StageSkyView extends Control` | `func set_sky(id: StringName) -> void`; `func get_sky() -> StringName`; `func apply_theme(theme: SandboxTheme) -> void` | Full-rect child of a dedicated `CanvasLayer` with `layer = -1`, mouse ignored. Deterministic vector `_draw()` on viewport resize/theme/choice only. No world hit test or physics. |
|
||||
| `scripts/stage/scenery/stage_sky_view.gd`, `class_name StageSkyView extends Control` | `func set_sky(id: StringName) -> void`; `func get_sky() -> StringName`; `func apply_theme(theme: SandboxTheme) -> void`; camera binding specified in the [Sky addendum](#phase-19-addition-camera-aware-sky-details) | Full-rect child of a dedicated `CanvasLayer` with `layer = -1`, mouse ignored. Deterministic vector drawing on preset, viewport, or effective camera-framing changes. No world hit test or physics. |
|
||||
| `scripts/stage/scenery/stage_wall_panel.gd`, `class_name StageWallPanel extends Node2D` | `var finish_id: StringName`; `var tint: Color`; `var panel_size: Vector2`; optional `var motif_origin_offset: Vector2`; `func configure(size: Vector2, finish: StringName, color: Color) -> void`; `func get_local_bounds() -> Rect2` | Finite world rectangle centered at `position`. No `CollisionObject2D`, navigation, support, or interaction registration. Resize changes `panel_size`, never root scale, so motifs retain their world pitch. The renderer lays the pattern out from each panel's current world transform, so eraser-created fragments keep continuous brick courses; the optional offset preserves source-wall lineage in saved data. |
|
||||
| `scripts/stage/scenery/stage_scenery_artwork.gd`, `class_name StageSceneryArtwork extends Node2D` | `var asset_path: String`; `var display_name: String`; `var depth: StringName`; `var last_known_local_bounds: Rect2`; `var missing: bool`; `func load_visual(path: String) -> bool`; `func set_depth(value: StringName) -> void`; `func get_visual_world_bounds() -> Rect2`; `func get_saved_footprint() -> Rect2` | Build a `Visuals` child via `PropVisuals.build()` from the complete shape stack. No `PropBlock`, collision hull, physics, Use spots, anchors, or event identity. If loading fails, render a neutral marker/known footprint from saved data and preserve path/name/transform. |
|
||||
| `scripts/stage/scenery/stage_surface_renderer.gd`, `class_name StageSurfaceRenderer extends RefCounted` | `static func is_wall_finish(id: StringName) -> bool`; `static func is_ground_finish(id: StringName) -> bool`; `static func draw_wall(canvas: CanvasItem, rect: Rect2, finish: StringName, tint: Color, world_origin: Vector2) -> void`; `static func draw_ground(canvas: CanvasItem, rect: Rect2, finish: StringName, tint: Color, world_origin: Vector2) -> void` | Shared vector definitions for Paint/Brick/Wood, with Grass/Dirt/Sidewalk/Asphalt for Ground. One motif vocabulary and world-coordinate phase; no raster scale blur. Drawing helpers clip motifs to finite panel/block bounds and bound work to visible tiles. |
|
||||
@@ -75,7 +75,7 @@ The manual **Erase wall area** tool adds one optional wall field, `motif_origin_
|
||||
|
||||
The semantic draw order is Sky → wall panels → Far art → Near art → terrain → floor art → characters and Props. `StageDrawOrder` applies bands to direct `World` children. Set all existing terrain varieties to the terrain band for consistent physical-surface drawing; their collision and input remain unchanged. Prop `-1/0/1` choices and World child order among legacy depth-zero Props and stickmen are preserved. Reapply the policy on spawn, load, depth change, and reset. Selection must use the same semantic tier order plus smaller actual art footprint preference, rather than assuming child insertion order expresses depth.
|
||||
|
||||
`StageSkyView` lives in its own `CanvasLayer` behind `World`, not under the camera or a wall. Its draw rectangle follows viewport size, so panning/zooming cannot reveal an edge. Use deterministic vector gradient bands, stars, clouds, sun/moon and shapes with bounded point counts. The preset controls only this view. Rendering should be static between sky or viewport changes; no per-frame random placement, animation, parallax, or tint propagation. Sky thumbnails use the same drawing definitions at card size so the choice predicts the Stage.
|
||||
`StageSkyView` lives in its own `CanvasLayer` behind `World`, not under the camera or a wall. Its draw rectangle follows viewport size, so panning/zooming cannot reveal an edge. Use deterministic vector gradient bands, stars, clouds, sun/moon and shapes with bounded point counts. The preset controls only this view. The gradient stays screen-fixed; the [camera-aware Sky addendum](#phase-19-addition-camera-aware-sky-details) defines bounded, camera-responsive details and redraw invalidation. There is no per-frame random placement, autonomous animation, or tint propagation. Sky thumbnails use the same drawing definitions at card size in their unbound default composition.
|
||||
|
||||
`StageSurfaceRenderer` defines finite vector motifs with a world-aligned origin and fixed world-unit pitch. Wall and Ground may share Brick/Wood definitions but use context-aware fill/outline colors and scale. `StageWallPanel._draw()` uses its current finite rectangle as a clip and asks the renderer for only intersecting motif cells. For brick walls, cull to the camera-visible local rectangle; if that visible area exceeds 12,000 cells, increase the brick pitch uniformly for all visible courses instead of truncating rows or columns. This keeps large walls fully patterned and fragments continuous without unbounded draw work. Ground renders within its existing rectangle geometry; existing `CollisionPolygon2D` and navigation surfaces are never rebuilt merely for a finish change. The selected **New Ground look** is passed into both `StageTerrainPainter` ghost creation and each committed Ground block; it never repaints earlier blocks. During group resize, wall motif lines retain their pitch and phase. Ground blocks adjacent on a shared edge use the same world phase; shared same-finish interior borders are suppressed or visually joined so a painted run reads as one surface. The neighbor check refreshes on place, delete, move, load, and per-piece finish change, and considers only nearby Ground rectangles; it does not change occupancy/collision. Other motif types remain bounded by the camera-visible region or a maximum cell count per finite panel so `_draw()` work stays limited.
|
||||
|
||||
@@ -185,6 +185,8 @@ On scene open, load every live artwork reference. A missing/invalid source leave
|
||||
|
||||
Keep versioned starter documents under `content/stages/starters/` with stable IDs for Blank, Town Street, and Living Room. They are ordinary `.scene` snapshots, not locked nested instances. Blank may be assembled from the current neutral empty-scene defaults; Town Street and Living Room data use the same wall, artwork, terrain, Prop, Sky, and camera schema as a saved child scene. The bundled files reference only explicitly approved production art. A window is a genuine gap between wall rectangles with a transparent frame and Sky behind it; no cutting or glass behavior is inferred. Furniture Props receive deliberate still/block/support roles from the normal Prop contract, without promising Sit.
|
||||
|
||||
The focused [Phase 19 starter-scene composition specification](phase_19_starter_scene_composition.md) defines the approved pan-safe Town Street and Living Room surfaces and the opening-shot bounds used by starter-card previews. It keeps the ordinary v1.5 scene records and camera free of limits.
|
||||
|
||||
The current `PendingFileAction.NEW_SCENE` unsaved Save/Discard/Cancel gate remains authoritative. After it resolves, show the shared starter gallery **before** replacing the current document. Canceling the gallery leaves the existing scene and post-gate dirty state. Selecting a starter preflights its scene data, loads ordinary editable objects, resets `current_path` to empty, stamps a fresh creation time, proposes its editable name, and marks the new document unsaved. Failed starter validation leaves the prior scene intact and shows one themed error. No starter is inserted into another scene. A first-save path follows the existing Save As flow. CLI scene playback continues to load `.scene` directly, with no starter-specific runtime branch.
|
||||
|
||||
## UI component and theme reuse
|
||||
@@ -237,7 +239,7 @@ Each row names a child path and a repeatable observation. Automated checks may c
|
||||
| Edit Scenery → select two copies of one artwork; drag a corner on one, release; then cancel another drag with Esc/RMB/touch Cancel and release the pointer | The opposite visible corner stays at the same world position; only the edited placement changes, and it commits on release without Keep. A canceled drag restores its snapshot and the late release makes no change. With Snap on, a moved artwork or wall's visible top-left lands on a grid intersection. Smaller then Bigger returns near the initial bounds. Save/reopen and check the two copies retain distinct scales, while Props/Ground/PLAY behavior remains unchanged. A missing artwork without known bounds shows no resize handle. |
|
||||
| Open a pre-amendment `.prop` and `.scene`, then explicitly overwrite one referenced `.prop` through ordinary Save | Opening alone does not rescale artwork or placement values. The explicit source overwrite changes every live reference to that source on reopen, while each scene placement retains its saved scale; there is no hidden compensation, copy prompt, or version migration. |
|
||||
| Move a referenced `.prop` aside, reopen, repair selected missing artwork; cancel once then confirm | One neutral marker remains at saved position/depth, summary appears once, ordinary EDIT targets are not stolen, cancel keeps path/dirty state, and confirmed repair changes only that placement. Missing footprint/no-footprint group resize rules and PLAY marker presentation hold. Restore source and reopen to confirm live-reference visual update. |
|
||||
| Select each Sky, pan/zoom, PLAY/stop, reopen | Sky fills screen without an edge or flicker; foreground pixel colors are unchanged by Sky choice. Layer order remains Sky/wall/Far/Near/Ground/floor/actors through camera movement, simulation, and reload; ghosts/handles/capture bars disappear in PLAY. |
|
||||
| Select each Sky, pan/zoom, PLAY/stop, reopen | The fixed gradient fills the screen without an edge or flicker; details follow the bounded response in the [Sky addendum](#phase-19-addition-camera-aware-sky-details). Foreground pixel colors are unchanged by Sky choice. Layer order remains Sky/wall/Far/Near/Ground/floor/actors through camera movement, simulation, and reload; ghosts/handles/capture bars disappear in PLAY. |
|
||||
|
||||
Visual acceptance requires opening the affected scene in a render-capable Godot GUI, capturing the named chooser, normal/zoomed Stage, missing-art, group-preview, **Prop Editor sizing guide at Small/Big and narrow width, active single-piece handles/capture bar**, and PLAY states, and inspecting the pixels. Record capture location, scene/state, and observation. Interactive acceptance requires exercising the actual controls and input events, including RMB/Esc/touch cancellation, button sizing, grid-aligned move, commit on release, and a late release after cancellation. If GUI or input automation is unavailable after an attempted route, name the unverified outcomes and hand the user a focused reproducible check; a headless parse or screenshot file alone is insufficient. Phase 19 user/editor verification remains open until the child checks the integrated behavior.
|
||||
|
||||
@@ -250,3 +252,58 @@ Visual acceptance requires opening the affected scene in a render-capable Godot
|
||||
- Rebased terrain drawing enables the required floor-art tier without changing Prop-to-character depth semantics. It changes terrain's relative visual position beneath Props; physics and navigation stay unchanged and must be regression checked.
|
||||
- Vector motifs retain clarity and world alignment without repeated raster textures, at the cost of careful clipping/culling and seam management. Keep motif definitions centralized so wall/Ground previews match runtime drawing.
|
||||
- Stage-wide Undo/Redo deserves one command history across placement, deletion, style, repair, and group commits. The Phase 19 Keep/Cancel preview is a local transaction only. Whole-set transform needs its own design because Ground physics, character/Prop placement, rules, and camera all have different transform invariants.
|
||||
|
||||
## Phase 19 addition: camera-aware Sky details
|
||||
|
||||
This focused addition implements the accepted [product addendum](../plans/phase_19_scenery_backgrounds_surface_finishes.md#camera-aware-sky-details-addendum) and [UI/UX contract](phase_19_scenery_backgrounds_ui_ux.md#camera-aware-sky-details). The original clauses above that describe Sky details as static are superseded here. **Only the four existing child-facing Sky presets gain automatic camera response.** The internal neutral Sky may use a zero-response profile. There is no motion setting, additional Sky card, theme token, scene field, or change to Background ground behavior.
|
||||
|
||||
### Ownership, nodes, and typed contract
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Camera[SandboxStage/Camera2D\nworld position + zoom] -->|bind once; sample framing| Sky[StageSkyLayer: CanvasLayer -1\nStageSkyView: Control]
|
||||
Sky --> Gradient[full-rect gradient\nscreen fixed]
|
||||
Sky --> Motifs[clouds / sun / moon / stars\nbounded camera response]
|
||||
World[World: Background ground + walkable Ground + actors] --> Camera
|
||||
Cards[Sky chooser and starter card previews] -->|SkyView without camera binding| Still[default still composition]
|
||||
```
|
||||
|
||||
`SandboxStage` (`class_name SandboxStage extends Node2D`) owns the one live `Camera2D` and passes it to the already-built `StageSkyView`; it does not dispatch a separate Sky update from every mouse, touchpad, scene-load, mode, or PLAY path. `StageSkyView` (`class_name StageSkyView extends Control`) owns all Sky drawing and the response calculation. Its narrow typed API is:
|
||||
|
||||
```gdscript
|
||||
func bind_camera(camera: Camera2D) -> void # A null camera restores the default still composition.
|
||||
func set_sky(id: StringName) -> void
|
||||
func get_sky() -> StringName
|
||||
func apply_theme(theme: SandboxTheme) -> void
|
||||
```
|
||||
|
||||
`StageSkyView` samples the bound camera's finite `global_position` and positive uniform `zoom.x` in its own `_process(delta: float) -> void`, compares the **effective** framing against the last drawn values, and calls `queue_redraw()` only when the visible composition changes. Its `NOTIFICATION_RESIZED` path also invalidates layout. Invalid or unavailable camera data falls back to the fixed default composition rather than propagating NaN into drawing. The first binding, preset change, and scene load invalidate immediately so a newly shown view does not display a previous frame's motifs. No signal or runtime state is serialized; the last sampled values exist only to avoid redundant redraws. The chooser's `StageBackgroundCardPreview` and starter's `StageStarterCardPreview` create unbound `StageSkyView` instances and remain representative still images. No new UI component, Stage inspector control, or `SandboxTheme.stage_panels` token is needed.
|
||||
|
||||
### Deterministic response and layout
|
||||
|
||||
Use the built-in Stage camera framing, `REF_POSITION := Vector2(0.0, -400.0)` and `REF_ZOOM := 0.5`, as the fixed reference for **every** scene. These values match `SandboxStage`'s current defaults and `sandbox_stage.tscn`. Evaluate each frame from absolute camera position and zoom; never accumulate input deltas, seed from elapsed time, or capture a per-session origin. Thus identical `{sky_id, camera.position, camera.zoom, viewport.size}` yields identical drawing after mode changes, PLAY/stop, and save/reopen. The existing `.scene` camera position/zoom and `sky_id` fields are sufficient; scene version and migration stay unchanged.
|
||||
|
||||
Each preset has one internal response profile, shared by all its motifs. A profile contains bounded horizontal/vertical pan limits in viewport fractions, a pan response span, and a bounded logarithmic zoom strength/span. These are renderer constants, not saved data or child-facing controls. A suitable starting envelope is at most about **5% of viewport width and 3% of height** for motif displacement and about **0.85–1.15×** for uniform motif size over the Stage's supported zoom range; tune each preset by rendered comparison at ordinary, close, and wide shots. Derive pan from the camera-position difference expressed in reference-frame screen pixels and pass it through a smooth odd saturating function (for example `tanh`). Derive zoom from `log(camera_zoom / REF_ZOOM)` through a smooth bounded function, then exponentiate to keep the size positive and reciprocal around the reference zoom. Avoid hard clamps in the visible response range, which can create a conspicuous change in motion at the limit. Camera movement of world objects must visibly exceed Sky-detail movement at ordinary framing. At the reference camera framing, displacement is zero and scale is one.
|
||||
|
||||
For clarity, one possible pure calculation is `shift.x = -W * limit_x * tanh(((camera_x - REF_POSITION.x) * REF_ZOOM / W) / span_x)` (and likewise for y), with `size_factor = exp(gain * tanh(log(camera_zoom / REF_ZOOM) / zoom_span))`; choose `limit/span` and `gain` so the detail response remains gentle. The final spec requirement is bounded, continuous, deterministic behavior, not these exact coefficients. Apply the same profile to all motifs of a preset as one visual group; individual stars may retain different authored sizes and positions, but do not acquire independent motion layers. Keep `sky_neutral` deterministic; a zero-response profile is acceptable for that internal default.
|
||||
|
||||
The gradient remains a full `Rect2(Vector2.ZERO, size)` draw in the screen-space `CanvasLayer`; **do not apply the motif transform to the gradient**. Place motif centers using normalized viewport anchors and scale their authored vector shapes **uniformly** with a single viewport-derived scalar, rather than separate x/y stretch. Apply the camera-induced size factor about the motif's own anchor, then add the bounded shift. Let the viewport clip any motif portion outside the visible rectangle; the gradient still fills those pixels, so no Sky edge appears. On resize, recompute anchors and the scalar from the new dimensions and reevaluate the same absolute camera frame. Retune the current low and oversized clouds, especially Cloudy Day and Sunset, so the ordinary lower action area remains comparatively open. This is art composition within the renderer, not a hard mask that hides clouds over arbitrary character positions.
|
||||
|
||||
The current star loop uses `fposmod(..., size.x/y)`, so changing viewport size can wrap a star abruptly to a distant position. Replace that layout with a fixed, deterministic set of normalized star anchors (or a fixed seeded list mapped to normalized coordinates); never seed it from the frame clock or call `randf()` while drawing. Existing cloud/moon curves should be sampled or cached once per shape/preset where practical, because camera movement now invalidates the drawing more often. Keep motif count and vector point count bounded; retain the gradient texture until the preset actually changes. There is no autonomous cloud drift, weather animation, horizon line, selection geometry, collision, or world tint.
|
||||
|
||||
`StageBackgroundGround` remains a world-anchored `Node2D` surface behind walkable Ground. Its landscape edge and the walkable Ground therefore move and scale together through the normal `Camera2D` transform. The Sky makes no ground-line claim and does not inspect terrain or character positions.
|
||||
|
||||
### Data flow and observable acceptance
|
||||
|
||||
1. The child chooses Clear Day, Cloudy Day, Sunset, or Starry Night. `SandboxStage` calls the existing `set_sky(id)` path; the Sky draws its fixed gradient and reference-frame motif composition. The chooser card remains a still preview.
|
||||
2. A pan or zoom changes `Camera2D` through any existing input route. The bound `StageSkyView` samples the resulting absolute framing, derives its bounded motif transform, and redraws only when that framing changes. No mode or PLAY-specific Sky branch runs.
|
||||
3. Save writes the existing `sky_id` and camera fields. Reopen loads those values; the next Sky draw derives the same composition from them and the current viewport size. A resize derives a coherent composition at the new size without changing saved state.
|
||||
|
||||
Repeat this in a rendered Stage: choose each preset; place a stickman on walkable Ground with a broad Background ground panel behind it; frame an ordinary shot, a close shot, and a wide shot; pan in EDIT and DIRECT, switch to PLAY and pan/zoom again, then stop. At an unchanged camera framing, motif anchors and apparent size must match across those mode and PLAY transitions. Resize the viewport twice and verify the gradient remains full-screen, motif shapes keep their proportions, and stars do not jump across the screen. Save at a nondefault camera framing, reopen at the same viewport size, and compare the motif composition; it must match without a separate Sky setting. Throughout, Sky displacement and scaling are perceptible but restrained, no edge or flicker is exposed, the lower ordinary action area remains usable, Ground and Background ground stay aligned, and foreground colors/physics remain unchanged. A screenshot or headless parse alone cannot establish the camera-response and composition outcomes; inspect rendered frames at the named camera states.
|
||||
|
||||
### Architectural trade-offs
|
||||
|
||||
- Keeping one `CanvasLayer` and one `StageSkyView` reuses current drawing and preview paths. The renderer must now redraw vector motifs while the camera moves; bounded geometry, geometry caching, and change-only invalidation keep that cost proportional to visible camera changes.
|
||||
- Fixed reference framing avoids a new `.scene` field and makes save/reopen exact. Very distant camera positions can sit near the response limit, so additional panning there may change the Sky little; that is the accepted cost of a bounded composition.
|
||||
- One profile per preset is simple to tune and preserves visual unity. It does not simulate separate cloud distances. True multilayer parallax and autonomous Sky animation remain outside this Phase 19 addition.
|
||||
- Normalized anchors and uniform motif size preserve proportions across viewport shapes. Wide or narrow aspect ratios change the available spacing between details, requiring visual checks on both shapes; exact pixel placement across resizes is not promised.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 19 — Scenery, backgrounds, and surface finishes: UI/UX specification
|
||||
|
||||
> **Status:** Approved and implemented, including the Stage-relative asset-sizing addition (2026-10-16). Based on the [accepted product brief](../plans/phase_19_scenery_backgrounds_surface_finishes.md). Full Phase 19 user/editor verification remains pending.
|
||||
> **Status:** The original Phase 19 UI/UX is approved and implemented, including the Stage-relative asset-sizing addition (2026-10-16). The accepted camera-aware Sky behavior is being specified as a focused Phase 19 addition. Based on the [accepted product brief](../plans/phase_19_scenery_backgrounds_surface_finishes.md). Full Phase 19 user/editor verification remains pending.
|
||||
>
|
||||
> **Decision:** Use a responsive Scenery chooser followed by canvas tools. Placement stays active for repeated copies. Right-click exits placement. Scenery artwork keeps live `.prop` references, with a visible, repairable placeholder if a referenced file cannot be found. The Phase 19 addition standardizes new artwork around a standing-stickman size guide and adds single-placement Scenery resizing; direct single-piece transforms commit on release, while the existing group preview keeps its Keep/Cancel boundary. Phase 19 does not add Undo; Stage Undo is deferred to its own future phase and should be recorded in tech debt during architecture planning.
|
||||
|
||||
@@ -114,7 +114,17 @@ These are relationship examples, not fixed pixel geometry. On a narrow viewport
|
||||
|
||||
- **Backgrounds → Sky** opens four designed thumbnail cards: **Clear Day**, **Cloudy Day**, **Sunset**, **Starry Night**. The currently selected card has a strong border, filled selected state, and the text **“Current Sky”**; keyboard focus remains a separate outline.
|
||||
- One tap or Enter/Space changes the Sky immediately; the chooser stays open so another card can be tried. Close means “stop choosing”, not “revert”. With no Undo in Phase 19, the child can restore the previous look by choosing its card again. Changing Sky never recolors or tints other pieces. Sky has no Stage selection handles or overlap entry.
|
||||
- The thumbnail shows enough clouds, sun/moon, and color variation to predict the Stage result. A chosen Sky is static during PLAY and stable through camera movement.
|
||||
- The thumbnail shows enough clouds, sun/moon, and color variation to predict the Stage result. It remains a representative still image; the Stage applies its automatic camera response without asking the child to configure motion.
|
||||
|
||||
### Camera-aware Sky details
|
||||
|
||||
- The four existing Sky cards and their immediate selection flow stay unchanged. The selected card still says **Current Sky**. Do not add a motion toggle, slider, new label, tutorial cue, cursor, or theme token.
|
||||
- Keep the vertical gradient fixed to and filling the full viewport. Clouds, sun, moon, and stars respond automatically and subtly to Stage camera pan and zoom in **EDIT**, **DIRECT**, and **PLAY**. They move less and change size less than the stickman and Ground, so they read as distant details while connecting to a dramatic shot.
|
||||
- Preset-specific composition keeps the main action area clear at ordinary framing. During close and wide views, details remain recognizable and do not grow across the character or walkable Ground. Camera response stays continuous and bounded; it does not expose an edge, pop, sweep distractingly, or reverse abruptly.
|
||||
- At unchanged camera framing, switching modes, starting/stopping PLAY, and reopening a saved scene reproduce the same Sky composition. On viewport resize, the gradient continues to cover the view and the details recompose coherently.
|
||||
- Keep landscape edges on world-anchored **Background ground** and scenery. Sky details do not draw a horizon, gain selection handles, enter selection, or alter the colors or behavior of world pieces.
|
||||
- The visible feedback is the Sky details' restrained response to the same pan/zoom gesture already used on the Stage. No additional status message or control feedback is needed.
|
||||
- Acceptance path: choose each Sky, then pan and zoom through ordinary, close, and wide framing in EDIT/DIRECT/PLAY; resize the viewport; start/stop PLAY; save/reopen with a saved camera view; and place Background ground behind walkable Ground. Confirm a subtle but perceptible Sky response, clear action area, no edge/flicker, consistent mode transitions, and Ground alignment.
|
||||
|
||||
### Wall panel
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# Phase 19 — Starter Scene Composition: Architecture and Implementation Specification
|
||||
|
||||
> **Status:** Implemented; focused behavior verification passed. Rendered-pixel and user/editor verification remain pending because the available Godot capture route exited before producing an image. The user requested an opaque Town Street Background ground layer and rows of ordinary-size walkable Ground tiles in both starters instead of oversized terrain blocks.
|
||||
>
|
||||
> **Approvals:** Everyday Storybook direction approved; architecture defaults approved: **1,280×720 logical-pixel reference viewport (16:9)**, saved zoom **0.5**, horizontal pan guarantee **±1,000 world units**, and **100 world units** of end margin.
|
||||
>
|
||||
> **Related documents:** [Accepted composition brief](../plans/phase_19_starter_scene_composition.md), [UI/UX specification](phase_19_starter_scene_composition_ui_ux.md), [Phase 19 scenery specification](phase_19_scenery_backgrounds_surface_finishes.md), and [Background Ground and Wall Color brief](../plans/phase_19_background_ground_and_wall_color.md).
|
||||
|
||||
## Decisions and scope
|
||||
|
||||
1. Revise the existing Town Street and Living Room `.scene` snapshots using the existing editable node types. Promote the edited starter snapshots to the existing current scene format v1.6; their bundled source snapshots are v1.5. Preserve current starter IDs, File → New flow, and ordinary save/reopen behavior. The existing 1.5→1.6 migration and `background_ground` object type are already supported; this feature adds no schema field and does not bump beyond v1.6.
|
||||
2. At saved zoom 0.5, author a horizontal surface span of at least **4,760 world units** for each surface that must remain present during pan. Use adjacent ordinary-size terrain tiles rather than scaling one walkable block across the whole span. The current Ground template snaps to a 192×32-world-unit polygon at scale 1; 25 adjacent tiles produce a 4,800-unit run from x=−2,400 to +2,400, covering a **1,280-logical-pixel rendered viewport**, camera center travel from the saved x-position by **±1,000 world units**, and at least **100 world units** of margin at either end:
|
||||
|
||||
`required_width = viewport_width / saved_zoom + 2 * pan_travel + 2 * margin`
|
||||
|
||||
`= 1,280 / 0.5 + 2 * 1,000 + 2 * 100 = 4,760 world units`
|
||||
|
||||
Each tile remains a normal-size, independently selectable Ground object. Adjacent edges must meet exactly: preserve one level collision top, avoid gaps/overlaps, and let the repeated tile edges read as intentional joints.
|
||||
|
||||
With the current saved camera center x=0, 25 tiles span x=−2,400 to +2,400. At camera x=±1,000, the viewport reaches ±2,280, leaving a 120-unit margin. Town Asphalt uses two additional normal-size tiles, one at each end, so a character can step down from the sidewalk onto the road at an exposed curb; its 27 tiles span 5,184 units.
|
||||
3. Keep camera panning unrestricted. The authored guarantee is finite and applies at the saved zoom over the stated envelope; scene edges can appear beyond that envelope or in a wider zoomed-out shot. Zooming in continues to show the same continuous surface across the viewport.
|
||||
4. Keep camera and placement state as ordinary saved `.scene` fields. Adjust the saved camera y only as needed to meet the approved opening composition; do not add camera limits or runtime surface extension.
|
||||
5. Reframe `StageStarterCardPreview` around the saved opening composition. Its image must not become panoramic merely because pan-only Ground or wall extents are longer. Derive the preview focus from the saved camera and visible central composition; do not add preview metadata to `.scene`.
|
||||
6. Reuse existing production artwork, surface finishes, Sky presets, and child-facing choices. This pass introduces no new controls, theme tokens, scene object types, signals, or graphical assets.
|
||||
|
||||
**In scope:** `content/stages/starters/town_street.scene`, `content/stages/starters/living_room.scene`, `scripts/stage/scenery/stage_starter_card_preview.gd`, and targeted navigation-ribbon coalescing plus the exposed Town curb connector in `scripts/editors/stage/sandbox_stage.gd` needed for tile seams and the 32-unit sidewalk-to-road step.
|
||||
|
||||
**Out of scope:** camera restrictions; runtime-generated or auto-extending scenery; a new starter or template system; new art; changes to Ground physics/collision, navigation behavior beyond adjacent tile seams and the exposed 32-unit Town curb step, wall collision, Prop roles, Sky response, chooser controls, or scene serialization; zoom-out edge guarantees beyond the stated envelope.
|
||||
|
||||
## Current state and architecture fit
|
||||
|
||||
Both bundled starter files currently use scene v1.5 with `sky_id`, saved camera `{position, zoom}`, and independently editable object records. `StageSceneIO` migrates them to the current v1.6 format on read. Both currently save zoom 0.5 and camera x=0. Town Street saves a Cloudy Day Sky, one Far skyline, four Near artworks, and 1,400-world-unit Sidewalk and Asphalt runs. The buildings and trees do not share one visible base line, and their current Ground ends inside the opening view. Living Room saves a Clear Day Sky, three painted wall panels around a real window gap, seven 200-unit Wood Ground pieces, separate wall art and rug, and three functional Props. The floor run is short; the couch and armchair are visibly above its support line.
|
||||
|
||||
`StageSceneIO` and `StageSceneAdapter` already persist and reload these records. `StageSpawner` creates ordinary terrain and Props. `TerrainBlock` remains the walkable surface; `StageBackgroundGround`, `StageWallPanel`, and `StageSceneryArtwork` remain inert Scenery. `StageSurfaceRenderer` already draws world-aligned surface motifs with viewport culling, and `StageRectSurface` caches wall patterns. These are existing contracts to reuse, not new abstractions.
|
||||
|
||||
The source art's measured visual bottoms are useful placement references: Town house about 128 local units below its origin, storefront 119, tree 113; couch 111, armchair 107, coffee table 94. These bounds are starting measurements only. Final alignment uses rendered visible contact at Stage scale, not saved origins or selection AABBs alone.
|
||||
|
||||
## Scene and rendering flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
FileNew[File → New → Town Street or Living Room] --> Read[StageSceneIO migrates v1.5 input to current v1.6]
|
||||
Read --> Apply[StageSceneAdapter + StageSpawner create ordinary World children]
|
||||
Apply --> Camera[Saved Camera2D position and zoom]
|
||||
Camera --> Sky[Screen-space StageSkyView]
|
||||
Camera --> World[World surfaces, scenery, Props, actors]
|
||||
World --> Opening[Everyday Storybook opening composition]
|
||||
Pan[Unrestricted camera pan] --> Camera
|
||||
Extent[Authored 4,760-unit surfaces] --> World
|
||||
Read --> Preview[StageStarterCardPreview]
|
||||
Camera --> Preview
|
||||
Preview --> Card[Opening-shot preview; ignores pan-only bounds]
|
||||
Opening --> Save[Existing scene save/reopen path]
|
||||
Save --> Read
|
||||
```
|
||||
|
||||
The live Sky remains the screen-space background. World surfaces and objects keep their existing semantic draw order: Sky, Background ground, wall/Far scenery, Near scenery, walkable Ground and floor scenery, then action objects and actors. Preserve the existing Sky response and keep the chooser card's Sky unbound to the live camera.
|
||||
|
||||
## Starter composition contracts
|
||||
|
||||
### Town Street
|
||||
|
||||
- Keep the Cloudy Day Sky, muted Far skyline, existing house/storefront/tree artwork, and normal Ground palette. Use the Far skyline and the placement/scale of existing Near art to imply more street while preserving a clear center.
|
||||
- Align the visible bottoms of each building and tree to one sidewalk top line. Use source-art visible bounds to seed placement, then adjust by rendered inspection. At saved zoom 0.5, the final visible gap from an asset base to the sidewalk must be no more than 3 logical screen pixels.
|
||||
- Give the initial camera frame a calm center lane at walking height: at least its middle third remains clear of Near artwork, leaving room for a standard standing stickman and a short speech bubble.
|
||||
- Add an opaque `StageBackgroundGround` behind the skyline, near artwork, and walkable tiles. It begins at the skyline's lower visible datum and extends below the opening viewport, covering the clouds that otherwise show through the gap between the skyline and street. Use the existing Grass finish. It is visual only and does not create another walking line.
|
||||
- Build Sidewalk as a horizontal row of 25 default-size Ground tiles, with centers from x=−2,304 to +2,304 and bounds x=−2,400…+2,400. Build Asphalt as a separate row of 27 default-size tiles, with one extra tile at either end and bounds x=−2,592…+2,592. Place Sidewalk at y=0 and Asphalt at y=32 so their edges meet and the Asphalt begins below the Sidewalk. The exposed 192-unit road ends provide a physically reachable 32-unit curb step; route navigation at those endpoints without merging the Ground blocks or colliders. Keep both rows flat and continuous; no single oversized block or deep scaled Asphalt piece.
|
||||
- Keep skyline and Near artwork as separate editable placements. The Far skyline should not reveal a distracting cut edge within the pan envelope; reuse/reposition or duplicate existing skyline art only if its actual bounds require it.
|
||||
|
||||
### Living Room
|
||||
|
||||
- Keep the Clear Day Sky and the existing window frame and room artwork. Extend the painted wall from side to side across x=−2,380…+2,380, with a clean floor boundary and editable baseboard or equivalent edge.
|
||||
- Build the wall as opaque editable panels around one deliberate window aperture. The opening is the only visible route to Sky. Extend the top and side panel bounds far enough to cover the full rendered viewport at pan endpoints; use slight opaque overlap at non-window joins to prevent subpixel Sky leaks. Do not overlap the aperture.
|
||||
- Preserve the window as a real gap between wall panels with the existing transparent frame in front of the Sky. Do not add fake glass, collision, or a new wall-cutout behavior.
|
||||
- Build `finish_ground_wood` as one horizontal row of 25 default-size Ground tiles. Centers are spaced 192 world units apart from x=−2,304 to +2,304 at y=200; at scale 1 the row's top is y=184 and its combined bounds are x=−2,400…+2,400. Keep one level collision top and the existing wood pattern scale; do not stretch one block across the room.
|
||||
- Place the couch, armchair, coffee table, and floor art so visible supports/base edges sit at the wood floor line within 3 logical screen pixels at saved zoom. Preserve Prop roles and their normal collision/support. Keep the rug centered, visually above the wood, below actors, and outside the central movement lane.
|
||||
- Keep the wall decorations separate and secondary to the window and action. No individual decoration or furniture piece may be merged into a backdrop image.
|
||||
|
||||
### Shared camera and child readability
|
||||
|
||||
- Retain saved zoom 0.5 unless rendered comparison shows the approved composition cannot fit. Save the chosen camera y/center in each scene so normal File → New opens directly to the intended shot.
|
||||
- At the supported 1,280×720-logical-pixel reference viewport, test the saved center and camera x at −1,000 and +1,000, at zoom 0.5. At all three positions, the applicable sidewalk/road, wall/baseboard, and floor cover the full rendered viewport width with no panel or terrain end, accidental Sky seam, or distracting join.
|
||||
- Tune vertical framing against the safe Stage area in a render-capable run: put the walking line about 70–80% down from the top of the usable Stage area. A standing stickman must fit from feet to head; a two-line speech bubble must remain readable with about 24 logical pixels of clearance from visible Stage chrome.
|
||||
- A narrower viewport is covered by the same surfaces and must retain actor/bubble legibility. Do not assert the 1,280-pixel guarantee for a wider logical viewport without recalculating the extent formula.
|
||||
|
||||
## Persistence and typed contracts
|
||||
|
||||
No new API, signal, or serialized field is introduced.
|
||||
|
||||
| Target | Existing contract and required update |
|
||||
| --- | --- |
|
||||
| `content/stages/starters/town_street.scene` | Write current v1.6 with the existing `sky_id`, camera, and ordinary object dictionaries. Add the Grass Background ground object, and use unique `file_id`s for each default-size Sidewalk (25) and Asphalt (27) terrain tile. Keep stable IDs for retained artwork and preserve the camera. |
|
||||
| `content/stages/starters/living_room.scene` | Write current v1.6. Update wall panel `position`/`size`, use unique `file_id`s for each default-size Wood Ground tile, and preserve artwork/Prop transforms and camera. Keep the window aperture represented by the spatial gap between wall panels. |
|
||||
| `StageSceneIO` (`class_name StageSceneIO`) | Read v1.5 legacy snapshots and write current v1.6 documents. Do not bump beyond v1.6 or add camera-envelope/preview fields. |
|
||||
| `StageSceneAdapter` (`class_name StageSceneAdapter`) | Continue mapping `terrain`, `background_ground`, `wall_panel`, `scenery_artwork`, and `prop` dictionaries into their existing node classes. No starter-specific loader branch. |
|
||||
| `SandboxStage` (`class_name SandboxStage extends Node2D`) and `Camera2D` | Load the saved camera normally. Do not clamp position to starter bounds. Coalesce adjacent collinear terrain top edges into continuous navigation ribbons so pathfinding crosses tile joints; connect Town's 32-unit curb step only where the lower Asphalt extends past the upper Sidewalk. Keep the terrain objects/colliders separate. Existing zoom limits and user input remain authoritative. |
|
||||
| `StageStarterCardPreview` (`class_name StageStarterCardPreview extends SubViewportContainer`) | Replace the all-object-bounds fit as the preview focus rule. Keep its private preview setup and local `_world.scale`/`_world.position`; derive an opening-shot focus from scene `camera.position`/`camera.zoom` and a stable reference aspect. Ignore bounds added solely to cover the pan envelope. Do not mutate the loaded document or live camera. No public signal or caller change. |
|
||||
| `TerrainBlock`, `StageBackgroundGround`, `StageWallPanel`, `StageSceneryArtwork`, `PropBlock`, `StageSurfaceRenderer`, `StageRectSurface` | Reuse existing typed configure/save/render/collision contracts. Modify one only if visual validation finds a concrete rendering, scaling, or hit-selection defect in the required bounds. |
|
||||
|
||||
Representative existing object shapes (not new schema):
|
||||
|
||||
```json
|
||||
{"file_id":"ground_sidewalk_13","type":"terrain","asset_id":"ground","position":{"x":0,"y":0},"rotation":0,"scale":{"x":1,"y":1},"finish_id":"finish_ground_sidewalk"}
|
||||
{"file_id":"ground_asphalt_13","type":"terrain","asset_id":"ground","position":{"x":0,"y":32},"rotation":0,"scale":{"x":1,"y":1},"finish_id":"finish_ground_asphalt"}
|
||||
{"file_id":"town_backdrop","type":"background_ground","position":{"x":0,"y":Y},"size":{"x":4800,"y":H},"finish_id":"finish_background_grass","color":"#72A84C"}
|
||||
{"file_id":"wall_left","type":"wall_panel","position":{"x":X,"y":Y},"rotation":0,"size":{"x":W,"y":H},"finish_id":"finish_wall_paint","color":"#5A9D70"}
|
||||
```
|
||||
|
||||
`N`, `X`, `Y`, `W`, and `H` are scene composition values to tune from rendered visible bounds and camera framing. They do not denote new runtime fields. Prefer scene objects whose visible geometry meets at common edges rather than hiding incorrect positions with z-order.
|
||||
|
||||
## Starter-card framing
|
||||
|
||||
Today `_fit_scene_to_preview()` unions every loaded object's world AABB and fits that union into 256×256. The 4,800-unit rows of tiles or wall would dominate that union. The replacement should:
|
||||
|
||||
1. Read the saved camera center and zoom from the already-loaded scene data.
|
||||
2. Build a square preview-only focus centered on the saved camera position. Use the 16:9 reference viewport height and saved zoom for the square world extent: at 1,280×720 and zoom 0.5, the crop is 1,440×1,440 world units. This keeps the town's central buildings/trees or the room's window/furniture in the chooser card while ignoring long pan-only bounds. Fit the square crop into the existing 220×220 usable area of the 256×256 preview.
|
||||
3. Fit that focus region inside the preview's usable bounds. Do not include pan-only surface tails when computing the frame; the live scene still renders those full surfaces.
|
||||
4. Leave the Sky preview still and full-rect. Apply preview pan/scale only to the preview `_world` node; never modify saved camera data.
|
||||
5. Inspect both cards at their actual chooser size. Each should show its identity and central composition without shrinking the actor/set to a tiny strip or clipping the window/road datum.
|
||||
|
||||
Do not add per-starter preview bounds to `.scene` unless a shared, camera-derived crop demonstrably cannot keep both cards recognizable. That would require a separately approved data/authoring need.
|
||||
|
||||
## User paths and observable acceptance
|
||||
|
||||
| Child path / repeatable scenario | Required observation |
|
||||
| --- | --- |
|
||||
| File → New → Town Street | At the saved shot, the child sees a readable neighborhood, opaque land behind the skyline, continuous tiled sidewalk/road, a clear center lane, and the existing cloudy Sky above the skyline. Near artwork bases rest on the same walkable line within 3 screen pixels. |
|
||||
| Town Street at saved zoom; pan to camera x=−1,000, 0, +1,000 | The full rendered width remains covered by sidewalk and Asphalt tile rows. Tile joints are intentional and edges have no collision or navigation gaps; no exposed row end or Sky/clouds show through below the skyline. A PLAY actor can walk off the sidewalk end and land on the exposed road, and navigation connects both walk lines there. The decorative backdrop does not support a stickman. |
|
||||
| File → New → Living Room | The child sees a complete painted room with visible baseboard and wood floor. Sky appears through the window opening only. Furniture supports meet the floor within 3 screen pixels; rug is centered below actors, with an open action lane. |
|
||||
| Living Room at saved zoom; pan to camera x=−1,000, 0, +1,000 | Wall, baseboard, and floor cover the full rendered width. No Sky leak appears at panel edges, under the floor, or outside the window. Furniture stays separate and the ground remains one support level. |
|
||||
| Place a standard standing stickman and a two-line speech bubble in the central lane; inspect EDIT and PLAY | Feet contact the intended surface. Head, body, and bubble are legible with the specified chrome clearance. PLAY preserves Ground support and existing Prop behavior; no decorative Background ground or wall panel gains collision. |
|
||||
| Open New Scene chooser; inspect Town Street and Living Room cards | Each card remains a recognizable opening-shot preview after the live-world pan surfaces are lengthened. The camera framing of the preview does not move the saved/live scene. |
|
||||
| Save each revised starter as an ordinary scene, close/reopen it | Existing object types, finish IDs, individual placements, roles, Sky ID, and saved camera framing round-trip through the current scene v1.6 format. The loaded scene remains editable and has no starter-only runtime behavior. |
|
||||
| Resize to a narrower Stage viewport; zoom in for a close shot | Continuous surfaces still cover the visible width. The action lane and window remain legible. This pass does not promise full-width coverage for zoom-out views wider than the reference envelope. |
|
||||
|
||||
Phase 2 must inspect rendered captures from a render-capable Godot Stage at opening frame, both pan endpoints, PLAY, both starter cards, and a narrower viewport; record the capture paths and observed pixel gaps/seams. Exercise actual camera pan, save/reopen, Ground support and navigation across tile joints, and normal selection/edit routes. Bake adjacent collinear tile tops into continuous navigation ribbons and connect the exposed Town curb step without merging separate terrain objects or colliders. A successful import or headless parse alone does not verify composition. If an interactive rendering/input route is unavailable after trying it, list each unverified outcome and provide a focused user check.
|
||||
|
||||
### Implementation and verification record (2026-10-21)
|
||||
|
||||
- The initial pass promoted both snapshots to v1.6 and aligned visible bases, but used oversized scaled walkable blocks and omitted Town Background ground. The user identified those issues; that surface implementation was superseded by the tile/backdrop revision below.
|
||||
- `StageStarterCardPreview` now derives a square crop from the saved camera position and zoom. At zoom 0.5, it frames a 1,440 × 1,440 world-unit opening crop in the existing 220 × 220 usable preview area.
|
||||
- `/Applications/Godot.app/Contents/MacOS/Godot --path /Users/ryan/Projects/stickman --headless --rendering-method gl_compatibility --script res://tests/test_phase19_starter_scene_composition.gd` passed **515 assertions** on Godot 4.7.1. It covers production scene loading, save/reopen, surface bounds, window opening, preview transforms, base contacts, PLAY support, navigation across each tile row, and sidewalk-to-road access. Town Asphalt has 27 scale-1 tiles spanning 5,184 units; a PLAY actor moved from x=2,280 to x=2,500 and landed grounded on the road at feet y=15.31. The navigation query from center to the opposite walk line follows the exposed right curb through `(2400, -4)` and `(2400, 12)`. Measured contact gaps at the saved zoom were 0.0 logical px for Town artwork; 0.0–0.123 px for Living Room floor artwork; and 0.025–0.225 px for the three Props. These are production-loaded geometry measurements, not pixel samples from rendered captures.
|
||||
- `/Applications/Godot.app/Contents/MacOS/Godot --path /Users/ryan/Projects/stickman --headless --rendering-method gl_compatibility --script res://tests/test_phase19_scenery.gd` exits 1. The Town/Living starter surface, count, scale, bounds, seam, pan-margin, and camera assertions pass; the nine remaining failures concern the Sky/surface preview card count, a wall-erasure document-change signal, and Edit Scenery erase/Cancel capture-bar behavior. These same failures were present before the latest tile and curb changes. The environment also reports a Stage autosave warning during that run.
|
||||
- Rendered-pixel inspection could not be completed here: a bounded GUI capture attempt exited 134 without output or an image, and an earlier headless compatibility capture produced no image before it was stopped after 30 seconds. The default headless Vulkan test path also crashed in shader translation. The focused compatibility test exited successfully; the environment printed a macOS CA-certificate lookup warning.
|
||||
- Remaining user/editor checks: opening frames, ±1,000 camera pan endpoints, the Town background and tile appearance, both starter cards, a narrower viewport, dramatic zoom-in, and rendered seams/Sky inspection. User-supplied Town Street imagery exposed the missing background; this revised pass still needs a fresh rendered visual review. Automated geometry and PLAY checks cover support across tile seams and the curb step.
|
||||
|
||||
### User visual follow-up (2026-10-21)
|
||||
|
||||
The user's Town Street capture showed Sky clouds visible below the skyline because the initial implementation omitted Background ground. The user also rejected the oversized scaled TerrainBlocks used for walkable surfaces and requested left-to-right rows of ordinary Ground tiles in both templates. The current revision adds opaque non-walkable Grass Background ground behind Town Street and uses adjacent scale-1 tiles for Sidewalk, Asphalt, and Living Room Wood. Sidewalk uses 25 tiles; Asphalt uses 27, extending one tile beyond each sidewalk end so the road is physically reachable. The focused production-path test passed 515 assertions, including seam routing and a grounded PLAY step onto Asphalt. Fresh rendered review and user/editor checks remain pending.
|
||||
|
||||
## Trade-offs and deferred work
|
||||
|
||||
- Ordinary-scale tile rows avoid oversized Ground selection bounds and keep each walkable piece independently editable. Their joints should remain intentional, with adjacent collision and visible edges meeting without gaps. The navigation bake merges contiguous coplanar tile tops for routing while the Ground pieces remain separate, and connects the Town curb step only at exposed road ends.
|
||||
- The 4,800-unit sidewalk/floor rows of ordinary-size Ground tiles cover the specified 4,760-unit minimum envelope and remain individually editable. Town Asphalt extends to 5,184 units for curb access. These finite surfaces do not track arbitrary viewport resizing, very wide zoom-out shots, or unbounded camera travel. The camera remains free; no edge clamp or auto-extension obscures that finite set boundary.
|
||||
- Preview framing is derived from the saved opening camera instead of saved preview metadata. This avoids schema/round-trip work but gives every card the same generic crop policy; if that cannot show a particular starter clearly, document the concrete limitation before proposing metadata.
|
||||
- No new art is required based on current library bounds. A curb/baseboard visual gap that cannot be composed from existing terrain finishes and wall panels should be described as one specific missing asset and sent through the existing asset-approval workflow.
|
||||
- Stage-wide Undo/Redo remains deferred. Starter polishing edits saved object data through existing flows and adds no new transaction system.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Phase 19 — Starter Scene Composition UI/UX Specification
|
||||
|
||||
> **Status:** Approved UI/UX specification. The separate architecture specification is approved for implementation. This defines scene presentation, not new controls.
|
||||
>
|
||||
> **Source:** [Phase 19 — Starter Scene Composition brief](../plans/phase_19_starter_scene_composition.md).
|
||||
|
||||
## User path
|
||||
|
||||
From **File → New**, the child chooses **Town Street** or **Living Room** and immediately sees a coherent set with a clear walkable area. At the saved camera frame, a standing stickman and a short speech bubble fit comfortably in the action area. The same visual relationships remain legible in EDIT, DIRECT, PLAY, and after save/reopen. A deliberate horizontal pan reveals more of the same set.
|
||||
|
||||
## Composition rules
|
||||
|
||||
### Town Street
|
||||
|
||||
- Keep the Cloudy Day Sky and a subdued Far skyline behind individual Near buildings and trees.
|
||||
- Add an opaque, non-walkable Background ground layer below the skyline datum. It must cover the lower opening view behind the street and prevent Sky clouds from showing through the gap between the skyline and the walkable surface.
|
||||
- Align all visible house, storefront, and tree bases to one sidewalk datum. Keep seams at or below 3 logical screen pixels at saved zoom.
|
||||
- Carry the Sidewalk and Asphalt across the usable stage frame as horizontal rows of ordinary-size, walkable Ground tiles. Keep their tops level and their edges adjacent so the tile joints are intentional, with no collision or visual gaps.
|
||||
- Leave at least the middle third of the initial frame visually calm at walking height. Near artwork may frame the action, but should not cover a character or speech placed in this area.
|
||||
- Keep Background ground behind the skyline, artwork, and terrain; distinguish it from the walkable sidewalk/road. It does not create another walking line.
|
||||
|
||||
### Living Room
|
||||
|
||||
- Continue the painted wall and a horizontal row of ordinary-size, walkable Wood Ground tiles across the initial frame and agreed pan envelope. Keep adjacent tile edges level, with intentional joints and no collision gaps. A visible baseboard or equivalent edge marks where wall meets floor.
|
||||
- Construct the window as a deliberate opening between editable wall pieces. The selected Sky is visible through that opening only; no Sky gaps appear at the room edges, below the floor, or along panel seams.
|
||||
- Place the couch, armchair, and coffee table so their visible supports meet the floor within 3 logical screen pixels at saved zoom. Keep the rug centered, above the floor finish, below actors, and outside the main movement lane.
|
||||
- Keep the middle third of the frame at walking height visually calm enough for a stickman and short speech bubble. Wall decoration stays secondary to the window and action.
|
||||
|
||||
## Camera framing and pan continuity
|
||||
|
||||
- Preserve the existing starter-choice flow and use a saved camera framing that shows the complete scene identity, walking datum, and clear action area at the actual safe Stage viewport size.
|
||||
- Place the walking line around 70–80% of usable viewport height measured from the top. Show a default standing stickman from feet to head and leave about 24 logical pixels between a two-line speech bubble and visible Stage chrome.
|
||||
- Define a horizontal pan envelope for each starter. At the saved zoom, the appropriate continuous surfaces must cover the full rendered viewport width at the envelope's left and right limits, with a small safety margin. Camera panning remains unrestricted beyond this authored envelope; finite set edges may appear there.
|
||||
- Compute surface width from the reference viewport: `rendered_viewport_width / saved_zoom + 2 * pan_travel + 2 * safety_margin`. For a 1,280-pixel reference width, zoom 0.5, ±1,000-world-unit travel, and 100 world units of margin per side, surfaces need at least 4,760 world units. Use 25 standard 192-unit Ground tiles for the Town sidewalk and Living Room floor. Town Asphalt uses 27 standard tiles, extending one tile beyond each sidewalk end so the road is physically reachable. Do not scale one walkable block across the scene.
|
||||
- Keep set parts independently editable. Tile rows use ordinary-scale pieces with adjacent edges, no collision gaps, and visible joints that read as regular surface tiles.
|
||||
- Do not stretch one walkable Ground block across the full pan envelope. Use a left-to-right line of default-size terrain tiles so each surface remains an ordinary, individually editable piece.
|
||||
|
||||
## Starter chooser preview
|
||||
|
||||
The starter card should show a representative opening composition, not fit the full pan-only extent. The current preview fits the combined bounds of every object; extending the ground and walls could make the important scene too small. Bound preview framing around the saved opening shot (or an explicit authored preview frame) while still showing the Sky, walking datum, and recognizable key artwork. This is a preview-only framing decision: it must not crop or constrain the loaded scene.
|
||||
|
||||
## Controls, copy, and theme
|
||||
|
||||
- Add no controls, filters, dialogs, inspectors, cursor modes, placement overlays, or status messages.
|
||||
- Keep the existing **Blank Scene**, **Town Street**, and **Living Room** choice labels.
|
||||
- Add no `SandboxTheme` colors or `stage_panels` tokens. Use the existing Sky, wall, Ground, Scenery, and Prop visual styles; tune scene-art contrast so a character and speech remain easy to read.
|
||||
- Reuse the existing EDIT, DIRECT, and PLAY mode treatments without adding mode-specific starter behavior.
|
||||
|
||||
## Observable UI/UX acceptance outcomes
|
||||
|
||||
1. Opening either starter at its saved frame reveals the intended place, a single readable walking datum, a calm central action area, and room for a default stickman plus short speech.
|
||||
2. At saved zoom, there is no visible (>3 logical screen-pixel) gap between Near artwork and the street datum or between furniture supports and the room floor.
|
||||
3. At the center and both ends of the specified pan envelope, the sidewalk/road or wall/baseboard/floor fills the usable Stage width without exposed ends, distracting seams, or stray Sky. In Living Room, Sky appears through the window opening only.
|
||||
4. The chooser card remains a recognizable view of the opening composition after the world surfaces are extended for panning.
|
||||
5. The same composition and visual ordering remain in EDIT, DIRECT, PLAY, and after save/reopen. Normal child edits can still move or remove individual pieces.
|
||||
|
||||
## Not applicable
|
||||
|
||||
There is no new interaction flow, so UI node/layout additions, focus behavior, cursor feedback, child-facing copy changes, dialogs, and inspector-token changes do not apply. Architecture will identify only the scene data, preview framing, or rendering changes needed to meet the observable outcomes.
|
||||
Reference in New Issue
Block a user