Files
LlamaCasty/docs/ai/environment.md
T
gramps 92d875c343 tools(stats): mine sessions from the logs, split by sink
`scripts/session-stats.py` turns every `startup*.log` into one row per FramePump session: tier,
scene, measured fps, drops, stalls, average/worst render, capture rate, and the build — with the
sink marked, because that is what makes the numbers quotable:

- **RECORD** — machine only, no socket. The cleanest minimum-config evidence.
- **TEST** — machine + socket, no audience; recognised by `Test session dock open` /
  `Test chat simulation`, which the creator pointed out are recorded too.
- **LIVE** — network and audience included.
- **UNKNOWN** — no marker; not evidence.

Verdicts use the average render against the frame budget, not the worst: the worst is dominated by
the 100 ms first-frame bake (a session that "holds" can still show worst 35 ms). `holds` = no drops,
no stalls, average under half the budget.

First run over the existing logs already separates the two halves of TASK 33: static Starting scenes
render at ~1.5 ms, the same pump with display capture running at 35–46 ms — the capture path, not the
overlays, is where a budget streamer loses the frame budget.
2026-11-09 09:04:04 -08:00

11 KiB
Raw Blame History

Environment, build & tests

Lookup material: LAN hosts, the Windows build commands, where the DB/auth/log live, and the test suite map. Read when you are about to build, run, or add a test — not on every session.

Paths and secrets belong with the runbook in ../../HANDOFF.md → "Secrets / paths" until the queue trim moves them here.

LAN infrastructure (192.168.50.x)

All LAN credentials live in CREDENTIALS.md (gitignored). Server IPs and roles:

Host IP Role
corsair .132 Gaming/streaming primary — RTX 5070 Ti (this machine)
llamavault .87 ODROID-HC4, 12TB RAID5, media storage (NFS). SSH: ssh mshallop@192.168.50.87. DHCP-bound — it moved off .86 after the 2026-10 power outage, so confirm with a ping before trusting any path. SMB is reached by the llamavault name, pinned in the Windows hosts file, never by literal IP: \\llamavault\llamaVault\...
pivault .158 RPi5 8GB, 10.83TB RAID5 NAS
jarvis .210 AMD Ryzen 5 5600X, RX 6600 XT — AI inference, SearXNG, Docker registry, Immich
ultron .108 AMD Ryzen 7 7840HS — AI orchestrator, llama-server, Qdrant
gordito .144 Home Assistant, Alarmo, Zigbee
officerfriendly .26 Pi-hole DNS
octopi .197 OctoPrint (Prusa MK4)
BigBlinkyRouter .1 ASUS ROG GT-BE98 Pro, WiFi 7

Web infra (DO droplet): llamacasty.com + llamachile.tube at 143.244.176.131.

Pushing to the Gitea instance (llgit.llamachile.tube). The repo is gramps/LlamaCasty (renamed from gramps/ytLlive; the old path 301-redirects). Push over HTTPS, not SSH: origin = https://llgit.llamachile.tube/gramps/LlamaCasty.git, with the credential helper /home/gramps/bin/gitea-credential (registered as credential.https://llgit.llamachile.tube.helper) reading the account password from the gitignored CREDENTIALS.md on demand — nothing stored in plaintext, nothing in a command line. The Gitea account password IS the portal password in CREDENTIALS.md, and the same password authenticates the REST API (curl --config …/api/v1/user) — that API is the fastest way to ask the server questions (the repo's real name and clone URL, the account's SSH keys, a token). ⛔ The SSH endpoint on llgit:2214 is not a usable push path: the account's SSH keys ARE registered (key id 6 "Corsair VSC" = this machine's ~/.ssh/id_ed25519) yet the server refuses the publickey, and the port later refused connections outright. Do not spend a session on key archaeology — ask the API, then push over HTTPS.

Run

First time on a fresh clone (or if ffmpeg/ was deleted) — this is REQUIRED, not optional:

./scripts/fetch-ffmpeg.sh   # ~65 MB once; verifies SHA-256, extracts 9 files into ./ffmpeg

