# Product: brand, licensing, monetization & distribution What the product is called, what it promises, what it may never do legally, and how it ships. Read before branding, marketing, pricing, licensing or packaging work. ## Brand **The UI design standard is [`DESIGN.md`](../../DESIGN.md)** — colours, type scale, spacing, radii and elevation, all normative, implemented by `Themes/Tokens.xaml`. Accent blue `#38bdf8` is the only interactive accent; red `#ff5451` means live/record and nothing else. The old brand red `#e94560` is retired from app chrome (the wordmark/logo artwork is unchanged). Marketing palette, official assets and copy live in `MARCOM.md` (gitignored — read it before any branding or marketing work). ### Product name vs repo/assembly branding (2026-09-14) The product is **llamacasty** (llamacasty.com, llamachile.tube, `llgit.llamachile.tube`). The repo path, csproj `AssemblyName`/`RootNamespace`, DB/log paths (`%APPDATA%\ytLlive\...`), and most internal namings are the legacy **ytLive/ytLlive** — a re-brand that never renamed the internals. Treat the two as separate: external/user-facing language says "llamacasty", code/assembly/repo names stay `ytLive`. If a full internal re-brand is ever done, this note and the UI locator (Premium/wordmark) are the checklists; it is NOT a goal pre-1.0. **Say "LlamaCasty" when reporting to the creator** — never "ytLive" in prose. `ytLive` is the internal repo/assembly/namespace name only. Exact current split: | surface | name | note | |---|---|---| | product / site / marketing | **LlamaCasty** | llamacasty.com, llamachile.tube | | repo dir, csproj, `RootNamespace` | `ytLive` | 230 files: `namespace ytLive.*` | | `AssemblyName` → shipped exe | `ytLive` | `ytLive.exe` | | app data dir | `%APPDATA%\ytLlive` | **capital L**, drifts from `ytLive` | | `Distribution.md` tells customers | `LlamaCasty.exe` | ⚠ **mismatch vs the real `ytLive.exe`** | **Consequences, so nobody re-hunts them:** - Renaming `` to `LlamaCasty` is the cheap win (the customer-visible exe name matches what `Distribution.md` already promises; namespaces can stay `ytLive.*`). It is **4 lines**, but it hard-breaks three pack URIs that spell the assembly name: `MainWindow.xaml:14` (the **window icon** — taskbar/Alt-Tab), `OverlayHost.xaml:96`, `RenameRecordingDialog.xaml:5`. Miss those and you ship a broken icon. Queued in TASKS.md under the shipping task, not done. - Renaming the `%APPDATA%\ytLlive` folder is **breaking** — it would orphan every existing user's layout DB, `Assets` and settings. Never do it casually; needs a migration. - No code compares its own assembly name (no `GetExecutingAssembly().GetName().Name` checks), so the assembly name is otherwise free to change. ### AI transparency This project is built through AI-assisted pair-programming. All code is generated under the supervision of an experienced developer with decades of coding experience. Architecture decisions, product direction, and quality gates are human-owned; AI accelerates implementation. This is a testimonial to successful human-AI collaboration, not autonomous code generation. ## Licensing — do not violate (GPL-3.0 open source since 2026-10-09; see `LICENSE`, `ADDITIONAL-TERMS.md`, `THIRD-PARTY-NOTICES.txt`) **LlamaCasty is free software under GPL-3.0-or-later** (creator rulings 2026-10-09 — free forever, public repo, "GPL-3 with attribution"). Copyright (C) 2026 Gramps. `ADDITIONAL-TERMS.md` carries the three §7 terms: **7(b)** every copy keeps "Based on LlamaCasty by Gramps — https://llamacasty.com" in its About screen; **7(c)** modified versions are marked and renamed; **7(e)** no trademark rights. The **icon + logo files** (`Assets/llamacasty-icon.png/.ico`, `Assets/llamacasty-logo.jpg`) are **all rights reserved, outside the GPL** — verbatim copies may carry them, forks must replace them (this is what keeps the merch brand protected). The scene backgrounds + default alert video are AI-generated **placeholders** (creator: made "with the intent of individual creators to immediately replace with their own images") — they go out under the GPL with the code, no reservation. `LICENSE` + `ADDITIONAL-TERMS.md` ship beside the exe (csproj `None` items); the About panel shows `THIRD-PARTY-NOTICES.txt`, whose header carries the LlamaCasty license notice (GPL §5(d) "Appropriate Legal Notices"). **Source offer (§6):** `https://llgit.llamachile.tube/gramps/LlamaCasty` (the Gitea repo; creator toggles it public — the old `gramps/ytLlive` path 301-redirects there), recorded in the notices header + README. Every bundled component must stay GPL-3.0-compatible (MIT/BSD/Apache-2.0/LGPL/public domain all are; **GPL-2.0-only and proprietary are not**; Apache-2.0 is fine with GPL-3 but NOT with GPL-2). The rules below predate the GPL switch; where their *reason* was "proprietary product" it is noted, but the rule itself still stands for its other reasons. **NEVER:** - **Swap the FFmpeg pin casually** — the BtbN `lgpl-shared` pin is the verified build. A GPL FFmpeg build would now be license-compatible (the old "GPL contaminates the paid product" reason is gone), but changing builds means re-verifying encoders, and libx264 carries no H.264 patent grant (Cisco's covers OpenH264). Keep `lgpl-shared` unless a task says otherwise. - **Distribute the static lgpl build** — LGPLv2.1 §6 wants relinkable object files for static linking. The **shared** (dynamic-DLL) build sidesteps that: compliance is "license text + source offer + unmodified binaries". The pin is `lgpl-shared`; when the pin is refreshed, keep the shared variant. - **Use BtbN's `nonfree` variant** — it adds fdk-aac (Fraunhofer code licensing). The native FFmpeg AAC encoder is fine (no Fraunhofer code) but grants no AAC patent license — accepted low-risk posture for RTMP→YouTube, since encoder vendors cover their implementations (Cisco OpenH264, NVIDIA NVENC, Intel QSV, AMD AMF). - **Link FFmpeg into the app** — it stays a separate subprocess fed frames over a pipe; that separation keeps the app's own code out of LGPL reach. - **Drop `THIRD-PARTY-NOTICES.txt`** from the shipped app or the About screen, or alter the FFmpeg copyright/LGPL notices inside the downloaded binaries. Automating the download counts as distribution — the obligations are not optional. - **Pin to a moving target** — the `latest` BtbN release tag floats. Only immutable autobuild tags give a reproducible source offer. Record the tag + variant beside the URL (TASKS.md) every time the pin moves. - **Use non-CC0 icon art** — the social bar's bundled SVG logo path data comes from **Simple Icons** (CC0 1.0, public domain — see `THIRD-PARTY-NOTICES.txt`). Replacing or adding logos must stay CC0 or another public-domain source; a logo asset under an incompatible or attribution-heavy license would complicate every redistribution. The app's icon + logo (`Assets/llamacasty-icon.*`, `Assets/llamacasty-logo.jpg`) are the creator's own art — all rights reserved, outside the GPL (`ADDITIONAL-TERMS.md`). - **Ship a font whose licence was not checked.** The two bundled families — **Inter** and **JetBrains Mono** — are **SIL Open Font License 1.1** (approved 2026-10-30 with the DESIGN.md standard). OFL is a free licence but is **not** GPL-compatible in the linking sense; they ship as separate `.ttf` files loaded through WPF's font stack, which is aggregation, so the two coexist. Obligations: keep the copyright line in `THIRD-PARTY-NOTICES.txt`, ship the `OFL-*.txt` text beside the files in `Assets/Fonts/`, and never modify the fonts or reuse their reserved family names for a modified version. Any new typeface needs this same check before it ships. - **Ship an ML model without clean provenance** — the background-effects model (TASK 51) is Google's official MediaPipe Selfie Segmenter download (Apache-2.0, model card), converted by our own script. Model-zoo re-hosts (e.g. PINTO #109 = ML Kit's model, extracted) are off-limits. Apache-2.0 §4 obligations (license notice + "we converted it") live in `THIRD-PARTY-NOTICES.txt`. - **Forget the v1 license-texts gate** — `THIRD-PARTY-NOTICES.txt` links the canonical license texts; at **v1 (GA)** the full texts of every license it names MUST ship alongside it (TASK 4 requirement 9 is the release blocker). Queued early is wrong; the release pass owns it. - **Break the in-app About requirement** — since TASK 11 the About overlay is the app's in-app creator hub: the real logo, the creator links (YouTube channel, Mastodon, Buy me a coffee, and the greyed "Unlock Premium" seam whose billing URL is tabled), and a **Licenses & legal** sub-panel that loads `THIRD-PARTY-NOTICES.txt` from the executable directory and renders it in a scrolling panel — **never the OS viewer, no Notepad, no browser tab**. The premium/coffee link constants live in `MainViewModel` (`PremiumUrl`, `CoffeeUrl`, `ChannelUrl`, `MastodonUrl`); filling in the billing URL lights up the premium button. Do not reintroduce an "open the notices in Notepad" button. ## Design Principle > This software is so intuitive that even the most right-brained person can easily intuit and use it. Apply this to every UI decision: - One-click go-live with working defaults - Prefilled YouTube defaults (RTMP URL, bitrate, resolution, latency) - Visual/drag-and-drop scene building over property panels - Every action produces a visible outcome — no dead ends - **"We're not them" (2026-09-01):** assume the creator's hardware is mediocre, because it is. Every feature spends the machine's budget once — never twice for the same result (record OR stream, not both; one reusable stream; one webcam; two mixer inputs). If a feature only sings on a high-end chassis, it doesn't ship — the OBS escape hatch is open by design. ## Monetization — free forever, no watermark **All features are free and ungated** (creator ruling 2026-10-08, TASK 54): no feature lock, no paid tier, no watermark. Money comes from gratuity and merch, not gating. **No licence chatter reaches a user** — no keys, no expiry, no licence UI. Support is email + GitHub issues; an in-app bug reporter is out of product. `IsPremium` gates nothing in v1. **The branding credit is OFF by default.** `MainViewModel.ShowBrandFlash` (internal static, default `false`) is the master switch; `BrandFlashEnabled => ShowBrandFlash && !IsPremium` feeds the presenter. The code is kept, not deleted — creator ruling: *"don't delete it, just make it conditionally displayed."* `BrandFlashOutputTests` opts in by setting the switch before constructing a `MainWindow`; `Credit_IsOffByDefault_EvenOnTheFreeTier` guards the default. If it is ever enabled, the mechanics that matter: - Full-frame neon "made with LlamaCasty!" at a **random position per presentation**, held **2s** (500ms in / 1000ms full / 500ms out), first ~5s after start then every `rand(30s)+30s`. - Published to the frame pump and the preview from the **same** `VideoFrame`, so preview and broadcast cannot drift. Deliberately **not** folded into `SceneGraph.GetBakedBase` — the bake cache compares element ids and would freeze the credit in place. - **App-lifetime cadence:** the presenter is `Start()`ed once in the `MainViewModel` ctor, not per go-live, because go-live and local recording share one `FramePump`. `IsPremium`'s setter therefore restarts the cadence timer when re-enabling (`IsCadenceTimerEnabled` is the test seam). - **It composites into the recording and the stream** (reversed 2026-09-26 — the free tier must be honest advertising). Test arithmetic trap: `Advance(5.1)` in one call blows through the 2s window (it both opens and ages the presentation) — step in 1/30s increments like `BrandFlashOutputTests`. - **A mid-credit downgrade** cuts the advertisement on the next tick: the presenter re-checks its own `Enabled` gate every frame rather than letting the credit finish. Pricing, billing, the Store IAP plan and the rejected alternatives are history → [`decisions.md`](decisions.md). **Monetization awareness (built-in, ungated, free — 2026-09-01; v1 SCOPE per the complete-v1 ruling — build order: capture → report → journey → Alerts):** the app is monetization-aware by design, for **every** creator, free and ungated (the only subscriber swap remains the branding-flash removal above). It has three layers, all free — this is the product's differentiator, not a paid tier: - **Reward-event capture (the data foundation).** The live-chat poll (`YouTubeChatService.Poll`) receives every reward event over `liveChat/messages` and — since TASK 43 (2026-09-24) — **decodes all six types**, not two: `superChatEvent`, `superStickerEvent`, `newSponsorEvent`, `membershipGiftingEvent`, `giftMembershipReceivedEvent`, `memberMilestoneChatEvent` land as `ChatMessage.Kind` (`ChatEventKind`) with their payload fields (display string, sticker description, gift count, gifter name, milestone months); free subscriptions emit NO event (API fact — a sub mention is a plain text row). The remaining half of the original capture plan stays open (TASK 3 item 20): persist each event to the canonical SQLite `RewardEvents` table (broadcastId, type, timestamp, amountMicros, currency, tier...) + `superChatEvents.list` (30-day lookback) backfill — that persistence is what the session report and the journey tracker read. Alerts did not wait on it: the shipped alert path (TASK 43) renders directly from the in-memory `ChatMessage` feed. - **Session / broadcast report.** A session is created at Go Live; the poll + End roll that session's reward totals (new members, Super Chat $, stickers $, gifts, milestones). This is the in-app replacement for the emailed external "stream activity report" — the incumbents (Streamlabs et al.) email near-useless post-stream summaries; we track the same data natively and maintain it. - **Journey tracker (TASK 39, slice 1 shipped 2026-09-22).** A **"YPP"** tab below the Stream Settings pull-out (Live screen right edge) shows the connected creator's position toward each YPP tier threshold. Slice 1 = current-scope data only: `channels.list?mine=true &part=statistics,contentDetails` (subs/views/videos + uploads-playlist id) + `playlistItems.list` for 90-day public uploads. Every refresh appends a `YppSnapshot` to SQLite (schema v10) — the history a later slice needs for velocity/ETA. **The `auditDetails` part is OUT (fixed 2026-09-23):** readding it 403s the WHOLE request — that part alone requires the `youtubepartner-channel-audit` scope no app for normal creators should request (the standing flags sat unreadable since slice 1 shipped; the app's `youtube` + `youtube.force-ssl` scopes were never sufficient for them — the "no re-consent" slice-1 claim was wrong for that part). Channel standing now deep-links to the Earn page; API stats come from `statistics,contentDetails` only. YPP/API failures log the response body's `error.reason`. **YPP thresholds are versioned, date-aware DATA, never constants** — the Tier-2 bar doubles for new applicants on **2027-02-01** (long-form 4,000 → 8,000 qualified hrs / 365d; Shorts 10M → 20M / 90d), while the Tier-1 fan-funding bar (500 subs / 3,000 hrs / 3M Shorts) is unchanged. Hardcoding the pre-2027 numbers would ship the wrong ETA. Watch-hours / Shorts / velocity / ETA are **slice 2**: Analytics-API scopes (`yt-analytics.readonly`, additive `yt-analytics-monetary.readonly`) behind the `IChannelStatsProvider` seam (additive capability, requires an OAuth re-consent). Compliance items no API exposes (2FA, AdSense linkage) are in-app self-reported checkboxes with deep links; channel standing (exposed only via the partner-audit scope) deep-links to the Earn page. Slice 2's once-daily refresh gate + snapshot history were baked into slice 1. ## Auth gates Go Live, but not exploration The app is fully usable without authentication: users can build scenes, add sources, compose previews, and audition the software with zero commitment. But **going live requires authentication** — it's the one capability gated behind YouTube sign-in. The sign-in should never pressure the user ("sign in (optional)", not a modal wall): the two-state top bar shows **Start Stream** (offline) / **End Stream** (live), and the Start Stream dialog hosts the account — a saved session appears as the default with "Change Account"; with none saved, a "Sign in to YouTube" button starts OAuth and the Start button stays disabled until signed in. ## Account assumption (do not build an account setup flow) Connecting uses Google OAuth ("Sign in with Google") to link an **existing** YouTube creator account. ytLlive **never creates or sets up accounts** — that is YouTube's job. If the creator has no YouTube channel, they go to YouTube first. This assumption is explicit and must never be silently replaced by an in-app account-creation step. Zero state = the Start Stream dialog's "Sign in to YouTube" button; going live is unreachable until an account is connected. ## Windows packaging & distribution (direct + Steam; Store dropped 2026-10-22) **The Microsoft Store route is dead** — a brand on the publisher line needs a Company account and a legal entity. Cause of death and the abandoned routes: [`decisions.md`](decisions.md). **The route now:** 1. **Direct download from `llamacasty.com`** — the app is **free forever** (TASK 54), so there is nothing to bill in v1. 2. **Steam when $100 exists** — Steam Direct is $100 per product (recoupable after $1,000 AGR), accepted categories include *Audio/Video Production* / *Player Tools* (OBS Studio is the precedent), the **developer/publisher name is self-chosen** so the brand needs no entity, 5+ screenshots at 1920×1080 + capsule art, a build Valve runs, and a **2-week live page** before release. Valve takes 30%. 3. **Polar is dormant, not deleted-in-principle** — chosen checkout/licence provider if a paid tier ever returns; $50 already paid. `Distribution.md` §4.2/§4.5 holds the design. 4. **The Microsoft Store: not pursued.** The MSIX package stays as a working artifact and is submitted nowhere. History: `TASKS/research-store-certification.md`, `TASKS/task-48-distribution-msix.md`. **Consequences that are architecture, not packaging detail:** - **⛔ The updater is now MISSING, not deleted-by-design.** Velopack and its bootstrap were removed on 2026-10-22 (TASK 48 slice 3) because the Store was going to deliver updates — that premise is gone. An update path must come back, and `ytLive.Tests/StorePackagingTests.cs` (which asserts no updater returns) must be rewritten with the chosen updater rather than left to veto one. - **The MSIX package still builds** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish `Release -r win-x64 --self-contained` into `artifacts/msix-stage`, copy `Packaging/Package.appxmanifest` in as `AppxManifest.xml` + `Packaging/Assets/`, then `makeappx pack` → `artifacts/LlamaCasty.msix` (159 MB, gitignored; `MSIX_PFX` signs it for a local install). The manifest is a **full-trust `packagedClassicApp` at `mediumIL`** with `runFullTrust` + webcam + microphone. `Square310x310Logo`/`Wide150x150Logo` traps are in `MyMistakes.md`. Logos come from the app's own artwork via `scripts/make-store-logos.py`. - **⛔ Bundle ffmpeg — no runtime downloads** (was Store policy 10.2.2; still right for a direct download, and it fixes the 2026-09-01 404). See [`output.md`](output.md) → "FFmpeg locator". - **⛔ 64-bit Windows only (creator ruling 2026-10-09).** Both csproj files set `x64` (the test project too — an AnyCPU test project referencing an x64 app raises MSB3270 and breaks the 0-warning gate). Why: the bundled ffmpeg is the win64 build, ONNX Runtime 1.31 ships no win-x86 native (only win-x64/win-arm64), and a 32-bit address space can't hold the 1080p frame buffers. Windows 11 is 64-bit-only anyway. Public copy says "64-bit Windows 10 or 11". **What does NOT change: the monetization posture.** The app is **free forever with no watermark** (TASK 54, 2026-10-08), so `IsPremium` gates nothing in v1 and the branding flash is OFF by default. `Monetization` above governs. ### ⛔ Durable invariant: full trust, or recording silently breaks MSIX packages run at one of two trust levels. A packaged WPF app declaring `rescap:Capability Name="runFullTrust"` runs at **mediumIL — the same permissions as a standard desktop app, NOT AppContainer** — and may spawn child processes, which is why bundling ffmpeg is legal. Writes to user-profile locations (AppData, Downloads, MyVideos) **pass through unvirtualized at mediumIL**; the package install folder is read-only. **`ViewModels/MainViewModel.Recording.cs` writes recordings to `%USERPROFILE%\Downloads` / `SpecialFolder.MyVideos` (`DefaultRecordFolder()`, `ChooseRecordFolder()` via `Microsoft.Win32.OpenFolderDialog`), and the whole temp-write-then-move lifecycle stays in one directory.** That works *only* because we are full trust. No `broadFileSystemAccess` capability is needed, and none should be added. ⚠️ **If anyone flips the manifest to `appContainer`, `Directory.CreateDirectory` and `File.Move` start resolving to a per-package virtualized location and output vanishes with no error.** A comment at `DefaultRecordFolder()` is owed **when the manifest lands** (TASK 48 item 6) — not before; a comment describing a manifest that doesn't exist is worse than none. ### Signing — do not buy EV Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**. An EV-signed file now accrues reputation exactly like an OV-signed one, so EV ($400+/yr) buys what OV (~$150–300/yr) buys. Signing is **not** instant regardless — a valid cert stops the *malware* warning immediately, but "unrecognized publisher" clears only as SmartScreen accrues reputation from real download volume. Cert validity is capped at **460 days** (recurring line item) and private keys must live on an **HSM or hardware token**. **The 2026-10-22 ruling is to ship unsigned and accept the "unrecognized publisher" warning** until download reputation accrues — buying a certificate is deferred, not rejected. `Distribution.md` §4.3 was corrected 2026-09-27 (it previously recommended EV). ### Distribution and licensing are independent — and the channel decision does not decide licensing A **Store-distributed** app *may* still verify licences with **Polar** (Store policy 10.8.1 explicitly permits a secure third-party purchase API for non-game PC products), so "which channel?" never implied "which billing?" — route B was a real option. The 2026-10-22 reversal proves the point in the other direction: dropping the Store did not by itself restart Polar, because the **2026-10-08 free-forever ruling** (TASK 54) had already removed the paid tier. Polar is therefore *dormant*, ready if a paid tier ever returns, and nothing in v1 depends on it. The lesson worth keeping: the licensing choice rides on a **monetization** decision, not on a distribution decision. ### ⛔ "Only when the user says so" is enforced by Windows, not by us Camera/mic consent is layered: Store policy **10.6** forbids circumventing OS checks; the **Windows desktop-app camera/microphone toggle** (Settings → Privacy & security, since Win10 1903) is the gate; hardware lights and tray indicators are the OS's disclosure mechanism. So **route everything through the standard MF/DShow/WASAPI paths and we never write that certification ourselves** — the OS does it and gets neither our credit nor our blame. ⚠️ **Untested:** whether that toggle reliably gates a **DShow webcam and a capture card**. Microsoft's own support page admits desktop apps "might still be able to access your camera or microphone even when these settings are turned off." This is **not** a certification failure (we declare `webcam` honestly and use supported APIs) but it is a real user-trust issue — so **no privacy-forward marketing copy until it is measured.** Guard note added to `MARCOM.md`. ### ⛔ Durable invariant: chat is rendered, never stored `TASK 49`'s profanity filter is **local, on-device, opt-in, non-persistent**, with a **user-supplied word list — never a hardcoded slur list baked into the binary** (unpleasant artifact, false positives across dialects, extractable). It must **never match the SuperChat amount or any reward field** — those are financial data under policy 10.5.5. The non-persistence half is not just hygiene: it is the load-bearing half of the **11.12 UGC certification answer** (mirrored YouTube chat is moderated at industrial scale upstream; ~1.6B comments removed in Q1 2026 alone, child safety the #2 reason at 124.6M). **No future feature — chat history, moderation logs, analytics, crash payloads — may quietly break that assumption.** If one is ever proposed, the 11.12 position must be re-argued first. ### Minors The **operator is 13+ by construction**, not by promise: LlamaCasty requires a YouTube account to stream, and YouTube's ToS sets the account floor at **13** (regional variants reach 14; 13–17 need parent/guardian permission; under-13s are on supervised/YouTube Kids accounts). So a COPPA theory premised on collecting PI from under-13s has no purchase against the operator. The residual is **viewers** — still possible on supervised family accounts — and it is weak: live chat is restricted there, YouTube moderates before we see it, and we persist nothing. COPPA is very unlikely to bind (not child-directed), but note the enforcement trend (FTC Sept 2025 *Apitor*: operators are responsible for what third-party components collect) — so the answer is a documented inquiry, and ours is "no third-party analytics or ad SDKs, no persisted chat or reward payloads."