docs(commercial): the claim page was never needed, signing waits for revenue, and support is a filter
Four things derived this session superseded what the plan said twenty-four hours earlier,
and the docs are corrected where each one died:
**Licensing needs no server.** Polar's customer-portal endpoints authenticate on the key
itself plus the public organization_id; the org access token is only needed to create
products. Channel binding rides in `conditions: {channel_id}`, re-checked on every
validate — so the "claim page" (a hosted OAuth flow that wrote the UC id onto the key)
and the key database it implied are deleted. Device management, key rotation and
subscription auto-revocation are Polar's; four states, one lease, no callback endpoint.
**Signing is deferred until two subscribers.** It never removes the day-one SmartScreen
warning (EV's instant-bypass died in 2024), so buying it before revenue buys only the
publisher line. Renamed to Azure Artifact Signing, $9.99/mo Basic, and it requires a
paid Azure subscription. Year-one fixed cost drops from ~$135 to ~$15.
**The Store account is free.** Microsoft waived the $19 individual fee and removed the
$99 company fee in May 2026, which makes the Store the only zero-cost route to a
Microsoft-signed install — the free answer to SmartScreen, noted beside the paid one.
**Support is a filter, not a promise.** Email, best effort, no SLA, refunds on request
without argument; no training service, no helpdesk. A repeated question becomes a
training video on @llamacasty, which is content and monetization instead of a ticket.
GitHub issues on the releases repo are the deliberate friction.
Also settled: binaries live in a second Gitea repo (`llamacasty-releases`) push-mirrored
to a public GitHub repo — issues and assets public, code private, one publish direction
(a git mirror carries refs, not GitHub Release assets); `@llamacasty` is
standard-verified with intermediate features, so live streaming, >15 min uploads and
custom thumbnails work today and no SIM or ID is needed; and OAuth comes out of Testing
mode by publishing the app (100-user lifetime cap, no 7-day token death).
TASKS.md now opens with the four-day surge slice — artifact shape, output gate,
entitlement seam, real-machine day — because those are what the first public release
waits on. HANDOFF.md rewritten. MyMistakes.md gains the Polar recipe and the
"verify means three different things" entry. MARCOM.md gains the support→content loop
and the surface roles.
This commit is contained in:
+62
-29
@@ -36,14 +36,46 @@ dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
|
|||||||
settings in `.\data\` instead of `%APPDATA%\ytLlive` (hung off `LayoutPathOverride`). Same binary,
|
settings in `.\data\` instead of `%APPDATA%\ytLlive` (hung off `LayoutPathOverride`). Same binary,
|
||||||
USB-stick behaviour, nothing to uninstall.
|
USB-stick behaviour, nothing to uninstall.
|
||||||
|
|
||||||
## 2. Release
|
## 2. Where releases live — the public handout repo
|
||||||
|
|
||||||
|
**The source repo stays private.** Releases live in a **second Gitea repo,
|
||||||
|
`llamacasty-releases`**, which is push-mirrored to a **public GitHub repo** (`llamacasty/releases`).
|
||||||
|
That public repo holds nothing but a README, an issue template, a known-issues page and the release
|
||||||
|
assets — so the only thing that can ever leak is content authored for publication.
|
||||||
|
|
||||||
|
Why it is shaped that way: GitHub has no "public issues on a private repo" switch, and issue
|
||||||
|
visibility follows repo visibility. An empty public repo gives a public issue tracker, a durable
|
||||||
|
binary URL for winget, and `releases/latest` for the update notice, without the code going anywhere.
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
```
|
```
|
||||||
publish → zip → SHA-256 → GitHub Release → winget manifest PR → update notice
|
publish → zip → SHA-256 → gh release create → winget manifest PR → update notice
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; attach `LlamaCasty-<version>-win-x64.zip`
|
1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; upload
|
||||||
to the **GitHub Release**. Binaries live there, never on the droplet — free CDN, durable URLs,
|
`LlamaCasty-<version>-win-x64.zip` with
|
||||||
|
`gh release create v1.0.0 LlamaCasty-1.0.0-win-x64.zip --repo llamacasty/releases
|
||||||
|
--notes-file RELEASE.md`. Binaries live there, never on the droplet — free CDN, durable URLs,
|
||||||
release notes in the same place, and `wingetcreate` reads `releases/latest` natively.
|
release notes in the same place, and `wingetcreate` reads `releases/latest` natively.
|
||||||
2. **Record the hash.** `sha256sum` the zip; it goes in the winget manifest and in the release
|
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
|
notes. This is the anti-tamper wall: the official channel delivers exactly this artifact or
|
||||||
@@ -52,38 +84,38 @@ publish → zip → SHA-256 → GitHub Release → winget manifest PR → update
|
|||||||
--submit` — it opens the PR against `microsoft/winget-pkgs`. Manifest shape:
|
--submit` — it opens the PR against `microsoft/winget-pkgs`. Manifest shape:
|
||||||
`InstallerType: portable`, `InstallerSha256`, `NestedInstallerType` where the zip nests the exe.
|
`InstallerType: portable`, `InstallerSha256`, `NestedInstallerType` where the zip nests the exe.
|
||||||
First submission is `wingetcreate new`; the identifier is `LlamaCasty.LlamaCasty`.
|
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
|
4. **Update notice.** Commit the current version into the releases repo (so it arrives in the same
|
||||||
`releases/latest` from the GitHub API). The app shows one line — *"v1.4 is out — `winget upgrade
|
push or a follow-up commit) or read `releases/latest` from the GitHub API. The app shows one
|
||||||
LlamaCasty.LlamaCasty`"*. **Nothing downloads or executes**: that is Store policy 10.2.2 and a
|
line — *"v1.4 is out — `winget upgrade LlamaCasty.LlamaCasty`"*. **Nothing downloads or
|
||||||
permanent no (see `product.md`).
|
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
|
5. **Optional mirrors.** Scoop (a small JSON manifest hosted in a bucket) and Chocolatey (a nupkg
|
||||||
with a checksum) — ten minutes each, same audience.
|
with a checksum) — ten minutes each, same audience.
|
||||||
6. **MSIX** stays buildable for the parked Store channel: `scripts/package-msix.sh` →
|
6. **MSIX** stays buildable for the parked Store channel: `scripts/package-msix.sh` →
|
||||||
`artifacts/LlamaCasty.msix`, submitted nowhere until the Individual account exists.
|
`artifacts/LlamaCasty.msix`, submitted nowhere until the (now free) developer account exists.
|
||||||
|
|
||||||
## 3. Signing
|
## 4. Signing — deferred until revenue
|
||||||
|
|
||||||
**Azure Trusted Signing, ~$9.99/month** — Microsoft-issued, individuals eligible after identity
|
**Unsigned is the launch state.** Artifact Signing is bought at **two subscribers**, not before:
|
||||||
verification, and it accrues SmartScreen reputation without buying a certificate.
|
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).
|
||||||
|
|
||||||
```powershell
|
- **Azure Artifact Signing** (renamed from "Trusted Signing") — **$9.99/month Basic**, 5,000
|
||||||
signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
|
signatures, **requires a paid Azure subscription**. Individual identity validation is the slow
|
||||||
/dlib "Azure.CodeSigning.Dlib.dll" /dmdf "metadata.json" LlamaCasty.exe
|
part (days to weeks) and must be renewed; start it when the purchase is decided.
|
||||||
```
|
- **⛔ No EV certificate, and no certificate with an HSM.** Microsoft removed the EV instant-bypass
|
||||||
|
in March 2024, so EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys.
|
||||||
- **⛔ No EV certificate.** Microsoft removed the EV SmartScreen instant-bypass in March 2024, so
|
- **The free alternative is the Microsoft Store**: the individual developer fee was waived, and
|
||||||
EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys — and a bought cert is a 460-day recurring
|
Microsoft signs the MSIX, so a Store install never meets SmartScreen at all.
|
||||||
line item whose key must live on an HSM or hardware token.
|
- **Unsigned costs a warning on every release**, because SmartScreen ties reputation to the file
|
||||||
- **No certificate at all** is the interim state: ship unsigned and accept the "unknown publisher"
|
hash. Signing accrues reputation to the **publisher** — the only exit from that loop.
|
||||||
line. winget spares the browser prompt, not the publisher line, so signing is what makes the
|
- **Signing never clears the warning on day one either.** Plan for weeks.
|
||||||
channel feel legitimate — the reason it is in the year-one budget.
|
|
||||||
- **Signing never clears the warning on day one** — only download volume does. Plan for weeks.
|
|
||||||
- Secrets: the Trusted Signing profile lives in CI secrets; **never commit** a `.pfx` or
|
- 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.
|
`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
|
- Verify every release: `signtool verify /pa /v <file>`, and confirm the hash in the manifest
|
||||||
matches the uploaded zip.
|
matches the uploaded zip.
|
||||||
|
|
||||||
## 4. Release checklist
|
## 5. Release checklist
|
||||||
|
|
||||||
- Build clean, 0 warnings (`./scripts/verify.sh`), full test suite green.
|
- Build clean, 0 warnings (`./scripts/verify.sh`), full test suite green.
|
||||||
- `signtool verify /pa` passes on the shipped exe.
|
- `signtool verify /pa` passes on the shipped exe.
|
||||||
@@ -95,7 +127,7 @@ signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
|
|||||||
- Portable mode: `portable.txt` present → data lands in `.\data\`, deleting the folder deletes the
|
- Portable mode: `portable.txt` present → data lands in `.\data\`, deleting the folder deletes the
|
||||||
app's footprint.
|
app's footprint.
|
||||||
|
|
||||||
## 5. 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. It is what makes a mirror site a licence breach rather than a grey area.
|
||||||
@@ -109,12 +141,13 @@ signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
|
|||||||
hash-pinned delivery, short leases, a free tier good enough that a crack buys nothing, and a
|
hash-pinned delivery, short leases, a free tier good enough that a crack buys nothing, and a
|
||||||
licence ID in About. **Not obfuscation, not a native DLL, not hardware locking.**
|
licence ID in About. **Not obfuscation, not a native DLL, not hardware locking.**
|
||||||
|
|
||||||
## 6. Effort, once
|
## 7. Effort, once
|
||||||
|
|
||||||
| Step | Effort |
|
| Step | Effort |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Publish profile + portable mode | 0.5 day |
|
| Publish profile + portable mode | 0.5 day |
|
||||||
| GitHub Release + hash + version notice | 0.5 day |
|
| Releases repo + Gitea→GitHub push mirror | 0.5 day |
|
||||||
|
| `gh release` + hash + version notice | 0.5 day |
|
||||||
| First winget manifest (`wingetcreate new`) | 0.5 day, then ~15 min per release |
|
| First winget manifest (`wingetcreate new`) | 0.5 day, then ~15 min per release |
|
||||||
| Azure Trusted Signing profile + CI step | 0.5 day |
|
|
||||||
| Clean-machine + `winget` smoke test | 0.5 day |
|
| Clean-machine + `winget` smoke test | 0.5 day |
|
||||||
|
| Artifact Signing profile + CI step (deferred) | 0.5 day, at two subscribers |
|
||||||
|
|||||||
+22
-21
@@ -1,35 +1,36 @@
|
|||||||
# HANDOFF — 2026-10-31 (commercial plan settled in docs; next code unit is the entitlement seam)
|
# HANDOFF — 2026-10-31 (commercial settled; the surge week starts at the artifact, not the Text layer)
|
||||||
|
|
||||||
## State
|
## State
|
||||||
|
|
||||||
- Branch `main`. Pushed through `c72b61b`; **`90d5140` + this commit are local only** (push at a
|
- Branch `main`, level with `origin` through `7827d23` + `0b41b7d`; this doc unit is local.
|
||||||
checkpoint, not per commit).
|
- **Commercial is settled and written down**: freemium (free = whole app at a 921,600 px / 30 fps
|
||||||
- **Commercial plan is settled and written down**: freemium (free = whole app at a 921,600 px /
|
profile, stream **and** recording), Pro $10/mo or $400 lifetime through **Polar with no server at
|
||||||
30 fps profile, stream **and** recording), Pro = $10/mo or $400 lifetime through **Polar** (key
|
all** — the client calls the customer-portal API with the key, and channel binding rides in
|
||||||
bound to the buyer's YouTube channel ID via a claim page, 2 activations, 7–14 day lease), one
|
`conditions: {channel_id}`. Signing is **deferred until two subscribers**; the Store account is
|
||||||
portable zip + **winget** as the primary channel, **Steam and the Store parked**, Azure Trusted
|
now free; binaries live in `llamacasty-releases` push-mirrored to public GitHub.
|
||||||
Signing, Founding 200 promo. New single source of truth: [`docs/ai/commercial.md`](docs/ai/commercial.md).
|
- **Support policy recorded**: email, best effort, no SLA, refunds on request; no training service;
|
||||||
- Docs consolidated this unit: `product.md` (money section → pointer), `decisions.md` (banners +
|
a repeated question becomes a training video on `@llamacasty`. `MARCOM.md` holds the loop.
|
||||||
history), `index.md`, `ai.md`, `README.md`, `Distribution.md` (rewritten to release mechanics,
|
- `www.llamacasty.com` resolves and 301s to the apex (CNAME + YunoHost domain + LE cert + nginx
|
||||||
736 → 120 lines), `TASKS.md` (five new rows), `capture.md` (GPL line falsified by the proprietary
|
`.d` include). Recipe in `MyMistakes.md`.
|
||||||
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. **Entitlement seam** — `IEntitlement` over Polar activate/validate/deactivate, channel-ID match,
|
**Surge week, day 1: artifact shape.** `LlamaCasty.exe` rename (three pack URIs:
|
||||||
2 activations, lease + offline grace, `--activate LCYT-…` CLI, fail closed. One Good Dog test.
|
`MainWindow.xaml:14`, `OverlayHost.xaml:96`, `RenameRecordingDialog.xaml:5`), `scripts/publish.sh`
|
||||||
2. Then the **OutputProfile gate** (one profile consumed by both the stream encoder and the
|
(self-contained win-x64, no test artifacts), `portable.txt` via `LayoutPathOverride`. It ends when
|
||||||
recorder; greyed tier entries; never downgrade mid-stream), the licence UI, and system messages.
|
the zip runs on a clean profile.
|
||||||
3. Scoreboard HTTP providers + goal inputs whenever the creator queues them.
|
|
||||||
|
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 site publishes the model, never the numbers** — and its CTA ("Coming soon to the Microsoft
|
- **The org access token never ships.** `organization_id` is public; the token is not. No claim
|
||||||
Store") stays until the creator says the download exists.
|
page, no callback endpoint, no key database — `conditions` does the binding.
|
||||||
|
- **The site publishes the model, never the numbers**, and its CTA stays "Coming soon to the
|
||||||
|
Microsoft Store" until the creator says the download exists.
|
||||||
- **No self-updater, by policy**: `winget upgrade` + a version banner. `StorePackagingTests` asserts it.
|
- **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
|
||||||
|
|||||||
@@ -111,6 +111,61 @@ survives YunoHost regenerating the `.conf`; never edit the `.conf` itself.
|
|||||||
%{redirect_url}' https://www.llamacasty.com/privacy.html` → `301 https://llamacasty.com/privacy.html`;
|
%{redirect_url}' https://www.llamacasty.com/privacy.html` → `301 https://llamacasty.com/privacy.html`;
|
||||||
the cert must read `CN=www.llamacasty.com`, issuer Let's Encrypt.
|
the cert must read `CN=www.llamacasty.com`, issuer Let's Encrypt.
|
||||||
|
|
||||||
|
### LICENSING WITHOUT A SERVER — POLAR'S CUSTOMER-PORTAL API (RECIPE, 2026-10-31)
|
||||||
|
|
||||||
|
**Problem:** the plan carried a "claim page" — a hosted OAuth flow that wrote the buyer's channel ID
|
||||||
|
onto the licence key — plus an implied server to hold keys. Both were invented from an assumption
|
||||||
|
that binding needs a browser.
|
||||||
|
|
||||||
|
**It does not.** Polar's customer-portal endpoints authenticate on the **key itself** plus the
|
||||||
|
`organization_id` (a public identifier, safe in the binary). The **org access token is only needed
|
||||||
|
to create products** and must never ship.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# activate (label is required; conditions are re-checked on every validate)
|
||||||
|
POST https://api.polar.sh/v1/customer-portal/license-keys/activate
|
||||||
|
{ "key": "LCYT-…", "organization_id": "c05fb364-…", "label": "DESKTOP-NAME",
|
||||||
|
"conditions": { "channel_id": "UC…" } }
|
||||||
|
|
||||||
|
# validate on launch + daily, with the SAME conditions object
|
||||||
|
POST https://api.polar.sh/v1/customer-portal/license-keys/validate
|
||||||
|
{ "key": "LCYT-…", "organization_id": "c05fb364-…", "activation_id": "…",
|
||||||
|
"conditions": { "channel_id": "UC…" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `limit_activations` on the benefit = the device cap (2). Polar's **customer portal** already does
|
||||||
|
deactivation, key rotation and "where is my key", so build no UI for it.
|
||||||
|
- A **subscription key is auto-revoked when the subscription ends** — that is what makes lapse a
|
||||||
|
real state; check `status`, not `expires_at`.
|
||||||
|
- ⚠️ `conditions` is **agreement, not cryptography**: a patched client can send someone else's ID.
|
||||||
|
Accepted — the free tier is the defence, not the check.
|
||||||
|
- ⚠️ There is **no push to a desktop app**: delivery is email or the customer portal. Auto-delivery
|
||||||
|
needs a webhook plus an endpoint *you* host, holding keys. Not worth it for one copy-paste.
|
||||||
|
|
||||||
|
### YOUTUBE "VERIFIED" — THREE DIFFERENT THINGS (2026-10-31)
|
||||||
|
|
||||||
|
`youtube.com/verify` saying "your account is verified" answers only the first tier. The tiers live
|
||||||
|
in Studio → Settings → Channel → **Feature eligibility**:
|
||||||
|
|
||||||
|
| Tier | Requires | Gives |
|
||||||
|
|---|---|---|
|
||||||
|
| Standard | nothing | uploads, playlists, posts (low daily limits) |
|
||||||
|
| **Intermediate** | phone verification | >15 min videos, **live streaming on a computer**, custom thumbnails |
|
||||||
|
| Advanced | **channel history** or ID/video verification | higher daily limits, clickable description links, chapters, pin comments, monetization application |
|
||||||
|
|
||||||
|
- **Live streaming is intermediate, not advanced** — a verified channel can stream; the API's
|
||||||
|
"live streaming not enabled" error means the *user's* channel is unverified, so the app must map
|
||||||
|
it to "verify at youtube.com/verify".
|
||||||
|
- **2 channels per phone number per year**, 365-day reset; VoIP/Google Voice numbers are refused.
|
||||||
|
That is the "used too many times" wall, and there is no appeal.
|
||||||
|
- Verification is **sticky** — it survives the phone being removed, which is why an old account can
|
||||||
|
read "verified" while no number on it will pass today.
|
||||||
|
- Advanced via **ID/video verification** is a privacy trade with a 24 h review; channel history is
|
||||||
|
free and usually sufficient within ~2 months of normal activity. Prefer history.
|
||||||
|
- ⚠️ Only the **primary owner** can do ID/video verification, and brand-account managers hit
|
||||||
|
*"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.
|
||||||
|
|
||||||
### 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`;
|
||||||
|
|||||||
@@ -7,11 +7,27 @@
|
|||||||
|
|
||||||
## Next up
|
## Next up
|
||||||
|
|
||||||
**TASK 3·17 — the Text layer.** The `TextOverlay` source type exists as a placeholder and nothing
|
**The surge week (4 days, in this order).** Not every row below — this slice only, because it is
|
||||||
else does: `AddSource` creates it 0×0, all four compositor paths `continue` past it, `FramePump`'s
|
what the first public release is waiting on:
|
||||||
hash skips it, it is not draggable, and it has no text/font/colour properties or persistence.
|
|
||||||
The unit: text content, font family/size/weight/style, colour, alignment, drag/resize, persisted;
|
1. **Artifact shape** — `LlamaCasty.exe` rename (three pack URIs), `scripts/publish.sh`,
|
||||||
renderer mirrors `Services/ChatBoxRenderer.cs`. One Good Dog test.
|
portable mode. Ends with a zip that runs on a clean machine.
|
||||||
|
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.
|
||||||
|
3. **Entitlement seam** — `IEntitlement` against Polar's customer-portal API with a fake for tests,
|
||||||
|
`--activate` parsing, system messages.
|
||||||
|
4. **Real-machine day** — licence UI skeleton, a clean-profile install, a test broadcast.
|
||||||
|
|
||||||
|
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
|
||||||
|
prerequisite for any *public* build).
|
||||||
|
|
||||||
|
**TASK 3·17 — the Text layer** stays the first product row after the surge. The `TextOverlay` source
|
||||||
|
type exists as a placeholder and nothing else does: `AddSource` creates it 0×0, all four compositor
|
||||||
|
paths `continue` past it, `FramePump`'s hash skips it, it is not draggable, and it has no
|
||||||
|
text/font/colour properties or persistence. The unit: text content, font family/size/weight/style,
|
||||||
|
colour, alignment, drag/resize, persisted; renderer mirrors `Services/ChatBoxRenderer.cs`. One Good
|
||||||
|
Dog test.
|
||||||
|
|
||||||
## Queue
|
## Queue
|
||||||
|
|
||||||
@@ -33,11 +49,12 @@ renderer mirrors `Services/ChatBoxRenderer.cs`. One Good 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 activate/validate/deactivate, channel-ID match, 2 activations, 7–14 day lease, `--activate` CLI | [`commercial`](docs/ai/commercial.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) |
|
||||||
| — | ☐ | **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** — claim page, four states, 7/1/0-day notices, portal link, licence ID in About | [`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) |
|
||||||
| — | ☐ | **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** — portable zip, GitHub Release, winget manifest, Azure Trusted Signing | [`Distribution.md`](Distribution.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) |
|
||||||
|
| — | ☐ | **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) |
|
||||||
| — | ☐ | 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 |
|
||||||
|
|||||||
+87
-37
@@ -1,8 +1,10 @@
|
|||||||
# Commercial: pricing, entitlement, channels
|
# Commercial: pricing, entitlement, channels, support
|
||||||
|
|
||||||
The single source of truth for what LlamaCasty charges, what a licence entitles, and how it
|
The single source of truth for what LlamaCasty charges, what a licence entitles, how it ships and
|
||||||
ships. Read before any pricing, licensing, distribution, storefront or promo work. Cause of death
|
what support it does and does not include. Read before any pricing, licensing, distribution,
|
||||||
for every retired path: [`decisions.md`](decisions.md).
|
storefront, support or promo work. Cause of death for every retired path:
|
||||||
|
[`decisions.md`](decisions.md). Release mechanics (build, zip, mirror, winget):
|
||||||
|
[`Distribution.md`](../../Distribution.md).
|
||||||
|
|
||||||
## Model
|
## Model
|
||||||
|
|
||||||
@@ -44,63 +46,111 @@ The cap is a pixel budget plus a frame rate, not the word "720p":
|
|||||||
## Entitlement
|
## Entitlement
|
||||||
|
|
||||||
**Polar is the merchant of record and the only source of truth.** It takes the money, remits
|
**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.
|
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 endpoint 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.
|
||||||
- **Bound to the creator's YouTube channel ID** (`UC…` from `channels.list?mine=true`) — the
|
- **The client talks to Polar directly.** The customer-portal endpoints authenticate on the **key
|
||||||
**active** brand account's channel, never the `@handle`. Written once through a **claim page**
|
itself** plus the `organization_id` (a public identifier). The **org access token must never
|
||||||
after purchase (OAuth), not prefilled at checkout: the buyer's browser may be signed into a
|
ship in the app** — it is only needed to create products, not to activate them.
|
||||||
different Google account, and that is manual reconciliation forever.
|
- **Bound to the buyer's YouTube channel ID** (`UC…` from `channels.list?mine=true` — the
|
||||||
- **Two activations**, any machine, one channel. A second channel is a second key. Two keys for
|
signed-in channel, never the `@handle`) through Polar's **`conditions`**, not through a browser
|
||||||
one channel is never sold — it doubles the support surface and invites "give one to a friend".
|
flow: `activate` is called with `conditions: { "channel_id": "UC…" }`, and every later `validate`
|
||||||
|
passes the same object. Polar compares them server-side, so the binding is enforced without us
|
||||||
|
hosting anything.
|
||||||
|
- **Two activations**, any machine, one channel (`limit_activations: 2` on the benefit). 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.
|
- **Runtime rule:** Pro requires `licence.channelId == connected.channelId`. Equal → Pro.
|
||||||
Anything else → free tier, working normally, with *"This licence belongs to another channel"*
|
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.
|
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**;
|
- **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
|
no network past grace = free tier until Polar answers. Never lock a paying user out on a DNS
|
||||||
failure.
|
failure.
|
||||||
- **Four states, and nothing else:** valid → Pro · expired → free with a banner · activation
|
- **Device management is Polar's** — the customer portal already lists and deactivates
|
||||||
limit reached → "manage devices" portal link · mismatch or offline → free, explained.
|
activations, so we build no UI for it. A subscription key is **auto-revoked when the
|
||||||
|
subscription ends**, which is what makes a lapse a real state on the monthly SKU.
|
||||||
|
- **Four states, and nothing else:** valid → Pro · expired/revoked → 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.
|
- **CLI activation** — `--activate LCYT-…` so silent and fleet installs never meet a dialog.
|
||||||
Interactive entry in Settings is the secondary path.
|
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
|
- **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,
|
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).
|
never orange (orange is the dev-only label colour).
|
||||||
|
|
||||||
|
⛔ **`conditions` is agreement, not cryptography.** A patched client can send someone else's
|
||||||
|
channel ID. That is accepted: the free tier is the defence, the licence is a lease, and a
|
||||||
|
determined crack buys little. Do not propose a signed token, a hardware fingerprint or a server
|
||||||
|
to "fix" it.
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
**One artifact, three ways to get it.** A self-contained single-file `LlamaCasty.exe` plus the
|
**One artifact, several 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
|
bundled `ffmpeg\` folder, zipped. No .NET install, no admin, no installer framework. Trimming
|
||||||
stays off (WPF reflection), so expect ~110–150 MB.
|
stays off (WPF reflection), so expect ~110–150 MB.
|
||||||
|
|
||||||
1. **Direct download** — `llamacasty.com` links the zip; the binaries live on **GitHub Releases**,
|
1. **Direct download** — `llamacasty.com` links the zip. **Binaries live in a second Gitea repo
|
||||||
not the droplet (free CDN, durable URLs, release notes).
|
(`llamacasty-releases`), push-mirrored to a public GitHub repo**, so the source repo stays
|
||||||
2. **winget — the primary channel.** Manifest in `microsoft/winget-pkgs`, `InstallerType:
|
private and the handout repo contains nothing but a README, an issue template and the release
|
||||||
|
assets. The mirror carries refs, not GitHub Release assets — the zip is uploaded with
|
||||||
|
`gh release create`. Mechanics: [`Distribution.md`](../../Distribution.md).
|
||||||
|
2. **winget — the primary install path.** Manifest in `microsoft/winget-pkgs`, `InstallerType:
|
||||||
portable`, pinned SHA-256. That hash pin is the anti-tamper wall: the official channel delivers
|
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:
|
exactly the published artifact or fails. One `wingetcreate update` PR per release. Optional:
|
||||||
Scoop and Chocolatey manifests over the same zip.
|
Scoop and Chocolatey manifests over the same zip.
|
||||||
3. **Steam — parked at $100.** Lifetime Pro as DLC only (Steam has no subscriptions), page public
|
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.
|
two weeks before release, 30% cut, and price parity with the site. It is the only channel with
|
||||||
4. **Microsoft Store — parked.** $19 Individual account; the only storefront that bills a
|
discovery *and* an install that never meets SmartScreen, and it updates itself — which is why it
|
||||||
subscription natively and installs without a warning. Bans self-updaters, so a Store build
|
stays in the plan despite the cut.
|
||||||
|
4. **Microsoft Store — parked, and now free.** Microsoft waived the **$19** individual developer
|
||||||
|
fee (and removed the **$99** company fee in May 2026). It is the only storefront that bills a
|
||||||
|
subscription natively *and* the only zero-cost route to a Microsoft-signed install with no
|
||||||
|
SmartScreen warning, because Microsoft signs the MSIX. Bans self-updaters, so a Store build
|
||||||
ships without one.
|
ships without one.
|
||||||
|
|
||||||
**Not channels, ever:** Patreon or any subscription platform of our own, paywalled early access,
|
**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
|
a paid beta, a Discord. Content is the funnel; the channel is only the checkout.
|
||||||
channel is only the checkout.
|
|
||||||
|
|
||||||
**Portable mode:** a `portable.txt` marker beside the exe keeps the layout DB and settings in
|
**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`.
|
`.\data\` instead of `%APPDATA%\ytLlive` — the USB-stick case, hung off `LayoutPathOverride`.
|
||||||
|
It is the reason the zip survives even if Steam becomes the primary channel.
|
||||||
|
|
||||||
## Signing
|
## Signing — deferred until there is revenue
|
||||||
|
|
||||||
- **Azure Trusted Signing, ~$9.99/month**, is the intended certificate: Microsoft-issued, accrues
|
- **Not bought at launch.** Artifact Signing is listed in the costs as **deferred**: it is paid for
|
||||||
SmartScreen reputation, enrols individuals after identity verification.
|
out of Pro revenue, and the trigger is **two subscribers**.
|
||||||
- **No EV certificate** — Microsoft removed the SmartScreen instant-bypass in March 2024.
|
- **What it actually buys:** a named publisher line, fewer antivirus false positives on a ~150 MB
|
||||||
- Until it is in place, builds ship unsigned and the "unknown publisher" line is accepted. winget
|
self-extracting exe, and tamper evidence for hand-downloaded zips. It does **not** remove the
|
||||||
spares the browser prompt, not the publisher warning.
|
SmartScreen warning on day one — Microsoft deleted the EV instant-bypass in March 2024, so a new
|
||||||
- Every release: same signing pass, SHA-256 in the manifest, licence ID visible in About.
|
certificate has zero reputation and clears only with download volume.
|
||||||
|
- **Azure Artifact Signing** (renamed from "Trusted Signing"), **$9.99/month Basic**, up to 5,000
|
||||||
|
signatures, **requires a paid Azure subscription**. Individual identity validation exists and
|
||||||
|
must be renewed before it expires; it is the slow (days-to-weeks) part, so start it *when the
|
||||||
|
purchase is decided*, not before.
|
||||||
|
- **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
|
||||||
|
|
||||||
|
**Email. Best effort. No SLA. Refunds granted on request, without argument.**
|
||||||
|
|
||||||
|
- **There is no training service.** "I want people to succeed" is not "I will train them". The app
|
||||||
|
teaches itself — one-click go-live, five scenes, nothing to configure. A how-to question means
|
||||||
|
the app or its docs fell short; the answer is a fix or a video, not a conversation.
|
||||||
|
- **No helpdesk.** No ticketing system, no live chat, no onboarding calls, no hand-holding.
|
||||||
|
- **A repeated question is a video, not a ticket** — see `MARCOM.md` → support → content loop. The
|
||||||
|
training channel is the support surface.
|
||||||
|
- **The issue tracker is a filter, deliberately.** Bugs go to the public releases repo on GitHub,
|
||||||
|
which requires an account; Steam Discussions stay pinned and unread; Announcements carry known
|
||||||
|
issues and release notes. Steam refunds are Valve's own (2 h / 14 days) and cost us nothing.
|
||||||
|
- **Nobody is locked out over a licence.** A billing hiccup drops the app to the free profile with
|
||||||
|
a banner and a portal link — never a blocked stream, never a modal.
|
||||||
|
|
||||||
## Founding 200 promo
|
## Founding 200 promo
|
||||||
|
|
||||||
@@ -122,16 +172,16 @@ Cash cost **$0**; it is priced in support time and forgone list revenue.
|
|||||||
|
|
||||||
| Item | Cost |
|
| Item | Cost |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Azure Trusted Signing | $9.99/month (~$120/yr) |
|
|
||||||
| Domain | ~$15/yr |
|
| Domain | ~$15/yr |
|
||||||
| Droplet | $0 — serves the existing site |
|
| Droplet | $0 — serves the existing site |
|
||||||
| GitHub Releases, winget, Scoop, Chocolatey | $0 |
|
| GitHub public repo (releases + issues), Gitea, winget, Scoop, Chocolatey | $0 |
|
||||||
| Polar | 5% + 50¢ per sale → $10 nets $9.00, $400 nets $379.50 |
|
| Polar | 5% + 50¢ per sale → $10 nets $9.00, $400 nets $379.50 |
|
||||||
|
| Azure Artifact Signing — **deferred** | $9.99/month (~$120/yr), bought at two subscribers |
|
||||||
| Steam Direct (optional) | $100 once, recoupable after $1,000 AGR |
|
| Steam Direct (optional) | $100 once, recoupable after $1,000 AGR |
|
||||||
| Microsoft Store (optional) | $19 once |
|
| Microsoft Store (optional) | **$0** — the individual fee was waived |
|
||||||
|
|
||||||
Fixed year one ≈ **$135**, and the same every year after. **Break-even is two subscribers**
|
Fixed year one ≈ **$15** while unsigned, ≈ **$135** once signing starts. **Break-even is two
|
||||||
(15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years.
|
subscribers** (15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years.
|
||||||
|
|
||||||
## Anti-piracy posture
|
## Anti-piracy posture
|
||||||
|
|
||||||
@@ -140,8 +190,8 @@ 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 builds** — a patched build cannot arrive through winget, and a
|
- **Hash-pinned delivery, signed once revenue exists** — a patched build cannot arrive through
|
||||||
forged one is not signed as us.
|
winget, and a forged one is not signed as us. Until signing starts, the SHA-256 pin is the wall.
|
||||||
- **Short leases** — a revoked or refunded key dies inside the grace window.
|
- **Short leases** — a revoked or refunded key dies inside the grace window.
|
||||||
- **Licence ID in About** — a leaked build traces back to a purchase.
|
- **Licence ID in About** — a leaked build traces back to a purchase.
|
||||||
|
|
||||||
|
|||||||
+30
-2
@@ -2,8 +2,36 @@
|
|||||||
|
|
||||||
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 commercial rules (prices, entitlement, channels,
|
you are about to re-open one of them. **Live commercial rules (prices, entitlement, channels,
|
||||||
signing, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in the
|
signing, support, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in
|
||||||
subsystem files. This is the graveyard with cause of death.
|
the subsystem files. This is the graveyard with cause of death.
|
||||||
|
|
||||||
|
## Commercial mechanics — died 2026-10-31 (same week they were written)
|
||||||
|
|
||||||
|
- **The licence "claim page" is dead.** The binding was going to be an OAuth web page that wrote
|
||||||
|
the buyer's `UC…` channel ID onto the key, because we assumed binding needed a browser. Polar's
|
||||||
|
**`conditions`** object does it natively: the client calls `activate` with
|
||||||
|
`conditions: { channel_id }` and repeats it on every `validate`. **No claim page, no hosted
|
||||||
|
endpoint, no key database, no OAuth redirect URI.**
|
||||||
|
- **"Signing is a year-one cost" is dead.** Artifact Signing is **deferred until two subscribers**
|
||||||
|
— it never removes the day-one SmartScreen warning, so buying it before revenue buys nothing but
|
||||||
|
the publisher line. It is also now called **Azure Artifact Signing** and **requires a paid Azure
|
||||||
|
subscription**.
|
||||||
|
- **"The Store developer account costs $19" is dead.** Microsoft waived the individual fee (and
|
||||||
|
removed the $99 company fee in May 2026), which makes the Store the **only zero-cost route to a
|
||||||
|
Microsoft-signed install** — the free answer to SmartScreen.
|
||||||
|
- **"Binaries on GitHub Releases" was half a plan.** There was no GitHub presence at all (the only
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Support — decided 2026-10-31
|
||||||
|
|
||||||
|
- **No training service, no helpdesk, no SLA.** Email, best effort, refunds on request without
|
||||||
|
argument. A repeated question becomes a training video on `@llamacasty`, which is content and
|
||||||
|
monetization rather than a ticket. See `commercial.md` → Support and `MARCOM.md` → support →
|
||||||
|
content loop.
|
||||||
|
- **Rejected as support surfaces:** a paid Discord, live chat, onboarding calls, a ticketing system.
|
||||||
|
GitHub issues on the releases repo is the deliberate filter — an account is enough friction to
|
||||||
|
keep the list short.
|
||||||
|
|
||||||
## Monetization
|
## Monetization
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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 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 |
|
| [`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 |
|
||||||
| [`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 |
|
||||||
|
|||||||
+24
-13
@@ -192,12 +192,15 @@ YouTube" button; going live is unreachable until an account is connected.
|
|||||||
## Windows packaging & distribution (winget primary; Steam + Store parked)
|
## Windows packaging & distribution (winget primary; Steam + Store parked)
|
||||||
|
|
||||||
Live channel policy, the artifact and signing costs: [`commercial.md`](commercial.md). Build
|
Live channel policy, the artifact and signing costs: [`commercial.md`](commercial.md). Build
|
||||||
mechanics (publish flags, GitHub Releases, the winget manifest): [`Distribution.md`](../../Distribution.md).
|
mechanics (publish flags, the releases repo mirror, the winget manifest):
|
||||||
Retired routes and cause of death: [`decisions.md`](decisions.md).
|
[`Distribution.md`](../../Distribution.md). Retired routes and cause of death:
|
||||||
|
[`decisions.md`](decisions.md).
|
||||||
|
|
||||||
**The channels, in order:** direct download from `llamacasty.com` (binaries on GitHub Releases) →
|
**The channels, in order:** direct download from `llamacasty.com` (binaries in a separate
|
||||||
**winget**, the primary install path → Steam (parked, $100, lifetime Pro as DLC only) → Microsoft
|
`llamacasty-releases` repo, push-mirrored to public GitHub) → **winget**, the primary install path →
|
||||||
Store (parked, $19 Individual account — the only storefront that bills a subscription natively).
|
Steam (parked, $100, lifetime Pro as DLC only; the only channel with discovery *and* an install
|
||||||
|
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:**
|
||||||
|
|
||||||
@@ -205,8 +208,8 @@ Store (parked, $19 Individual account — the only storefront that bills a subsc
|
|||||||
2026-10-22 (TASK 48 slice 3) because it downloaded *and executed*, which Store policy 10.2.2
|
2026-10-22 (TASK 48 slice 3) because it downloaded *and executed*, which Store policy 10.2.2
|
||||||
forbids — and the same rule would close the Store for good if it came back. Updates arrive
|
forbids — and the same rule would close the Store for good if it came back. Updates arrive
|
||||||
through the package manager (`winget upgrade`) plus an in-app "newer version exists" banner read
|
through the package manager (`winget upgrade`) plus an in-app "newer version exists" banner read
|
||||||
from a version file on GitHub Releases. `ytLive.Tests/StorePackagingTests.cs` must keep asserting
|
from a version file in the releases repo. `ytLive.Tests/StorePackagingTests.cs` must keep
|
||||||
that no self-updater returns.
|
asserting that no self-updater returns.
|
||||||
- **The MSIX package stays buildable** via `scripts/package-msix.sh` (TASK 48 slice 2) — publish
|
- **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
|
||||||
@@ -249,15 +252,23 @@ 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 — Azure Trusted Signing, never EV
|
### Signing — deferred until revenue; Azure Artifact Signing, never EV
|
||||||
|
|
||||||
Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**, so EV
|
Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**, so EV
|
||||||
($400+/yr) buys what OV (~$150–300/yr) buys; both need an HSM or hardware token and renew inside
|
($400+/yr) buys what OV (~$150–300/yr) buys; both need an HSM or hardware token and renew inside
|
||||||
**460 days**. The 2026-10-31 ruling is **Azure Trusted Signing (~$9.99/month)**: Microsoft-issued,
|
**460 days**. The 2026-10-31 ruling was **Azure Trusted Signing (~$9.99/month)**; the later ruling
|
||||||
individuals eligible after identity verification, and it accrues reputation without a certificate
|
the same week is that it is **deferred until two subscribers exist**, because it never removes the
|
||||||
purchase. Until the profile exists, builds ship unsigned and the "unknown publisher" line is
|
warning on day one — only download volume does — and the storefront alternative is now free.
|
||||||
accepted. Signing never clears the warning on day one either way — only download volume does.
|
|
||||||
Signing steps: [`Distribution.md`](../../Distribution.md).
|
- **Renamed**: it is **Azure Artifact Signing** now. Basic is $9.99/month, 5,000 signatures, and it
|
||||||
|
**requires a paid Azure subscription**; individual identity validation exists and must be
|
||||||
|
renewed, and it is the slow (days-to-weeks) part — start it when the purchase is decided.
|
||||||
|
- **Unsigned is a per-release tax**: SmartScreen ties reputation to the file hash, so an unsigned
|
||||||
|
product warns on **every** release, forever. Signing is the only exit from that loop.
|
||||||
|
- **The free exit is the Microsoft Store** — the individual developer account fee was waived, and
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,22 @@ These are the hard facts behind every decision. Full list in `TASKS.md`.
|
|||||||
|
|
||||||
## Auth & session (YouTube OAuth)
|
## Auth & session (YouTube OAuth)
|
||||||
|
|
||||||
|
- **Publishing status, not phone verification, is what kills sessions.** An OAuth project in
|
||||||
|
**Testing** with user type *External* expires every authorization **and its refresh token after
|
||||||
|
7 days** — so every user re-signs in weekly. **Publish app** (→ *In production*) removes that
|
||||||
|
expiry. It does **not** require Google's verification review, but an unverified production app
|
||||||
|
shows the "unverified app" warning and carries a **100-new-user cap for the lifetime of the
|
||||||
|
project, which cannot be reset** — so publish when strangers are expected, not while testing.
|
||||||
|
Verification review only removes the warning and the cap. Source:
|
||||||
|
<https://support.google.com/cloud/answer/15549945>, and `commercial.md` → Entitlement is unrelated.
|
||||||
|
- **A channel must be verified before it can stream.** YouTube's feature tiers:
|
||||||
|
*standard* (nothing) → *intermediate* (**phone verification**: >15 min uploads, **live streaming
|
||||||
|
on a computer**, custom thumbnails) → *advanced* (channel history **or** ID/video verification:
|
||||||
|
higher daily limits, clickable description links, chapters, pin comments, monetization
|
||||||
|
application). Live streaming is **intermediate**, not advanced — a user without it gets a
|
||||||
|
live-streaming-disabled error from the API. Map that error in the go-live path to one sentence:
|
||||||
|
*"Verify your channel at youtube.com/verify, then try again — it takes a minute."* A raw Google
|
||||||
|
error here reads as "the app is broken". Source: <https://support.google.com/youtube/answer/9890437>.
|
||||||
- `Helpers/OAuthCredentials.cs` holds the real ClientId/ClientSecret. Auth is complete and the
|
- `Helpers/OAuthCredentials.cs` holds the real ClientId/ClientSecret. Auth is complete and the
|
||||||
session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`,
|
session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`,
|
||||||
CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry
|
CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry
|
||||||
|
|||||||
Reference in New Issue
Block a user