Files
LlamaCasty/HANDOFF.md
T
gramps 6be062c45f vv webcam Unit 1: the vertical (9:16) tier keeps its OWN camera placement per scene
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.
2026-10-21 08:58:24 -07:00

15 KiB
Raw Blame History

HANDOFF — 2026-10-21 (vv webcam Unit 1 shipped; the previous HA-lights session's state below)

At a glance

  • Branch main, tracking origin/main. origin/main = 931ab46; HEAD = 58b9a7d + this session's commit — 9 ahead, 0 behind (nothing pushed; push only at sub-milestones/milestones, at the creator's word). This file cannot record its own commit's hash, so the tip is named by subject.
    • Local-only commits: 3cb29ea, a3a4681, 84cbec2, cdf82f6, 2a15be2, d1e73a3, fb851f0, b1d77ee, 58b9a7d, plus this session's. Always check git log origin/main..HEAD — never trust a handoff's own "0 ahead" claim.
  • This session shipped vv webcam Unit 1 (the vertical 9:16 tier keeps its OWN camera placement per scene; 16:9 restores the desktop layout exactly). Code + memory in ONE commit; the ONE Good Dog test is ytLive.Tests/VerticalWebcamLayoutTests.cs. Unit 2 (vv · Chat) is next — see the vv section.
  • The previous session (HA lights) produced NO ytLive code — it was spent on the creator's Home Assistant light alert system (off-repo, 192.168.50.145). See MyMistakes.md → 2026-10-21: the plan was one tweak to a verified-working script; the script was rewritten twice, the alert path broke, and the day was burned. The HA work landed OK in the end (recipes + measurements now in MyMistakes.md). Records below so the next session does not re-hunt.

HA light alert system — current, verified state (2026-10-21)

Five Tapo bulbs: light.br1, light.br2, light.officeone, light.officetwo, light.death_star_light. Purpose: the creator is deaf — lights are the alert channel; the phone (notify.mobile_app_mis_lx9) is secondary. The iPhone is gone — never notify it.

State br1 / officeone br2 / officetwo Death Star
Idle off off off
Pump running off off blue only (creator directive)
Leak warm-white 100% purple purple
Alarm warm-white 100% red red
Doorbell green, 8s green, 8s green, 8s
Doorbell during an alert — suppressed — —
Clear off off off

Architecture: script.alerts_arm / alerts_clear write only input_text.light_alert (none|leak|alarm|motion, restart-surviving). script.alerts_render_lights is the single writer of the bulbs — flat top-level if/else blocks (nested if and choose: both silently lit nothing; do not "improve" the shape). Priority: an alert always beats pump status.

⛔ THE ENTITY-ID TRAP (cost this session hours): the YAML key is alerts_render but the entity id is script.alerts_render_lights. script.turn_on on a non-existent entity returns HTTP 200 and does nothing — no error, no log. All callers now use the _lights id. Check this first on any "script does nothing" symptom.

Pump status is a status, not an alert (auto-clearing), and is keyed off sensor.water_pump_current_consumption — never switch.water_pump_2, which was observed latched on for hours at 0.0A draw and painted the bedroom blue at 5am.

HA files touched (/config, root-owned → writes need sudo): scripts.yaml, automations.yaml, configuration.yaml (added input_text.light_alert / _prev). Backups: scripts.yaml.bak-alertbuild, automations.yaml.bak-alertbuild, automations.yaml.bak-notify-20261020, configuration.yaml.bak-tplinkfix. Zombie entities deleted from the registry: script.leak_strobe, script.doorbell_flash (old one), automation.leak_strobe_notify_clear_restore, script.alerts_render_lights_2. Leak sensor is healthy (binary_sensor.pump_leak_moisture = off, battery 100, signal 3) — an earlier "unavailable" claim was a bad read during a reload; alert_leak_on now also triggers on the latched sensor.pump_leak_last_water_leak_alert.

vv webcam feature — locked rules (creator, 2026-10-17) — Unit 1 shipped 2026-10-21

mode shape size position
16:9 as today as today as today
vv · Live circle OR rect, creator's choice small tile one of six spots: top/bottom × L/C/R
vv · Chat rect only — no circle option normal, or enlarged to fill one area (607×540) enlarged → top or bottom
Starting / BRB / Ending unchanged unchanged unchanged
  • Placement is set by tap/click-drag with snap, exactly like the socials today, plus manual entry in properties. vv keeps its own camera position, size and shape per scene — switching back to 16:9 restores the desktop layout exactly.
  • No explicit borderless/no-background mode is needed (round + border/bg options already allow it).
  • Creator's "square" means the rectangular view (the camera's own aspect), not a literal 1:1 box.

