docs(commercial): one source of truth for the money — prices, entitlement, channels

The commercial plan was spread over five documents written in five different weeks,
and they contradicted each other: product.md sold the Store, decisions.md called the
Store dead then re-opened, Distribution.md (736 lines) still described Polar as a
download host, MARCOM.md quoted "no subscription" next to a paid tier, and README.md
promised "free forever". An outsider could not tell what ships.

docs/ai/commercial.md is now the only place money is decided: free = whole app at a
921,600 px / 30 fps output profile covering the recording as well as the stream; Pro =
$10/mo or $400 lifetime through Polar, key bound to the buyer's YouTube channel ID via
a claim page, 2 activations, 7–14 day lease with offline grace, fail closed, never
downgrade a running stream; one portable zip + winget primary, Steam and the Store
parked; Azure Trusted Signing (~$9.99/mo), no EV; Founding 200 promo; ~$135/yr costs;
anti-piracy posture and what is permanently rejected.

Everything else now points at it instead of restating it:
- product.md — money sections replaced with pointers; packaging rewritten to
  winget-first, Store parked; signing updated to Trusted Signing.
- decisions.md — graveyard with cause of death for free-forever, the Store ordering,
  the updater, and the 2026-10-30 reversal of the quality-ceiling rejection.
- Distribution.md — rewritten to release mechanics only (artifact, GitHub Releases,
  winget manifest, signing, checklist, takedown): 736 → 120 lines.
- TASKS.md — TASK 54 rewritten, TASK 48's route settled, TASK 10 superseded, five new
  rows (entitlement seam, output-profile gate, licence UI, system messages, packaging).
- README.md — "free forever" replaced with the real split.
- capture.md — obs-backgroundremoval is GPL-3.0 and so licence-INCOMPATIBLE since the
  proprietary EULA; the old "compatible" line was falsified by that change.
- TASKS/task-10, task-48, task-51, task-54 — superseded-banner at the top of each, so
  none of them reads as current policy.
- HANDOFF.md — rewritten: plan settled, next code unit is the entitlement seam.

