The vertical tier is a centred band crop of the SAME 1920x1080 master (band x
656.25, w 607.5, h 1080), so the 16:9 camera placement (x~1408) falls outside the
cropped window and the creator is simply not in the vertical stream. Each scene
now keeps its own vertical position, size and shape; switching back to 16:9
restores the desktop layout exactly (the creator's locked table, 2026-10-17).
Geometry only - NO render-path change. The preview and the compositor both read
the element's own X/Y/W/H + ClipShape, so the tier switch swaps the LIVE geometry:
- Models/VerticalCameraLayout.cs (new): the pure math - the band, its two 607x540
areas, the six spots (top/bottom x left/centre/right), the 32px inset and the
270x152 default tile (480x270 in the 1080x1920 output) in the bottom-right spot,
the corner the 16:9 default webcam uses.
- Models/WebcamSceneConfig.cs: the vertical placement lives in seven additive
columns (VerticalX/Y/Width/Height/ClipShape/RectWidth/RectHeight; NULL = "this
scene has no vertical placement yet"). The 16:9 layout stays in the old columns
and is parked in the transient Landscape* fields while the vertical tier is
live; Begin/EndVerticalLayout + CommitLiveLayoutToVertical do the swap. The
X/Y/W/H overrides clamp every write into the band - outside it the camera is
invisible in the vertical output.
- Models/SceneElement.cs: X/Y/Width/Height became virtual so the derived element
constrains every writer (drag, resize handle, typed fields) in ONE choke point
instead of three writers each re-implementing the rule.
- ViewModels/MainViewModel.Webcam.cs + .Streaming.cs: IsVerticalTier/VerticalBand,
ResolveVerticalCameraLayout(s) (the tier swap), MoveWebcamInVerticalTier /
SnapWebcamInVerticalTier (the six-spot snap, by the tile's centre),
EnsureVerticalCameraLayout; ApplyStreamQuality is the tier-change hook. The
Live scene gets its small tile automatically on the first vertical switch (the
vertical stream would otherwise have no camera at all); the other scenes keep
their 16:9 placement until the creator drags them into the band.
- Controls/PreviewPane.xaml.cs: the drag/resize route through the VM snap under
the vertical tier instead of free-floating over the master.
Controls/LeftPanel.xaml: the webcam properties gained a Geometry section
(X/Y/W/H) for the manual entry the creator asked for, bound to the LIVE
placement, gated on the VM's IsWebcamSelected (NOT SelectedElement.IsWebcam -
the TASK 51 test finds that panel by binding text and would match twice).
- The store: seven guarded ADD COLUMNs, the INSERT/SELECT extended, and the
X/Y/W/H/ClipShape columns stay the 16:9 layout - while the vertical tier is
live the save writes the parked values there and the live ones into Vertical*.
Test: ytLive.Tests/VerticalWebcamLayoutTests.cs - the ONE Good Dog test. The
vertical tier brings the Live camera INTO the band as a small tile; dragging it to
the top-right lands it top-right inside the band; back to 16:9 the desktop layout
is restored exactly (1408/778/480x270); both placements survive a save + reload.
Full suite 429/429, 0 warnings. LayerReorderPersistenceTests.RealMouseDrag fails
ALONE on a clean tree in this environment - pre-existing flake, proven by stashing
this work, not a regression.
Derivative work: OBS has no built-in per-aspect-ratio layout storage - the
maintainers' answer is "create a second scene and add the scene you want to send
as source" (obsproject.com/forum/threads/161307) - and Streamyard's dual-aspect
guide says the same ("no built-in way to store separate layouts per aspect ratio",
so you rely on manual workarounds and plugins):
https://streamyard.com/blog/dual-aspect-ratio-streaming-tool
Our per-scene, per-tier stored placement, resolved on the tier change, is the
step past both.
Memory in the same commit: ai.md (new section), TASKS.md + TASKS/task-57-*.md
(Unit 1 shipped, Unit 2 = the Chat scene still open), HANDOFF.md, MyMistakes.md
(the per-tier geometry-swap recipe, the binding-text panel trap, the store's
webcam-identity rule) and the Models/Services/ViewModels/Controls index rows.
70 lines
4.3 KiB
Markdown
70 lines
4.3 KiB
Markdown
# TASK 57 — Vertical (9:16) tier camera placement — the "vv" feature
|
||
|
||
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
|
||
|
||
**Status:** 🔶 Unit 1 shipped 2026-10-21 (the Live scene). **Unit 2 open** (the Chat scene).
|
||
|
||
## Why
|
||
The vertical tier (1080×1920) is a centred **band crop** of the same 1920×1080 master every
|
||
16:9 tier uses: `OutputRect*` = x 656.25, y 0, w 607.5, h 1080. The default webcam sits at
|
||
x≈1408 — **outside the cropped window entirely**, so in a vertical stream the creator is simply
|
||
not in the picture. Switching tiers must not cost the creator their camera.
|
||
|
||
## The creator's locked table (2026-10-17)
|
||
|
||
| mode | shape | size | position |
|
||
|---|---|---|---|
|
||
| 16:9 | as today | as today | as today |
|
||
| **vv · Live** | circle **or** rect, the creator's choice | small tile | one of **six** spots: top/bottom × left/centre/right |
|
||
| vv · Chat | rect only | normal, or enlarged to one 607×540 area | the same six spots (Unit 2) |
|
||
| Starting / BRB / Ending | unchanged | unchanged | unchanged |
|
||
|
||
Plus: each scene keeps its **own** vertical position, size and shape; switching back to 16:9
|
||
restores the desktop layout **exactly**; placement is set by drag with snap (like the socials)
|
||
**and** by manual entry in the properties.
|
||
|
||
## Design (Unit 1)
|
||
|
||
- **Geometry only — no render-path change.** The preview and the compositor read the element's
|
||
own `X`/`Y`/`Width`/`Height` + `ClipShape`, so the tier switch swaps the live geometry:
|
||
`WebcamSceneConfig.BeginVerticalLayout(...)` parks the 16:9 layout and brings the vertical one
|
||
in; `EndVerticalLayout()` commits the vertical placement back to its stored slot and restores
|
||
the parked 16:9 layout.
|
||
- **Storage.** Seven additive columns on `WebcamSceneConfig` (`VerticalX/Y/Width/Height/
|
||
VerticalClipShape/VerticalRectWidth/VerticalRectHeight`), NULL = "this scene has no vertical
|
||
placement yet". The X/Y/W/H/ClipShape columns stay the **16:9** layout, so a save never loses
|
||
the desktop placement.
|
||
- **The band and the six spots** (`Models/VerticalCameraLayout.cs`, pure math): the band splits
|
||
into two 607×540 areas (top/bottom), each with three columns (left/centre/right). A tile snaps
|
||
to the spot its **centre** falls in, inset 32 master px from the band's outer edge and never
|
||
outside the band. Default tile: 270×152 master px (480×270 in the 1080×1920 output), bottom-right
|
||
spot — the corner the 16:9 default webcam uses. The Live scene gets it automatically the first
|
||
time the vertical tier is picked; the other scenes keep their 16:9 placement until the creator
|
||
drags them into the band.
|
||
- **One clamp, one choke point.** `SceneElement.X/Y/Width/Height` became `virtual` and the webcam
|
||
constrains every write (drag, resize handle, typed Geometry fields) into the band — outside it
|
||
the camera is invisible in the vertical stream.
|
||
- **Manual entry.** The webcam's Layers properties gained a Geometry section (X/Y/W/H) bound to
|
||
the **live** placement — the one the current tier shows.
|
||
|
||
## Slices (one integration test each)
|
||
|
||
1. ✅ **Unit 1 — the Live scene** (shipped 2026-10-21): the vertical tier brings the Live camera
|
||
into the band as a small tile; dragging it to the top-right snaps it top-right inside the
|
||
band; switching back to 16:9 restores the desktop layout exactly; both placements survive a
|
||
save + reload. `ytLive.Tests/VerticalWebcamLayoutTests.cs` — the ONE test for the unit.
|
||
2. ☐ **Unit 2 — the Chat scene**: rect only (no circle) in the vertical tier, either the normal
|
||
size or enlarged to fill one 607×540 area; the same six-spot snap. The Chat scene's 16:9
|
||
exemption from the half-frame cap must not leak into the vertical tier.
|
||
3. ☐ **Creator check** (needs the creator's hands): switch the dropdown to "Vertical 1080p60",
|
||
confirm the camera tile lands inside the band and the Shape button toggles circle/rect in the
|
||
vertical tier, then switch back and confirm the desktop layout is untouched.
|
||
|
||
## Notes
|
||
|
||
- OBS cannot do this (no per-aspect layout storage — the maintainers' answer is a second scene
|
||
fed as a source); Streamyard documents the same gap. Our per-scene, per-tier stored placement
|
||
resolved on the tier change is the step past both.
|
||
- The vertical tier's output crop is TASK 53's business (safe zones, caption clearance); the
|
||
camera tile is inside the band, but the band is not yet proven against TikTok/Shorts chrome.
|