docs(distribution): Steam is the only channel — the zip, winget and the Store are dead

The site said "Coming soon to Steam" while the app docs still said direct download plus
winget primary with Steam parked. One of them had to move, and the creator's ruling is
Steam exclusive: a free app on Steam with the paid edition as a Steam DLC, and the
$10/month plan sold off-Steam by Polar.

Records the constraint that shapes the code, not just the prose:
- Steamworks requires in-app purchases to use the Steam Wallet (Microtransactions doc,
  quoted in commercial.md), so the monthly is *stated*, never *sold*, inside a Steam
  build; the lifetime is plain DLC bought on the store page and read with BIsDlcInstalled.
- Entitlement is `steamDlcOwned || polarKeyValid`, both resolving to one OutputProfile.
  steam_api64.dll ships beside the exe and is a functional dependency, not the rejected
  "IP protection" native DLL — without it DLC ownership cannot be read. Launched without
  Steam, the app falls back to the free profile rather than refusing to start.
- Signing is moot: Steam installs never meet SmartScreen. Artifact Signing stays on the
  shelf for the day a non-Steam download exists. Year-one fixed cost is ~$115.
- The exe rename must land before the first depot upload — Steamworks warns about
  renaming a live exe.

Distribution.md is rewritten to SteamPipe mechanics (publish output, depot, steamcmd,
DLC wiring, clean-account purchase/refund test). product.md, decisions.md (a new
"Steam exclusive" entry with every dead vector), TASKS.md (Steamworks entitlement row,
Steam packaging row), ai.md, index.md and the TASK 48 banner follow. MyMistakes.md gains
the Steam rules that cost real time today: installs skip SmartScreen, updates are free,
the Wallet rule, and the recurring-billing caveat.
This commit is contained in:
2026-10-31 11:05:53 -07:00
parent 57ef9e7a5e
commit 8f79b20a18
10 changed files with 271 additions and 263 deletions
+71 -114
View File
@@ -1,153 +1,110 @@
# Distribution & release mechanics # Distribution & release mechanics
How the build becomes a file someone installs. **Policy — prices, entitlement, which channels How the build becomes an install on someone's PC. **Policy — prices, entitlement, why Steam is the
exist and why — is [`docs/ai/commercial.md`](docs/ai/commercial.md).** This file is the how. only channel, support — is [`docs/ai/commercial.md`](docs/ai/commercial.md).** This file is the how.
> The previous version of this document was a 736-line Polar/MSIX/obfuscation plan. It was > Superseded twice on 2026-10-31. The 736-line Polar/MSIX/obfuscation plan is in
> superseded on 2026-10-31; the reasoning that still matters lives in `commercial.md` and > `git log -- Distribution.md`; the zip-plus-winget plan written earlier the same day is in the
> `docs/ai/decisions.md`, and the full text is in `git log -- Distribution.md`. > same place. The reasoning that survives lives in `commercial.md` and `docs/ai/decisions.md`.
## 1. The artifact ## 1. The artifact
One self-contained single-file exe plus the bundled ffmpeg folder, zipped. No .NET install on the A self-contained win-x64 publish plus the bundled `ffmpeg\` folder, uploaded to a Steam depot. No
target machine, no installer framework, no admin rights. installer, no admin rights, no zip for anyone to mirror.
```bash ```bash
dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \ dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true \ -p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \ -p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \ -p:EnableCompressionInSingleFile=true \
-p:PublishReadyToRun=true -p:PublishReadyToRun=true -o artifacts/steam-stage
``` ```
- **`PublishTrimmed` stays OFF.** Trimming breaks WPF's XAML reflection (`Binding`, `DataTemplate`, - **`PublishTrimmed` stays OFF.** Trimming breaks WPF's XAML reflection (`Binding`, `DataTemplate`,
`StaticResource`). The exe is therefore fat — expect ~110–150 MB compressed with the runtime `StaticResource`). Expect ~110–150 MB in the depot; that is the cost of "no .NET install".
inside, and that is the accepted cost of "copy it and run it". - **ffmpeg stays outside the exe** — `ffmpeg\` beside it (the app's locator contract, see
- **ffmpeg stays outside the exe** — `ffmpeg\` beside it (already the app's locator contract, see [`output.md`](output.md) → "FFmpeg locator").
[`output.md`](output.md) → "FFmpeg locator"). Embedding it passes 200 MB and adds an extraction - **`steam_api64.dll` ships beside the exe** and is loaded at startup; without it the app cannot
step for no benefit. read DLC ownership. Its absence must degrade to the free profile, never to a crash.
- **WebView2 is the one machine dependency**: the Evergreen runtime ships with Windows 11 and most - **WebView2 is the one machine dependency.** Valve's Common Redistributables configure the
Windows 10; a genuinely clean machine needs the bootstrapper once. Say so in the download note. Evergreen bootstrapper as a prerequisite, so the depot declares it instead of us shipping it.
- **`AssemblyName` is still `ytLive`** → the shipped exe is `ytLive.exe` today. The rename to - **`AssemblyName` is still `ytLive`** → the exe is `ytLive.exe` today. The rename to
`LlamaCasty.exe` is queued (three pack URIs break — see `product.md`), and until it lands every `LlamaCasty.exe` is queued (three pack URIs break — see `product.md`), and **must land before the
release note names the real file. first depot upload**, because Steam's docs are explicit that renaming a live exe is painful.
- **Portable mode** (`portable.txt` → `.\data\`) survives as a feature for a copied install; it is
not a delivery vector.
**Portable mode.** A `portable.txt` marker beside the exe keeps the layout DB, `Assets` and ## 2. Steamworks setup, once
settings in `.\data\` instead of `%APPDATA%\ytLlive` (hung off `LayoutPathOverride`). Same binary,
USB-stick behaviour, nothing to uninstall.
## 2. Where releases live — the public handout repo 1. **Steamworks account** → onboarding (NDA, identity, company details, tax interview, payout bank,
then the **$100 Steam Direct fee** per app). The store page cannot be configured before the fee.
**The source repo stays private.** Releases live in a **second Gitea repo, 2. **App IDs.** One **base app** (free) and one **DLC app** (the lifetime Pro). Note both numbers:
`llamacasty-releases`**, which is push-mirrored to a **public GitHub repo** (`llamacasty/releases`). the DLC app ID is what the app passes to `BIsDlcInstalled`.
That public repo holds nothing but a README, an issue template, a known-issues page and the release 3. **Depot.** One depot holding the publish output; the base app and the DLC share the install
assets — so the only thing that can ever leak is content authored for publication. directory, and the DLC package grants no files of its own — it is entitlement only.
4. **Steamworks SDK.** Drop `steam_api64.dll` (+ `steam_appid.txt` for local runs) into the publish
Why it is shaped that way: GitHub has no "public issues on a private repo" switch, and issue output; the managed side is a thin wrapper (`Steamworks.NET` or hand-written interop) behind
visibility follows repo visibility. An empty public repo gives a public issue tracker, a durable `IEntitlement`.
binary URL for winget, and `releases/latest` for the update notice, without the code going anywhere. 5. **Store page** configured and made public **two weeks before release** — Valve's rule, and it is
the window where wishlists accumulate.
**The mirror carries refs, not release assets.** Git mirrors branches and tags; GitHub Release
uploads live outside the object store, so the zip is uploaded separately.
**Setup, once:**
1. Gitea → new repo `llamacasty-releases` (private) with `README.md`,
`.github/ISSUE_TEMPLATE/bug.yml`, `known-issues.md`.
2. Gitea → that repo → **Settings → Mirror Settings → push mirror** →
`https://github.com/llamacasty/releases.git` + a GitHub PAT. One-way and force-updating, so the
public copy can never drift from what was authored.
3. Create the free GitHub **organisation** `llamacasty` so URLs aren't tied to a personal account.
4. Point the Steam store page's support URL at
`https://github.com/llamacasty/releases/issues/new/choose` — the template chooser, never the
open-issue list.
**Rejected:** a submodule (publishes a pointer, not files), `git subtree split` (three static files
don't justify the history surgery, and one stray private file in the prefix leaks forever), and
serving zips from the droplet (uses the VPS as a CDN and makes the update notice hand-rolled).
## 3. Release ## 3. Release
``` ```
publish → zip → SHA-256 → gh release create → winget manifest PR → update notice publish → steam-stage → steamcmd + app_build.vdf → build upload → set live → announcement
``` ```
1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; upload 1. **Upload the depot** with `steamcmd +login <user> +run_app_build ..\scripts\app_build_<appid>.vdf
`LlamaCasty-<version>-win-x64.zip` with +quit`. The VDF names the app, depot, content root and branch.
`gh release create v1.0.0 LlamaCasty-1.0.0-win-x64.zip --repo llamacasty/releases 2. **Set the build live** in Steamworks → *Builds* → the new build → **Set Build Live** on the
--notes-file RELEASE.md`. Binaries live there, never on the droplet — free CDN, durable URLs, default branch. That *is* the release; there is no artifact to hash and no manifest to submit.
release notes in the same place, and `wingetcreate` reads `releases/latest` natively. 3. **Announce** with Steam Events (release notes appear in the library and on the store page).
2. **Record the hash.** `sha256sum` the zip; it goes in the winget manifest and in the release 4. **Verify**: install from a clean Steam account, confirm `BIsDlcInstalled` returns false, buy the
notes. This is the anti-tamper wall: the official channel delivers exactly this artifact or DLC (or use a test licence), confirm the high tiers unlock, then refund the test purchase.
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.** Commit the current version into the releases repo (so it arrives in the same
push or a follow-up commit) 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 (now free) developer account exists.
## 4. Signing — deferred until revenue **Updates** are the same upload with a higher build ID. The app keeps **no self-updater and no
version banner** — Steam is the update path, and that policy has not changed.
**Unsigned is the launch state.** Artifact Signing is bought at **two subscribers**, not before: ## 4. Entitlement wiring
it never removes the day-one warning (EV's SmartScreen bypass died in March 2024), so the only
thing it changes early is the publisher line. Costs and posture:
[`docs/ai/commercial.md`](docs/ai/commercial.md).
- **Azure Artifact Signing** (renamed from "Trusted Signing") — **$9.99/month Basic**, 5,000 - `IEntitlement` has two implementations: **Steam DLC** (lifetime, local) and **Polar** (monthly,
signatures, **requires a paid Azure subscription**. Individual identity validation is the slow network + 7–14 day lease). Resolution is `Pro = steamDlc || polarValid`; anything unresolved is
part (days to weeks) and must be renewed; start it when the purchase is decided. the free profile.
- **⛔ No EV certificate, and no certificate with an HSM.** Microsoft removed the EV instant-bypass - **Never sell the monthly inside the Steam build.** Steamworks requires in-app purchases to use
in March 2024, so EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys. the Steam Wallet; the app may *state* that monthly plans exist on the site, with no checkout,
- **The free alternative is the Microsoft Store**: the individual developer fee was waived, and no price and no payment field. Confirm the wording with Steamworks support before shipping it.
Microsoft signs the MSIX, so a Store install never meets SmartScreen at all. - **Never gate a feature.** The only delta is the output profile.
- **Unsigned costs a warning on every release**, because SmartScreen ties reputation to the file
hash. Signing accrues reputation to the **publisher** — the only exit from that loop.
- **Signing never clears the warning on day one either.** Plan for weeks.
- 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.
- Verify every release: `signtool verify /pa /v <file>`, and confirm the hash in the manifest
matches the uploaded zip.
## 5. Release checklist ## 5. Release checklist
- Build clean, 0 warnings (`./scripts/verify.sh`), full test suite green. - Build clean, 0 warnings (`./scripts/verify.sh`), tests green.
- `signtool verify /pa` passes on the shipped exe. - `steam_api64.dll` present; app starts **without** Steam and falls back to the free profile.
- Launch on a clean Windows profile: first-run works, recording writes where it claims, ffmpeg is - DLC ownership false → free profile; true → every tier offered.
found beside the exe, WebView2-loaded UI renders. - `LlamaCasty.exe` rename landed (never rename a live exe).
- Fresh install through `winget install LlamaCasty.LlamaCasty` on a second machine, then - Clean Steam install on a second machine, then an update pushed and observed.
`winget upgrade` over the previous version. - Store page public for the full two weeks; support contact and announcements configured.
- ZIP hash published; manifest PR merged; version notice updated. - Portable mode: `portable.txt` present → data lands in `.\data\`.
- Portable mode: `portable.txt` present → data lands in `.\data\`, deleting the folder deletes the
app's footprint.
## 6. Legal and takedown ## 6. Legal and takedown
- **`LICENSE`** (EULA) forbids redistribution, resale, reverse-engineering and circumvention, and - **`LICENSE`** (EULA) forbids redistribution, resale, reverse-engineering and circumvention, and
disclaims warranty. It is what makes a mirror site a licence breach rather than a grey area. disclaims warranty.
- **`PRIVACY.md`** must name any third party the app contacts — today that is Google/YouTube, - **`PRIVACY.md`** must name any third party the app contacts — Google/YouTube today, **Polar** the
ffmpeg is local, and **Polar** is added the day licence validation ships. day licence validation ships. Steam itself is named by Valve's own policy, not ours.
- **`THIRD-PARTY-NOTICES.txt`** stays shipped and shown in About; the ffmpeg pin stays - **`THIRD-PARTY-NOTICES.txt`** stays shipped and shown in About; the ffmpeg pin stays
`lgpl-shared`, and no GPL component may be linked (see `product.md` → "Licensing"). `lgpl-shared`; no GPL component may be linked (see `product.md` → "Licensing").
- **DMCA**: statutory damages need a registration ($65), and a takedown needs only the notice — - **DMCA**: a takedown needs only the notice — host, link, good-faith statement, signature.
host, link, good-faith statement, signature. Worth doing once there is something to pirate. Statutory damages need a registration ($65). Worth doing once there is something to pirate.
- **What actually protects the product** is in `commercial.md` → "Anti-piracy posture": signed - **What actually protects the product** is in `commercial.md` → "Anti-piracy posture": a free tier
hash-pinned delivery, short leases, a free tier good enough that a crack buys nothing, and a good enough that a crack buys nothing, Steam's own delivery, short Polar leases, and a licence ID
licence ID in About. **Not obfuscation, not a native DLL, not hardware locking.** in About. **Not obfuscation, not a native DLL, not hardware locking.**
## 7. Effort, once ## 7. Effort, once
| Step | Effort | | Step | Effort |
|---|---| |---|---|
| Publish profile + portable mode | 0.5 day | | Steamworks onboarding + $100 fee | 0.5–1 day, mostly waiting on verification |
| Releases repo + Gitea→GitHub push mirror | 0.5 day | | Publish profile + `steam_api64.dll` + `IEntitlement` Steam path | 1 day |
| `gh release` + hash + version notice | 0.5 day | | Depot + first `steamcmd` upload | 0.5 day, then ~15 min per release |
| First winget manifest (`wingetcreate new`) | 0.5 day, then ~15 min per release | | Store page + assets (screenshots, capsule art) | 1–2 days |
| Clean-machine + `winget` smoke test | 0.5 day | | Clean-account install + DLC purchase/refund test | 0.5 day |
| Artifact Signing profile + CI step (deferred) | 0.5 day, at two subscribers |
+22 -24
View File
@@ -1,37 +1,35 @@
# HANDOFF — 2026-10-31 (commercial settled; the surge week starts at the artifact, not the Text layer) # HANDOFF — 2026-10-31 (Steam is the only channel; the site says so)
## State ## State
- Branch `main`, level with `origin` through `7827d23` + `0b41b7d`; this doc unit is local. - Branch `main`, level with `origin` through `57ef9e7`; this unit is local.
- **Commercial is settled and written down**: freemium (free = whole app at a 921,600 px / 30 fps - **Distribution is Steam-exclusive** (creator ruling 2026-10-31): a free app on Steam with the paid
profile, stream **and** recording), Pro $10/mo or $400 lifetime through **Polar with no server at edition as a **Steam DLC**; the **$10/month** plan is sold off-Steam by Polar and *stated, never
all** — the client calls the customer-portal API with the key, and channel binding rides in sold*, inside a Steam build. Winget, the direct zip, the Store and the certificate that served
`conditions: {channel_id}`. Signing is **deferred until two subscribers**; the Store account is them are retired. `commercial.md` → Channels owns policy, `Distribution.md` the depot mechanics,
now free; binaries live in `llamacasty-releases` push-mirrored to public GitHub. `decisions.md` the cause of death.
- **Support policy recorded**: email, best effort, no SLA, refunds on request; no training service; - **Entitlement is `steamDlcOwned || polarKeyValid`**, both resolving to one `OutputProfile`.
a repeated question becomes a training video on `@llamacasty`. `MARCOM.md` holds the loop. `steam_api64.dll` ships beside the exe; the app must start without Steam and fall back to free.
- `www.llamacasty.com` resolves and 301s to the apex (CNAME + YunoHost domain + LE cert + nginx - **Site updated and deployed** (`a9d07ce`): "Coming soon to Steam" ×6, zero Microsoft references,
`.d` include). Recipe in `MyMistakes.md`. privacy page says Steam bills the paid edition. The CTA gains a wishlist button only when the
store page is public.
- `www.llamacasty.com` resolves and 301s to the apex. Recipe in `MyMistakes.md`.
## Next action ## Next action
**Surge week, day 1: artifact shape.** `LlamaCasty.exe` rename (three pack URIs: 1. **Steamworks onboarding** (creator is in it now): NDA → identity → company details → tax
`MainWindow.xaml:14`, `OverlayHost.xaml:96`, `RenameRecordingDialog.xaml:5`), `scripts/publish.sh` interview → payout bank → **$100 Steam Direct** → create the base app, note the App ID.
(self-contained win-x64, no test artifacts), `portable.txt` via `LayoutPathOverride`. It ends when 2. Then the surge slice: artifact shape (rename + publish + `steam_api64.dll`) → OutputProfile gate
the zip runs on a clean profile. + TASK 53 geometry tests → entitlement seam → licence UI + a Steam-less launch test.
Then: OutputProfile gate + TASK 53 geometry tests → entitlement seam + system messages → licence UI
and a real test broadcast. Day 5 if all four land: the visibility picker.
## Landmines ## Landmines
- **Nothing may gate a feature.** The only paid delta is the output profile. `ShowBrandFlash` stays - **Nothing may gate a feature.** The only paid delta is the output profile; `ShowBrandFlash` stays
`false` and entitlement must never enable it. `false` and entitlement must never enable it.
- **The org access token never ships.** `organization_id` is public; the token is not. No claim - **No checkout inside a Steam build** — the Wallet rule. The monthly is named, never sold, in-app.
page, no callback endpoint, no key database — `conditions` does the binding. - **The org access token never ships.** `organization_id` is public; the token is not.
- **The site publishes the model, never the numbers**, and its CTA stays "Coming soon to the - **The site publishes the model, never the numbers**, and never takes payment or links a checkout.
Microsoft Store" until the creator says the download exists. - **No self-updater and no version banner**: Steam updates the app.
- **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 - Not every hex is a theme colour — `BorderColorPalette`/chat defaults are USER DATA; on-stream
graphics come from `OutputPalette`. A red settings control is still a bug. graphics come from `OutputPalette`. A red settings control is still a bug.
+23
View File
@@ -166,6 +166,29 @@ in Studio → Settings → Channel → **Feature eligibility**:
*"Advanced YouTube features aren't available for this account"*. If ID verification is ever *"Advanced YouTube features aren't available for this account"*. If ID verification is ever
wanted on a personal channel, do it **before** converting to a brand account. wanted on a personal channel, do it **before** converting to a brand account.
### STEAM IS THE DISTRIBUTOR — AND WHAT IT DOES NOT ALLOW (2026-10-31)
**Why recorded:** the distribution answer changed three times in one day (winget → zip+GitHub →
Steam), and each pivot invalidated a doc written hours earlier. These are the facts that won't
change.
- **Steam installs skip SmartScreen entirely** — Valve's client delivers the files, so there is no
browser download, no Mark-of-the-Web and no publisher warning. An unsigned exe is *fine* in this
channel, and a certificate is only needed if a non-Steam download ever exists.
- **Steam updates the app** — no self-updater, no version banner, no update endpoint.
- **⛔ In-app purchases must use the Steam Wallet.** Steamworks, *Microtransactions (In-Game
Purchases)*: *"For any in-game purchases, you'll need to use the microtransaction API so Steam
customers can only make purchases from the Steam Wallet."* So a subscription sold through Polar
inside a Steam build is a policy violation. **Lifetime = plain DLC bought on the store page**
(the app only calls `BIsDlcInstalled`); the monthly is *stated*, never *sold*, in-app.
- **Steam can technically do recurring billing** (`ISteamMicroTxn` + `ProcessAgreement`) but its own
doc opens with *"Recurring Subscriptions is not a fully supported feature"* and requires a server
of yours to poll renewals — more infrastructure than Polar, for a 30% cut.
- **Renaming a live exe is painful** (Steamworks → *Tip – Renaming a live exe*). Do the
`LlamaCasty.exe` rename **before** the first depot upload, not after.
- **The Steam store page must be public two weeks before release**, and the $100 Steam Direct fee
must be paid before the page can be configured at all.
### CONVERTING A MEDIAPIPE .tflite (WITH CUSTOM OPS) TO ONNX — AND PROVING IT (RECIPE, TASK 51) ### CONVERTING A MEDIAPIPE .tflite (WITH CUSTOM OPS) TO ONNX — AND PROVING IT (RECIPE, TASK 51)
**Problem:** MediaPipe segmentation models end in the custom op `Convolution2DTransposeBias`; **Problem:** MediaPipe segmentation models end in the custom op `Convolution2DTransposeBias`;
+11 -8
View File
@@ -10,13 +10,15 @@
**The surge week (4 days, in this order).** Not every row below — this slice only, because it is **The surge week (4 days, in this order).** Not every row below — this slice only, because it is
what the first public release is waiting on: what the first public release is waiting on:
1. **Artifact shape** — `LlamaCasty.exe` rename (three pack URIs), `scripts/publish.sh`, 1. **Artifact shape** — `LlamaCasty.exe` rename (three pack URIs), `dotnet publish` profile with
portable mode. Ends with a zip that runs on a clean machine. `steam_api64.dll` beside the exe. Ends with a build that starts standalone and falls back to the
free profile.
2. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier 2. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier
entries, plus the TASK 53 geometry tests that pin every tier. entries, plus the TASK 53 geometry tests that pin every tier.
3. **Entitlement seam** — `IEntitlement` against Polar's customer-portal API with a fake for tests, 3. **Entitlement seam** — `IEntitlement` with the Steam DLC path and a fake for tests, Polar's
`--activate` parsing, system messages. customer-portal path for the monthly, `--activate` parsing, system messages.
4. **Real-machine day** — licence UI skeleton, a clean-profile install, a test broadcast. 4. **Real-machine day** — licence UI skeleton, a Steam-less launch proving the free profile, a test
broadcast.
Detail: [`commercial.md`](docs/ai/commercial.md) → Output profile / Entitlement; Detail: [`commercial.md`](docs/ai/commercial.md) → Output profile / Entitlement;
[`Distribution.md`](Distribution.md). Day 5, if the four go well: the visibility picker (a hard [`Distribution.md`](Distribution.md). Day 5, if the four go well: the visibility picker (a hard
@@ -49,11 +51,12 @@ Dog test.
| 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 | 🔶 | 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) | | 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's customer-portal API (org ID public, org token never in the app), `conditions: {channel_id}`, 2 activations, 7–14 day lease, `--activate` CLI. **No claim page, no server** | [`commercial`](docs/ai/commercial.md) | | — | ☐ | **Steamworks entitlement** — `steam_api64.dll` + `SteamAPI.Init`, `BIsDlcInstalled(dlcAppId)` behind `IEntitlement`, free profile when Steam is absent | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **Entitlement seam** — Polar customer-portal path for the monthly (org ID public, org token never in the app), `conditions: {channel_id}`, 7–14 day lease, `--activate` CLI. **No claim page, no server** | [`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) | | — | ☐ | **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** — key entry + four states, 7/1/0-day notices, portal link, licence ID in About | [`commercial`](docs/ai/commercial.md) | | — | ☐ | **Licence UI** — four states, 7/1/0-day notices, "monthly is on the website, not Steam" line, 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) | | — | ☐ | **System messages** — `SystemMessage` + `MessageCenter`, strip above the dock, dedupe by key, short history, never composited | [`ui-surface`](docs/ai/ui-surface.md) |
| — | ☐ | **Packaging** — rename, `scripts/publish.sh`, portable mode; then `llamacasty-releases` repo + Gitea→GitHub push mirror, `gh release create`, winget manifest. Signing **deferred** until two subscribers | [`Distribution.md`](Distribution.md) | | — | ☐ | **Steam packaging** — rename, `publish` output + `steam_api64.dll`, depot, `steamcmd` upload, store page + assets, DLC app | [`Distribution.md`](Distribution.md) |
| — | ☐ | **Training-video series** — capture the first 8–10 recurring questions as videos on `@llamacasty`; each is documentation, channel content and monetization at once | MARCOM → support→content loop | | — | ☐ | **Training-video series** — capture the first 8–10 recurring questions as videos on `@llamacasty`; each is documentation, channel content and monetization at once | MARCOM → support→content loop |
| 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) |
+5 -6
View File
@@ -1,11 +1,10 @@
# 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 > ⛔ **THE ROUTE IN THIS FILE IS SUPERSEDED — 2026-10-31.** Distribution is **Steam exclusive**: a
> `llamacasty.com` (GitHub Releases) + winget primary**; **Steam and the Microsoft Store are parked**, > free app on Steam with the paid edition as a Steam DLC; the monthly plan is sold off-Steam. There
> third and fourth. Store IAP is not the billing path — **Polar** is, and the paid delta is output > is no direct download, no winget and no Store submission. Live policy:
> quality. Live policy: [`docs/ai/commercial.md`](../docs/ai/commercial.md); mechanics: > [`docs/ai/commercial.md`](../docs/ai/commercial.md); mechanics: [`Distribution.md`](../Distribution.md).
> [`Distribution.md`](../Distribution.md); cause of death: [`docs/ai/decisions.md`](../docs/ai/decisions.md). > The MSIX build steps below still work and are simply unused.
> 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".
+1 -1
View File
@@ -52,7 +52,7 @@ C# / WPF (.NET 8) following MVVM:
| 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, packaging, legal posture | [`docs/ai/product.md`](docs/ai/product.md) | branding, naming, packaging behaviour, legal obligations | | 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 | | Prices, entitlement, channels, support | [`docs/ai/commercial.md`](docs/ai/commercial.md) | anything about money: pricing, licence keys, output cap, Steam/Steamworks, the monthly plan, 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 |
+76 -60
View File
@@ -3,7 +3,7 @@
The single source of truth for what LlamaCasty charges, what a licence entitles, how it ships and The single source of truth for what LlamaCasty charges, what a licence entitles, how it ships and
what support it does and does not include. Read before any pricing, licensing, distribution, what support it does and does not include. Read before any pricing, licensing, distribution,
storefront, support or promo work. Cause of death for every retired path: storefront, support or promo work. Cause of death for every retired path:
[`decisions.md`](decisions.md). Release mechanics (build, zip, mirror, winget): [`decisions.md`](decisions.md). Release mechanics (build, depot, SteamPipe upload):
[`Distribution.md`](../../Distribution.md). [`Distribution.md`](../../Distribution.md).
## Model ## Model
@@ -43,11 +43,24 @@ The cap is a pixel budget plus a frame rate, not the word "720p":
(cancel, revoke, sign-out, channel switch) greys them again on the next refresh. (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. - **Bitrate follows the profile** — a capped user is not offered a 1080p bitrate.
## Entitlement ## Entitlement — two sources, one profile
**Polar is the merchant of record and the only source of truth.** It takes the money, remits **Pro is `steamDlcOwned || polarKeyValid`.** Both resolve to the same `OutputProfile`; neither
VAT/GST, handles refunds and chargebacks, and holds the keys. **There is no server of ours in the touches a feature. Fail closed: anything unresolved is the free tier.
loop** — no claim page, no callback endpoint, no key database.
### Steam (lifetime)
- **Steam owns the lifetime SKU.** `ISteamApps::BIsDlcInstalled(dlcAppId)` after `SteamAPI.Init`.
It is a local check against Steam's own state — no server, no key, no lease.
- **Steam down or not running** → free profile, with a line explaining why, never a refusal to
start. A Steam build launched outside Steam is the common case for people who pin shortcuts.
- **No licence key exists for the Steam SKU**, so nothing to paste, email or lose.
### Polar (monthly, sold off-Steam)
**Polar is the merchant of record and the only source of truth** for the monthly plan. It takes the
money, remits VAT/GST, handles refunds and chargebacks, and holds the keys. **There is no server of
ours in the loop** — no claim page, no callback endpoint, no key database.
- **Key** — `LCYT-` prefix; org `c05fb364-…` and the endpoints are in `CREDENTIALS.md`. No - **Key** — `LCYT-` prefix; org `c05fb364-…` and the endpoints are in `CREDENTIALS.md`. No
product is created yet. product is created yet.
@@ -84,57 +97,51 @@ channel ID. That is accepted: the free tier is the defence, the licence is a lea
determined crack buys little. Do not propose a signed token, a hardware fingerprint or a server determined crack buys little. Do not propose a signed token, a hardware fingerprint or a server
to "fix" it. to "fix" it.
## Channels ## Channels — Steam only
**One artifact, several ways to get it.** A self-contained single-file `LlamaCasty.exe` plus the **Steam is the distributor.** One free app on the store, the paid edition as a Steam **DLC**, and
bundled `ffmpeg\` folder, zipped. No .NET install, no admin, no installer framework. Trimming no other vector: no direct download, no winget, no Scoop/Chocolatey, no Microsoft Store. The store
stays off (WPF reflection), so expect ~110–150 MB. page is the single authoritative source for the download, the stats and the reviews.
1. **Direct download** — `llamacasty.com` links the zip. **Binaries live in a second Gitea repo - **Lifetime Pro = Steam DLC.** Bought on the **store page**; the app only reads ownership
(`llamacasty-releases`), push-mirrored to a public GitHub repo**, so the source repo stays (`ISteamApps::BIsDlcInstalled` / `UserHasLicenseForApp`). No purchase flow in the app, no Steam
private and the handout repo contains nothing but a README, an issue template and the release Wallet code, nothing to reconcile.
assets. The mirror carries refs, not GitHub Release assets — the zip is uploaded with - **The $10/month plan is sold outside Steam** and is **never purchasable inside a Steam build**.
`gh release create`. Mechanics: [`Distribution.md`](../../Distribution.md). Steamworks requires in-app purchases to go through the Steam Wallet — so the app *states* that
2. **winget — the primary install path.** Manifest in `microsoft/winget-pkgs`, `InstallerType: monthly plans exist on the site and does not sell them. If the app ever grows a checkout for it,
portable`, pinned SHA-256. That hash pin is the anti-tamper wall: the official channel delivers that is the policy violation.
exactly the published artifact or fails. One `wingetcreate update` PR per release. Optional: - **Boot shape:** the app ships `steam_api64.dll` beside the exe and calls `SteamAPI.Init`. That
Scoop and Chocolatey manifests over the same zip. is a functional dependency, not the rejected "native helper DLL for IP protection" — Steam
3. **Steam — parked at $100.** Lifetime Pro as DLC only (Steam has no subscriptions), page public ownership cannot be checked without it. Launched without Steam running, the app must fall back to
two weeks before release, 30% cut, and price parity with the site. It is the only channel with the **free profile** and say so, never refuse to start.
discovery *and* an install that never meets SmartScreen, and it updates itself — which is why it - **Updates are Steam's.** The depot push is the update; the app keeps no updater and no version
stays in the plan despite the cut. banner (the old "no self-updater" rule stays, for the same reason: it works differently).
4. **Microsoft Store — parked, and now free.** Microsoft waived the **$19** individual developer - **Portable mode is no longer a distributable.** `portable.txt` remains as a feature for anyone who
fee (and removed the **$99** company fee in May 2026). It is the only storefront that bills a copies the installed folder; it is not a delivery mechanism.
subscription natively *and* the only zero-cost route to a Microsoft-signed install with no - **Steam Direct** is **$100 per app**, recoupable after $1,000 adjusted gross revenue.
SmartScreen warning, because Microsoft signs the MSIX. Bans self-updaters, so a Store build - **Price parity:** if the lifetime is ever sold off-Steam, it may not be cheaper than the Steam
ships without one. price.
**Not channels, ever:** Patreon or any subscription platform of our own, paywalled early access, **Not channels, ever:** direct download, winget/Chocolatey/Scoop, the Microsoft Store, Patreon or
a paid beta, a Discord. Content is the funnel; the channel is only the checkout. any subscription platform of our own, paywalled early access, a paid beta, a Discord. Content is
the funnel; Steam is the checkout.
**Portable mode:** a `portable.txt` marker beside the exe keeps the layout DB and settings in **Support surfaces this creates:** the Steam store page's support contact, Steam **Announcements**
`.\data\` instead of `%APPDATA%\ytLlive` — the USB-stick case, hung off `LayoutPathOverride`. for known issues and release notes, Steam Discussions left pinned and unread, and one public issue
It is the reason the zip survives even if Steam becomes the primary channel. repo for bugs that survive the friction of needing an account.
## Signing — deferred until there is revenue ## Signing — not needed for the Steam channel
- **Not bought at launch.** Artifact Signing is listed in the costs as **deferred**: it is paid for - **Steam installs never meet SmartScreen.** Valve's client delivers the files, so there is no
out of Pro revenue, and the trigger is **two subscribers**. browser download, no Mark-of-the-Web and no "unknown publisher" wall. The certificate that
- **What it actually buys:** a named publisher line, fewer antivirus false positives on a ~150 MB existed only to soften that wall has nothing to do.
self-extracting exe, and tamper evidence for hand-downloaded zips. It does **not** remove the - **Artifact Signing** (renamed from "Trusted Signing") stays on the shelf: Azure Artifact
SmartScreen warning on day one — Microsoft deleted the EV instant-bypass in March 2024, so a new Signing, $9.99/month Basic, requires a paid Azure subscription, individual identity validation
certificate has zero reputation and clears only with download volume. is the slow part. Buy it the day a non-Steam download exists — the zip case, not this one.
- **Azure Artifact Signing** (renamed from "Trusted Signing"), **$9.99/month Basic**, up to 5,000 - **Never EV, never a certificate with an HSM.** Microsoft removed the EV SmartScreen instant-bypass
signatures, **requires a paid Azure subscription**. Individual identity validation exists and in March 2024; it buys nothing.
must be renewed before it expires; it is the slow (days-to-weeks) part, so start it *when the - **Steam does not sign our exe either** — it just never puts it through the browser gauntlet.
purchase is decided*, not before. Windows may still show a UAC prompt for the app; that is elevation, not publisher trust.
- **Reputation accrues to the publisher, not the file.** Unsigned means the warning on **every**
release, forever, because SmartScreen ties reputation to each file hash. Signing is the only exit
from that per-release tax.
- **The free alternative is the Store** (see Channels 4): a free developer account gets
Microsoft-signed installs with no certificate of ours.
- **No EV certificate, ever** — $400+/yr for what OV buys, and the bypass is gone.
- Every signed release: same pass, SHA-256 in the winget manifest, licence ID visible in About.
## Support ## Support
@@ -174,14 +181,19 @@ Cash cost **$0**; it is priced in support time and forgone list revenue.
|---|---| |---|---|
| Domain | ~$15/yr | | Domain | ~$15/yr |
| Droplet | $0 — serves the existing site | | Droplet | $0 — serves the existing site |
| GitHub public repo (releases + issues), Gitea, winget, Scoop, Chocolatey | $0 | | Steam Direct | **$100 once**, recoupable after $1,000 AGR |
| Polar | 5% + 50¢ per sale → $10 nets $9.00, $400 nets $379.50 | | Steam revenue share | 30% on the lifetime DLC → $400 nets **$280** |
| Azure Artifact Signing — **deferred** | $9.99/month (~$120/yr), bought at two subscribers | | Polar | 5% + 50¢ per sale → $10 nets $9.00 (monthly only) |
| Steam Direct (optional) | $100 once, recoupable after $1,000 AGR | | Steamworks, public issue repo, Gitea | $0 |
| Microsoft Store (optional) | **$0** — the individual fee was waived | | Azure Artifact Signing — **not needed** | Steam installs don't meet SmartScreen |
| Microsoft Store — **not used** | — |
Fixed year one ≈ **$15** while unsigned, ≈ **$135** once signing starts. **Break-even is two Fixed year one ≈ **$115** (domain + Steam Direct, one time). The Steam cut is the price of not
subscribers** (15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years. paying for a certificate, a storefront or an update path.
**Signing is now moot as a launch cost.** Steam installs never meet SmartScreen, so the certificate
that existed only to soften that wall has no job. Artifact Signing stays on the shelf for the day a
non-Steam download exists.
## Anti-piracy posture ## Anti-piracy posture
@@ -190,9 +202,13 @@ 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 - **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. build buys almost nothing and forfeits updates, support and the publisher's signature.
- **Hash-pinned delivery, signed once revenue exists** — a patched build cannot arrive through - **Steam DRM is optional and not needed.** The DLC check is already the entitlement; adding
winget, and a forged one is not signed as us. Until signing starts, the SHA-256 pin is the wall. Valve's DRM wrapper buys nothing a determined crack respects, and it can break an exe on a
- **Short leases** — a revoked or refunded key dies inside the grace window. machine that never runs Steam.
- **The distribution is not a handout.** Steam deliveries go through Valve's client and account, so
there is no public zip for a mirror site to repackage, and no hash pin for us to maintain.
- **Short leases** — a revoked or refunded Polar key dies inside the grace window; a refunded Steam
DLC stops reporting ownership the next time Steam is asked.
- **Licence ID in About** — a leaked build traces back to a purchase. - **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 ⛔ **Rejected, do not re-propose:** a native helper DLL for "IP protection", obfuscation, hardware
+24
View File
@@ -23,6 +23,30 @@ the subsystem files. This is the graveyard with cause of death.
remote is Gitea). The route is a separate **`llamacasty-releases`** Gitea repo push-mirrored to a remote is Gitea). The route is a separate **`llamacasty-releases`** Gitea repo push-mirrored to a
public GitHub repo: issues and assets public, code private, one publish direction. public GitHub repo: issues and assets public, code private, one publish direction.
## Distribution — Steam exclusive (2026-10-31, final)
**Steam is the only vector.** A free app on Steam with the paid edition as a Steam **DLC**; the
monthly plan is sold outside Steam and never purchasable in a Steam build. Live rules:
[`commercial.md`](commercial.md).
**Why the other vectors died, all on the same day:**
- **Direct download + winget** (the plan of that morning) — a zip on a second Gitea repo mirrored to
public GitHub, hash-pinned into winget. Superseded because Steam supplies delivery, stats,
discovery, install trust and updates in one move, and because an unsigned zip meets SmartScreen on
every single release while a Steam install never meets it at all.
- **Microsoft Store** — was free, and free is not the same as wanted. It was the last thing keeping
a Microsoft account, an MSIX pipeline and a publisher line in the plan.
- **Azure Artifact Signing** — never needed once Steam delivers the install. Kept on the shelf for
the day a non-Steam download exists.
- **The public `llamacasty-releases` repo** survives only as the **issue tracker**; it holds no
binaries, no manifest and no version file.
**The constraint that shapes the app:** Steamworks requires in-app purchases to use the **Steam
Wallet**. Therefore the monthly is *stated*, never *sold*, inside the Steam build — no checkout, no
price, no payment fields. The lifetime is plain DLC bought on the store page, so it needs no
transaction code at all.
## Support — decided 2026-10-31 ## Support — decided 2026-10-31
- **No training service, no helpdesk, no SLA.** Email, best effort, refunds on request without - **No training service, no helpdesk, no SLA.** Email, best effort, refunds on request without
+1 -1
View File
@@ -14,7 +14,7 @@ Read the file whose row matches the task. There is no required reading order.
| [`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 licensing prohibitions, the freemium posture, auth/account assumptions, and packaging behaviour | Branding, naming, packaging, or a legal/third-party obligation | | [`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 via `conditions` (no server, no claim page), the channels (releases repo → winget → Steam → Store), signing (deferred), the Founding 200 promo, costs, **support policy**, anti-piracy posture | Any question about money or support: pricing, licence keys, what free gets, how it ships, what a sale costs | | [`commercial.md`](commercial.md) | The live commercial policy: model and prices, the output-profile cap (stream **and** recording), entitlement (`steamDlcOwned \|\| polarKeyValid`; Steam DLC for lifetime, Polar for the monthly, no server), **Steam as the only channel**, signing (moot), the Founding 200 promo, costs, **support policy**, anti-piracy posture | Any question about money or support: 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 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 | | [`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 |
+37 -49
View File
@@ -189,46 +189,40 @@ 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 (winget primary; Steam + Store parked) ## Windows packaging & distribution (Steam only)
Live channel policy, the artifact and signing costs: [`commercial.md`](commercial.md). Build Live channel policy, the artifact and costs: [`commercial.md`](commercial.md). Build and depot
mechanics (publish flags, the releases repo mirror, the winget manifest): mechanics: [`Distribution.md`](../../Distribution.md). Retired routes and cause of death:
[`Distribution.md`](../../Distribution.md). Retired routes and cause of death:
[`decisions.md`](decisions.md). [`decisions.md`](decisions.md).
**The channels, in order:** direct download from `llamacasty.com` (binaries in a separate **One vector: Steam.** A free app on Steam with the paid edition as a Steam **DLC**; the monthly
`llamacasty-releases` repo, push-mirrored to public GitHub) → **winget**, the primary install path → plan is sold outside Steam and is never purchasable inside a Steam build (Steamworks requires the
Steam (parked, $100, lifetime Pro as DLC only; the only channel with discovery *and* an install Steam Wallet for in-app purchases). No direct download, no winget, no Microsoft Store.
that never meets SmartScreen) → Microsoft Store (parked, **free** account since the $19 individual
fee was waived — the only zero-cost route to a Microsoft-signed install).
**Consequences that are architecture, not packaging detail:** **Consequences that are architecture, not packaging detail:**
- **⛔ There is no self-updater, by policy as well as by history.** Velopack was removed on - **⛔ There is no self-updater, and now no version banner either.** Steam delivers updates; the
2026-10-22 (TASK 48 slice 3) because it downloaded *and executed*, which Store policy 10.2.2 app keeps nothing that downloads or executes, for the same Store-policy reason it always had.
forbids — and the same rule would close the Store for good if it came back. Updates arrive `ytLive.Tests/StorePackagingTests.cs` must keep asserting that no self-updater returns.
through the package manager (`winget upgrade`) plus an in-app "newer version exists" banner read - **⛔ `steam_api64.dll` is a functional dependency**, not the rejected "native helper DLL for IP
from a version file in the releases repo. `ytLive.Tests/StorePackagingTests.cs` must keep protection" — Steam DLC ownership cannot be read without it. The app must start without Steam
asserting that no self-updater returns. running and fall back to the free profile.
- **The MSIX package stays buildable** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish - **The MSIX package stays buildable but is now unused** — `scripts/package-msix.sh` (TASK 48
`Release -r win-x64 --self-contained` into `artifacts/msix-stage`, copy slice 2) still produces `artifacts/LlamaCasty.msix` from `Packaging/Package.appxmanifest` +
`Packaging/Package.appxmanifest` in as `AppxManifest.xml` + `Packaging/Assets/`, then `Packaging/Assets/`. It was the Store channel's artifact; with Steam exclusive there is no
`makeappx pack` → `artifacts/LlamaCasty.msix` (159 MB, gitignored; `MSIX_PFX` signs it for a submission target. The full-trust `packagedClassicApp` findings below remain true and are still
local install). It is the Store channel's artifact, unsubmitted until that account exists. The worth reading before any packaged build is attempted.
manifest is a **full-trust `packagedClassicApp` at `mediumIL`** with `runFullTrust` + webcam + - **⛔ Bundle ffmpeg — no runtime downloads** (was Store policy 10.2.2; still right for a Steam
microphone. `Square310x310Logo`/`Wide150x150Logo` traps are in `MyMistakes.md`. Logos come from depot, and it fixes the 2026-09-01 404). See [`output.md`](output.md) → "FFmpeg locator".
the app's own artwork via `scripts/make-store-logos.py`.
- **⛔ Bundle ffmpeg — no runtime downloads** (was Store policy 10.2.2; still right for a direct
download, and it fixes the 2026-09-01 404). See [`output.md`](output.md) → "FFmpeg locator".
- **⛔ 64-bit Windows only (creator ruling 2026-10-09).** Both csproj files set - **⛔ 64-bit Windows only (creator ruling 2026-10-09).** Both csproj files set
`<PlatformTarget>x64</PlatformTarget>` (the test project too — an AnyCPU test project `<PlatformTarget>x64</PlatformTarget>` (the test project too — an AnyCPU test project
referencing an x64 app raises MSB3270 and breaks the 0-warning gate). Why: the bundled ffmpeg referencing an x64 app raises MSB3270 and breaks the 0-warning gate). Why: the bundled ffmpeg
is the win64 build, ONNX Runtime 1.31 ships no win-x86 native (only win-x64/win-arm64), and a 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 - **Portable mode is a feature, not a channel:** `portable.txt` beside the exe keeps the layout DB
and settings in `.\data\` (hung off `LayoutPathOverride`), so a USB stick carries someone's and settings in `.\data\` (hung off `LayoutPathOverride`), so a copied install carries someone's
scenes. The `winget` install and the hand-to-a-friend zip are the same artifact. scenes. Steam installs it; the marker is for afterwards.
**What does NOT change: no watermark and no feature gating.** The free tier is the whole app at a **What does NOT change: no watermark and no feature gating.** The free tier is the whole app at a
720p30 profile. `Monetization` above governs. 720p30 profile. `Monetization` above governs.
@@ -252,32 +246,26 @@ 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 — deferred until revenue; Azure Artifact Signing, never EV ### Signing — not needed, because Steam installs the app
Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**, so EV Steam's client delivers the files, so there is no browser download, no Mark-of-the-Web and no
($400+/yr) buys what OV (~$150–300/yr) buys; both need an HSM or hardware token and renew inside SmartScreen wall. **The certificate has no job in this channel.** It stays on the shelf for the day
**460 days**. The 2026-10-31 ruling was **Azure Trusted Signing (~$9.99/month)**; the later ruling a non-Steam download exists:
the same week is that it is **deferred until two subscribers exist**, because it never removes the
warning on day one — only download volume does — and the storefront alternative is now free.
- **Renamed**: it is **Azure Artifact Signing** now. Basic is $9.99/month, 5,000 signatures, and it - **Azure Artifact Signing** (renamed from "Trusted Signing") — $9.99/month Basic, up to 5,000
**requires a paid Azure subscription**; individual identity validation exists and must be signatures, **requires a paid Azure subscription**; individual identity validation is the slow
renewed, and it is the slow (days-to-weeks) part — start it when the purchase is decided. part and must be renewed.
- **Unsigned is a per-release tax**: SmartScreen ties reputation to the file hash, so an unsigned - **Never EV** — Microsoft removed the EV SmartScreen instant-bypass in March 2024, so EV buys what
product warns on **every** release, forever. Signing is the only exit from that loop. OV buys. **Never a certificate with an HSM** for this product.
- **The free exit is the Microsoft Store** — the individual developer account fee was waived, and - **Steam does not sign the exe.** A UAC prompt for the app is elevation, not publisher trust.
Microsoft signs the MSIX, so a Store install never meets the wall.
- **Never EV, never a certificate with an HSM**, for this product.
Signing steps: [`Distribution.md`](../../Distribution.md). Costs: [`commercial.md`](commercial.md).
### Distribution and licensing are independent — the channel never decides billing ### 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 The Steam build sells the lifetime as **Steam DLC** and reads it with `BIsDlcInstalled`, while the
explicitly permits a secure third-party purchase API for non-game PC products), so "which monthly plan is billed by **Polar** outside Steam. Steamworks forbids taking payment inside the app
channel?" never implies "which billing?". **Polar is the live provider** (2026-10-31) because the with anything but the Steam Wallet, so the app states where the monthly lives and never sells it.
2026-10-30 freemium ruling created something to sell; winget has no storefront at all, so the The lesson worth keeping: the licensing choice rides on a **monetization** decision, not on a
licence path is entirely ours there. The lesson worth keeping: the licensing choice rides on a distribution decision.
**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