Cards 18 onward come from a generator the founder runs and a web the founder harvests with the maker’s yes; none goes live faster than the feed can judge it; and until a hundred networks a day are voting, the feed’s job is composition, not ranking.
The thesis carries its own test, in section 8: if seventeen cards under the new chrome and composition do not move position-0 hold and depth, no number of generated cards will.
Founder's positioning, 25 September — this is the headline, and everything else is read under it. Blitz is a feed of content, much of it made with AI: pictures, videos, graphics, web pages and games. AI-generated content is getting better every month and is about to explode — we are entering an AI renaissance — and Blitz is the feed built for it. Blitz is not a gaming platform, and "things you play" is not the headline anywhere: a game is one kind of card among many, beside the image, the clip, the graphic and the page.
What sets the feed apart is what it trusts. TikTok's ranking is built on a watch-time signal it can see end to end; Blitz cannot see inside a card that runs its own code and refuses to trust what a card says about itself, so its only currency is what a phone gives away honestly: whether you stayed, and whether you pressed the button. The next card is a different kind of thing, and one button is the whole interface. StumbleUpon was the existing web filtered by people, and it died when the web stopped fitting the frame; Blitz's supply is the part of the web that fits a thumb, plus a generator that makes more of it on demand, plus the people who made those things, who can be shown host-measured numbers nobody else will give them. Roblox is a place you install, join and learn; Blitz is one URL, no install, no sign-up, playing before the network answers. For the next twelve weeks the thesis is one sentence: cards 18 onward come from a generator the founder runs and a web the founder harvests with the maker's yes, not from creators who have not arrived, and none of them goes live faster than the feed can give it a verdict; and until a hundred networks a day are voting, the feed's job is composition, not ranking — a different kind of card every BLITZ, shuffled per visit from what this phone has already held, looping, never ending on a dead button, and remembering you when you come back. The thesis carries its own test, written in section 8: if seventeen cards under the new chrome and composition do not move position-0 hold and depth, no number of generated cards will, and that is a verdict on the feed, not on the catalogue.
Ordered by expected retention impact per week of work. Every bet is startable today by one engineer with an agent; none needs a second engineer to begin. Costs: days means under one working week; weeks means two to four; months means a pipeline built in days that runs for the quarter.
Bet 1
The first-minute floor: consumer chrome, a way back, boot before the network, an end that is a sentence
days
What
First, confirm the live build is 6e5c69d, which carries b98c77b (viewport between the bars, meta strip below the surface); if a fresh tour still shows the strip over Colour Split's palette, it is a deploy problem and gets fixed before anything else. Then, in App.tsx, TelemetryDisplay.tsx and GameViewer.tsx: the stat cells, the toolbar button, the "+" for anonymous visitors, the tick-0 badge, the spinner card, the crash <code>, and the light feed theme move behind ?dev=1; the verb chips become one plain word; Like, Share and a "…" holding Report move into the right end of the 56 px strip; Tip goes; share text follows the card's class. A 36 px back chevron left of BLITZ that calls the Carousel's existing goTo(i-1); replaceState stays exactly as share.ts reasons it should (one entry, Back leaves the site), the chevron is logged as a ui_event of kind back and its exit recorded as SWIPE_DOWN, and the sentinel-entry question goes to the founder in one sentence before this ships. The first minute exists nowhere in data today, so one commit rewrites card_editorial (Ripple Tap 0, Stack Drop 1, the wasm games at 8 or later), MOCK_GAMES, feed.json, tools/seed-cards.mjs and the e2e fixture together, so the bundled boot and the server show the same card 0. Card 0 mounts from the bundled MOCK_GAMES the moment the bundle executes; anonymous sign-in runs, then feed() — not before it, as App.tsx does today, or the seed and the actor arrive null — and the server order is spliced in behind the active index only, with a second reconcile when sign-in lands late (30 an hour per IP; carrier NAT will 429, and the transport keeps queueing). A share link /c/<slug> whose card is not in the returned page resolves the card from the bundle or a card_public select by id and splices it at index 0 ahead of the deck — today feed(n: 60) plus Math.max(0, findIndex) in Carousel.tsx opens Ripple Tap for any card past the page and stamps entry_surface='share' on the wrong card, which is every concierge and remix link there will be; n rises and an e2e case covers a slug outside the page. feed.json joins the service worker's shell URLs; a host-stamped boot_ms (navigationStart to first running) rides the first batch of visit_ordinal 1. The end card ships as a hosted card of kind end, with the webview line and the add-to-home-screen card beside it. On the landing page the primary CTA becomes "Open the feed" linking /c/ripple-tap?via=landing, "Create account" moves to the creators section, the three real card names replace the invented ones, the "<1s" fact becomes the measured p50 or nothing, and Supabase Confirm-email is switched off once the concierge makers' handles sit in reserved_handle. The e2e helpers' slots-0-to-4 contract moves to a fixture.
Why
Every visitor today reads telemetry labels before content, cannot go back, and ends on an error state with no error message. The repo's own comment states the rule the deployed build violated. These are the cheapest points of position-0 hold and depth there are, and every later bet ships new cards under whatever chrome exists.
Metric
Position-0 hold (server span ≥ 5 s, no crash) for first-visit actors ≥ 50%; SWIPE_UP within 3 s at position 0 < 25%; median cards per visit (max visit_ordinal per visit_id) ≥ 5 before composition; SWIPE_DOWN share of exits > 0 on touch devices (structurally 0 today); boot_ms p50 < 1,000 ms on visit_ordinal 1 (headless proxy < 900 ms across three fresh contexts); a share link to a card outside the page opens on that card (e2e) and entry_surface='share' lands on the card that was shared; visits reaching the end card ≥ 25%, and the loop taken (a position-0 session with visit_ordinal past the deck length) more often than TAB_HIDDEN on the last card. Every number read against the baseline row written on day 1.
Bet 2
blitz-card.js, the idle policy and the save channel: make web cards visible to the host, and let the host remember for them
days
What
A ~1 KB dependency-free snippet, inlined — the CSP's script-src 'unsafe-inline' has no host source, so a hosted card cannot <script src> it; the lint looks for the inline marker, and only external sites load it by URL. On the first requestAnimationFrame post {blitz:'ready'}; on every pointerdown post {blitz:'touch'} throttled to 4 Hz (WebCardHost.onWindowMessage already resets lastInputAt and counts it); expose window.blitz.beat(); set touch-action:none; -webkit-touch-callout:none; user-select:none on the root so a long press never pops the iOS callout inside the frame. And a save channel, because a hosted frame is an opaque origin (sandboxTokensFor gives /cards/* allow-scripts only) where localStorage, IndexedDB and cookies throw, and connect-src 'none' leaves it no server: {blitz:'save', data} (4 KB cap, throttled to one a second) that the host writes to the parent's localStorage keyed by card id, and {blitz:'load'} answered at ready with whatever it holds — per-actor Supabase rows later, for accounts. window.blitz.save(obj) and window.blitz.load() wrap it. The host measured what it stores, so the strip may show "your best" from it; nothing in it ever ranks. Add the snippet to all eleven toys under public/cards and to the factory template; Stack Drop and Tile Flip keep a best score through it as the first two users. Idle policy in Carousel.tsx: hosted cards keep the 20 s sweep (a hosted card that sends nothing is a lint failure, not a policy case); external cards drop the timer entirely — the session ends only on BLITZ, back, TAB_HIDDEN or DESTROYED; wasm cards pause at 20 s (pause('idle'), a dim plus one line at the bottom of the surface) and exit at 80 s under the same session id, which means GameHost.resume() stops minting a uuid, GameWorker and TelemetryAgent learn the paused-not-ended state, and tests/e2e/telemetry.spec.ts, which asserts the current contract, is rewritten — a day of the budget, not a flag. External frames get a transparent background so the slide-thumb shows behind them and a permanent "Open" at the right of the strip, target=_blank from the parent DOM (the frame itself never gets allow-popups). The 3 s "Nothing showing?" line is gone: no third-party page sends ready or touch, so it would fire under every harvested card, Deepstate at 100% painted included, and never clear. Blankness is bet 6's job, at admission. reported_touches and reported_beats ride the batch as diagnostic columns that never rank. Publish the snippet with the scoped frame-ancestors header in docs/EMBEDDING-A-SITE.md as "make your page a Blitz card".
Why
Twelve of seventeen cards are iframes the host cannot see into; the sweep fires while people are playing (Bloom, Deepstate), ends the session, caps recorded web-card dwell at 20 s and mints a new id on resume. Every later bet measures hold and dwell on web cards, and the host side of the protocol already exists — only the cards are silent. And no card can remember anyone: an instant, stateless card is Playco's mode, one session and no second, and every family whose loop is "come back and see" — idle, clicker, drawing, puzzle — is dead without the save channel, as is any D1 number set against it.
Metric
IDLE_TIMEOUT share of hosted-card exits under 10% within a week of deploy (today it is every session that outlasts 20 s), exactly 0 for external cards; p50 server-observed span on hosted cards moves past the 20 s ceiling; hosted "held" per card_rank becomes comparable with the wasm cards; a second visit to Stack Drop on the same device shows its best (e2e).
Bet 3
Instrument the loop, then seed it
days
What
One migration: a ui_event table (actor, card_id, kind in like/share/back/report, reason, visit_id, entry_surface, entry_source, ua_class, server_ts) written by a log_ui RPC from GameViewer's handlers and the back chevron; a host-stamped liked boolean on the final batch; boot_ms, reported_touches, reported_beats and the SWIPE_DOWN exit as ingest_batch columns, since it ignores payload keys it does not know; card_event.entry_source from ?via= captured next to ?r= in captureEntry, and ua_class (browser, in-app webview, headless) from the user agent; on card, status gains bench, and the columns provenance (editorial, factory, harvest, remix, link — source stays what it is, the sandbox reads it), template_id, parent_card_id, content_hash, platform, platform_terms, pack jsonb and generation jsonb, with ingest_batch joining creator_id, template_id and parent_content_id from card instead of trusting the payload; a house flag on actor for the founder's uploading account; card_status_log (card, from, to, reason, actor, ts) and an owner-only card_set_status that is the only thing that ever flips a status, scheduled by pg_cron for the automatic hides in section 5 — card_rank is read-only today and nothing flips anything. Before the first seed link, the existing internal-flag function runs for the founder's, the checker's and the smoke's actors, so a test tour never votes. A growth_weekly view, owner-only: sessions and distinct networks per day; new actors by entry_surface × entry_source × ua_class; position-0 hold; median depth; held rate, deep hold (server span ≥ 20 s), IDLE_TIMEOUT share and crash rate by provenance and by family; share taps and reports per 100 impressions; share arrivals and their hook rate (visit_ordinal ≥ 2); D1 and D7 by first-session week, browser arrivals only; cards awaiting 20 votes and median days-to-graduate; live and bench cards by provenance and by handle. Then the traffic, in two steps: a 100-visit personal pilot the day bets 1 and 2 are live, ?via=pilot&r=<founder>, read for two days and fixed; then, only after composition is live, the seed cohort — 200–300 visits through founder-controlled links only, personal network, r/WebGames, one generative-art Discord, each ?via=<community>&r=<founder>. Nothing broader. Numbers are per browser profile, not per person: the cross-device merge is the known limitation in docs/ACCOUNTS.md, and it stays known, not fixed, this quarter; ua_class is what makes the retention rows readable in the meantime.
Why
Like is a local boolean, Share and Tip taps vanish, channels are unattributed, reports have no table, nothing can flip a status, and the only readout is card_rank(28) in SQL. Nothing else in this plan can be judged without these columns, and the cohort is only worth sending once they land and once composition exists, because it is the one denominator this plan gets for months and a cohort sent into a fixed-order feed measures the build we are replacing.
Metric
The Monday query returns every column for the previous week; the pilot read (position-0 hold, depth, IDLE_TIMEOUT share) exists by day 7; ≥ 200 new actors with entry_source and ua_class set by day 14; D1 on browser arrivals reported on day 21 with its interval written next to it (at 200 actors that is about ±5 points before the webview rows are dropped, wider after).
Bet 4
Composition: a different kind of card every BLITZ, shuffled per visit, looping, and a deck that knows what this phone held
weeks
What
feed() becomes feed(n, seed, exclude). The client mints a device seed once into localStorage and sends it; the server and the bundled fallback compute the same md5(seed || day || card_id), so card 0 never swaps under the visitor's thumb and the server order and the bundle agree. Tiers are editorial data, never engagement: anchor is the card_editorial rows, fresh is a live card under 14 days old or under 20 votes, tail is the rest; a card demoted two or more slots drops a tier, so demotion still bites in a shuffled world where a one-slot move inside a tier would be invisible — this is the v1.5 rule and goes into docs/RANKING.md. No tier boundary reads hold_ucb until Gate 1 opens: the earlier draft's "anchor = top hold_ucb" was promotion on telemetry through one indirection, and with the 40-pseudo-vote prior twenty cheap networks in a day would have lifted a tail card into it. Rules: (a) slots 0–1 editorial, slot 2 "New today" — the audition pick, rewritten so it can reach house cards (section 6) — falling back to the third editorial card; (b) within each tier a seeded shuffle under one hard rule, no two consecutive cards share a normalised verb family (the map from raw verbs to families is checked into src/api and the migration), with same source and same template_id within three slots as penalties in a plpgsql greedy that has an explicit fallback when the penalty cannot be avoided, and a unit test that runs the sequencer against the seventeen on both sides so server and bundle produce the same order for the same seed; generated cards fill one slot in two below the editorial block while the live catalogue is under 100 and one in four after; (c) the five wasm games no earlier than slot 8; (d) the end card at the tail, and BLITZ on it calls feed with the card ids held this visit in exclude. The returning device is the client's business and only the client's: from its own localStorage record of what it held — never sent, never read by the server, so it opens no Sybil path — it puts "New today" first, the card it held longest second, then interleaves unseen and held one-to-two with the shuffle weighted 3:1 towards the verb families it stayed for, until the unseen pool is larger than three decks, at which point held cards go to the back. Demote-only ranking is untouched; composition reads tiers, not rates, so it opens no fabrication path.
Why
Every visitor sees the same seventeen in the same order, worst five first, and feed_position is perfectly confounded with quality, which docs/RANKING.md says v2 cannot untangle after the fact. "The next thing is unknown and still good" is a composition property before it is a ranking property, and it works at seventeen cards today. And a returning visitor must not open on the leftovers: sending what they held to the back is the opposite of what a feed people come back to does, and refusing server-side personalisation on fabrication grounds is right while extending that refusal to one phone reordering its own deck from its own record is a category error.
Metric
Position-0 bounce (SWIPE_UP before a 5 s span) down by a third against the baseline row; median cards per visit up 50%, to ≥ 8; per-card feed_position variance above zero so position-adjusted hold becomes computable; returning devices' position-0 hold ≥ new visitors'; the sequencer test green on both sides; D1 reported from day 21.
Bet 5
The generated-toy factory: a 12-family probe onto a bench, live only as fast as the feed can judge
weeks
What
Before the first generated file is served: the CSP line tools/verify-cards.mjs already tests under (default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; connect-src 'none'; form-action 'none'; base-uri 'none') goes into public/_headers and netlify.toml for /cards/* — it is absent on the live site today — and the eleven toys are re-verified under it. Generated files are committed into the repo at public/cards/g/<id>/index.html, one batched commit a day, because Netlify applies custom headers only to files it serves itself and a /cards/g/* rewrite to Supabase storage would ship the machine-written class with no CSP at all — fetch, image beacons and remote scripts all working, "no external request" living only in the lint; every generated file also carries its own <meta http-equiv="Content-Security-Policy"> as belt and braces, the lint requires it, and verify-cards runs in CI on the commit with Playwright installed. Then the pipeline the founder runs, as a probe, not a catalogue: one family spec each for word toys, quizzes and trivia with a score, this-or-that and polls, drawing and creation tools, sound and music toys, physics toys, logic puzzles, one-thumb arcade with 15 s rounds and instant restart, idle and clickers, ambient art, micro-stories and choose-your-path, maps and data explorers — five cards per family, sixty in all, so words, people, scores and jokes are forced into the mix and no family is expanded before it has been read. Idle, clicker, drawing and puzzle families must use the save channel; the lint refuses them without it. A generation prompt that emits one self-contained file with the snippet inlined and its tunables in a single <script type="application/json" id="pack"> block (the shape bet 7 needs). A lint that refuses anything over 40 KB, any external request, a missing snippet or meta CSP, a card that does not move on its own within 1 s or react to a touch within 100 ms, or one that consumes vertical drag. Duplicate refusal at admission: sha256 of the file plus a normalised-script hash with whitespace and identifiers stripped, so "regenerate with a new title" is the same card. A headless smoke: loads under the CSP, paints non-background pixels within 1 s, survives 12 s of synthetic taps and drags with zero console errors; a 375x667 screenshot at 3 s becomes the thumbnail, as a WebP under 30 KB, because five hundred PNGs would outweigh the catalogue. A review page where the founder plays it on a phone and answers yes/no plus two verbs and the one plain word. Every admitted card is inserted status='bench', provenance='factory', source='hosted', template_id = family, generation = {prompt_hash, model, spec_version}, creator_id = the founder's house account. The bench is the rule that answers the arithmetic: a vote is one network per card per day, first-ever sessions and pure-bounce days never count, the fresh tier gets a fraction of each visit, and at the plan's own target — 50 visits a day at depth 8 — the whole fresh tier earns on the order of a hundred votes a day; spread over 300 cards no card would ever reach 20, no family criterion could fire, Gate 1 could never open, and the feed would be shuffling cards it could neither rank nor retire. So the live fresh set is capped at what the feed can judge in a 28-day window — (fresh votes per day × 28) / 20, about 15 cards today and about 50 at target, recomputed weekly from growth_weekly — and a bench card goes live only when a fresh card graduates (20 votes: it keeps its tier or is retired) or the cap rises, oldest-reviewed first, rotating by provenance, logged in card_status_log. The daily slot reaches them by the section 6 lottery; otherwise they enter at the tail.
Why
The eleven toys are the only cards a visitor would show a friend; they cost 7–20 KB, paint in 100–260 ms and crashed zero times in three tours. The class is proven and cheap, and its bottleneck is variety, not speed: by the fifth one "the next card is genuinely unknown" has collapsed into "another dark neon particle thing". StumbleUpon's supply was the web; Blitz's cheapest supply is a generator, provided the grid, not taste, decides what gets made — and provided the catalogue never outruns the signal, which is how Facebook Instant Games died: thousands of games nobody could rank, and users learned that instant meant junk. Sixty cards the feed can judge are worth more than five hundred it cannot.
Metric
Sixty probe cards admitted to the bench by week 3, six families by day 10; every live card has a verdict — cards awaiting 20 votes never above the cap, median days-to-graduate ≤ 28, and if it exceeds 28 generation stops; a family is read on its five cards' pooled votes, not on 5-second hold, which every self-animating card satisfies by being stared at: deep hold (server span ≥ 20 s) and share taps per 100 impressions, reported_touches as the tiebreak, first read at 50 pooled votes and expanded or rewritten at 100 (about two weeks a family at target volume; at today's volume the bench waits, which is the point); generation pass rate on first try ≥ 60%; reports and crashes per 1,000 impressions on generated versus editorial cards.
Bet 6
Harvest the embeddable web through a phone-fit check, ask before framing anyone's work, and give makers a phone link before asking for anything
weeks
What
One RPC and a script. card_stats(slug) returns aggregates only — impressions, held, distinct networks, share arrivals — with card_rank's exposure discipline, shown on the profile view. There is no check_link RPC: 20260908130000_accounts.sql revoked embed_check from clients precisely because it was an unmetered make-the-database-fetch-any-URL endpoint, and every anonymous session is authenticated; the checker is a Playwright script on the founder's machine that reads the final URL, X-Frame-Options, frame-ancestors and content type itself, and submit_link's server-side check stays authoritative at insert. The checker loads the candidate in a 375x667 touch viewport, waits 3 s, screenshots and counts painted pixels — blank or under 60% painted is a refusal, because from the feed's side a blocked frame and a blank page are identical — and the verdict is written on the row. Two kinds of candidate. Platform embed endpoints built for third parties — Shadertoy /embed, Scratch /embed, OpenProcessing's embed, p5.js full-view, CodePen full-page, itch.io HTML5 embeds, Observable embeds — are harvested without a DM, with the platform's embed terms recorded in platform_terms next to platform. Anything with an identifiable author on their own address — Neocities, *.github.io, *.vercel.app, *.netlify.app, *.pages.dev — is asked first and framed second; StumbleUpon's frame was resented, and Blitz does not get to find that out twice. Passers reviewed on a phone, inserted status='bench' with provenance='harvest', developer = domain, and live through the same cap as everything else. A nightly embed_check by pg_cron on every live external card and a weekly pixel check from the founder's machine; a card that fails either is hidden by card_set_status with the reason, because a site that adds X-Frame-Options a month later becomes a blank card that "holds" while the visitor stares at it, and inflates external hold against hosted. The concierge, an hour a day from day 10: three makers found where they post — r/vibecoding, r/ClaudeAI "I built", Bolt and Lovable showcases, p5, itch — by DM only, never a post, never where the community forbids it, and the DM leads with the thing they lack: a phone link that opens their sketch playing, with an OG image and a thumbnail; Blitz as the share-my-sketch-to-a-phone tool, the feed as the bonus. Claiming is a one-tap claim-token URL in the DM — account plus handle, no domain proof, because a maker on OpenProcessing, the p5 editor, Shadertoy or Scratch cannot set a header or a meta tag on a page they do not serve; on claim developer becomes the handle and the profile gains a per-card panel from card_rank aggregates with the snippet offered as "make it Blitz-ready". The "one more thing" ask waits until their card has 20 votes to show, because a dashboard that says n=7 is the Instant Games payoff. Every harvested card carries "yours? Remove it" as well as "yours? Claim it": a takedown address on the card and in docs/EMBEDDING-A-SITE.md, and a removal is done the same day, by hand.
Why
The server-side header check exists, but the one linked card shows the real failure is layout, which can only be judged from a phone-sized render. Platforms built for embedding are the part of the web that says yes to framing, and their sketches are already one-screen, one-touch things. What a p5 or itch maker cannot get anywhere else is a link that opens their thing playing on a phone, and after that honest, host-measured hold from a phone audience; StumbleUpon grew supply by showing site owners the traffic, and lost the goodwill by framing them without asking. This is the upload path the landing page already promises, run from our side of the funnel, with the ask in the right order.
Metric
Live external cards 25 by week 2 from platform embed endpoints, then as many as the bench graduates; per-platform deep hold and share taps within 10 points of the hosted toys at 50 pooled votes (a platform below that is dropped); crash rate on linked cards under 2%; nightly re-check hides counted weekly; ≥ 15 handles with a live card by day 21 and 30 claimed harvested cards by week 12; creator self-share ≥ 50% (a session with referrer_actor_id = the creator); checker pass rate on approached URLs around 60% — below 30% the hunting ground is wrong; DM reply rate ≥ 20%, and zero takedowns not honoured the same day.
Bet 7
Remix-by-pack: one change to an existing toy is the first creator generation path
weeks
What
Refactor four toys with obvious tunables — Tile Flip (grid, palette, target, par), Stack Drop (slab colours, swing speed, start width), Tide Pool (creature count, colours, speed), Bloom (palette, branching, wind) — so every tunable reads from the pack block, each with a checked-in JSON Schema including a title and one-line caption; the factory template emits the same shape for every new family. submit_pack(template_id, pack, title): account with handle required (the submit_link gate), schema-validated server-side, every string field run through a profanity and URL filter — the code in a pack is inert, its strings are not, and a title, a caption or a word-toy's word list is the moderation surface — sha256-deduped (same template plus same pack is the same card), creator_id = caller, parent_card_id = the card Remix was pressed on, provenance='remix', 3 per actor per day and the existing 10 per network. There is no server to inject anything on a static site and a hosted frame cannot fetch its pack under connect-src 'none', so the pack travels in the URL fragment: /p/<template>#<base64 pack> is a Netlify rewrite to the template's own file (so _headers applies), the template reads location.hash, nothing is sent to any server, page_url stays unique per hash, and the link is shareable as it is; if a pack ever outgrows a URL the host posts {blitz:'pack', pack} after ready instead. A Remix button in the "…" on packed cards opens a sheet with controls derived from the schema; the one-line prompt box that fills the schema through a Supabase Edge Function as constrained structured output is shown to accounts with a handle only, with its own token bucket on the function, because an ungated generation endpoint is a cost problem before it is anything else; the result previews instantly and Publish calls submit_pack. The card reads "remix of <parent> by @handle", the parent shows a remix count, and the remix enters on the bench with its own place in the section 6 lottery.
Why
The premise is the AI-generated-content era and there is no make-a-card flow at all. Open prompts from anonymous visitors are a moderation and cost problem; a bounded pack on a reviewed mechanic cannot execute, cannot fetch, cannot be a login form — the worst its code can be is ugly, and its strings are filtered and reportable — so its moderation is automatic on day one and its quality floor is the parent's. It gives every card a face other than @blitzlabs and makes lineage real in the schema before it must be backfilled.
Metric
Remix conversion: of held sessions on a packed card where the sheet opened, the share that published ≥ 3%; hold rate of remixes within 10 points of their parents over the same 28-day window; share of live cards not by @blitzlabs at 20% by week 12; distinct handles with a live card (today: one); reports per 1,000 impressions on remixes versus their parents.
Bet 8
Ranker v2 in gates keyed to measured volume
months
What
Write the thresholds into card_rank now and flip each on only when its number is met (section 6). Gate 0 is bet 4's tier rule and tier-drop demotion, already live. Gate 1: position-adjusted promotion within tier. Gate 2: per-actor verb affinity with a permanent no-personalisation holdout. Gate 3: promotion into the editorial block. Verbs stay author-assigned until the wasm telemetry and the snippet's beats can recover them; content-reported numbers never rank.
Why
The ranker is correct and idle at tens of sessions a day. Opening promotion early is a ranking by willingness to fabricate; building personalisation before there is a returning population to personalise for is building for nobody. Writing the gates now means the day the numbers arrive is a flag flip, not a design meeting.
Metric
Gate 1: distinct voting networks per day, and once open, hold rate of promoted cards in their new slot versus the slot's prior occupant. Gate 2: cards per visit and D7 for personalised actors versus the holdout. All from card_event using actor_id, visit_id, feed_position and the network hash only.
To a catalogue with a verdict, not to a number. Three sources, one review, one bench. In the first quarter: sixty probe cards from the factory (twelve families, five each), the harvested cards that pass the phone-fit check from platforms built for embedding and from makers who said yes, and the cards other people make — claimed harvests, concierge makers' second cards, remixes. Every one of them is opened once on a phone by a person before it leaves the bench, and reviewed against three questions only: does it respond to the first touch, does it fit 375x667, would you show it to someone. Admitted cards wait on the bench and go live only as fast as the feed can judge them — the live fresh set is capped at what 28 days of votes can carry to 20, about fifteen cards at today's traffic and fifty at the target — oldest-reviewed first, rotating by provenance. Live cards enter at the tail; the daily slot is their only distribution until they have votes, and the lottery rotates by provenance from day to day so "New today" alternates among the factory, the harvest and the remixes rather than by anyone's taste. The number of live cards at week 12 is whatever graduated; the target is that every one of them has a verdict.
To 5,000. The same three sources, with the gate on review moved, not removed, and the cap on the live fresh set raised only by traffic. Remix-by-pack scales without review because packs are inert and their strings are filtered. Full-code generation opens to accounts, not anonymous visitors, and only after a per-creator track record: three graduated cards holding within 10 points of the pool. Review becomes sampled — every card from a creator with fewer than three graduated cards, one in five otherwise — once the lint and smoke have 500 graduated cards of precision data behind them. Families expand from five cards to twenty-five only on pooled deep hold and share taps. Harvest scales by platform, driven only by per-platform numbers. The end-of-feed "Make one" entry opens when the factory's first-try pass rate is above 60% and the first 500 have been played, because a generation path that fails four times in ten in front of a stranger is a bounce, not a feature.
What is automated. Generation from the family spec; the lint; the smoke; the screenshot and thumbnail; duplicate refusal at admission; insertion on the bench; bench-to-live within the cap; the audition lottery; the nightly header re-check and the weekly pixel check on external cards; auto-hide (never auto-block), by card_set_status under pg_cron with a card_status_log row for every flip, on host-detected crash rate above 25% over eight or more votes, on a snippet heartbeat that stops while dwell continues, on a re-check failing, or on reports above 3 per 100 impressions over 50 or more impressions; the string filter in submit_pack; the daily and weekly readouts.
What is deliberately not. The grid — which families exist and how many of each — is decided by a person against the per-family deep-hold and share numbers, never by what the generator finds easiest and never by 5-second hold, which a screensaver satisfies. The yes/no on a phone. The flip from hidden to blocked, or back to live. A takedown. Promotion of any card into the top three. The DM to a maker. No model ever decides what executes in front of strangers; the model makes, the lint refuses, a person admits, the bench waits. The Deepstate lesson stands: framing permission is not fit, and only a phone-sized render can tell.
The founder's week. One person ships bets 1 to 8 and runs the content. The standing hours, per week, once the pipeline exists: phone review, about three minutes a card, three hours for the sixty-card probe spread over three weeks; harvest triage, about three hours per hundred URLs; the concierge, five hours; generation spend at a 60% first-try pass, cents a card; Netlify bandwidth trivial with cards committed rather than proxied. Around fifteen hours a week on content against a full engineering week, which is why the probe is sixty and not three hundred. When the hours run out the order of what stops is: the concierge, then harvest triage, then generation beyond the first six families; the measurement, the pilot and the phone review of anything that goes live are never cut. Hiring for content stays the signal the loop failed.
Today, per docs/RANKING.md and card_rank: editorial order is the ranking; a card needs 20 real votes before it can be demoted, only from positions ≥ 2, one slot per 0.10 of hold-rate deficit against the pool, capped at three, never out of the top two; a 40-pseudo-vote prior, a one-sided 90% upper bound, a 28-day window with a 14-day half-life; one network one vote per card per day, at most 20 votes per card per day; first-ever sessions, pure-bounce actor-days, internal actors and a card's own uploader never vote; one upload a day auditions at slot 2 until it has 30 lifetime votes or is 30 days old. RANKING.md says promotion opens "when there are enough distinct networks voting per day that one-network-one-vote makes fabrication expensive" and leaves the number open. This plan sets it.
Gate 0, now: composition, not ranking. Everything in bet 4 reads tiers that are editorial data — anchor is card_editorial, fresh is age under 14 days or votes under 20, tail is the rest — and interleaves by verb family, with source and template as penalties. It never reads a hold rate, directly or through an indirection, so a forger gains nothing from it. Demote-only stays exactly as written in SQL and, because a one-slot move inside a shuffled tier would be invisible, gains a v1.5 rule that makes it bite in the feed: a card demoted two or more slots drops a tier. And the audition slot is rewritten, because as written it does not reach the cards this plan makes: explore_pool draws one ticket per uploader actor, requires the uploader not to be internal, and distinct on (uploader_actor_id) … order by created_at shows that uploader's oldest card until it clears 30 votes — so sixty factory cards and every harvested card under the founder's account would share one ticket and show the same oldest card for a month, and zero tickets the moment the founder's account is marked internal, which it must be before the first seed link or every test tour votes on every editorial card. The change, shipped with bet 4: a house flag on actor; for house uploads the pick is md5(current_date || card_id) over eligible live cards bucketed by provenance, with the bucket rotating by day; the internal exclusion applies to voters, not uploaders; the three-held-cards test stays for outside uploaders. Before day 3 the three-line SQL that shows today's pick against a seeded house card runs; if it returns Gravity Well, nothing about "New today" is true.
Gate 1, position-adjusted promotion within tier. Opens when distinct voting networks per day (count distinct ip_hash in card_event) has been at or above 100 for 7 consecutive days and at least 20 cards carry 20 or more votes — a condition the bench cap of bet 5 is what makes reachable at all. Why 100: the daily sample per card is capped at 20 votes drawn from the day's networks, so at 100 networks a forger who wants to own a card's sample needs dozens of distinct addresses every day for the 14-day half-life to move it five slots — more than the slots are worth. Mechanics: hold is regressed on the feed_position bucket the client has been stamping since v1, sessions with visit_ordinal 1 are excluded as orientation, one-network-one-vote and the 20-vote cap stay, and a card may rise at most 5 slots a day within its tier. The editorial top three and the audition slot are untouched.
Gate 2, per-actor verb affinity. Opens when at least 30% of actors in the 28-day window have sessions on two or more distinct days, and there are at least 500 such actors. Per actor, held versus bounced by primary verb over 28 days is the whole interest vector — for a playable card, interest is not a topic; it is how you like to touch (tap, drag, time, plan), the tempo (calm or twitch) and the goal shape (score, finish, toy, social object). The next-card picker draws from the actor's top two verb families three times in four and from a family they have not played once in four. Actors with hash mod 10 = 0 are a permanent no-personalisation holdout, and the gate stays open only while personalised actors beat the holdout on cards per visit and D7. The on-device ordering in bet 4 is not this gate: it reorders one phone's deck from that phone's own record, the server never reads it, and it opens no fabrication surface; this gate is the server learning across phones, and it waits.
Gate 3, promotion into the editorial block. "Goes viral, ranks first" opens at 1,000 distinct networks per day sustained for 14 days, still one vote per network, still capped, still only on held and bounced. Until then, moving a card into the top three is an editorial act, as it is today.
Nothing a card or a client reports ever enters any gate: not Like, score, touches, beats, saved state, remix counts, reports or share taps. They are diagnostics, and they stay in their own columns.