Reconcile the pending doc edits with the creator's actual ruling, and close the revenue-share question that was blocking the monthly price. Verified from the App Developer Agreement v8.10 PDF itself (downloaded and text-extracted, not a search excerpt). Section 6(b) has only three tiers: 6(b)(i) 15% for Apps and their In-App Products *not listed in* 6(b)(iii); 6(b)(ii) 12% Games-only; 6(b)(iii) 30% for Xbox console apps/games, Xbox non-subscription IAP, and Windows 8/Phone 8. LlamaCasty is a Windows PC App, so 6(b)(i) governs BOTH tiers -- there is no subscription surcharge. The agreement's own changelog (v8.0, Oct 26 2017) states it outright: "implement the 85/15 revenue share for non-Game subscriptions." => $10/mo nets $8.50 (~$102/yr); $400 lifetime nets $340. Also confirmed: the 15% applies after VAT/GST (Net Receipts definition), payouts are monthly above a $50 threshold, and no better small-indie rate exists in the standard terms. Creator ruling recorded: $10/mo subscription or $400 lifetime, no annual, no .99. This supersedes the 2026-09-21 one-time $29 -> $49 model. Two obligations ADA 6(h) attaches to the recurring tier, recorded because they change its risk profile and are the reason lifetime is the hedge: we must fulfil the subscription for the entire period as marketed (on breach Microsoft may refund the full amount plus taxes in its sole discretion), and raising the price disables auto-renew -- so $10 is effectively locked for the product's life. Stale facts fixed in the same change rather than appended: - research-store-certification.md: IARC was 11.11.1/11.11.2 in one table and 10.11.1 in another; corrected to 10.11.x. - research-store-certification.md: policy 10.8.1/10.8.2 still asserted "Polar explicitly permitted" and "tick the third-party purchase box". Void now that Store IAP is the route and Polar is being torn down. - task-48: the working YouTube demo account (10.3.1) was accidentally dropped from the certification list during the Store-IAP edit. Restored -- a reviewer cannot use the app without one. - MyMistakes.md: the verified-fact-vs-decided-outcome lesson now closes its loop (the creator did rule the way the research pointed), and records the over-correction that followed. Docs-only. No code, no build, no tests.
13 KiB
TASK 48 — Distribution & packaging: route decision, MSIX, signing
Catalog:
TASKS.md— status and requirements live here. Research:TASKS/research-store-certification.md— the "why". Carved out of TASK 36 item 6 (release engineering) on 2026-09-27 so distribution has one owner instead of three scattered mentions.
Status: 🔶 In progress — ✅ ROUTE DECIDED 2026-09-27: Microsoft Store MSIX + Store IAP. Polar is deleted; every licensing subsystem goes with it.
Goal: get LlamaCasty in front of a user without a SmartScreen scarewall, ideally without an annual certificate bill, and without breaking recording, capture, or audio.
0. ✅ THE DECISION — Microsoft Store, MSIX, Store IAP (creator ruling 2026-09-27)
Creator's stated criteria, verbatim: "zero headaches, minimal maintenance (for me) while still providing accountability and a reasonably easy upgrade flow."
Ruling: distribute as an MSIX package through the Microsoft Store, and take payment through Store IAP. All four criteria are satisfied by the same mechanism:
| Criterion | How MSIX + Store IAP satisfies it |
|---|---|
| Zero headaches | $0/yr — Microsoft holds the signing certificate, the reputation, and the installer. No cert, no HSM, no annual renewal, no SmartScreen ramp |
| Minimal maintenance | Microsoft handles updates, payments, entitlements, refunds, and customer support. Nothing to run |
| Accountability | Microsoft reviews every submission — that is the accountability layer, and it is a real one |
| Easy upgrade flow | Store auto-update. Velopack, the update URL, and the DO droplet all go |
The consequence that makes this cheap: the entire licensing subsystem is deleted
Choosing Store IAP over Store+Polar is what makes "minimal maintenance" real. Keeping Polar would have left the creator maintaining a licensing backend, a customer portal, activation limits, and an offline-grace machine — the exact burden the ruling was made to avoid.
Dead code / deleted concepts (TASK 10 is now a teardown, not a build):
Services/PolarLicenseService.cs— HTTP validation, the swallowed network failures, the ignoredexpires_atHelpers/PolarLicense.cs— the record typeViewModels/MainViewModel.License.cs—PremiumUrl, the customer portal, theOfflineGracePeriod = 14 dayssubscription-era artifact, renewal/lapse copyControls/OverlayHost.xaml— the incorrect "Polar unlocks alerts" copy resolves itselfytLive.csproj:92VelopackPackageReference+App.xaml.cs:3,38-46— see item 3- The
MONETIZATION.mdprovider sections (gitignored — local file)
What replaces it: IsPremium is derived from the Store entitlement instead of a
remote HTTP call. One bit, locally cached, refreshed by the OS. The offline-grace machine
disappears because the OS supplies license state — and with it the whole class of
"network flaky → app thinks I'm expired" bugs.
⚠️ The watermarks posture is unchanged: free gets everything; the branding flash stays the ONLY paid delta (TASK 36 item 2). Store IAP changes how the bit is obtained, never what it gates.
Still to verify (does not block packaging work)
- ☑ Current Store revenue-share terms — VERIFIED 15%, subscriptions included. Read from the
ADA v8.10 PDF (downloaded and text-extracted), §6(b): 15% for Apps and their In-App Products
not listed in 6(b)(iii); 12% is Games-only; 30% is Xbox console / Xbox non-subscription IAP /
Windows 8. LlamaCasty is a Windows PC App, so 6(b)(i) 15% governs ⇒
$8.50/monet,$340lifetime net. The agreement's own changelog (v8.0, Oct 26 2017) states it plainly: "implement the 85/15 revenue share for non-Game subscriptions." Smaller-indie better rates were not found in the standard terms. Microsoft also requires that any IAP products and their pricing be declared in Partner Center. - ☑ Store IAP tier shape — ✅ DECIDED 2026-09-27:
$10/mosubscription +$400lifetime, no annual. Declare two products in Partner Center: one auto-renewing monthly subscription, one non-expiring lifetime IAP. ⛔ No.99prices (settled). ⛔ No annual product — Microsoft does not pro-rate: "monthly subscriptions and initial (pre-renewal) purchases aren't eligible for a prorated refund", so an annual sub cancelled in year 1 forfeits the remainder and the developer cannot refund it. Net after the 15% Store Fee (verified, §6(b)(i) — applies to non-Game subscriptions too):$8.50/moand$340. Note ADA §6(h): we must fulfil the subscription for the whole period, and raising the price disables auto-renew — so$10is effectively locked for the product's life. ⛔ A discounted subscriber→lifetime upgrade SKU is not approved; do not declare one. - ☐ Individual vs Company Partner Center account (10.8.3 / 10.14 — see
research-store-certification.md§11 item 1). Get this right before enrolling; a Store+IAP product needing financial info for primary functionality would require Company, but a free product with a paid upgrade tier is the normal consumer shape and is fine on an individual account.
1. ⛔ Bundle ffmpeg instead of downloading it — do this on EVERY route
This is item 1 because it is genuinely first — it fixes a user-facing failure on its own and is a hard certification gate.
Services/Encoder/FfmpegLocator.cs:37-38 pins a GitHub release URL; LocateAsync
downloads (line 69), extracts (line 73), and the encoder executes the result. On a cold
cache the app downloads an unsigned executable from the internet and runs it.
- Store blocker: policy 10.2.2 forbids dynamic code inclusion (download-and-execute).
- Route-independent bug class:
FfmpegLocator.cs:30-35records that the previous daily pin aged out of GitHub retention and 404'd on the first real recording attempt (2026-09-01). Bundling kills this permanently and removes the startup network dependency.
Do: ship ffmpeg.exe + ffprobe.exe + the libav*.dll family inside the package;
FfmpegLocator resolves PATH → package-local → cache, and never downloads.
IFfmpegLocator's contract, the ExtractBinaries staging discipline, and the existing
FfmpegLocatorTests cold/cache/PATH coverage all still apply.
Licensing: the LGPL-shared build was chosen for LGPL §6 compliance (dynamic linking =
"license text + source offer"). That reasoning is unaffected. Confirm
THIRD-PARTY-NOTICES.txt covers the bundled build.
Record the immutable tag in TASKS.md per the licensing rule, and note the URL is now
a provenance record rather than a runtime fetch.
2. ☐ Package.appxmanifest — full trust, camera, microphone
There is currently no .manifest file in the repo at all, so this is purely additive.
rescap:Capability Name="runFullTrust"— medium integrity, not AppContainerwebcamandmicrophonecapabilitiesuap10:TrustLevel="mediumIL"anduap10:RuntimeBehavior="packagedClassicApp"— the latter is what allows launching package executables as child processes- Declare English only (10.7 — otherwise the description must be localized into every declared language)
- Block map SHA2-256,
StoreManifest, under the 25 GB cap
Do NOT use appContainer — see the invariant in item 6.
3. ☐ Remove Velopack — 3 edit sites
The Store handles updates, so this is simply deleted. Trivially removable; the code was written defensively for exactly this.
ytLive.csproj:92—<PackageReference Include="Velopack" Version="1.2.0" />App.xaml.cs:3—using Velopack;App.xaml.cs:38-46— the sole call site, already try/caught and commented "(non-fatal) … when no URL is set, the check is a no-op"
Retires: the pending "Velopack update URL" item (TASKS.md open items,
task-10-monetization.md:43) and the self-hosted DigitalOcean droplet.
4. ☐ Windows App Certification Kit
Run before submission. Technical compliance is tested by WACK; it is not optional. Known relevant check: the app must start promptly, stay responsive, and shut down gracefully (10.4.2).
5. ☐ Certification notes + the three documentation artifacts
In Partner Center (submission):
- ☐ The IARC age-rating questionnaire (10.11.1) — general audience, 12+ territory
- ☐ A working YouTube demo account (10.3.1) — the app is useless to a reviewer without one.
Must be a real, maintained account able to sign in and start a stream; a dead or
credentials-lapsed account is a rejection. ⛔ This was accidentally dropped from this list
during the Store-IAP edit — it is in
research-store-certification.md§4 and must be done. - ☐ Declare the IAP products and their pricing in Partner Center (required for Store IAP).
⛔ No
.99prices — creator ruling, and incoherent for a no-dark-patterns brand. Confirm the net after the revenue share, not the list. - ☐ Note that the product is general audience, not directed at under-13s
- ☐ The 11.12 UGC position is less load-bearing now that Polar is gone — the strong part of
the original argument was "we render transiently, persist nothing, and provide no
user-to-user communication surface," which is unaffected. State it in full anyway; the
reasoning and the Q1-2026 YouTube enforcement numbers are in
research-store-certification.md§9 - ☐ The YouTube age-gate argument — the operator is 13+ by construction (§8)
On llamachile.shop:
- ☐ Privacy policy (10.5.1 — mandatory for Win32/Desktop Bridge). Must state: camera/mic access is user-directed and OS-gated; no retention of chat or reward payloads; no viewer data collected; ffmpeg is bundled and nothing is fetched at runtime
- ☐ Code of conduct + content guidelines (11.12 / 11.15)
- ☐ Confirm
THIRD-PARTY-NOTICES.txtcovers the bundled LGPL ffmpeg build
In Partner Center (listing):
- ☐ Title is exactly
LlamaCasty— 10.1.1 forbids marketing text or extraneous keywords - ☐ Search terms: max 7, no pricing terms, and no other product titles (10.1.3 — cannot
list
OBSorStreamYard) - ☐ Real listing content — 10.1.4 requires an active, substantive presence
- ☐ Review the Individual vs Company account question before enrolling
6. ☐ The full-trust recording invariant — comment lands WITH this work
ViewModels/MainViewModel.Recording.cs:173-179 (DefaultRecordFolder()) writes to
%USERPROFILE%\Downloads or SpecialFolder.MyVideos; ChooseRecordFolder() (line 151)
uses Microsoft.Win32.OpenFolderDialog; the temp-write-then-move lifecycle stays in one
directory (line 131-132).
This works only because the package is full trust. At mediumIL, user-profile writes
pass through unvirtualized and OpenFolderDialog is an ordinary Win32 browser with no
capability token. No broadFileSystemAccess needed.
⚠️ If anyone flips the manifest to appContainer, recording silently breaks —
Directory.CreateDirectory and File.Move start resolving to a per-package virtualized
location and output vanishes.
Add a comment at DefaultRecordFolder() in this task, not before. A comment describing
a manifest that does not exist is worse than no comment.
Also verify on a real packaged build before trusting it with recordings — the docs say full trust passes writes through, but that is worth confirming with an actual package identity rather than an unpackaged run.
7. ☐ Confirm the disqualifiers stay clear after packaging
All four are currently clear (verified by grep 2026-09-27) — re-verify once the packaging project exists:
- No Windows driver installed (we consume DShow filters, we do not install one)
- No per-user Windows service
- No elevation /
requireAdministrator/ UAC manifest - No shell extension, no in-process module loading by outside processes, no jump list
8. ☐ Pre-submission gates
- ☐ 0 warnings, full suite green
- ☐ Vertical-recording path verified end-to-end (compositor tier is proven by
Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop; the record path has never been exercised —Services/Pump/FramePump.cs:645flags off-size tiers as a known follow-up) - ☐ The real-output confirmation the creator has been doing manually
- ☐ Camera/mic/wasapi capture verified from the packaged build, not just unpackaged
- ☐ Clean uninstall verified (10.2.7)
- ☐ Notification-disabled path leaves the app functional (10.9)
Not in this task (deliberately)
- Velopack hardening / obfuscation / assembly splitting — stays a GA-time decision per
TASK 36 item 6's agreed ceiling. Compile flags max at
HARDENED+MOCK_REWARDS, additive-only. - EULA draft/review and the THIRD-PARTY-NOTICES gate — stay in TASK 36 item 6.
- Vertical-canvas feature work — the feature exists; only the record-path test above is missing.
- macOS / Avalonia port — deferred; would be a second app, not a port.