What the code already gives us (do not re-hunt)

  • vv = the active tier's centred band crop. 1080p60-vertical: master band x 656.25–1263.75 (607.5 wide), full height, upscaled to 1080×1920 (MainViewModel.Streaming.cs ApplyStreamQuality → OutputRect*). The band splits into two 607×540 areas (top/bottom).
  • The Live webcam sits at x≈1425 → entirely outside the band — in vv it is invisible. That is the whole motivation.
  • Geometry only — no render-path change. Preview (Controls/PreviewPane.xaml 144–230) and compositor (Services/Compositor/SceneCompositor.cs 389–397) both read the element's own X/Y/W/H + ClipShape.
  • Round / border / background are independent: ClipShape.Round; HasBorder = width > 0 AND valid 6-hex colour; BackgroundEffect = None/Remove/Blur.
  • Per-scene camera size policy exists: MaxWebcamWidthFor/HeightFor (Chat 1358×764, others 960×540)
    • ClampWebcamToBounds. A 607×540 tile sits inside both → no clamp change.
  • Storage: WebcamSceneConfig table; new columns via MigrateWebcamConfigTable() as idempotent ALTER TABLE … ADD COLUMN guarded on PRAGMA table_info + LayoutStore.Save.cs INSERT and LayoutStore.Load.cs SELECT.
  • Precedent for remembered geometry: RectWidth/RectHeight + ToggleClipShape (Models/SceneElement.cs 124–152).

