From 5d836aecbb1acfabe88c1e5df30442ea09045547 Mon Sep 17 00:00:00 2001 From: gramps Date: Sun, 1 Nov 2026 15:40:44 -0800 Subject: [PATCH] =?UTF-8?q?docs(commercial):=20one=20price=20=E2=80=94=20$?= =?UTF-8?q?79=20Steam=20DLC=20rising=20to=20$99,=20subscription=20retired?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creator ruling 2026-11-01. One SKU, one price, Steam does the selling. - Pro is a one-time Steam DLC on the free app: $79 introductory, $99 on a stated date, announced where the price is shown and actually raised. - The subscription is retired. The reasoning is in decisions.md: no service behind a monthly fee (no servers, no cloud, no content), ~1-3 subscribers per 1,000 installs against 5-8% churn, and it carried the most expensive code in the product. - Polar goes dormant. Its customer-portal design is recorded, not built. The "freemium on Steam, convert on Polar, Valve gets nothing" proposal is rejected: in-app purchases must use the Steam Wallet, so it risks the listing to save ~$24 a sale. - Entitlement collapses to `steamDlcOwned`. No lease, no key, no portal, no device limit; the greyed tier opens the Steam store overlay, so every purchase stays inside Valve's own surface. - The site publishes the model and links the store page. It takes no payment and hosts no binaries. - The $400 lifetime died with the subscription it anchored. Founding 200 died with the coupons and passes it needed. Costs: $115 fixed year one; $79 nets $55.30 after Steam, so three sales cover the year. --- TASKS.md | 6 +- docs/ai/commercial.md | 130 +++++++++++++++--------------------------- docs/ai/decisions.md | 26 +++++++++ 3 files changed, 76 insertions(+), 86 deletions(-) diff --git a/TASKS.md b/TASKS.md index 914b120..320ecc8 100644 --- a/TASKS.md +++ b/TASKS.md @@ -58,10 +58,10 @@ persisted; renderer mirrors `Services/ChatBoxRenderer.cs`. | 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) | | 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) | -| β€” | πŸ”Ά | **Steamworks entitlement** β€” `IEntitlement` + `SteamEntitlement` (Steamworks.NET) + fake **done**; needs the DLC app id from Steamworks, then the ownership path is live | [`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) | +| β€” | πŸ”Ά | **Steamworks entitlement** β€” `IEntitlement` + `SteamEntitlement` (Steamworks.NET) + fake **done**; needs the DLC app id from Steamworks. **Polar path retired** β€” one-time Steam DLC only | [`commercial`](docs/ai/commercial.md) | +| β€” | ❌ | **Polar subscription path** β€” retired 2026-11-01 with the one-price model. Design kept in `decisions.md`, not built | [`decisions`](docs/ai/decisions.md) | | β€” | πŸ”Ά | **OutputProfile gate** β€” resolver + `EncoderOptions` wiring **done** (`Models/OutputProfile.cs`, one ffmpeg/two outputs, dev override); remaining: greyed tier entries in the dropdown, entitlement wiring | [`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) | +| β€” | ☐ | **Licence UI** β€” two states only: owned β†’ Pro Β· not owned β†’ greyed tiers + "unlock on Steam" (opens the store overlay). No key entry, no notices, no portal | [`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) | | β€” | ☐ | **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 | diff --git a/docs/ai/commercial.md b/docs/ai/commercial.md index f2bd443..bf01b3e 100644 --- a/docs/ai/commercial.md +++ b/docs/ai/commercial.md @@ -6,15 +6,19 @@ storefront, support or promo work. Cause of death for every retired path: [`decisions.md`](decisions.md). Release mechanics (build, depot, SteamPipe upload): [`Distribution.md`](../../Distribution.md). -## Model +## Model β€” one purchase, one price - **Free tier β€” every feature, every scene, every overlay**, free forever, no account, no watermark, nothing nagged inside the output. It is not a trial. -- **Pro β€” $10/month or $400 lifetime.** No annual tier. +- **Pro is a one-time Steam purchase: a DLC on the free app.** + **$79 introductory β†’ $99** on a stated date before launch. No subscription, no lifetime SKU, no + third-party checkout. - **The only delta is output quality.** Features are never gated; a payment buys pixels. - **No watermark, ever**, in either edition. The branding flash is retired as a paid delta and stays off (`ShowBrandFlash`, default `false`). -- **Clean numbers, no `.99`**, no discount codes, no countdowns, no fake scarcity. +- **Clean numbers, no `.99`**, no discount codes, no countdowns. **The one dated exception** is the + introductory price: it is announced in advance, shown where the price is shown, and actually + raised on the date β€” otherwise the next announcement is worthless. - **The site publishes the model, never the numbers** β€” "free forever, every feature unlocked, a paid edition with higher output quality at launch". Price, tier name, cap and frame rate are published by the **app**, at the point of sale, not by the page. @@ -49,64 +53,30 @@ 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. - **Bitrate follows the profile** β€” a capped user is not offered a 1080p bitrate. -## Entitlement β€” two sources, one profile +## Entitlement β€” one source -**Pro is `steamDlcOwned || polarKeyValid`.** Both resolve to the same `OutputProfile`; neither -touches a feature. Fail closed: anything unresolved is the free tier. +**Pro is `steamDlcOwned`, and nothing else.** It resolves to the `OutputProfile`; it never touches a +feature. Fail closed: anything unresolved is the free tier. -### Steam (lifetime) - -- **Steam owns the lifetime SKU.** `BIsDlcInstalled(dlcAppId)` after `SteamAPI.Init`, behind +- **Steam owns the SKU.** `BIsDlcInstalled(dlcAppId)` after `SteamAPI.Init`, behind `IEntitlement` (`Services/Entitlement/`). It is a local check against Steam's own state β€” no - server, no key, no lease. -- **Fail closed, four ways:** no DLC app id configured, `SteamAPI.Init()` false (no client), - a thrown native load (missing `steam_api64.dll`), and "not owned" all resolve to the free tier. + server, no key, no lease, no account, no email. +- **Fail closed, four ways:** no DLC app id configured, `SteamAPI.Init()` false (no client), a + thrown native load (missing `steam_api64.dll`), and "not owned" all resolve to the free tier. None of them may crash the app or grant Pro. - **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. A refunded DLC - simply stops reporting ownership on the next start. +- **No licence key exists**, so nothing to paste, email, lose or reclaim. A refunded DLC simply + stops reporting ownership on the next start. +- **The upgrade path is the Steam store overlay** (`ActivateGameOverlayToStore`) from the greyed + tier menu β€” Valve's own surface, so the purchase stays inside the Steam Wallet rules. Never a + checkout of ours, never an external payment page. +- **No leases, no dunning, no portal, no device limits.** A one-time purchase has one state: + owned or not. -### 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 - product is created yet. -- **The client talks to Polar directly.** The customer-portal endpoints authenticate on the **key - itself** plus the `organization_id` (a public identifier). The **org access token must never - ship in the app** β€” it is only needed to create products, not to activate them. -- **Bound to the buyer's YouTube channel ID** (`UC…` from `channels.list?mine=true` β€” the - signed-in channel, never the `@handle`) through Polar's **`conditions`**, not through a browser - 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. - Anything else β†’ free tier, working normally, with *"This licence belongs to another channel"* - and a link to the Polar customer portal. Never an error wall, never a blocked stream. -- **Lease, not a permanent cache:** validate on launch and daily; **7–14 days offline grace**; - no network past grace = free tier until Polar answers. Never lock a paying user out on a DNS - failure. -- **Device management is Polar's** β€” the customer portal already lists and deactivates - 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. - Interactive entry in Settings is the secondary path. -- **Notice cadence** β€” 7 days out, 1 day out, and on the day, inline in the go-live panel with a - date and a portal link. Recording gets the banner only. Never modal, never blocking a Start, - never orange (orange is the dev-only label colour). - -β›” **`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. +β›” **Subscriptions and third-party checkout are retired for launch** β€” see +[`decisions.md`](decisions.md). Polar stays dormant; its design is recorded there so it is not +re-derived. ## Channels β€” Steam only @@ -114,13 +84,12 @@ to "fix" it. no other vector: no direct download, no winget, no Scoop/Chocolatey, no Microsoft Store. The store page is the single authoritative source for the download, the stats and the reviews. -- **Lifetime Pro = Steam DLC.** Bought on the **store page**; the app only reads ownership - (`ISteamApps::BIsDlcInstalled` / `UserHasLicenseForApp`). No purchase flow in the app, no Steam - Wallet code, nothing to reconcile. -- **The $10/month plan is sold outside Steam** and is **never purchasable inside a Steam build**. - Steamworks requires in-app purchases to go through the Steam Wallet β€” so the app *states* that - monthly plans exist on the site and does not sell them. If the app ever grows a checkout for it, - that is the policy violation. +- **Pro = Steam DLC, one-time.** Bought on the **store page**; the app only reads ownership + (`ISteamApps::BIsDlcInstalled`). No purchase flow, no Wallet code, nothing to reconcile. +- **The website sends people to Steam.** It publishes the model and links the store page; it takes + no payment, collects no email, and hosts no binaries. One shop, one price, one receipt. +- **No in-app checkout of ours, ever.** Steamworks requires in-app purchases to use the Steam + Wallet, so the only purchase surface is Steam's own store overlay. - **Boot shape:** the app ships `steam_api64.dll` beside the exe and calls `SteamAPI.Init`. That is a functional dependency, not the rejected "native helper DLL for IP protection" β€” Steam ownership cannot be checked without it. Launched without Steam running, the app must fall back to @@ -130,8 +99,7 @@ page is the single authoritative source for the download, the stats and the revi - **Portable mode is no longer a distributable.** `portable.txt` remains as a feature for anyone who copies the installed folder; it is not a delivery mechanism. - **Steam Direct** is **$100 per app**, recoupable after $1,000 adjusted gross revenue. -- **Price parity:** if the lifetime is ever sold off-Steam, it may not be cheaper than the Steam - price. +- **Price parity is trivially satisfied** β€” there is no off-Steam price to keep in step. **Not channels, ever:** direct download, winget/Chocolatey/Scoop, the Microsoft Store, Patreon or any subscription platform of our own, paywalled early access, a paid beta, a Discord. Content is @@ -170,21 +138,16 @@ repo for bugs that survive the friction of needing an account. - **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 +## The introductory price β€” the only promotion -Cash cost **$0**; it is priced in support time and forgone list revenue. +Cash cost **$0**. With one SKU there is no coupon, no founder tier, no pass and no reminder cadence. -- **90 days free** β€” a 100%-off Polar coupon, `duration = repeating, months = 3`. -- **Founder price lock** β€” their $10/month stays $10 permanently. -- **Credits + in-app Founder badge** (opt-in channel name on the site). -- **30-day passes** β€” unique keys carrying the issuer's channel, issued **one per 30 active days, - max 3 held**, activation limit 1–2, expiring 30 days **after activation**. Earned, never handed - out: a pass nobody uses teaches nothing. -- Optional **$250 lifetime** window in the first 90 days β€” converting a founder beats three - months of free. -- Reminders at day 75 and 85; the renewal cliff is where founders churn. -- Terms, one paragraph: one per person, passes expire 30 days after activation, no cash value, - revoked for resale or abuse, price lock void if the subscription lapses. +- **$79 until a stated date, then $99.** The date is announced at launch on the store page, the + site and the in-app upgrade panel, and it is honoured β€” an introductory price that never rises + makes the next announcement worthless. +- **No launch discount stacked on top.** Two overlapping stories ("introductory" plus "15% off") + muddy both, and the increase afterwards triggers Steam's 30-day discount cooldown. +- **No coupons, no keys, no founder badges.** A one-time Steam purchase has nothing to administer. ## Costs @@ -193,14 +156,14 @@ Cash cost **$0**; it is priced in support time and forgone list revenue. | Domain | ~$15/yr | | Droplet | $0 β€” serves the existing site | | Steam Direct | **$100 once**, recoupable after $1,000 AGR | -| Steam revenue share | 30% on the lifetime DLC β†’ $400 nets **$280** | -| Polar | 5% + 50Β’ per sale β†’ $10 nets $9.00 (monthly only) | +| Steam revenue share | 30% β†’ **$79 nets $55.30**, **$99 nets $69.30** | +| Polar | **$0 β€” dormant.** No subscription at launch | | Steamworks, public issue repo, Gitea | $0 | | Azure Artifact Signing β€” **not needed** | Steam installs don't meet SmartScreen | | Microsoft Store β€” **not used** | β€” | -Fixed year one β‰ˆ **$115** (domain + Steam Direct, one time). The Steam cut is the price of not -paying for a certificate, a storefront or an update path. +Fixed year one β‰ˆ **$115** (domain + Steam Direct, one time). At $79, **three sales cover the year**. +The Steam cut is the price of not running a storefront, a certificate 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 @@ -218,9 +181,10 @@ pointless and a leak that is traceable β€” not an unbreakable binary. 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. +- **Steam is the licence state** β€” a refunded DLC stops reporting ownership the next time Steam is + asked, and the app drops to the free profile on the next start. Nothing to revoke by hand. +- **The Steam purchase is in About** β€” a leaked build traces back to a Steam account, which Valve + can action; we hold nothing to trace. β›” **Rejected, do not re-propose:** a native helper DLL for "IP protection", obfuscation, hardware locking, remote code that downloads and executes, and gating asset downloads as an auth check. diff --git a/docs/ai/decisions.md b/docs/ai/decisions.md index 4503889..cf19947 100644 --- a/docs/ai/decisions.md +++ b/docs/ai/decisions.md @@ -5,6 +5,32 @@ you are about to re-open one of them. **Live commercial rules (prices, entitleme signing, support, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in the subsystem files. This is the graveyard with cause of death. +## The business model β€” one price, settled 2026-11-01 + +**$79 one-time, raising to $99 on a stated date. Sold only on Steam, as a DLC on the free app.** +The website publishes the model and links the store page; it never takes payment. + +**What died the same day:** + +- **The subscription.** $10/month for pixels, with no service behind it β€” no servers, no cloud, no + content library, updates free through Steam. Estimated 1–3 subscribers per 1,000 installs, at + 5–8% monthly churn, against the most expensive code in the product (leases, activations, + revocation, portal, dunning). Retired for launch. +- **Polar as a live rail.** Dormant. Its customer-portal design (`conditions` binding, two + activations, 7–14 day lease) is preserved here only so it is not re-derived if a cheaper entry + tier is ever justified by real support traffic. +- **The $400 lifetime** β€” it was an anchor for a subscription that no longer exists, and above what + a beginner pays for a tool with a free incumbent. +- **Founding 200** β€” coupons, price locks, earned 30-day passes, founder badges: all machinery for + a recurring plan. +- **Off-Steam conversion of any kind.** The "freemium on Steam, convert on Polar, Valve gets + nothing" proposal was rejected: Steamworks requires in-app purchases to use the Steam Wallet, so + it is a listing-level risk to save ~$24 a sale. + +**Kept:** the free tier is the whole app at a 720p30 output profile, the paid delta is output +quality only, and the app never sells anything itself β€” the greyed tier opens the Steam store +overlay. + ## 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