FFmpeg is bundled, never downloaded at runtime (Windows Store policy 10.2.2 — the old runtime download 404'd on 2026-09-01 and would fail certification). The binaries are gitignored because 135 MB of DLLs in git would tax every commit on this 9p tree; the pinned URL + SHA-256 in the script are the audit trail. ytLive.csproj's GuardBundledFfmpeg target fails the build with that exact instruction if the folder is missing. FFMPEG_ZIP=<path> reuses a pre-downloaded archive.

# From WSL, ALWAYS use the Windows dotnet host — never Linux `dotnet` for this project:
"/mnt/c/Program Files/dotnet/dotnet.exe" build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj"
"/mnt/c/Program Files/dotnet/dotnet.exe" run -c Release   # the launch command (creator, 2026-10-16): Release, not Debug — perf takes must read Release numbers

Run-mode convention (creator directive 2026-10-16): the creator launches with -c Release by DEFAULT. Debug runs happen ONLY when the AI explicitly names the Debug command for a specific reason — never implicitly, and never for reading performance numbers (Debug pixel paths are several × slower; every take in the 141ms saga read Debug numbers where Release is the real reading).

Pre-commit gate (write it down — I forgot once): ALWAYS run ./scripts/verify.sh "<file1>" "<file2>" ... (declared scope) before committing. It forces a clean build (0 warnings), the full test suite (passes only the 2 known pre-existing [FAIL]s), and the scope check in one shot. An incremental build can report 0 Warning(s) while skipping recompiles — that's how CS8601 slipped past on 2026-08-29. Never claim 0 warnings from anything but verify.sh's clean build.

EnableWindowsTargeting=true in ytLive.csproj lets a cold restore work from WSL, but a Linux dotnet run/build re-downloads 100M+ of windowsdesktop.app.* packs into the Linux NuGet cache (which lacks them) over the slow 9p /mnt/c bridge — twice, because the WPF _wpftmp generated project triggers a second restore (203s observed). The Windows cache has the SQLite packages and the packs resolve from C:\Program Files\dotnet\packs, so the Windows host never re-downloads. Never use --no-restore right after an interrupted restore — the stale project.assets.json produces misleading NETSDK1064 "package not found" errors. Running requires Windows anyway.

Tests

xUnit in ytLive.Tests (net8.0-windows10.0.19041.0, matches the app TFM). Run on Windows — WSL can't run net8.0-windows tests:

dotnet.exe vstest "C:\...\ytLive.Tests\bin\Debug\net8.0-windows10.0.19041.0\ytLive.Tests.dll"

Good Dog Rule: ONE integration test per change (no feature branches pre-1.0 — work lands on main, see AGENTS.md). Current: TokenStore DPAPI roundtrip/corrupt/missing/clear, OAuth exchange/refresh/ClearSession, CameraManager refcount + frame pump + failure handling (fakes for the WinRT seams), real-MainWindow round-clip interaction test, LayoutStore delete roundtrip, LayoutStore pre-round-rect-dims roundtrip, LayoutStore background roundtrip, LayoutStore HasBackground roundtrip + v5→v6 non-Live backfill, ScreenCaptureManager refcount + shared-bitmap + coalescing (fake IScreenCaptureSource + a real background-STA Dispatcher), BackgroundTests, SceneCatalogTests, the TASK 25 dirty-layout heal (BackgroundHealIntegrationTests), WebcamSafeguardTests, SceneCompositorTests (full-scene composite + vertical tier), StretchMathTests, FfmpegLocatorTests, FfmpegEncoderTests, FramePumpTests, the TASK 8/11/12 audio chain (AudioPipelineTests + MasterLimiter), the Socials fediverse-heal roundtrip, AboutHubTests, NotificationAreaIntegrationTests (TASK 24), GlobalHotkeyTests + HotkeyConfigTests (TASK 20), WebcamMenuGateTests (TASK 26), ChatLayerGateTests (TASK 27), BroadcastPullOutTests (TASK 29), DefaultRecordFolder fallback (TASK 30), WebView2ManagerTests (TASK 17), RecordingFileTests + OnAirSignTests (TASK 18), SessionTeardownTests + WebcamOutputKeyTests + GameMeterHonestyTests (2026-09-01 recording-verification fixes) — ZERO known failures as of 2026-09-01. AudioPipelineTests 25/25 green in 26ms — the "known failing" Mix_HonorsProviderGains… and the class's notorious STANDALONE HANG shared one root cause: TASK 22's _delayedMix (nullable, never initialized) was dereferenced (delayed.Length on null) every live-mix tick — a swallowed NRE starved the pipe the tests read and flooded startup.log. Test was right, code drifted from the map's own contract (loopbackGain = GameAudioVolume — now honored; the "unity because loopback scales with the endpoint" assumption was disproven by the creator's 20%-volume meter observation). The former RoundClip "known failure" (stale-test layers: namescoped FindName + VisualTreeHelper.HitTest where UIElement.InputHitTest models input — see MyMistakes) was fixed the same day. Per-class runs through the Windows dotnet.exe host execute the RealApp/MainWindow suites fine; only the FULL-suite run still hangs (WASAPI teardown, pre-existing) — and the whole-suite "247 total" era count is stale; trust per-class results.