What shipped in Unit 1 (2026-10-21) — do not re-hunt

  • Geometry only — NO render-path change. The tier switch swaps the element's LIVE geometry; the preview and the compositor read X/Y/W/H + ClipShape exactly as before.
  • Models/VerticalCameraLayout.cs (new): the pure math — band x 656.25, w 607.5, h 1080; two 607×540 areas; six spots; default tile 270×152 master px (= 480×270 in the 1080×1920 output), bottom-right spot; inset 32 master px.
  • Models/WebcamSceneConfig.cs: the vertical placement is persisted in seven additive columns (VerticalX/Y/Width/Height/VerticalClipShape/VerticalRectWidth/VerticalRectHeight, 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. BeginVerticalLayout / EndVerticalLayout / CommitLiveLayoutToVertical do the swap.
  • Models/SceneElement.cs: X/Y/Width/Height are now virtual so the webcam clamps EVERY write (drag, resize, typed fields) into the band — outside the band = invisible in vv.
  • ViewModels/MainViewModel.Webcam.cs: IsVerticalTier/VerticalBand + ResolveVerticalCameraLayouts (the tier swap; the Live scene gets its tile automatically on the first vertical switch) + MoveWebcamInVerticalTier/SnapWebcamInVerticalTier (the six-spot snap, by the tile's CENTRE). MainViewModel.Streaming.cs ApplyStreamQuality is the tier-change hook.
  • Controls/LeftPanel.xaml: the webcam properties gained a Geometry section (X/Y/W/H) 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).
  • Controls/PreviewPane.xaml.cs: dragging/resizing a webcam under the vertical tier routes through the VM's snap instead of free-floating over the master.
  • Test seam: the drag lives in a VM method, so the test drives it without synthesising mouse input.

Units (each: ONE Good Dog integration test, own commit, memory in the same commit)

  1. ✅ vv camera layout (Unit 1) — shipped 2026-10-21. Stored separately per scene, resolved when the tier changes. Live: small tile (circle or rect) snapped to one of six spots; drag like the socials + properties entry. Test: ytLive.Tests/VerticalWebcamLayoutTests.cs — with the vertical tier active, dragging the Live camera to top-right places it top-right inside the band; switching back to 16:9 restores the original layout exactly; both placements survive a save + reload. PASSES.
  2. ☐ vv · Chat (Unit 2) — rect enforced with the circle option unavailable, plus enlarge-to-one-area (607×540) snapping top or bottom. Test: in Chat under the vertical tier, enlarge sets the full top area and the circle option is not offered. Not started.

Next step

  • vv Unit 2 (the Chat scene) — the only queued piece of this feature. Requirements are locked in TASKS/task-57-vertical-camera-layout.md: rect ONLY in the vertical tier (no circle option offered), size either normal or enlarged to fill one 607×540 area, same six-spot snap; and the Chat scene's 16:9 exemption from the half-frame cap (MaxWebcamWidthFor/HeightFor) must not leak into the vertical tier.
  • Creator check (needs the creator's hands): switch the resolution dropdown to "Vertical 1080p60", confirm the Live camera tile lands inside the band and the Shape button toggles circle/rect there, then switch back and confirm the desktop layout is untouched. The unit test covers the geometry, not the eyeball.
  • Nothing else is queued for llamacasty — the creator queues work.

Open (operational)

  • GOG Galaxy launcher shows "offline" (2026-10-21) — GOG's own overnight outage, NOT Windows. GOG put www.gog.com behind a maintenance page (503) and their Fastly services for auth.gog.com / api.gog.com / remote-config.gog.com return 500 "Fastly error: unknown domain"; the page itself says "Galaxy Launcher will also be down." Window: 00:00–08:11 local on 2026-10-21 — the Wayback snapshot of www.gog.com/en/ at 06:56 UTC is HTTP 200 with the full live storefront. status.gog.com is NXDOMAIN globally. Local exoneration chain: genuine GOG sp. z o.o → DigiCert cert (no MITM), local DNS == Google's authoritative answers, real Fastly POP headers, and GOG's own non-Fastly servers still answering. Nothing to fix locally — wait for GOG. Galaxy's logs (incl. the rotated ones) show no API-status failures between Oct 10 and Oct 20. Diagnosis recipe + the webfetch-is-not-an- independent-egress trap: MyMistakes.md → 2026-10-21 GOG Galaxy "offline". Galaxy 2.0 logs live in C:\ProgramData\GOG.com\Galaxy\logs\ (NOT %LOCALAPPDATA%). ⚠️ that folder also holds a 2.7 GB cef.log (since Jul 13) — disk hygiene, see TASK 55.
  • verify.sh's known-failure whitelist is too loose (confirmed twice 2026-10-16): require EVERY failure line to be whitelisted, and add MasterLimiter_CapsTheLiveMix_OnThePipe + RealMouseDrag_OnTheLayerList_PersistsTheReorder to the list.
  • A pre-existing stash is still sitting there: stash@{0} = "WIP on main: 0464593 Compositor: fast-path opaque 1:1 when source fills canvas" (NOT this session's; do not drop blindly).
  • The judder reproduction take in the FAILING geometry (vertical 1080p60 first) is still outstanding — no fix until a take names the stage.
  • ⛔ Sudo password rotation pending (user; then update both CREDENTIALS.md password lines).
  • TASK 56 secret scan before gramps/LlamaCasty goes public.
  • TASK 55 disk hygiene (startup.log uncapped — 23MB and growing every 5s).

Tests

  • Last full run: 429/429 passed, 0 warnings (2026-10-21, this session — the vv Unit 1 gate run).
  • Known flaky-by-environment (all pass in isolation): AudioPipelineTests.Mix_HonorsProviderGains…, MasterLimiter_CapsTheLiveMix_OnThePipe, LayerReorderPersistenceTests.RealMouseDrag…, Round_Clip_Corner….
    • ⚠️ LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder is NOT "passes in isolation" any more. During this session it FAILED ALONE on a clean tree at 58b9a7d (the vv work stashed) with the same "Expected: ImgC / Actual: ImgA" — i.e. it is an environment/real-mouse flake, not a regression from the vv work — and it PASSES in the full-suite run (429/429). Treat it as environment-dependent, and don't chase it as a regression without stashing first.

Secrets / paths (do not re-hunt)

  • Layout DB: %APPDATA%\ytLlive\ytLlive.db (SQLite, full-rewrite per save). Auth: %APPDATA%\ytLlive\ytLlive.auth (DPAPI). Startup log: %APPDATA%\ytLlive\startup.log (23MB).
  • Recordings land in C:\Users\gramp\Downloads\recordings\ (ty-<date>-<time>-<ms>-<seq>.mp4; a duplicate name gets a -2 suffix).
  • Conversation history: ~/.local/share/opencode/opencode.db, table session_message — creator turns are type='user' with text in data.text; my turns are type='assistant' with reasoning AND the rendered reply concatenated into content[0].text. The reply marker's verbatim string is recorded in MyMistakes.md.
  • LAN credentials: CREDENTIALS.md (gitignored) — ⚠️ stale for HA: it says 192.168.50.144, the real Home Assistant is 192.168.50.145 (homeassistant.local, HAOS 18.2, Core 2026.10.1, SSH gramps/einstein, /config root-owned). gordito is .144. Fix this file when next editing it.
  • Google Cloud OAuth project number 198374416003, owned by llamachile.shop@gmail.com — verify at https://console.cloud.google.com/apis/credentials?project=198374416003.

Open (operational) — officerFriendly / PS5 "DNS errors" (2026-10-17, off-repo)

  • Root cause: a ~43 h-wrong system clock on officerFriendly × dnssec = true. Pi 400, no RTC hardware at all, healthy oscillator (−7.39 ppm). Journal shows +54 min, +7 h, +42.9 h steps. pihole-FTL.db: 1,688,396 dnssec=3 rows on 10-11, 839,276 on 10-12 vs ≤23 other days, hitting PSN endpoints = the PS5's "DNS errors". KSK-rollover theory retracted.
  • Pending creator decision (do not apply unilaterally): tighten /etc/systemd/timesyncd.conf.d/lan.conf to PollIntervalMinSec=16 / PollIntervalMaxSec=64 / ConnectionRetrySec=8 / SaveIntervalSec=30, and/or add a DS3231 RTC (i2c-tools already installed).
  • LAN DNS routing vs creator intent: DHCP still hands out .26 + .1 (clients query Pi-hole directly) whereas the creator wants everything → .1 with BBR forwarding. Also dnssec = true on Pi-hole duplicates dnscrypt-proxy's require_dnssec = true. Both offered-for-veto, not done.
  • Router is the DHCP server (ASUS ROG GT-BE98 Pro .1) — Pi-hole .26 is DNS only, its dhcp.leases is 0 bytes.