Renames AssemblyName to LlamaCasty before the first Steam depot — Steamworks is explicit that renaming a live exe is painful, so this had to land first, not later. Four assembly-qualified pack URIs carried the old name, not three: MainWindow (window icon), OverlayHost (logo), RenameRecordingDialog and KeepBroadcastDialog. Pack URIs resolve by assembly name at runtime, so a stale one compiles, passes every test and then silently renders nothing the first time that view opens. AssemblyIdentityTests guards both halves — the assembly name, and no source XAML still naming the legacy assembly. Packaging/Package.appxmanifest and package-msix.sh carry the exe name too. scripts/publish.sh replaces the by-hand publish: single-file win-x64, ReadyToRun, ffmpeg beside the exe, and it refuses to finish if ffmpeg, LICENSE, PRIVACY.md or THIRD-PARTY-NOTICES.txt are missing, or if a test assembly leaked in. That last guard exists because the first publish was wrong in a way no test could see: PublishSingleFile + IncludeNativeLibrariesForSelfExtract bundled the libav*.dll family into the exe — a 9-file output with no ffmpeg folder and a 182 MB binary, so the locator would have found nothing at runtime. ExcludeFromSingleFile="true" on the content keeps the folder a folder. Diffing against a control publish without the single-file flags found it; guessing at item globs wasted two attempts first (recorded in MyMistakes.md). Measured: 127 MB exe + 129 MB ffmpeg = 257 MB depot. Tests: AssemblyIdentity, StorePackaging, BuildStamp, BundledFonts all pass; app builds with 0 warnings.
6.7 KiB
6.7 KiB
Distribution & release mechanics
How the build becomes an install on someone's PC. Policy — prices, entitlement, why Steam is the
only channel, support — is docs/ai/commercial.md. This file is the how.
Superseded twice on 2026-10-31. The 736-line Polar/MSIX/obfuscation plan is in
git log -- Distribution.md; the zip-plus-winget plan written earlier the same day is in the same place. The reasoning that survives lives incommercial.mdanddocs/ai/decisions.md.
1. The artifact
A self-contained win-x64 publish plus the bundled ffmpeg\ folder, uploaded to a Steam depot. No
installer, no admin rights, no zip for anyone to mirror.
dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:PublishReadyToRun=true -o artifacts/steam-stage
PublishTrimmedstays OFF. Trimming breaks WPF's XAML reflection (Binding,DataTemplate,StaticResource). Expect ~110–150 MB in the depot; that is the cost of "no .NET install".- ffmpeg stays outside the exe —
ffmpeg\beside it (the app's locator contract, seeoutput.md→ "FFmpeg locator"). steam_api64.dllships beside the exe and is loaded at startup; without it the app cannot read DLC ownership. Its absence must degrade to the free profile, never to a crash.- WebView2 is the one machine dependency. Valve's Common Redistributables configure the Evergreen bootstrapper as a prerequisite, so the depot declares it instead of us shipping it.
AssemblyNameisLlamaCasty(renamed 2026-11-01, before the first depot because Steamworks warns about renaming a live exe) → the artifact isLlamaCasty.exeplus itsffmpeg\folder.scripts/publish.shstages both and refuses to finish if either is missing.- ⛔
ExcludeFromSingleFile="true"on the ffmpeg content. Without it,PublishSingleFile+IncludeNativeLibrariesForSelfExtractswallow the libav*.dll family into the exe — a 182 MB binary with noffmpeg\folder beside it, so the locator finds nothing and every stream fails at runtime. The publish output is ~257 MB: 127 MB exe + 129 MB ffmpeg. - Portable mode (
portable.txt→.\data\) survives as a feature for a copied install; it is not a delivery vector.
2. Steamworks setup, once
- 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.
- App IDs. One base app (free) and one DLC app (the lifetime Pro). Note both numbers:
the DLC app ID is what the app passes to
BIsDlcInstalled. - Depot. One depot holding the publish output; the base app and the DLC share the install directory, and the DLC package grants no files of its own — it is entitlement only.
- Steamworks SDK. Drop
steam_api64.dll(+steam_appid.txtfor local runs) into the publish output; the managed side is a thin wrapper (Steamworks.NETor hand-written interop) behindIEntitlement. - Store page configured and made public two weeks before release — Valve's rule, and it is the window where wishlists accumulate.
3. Release
publish → steam-stage → steamcmd + app_build.vdf → build upload → set live → announcement
- Upload the depot with
steamcmd +login <user> +run_app_build ..\scripts\app_build_<appid>.vdf +quit. The VDF names the app, depot, content root and branch. - Set the build live in Steamworks → Builds → the new build → Set Build Live on the default branch. That is the release; there is no artifact to hash and no manifest to submit.
- Announce with Steam Events (release notes appear in the library and on the store page).
- Verify: install from a clean Steam account, confirm
BIsDlcInstalledreturns false, buy the DLC (or use a test licence), confirm the high tiers unlock, then refund the test purchase.
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.
4. Entitlement wiring
IEntitlementhas two implementations: Steam DLC (lifetime, local) and Polar (monthly, network + 7–14 day lease). Resolution isPro = steamDlc || polarValid; anything unresolved is the free profile.- Never sell the monthly inside the Steam build. Steamworks requires in-app purchases to use the Steam Wallet; the app may state that monthly plans exist on the site, with no checkout, no price and no payment field. Confirm the wording with Steamworks support before shipping it.
- Never gate a feature. The only delta is the output profile.
5. Release checklist
- Build clean, 0 warnings (
./scripts/verify.sh), tests green. steam_api64.dllpresent; app starts without Steam and falls back to the free profile.- DLC ownership false → free profile; true → every tier offered.
LlamaCasty.exerename landed (never rename a live exe).- Clean Steam install on a second machine, then an update pushed and observed.
- Store page public for the full two weeks; support contact and announcements configured.
- Portable mode:
portable.txtpresent → data lands in.\data\.
6. Legal and takedown
LICENSE(EULA) forbids redistribution, resale, reverse-engineering and circumvention, and disclaims warranty.PRIVACY.mdmust name any third party the app contacts — Google/YouTube today, Polar the day licence validation ships. Steam itself is named by Valve's own policy, not ours.THIRD-PARTY-NOTICES.txtstays shipped and shown in About; the ffmpeg pin stayslgpl-shared; no GPL component may be linked (seeproduct.md→ "Licensing").- DMCA: a takedown needs only the notice — host, link, good-faith statement, signature. 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": a free tier good enough that a crack buys nothing, Steam's own delivery, short Polar leases, and a licence ID in About. Not obfuscation, not a native DLL, not hardware locking.
7. Effort, once
| Step | Effort |
|---|---|
| Steamworks onboarding + $100 fee | 0.5–1 day, mostly waiting on verification |
Publish profile + steam_api64.dll + IEntitlement Steam path |
1 day |
Depot + first steamcmd upload |
0.5 day, then ~15 min per release |
| Store page + assets (screenshots, capsule art) | 1–2 days |
| Clean-account install + DLC purchase/refund test | 0.5 day |