Reward-event capture (monetization awareness, see the Monetization section) will add its integration tests here when it ships: one real chat-poll payload containing all seven reward event types → assert the persisted canonical RewardEvent rows (type + amount/currency/tier/memberLevel/participant ids) round-trip into SQLite.

Real-MainWindow tests MUST be hermetic (DB pollution bug)

The integration test boots a real MainWindow → MainViewModel → real LayoutStore (%APPDATA%\ytLlive\ytLlive.db). Shutdown() on close saves the layout (full rewrite: DELETE all scenes/sources, re-insert), so any source a test adds would be persisted over the user's real ones — this happened and wiped the real webcam source (DeviceId replaced by the test's fake test-camera). Rule: a test that constructs MainWindow MUST first set MainViewModel.LayoutPathOverride to a temp DB path and reset it (plus SqliteConnection.ClearAllPools() + delete) in finally. The seam is internal static string? LayoutPathOverride (line ~529 in MainViewModel.cs), ytLive.csproj has InternalsVisibleTo("ytLive.Tests").

The layout DB is a full rewrite per save (delete all, re-insert from memory), so save/load round trips are exact: an element removed in the UI (RemoveElement → scene.Elements.Remove → OnElementsChanged → debounced ScheduleSave, plus Shutdown on close) does not come back after reload (LayoutStorePersistenceTests guards this).

Session performance evidence

python3 scripts/session-stats.py mines every startup*.log (primary, rotated, and per-instance) into one row per FramePump session: tier, scene, measured fps, drops, stalls, average/worst render, capture rate — and the sink, which is the whole point:

Sink How it is recognised What it proves
RECORD Recording ended / Recording discarded the machine alone — no socket
TEST Test session dock open / Test chat simulation the machine + socket, no audience
LIVE Broadcast created / Broadcast bound everything, network included
UNKNOWN no marker (crashed or paused) nothing — investigate before quoting it

Verdicts: holds = 0 drops, 0 stalls, average render under half the frame budget; strained = under the full budget; otherwise over-budget. Worst render is printed but never judged — it is dominated by one-off startup hitches. This is the evidence source for the minimum-configuration table in product.md: recordings first, because the network cannot pollute them.

Persistence & external accounts (lookup)

  • Layout: %APPDATA%\ytLlive\ytLlive.db (SQLite, schema v8 — scenes, sources, assets by SHA-256 content hash, socials, settings; full rewrite per save).
  • Auth session: %APPDATA%\ytLlive\ytLlive.auth (DPAPI, CurrentUser scope).
  • Startup log: %APPDATA%\ytLlive\startup.log, capped at 5 MB with one rolled copy (Helpers/LogRotation.cs, TASK 55 slice 1).
  • Google Cloud project 198374416003 — the OAuth ClientId prefix is the project number. It is owned by llamachile.shop@gmail.com ("gramps"), which is NOT the Google account a creator signs into YouTube with; console access and channel identity are separate logins. Direct link: https://console.cloud.google.com/apis/credentials?project=198374416003 — if the picker shows no match it defaults to the "Recent" tab, so switch to "All".
  • LAN credentials live in the gitignored CREDENTIALS.md. ⚠️ It says Home Assistant is 192.168.50.144; the real HA is 192.168.50.145 (.144 is gordito). Fix that file when next editing it.