ZINGERS: One soul, two doors (product-framing decision)
In short: This doc answers "so what's this gonna be?": the identity decision for the whole product. Flight is the game: the spine, the face, the brand, and the first thing anyone does. Battles are what you meet on the way up: the depth the climb reveals, and what lets you climb higher. Mobile is the face (a bus-time flight anyone gets in zero seconds); desktop is the full world (the same Flight soul, plus the living map it rises over). The AI-agent layer is the invisible engine, sold as fantasy, never as tech. Crypto stays off to the side as fuel.
Version 2.1 · July 2026 (Flight-First, aligned with design-vision.md v3.1).
Decision record + implementation plan.
Companions: design-vision.md, essence.md,
long-game.md, mobile.md (§3 default door
superseded here), vocabulary.md. Copy follows the vocabulary
doc; identifiers/analytics keys never change for copy reasons.
0. The decision, in five lines
- Flight is the game. One soul on phone and desktop. Battles are not deleted
or shrunk; they are repositioned from the product to what you meet on the
way up (see
flyover.md,design-vision.mdv3.1). - Two doors on one Flight. Mobile is the face: a bus-time one-thumb flight, with champion/watch/rank revealing themselves as the world the flight is over. Desktop is the full world: the same Flight soul in six-degree flight, plus the Hub and regions where deep raising happens. Neither is the other's leftovers.
- Agents are the engine, not the pitch. Player-facing surfaces sell the
fantasy ("the creatures actually think"); the word "agent" lives only on
developer surfaces (
/agents, docs, protocol). - Crypto stays fuel. Per
zing-model.md: $ZING is an optional premium layer, Crowns stay off-chain and server-authoritative, and no player-facing surface promises earnings. Collectors may later mint or trade provenance; they never forge new champions. - Web-first now; platforms later. Steam/console are marketing beats after the Launch v0.1 gates are green, not engineering projects now (§6).
The one-liner: You fly. It fights. You both rise. A 30-second flight anyone gets on a phone, that opens into a whole world you keep climbing on a desktop.
1. Why this framing (the reasoning we're committing to)
- The game's moat (per
AGENCY.md) is original IP we craft + an evolving battle meta + a collector culture around legends. All three depend on the minds being real. So we do not strip the agent layer to make a simpler game. But none of them require the word "agent" anywhere a player first lands. - The Climb is the only verb a stranger understands in zero seconds (the
Flappy Bird atom,
essence.md§3). "Today's fight to call". The doormobile.md§3 chose. Presumes the visitor already cares about a champion. A cold visitor doesn't. So the door changes; the body of the game behind it (the tab shell, the spectate-lane thesis) does not. - A generic ascender is a commodity (the hyper-casual space is brutal, and we'd enter it as WebGL without an app store). What makes ours not-a-clone is precisely the soul thread: the run marks your champion, your champion flies beside you, the Reaches are the real regions of a real world, and the battles it flies over are minds actually thinking. The Climb isn't a lobby that leads to the game. the Climb is the game, and it keeps revealing more of itself as you rise.
- Desktop's open questions ("would people understand it? is it fun? do they feel reward?") are exactly Launch v0.1 gates 1–2, and they are currently unmeasured. The answer is instrumentation, not redesign (§5).
The test this doc must always pass
Every change below either (a) shortens a stranger's path to the soul, or (b) measures whether that path works. Anything that does neither is out of scope here.
2. Narrative positioning (what we say, everywhere)
Sell the fantasy, not the tech. The narrative we have. The Hum, the Long Vault, the Keepers, "a mind argues itself into a body". Is a creature-raising fantasy that AI makes possible. Pokémon never marketed its creatures as state machines.
Rules (apply to every player-facing surface. Landing, /m, store pages,
social copy):
- The magic line is: "the creatures actually think. No two battles are the same." Never "AI agents battle each other."
- "Agent" is developer vocabulary. It lives on
/agents, inagent-protocol.md,docs/, and the MCP surface. The nav already frames this correctly (lib/play-nav.ts"Build" group); keep it that way. Audit task: sweep player-facing strings for "agent"/"AI agent"/"LLM" (landingcomponents/home/landing.tsx,/mtabs,/howitworks, share cards) and replace with fantasy copy. copy only, never identifiers/keys (pervocabulary.mdandAGENTS.md). - Crypto is invisible until it's real. No $ZING mention on any game surface pre-launch. No "earn money" promises anywhere. Optional collector permanence (trading, mint-to-immortalize) stays in docs until it exists. Champions remain studio-crafted; players collect, they do not create.
- The reward vocabulary players do get: your champion's body (the visible record of its career), rank (Trainer Rank + the standings), Crowns, the Saga, your rival. These are the honest incentives. lead with them when copy asks "why play?".
3. Mobile: the Flight-first door (supersedes mobile.md §3 "default door")
3.1 What changes and what doesn't
Kept from mobile.md | Changed by this doc |
|---|---|
The bottom-tab shell (components/mobile/mobile-shell.tsx), all five tabs | The default door is no longer the Today tab |
| Phone = spectate/predict/share lane as the depth behind the door | The door is the Climb (perform first, care later) |
| "One soul, native bodies", every verb's atom | Climb gains a guest mode (see 3.3. The atom is preserved) |
No login; identity = device token (lib/owner.ts). Champions named on standings claim (champion-names) | Copy: never "sign in". "Claim a mind" |
3.2 The first screen: the poster
A cold visitor to /m (or a phone routed from Play, lib/play-nav.ts
MOBILE_PLAY_HREF) gets a full-screen splash, not a tab grid:
- Art: the epic image. The Trainer flying with the jetpack, their champion
flying beside them. (Canon-perfect: the Trainer flies, the champion fights.)
- Asset: a rendered poster. Options, in order of preference: (a) a static
art asset (fastest, most control. needs to be produced, this is the
one external dependency of the whole phase); (b) a live R3F scene reusing
ChampionPortraitScene+ the jetpack rig (zero new art, but WebGL cost at the worst possible moment. First paint). Start with (a).
- Asset: a rendered poster. Options, in order of preference: (a) a static
art asset (fastest, most control. needs to be produced, this is the
one external dependency of the whole phase); (b) a live R3F scene reusing
- Copy: brand + tagline + one line of fantasy. Suggested:
ZINGERS/ "Raise a mind. Make it legend." / primary CTA "FLY". - CTAs: primary Fly → straight into Flight (guest or owned, 3.3). Secondary, smaller: "Your champion" (owned trainers) or "What is this?" (→ Today tab / howitworks). Returning players with an owned champion may skip the splash entirely after the first session (see 3.5 flags).
- New component:
components/mobile/mobile-splash.tsx, mounted byMobileShellas a pre-tab state. Session-scoped dismissal flag inlib/brand.tsSTORAGE(e.g.mSplash: "zingers_m_splash_v1". sessionStorage, so every cold open of a new session shows the poster, but in-session navigation never re-interrupts).
3.3 Guest Flight (the design decision that makes the door possible)
Today the Flight tab is locked until a champion is claimed
(mobile-shell.tsx climbLocked, ~line 50). A bus-time door cannot open on a
lock. Decision: unlock it with a loaner.
- The loaner: a "wild mind". Deterministic, from the current weekly
rotation (
firstDuelStarterKeys(),lib/first-duel.ts), so the guest sees a real champion fly beside them, not a placeholder. Seed the pick from the device token so the same visitor keeps meeting "their" wild mind (attachment before adoption). - The atom, preserved (
essence.md§2: the run marks the champion): a guest run marks nothing. And that's the hook, not a violation. Banner during/after a guest run: "A wild mind flies with you. Claim it to keep your climb." On claim, the current run's best depth converts into the first career mark (one-time, client-held until adoption, then written through the normal Climb reward path). - Boards: guest runs never write the leaderboard (
/api/circuitPOST is simply not called without an owned champion. Mirror the existing gating incircuit-lite.tsxrecordRun). No new server surface needed for guests. - The claim moment: after a fall (the natural pause), the retry card gains
a second button: "Claim this mind" → the existing selection surface
(
components/mobile/mobile-adopt.tsxvia the Champion tab). Never interrupt a live run with acquisition UX. - Code touchpoints:
mobile-shell.tsx(drop the lock → route guests into Climb withloanerprop; keep the lock logic as the "is guest" signal),circuit-lite.tsx(accept aguestChampion?: stringprop; suppress board/reward writes while guest; show the claim banner),mobile-adopt.tsx(accept a preselected key so "Claim this mind" lands on the wild mind).
3.4 The shell behind the door
Unchanged in structure (Today · Watch · Champion · Flight · Rank), two adjustments:
- Exiting the Climb as a guest lands on Champion (the claim surface), not Today. A guest has no "today" yet. Owned trainers keep the current behavior (back to last browse tab).
- Copy: no "sign in" anywhere. The acquisition verb is claim
(
READER_COPY.claimLinecanon: you did not become this champion, you claimed it). The Trainer-code (components/trainer-code.tsx) remains the "bring your progress to another device" story. Frame it as "your Trainer sigil", never as an account.
3.5 Flags & measurement for the door
STORAGE.mSplash(sessionStorage). Splash seen this session.- New client events (see §5 for the mechanism):
m_splash,m_fly(guest Fly tap),m_guest_run(first guest run started),m_claim_from_climb(adoption reached from the Climb hook). The door's success metric is guest run → claim conversion, visible on/stats.
3.6 Explicit non-goals (mobile)
- No app-store build, no separate mobile save, no accounts (all per
mobile.md§4–5. Unchanged). - No stripping of Watch/Today/Rank: the shell stays whole. We are re-ordering the door, not amputating the lobby.
4. Desktop: unchanged scope, plus one honesty rule
Desktop is the product and stays whole: the world, Act 1, the venues, the league, the saga. No redesign is part of this decision. Two commitments:
- "Online game you could ship anywhere" discipline. Keep the game playable, complete and legible as a plain web game (it already is. Launch v0.1's six gates are exactly this bar). Anything that would only make sense inside a future wrapper (overlay SDKs, platform achievements) waits.
- The core-loop questions get measured, not debated. "Would people understand it? is it fun? do they feel reward?". §5 wires the funnel so these become dashboard numbers. Design reacts to the numbers after they exist.
On incentives (the honest answer to "what does the player get?"): identity (the body as track record), standing (rank/standings/war), story (saga, rival), and one pure-skill lane (the Climb/Circuit. The only place the Trainer performs). There is deliberately no money-earning promise (§2.3). If playtests show the reward loop is thin, the lever is making watching more dramatic (the debate lines, the Highlight, the judge). Not adding extrinsic rewards.
5. Measurement: turn gates 1–2 from assumptions into numbers
This is the highest-leverage work in the whole doc. Today the funnel is coarse
(session → claim → train → fight → return; analytics may still say bout). The
first-journey steps and time-to-first-evolution are invisible.
5.1 New events (aggregate counters. Same privacy posture, no per-user trail)
Extend Z_EVENTS in lib/server/track.ts:
fj_cinematic // FirstRun cinematic completed (not skipped)
fj_pick // champion picked in first-duel funnel
fj_tune // doctrine tuned (first journey)
fj_duel // first duel finished
fj_evolve // evolve card seen (the payoff moment)
fj_land // Concord landing reached
ttfe_u5 | ttfe_u8 | ttfe_over // time-to-first-evolution bucket (see 5.3)
m_splash | m_fly | m_guest_run | m_claim_from_climb // the mobile door (§3.5)
All are client events (only the browser sees these moments): add them to
CLIENT_EVENTS in app/api/track/route.ts, fire via the existing
fire-and-forget helper (lib/track.ts). Analytics stays non-fatal,
rate-limited, aggregate-only.
5.2 Fire points (desktop first journey)
| Event | Where it fires |
|---|---|
fj_cinematic | components/intro/first-run.tsx. Cinematic complete |
fj_pick / fj_tune / fj_duel / fj_evolve | components/intro/first-duel.tsx. The funnel's step transitions (pick → tune → fight → evolve card) |
fj_land | the Concord-landing beat (components/grounds/grounds-screen.tsx, the guided-funnel/first-landing path around the guideWorld logic) |
Fire each once per browser (localStorage latch alongside the existing
first-run flags in STORAGE) so the funnel reads as unique-visitor steps, not
replays.
5.3 Time-to-first-evolution (gate 1) without per-user tracking
The tracker is aggregate-only, so we bucket client-side: stamp
performance-independent wall-clock at first session start (localStorage,
alongside the new_user latch in lib/track.ts), and when fj_evolve fires,
send exactly one of ttfe_u5 / ttfe_u8 / ttfe_over (<5 min, 5–8 min,
8 min). Gate 1 is green when the sub-8-minute share is a healthy majority of new users. Visible as three bars on
/stats.
5.4 Dashboard
Extend the funnel array in getAnalytics (lib/server/track.ts ~line 83) with
the fj_* steps and render the deeper funnel + TTFE buckets in
components/stats/stats-screen.tsx. This turns "would people understand the
game?" into a chart with a bounce-point.
6. Platforms (Steam, console): posture, not project
- Now: nothing. Web-first is the strategy, not a compromise. Zero-install is what makes the mobile door and share cards work as growth.
- After the six Launch v0.1 gates are green: a Steam release is a marketing beat implemented as a wrapper (Electron/CEF-class shell around the same Next.js build). Prereqs to note (not build): offline-tolerant boot screen, keyboard/gamepad-only path through menus, no reliance on a visible browser URL bar for navigation.
- Console: genuinely a port + certification + likely a real accounts system. Explicitly post-accounts (Phase 6), not on any current roadmap.
- Rule until then: don't add web-only dead ends (surfaces that assume a second browser tab or external links to progress the core loop).
7. Implementation plan (ordered; each slice ships alone)
Effort scale: quick = within a session · session = one focused session · deep = multiple sessions.
T0. Measurement first (desktop funnel + TTFE). session
The instrumentation of §5. No visible product change; unblocks every "is it understandable/fun?" debate with data. Do this before touching the mobile door so the door's effect is measurable from day one.
- Files:
lib/server/track.ts,app/api/track/route.ts,lib/track.ts,components/intro/first-run.tsx,components/intro/first-duel.tsx,components/grounds/grounds-screen.tsx,lib/stats-types.ts,components/stats/stats-screen.tsx,lib/brand.ts(latch keys). - Accept:
/statsshows the 6-step first-journey funnel + 3 TTFE buckets; events fire once per browser;npm run build+ lints clean.
T1. Guest Flight (the unlock). session/deep
§3.3. The Climb runs with a loaner wild mind for visitors with no champion; claim hook on the fall card; no board/career writes while guest.
- Files:
components/mobile/mobile-shell.tsx,components/grounds/circuit-lite.tsx,components/mobile/mobile-adopt.tsx,lib/first-duel.ts(export the seeded pick if needed). - Accept: fresh browser →
/m→ Flight tab playable immediately; falling shows "Claim this mind"; claiming lands on adoption with that mind preselected; guest depth never appears on/api/circuitboards; owned-champion behavior byte-identical to today.
T2. The splash door. session
§3.2. mobile-splash.tsx + default-door rewiring + STORAGE.mSplash +
m_* events. Depends on the poster asset. If art isn't ready, ship with
a typographic poster (brand + tagline over a dark gradient) and swap the image
later; don't block the flow on the asset.
- Accept: cold phone visit → poster → one tap → flying; returning owned trainer skips to their last browse tab; splash never re-shows in-session.
T3. Vocabulary sweep (fantasy, not tech). quick
§2.2. Audit player-facing strings for "agent/AI/LLM"; replace with fantasy copy. Copy only. No identifier, key, or route changes.
- Files:
components/home/landing.tsx,components/mobile/*,/howitworks, share-card copy./agents,/readme, docs stay technical. - Accept:
rg -i "\bagents?\b|\bLLM\b" components app --glob '*.tsx'returns only developer surfaces (/agents, docs renderers) and code comments.
T4. Read the numbers, then decide. ops, recurring
After T0–T2 have a week of traffic: read the funnel bounce-point, the TTFE
buckets, and guest→claim conversion on /stats. The next product decision
(deepening watch-drama vs. Shortening onboarding vs. Mobile-door iteration)
is made from that chart, not from intuition. Feeds the weekly ledger.
Dependencies & open questions
- Poster art (T2). The one external asset. Owner: human.
- Splash for owned trainers: current call is skip-after-first-session; revisit if returning players report missing the "front door" feel.
- Guest depth → first career mark on claim (3.3): the one-time conversion needs a small design pass on which reward path writes it (suggest: the normal Climb reward path with the guest depth replayed as one run). Punt-able: T1 ships without the conversion (claim just unlocks marking from then on).
- Unrelated but sequenced before all of this: the four device-verify items already in the ledger (Flight feel, Amphitheatre, live-fight latency). Decide the door only after confirming the door feels good on a phone.
8. What this doc deliberately does NOT change
- The desktop game's scope, the engine, rating, the judge bounds, Crowns
server-authority. Untouched (guardrails in
AGENCY.md). mobile.md's shell architecture, verb bodies, and M1–M3 sequencing. Still the plan for the lobby's depth. Only its §3 default door is superseded.- The Launch v0.1 six gates. This doc serves gates 1, 2 and 5; it replaces none of them.
- Analytics/event keys,
boutvocabulary in code. Stable, perAGENTS.md.