MARCOM.md and MONETIZATION.md (gitignored) were corrected in place: policy pointer at
the top of each, the flash removed from every live claim, the Polar fee break-even
corrected to ~$900/mo, and the projections flagged as written against a dead model.
This commit is contained in:
2026-10-31 08:32:25 -07:00
parent 90d5140906
commit 7827d239e2
14 changed files with 445 additions and 854 deletions
+102 -718
View File
@@ -1,736 +1,120 @@
# Distribution & IP Protection Plan # Distribution & release mechanics
> 360-degree protection strategy for LlamaCasty (.NET 8 / WPF). How the build becomes a file someone installs. **Policy — prices, entitlement, which channels
exist and why — is [`docs/ai/commercial.md`](docs/ai/commercial.md).** This file is the how.
> ## ⛔ SUPERSEDED IN PART — 2026-09-27 > The previous version of this document was a 736-line Polar/MSIX/obfuscation plan. It was
> > superseded on 2026-10-31; the reasoning that still matters lives in `commercial.md` and
> **Distribution is now the Microsoft Store: MSIX package + Store IAP.** The creator's criteria > `docs/ai/decisions.md`, and the full text is in `git log -- Distribution.md`.
> were *"zero headaches, minimal maintenance (for me) while still providing accountability and
> a reasonably easy upgrade flow"*, and that ruling deleted this document's central premise.
>
> **What still stands below:** §1 (code architecture / IP protection), §5 (obfuscation posture),
> §6 (legal), §7 (hardening) — those are about protecting the *binary*, and an MSIX package is
> still a binary. **§1.2's build-posture ceiling (HARDENED + MOCK_REWARDS, additive-only)
> remains the agreed ceiling.**
>
> **What is dead:**
> - **Polar as the distribution backbone** — the entire section below. Store IAP handles
> checkout, entitlements, refunds, tax, and the customer portal. So does **file hosting**:
> the Store is the delivery mechanism, so "upload `LlamaCasty.exe` to Polar as a File
> Download benefit" (§2) no longer exists as a step.
> - **Code signing as our problem** — MSIX is signed by **Microsoft**. There is no certificate
> to buy, no HSM, no annual renewal. See §4.3, which was already corrected to say this, and
> `TASKS/research-store-certification.md` §2–3.
> - **Velopack / the update URL / the DO droplet** — Store auto-update.
>
> **The authoritative plan is now [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md).**
> Keep this file for the protection sections; do not re-derive a delivery strategy from it.
--- ## 1. The artifact
## Polar.sh as the Distribution Backbone — ⛔ OBSOLETE (2026-09-27) One self-contained single-file exe plus the bundled ffmpeg folder, zipped. No .NET install on the
target machine, no installer framework, no admin rights.
> **Historical record only.** Retained because the fee tables and the reasoning about *why* ```bash
> one-time beat subscription were worth working out. **None of the "How We Use It" rows are dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
> current**, with one exception worth keeping: the Merchant-of-Record point — *someone else -p:PublishSingleFile=true \
> collects and remits VAT/GST* — is still true under Store IAP, because **Microsoft** is now -p:IncludeNativeLibrariesForSelfExtract=true \
> that someone. That fact alone was the strongest argument for the ruling. -p:EnableCompressionInSingleFile=true \
-p:PublishReadyToRun=true
Polar handled everything between "customer wants to pay" and "customer has a working license key." We didn't build any of that. What Polar *was* going to give us:
| Capability | How We Use It |
|------------|---------------|
| **License key management** | Polar generates, validates, expires, rotates, and revokes keys. Activation limits (device caps) are built-in — no custom device registry. |
| **File hosting** (up to 10GB) | Upload `LlamaCasty.exe` (self-contained, ~80-120MB) as a "File Download" benefit. Customers get signed, personal download URLs. SHA-256 checksums included. |
| **Checkout** | Polar's hosted checkout page handles payment. No Stripe integration, no PCI scope, no custom checkout UI. |
| **Customer portal** | Customers self-serve: view/copy license keys, see expiry, deactivate devices, rotate compromised keys. We don't build a portal. |
| **One-time orders** | **Chosen model (2026-09-21):** perpetual license via a one-time Polar order — no subscription. Polar also supports recurring billing if ever needed, but we do not use it. |
| **Merchant of Record** | Polar collects and remits VAT/GST/sales tax globally. We never touch tax compliance. |
| **Webhooks** | Polar notifies our backend on purchase, cancellation, key rotation. Used for optional telemetry (section 6.3). |
**Polar pricing (2026):** 5% + $0.50 per transaction (Starter plan). No monthly fee. ⛔ *No
longer paid to anyone — the equivalent cost is now inside the Store revenue share, whose rate
is unverified (see `TASKS/research-store-certification.md` §11).*
**What Polar did NOT handle:** Obfuscation, code signing, anti-tamper, runtime protection, EULA, DMCA. That's all us — sections 1-3, 5-7 below.
---
## 1. Code Architecture for Protection
### 1.1 Assembly Splitting (est. 2–3 days)
Split the monolith into three assemblies with different protection tiers:
| Assembly | Contents | Protection Level |
|----------|----------|-----------------|
| `LlamaCasty.Core.dll` | Models, DTOs, interfaces, enums, constants | Light obfuscation (symbol renaming only) |
| `LlamaCasty.Services.dll` | YouTube auth, stream service, encoder, audio pipeline, layout store | Full obfuscation + control flow + string encryption |
| `LlamaCasty.exe` | WPF UI, ViewModels, XAML | No obfuscation (XAML constraints) |
**Why:** Obfuscators break WPF XAML/BAML bindings when they rename types referenced in `DataContext`, `Binding Path=`, or `x:Name`. Keeping the UI assembly separate lets you protect the business logic aggressively while leaving the presentation layer safe.
**How:**
- `LlamaCasty.Core` → Class library, `net8.0`, no WPF dependency. Pure C#.
- `LlamaCasty.Services` → Class library, `net8.0`, references Core. No XAML.
- `LlamaCasty` → WPF app, references both. All `MainWindow.xaml`, `Themes/`, ViewModels live here.
### 1.2 Obfuscation-Friendly Patterns (est. 1–2 days, ongoing)
**Do:**
- Use `public`/`internal` properties with `{ get; set; }` for data binding (XAML `Binding Path=StringLiteral` resolves at runtime by string match — obfuscators handle this if configured).
- Use `[Obfuscation(Exclude = true)]` on anything bound to XAML via `Binding`, `CommandParameter`, or `x:Name`.
- Prefer `nameof()` for property names passed to `INotifyPropertyChanged` so renaming works.
- Use `RelayCommand` (already in use) — it takes lambdas, not string names.
**Avoid:**
- `FindName("SomeControl")` in code-behind (breaks if XAML name is mangled).
- `x:Name` references accessed from code-behind (use `Binding` instead).
- `Activator.CreateInstance(Type.GetType("SomeString"))` — obfuscator renames the type string.
- `Assembly.Load("LlamaCasty.Core")` by string — use direct references.
- `[Serializable]` DTOs that get binary-serialized (renamed fields break deserialization).
### 1.3 Strategic Attribute Placement (est. 1 day)
```csharp
// Exclude from obfuscation — XAML binding relies on property name
[Obfuscation(Exclude = true)]
public string RecordFolderDisplay { get; }
// Exclude — DTO serialized to SQLite
[Obfuscation(Exclude = true)]
public class StreamHealth { ... }
// Exclude — ViewModels bound by name in XAML
[Obfuscation(Exclude = true)]
public partial class MainViewModel : ViewModelBase { ... }
// Safe to obfuscate — internal service, no XAML binding
public class FfmpegEncoder { ... }
``` ```
**Rules:** - **`PublishTrimmed` stays OFF.** Trimming breaks WPF's XAML reflection (`Binding`, `DataTemplate`,
- All ViewModels: `[Obfuscation(Exclude = true)]` (XAML `Binding Path=` resolves by name at runtime). `StaticResource`). The exe is therefore fat — expect ~110–150 MB compressed with the runtime
- All Models/DTOs used in SQLite: `[Obfuscation(Exclude = true)]` (column names are strings). inside, and that is the accepted cost of "copy it and run it".
- All public service interfaces: `[Obfuscation(Exclude = true)]` (DI resolution). - **ffmpeg stays outside the exe** — `ffmpeg\` beside it (already the app's locator contract, see
- Internal service implementations: safe to obfuscate. [`output.md`](output.md) → "FFmpeg locator"). Embedding it passes 200 MB and adds an extraction
- Helpers/Converters in XAML: `[Obfuscation(Exclude = true)]` on the class. step for no benefit.
- **WebView2 is the one machine dependency**: the Evergreen runtime ships with Windows 11 and most
Windows 10; a genuinely clean machine needs the bootstrapper once. Say so in the download note.
- **`AssemblyName` is still `ytLive`** → the shipped exe is `ytLive.exe` today. The rename to
`LlamaCasty.exe` is queued (three pack URIs break — see `product.md`), and until it lands every
release note names the real file.
--- **Portable mode.** A `portable.txt` marker beside the exe keeps the layout DB, `Assets` and
settings in `.\data\` instead of `%APPDATA%\ytLlive` (hung off `LayoutPathOverride`). Same binary,
USB-stick behaviour, nothing to uninstall.
## 2. Build Pipeline ## 2. Release
### 2.1 Build Configuration (est. 0.5 day) ```
publish → zip → SHA-256 → GitHub Release → winget manifest PR → update notice
In `ytLive.csproj`:
```xml
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<OutputType>WinExe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode> <!-- 'full' breaks WPF reflection; 'partial' is safe -->
<AssemblyName>LlamaCasty</AssemblyName>
<Version>0.9.0</Version>
<Company>LlamaCasty</Company>
<Product>LlamaCasty</Product>
</PropertyGroup>
``` ```
**Key decisions:** 1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; attach `LlamaCasty-<version>-win-x64.zip`
- `PublishTrimmed` + `TrimMode=partial`: Trims unused BCL code but preserves WPF infrastructure. `full` trimming breaks `Binding`, `DataTemplate`, and XAML reflection. to the **GitHub Release**. Binaries live there, never on the droplet — free CDN, durable URLs,
- `PublishSingleFile`: One `.exe` to distribute. Self-contained = no .NET runtime dependency on user machines. release notes in the same place, and `wingetcreate` reads `releases/latest` natively.
- `win-x64`: Target the dominant platform. ARM64 is a separate RID if needed later. 2. **Record the hash.** `sha256sum` the zip; it goes in the winget manifest and in the release
notes. This is the anti-tamper wall: the official channel delivers exactly this artifact or
fails.
3. **winget.** `wingetcreate update LlamaCasty.LlamaCasty --version <v> --urls <release-url>
--submit` — it opens the PR against `microsoft/winget-pkgs`. Manifest shape:
`InstallerType: portable`, `InstallerSha256`, `NestedInstallerType` where the zip nests the exe.
First submission is `wingetcreate new`; the identifier is `LlamaCasty.LlamaCasty`.
4. **Update notice.** Write the current version into a small version file on the site (or read
`releases/latest` from the GitHub API). The app shows one line — *"v1.4 is out — `winget upgrade
LlamaCasty.LlamaCasty`"*. **Nothing downloads or executes**: that is Store policy 10.2.2 and a
permanent no (see `product.md`).
5. **Optional mirrors.** Scoop (a small JSON manifest hosted in a bucket) and Chocolatey (a nupkg
with a checksum) — ten minutes each, same audience.
6. **MSIX** stays buildable for the parked Store channel: `scripts/package-msix.sh` →
`artifacts/LlamaCasty.msix`, submitted nowhere until the Individual account exists.
### 2.2 Obfuscator Integration (est. 2–3 days) ## 3. Signing
**Recommended: Eazfuscator.NET** (free for projects under $1M revenue, excellent WPF support, .NET 8 compatible). **Azure Trusted Signing, ~$9.99/month** — Microsoft-issued, individuals eligible after identity
verification, and it accrues SmartScreen reputation without buying a certificate.
Alternative commercial: **ArmDot** ($399/yr, better control flow, built-in anti-tamper).
**Integration as post-publish step:**
```powershell ```powershell
# publish.ps1 — called after dotnet publish signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
param([string]$PublishDir = "publish") /dlib "Azure.CodeSigning.Dlib.dll" /dmdf "metadata.json" LlamaCasty.exe
dotnet publish ytLive.csproj -c Release -r win-x64 `
--self-contained true `
-p:PublishSingleFile=true `
-p:PublishTrimmed=true `
-p:TrimMode=partial `
-o $PublishDir
# Obfuscate the Services assembly (core business logic)
eazfuscator.exe "$PublishDir\LlamaCasty.Services.dll" `
--target-framework net8.0 `
--Renaming-mode unprintable `
--Control-flow-obfuscation true `
--String-encryption true `
--Resource-encryption true
# Obfuscate the Core assembly (lighter touch)
eazfuscator.exe "$PublishDir\LlamaCasty.Core.dll" `
--target-framework net8.0 `
--Renaming-mode unprintable
# Do NOT obfuscate LlamaCasty.exe (WPF entry point + XAML)
``` ```
### 2.3 XAML/BAML Handling (est. 1 day research) - **⛔ No EV certificate.** Microsoft removed the EV SmartScreen instant-bypass in March 2024, so
EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys — and a bought cert is a 460-day recurring
**Problem:** WPF compiles XAML into BAML (binary XAML) embedded as resources. Obfuscators that rename types break BAML because it references types by assembly-qualified name. line item whose key must live on an HSM or hardware token.
- **No certificate at all** is the interim state: ship unsigned and accept the "unknown publisher"
**Solution:** line. winget spares the browser prompt, not the publisher line, so signing is what makes the
- Do NOT obfuscate the main `.exe` assembly (contains all BAML). channel feel legitimate — the reason it is in the year-one budget.
- Only obfuscate `Core` and `Services` DLLs (no XAML). - **Signing never clears the warning on day one** — only download volume does. Plan for weeks.
- If an obfuscator must touch the exe: use Eazfuscator.NET's `[Obfuscation(Exclude = true, Feature = "XAML")]` attribute on types referenced in BAML, or configure the obfuscator to skip the `LlamaCasty.g.resources` manifest resource. - Secrets: the Trusted Signing profile lives in CI secrets; **never commit** a `.pfx` or
`metadata.json` with credentials. `MSIX_PFX`/`MSIX_PFX_PASSWORD` are for local test installs only.
**Testing:** After obfuscation, launch the app and verify: - Verify every release: `signtool verify /pa /v <file>`, and confirm the hash in the manifest
- All screens render correctly matches the uploaded zip.
- `Binding` expressions resolve (no blank/missing data)
- `DataContext` inheritance works ## 4. Release checklist
- Storyboards/animations play
- `ContextMenu` items bind correctly - Build clean, 0 warnings (`./scripts/verify.sh`), full test suite green.
- Dialogs open/close properly - `signtool verify /pa` passes on the shipped exe.
- Launch on a clean Windows profile: first-run works, recording writes where it claims, ffmpeg is
### 2.4 CI/CD Pipeline (est. 1 day) found beside the exe, WebView2-loaded UI renders.
- Fresh install through `winget install LlamaCasty.LlamaCasty` on a second machine, then
```yaml `winget upgrade` over the previous version.
# .github/workflows/release.yml - ZIP hash published; manifest PR merged; version notice updated.
steps: - Portable mode: `portable.txt` present → data lands in `.\data\`, deleting the folder deletes the
- uses: actions/checkout@v4 app's footprint.
- uses: actions/setup-dotnet@v4
with: ## 5. Legal and takedown
dotnet-version: 8.0.x
- **`LICENSE`** (EULA) forbids redistribution, resale, reverse-engineering and circumvention, and
- name: Test disclaims warranty. It is what makes a mirror site a licence breach rather than a grey area.
run: dotnet test --no-restore -c Release - **`PRIVACY.md`** must name any third party the app contacts — today that is Google/YouTube,
ffmpeg is local, and **Polar** is added the day licence validation ships.
- name: Publish - **`THIRD-PARTY-NOTICES.txt`** stays shipped and shown in About; the ffmpeg pin stays
run: dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=partial -o publish `lgpl-shared`, and no GPL component may be linked (see `product.md` → "Licensing").
- **DMCA**: statutory damages need a registration ($65), and a takedown needs only the notice —
- name: Obfuscate host, link, good-faith statement, signature. Worth doing once there is something to pirate.
run: | - **What actually protects the product** is in `commercial.md` → "Anti-piracy posture": signed
eazfuscator.exe publish/LlamaCasty.Services.dll --target-framework net8.0 --Renaming-mode unprintable --Control-flow-obfuscation true --String-encryption true hash-pinned delivery, short leases, a free tier good enough that a crack buys nothing, and a
eazfuscator.exe publish/LlamaCasty.Core.dll --target-framework net8.0 --Renaming-mode unprintable licence ID in About. **Not obfuscation, not a native DLL, not hardware locking.**
- name: Sign ## 6. Effort, once
run: |
$cert = Get-Content -Raw -Path secrets/certificate.pfx | ConvertTo-SecureString -AsPlainText -Force | Step | Effort |
Set-AuthenticodeSignature -FilePath publish/LlamaCasty.exe -Certificate $cert -TimestampServer http://timestamp.digicert.com -HashAlgorithm SHA256 |---|---|
| Publish profile + portable mode | 0.5 day |
- name: Upload to Polar | GitHub Release + hash + version notice | 0.5 day |
run: | | First winget manifest (`wingetcreate new`) | 0.5 day, then ~15 min per release |
# Upload the signed .exe to Polar as a file download benefit | Azure Trusted Signing profile + CI step | 0.5 day |
# Update the product's file benefit with the new version | Clean-machine + `winget` smoke test | 0.5 day |
curl -X POST "https://api.polar.sh/v1/products/{product_id}/benefits" \
-H "Authorization: Bearer $POLAR_ACCESS_TOKEN" \
-F "type=file_download" \
-F "file=@publish/LlamaCasty.exe"
```
---
## 3. Obfuscation Strategy
### 3.1 Recommended Obfuscator
**Eazfuscator.NET** (primary):
- Free for projects under $1M revenue — fits solo dev.
- Excellent WPF support: knows to skip BAML resources, handles `INotifyPropertyChanged` patterns.
- .NET 8 support confirmed.
- Symbol renaming (unprintable Unicode), control flow obfuscation, string encryption, resource encryption.
- Simple CLI integration.
**ArmDot** (commercial, $399/yr):
- Better control flow (virtualization of critical methods).
- Anti-tamper and anti-debug built in.
- Better at handling complex WPF scenarios.
**What NOT to use:**
- ConfuserEx — abandoned, doesn't support .NET 8.
- Obfuscar — limited WPF support, BAML breakage issues.
- Dotfuscator Community — too basic, no string encryption.
### 3.2 What to Obfuscate
| Technique | Where | Effect |
|-----------|-------|--------|
| Symbol renaming (unprintable) | `Services.dll`, `Core.dll` | All type/method/field names → Unicode garbage. 10x decompilation difficulty. |
| Control flow obfuscation | `Services.dll` | Flattens `if/else/switch` into state machines. Decompiler produces spaghetti. |
| String encryption | `Services.dll` | All string literals encrypted; decrypted at runtime. Hides API URLs, error messages, config values. |
| Resource encryption | `Services.dll` | Embedded resources encrypted. Prevents extraction of embedded data. |
| Anti-tamper | `Services.dll` | CRC check on method bodies at runtime. Detects binary patching. |
### 3.3 What to Exclude
| Item | Reason |
|------|--------|
| `LlamaCasty.exe` (WPF entry point) | BAML resources break if types are renamed. |
| All ViewModels | `Binding Path=` resolves property names at runtime. |
| All Models/DTOs | SQLite column mapping uses property names as strings. |
| `LayoutStore` (public interface) | Called by ViewModels via interface — renaming breaks DI. |
| All `IValueConverter` implementations | XAML references converters by type name in `StaticResource`. |
| All `MarkupExtension` subclasses | XAML parser resolves by type name. |
| `App.xaml.cs` entry point | Must be discoverable. |
### 3.4 Testing Obfuscated Build (est. 1 day per release)
**Automated checks:**
1. App launches without crash (startup log at `%APPDATA%\ytLlive\startup.log`).
2. All 5 scenes load (Starting/Live/BRB/Chat/Ending).
3. Social bar renders with entries.
4. TRAX button click opens file picker.
5. Recording starts/stops correctly.
6. YouTube OAuth flow completes (if signed in).
7. Settings panel opens from Gear menu.
8. All 247 unit tests pass against the unobfuscated assemblies (tests never run against obfuscated code).
**Manual smoke test:**
- Start a recording, verify file appears in configured folder.
- Open social dialog, add/edit/delete a handle.
- Toggle mic mute, verify meter responds.
- Switch scenes via thumbnail strip.
---
## 4. Distribution Artifacts
### 4.1 Files to Ship
```
LlamaCasty.msix (full-trust x64 package, self-contained, ~159MB)
```
Built by **`scripts/package-msix.sh`** (TASK 48 item 2, shipped 2026-10-22): it publishes
`Release -r win-x64 --self-contained` into `artifacts/msix-stage`, stages
`Packaging/Package.appxmanifest` (as `AppxManifest.xml`) + `Packaging/Assets/`, and packs with
the Windows SDK's `makeappx` → `artifacts/LlamaCasty.msix` (gitignored). Unsigned by default —
**the Store signs submissions**; set `MSIX_PFX`/`MSIX_PFX_PASSWORD` to sign a package for a
local test install. The manifest is a full-trust `packagedClassicApp` at `mediumIL` with
`runFullTrust` + webcam + microphone (see `TASKS/task-48-distribution-msix.md` item 2).
A single-file `.exe` is **no longer the artifact**: the Store needs a package, and the package
carries the runtime (~80-120MB of it) plus the bundled ffmpeg (~135MB of DLLs). No installer
framework and no admin rights — the Store handles install, update and uninstall.
### 4.2 Distribution via Polar — ⛔ OBSOLETE (2026-09-27)
> **Superseded by Microsoft Store MSIX + Store IAP** (ruling in
> `TASKS/task-48-distribution-msix.md` item 0). The Store re-signs, hosts, updates, and takes
> payment — so file hosting, license keys, checkout, the customer portal, device deactivation and
> key rotation are **all** handled by Microsoft and none of it needs building. Retained below as a
> record of the model that was designed and abandoned. ⚠️ **The `$29`/`$49` one-time prices in this
> section are dead** — the settled pricing is **`$10/mo` or `$400` lifetime**, net of a verified
> 15% Store share.
**Product setup on Polar (as designed, never shipped):**
1. Create a product: "LlamaCasty" — **one-time purchase** (perpetual license; $29 founder / $49 list).
2. Add two benefits:
- **License Key** — brandable prefix `LLAMA-****`, activation limit (e.g., 3 devices), optional expiry.
- **File Download** — upload the signed `LlamaCasty.exe` (~80-120MB). Polar generates signed, personal download URLs. SHA-256 checksum included.
**Customer flow:**
1. Customer visits `buy.polar.sh/polar_cl_...` (the Premium URL already in `MainViewModel.PremiumUrl`).
2. Customer pays via Polar checkout.
3. Polar generates a license key + provides a signed download URL.
4. Customer downloads `LlamaCasty.exe`, runs it, enters license key in Settings.
5. App calls `PolarLicenseService.ValidateAsync(key)` → Polar API validates → `IsValid = true`.
6. Premium features unlock locally (persisted in `LayoutStore.Settings`).
**Activation limits:**
- Polar supports `limit_activations` per license key (e.g., 3 devices).
- Client calls `/v1/customer-portal/license-keys/activate` before first validation.
- Polar tracks activations; customer can deactivate via Polar's customer portal.
- No custom device registry needed — Polar handles it.
**Key rotation:**
- If a key is compromised, customer or merchant can rotate it via Polar.
- Previous key stops validating immediately. Status, usage, limits, expiry preserved.
**What the Store does instead:** the customer installs from the Store listing, pays through Store
checkout, and the app reads the entitlement the OS already holds. There is no key to enter, no
activation call, no portal to visit, and no offline-grace period to maintain — which also retires
the whole "network flaky → app thinks I'm expired" bug class in §4.5.
### 4.3 Code Signing
> ⚠️ **The cert choice below is a live decision, and the old plan's EV recommendation is
> dead.** 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 paying
> $400+/yr buys precisely what paying ~$150 buys. **Do not buy EV.**
>
> **But the more interesting finding is that you may not need a certificate at all.** Three
> costs, current as of 2026-09-27:
>
> | Route | Cert/yr | SmartScreen | Note |
> |---|---|---|---|
> | **Microsoft Store (MSIX)** | **$0** — Microsoft re-signs | **No warning** | Requires MSIX packaging; Store review; public listing |
> | **Microsoft Store (EXE/MSI)** | $120–300 — *your* cert | Warning ramp | Store does **not** re-sign; silent install mandatory. Strictly dominated — cert required anyway |
> | **Direct + OV cert** | $150–300 | Warning ramp | Worldwide availability |
> | **Direct + Azure Artifact Signing** | ~$9.99/mo ≈ $120 | Warning ramp | **Individuals: USA/Canada only.** Identity validation required |
>
> Signing does **not** clear the warning on day one either way: a valid cert stops the
> *malware* warning immediately, but "unrecognized publisher" clears as SmartScreen accrues
> reputation from real download volume. Budget cert validity at **460 days** (CA/B Forum
> CSC-31) — this is a recurring line item, not a one-time purchase — and note that private
> keys must sit on an **HSM or hardware token**.
>
> **The full comparison, the four real combinations, and the policy analysis live in
> [`TASKS/research-store-certification.md`](TASKS/research-store-certification.md) §2–3.
> The executable checklist is [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md),
> and the route decision is the creator's.**
**Certificate:** Buy an **OV (Organization Validation)** code-signing certificate from DigiCert
or Sectigo (~$150–300/year), or an **Azure Artifact Signing** profile (~$9.99/month,
USA/Canada individuals only) if the geography allows. ⛔ **Not EV** — no SmartScreen benefit
since March 2024. **Or buy nothing at all** if the route is the Microsoft Store.
**Signing process:**
```powershell
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
"path\to\certificate.pfx",
"password",
[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
)
Set-AuthenticodeSignature -FilePath "LlamaCasty.exe" `
-Certificate $cert `
-TimestampServer "http://timestamp.digicert.com" `
-HashAlgorithm SHA256
```
**Storage:** Certificate `.pfx` file stored in GitHub Secrets (or Azure DevOps secure files). Never committed to the repo.
### 4.4 Update Mechanism — ⛔ OBSOLETE (2026-09-27)
> **Superseded by Store updates.** Microsoft delivers and updates the app from the listing, so
> there is nothing to check and no manifest to host. The Velopack updater that used to sit in
> `App.xaml.cs` **downloaded *and executed***, which Store policy **10.2.2** does not permit — its
> removal was a **compliance action**, not optional cleanup, and it is **done** (TASK 48 slice 3,
> 2026-10-22: the package reference and the bootstrap are gone; `ytLive.Tests/StorePackagingTests.cs`
> guards it). The Polar-native design below is retained as a record of the approach that was
> rejected; it is not an option.
**Polar-native approach (no custom infrastructure):**
```csharp
// In Services.dll (obfuscated)
public class UpdateCheckService
{
// Polar's customer portal API — we check if a newer file is available
// by validating the license key and checking the benefit's file checksum
private const string ValidateUrl = "https://api.polar.sh/v1/customer-portal/license-keys/validate";
public async Task<UpdateInfo?> CheckForUpdateAsync(string currentVersion, string licenseKey)
{
// Validate the key — Polar returns the current benefit state
// If the file benefit's checksum changed, a new version is available
// The download URL is Polar's signed URL — we never hardcode it
// ... implementation in PolarLicenseService ...
}
}
```
**Design:**
- App validates license key on startup (already implemented in `PolarLicenseService`).
- Polar's validation response includes benefit metadata — if the file benefit's SHA-256 changed, a new version exists.
- App prompts user to download from Polar's customer portal (or provides the signed URL).
- No custom update manifest, no self-hosted endpoints, no signing key infrastructure.
- Version obsolescence: Polar can disable old file benefits, making old versions unsupported.
**Alternative (if Polar's API doesn't expose file version metadata):**
- Host a lightweight `version.json` on GitHub Releases (public, no secrets).
- App checks GitHub Releases API for latest version.
- Download link points to Polar's customer portal (not direct download).
- This adds one external dependency (GitHub) but keeps Polar as the source of truth.
### 4.5 Licensing Logic — ⛔ BEING DELETED (2026-09-27)
> ⚠️ **"Already implemented" was true as of 2026-09-21 and is no longer.** `PolarLicenseService`
> and `Helpers/PolarLicense.cs` are being **removed** as part of the TASK 10 teardown (TASK 48 item 0).
> Store IAP replaces the whole validation hop: `IsPremium` is derived from the **OS-supplied Store
> entitlement**, so there is no network call, no key, and — importantly — **no 14-day offline-grace
> machine to protect**, which retires the entire "network flaky → app thinks I'm expired" bug class.
> What survives is only `IsPremium` → `BrandFlashEnabled = !value`. Retained below as the record of
> what shipped and why it no longer applies.
```csharp
// Services/PolarLicenseService.cs — shipped under Polar; being deleted
// Calls Polar's customer-portal validation endpoint (no auth required)
// Survives obfuscation — property names excluded via [Obfuscation(Exclude = true)]
[Obfuscation(Exclude = true)]
public class LicenseValidationResult
{
public bool IsValid { get; set; }
public string? Status { get; set; } // "granted", "revoked", "disabled"
public DateTime? ExpiresAt { get; set; }
public string? Error { get; set; }
}
```
**What shipped under Polar (now being deleted):**
- `PolarLicenseService` validated keys against the Polar API.
- `OrganizationId` was hardcoded.
- License state persisted in `LayoutStore.Settings` (SQLite) with an offline grace period.
- `PremiumUrl` pointed to Polar checkout.
**What survives obfuscation:** The validation logic in `Services.dll` is obfuscated. An attacker who decompiles the exe sees only the UI calling an interface — the actual Polar API call is in the obfuscated DLL.
---
## 5. Runtime Protection
### 5.1 Anti-Debugging (est. 1–2 days)
```csharp
// In Services.dll (obfuscated)
internal static class AntiTamper
{
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool IsDebuggerPresent();
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool CheckRemoteDebuggerPresent(IntPtr hProcess, ref bool isDebuggerPresent);
internal static void Validate()
{
if (IsDebuggerPresent())
Environment.FailFast("Tamper detected.");
bool debuggerPresent = false;
CheckRemoteDebuggerPresent(IntPtr.Zero, ref debuggerPresent);
if (debuggerPresent)
Environment.FailFast("Tamper detected.");
// Timing check: if a method takes >10x expected time, a debugger is stepping
var sw = System.Diagnostics.Stopwatch.StartNew();
SomeFrequentlyCalledMethod();
sw.Stop();
if (sw.ElapsedMilliseconds > 100)
Environment.FailFast("Tamper detected.");
}
}
```
**Trade-offs:**
- `IsDebuggerPresent()` is trivially patched (NOP the call). Useful against casual inspection, not determined attackers.
- Timing checks can false-positive on slow machines. Use conservative thresholds.
- `Environment.FailFast()` terminates the process immediately — no data corruption, no recovery.
### 5.2 Anti-Tampering (est. 1 day)
```csharp
// Method body CRC check — runs at startup
// Obfuscator can inject this automatically (ArmDot)
internal static void VerifyMethodBodies()
{
var assembly = Assembly.GetExecutingAssembly();
var location = assembly.Location;
var bytes = File.ReadAllBytes(location);
// Check known CRC offsets (computed at build time)
foreach (var (offset, expectedCrc) in KnownChecksums)
{
var actualCrc = Crc32.Compute(bytes, offset, MethodBodyLength);
if (actualCrc != expectedCrc)
Environment.FailFast("Binary integrity check failed.");
}
}
```
### 5.3 Environment Validation (est. 0.5 day)
```csharp
// Detect common analysis environments — NOT bulletproof, raises the bar
internal static bool IsAnalysisEnvironment()
{
var suspiciousProcesses = new[] { "dnSpy", "ILSpy", "x64dbg", "x32dbg", "ollydbg", "procmon", "wireshark" };
var running = System.Diagnostics.Process.GetProcesses()
.Select(p => p.ProcessName.ToLowerInvariant());
if (running.Any(p => suspiciousProcesses.Contains(p)))
return true;
var vmIndicators = new[] { "VMware", "VirtualBox", "Hyper-V", "Sandboxie" };
var wmi = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem");
foreach (var obj in wmi.Get())
{
var manufacturer = obj["Manufacturer"]?.ToString() ?? "";
if (vmIndicators.Any(v => manufacturer.Contains(v, StringComparison.OrdinalIgnoreCase)))
return true;
}
return false;
}
```
**Privacy note:** Environment checks that query WMI or enumerate processes require the app to have no network exfiltration of this data. Log locally only. Do NOT send "user is in a VM" telemetry — that's a privacy violation and potential legal issue.
### 5.4 Preventing Memory Dumps (est. 0.5 day, limited effectiveness)
```csharp
internal static void ProtectSensitiveMemory()
{
// Encrypt license keys in memory when not in use
// Zero memory after use
// Use SecureString for transient secrets
}
```
**Reality check:** Against a determined attacker with admin rights, memory protection is bypassable. The value is in raising the bar, not creating an impenetrable wall. Focus effort on:
1. String encryption (hides API endpoints, keys, config).
2. Control flow obfuscation (makes logic hard to follow).
3. Polar server-side validation (license checks hit Polar's API, not our code).
### 5.5 Secrets Management
| Secret | Storage | Notes |
|--------|---------|-------|
| YouTube OAuth ClientSecret | Never in binary. Server-side only. | Client uses OAuth device flow; the secret lives on your auth server. |
| Code-signing certificate | CI/CD secrets only. | Never in source. |
| Polar access token (for uploads) | CI/CD secrets only. | Used in pipeline to upload new .exe to Polar. |
| User tokens (DPAPI) | `%APPDATA%\ytLlive\ytLlive.auth` | OS-level encryption. Never sent anywhere. |
**Principle:** The client binary contains zero secrets that matter. Polar handles license validation server-side. The binary is a consumer of Polar's API, not a holder of credentials. The `OrganizationId` in `PolarLicenseService` is not a secret — it's required by Polar's public validation endpoint.
---
## 6. Legal and Operational Measures
### 6.1 EULA (est. 1 day, lawyer review recommended)
Key clauses:
```
1. LICENSE GRANT: Limited, non-exclusive, non-transferable license to use the software.
2. RESTRICTIONS: No reverse engineering, decompilation, disassembly, or circumvention
of technical protection measures. No redistribution, sublicensing, or rental.
3. INTELLECTUAL PROPERTY: All rights reserved. The software is protected by copyright
law and international treaties.
4. TERMINATION: License terminates automatically upon breach. Upon termination,
you must destroy all copies.
5. DISCLAIMER: Software provided "AS IS" without warranty.
6. LIMITATION OF LIABILITY: Not liable for damages arising from use.
7. GOVERNING LAW: [Your jurisdiction].
```
**Enforceability:** EULAs that prohibit reverse engineering have been upheld in the US (DMCA §1201) and EU (Copyright Directive Art. 6). They're less enforceable in some jurisdictions (e.g., Germany has compulsory decompilation rights under §69e UrhG for interoperability). A lawyer should review for your target markets.
### 6.2 DMCA / Takedown
1. **Register copyright** with the US Copyright Office ($65 per work). Required for statutory damages in infringement suits.
2. **Monitor** GitHub, torrent sites, and software sharing sites for pirated copies. Set up Google Alerts for "LlamaCasty download", "LlamaCasty crack".
3. **DMCA takedown** to GitHub/hosting provider when found. Template:
```
Subject: DMCA Takedown Notice — Copyright Infringement
I am the copyright owner of the software "LlamaCasty".
The following repository contains unauthorized copies:
[URL]
I have a good faith belief that the use is not authorized.
I swear under penalty of perjury that this information is accurate.
```
4. **DMCA to Google** to delist pirate download pages.
### 6.3 Telemetry for Unauthorized Use (est. 0.5 day, optional)
```csharp
// Optional: anonymized heartbeat that detects unauthorized distribution
// Only send: installation GUID (random, stored locally), version, license tier
// DO NOT send: file paths, user names, screen content, keystrokes
internal static async Task SendHeartbeatAsync()
{
var id = GetOrCreateInstallationId(); // Random GUID, stored in Settings
var version = Assembly.GetExecutingAssembly().GetName().Version;
// POST to your API — response can include "deactivate" flag
}
```
**Privacy:** Heartbeat must be disclosed in the EULA and privacy policy. Must not collect PII. Must be opt-out-able in EU (GDPR). Consider whether this is worth the complexity for a solo dev — Polar already tracks validation counts per key, which gives you usage data without custom telemetry.
---
## 7. Fallback and Recovery
### 7.1 If Protection Is Bypassed
**Tier 1 — Casual piracy (someone shares the .exe):**
- Monitor download sites, issue DMCA takedowns.
- Polar can disable the file benefit — old download URLs stop working.
- Push a new version to Polar; old versions become obsolete.
- No kill-switch needed — version obsolescence is sufficient.
**Tier 2 — Cracked version distributed (license check bypassed):**
- Revoke the compromised license key via Polar dashboard (or API: `POST /v1/license-keys/{id}/revoke`).
- Rotate the key — previous key stops validating immediately.
- Push emergency update with new validation logic.
- The cracked version is now outdated; Polar's API rejects old keys.
**Tier 3 — Full decompilation and source reconstruction:**
- This means obfuscation failed. Audit your obfuscation settings.
- Switch from Eazfuscator to ArmDot (virtualization is harder to reverse).
- Add server-side validation for critical features (license, updates).
- Accept that a determined attacker with enough time will always win — focus on making it not worth their time.
### 7.2 Emergency Update Push
**Via Polar (no custom infrastructure):**
1. Upload new `.exe` to Polar dashboard as updated file benefit.
2. Disable old file benefit — existing customers lose access to old download URL.
3. Customers with valid license keys see the new version in their Polar purchase page.
4. Optional: Use Polar webhook to notify a lightweight endpoint → push in-app notification.
**Via GitHub Releases (fallback):**
1. Create a new release on GitHub with the updated `.exe`.
2. App checks GitHub Releases API for latest version (if Polar's file version metadata isn't exposed).
3. Download link points to Polar's customer portal (not direct GitHub download).
### 7.3 Kill-Switch Ethics
A kill-switch is legally and ethically complex:
- **Legal:** You can deactivate your own software per your EULA. Users agreed to it.
- **Ethical:** Don't deactivate without warning. Give 30 days notice. Provide data export.
- **Practical:** A kill-switch that bricks the app without notice will generate bad press and potential legal action.
- **Recommendation:** Use Polar's key revocation + version obsolescence instead of kill-switches. Let the version go stale rather than actively disabling it.
---
## Summary: Feasibility Assessment
| Step | Time | Difficulty | Solo Dev? | Polar Handles? |
|------|------|------------|-----------|----------------|
| Assembly split | 2–3 days | Medium | Yes | No |
| Obfuscation-friendly patterns | 1–2 days | Low | Yes | No |
| Attribute placement | 1 day | Low | Yes | No |
| Build pipeline | 1 day | Medium | Yes | No |
| Obfuscator integration | 2–3 days | Medium | Yes | No |
| XAML/BAML testing | 1 day | Medium | Yes | No |
| CI/CD pipeline | 1 day | Medium | Yes | No |
| Code signing setup | 0.5 day | Low | Yes | No |
| License key setup (Polar) | 0.5 day | Low | Yes | **Yes** |
| File hosting (Polar) | 0.5 day | Low | Yes | **Yes** |
| Checkout (Polar) | 0 | None | Yes | **Yes** |
| Customer portal (Polar) | 0 | None | Yes | **Yes** |
| Activation limits (Polar) | 0.5 day | Low | Yes | **Yes** |
| Key rotation/revocation (Polar) | 0 | None | Yes | **Yes** |
| Update mechanism | 1 day | Medium | Yes | Partial |
| Anti-debug/anti-tamper | 1–2 days | Medium | Yes | No |
| Environment validation | 0.5 day | Low | Yes | No |
| EULA (draft + review) | 1–2 days | Low | Lawyer recommended | No |
| DMCA preparation | 0.5 day | Low | Yes | No |
| **Total** | **~12–18 days** | | **Yes** | **~5 days saved** |
**Bottom line:** Polar eliminates ~5 days of custom infrastructure (licensing backend, checkout, customer portal, device management, tax compliance, file hosting). What remains is purely our protection layer (obfuscation, code signing, anti-tamper) and legal measures.
The most cost-effective approach for a solo dev:
1. **Do (core):** Assembly split + Eazfuscator (Services.dll only) + code signing + Polar license keys + Polar file hosting. Stops 95% of casual piracy. (~7 days)
2. **Do (hardening):** Anti-debug checks + Polar activation limits (device caps). Adds another layer. (~3 days)
3. **Skip (for now):** Environment VM detection, memory protection, complex kill-switches. The cost/benefit doesn't work at indie scale. These are for enterprise or high-value targets.
The determined reverse engineer with IDA Pro and unlimited time will always win. The goal is to make the cost of cracking exceed the value of the software — for a solo dev product, that bar is low. Focus on making a great product that people want to pay for.
+26 -30
View File
@@ -1,40 +1,36 @@
# HANDOFF — 2026-10-30 (scoreboard built to the polling policy; two wiring items left) # HANDOFF — 2026-10-31 (commercial plan settled in docs; next code unit is the entitlement seam)
## State ## State
- Branch `main`. The audio dock is shipped and pushed. **The scoreboard is built** in ten units, - Branch `main`. Pushed through `c72b61b`; **`90d5140` + this commit are local only** (push at a
local commits only: tallies (`Models/ScoreboardState`, session-scoped), lifecycle checkpoint, not per commit).
(`ScoreboardRecorder`: start clears, End freezes), layout (`Models/ScoreboardLayout`: coupled - **Commercial plan is settled and written down**: freemium (free = whole app at a 921,600 px /
edges resolved at render time, shrink-then-wrap, never scroll), renderer 30 fps profile, stream **and** recording), Pro = $10/mo or $400 lifetime through **Polar** (key
(`Services/Compositor/ScoreboardRenderer`: plate + text colour + opacity + 14–24px, cached), bound to the buyer's YouTube channel ID via a claim page, 2 activations, 7–14 day lease), one
tile selection (`Models/ScoreboardTiles`, opt-in, money per currency), the cached presenter, the portable zip + **winget** as the primary channel, **Steam and the Store parked**, Azure Trusted
layer itself (`SourceType.Scoreboard`, strip geometry, resolver case), the properties panel Signing, Founding 200 promo. New single source of truth: [`docs/ai/commercial.md`](docs/ai/commercial.md).
(eight fact switches + colours + plate + size, theme widgets only), the Analytics scope - Docs consolidated this unit: `product.md` (money section → pointer), `decisions.md` (banners +
(`yt-analytics.readonly`, incremental, `HasAnalyticsScope`), and the polling policy history), `index.md`, `ai.md`, `README.md`, `Distribution.md` (rewritten to release mechanics,
(`Models/ScoreboardCadence`). 736 → 120 lines), `TASKS.md` (five new rows), `capture.md` (GPL line falsified by the proprietary
- Fixed on the way: the chat feed now reaches the tallies (the recorder had no caller). EULA), plus gitignored `MARCOM.md` / `MONETIZATION.md`. Site repo: `AGENTS.md`, `README.md`,
`build-this.md`.
- Scoreboard: built, unchanged from the last handoff (HTTP providers + goal inputs still missing).
## Next action ## Next action
1. **HTTP providers** behind the cadence policy: watch hours (`reports.query`, 1. **Entitlement seam** — `IEntitlement` over Polar activate/validate/deactivate, channel-ID match,
`metrics=estimatedMinutesWatched`) and subscriber count (`channels.list?mine=true&part=statistics`), 2 activations, lease + offline grace, `--activate LCYT-…` CLI, fail closed. One Good Dog test.
both behind an interface with a fake for tests, plus the timer that calls 2. Then the **OutputProfile gate** (one profile consumed by both the stream encoder and the
`ScoreboardCadence.Due(...)` and writes the values into `ScoreboardTiles`. recorder; greyed tier entries; never downgrade mid-stream), the licence UI, and system messages.
2. **Goal-target inputs** in the properties panel (the tiles read `SubscriberGoal`/`WatchHourGoal` 3. Scoreboard HTTP providers + goal inputs whenever the creator queues them.
but nothing sets them yet).
3. **On-screen check** of the strip: one line at 24px, plate/opacity, and the edge swap against the
social bar.
## Landmines ## Landmines
- **Not every hex is a theme colour.** `BorderColorPalette` and the chat/socials config defaults - **Nothing may gate a feature.** The only paid delta is the output profile. `ShowBrandFlash` stays
are USER DATA — they keep their literals. On-stream graphics now come from `OutputPalette`, `false` and entitlement must never enable it.
never from a literal or from app resources (the pump thread must not touch them). - **The site publishes the model, never the numbers** — and its CTA ("Coming soon to the Microsoft
- A red settings control is a bug now — red means live/record. Store") stays until the creator says the download exists.
- **No self-updater, by policy**: `winget upgrade` + a version banner. `StorePackagingTests` asserts it.
- Pre-existing: the **full test suite hangs** (WASAPI teardown). Per-class runs are the way. - Pre-existing: the **full test suite hangs** (WASAPI teardown). Per-class runs are the way.
- Not every hex is a theme colour — `BorderColorPalette`/chat defaults are USER DATA; on-stream
## Pending graphics come from `OutputPalette`. A red settings control is still a bug.
- **Launch and eyeball**: fonts render (pack-URI resolution cannot be proven in the test host),
the blue accent, blue-staged / red-live tiles, and mono telemetry.
- Vertical-tier camera check; webcam-cadence take in the failing geometry.
+15 -8
View File
@@ -36,13 +36,18 @@ setup — they need one that's ready for *theirs*. So I built LlamaCasty around
The general-purpose tools are the right choice when you need everything. LlamaCasty is for when you The general-purpose tools are the right choice when you need everything. LlamaCasty is for when you
just want to get your show on air. just want to get your show on air.
## Free, and staying that way ## Free, and the paid edition
LlamaCasty is free forever: no watermark, no branding on your stream, no subscription, no The free version is the whole app, not a trial: every feature, every scene, every overlay, no
LlamaCasty account. It is commercial software (see [License](#license)) — try it, stream with it, watermark, no LlamaCasty account, and nothing in your stream advertising us. It stays free.
experiment with it. If it earns a spot in your setup and you'd like to say thanks, there's a
coffee jar and a merch store linked from https://llamacasty.com. Completely optional, never A paid edition exists for creators who need more output quality — free streams and records at up to
expected, and the app will never ask. 720p30, and Pro unlocks the higher tiers. **No feature is ever gated behind it.** The app shows the
current price at the point of sale; the website deliberately does not.
If it earns a spot in your setup and you'd like to say thanks, there's a coffee jar and a merch
store linked from https://llamacasty.com. Completely optional, never expected, and the app will
never ask.
## How it's built ## How it's built
@@ -96,7 +101,7 @@ What v1 ships: the five-scene control surface, webcam + screen capture, media +
sources, chat on stream, the social bar, mic/desktop audio with auto-ducking and TRAX background sources, chat on stream, the social bar, mic/desktop audio with auto-ducking and TRAX background
music, local recording, quality tiers including vertical 9:16, one-click go-live + scheduling, music, local recording, quality tiers including vertical 9:16, one-click go-live + scheduling,
stream resilience, monetization awareness (YPP tracker), animated alerts, and webcam background stream resilience, monetization awareness (YPP tracker), animated alerts, and webcam background
removal and blur — all free and ungated. removal and blur — every feature in the free tier, with a paid edition that buys output quality.
## Structure ## Structure
@@ -118,7 +123,9 @@ queue; `HANDOFF.md` is the current state. Each code directory carries an `index.
## License ## License
LlamaCasty is **proprietary commercial software** — see [`LICENSE`](LICENSE) for the full End User LlamaCasty is **proprietary commercial software** — see [`LICENSE`](LICENSE) for the full End User
License Agreement. The free tier streams at up to **720p30**; a paid licence unlocks 1080p. License Agreement. The free tier streams **and records** at an output profile of up to **921,600
pixels at 30 fps** (720p30 landscape, 720×1280 vertical); a paid licence unlocks every shipped tier,
including 1080p60. Prices, entitlement and channels: `docs/ai/commercial.md`.
Earlier development builds were published under GPL-3.0 and remain covered by that licence for the Earlier development builds were published under GPL-3.0 and remain covered by that licence for the
versions that shipped under it; nothing here revokes that. versions that shipped under it; nothing here revokes that.
+8 -3
View File
@@ -32,13 +32,18 @@ renderer mirrors `Services/ChatBoxRenderer.cs`. One Good Dog test.
| 49 | ☐ | Chat profanity filter: local, opt-in, **non-persistent**, user-supplied word list | [`task-49`](TASKS/task-49-chat-profanity-filter.md) | | 49 | ☐ | Chat profanity filter: local, opt-in, **non-persistent**, user-supplied word list | [`task-49`](TASKS/task-49-chat-profanity-filter.md) |
| 50 | ☐ | One-button publish of the local recording (resumable upload) | [`task-50`](TASKS/task-50-publish-recording.md) | | 50 | ☐ | One-button publish of the local recording (resumable upload) | [`task-50`](TASKS/task-50-publish-recording.md) |
| 53 | ☐ | Output-format contract: pin every tier with a geometry test, prove 9:16 end-to-end, recorded file matches output | [`task-53`](TASKS/task-53-output-formats.md) | | 53 | ☐ | Output-format contract: pin every tier with a geometry test, prove 9:16 end-to-end, recorded file matches output | [`task-53`](TASKS/task-53-output-formats.md) |
| 54 | 🔶 | Licensing posture changed 2026-10-30: **freemium** (720p30 free, $10/mo or $400 lifetime) — EULA + privacy shipped; site copy still says free forever, entitlement/keys not built | [`task-54`](TASKS/task-54-marcom-free-merch.md) | | 54 | 🔶 | Freemium: EULA + privacy shipped; **no licence path built** — needs the entitlement seam, the output-profile cap and the licence UI below; site copy still says free forever | [`task-54`](TASKS/task-54-marcom-free-merch.md) |
| — | ☐ | **Entitlement seam** — `IEntitlement` over Polar activate/validate/deactivate, channel-ID match, 2 activations, 7–14 day lease, `--activate` CLI | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **OutputProfile gate** — ≤921,600 px / ≤30 fps for stream **and** recording, greyed tier entries, live re-resolve, never mid-stream | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **Licence UI** — claim page, four states, 7/1/0-day notices, portal link, licence ID in About | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **System messages** — `SystemMessage` + `MessageCenter`, strip above the dock, dedupe by key, short history, never composited | [`ui-surface`](docs/ai/ui-surface.md) |
| — | ☐ | **Packaging** — portable zip, GitHub Release, winget manifest, Azure Trusted Signing | [`Distribution.md`](Distribution.md) |
| 9·5 | ☐ | Webcam identity key reconciliation | [`task-09`](TASKS/task-09-live-stream-management.md) | | 9·5 | ☐ | Webcam identity key reconciliation | [`task-09`](TASKS/task-09-live-stream-management.md) |
| 14 | 🔶 | Creator feedback batch — branch 3+ | [`task-14`](TASKS/task-14-creator-feedback-batch.md) | | 14 | 🔶 | Creator feedback batch — branch 3+ | [`task-14`](TASKS/task-14-creator-feedback-batch.md) |
| — | ☐ | Small: `StreamConfig` defaults (`TargetBitrate=6000`, `1920x1080`) are stale — the dropdown drives `StreamHealth` | this line | | — | ☐ | Small: `StreamConfig` defaults (`TargetBitrate=6000`, `1920x1080`) are stale — the dropdown drives `StreamHealth` | this line |
| 36 | ☐ | **Gold pass** — the v1 gate, always the last unit (see 1.0 gates) | [`task-36`](TASKS/task-36-gold-pass.md) | | 36 | ☐ | **Gold pass** — the v1 gate, always the last unit (see 1.0 gates) | [`task-36`](TASKS/task-36-gold-pass.md) |
| 48 | ⏸ | Distribution & packaging — deferred to near-v1 (creator ruling 2026-10-26); route contradiction settled in one sweep then | [`task-48`](TASKS/task-48-distribution-msix.md) | | 48 | ⏸ | Distribution & packaging — route settled 2026-10-31 (winget/portable first; Steam + Store parked). Build steps: [`Distribution.md`](Distribution.md); starts after the entitlement seam | [`task-48`](TASKS/task-48-distribution-msix.md) |
| 10 | ❌ | Retired with free-forever; a Polar teardown runs only if a paid tier ever returns | [`task-10`](TASKS/task-10-monetization.md) | | 10 | ❌ | Retired 2026-10-08; superseded by the TASK 54 rows above — the Polar design it described is rebuilt as a lease (`commercial.md`), not revived as written | [`task-10`](TASKS/task-10-monetization.md) |
## Pending the creator's hands (not programming) ## Pending the creator's hands (not programming)
+6
View File
@@ -1,5 +1,11 @@
# TASK 10 — Monetization: watermark-only paid delta (branding flash) # TASK 10 — Monetization: watermark-only paid delta (branding flash)
> ⛔ **RETIRED — 2026-10-30.** Both halves of this file are dead: the watermark/branding-flash
> delta (retired 2026-10-30) and Store IAP as the billing provider (Store is parked, third channel,
> 2026-10-31). The paid delta is **output quality** now, sold through **Polar**. Live policy:
> [`docs/ai/commercial.md`](../docs/ai/commercial.md); cause of death:
> [`docs/ai/decisions.md`](../docs/ai/decisions.md). Nothing here is a build instruction.
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. > Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
**Goal:** a **paid tier that removes the branding flash and nothing else.** Every feature is free **Goal:** a **paid tier that removes the branding flash and nothing else.** Every feature is free
+7
View File
@@ -1,5 +1,12 @@
# TASK 48 — Distribution & packaging: route decision, MSIX, signing # TASK 48 — Distribution & packaging: route decision, MSIX, signing
> ⛔ **THE ROUTE IN THIS FILE IS SUPERSEDED — 2026-10-31.** Distribution is **direct download from
> `llamacasty.com` (GitHub Releases) + winget primary**; **Steam and the Microsoft Store are parked**,
> third and fourth. Store IAP is not the billing path — **Polar** is, and the paid delta is output
> quality. Live policy: [`docs/ai/commercial.md`](../docs/ai/commercial.md); mechanics:
> [`Distribution.md`](../Distribution.md); cause of death: [`docs/ai/decisions.md`](../docs/ai/decisions.md).
> The MSIX build steps below are still accurate and remain the parked Store channel's artifact.
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. > Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
> **Research: [`TASKS/research-store-certification.md`](research-store-certification.md)** — the "why". > **Research: [`TASKS/research-store-certification.md`](research-store-certification.md)** — the "why".
> Carved out of TASK 36 item 6 (release engineering) on 2026-09-27 so distribution has one > Carved out of TASK 36 item 6 (release engineering) on 2026-09-27 so distribution has one
+4 -2
View File
@@ -39,8 +39,10 @@ delivery shape changed:
same posture as the ffmpeg bundling in TASK 48 item 1.) same posture as the ffmpeg bundling in TASK 48 item 1.)
2. **No hardware requirement.** CPU path must work; DirectML is an optional accelerator. 2. **No hardware requirement.** CPU path must work; DirectML is an optional accelerator.
3. **Per-element (Meld-style), not global.** Each webcam element carries its own effect. 3. **Per-element (Meld-style), not global.** Each webcam element carries its own effect.
4. **No feature gating.** Everything is free (free-forever ruling 2026-10-08, TASK 54 — the old 4. **No feature gating.** Background effects are in the free tier (2026-10-31 freemium keeps every
"only the branding flash is paid" line here was superseded). feature free; the paid delta is output quality only — `docs/ai/commercial.md`). The old "only the
branding flash is paid" line here was superseded in 2026-10-08, and the flash delta itself died
on 2026-10-30.
5. **Scene change / webcam add/remove must not crash or stall the pump** — a failing model 5. **Scene change / webcam add/remove must not crash or stall the pump** — a failing model
run skips to passthrough, never drops the layer. run skips to passthrough, never drops the layer.
+8
View File
@@ -1,5 +1,13 @@
# TASK 54 — Marcom round: free-forever positioning + merch (queued 2026-10-08) # TASK 54 — Marcom round: free-forever positioning + merch (queued 2026-10-08)
> ⛔ **THE FREE-FOREVER RULING IN THIS FILE IS SUPERSEDED — 2026-10-30.** The app is **freemium**:
> every feature is still free, and a paid edition exists for higher output quality (free is capped
> at a 921,600 px / 30 fps profile, stream **and** recording). Live policy:
> [`docs/ai/commercial.md`](../docs/ai/commercial.md). Cause of death:
> [`docs/ai/decisions.md`](../docs/ai/decisions.md) → Monetization. Read the rest of this file as
> the 2026-10-08 record — the merch/gratuity half still stands, the "free forever, no paid tier"
> half does not.
> Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. > Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here.
**Why:** the monetization decision changed. LlamaCasty launches **watermark-free and free as in beer, **Why:** the monetization decision changed. LlamaCasty launches **watermark-free and free as in beer,
+5 -3
View File
@@ -8,8 +8,9 @@
A Windows-only WPF (.NET 8) app that live-streams to YouTube. Five fixed scenes A Windows-only WPF (.NET 8) app that live-streams to YouTube. Five fixed scenes
(Starting / Live / BRB / Chat / Ending), a pure C# software compositor over an always-1920×1080 (Starting / Live / BRB / Chat / Ending), a pure C# software compositor over an always-1920×1080
master, a bundled-ffmpeg encoder pushing RTMP, and a WASAPI audio chain. Free forever, GPL-3.0, master, a bundled-ffmpeg encoder pushing RTMP, and a WASAPI audio chain. Freemium — free is the
no account creation — it links an existing YouTube channel. One-click go-live is the product; whole app at a 720p30 output profile, Pro buys higher output quality — under a proprietary EULA,
with no account creation: it links an existing YouTube channel. One-click go-live is the product;
OBS is the escape hatch for anything bigger. OBS is the escape hatch for anything bigger.
## Build, run, test ## Build, run, test
@@ -50,7 +51,8 @@ C# / WPF (.NET 8) following MVVM:
| Social bar, alerts, chat overlays | [`docs/ai/overlays.md`](docs/ai/overlays.md) | anything drawn on top of the output | | Social bar, alerts, chat overlays | [`docs/ai/overlays.md`](docs/ai/overlays.md) | anything drawn on top of the output |
| Go-live, broadcasts, health, chat API | [`docs/ai/youtube-api.md`](docs/ai/youtube-api.md) | go-live, broadcast lifecycle, health polling, streaming close-out | | Go-live, broadcasts, health, chat API | [`docs/ai/youtube-api.md`](docs/ai/youtube-api.md) | go-live, broadcast lifecycle, health polling, streaming close-out |
| Control surface, theming, transitions | [`docs/ai/ui-surface.md`](docs/ai/ui-surface.md) | anything the creator looks at | | Control surface, theming, transitions | [`docs/ai/ui-surface.md`](docs/ai/ui-surface.md) | anything the creator looks at |
| Brand, licensing, monetization, distribution | [`docs/ai/product.md`](docs/ai/product.md) | branding, pricing, licensing, packaging | | Brand, packaging, legal posture | [`docs/ai/product.md`](docs/ai/product.md) | branding, naming, packaging behaviour, legal obligations |
| Prices, entitlement, channels, signing, promo | [`docs/ai/commercial.md`](docs/ai/commercial.md) | anything about money: pricing, licence keys, output cap, winget/Steam/Store, costs |
| Hosts, build commands, test map | [`docs/ai/environment.md`](docs/ai/environment.md) | building, running, adding a test | | Hosts, build commands, test map | [`docs/ai/environment.md`](docs/ai/environment.md) | building, running, adding a test |
| Routes for every subsystem | [`docs/ai/index.md`](docs/ai/index.md) | you want the full table with a "read when" per file | | Routes for every subsystem | [`docs/ai/index.md`](docs/ai/index.md) | you want the full table with a "read when" per file |
+3 -2
View File
@@ -312,8 +312,9 @@ This replaces the old five-seeder cluster (`Seed{Starting,Brb,Ending,Chat}Backgr
`verify_against_mediapipe.py` feeds the same frame to MediaPipe's `ImageSegmenter` (original `verify_against_mediapipe.py` feeds the same frame to MediaPipe's `ImageSegmenter` (original
.tflite) and onnxruntime (our .onnx): max |diff| 5e-5, 100% binary agreement on two images. .tflite) and onnxruntime (our .onnx): max |diff| 5e-5, 100% binary agreement on two images.
⛔ Do NOT swap in PINTO model-zoo #109 (it's ML Kit's model, extracted — not Apache provenance). ⛔ Do NOT swap in PINTO model-zoo #109 (it's ML Kit's model, extracted — not Apache provenance).
obs-backgroundremoval is GPL-3.0 — license-compatible since LlamaCasty went GPL-3.0 (2026-10-09), obs-backgroundremoval is GPL-3.0 and therefore **licence-incompatible since the app went
but none of its code is used; if any is ever borrowed, it needs its copyright notice kept. proprietary (2026-10-30)** — none of its code is used, and none may be borrowed into this
codebase without a licence ruling first. Google's model is the provenance that matters.
- **Contract:** input `[1,144,256,3]` RGB /255 NHWC → output `[1,144,256,1]` person probability. The - **Contract:** input `[1,144,256,3]` RGB /255 NHWC → output `[1,144,256,1]` person probability. The
whole frame is stretched (not cropped) to 256×144, so the mask maps back by normalized coordinates. whole frame is stretched (not cropped) to 256×144, so the mask maps back by normalized coordinates.
- **Shipping:** `Assets/Models/selfie_segmenter_landscape.onnx` is an **EmbeddedResource** (in git, - **Shipping:** `Assets/Models/selfie_segmenter_landscape.onnx` is an **EmbeddedResource** (in git,
+152
View File
@@ -0,0 +1,152 @@
# Commercial: pricing, entitlement, channels
The single source of truth for what LlamaCasty charges, what a licence entitles, and how it
ships. Read before any pricing, licensing, distribution, storefront or promo work. Cause of death
for every retired path: [`decisions.md`](decisions.md).
## Model
- **Free tier — every feature, every scene, every overlay**, free forever, no account, no
watermark, nothing nagged inside the output. It is not a trial.
- **Pro — $10/month or $400 lifetime.** No annual tier.
- **The only delta is output quality.** Features are never gated; a payment buys pixels.
- **No watermark, ever**, in either edition. The branding flash is retired as a paid delta and
stays off (`ShowBrandFlash`, default `false`).
- **Clean numbers, no `.99`**, no discount codes, no countdowns, no fake scarcity.
- **The site publishes the model, never the numbers** — "free forever, every feature unlocked, a
paid edition with higher output quality at launch". Price, tier name, cap and frame rate are
published by the **app**, at the point of sale, not by the page.
## Output profile — the one entitlement rule
The cap is a pixel budget plus a frame rate, not the word "720p":
| | Free | Pro |
|---|---|---|
| Pixels | ≤ **921,600** | any shipped tier |
| Frame rate | ≤ **30 fps** | any shipped tier |
| Landscape | 1280×720 @30 | 1280×720 @60 · 1920×1080 @30 · @60 |
| Vertical 9:16 | 720×1280 @30 | 1080×1920 @60 |
- **Records too.** The cap governs the file the creator keeps as well as the stream; otherwise
free 1080p recording is a re-upload hole. One `OutputProfile`, resolved once per session and
consumed by **both** the stream encoder and the recorder — never two checks.
- **Fail closed.** Unresolved, unreadable or unvalidated entitlement = free tier. Never Pro.
- **Never downgrade a running stream.** The gate applies to the next start; pulling quality out
of a live broadcast is a worse bug than one free 1080p session.
- **The greyed menu entry is the sales surface.** It stays clickable, says what it costs, opens
checkout, and is labelled ("720p30 · free tier") — never silently missing, because silence
reads as broken.
- **Activation is live**, not restart-gated: activating enables the high tiers and a lapse
(cancel, revoke, sign-out, channel switch) greys them again on the next refresh.
- **Bitrate follows the profile** — a capped user is not offered a 1080p bitrate.
## Entitlement
**Polar is the merchant of record and the only source of truth.** It takes the money, remits
VAT/GST, handles refunds and chargebacks, and holds the keys. Nothing of ours sits in the middle.
- **Key** — `LCYT-` prefix; org `c05fb364-…` and the endpoint are in `CREDENTIALS.md`. No
product is created yet.
- **Bound to the creator's YouTube channel ID** (`UC…` from `channels.list?mine=true`) — the
**active** brand account's channel, never the `@handle`. Written once through a **claim page**
after purchase (OAuth), not prefilled at checkout: the buyer's browser may be signed into a
different Google account, and that is manual reconciliation forever.
- **Two activations**, any machine, one channel. A second channel is a second key. Two keys for
one channel is never sold — it doubles the support surface and invites "give one to a friend".
- **Runtime rule:** Pro requires `licence.channelId == connected.channelId`. Equal → Pro.
Anything else → free tier, working normally, with *"This licence belongs to another channel"*
and a link to the Polar customer portal. Never an error wall, never a blocked stream.
- **Lease, not a permanent cache:** validate on launch and daily; **7–14 days offline grace**;
no network past grace = free tier until Polar answers. Never lock a paying user out on a DNS
failure.
- **Four states, and nothing else:** valid → Pro · expired → free with a banner · activation
limit reached → "manage devices" portal link · mismatch or offline → free, explained.
- **CLI activation** — `--activate LCYT-…` so silent and fleet installs never meet a dialog.
Interactive entry in Settings is the secondary path.
- **Notice cadence** — 7 days out, 1 day out, and on the day, inline in the go-live panel with a
date and a portal link. Recording gets the banner only. Never modal, never blocking a Start,
never orange (orange is the dev-only label colour).
## Channels
**One artifact, three ways to get it.** A self-contained single-file `LlamaCasty.exe` plus the
bundled `ffmpeg\` folder, zipped. No .NET install, no admin, no installer framework. Trimming
stays off (WPF reflection), so expect ~110–150 MB.
1. **Direct download** — `llamacasty.com` links the zip; the binaries live on **GitHub Releases**,
not the droplet (free CDN, durable URLs, release notes).
2. **winget — the primary channel.** Manifest in `microsoft/winget-pkgs`, `InstallerType:
portable`, pinned SHA-256. That hash pin is the anti-tamper wall: the official channel delivers
exactly the published artifact or fails. One `wingetcreate update` PR per release. Optional:
Scoop and Chocolatey manifests over the same zip.
3. **Steam — parked at $100.** Lifetime Pro as DLC only (Steam has no subscriptions), page public
two weeks before release, 30% cut, and price parity with the site.
4. **Microsoft Store — parked.** $19 Individual account; the only storefront that bills a
subscription natively and installs without a warning. Bans self-updaters, so a Store build
ships without one.
**Not channels, ever:** Patreon or any subscription platform of our own, paywalled early access,
a paid beta, a Discord. Support is a public GitHub issue tracker. Content is the funnel; the
channel is only the checkout.
**Portable mode:** a `portable.txt` marker beside the exe keeps the layout DB and settings in
`.\data\` instead of `%APPDATA%\ytLlive` — the USB-stick case, hung off `LayoutPathOverride`.
## Signing
- **Azure Trusted Signing, ~$9.99/month**, is the intended certificate: Microsoft-issued, accrues
SmartScreen reputation, enrols individuals after identity verification.
- **No EV certificate** — Microsoft removed the SmartScreen instant-bypass in March 2024.
- Until it is in place, builds ship unsigned and the "unknown publisher" line is accepted. winget
spares the browser prompt, not the publisher warning.
- Every release: same signing pass, SHA-256 in the manifest, licence ID visible in About.
## Founding 200 promo
Cash cost **$0**; it is priced in support time and forgone list revenue.
- **90 days free** — a 100%-off Polar coupon, `duration = repeating, months = 3`.
- **Founder price lock** — their $10/month stays $10 permanently.
- **Credits + in-app Founder badge** (opt-in channel name on the site).
- **30-day passes** — unique keys carrying the issuer's channel, issued **one per 30 active days,
max 3 held**, activation limit 1–2, expiring 30 days **after activation**. Earned, never handed
out: a pass nobody uses teaches nothing.
- Optional **$250 lifetime** window in the first 90 days — converting a founder beats three
months of free.
- Reminders at day 75 and 85; the renewal cliff is where founders churn.
- Terms, one paragraph: one per person, passes expire 30 days after activation, no cash value,
revoked for resale or abuse, price lock void if the subscription lapses.
## Costs
| Item | Cost |
|---|---|
| Azure Trusted Signing | $9.99/month (~$120/yr) |
| Domain | ~$15/yr |
| Droplet | $0 — serves the existing site |
| GitHub Releases, winget, Scoop, Chocolatey | $0 |
| Polar | 5% + 50¢ per sale → $10 nets $9.00, $400 nets $379.50 |
| Steam Direct (optional) | $100 once, recoupable after $1,000 AGR |
| Microsoft Store (optional) | $19 once |
Fixed year one ≈ **$135**, and the same every year after. **Break-even is two subscribers**
(15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years.
## Anti-piracy posture
**Any check that runs on the user's machine can be removed.** The goal is a crack that is
pointless and a leak that is traceable — not an unbreakable binary.
- **The free tier is the real defence.** If the only delta is pixels above 921,600, a cracked
build buys almost nothing and forfeits updates, support and the publisher's signature.
- **Hash-pinned delivery + signed builds** — a patched build cannot arrive through winget, and a
forged one is not signed as us.
- **Short leases** — a revoked or refunded key dies inside the grace window.
- **Licence ID in About** — a leaked build traces back to a purchase.
⛔ **Rejected, do not re-propose:** a native helper DLL for "IP protection", obfuscation, hardware
locking, remote code that downloads and executes, and gating asset downloads as an auth check.
None of them make a licence enforceable; the first three add failure modes without buying
anything, the fourth needs the server this plan exists to avoid, and the fifth is Store policy
10.2.2.
+50 -25
View File
@@ -1,15 +1,18 @@
# Decisions log — retired paths, kept so they are not re-litigated # Decisions log — retired paths, kept so they are not re-litigated
One dated line per decision that **no longer governs**, plus the reason it died. Read only when One dated line per decision that **no longer governs**, plus the reason it died. Read only when
you are about to re-open one of them. Live rules live in the subsystem files; this is the you are about to re-open one of them. **Live commercial rules (prices, entitlement, channels,
graveyard with cause of death. signing, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in the
subsystem files. This is the graveyard with cause of death.
## Monetization ## Monetization
### 2026-10-30 — freemium replaces free-forever (creator ruling) ### 2026-10-30 — freemium replaces free-forever (creator ruling)
Free tier caps output at **720p30**; **$10/month** or **$400 lifetime** unlocks 1080p. Reverses the Free tier caps output at **720p30**; **$10/month** or **$400 lifetime** unlocks the higher tiers.
2026-10-08 free-forever ruling below, which was a *current decision*, not an absolute. Reverses the 2026-10-08 free-forever ruling below, which was a *current decision*, not an absolute.
The live detail — pixel budget, stream + recording, channel binding, lease, notice cadence — is in
[`commercial.md`](commercial.md); do not re-derive it here.
**The website publishes the model, never the numbers** (free forever, every feature unlocked, a paid **The website publishes the model, never the numbers** (free forever, every feature unlocked, a paid
edition at launch) — the terms may still change. The real plan is recorded internally in the site's edition at launch) — the terms may still change. The real plan is recorded internally in the site's
@@ -33,23 +36,41 @@ edition at launch) — the terms may still change. The real plan is recorded int
- **Rejected**: always-on watermark (obscurable — replaced by the flash), hard stream-time cutoffs - **Rejected**: always-on watermark (obscurable — replaced by the flash), hard stream-time cutoffs
(a stream dying mid-broadcast reads as broken), soft-limit nagging, freemium feature tiers, (a stream dying mid-broadcast reads as broken), soft-limit nagging, freemium feature tiers,
donation-only, and any resolution/quality ceiling. Auto step-down (TASK 33) is a *protect the donation-only, and any resolution/quality ceiling. Auto step-down (TASK 33) is a *protect the
stream* feature, never a monetization penalty. stream* feature, never a monetization penalty. ⚠️ **The quality-ceiling and feature-tier halves
- **Polar** — $50 already paid, 5% + $0.50 Starter, merchant of record. Dormant, not were reversed on 2026-10-30** (freemium, output-quality delta); the watermark, cutoff and
deleted-in-principle: no product, org ID, key prefix or discount code exists nagging rejections still stand.
(`d105dfa1-…`, `c05fb364-…`, `LCYT-`, `LLAMAFOUNDER`/`LLAMA50`, the portal-validate endpoint and - **Polar** — $50 already paid, 5% + $0.50 Starter, merchant of record. **Live again as of
the 14-day offline-grace machine are all gone). `Distribution.md` §4.2/§4.5 keeps the design. 2026-10-31** (`commercial.md`). The org ID `c05fb364-…`, the `LCYT-` key prefix and the
customer-portal validate endpoint are recorded in `CREDENTIALS.md`; no product, licence keys or
discount codes exist yet. The old `PolarLicenseService`/`Helpers/PolarLicense.cs` are still gone
— the 14-day-grace design has to be rebuilt (as a lease, not a permanent cache).
- **Store IAP** was never implemented — `Windows.Services.Store` appears nowhere in the repo. - **Store IAP** was never implemented — `Windows.Services.Store` appears nowhere in the repo.
- ⚠️ **Two obligations any future subscription would create** (ADA §6(h)): fulfil the whole period - ⚠️ **Two obligations any future subscription would create** (ADA §6(h)): fulfil the whole period
as marketed (breach → Microsoft may refund in full at its discretion), and **raising the price as marketed (breach → Microsoft may refund in full at its discretion), and **raising the price
disables auto-renew** — the launch price is effectively locked for the product's life. disables auto-renew** — the launch price is effectively locked for the product's life.
## Distribution — Store route RE-OPENED 2026-10-30 (Individual account) ## Distribution — Store PARKED as the third channel (2026-10-31)
**2026-10-30:** the Store is the route again, via a **Microsoft Individual developer account** **2026-10-31:** **winget over a portable zip is the primary channel** — one self-contained
(not Company), so no legal entity is needed and the publisher line shows the verified individual `LlamaCasty.exe` + bundled `ffmpeg\`, zipped, hosted on GitHub Releases, manifest in
name. The creator will open the account; distribution work stays parked until then. The site copy `microsoft/winget-pkgs` with a pinned SHA-256. Steam is second and parked at $100 (lifetime Pro as
("Coming soon to the Microsoft Store") is therefore accurate again, and the 2026-10-22 DLC only — Steam has no subscriptions). The **Microsoft Store is third and parked** until the
"Store is dead / direct + Steam" ruling below is superseded. creator opens the **$19 Individual** developer account: it is the only storefront that bills a
subscription natively and installs without a SmartScreen warning, and it bans self-updaters.
Channels, costs, artifact and signing: [`commercial.md`](commercial.md).
**Why the ordering inverted:** winget has no discovery, but neither does the Store for utilities,
and winget costs nothing, needs no account, no certification and no MSIX pipeline — while the
audience installs Windows tools from a command line. The site and the YouTube channel are the
funnel in both cases.
## Superseded: Store was "the route again" — 2026-10-30 (ordering only)
**2026-10-30:** a **Microsoft Individual developer account** (not Company) removed the legal-entity
blocker — the publisher line shows the verified individual name, and Partner Center does not
convert Individual → Company later. That finding stands; what it no longer implies is *primary*.
The site copy ("Coming soon to the Microsoft Store") stays true until the download page lands, then
the Store line becomes the secondary note.
## Superseded: Store route withdrawn 2026-10-22 ## Superseded: Store route withdrawn 2026-10-22
@@ -58,20 +79,19 @@ name. The creator will open the account; distribution work stays parked until th
New Mexico with a ~2-week SOS backlog, ~$143 Wyoming). Partner Center does **not** convert an New Mexico with a ~2-week SOS backlog, ~$143 Wyoming). Partner Center does **not** convert an
Individual account to a Company account later, and an Individual account publishes under the Individual account to a Company account later, and an Individual account publishes under the
verified legal name. The creator will not fund an entity for a nameplate. verified legal name. The creator will not fund an entity for a nameplate.
- **2026-10-22 Velopack deleted** — it downloaded and executed, which Store policy 10.2.2 forbids. - **2026-10-22 Velopack deleted** — it downloaded and executed, which Store policy 10.2.2 forbids
The Store was going to update the app, so that premise died with the route: **the app now has no (and which the 2026-10-31 plan keeps forbidden). The app has **no self-updater by policy**:
update path**, and `StorePackagingTests` currently asserts that no updater exists — rewrite that `winget upgrade` plus an in-app "newer version exists" banner is the update path, and
guard with the chosen updater, never leave it to veto one. `StorePackagingTests` asserts that no self-updater returns.
- **The MSIX package still builds** (159 MB `artifacts/LlamaCasty.msix`, submitted nowhere). The - **The MSIX package still builds** (159 MB `artifacts/LlamaCasty.msix`, submitted nowhere) — it
route contradiction between `ai.md`'s older text and the TASKS files is settled in one sweep at is the parked Store channel's artifact.
the distribution revisit, not patched piecemeal.
- **Signing**: Microsoft removed the EV instant-bypass for SmartScreen in March 2024, so EV buys - **Signing**: Microsoft removed the EV instant-bypass for SmartScreen in March 2024, so EV buys
what OV buys; the 2026-10-22 ruling is **ship unsigned** and accept the "unrecognized publisher" what OV buys. The 2026-10-22 ruling was *ship unsigned*; **2026-10-31 supersedes it with Azure
warning until download reputation accrues. Deferred, not rejected. Trusted Signing (~$9.99/month)**, which is Microsoft-issued and accrues reputation.
- **Steam when $100 exists**: Steam Direct $100/product (recoupable after $1,000 AGR), categories - **Steam when $100 exists**: Steam Direct $100/product (recoupable after $1,000 AGR), categories
*Audio/Video Production* / *Player Tools* (OBS precedent), developer/publisher name self-chosen *Audio/Video Production* / *Player Tools* (OBS precedent), developer/publisher name self-chosen
(no entity needed), 5+ 1920×1080 screenshots + capsule art, a build Valve runs, a 2-week live (no entity needed), 5+ 1920×1080 screenshots + capsule art, a build Valve runs, a 2-week live
page, 30% cut. page, 30% cut, and no subscription support.
## Product surface — retired ## Product surface — retired
@@ -91,6 +111,11 @@ name. The creator will open the account; distribution work stays parked until th
Do not build these; each was explicitly ruled out. Do not build these; each was explicitly ruled out.
- **Anti-piracy theatre (2026-10-31)**: a native helper DLL for "IP protection", obfuscation,
hardware locking, remote code that downloads and executes, and gating asset downloads as an auth
check. None make a licence enforceable; see `commercial.md` → "Anti-piracy posture".
- **Commercial structures (2026-10-31)**: Patreon or any subscription platform of our own, paid
early access, a paid beta, a gated Discord, and a Pro trial (the free tier is the trial).
- D3D11 swap compositor (superseded by the software compositor). - D3D11 swap compositor (superseded by the software compositor).
- OBS-style source expansion — game capture, browser-source zoo, media playlist, VLC, colour-key, - OBS-style source expansion — game capture, browser-source zoo, media playlist, VLC, colour-key,
MIDI. The minimal source set is deliberate; creators who need more have graduated to OBS. MIDI. The minimal source set is deliberate; creators who need more have graduated to OBS.
+3 -2
View File
@@ -13,10 +13,11 @@ Read the file whose row matches the task. There is no required reading order.
| [`capture.md`](capture.md) | The locked Background layer (screen/game/window capture, WinRT GraphicsCapture interop), the webcam seam + `CameraManager` sessions, MediaPipe background effects, and the vertical 9:16 tier's per-scene camera placement | Touching any frame source, the Background row, camera acquisition/conflicts, background effects, or vertical-tier camera geometry | | [`capture.md`](capture.md) | The locked Background layer (screen/game/window capture, WinRT GraphicsCapture interop), the webcam seam + `CameraManager` sessions, MediaPipe background effects, and the vertical 9:16 tier's per-scene camera placement | Touching any frame source, the Background row, camera acquisition/conflicts, background effects, or vertical-tier camera geometry |
| [`output.md`](output.md) | The bundled `ffmpeg.exe` and the encoder subprocess (args, probe order, seams), local recording (REC/ON-AIR, filenames, private-broadcast close-out), and the WASAPI capture → filter → mix → named-pipe audio chain (TRAX, sync offset, ducking, limiter) | Touching ffmpeg args/bundling, encoder or recording behaviour, REC/ON-AIR, the audio mix and its meters, or loudness/sync problems | | [`output.md`](output.md) | The bundled `ffmpeg.exe` and the encoder subprocess (args, probe order, seams), local recording (REC/ON-AIR, filenames, private-broadcast close-out), and the WASAPI capture → filter → mix → named-pipe audio chain (TRAX, sync offset, ducking, limiter) | Touching ffmpeg args/bundling, encoder or recording behaviour, REC/ON-AIR, the audio mix and its meters, or loudness/sync problems |
| [`environment.md`](environment.md) | LAN hosts/IPs/SSH and the Gitea push recipe, the Windows `dotnet.exe` build + run commands, the test suite map, and the hermetic real-`MainWindow` test rule | Before building, running, adding a test, or reaching any box on the LAN | | [`environment.md`](environment.md) | LAN hosts/IPs/SSH and the Gitea push recipe, the Windows `dotnet.exe` build + run commands, the test suite map, and the hermetic real-`MainWindow` test rule | Before building, running, adding a test, or reaching any box on the LAN |
| [`product.md`](product.md) | Brand/naming split, AI transparency, the GPL licensing prohibitions, the free-forever monetization posture, auth/account assumptions, and packaging/distribution | Branding, marketing, pricing, licensing, or packaging decisions | | [`product.md`](product.md) | Brand/naming split, AI transparency, the licensing prohibitions, the freemium posture, auth/account assumptions, and packaging behaviour | Branding, naming, packaging, or a legal/third-party obligation |
| [`commercial.md`](commercial.md) | The live commercial policy: model and prices, the output-profile cap (stream **and** recording), Polar entitlement and channel binding, the channels (winget, Steam, Store), signing, the Founding 200 promo, costs, anti-piracy posture | Any question about money: pricing, licence keys, what free gets, how it ships, what a sale costs |
| [`youtube-api.md`](youtube-api.md) | Go-live ordering + the broadcast-binding fix, the private-only lock, health parsing, end-stream pre-flight, Test mode, and chat-insert shape | Touching go-live, broadcast lifecycle, health polling, streaming close-out, or the chat service | | [`youtube-api.md`](youtube-api.md) | Go-live ordering + the broadcast-binding fix, the private-only lock, health parsing, end-stream pre-flight, Test mode, and chat-insert shape | Touching go-live, broadcast lifecycle, health polling, streaming close-out, or the chat service |
| [`overlays.md`](overlays.md) | The social bar, the native alert box (six animations, alert clip, ticker) and the chat box conventions — everything composited over the frame | Adding or changing anything drawn on top of the output | | [`overlays.md`](overlays.md) | The social bar, the native alert box (six animations, alert clip, ticker) and the chat box conventions — everything composited over the frame | Adding or changing anything drawn on top of the output |
| [`decisions.md`](decisions.md) | Retired paths with cause of death: the paid tier and pricing, Polar, Store IAP, the Store route, the deleted updater, the rejected monetization ideas, and the permanent out-of-product list | You are about to re-open a decision that was already settled | | [`decisions.md`](decisions.md) | Retired paths with cause of death: the paid-tier history and pricing, the free-forever era, Store IAP, the Store ordering, rejected monetization and anti-piracy ideas, and the permanent out-of-product list | You are about to re-open a decision that was already settled |
| [`ui-surface.md`](ui-surface.md) | The control surface, scene strip, preview rail, hotkeys, broadcast drawer, toasts, transitions, brand logo, launch geometry, and the theme/properties-panel conventions | Changing anything the creator sees: layout, controls, theming, properties panel | | [`ui-surface.md`](ui-surface.md) | The control surface, scene strip, preview rail, hotkeys, broadcast drawer, toasts, transitions, brand logo, launch geometry, and the theme/properties-panel conventions | Changing anything the creator sees: layout, controls, theming, properties panel |
## Split-out yet to come ## Split-out yet to come
+56 -61
View File
@@ -56,7 +56,9 @@ collaboration, not autonomous code generation.
## Licensing — proprietary since 2026-10-30 ## Licensing — proprietary since 2026-10-30
**LlamaCasty is closed-source commercial software.** `LICENSE` is the EULA; the paid tier exists **LlamaCasty is closed-source commercial software.** `LICENSE` is the EULA; the paid tier exists
because the free tier caps output at **720p30** (creator ruling 2026-10-30, superseding free-forever). because output quality is the paid delta — the free tier is capped at a 720p30 profile (creator
ruling 2026-10-30, superseding free-forever). Prices, entitlement and channels:
[`commercial.md`](commercial.md).
- **Every GPL build already published stays GPL for that snapshot.** Attribution/§7 terms - **Every GPL build already published stays GPL for that snapshot.** Attribution/§7 terms
(`ADDITIONAL-TERMS.md`) are deleted and were GPL-only; the files are gone. (`ADDITIONAL-TERMS.md`) are deleted and were GPL-only; the files are gone.
@@ -88,18 +90,21 @@ Apply this to every UI decision:
stream, not both; one reusable stream; one webcam; two mixer inputs). If a feature only sings on 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. a high-end chassis, it doesn't ship — the OBS escape hatch is open by design.
## Monetization — free forever, no watermark ## Monetization — free tier + Pro output quality
**All features are free and ungated** (creator ruling 2026-10-08, TASK 54): no feature lock, no paid **Every feature is free and ungated** (creator ruling 2026-10-08, TASK 54, kept by the 2026-10-30
tier, no watermark. Money comes from gratuity and merch, not gating. **No licence chatter reaches a freemium ruling): no feature lock, no watermark, and nothing licence-shaped in the output. The
user** — no keys, no expiry, no licence UI. Support is email + GitHub issues; an in-app bug reporter paid delta is **output quality alone** — free is capped at a 720p30 profile, Pro unlocks the
is out of product. `IsPremium` gates nothing in v1. higher tiers for **both the stream and the recording**. Prices, entitlement and channels:
[`commercial.md`](commercial.md). Support is email + GitHub issues; an in-app bug reporter is out
of product.
**The branding credit is OFF by default.** `MainViewModel.ShowBrandFlash` (internal static, default **The branding credit is retired as a paid delta and stays off.** `MainViewModel.ShowBrandFlash`
`false`) is the master switch; `BrandFlashEnabled => ShowBrandFlash && !IsPremium` feeds the (internal static, default `false`) is the master switch; `BrandFlashEnabled => ShowBrandFlash &&
presenter. The code is kept, not deleted — creator ruling: *"don't delete it, just make it !IsPremium` feeds the presenter. The code is kept, not deleted — creator ruling: *"don't delete
conditionally displayed."* `BrandFlashOutputTests` opts in by setting the switch before constructing it, just make it conditionally displayed."* Entitlement must never enable it.
a `MainWindow`; `Credit_IsOffByDefault_EvenOnTheFreeTier` guards the default. `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: If it is ever enabled, the mechanics that matter:
@@ -117,14 +122,14 @@ If it is ever enabled, the mechanics that matter:
- **A mid-credit downgrade** cuts the advertisement on the next tick: the presenter re-checks its own - **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. `Enabled` gate every frame rather than letting the credit finish.
Pricing, billing, the Store IAP plan and the rejected alternatives are history → Live prices, entitlement and channels: [`commercial.md`](commercial.md). Retired pricing, the
[`decisions.md`](decisions.md). Store IAP plan and the rejected alternatives: [`decisions.md`](decisions.md).
**Monetization awareness (built-in, ungated, free — 2026-09-01; v1 SCOPE per the complete-v1 **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 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 design, for **every** creator, free and ungated. It has three layers, all free — the capability no
removal above). It has three layers, all free — this is the product's differentiator, not a paid tier: competitor ships (the positioning order across features lives in `MARCOM.md`):
- **Reward-event capture (the data foundation).** The live-chat poll (`YouTubeChatService.Poll`) - **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) — receives every reward event over `liveChat/messages` and — since TASK 43 (2026-09-24) —
@@ -184,40 +189,32 @@ YouTube channel, they go to YouTube first. This assumption is explicit and must
replaced by an in-app account-creation step. Zero state = the Start Stream dialog's "Sign in to 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. YouTube" button; going live is unreachable until an account is connected.
## Windows packaging & distribution (direct + Steam; Store dropped 2026-10-22) ## Windows packaging & distribution (winget primary; Steam + Store parked)
**The Microsoft Store route is dead** — a brand on the publisher line needs a Company account and Live channel policy, the artifact and signing costs: [`commercial.md`](commercial.md). Build
a legal entity. Cause of death and the abandoned routes: [`decisions.md`](decisions.md). mechanics (publish flags, GitHub Releases, the winget manifest): [`Distribution.md`](../../Distribution.md).
Retired routes and cause of death: [`decisions.md`](decisions.md).
**The route now:** **The channels, in order:** direct download from `llamacasty.com` (binaries on GitHub Releases) →
**winget**, the primary install path → Steam (parked, $100, lifetime Pro as DLC only) → Microsoft
1. **Direct download from `llamacasty.com`** — the app is **free forever** (TASK 54), so there is Store (parked, $19 Individual account — the only storefront that bills a subscription natively).
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:** **Consequences that are architecture, not packaging detail:**
- **⛔ The updater is now MISSING, not deleted-by-design.** Velopack and its bootstrap were - **⛔ There is no self-updater, by policy as well as by history.** Velopack was removed on
removed on 2026-10-22 (TASK 48 slice 3) because the Store was going to deliver updates — that 2026-10-22 (TASK 48 slice 3) because it downloaded *and executed*, which Store policy 10.2.2
premise is gone. An update path must come back, and forbids — and the same rule would close the Store for good if it came back. Updates arrive
`ytLive.Tests/StorePackagingTests.cs` (which asserts no updater returns) must be rewritten through the package manager (`winget upgrade`) plus an in-app "newer version exists" banner read
with the chosen updater rather than left to veto one. from a version file on GitHub Releases. `ytLive.Tests/StorePackagingTests.cs` must keep asserting
- **The MSIX package still builds** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish that no self-updater returns.
- **The MSIX package stays buildable** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish
`Release -r win-x64 --self-contained` into `artifacts/msix-stage`, copy `Release -r win-x64 --self-contained` into `artifacts/msix-stage`, copy
`Packaging/Package.appxmanifest` in as `AppxManifest.xml` + `Packaging/Assets/`, then `Packaging/Package.appxmanifest` in as `AppxManifest.xml` + `Packaging/Assets/`, then
`makeappx pack` → `artifacts/LlamaCasty.msix` (159 MB, gitignored; `MSIX_PFX` signs it for a `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 local install). It is the Store channel's artifact, unsubmitted until that account exists. The
`runFullTrust` + webcam + microphone. `Square310x310Logo`/`Wide150x150Logo` traps are in manifest is a **full-trust `packagedClassicApp` at `mediumIL`** with `runFullTrust` + webcam +
`MyMistakes.md`. Logos come from the app's own artwork via `scripts/make-store-logos.py`. 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 - **⛔ 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". 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 - **⛔ 64-bit Windows only (creator ruling 2026-10-09).** Both csproj files set
@@ -226,10 +223,12 @@ a legal entity. Cause of death and the abandoned routes: [`decisions.md`](decisi
is the win64 build, ONNX Runtime 1.31 ships no win-x86 native (only win-x64/win-arm64), and a 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. 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". Public copy says "64-bit Windows 10 or 11".
- **Portable mode is the distributable's shape:** `portable.txt` beside the exe keeps the layout DB
and settings in `.\data\` (hung off `LayoutPathOverride`), so a USB stick carries someone's
scenes. The `winget` install and the hand-to-a-friend zip are the same artifact.
**What does NOT change: the monetization posture.** The app is **free forever with no watermark** **What does NOT change: no watermark and no feature gating.** The free tier is the whole app at a
(TASK 54, 2026-10-08), so `IsPremium` gates nothing in v1 and the branding flash is OFF by 720p30 profile. `Monetization` above governs.
default. `Monetization` above governs.
### ⛔ Durable invariant: full trust, or recording silently breaks ### ⛔ Durable invariant: full trust, or recording silently breaks
@@ -250,28 +249,24 @@ trust. No `broadFileSystemAccess` capability is needed, and none should be added
error.** A comment at `DefaultRecordFolder()` is owed **when the manifest lands** (TASK 48 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. item 6) — not before; a comment describing a manifest that doesn't exist is worse than none.
### Signing — do not buy EV ### Signing — Azure Trusted Signing, never EV
Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**. An Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**, so EV
EV-signed file now accrues reputation exactly like an OV-signed one, so EV ($400+/yr) buys ($400+/yr) buys what OV (~$150–300/yr) buys; both need an HSM or hardware token and renew inside
what OV (~$150–300/yr) buys. Signing is **not** instant regardless — a valid cert stops the **460 days**. The 2026-10-31 ruling is **Azure Trusted Signing (~$9.99/month)**: Microsoft-issued,
*malware* warning immediately, but "unrecognized publisher" clears only as SmartScreen accrues individuals eligible after identity verification, and it accrues reputation without a certificate
reputation from real download volume. Cert validity is capped at **460 days** (recurring purchase. Until the profile exists, builds ship unsigned and the "unknown publisher" line is
line item) and private keys must live on an **HSM or hardware token**. **The 2026-10-22 ruling accepted. Signing never clears the warning on day one either way — only download volume does.
is to ship unsigned and accept the "unrecognized publisher" warning** until download reputation Signing steps: [`Distribution.md`](../../Distribution.md).
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 ### Distribution and licensing are independent — the channel never decides billing
A **Store-distributed** app *may* still verify licences with **Polar** (Store policy 10.8.1 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 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 channel?" never implies "which billing?". **Polar is the live provider** (2026-10-31) because the
proves the point in the other direction: dropping the Store did not by itself restart Polar, 2026-10-30 freemium ruling created something to sell; winget has no storefront at all, so the
because the **2026-10-08 free-forever ruling** (TASK 54) had already removed the paid tier. licence path is entirely ours there. The lesson worth keeping: the licensing choice rides on a
Polar is therefore *dormant*, ready if a paid tier ever returns, and nothing in v1 depends on it. **monetization** decision, not on a distribution decision.
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 ### ⛔ "Only when the user says so" is enforced by Windows, not by us