Files
stickman/docs/specs/phase_19_starter_scene_composition.md
T
ryan 0b1dda2e2f 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.
2026-10-21 17:46:58 -04:00

158 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.