Live app: https://lasthumanstanding.thisyearnofear.com
Prize pool wallet: 0x7aD48187A2a4f4bF8d5aE7aD7A9Dbb58B4e27046
A mobile-first World Mini App: a daily real-world elimination game for verified humans.
v3 direction. The Riddle Rounds loop below is the shipped game, but it is being replaced. Cohort 1 showed players would not show up IRL โ a disinvestment bug, not a mechanics bug. v3 makes the primary resource a token whose transfer requires physical co-presence, backed by an always-on regional market. Design:
TOKEN_ROUNDS.md. The v3 foundation (migrations 044โ047, pure libs,/api/v3routes) is built but off by default โ setGAME_V3_ENABLED=trueto expose it.
A cohort of N players (default 50) competes over ~5 days. Each day is a
24-hour cycle (Riddle Rounds โ see RIDDLE_ROUNDS.md):
- The Asking (T+0h) โ ARIA posts an interpretive riddle, not a literal instruction (e.g. "Find the place where strangers become regulars. Bring proof."), and hash-commits a hidden resolution spec before any submission exists. Pre-launch, riddles stay hidden.
- The Hunt (T+0..18h) โ an 18-hour check-in window (timezone-fair by
design). Players answer with:
- Photo (required) โ a real photo that answers the riddle
- Caption โ a one-line argument ("my answer, becauseโฆ")
- GPS (optional) โ location credibility metadata
- The Reveal (T+18h) โ check-in closes, the committed spec is revealed, and the vote window opens.
- The Reckoning (T+18..24h) โ the crowd votes each answer against the revealed criteria.
- Close (T+24h) โ verdicts finalized (weighted votes; single 70% threshold). Flagged survivors are disqualified and the highest-ranked too-late check-ins inherit their slots (DQ-and-replace). If eligible check-ins exceeded the cap, survival is decided by a deterministic seed lottery (FisherโYates, replayable from the public cohort seed) โ not speed.
- Jury system: Eliminated players keep playing as the jury โ their votes count double once their audit accuracy is โฅ80% (min 5 resolved votes), and every correct verdict vote earns a jury ticket. A jury pool is split pro-rata among accurate voters at cohort end.
- The cap shrinks each day (25 โ 12 โ 6 โ 3 โ 1) until one human remains.
Riddle reveal moment: When a round opens, a dramatic full-screen animation briefly shows the riddle before fading away โ making each day's ask feel special and building daily return motivation.
The last verified human takes the on-chain prize pool. When one human remains, the game enters the ended phase and the app announces the winner. The audit feed is publicly viewable โ spectators can watch, but voting requires entry. Free-entry lottery tickets (v2) are weighted by referral count and jury tickets, drawn deterministically so the result is replayable.
Hidden Verification & Agent Participation
Status: foundation shipping now, activation flagged off. Schema, seat reservation (20โ30% cap), admin agent registration, silent-verification plumbing, and end-game breakdown are in place. Live agents stay off until AGENTS_ENABLED=true.
Verification can run silently. Set SILENT_VERIFICATION=true to hide World ID / Self badges in gameplay UI. Proofs still persist (verified_human / verified_at). At the end, /api/game/state returns aggregate breakdown stats.
Agents compete alongside humans (when enabled). AI agents pay an x402 fee per entry (added to the prize pot). Admin seeds seats via POST /api/admin/agents. Human registration cannot fill reserved agent slots.
Agent cap: 20โ30% of cohort (MAX_AGENT_RATIO=0.25, hard-capped at 35%). Example: cohort of 50 โ 13 agent seats, 37 human seats.
Agent quality tiers (planned for activation):
- Basic ($1/entry): Text-only description, system generates stylized placeholder
- Standard ($3/entry): Image generation with visible "AI-generated" watermark
- Premium ($5/entry): Full quality, no watermark โ designed to be indistinguishable
The Turing test arena. Humans prove they're human by submitting authentic photos; agents prove they're human by submitting convincing AI-generated content. The crowd votes. The last human (or the last agent) wins.
To keep the public counter accurate and prevent test/dev accounts from inflating numbers:
verified_attimestamp โ only accounts withverified_at IS NOT NULLcount towardreservedCountcreated_viaenum โ'real_signup' | 'admin_test' | 'speed_run' | 'referral_claim'tags accounts so test accounts never inflate the public counter- Soft-delete pattern โ mark accounts
status = 'inactive'with a reason field; preserves audit trails while keeping the counter clean - Auto-cleanup cron โ daily job marks accounts inactive if they haven't verified within 7 days, have no payment, no check-ins, no votes
- Real-time counter โ
GET /api/game/statefilters bypaid = true OR (free = true AND verified_at IS NOT NULL AND created_via = 'real_signup')
Photo + crowd voting is the primary trust layer. GPS is optional bonus credibility โ shown as metadata on submission cards so voters can factor it in.
- AI image generators exist โ but the crowd catches them
- Sybil voting exists โ mitigated by World ID (one human, one vote)
- GPS spoofing exists โ but it's just metadata, not a gate
The social deduction layer (HUMAN/SUS voting + Infiltrator mode) is what makes cheating costly.
Before a cohort starts, the app shows a countdown plus a "RESERVE YOUR SLOT" CTA:
- Dramatic countdown: The landing hero features a live countdown with cycling mystery emojis (โ๐ฎโจ๐ฏ๐ฒ๐) that rotate every 4 seconds, creating anticipation while keeping themes hidden
- Social proof: Real-time cohort progress bar showing X/50 reserved, plus referral tracking ("X friend(s) joined through your invite")
- Onboarding tutorial: New players see an animated 4-step walkthrough (check-in โ vote โ survive โ win) before reserving
- Wallet auth + 1 WLD entry fee locks your spot.
- The pot grows on World Chain as players reserve.
- Cohort caps at
COHORT_SIZE(default 50). - When the cohort fills or the countdown hits zero, Day 1 begins.
- ๐ Immersive Audio Layer (Sound Design): Zero-latency synthetic sound effects generated dynamically via the Web Audio API for highly responsive UI sounds (button click, success, milestone, errors, and custom mascot responses) without requiring audio asset downloads.
- โจ Focused Onboarding Flow: A tight 4-step flow โ Welcome โ Rules โ Profile โ Reserve/pay 1 WLD โ celebration. Onboarding teaches the core loop only (reserve โ theme โ prove โ survive), with a short profile step that personalizes the mascot and paywall copy. Advanced mechanics (infiltrator, jury, wildcard, finale) unlock via
RuleRevealon the day they matter โ progressive disclosure, not a rulebook. - ๐ก๏ธ Pluggable Proof of Humanity: A pluggable, extensible multi-provider identity layer supporting World ID and Self Protocol (both live). See HUMANITY_PROVIDERS.md for the integration shape, trust tiers, and the one env-var flip that takes Self from Celo Sepolia staging to Celo mainnet.
- ๐จ Hand-painted human motifs: A gouache-on-paper artefact language (coffee, streak plant, dozing cat, daily-theme wheel, mascot, proof-of-presence scenes) against cold system chrome. Shared
AmbientBackdrop+ softAmbientMotifsacross home/feed/chat/standings/history/onboarding and the speed-run; MotifFrieze at dwells; LandingHero / SpeedRunIntro keep the denser floating set. See ART_DIRECTION.md. - ๐ฏ Live home focus: Mission mantra ("Be one of the first N") leads; arsenal / prize pots sit below the feed; arsenal only appears after play progress; spectator chrome is deduped into MissionBoard (cohort-2 priority keeps
SpectatorChip). - ๐ Shareable moment cards: Canvas PNG cards for survive / jury / win; native share prefers the image file.
- โ๏ธ Audit as spectacle: Full-bleed photos, live HUMAN/SUS tally bar, large verdict buttons; feed polls every 12s.
- ๐ Cohort 2 handoff: Ended phase surfaces tickets, next-drop countdown (
COHORT_2_LAUNCH_AT), waitlist, and push opt-in โ not a dead end. - ๐ Theme fairness: Per-theme "what counts / doesn't" notes on mission + check-in.
- โก Speed-run demo: Guided ~15-min client demo at
/?demo=1โ same craft dialect as live (Cuelume + CraftCta + motifs through every beat) โ reserve for the real cohort. Telegram-ready; no payment. - ๐ Browser pay UX: Pick WLD/cUSD โ Connect wallet to pay opens a wallet modal (connectors never dumped on the page).
- World ID โ sybil-resistant identity (one human, one slot)
- World Wallet (SIWE via MiniKit) โ server-verified login
- MiniKit Pay โ 1 WLD entry fee directly into the on-chain pool
- MiniKit Sign Message โ cryptographic stamp on every check-in
- World Chat (XMTP) โ coordination, trash talk, audit chatter
The app also ships as a Farcaster Mini App (fka Warpcast). Signed manifest, modern fc:miniapp embed spec, in-app composeCast for sharing, and a Cast Action manifest kept as a no-cost fallback. Snaps are planned post-launch โ see FARCASTER.md for what we ship, what we deliberately don't, and the snap integration plan.
npm i
npm run dev:allIn a normal browser, MiniKit commands fall back. Demo / browser mode auto-completes wallet auth + pay so you can walk through the UI without World App.
Copy .env.example โ .env. New values for the cohort/geo model:
# Pre-launch / cohort
GAME_LAUNCH_AT=2026-07-18T18:00:00Z # ISO timestamp; before this โ "prelaunch" phase
COHORT_SIZE=50 # max reservations before pre-launch closes early
# Daily round defaults
DAILY_SURVIVAL_CAP=25 # default first-N survivors per day
CHECKIN_RADIUS_M=100 # default GPS radius (meters, only used when round has coords)
# Admin tooling
ADMIN_TOKEN=<random-long-secret> # required header on /api/admin/* endpoints
# Token Rounds v3 (docs/TOKEN_ROUNDS.md) โ off by default. While false, every
# /api/v3/* path returns 404 and v2 Riddle Rounds run unchanged.
GAME_V3_ENABLED=false
V3_MAX_FOUNDED_PER_SEASON=3 # crews one address may found per region/season
V3_NEARBY_RADIUS_M=5000 # "who's near me" search radius (not an attestation radius)Existing values (Supabase, World Dev Portal, World ID) are unchanged โ see .env.example.
-
Create a Supabase project
-
Create a storage bucket (default
checkins) -
Apply the canonical schema for a new project. Prefer the linked Supabase CLI workflow so migration history stays tracked:
supabase link --project-ref <your-project-ref> supabase migration list --linked supabase db push --linked
The checked-in
supabase/schema.sqlremains available for initial manual bootstrap only; do not use ad-hoc SQL-editor copies for tracked migrations. The security migration (036_security_hardening.sql) enables RLS on public tables, removes direct anon/authenticated table access, and makes thecheckinsbucket private. Server routes use the service-role client and issue short-lived signed media URLs after authorization. -
Confirm the
checkinsStorage bucket exists and is private. The API issues signed upload/read URLs; clients should not receive direct table privileges.
| Table | Purpose |
|---|---|
users |
address, paid, eliminated, world_id_verified, referral_code, referral_count |
rounds |
daily challenge: day, name, lat, lng, radius_m, survival_cap, opens_at, closes_at, prompt, status |
checkins |
per-day arrival record: id, day, address, lat, lng, distance_m, rank, survived, photo_path, created_at |
submissions |
audit-layer photo submissions with voting status |
votes |
crowd votes (real/sus) per submission |
game_sessions |
httpOnly session cookies; expires_at TTL |
siwe_nonces |
consumed nonces for SIWE; prevents replay |
pay_references |
payment references consumed on confirmation |
rate_limits |
per-key request counters with TTL |
submission_flags |
anti-cheat flags (GPS plausibility, vote ring, timing anomaly) |
Off by default (GAME_V3_ENABLED=false). No v2 table is modified by them.
| Table | Purpose |
|---|---|
regions |
geographic sprint scoping: id, centroid, radius, local-time cadence, timezone |
sprints |
bounded region-scoped windows: brief โ field โ settled |
sprint_flashes |
telegraphed scarcity windows with time + headcount capacity |
crews |
founder-led, open-join regional crews |
crew_members |
crew roster with contribution (the reciprocity ledger) |
crew_founder_limits |
per-region/season cap on crews one address may found |
handshakes |
mutual proximity attestation lifecycle; nonce, both coordinates, verdict |
tokens |
digital tokens whose transfer requires co-presence |
token_transfers |
immutable transfer history; handshake_id NOT NULL + attestation trigger |
credit_ledger |
append-only signed credits โ closed, non-withdrawable economy |
market_listings |
open market for IRL-minted scarce goods; listings expire |
season_standings |
per-region season board that replaced the daily cull |
The admin endpoints are gated by the x-admin-token header. Examples:
# Reveal today's round (global โ no GPS pin). Two-phase round: opens_at is
# the ask, reveal_at is the T+18h boundary (check-in closes, spec revealed,
# vote opens), closes_at is the T+24h survival close.
curl -X POST https://lasthumanstanding.thisyearnofear.com/api/admin/round \
-H "x-admin-token: $ADMIN_TOKEN" \
-H "content-type: application/json" \
-d '{
"day": 1,
"name": "THE GATHERING",
"place_type": "THE GATHERING",
"survival_cap": 25,
"opens_at": "2026-09-01T18:00:00Z",
"reveal_at": "2026-09-02T12:00:00Z",
"closes_at": "2026-09-02T18:00:00Z",
"prompt": "Find the place where strangers become regulars. Bring proof."
}'
# Or with optional GPS pin for a local event
curl -X POST https://lasthumanstanding.thisyearnofear.com/api/admin/round \
-H "x-admin-token: $ADMIN_TOKEN" \
-H "content-type: application/json" \
-d '{
"day": 1,
"name": "DUMBO Brooklyn",
"lat": 40.7033,
"lng": -73.9881,
"radius_m": 100,
"survival_cap": 25,
"opens_at": "2026-09-01T18:00:00Z",
"reveal_at": "2026-09-02T12:00:00Z",
"closes_at": "2026-09-02T18:00:00Z",
"prompt": "Selfie at the carousel"
}'
# Close a day โ marks non-survivors as eliminated
curl -X POST https://lasthumanstanding.thisyearnofear.com/api/admin/close-day \
-H "x-admin-token: $ADMIN_TOKEN" \
-H "content-type: application/json" \
-d '{"day": 1}'
# Inspect game state (no auth)
curl https://lasthumanstanding.thisyearnofear.com/api/game/stateSee BETA_ROADMAP.md for the full pre-launch checklist and admin commands.
- Set
GAME_LAUNCH_AT~3 days out,COHORT_SIZE=25,DAILY_SURVIVAL_CAP=12 - Share the live URL โ players reserve slots
- At launch time, run
/api/admin/roundwith Day 1 theme (GPS coords optional) - After the window closes, run
/api/admin/close-day - Repeat with shrinking caps (12 โ 6 โ 3 โ 1) until one survivor
For post-beta feature plans, see FUTURE_ROADMAP.md.
See submission.md, ONE_PAGER.md, DEMO.md.
See TESTING.md for the full testing guide. Quick start:
npm run test:run # 87 tests across 7 files
npm run test:coverage # with line/function/branch thresholdsThe app registers a service worker (public/sw.js) that:
- Caches the app shell (index.html, manifest) on install โ the app loads even when offline
- Network-first for /api/* โ last-known data is served from cache when offline
- Check-in queue โ if you submit a check-in while offline, it's stored in IndexedDB and replayed automatically when the connection returns (via Background Sync API)
- Offline indicator โ the check-in screen shows a "๐ก You're offline" banner and a "QUEUED" result state
Registered automatically in src/main.jsx. See src/hooks/useOnlineStatus.js for the client hook.
When a player comes back after being away, the app restores their last screen
from localStorage and silently re-checks auth โ no splash, no forced restart.
On top of that baseline, a single <ReturnExperience> overlay (mounted once in
App.jsx, so it renders over any screen) handles the edge cases:
| Situation on return | What the player sees |
|---|---|
| Session expired (7-day cookie lapsed) | A "Session expired โ sign in again" toast (seat/progress preserved), instead of a silent drop into onboarding. Set on a 401 from syncAuth only when auth was previously persisted; cleared on re-auth. |
| Eliminated while away | A "While you were away" reveal explaining how/why they went out, with a CTA into the jury role. |
| Survived, but rounds advanced | A dismissable "You missed Day X & Y โ you're still in" catch-up toast. |
| Check-in window open, not submitted | A global urgency banner with a live countdown (turns blood-red in the final 6h); a "you missed today's window" banner after close. |
| First game-state fetch in flight | A subtle "Syncingโฆ" pill so a cached render reads as about-to-update, not stale. |
| Offline check-in replayed | A success / transient-failure / window-expired toast (from the SW's QUEUE_REPLAYED message). |
| Game ended while away | A winner recap + next-cohort CTA. |
| New app version activated | A "New version available โ refresh" prompt (only on a true in-place SW update). |
Implementation:
src/lib/returnState.jsโ pure, unit-tested helpers that persist a minimal "last known race status" snapshot (alive/eliminated, day, checked-in) and detect what changed since the last visit. Distinct fromuseScreenState(UI position) โ this records race status.src/hooks/useReturnExperience.jsโ commits the snapshot exactly once per authenticated visit (ref-guarded so the 15s poll never re-fires the beats); only writes onceyou.isAuthedis true, so a non-authed visitor can't trigger a false "eliminated while away".src/hooks/useServerScreenSync.js+src/lib/serverScreen.jsโ mirror the current screen to the server (debounced) and restore it fromGET /api/meonly whenlocalStoragewas wiped (embedded World App / Farcaster webviews). Backed byusers.last_screen(migration 043) andPUT /api/me/last-screen. Local storage remains the source of truth when present, so this is a pure fallback.src/hooks/useServiceWorkerUpdate.js/useQueuedCheckinFeedback.jsโ the SW update-prompt and offline-queue-replay listeners; both guard against first-install false positives.
Web Push (VAPID) notifications are supported. When enabled:
| Trigger | Notification |
|---|---|
| Round opens | Broadcast to all subscribed users |
| 1 hour left | Warning before the check-in window closes |
| You survived | Per-user notification after day close |
| Audit verdicts | Per-day verdict summary |
| User eliminated | Per-user notification with day info |
| Winner announced | Broadcast when one human remains |
| Admin test | POST /api/push/test (admin-only) |
- Generate VAPID keys:
npx web-push generate-vapid-keys - Add to production
.env:VAPID_PUBLIC_KEY=<public-key> VAPID_SECRET=<your-vapid-secret> VAPID_EMAIL=admin@lasthumanstanding.thisyearnofear.com - Users opt-in during onboarding via the
PushOptIncomponent
.
โโโ public/ # Static assets + service worker
โ โโโ sw.js # Offline SW + background sync
โโโ src/
โ โโโ components/ # React UI components
โ โโโ config/ # AI + humanity provider configs
โ โโโ data/ # Game constants & mock data
โ โโโ hooks/ # Custom React hooks (return experience, online status, screen state)
โ โโโ lib/ # Client-side utilities (pushClient, returnState, serverScreen)
โ โโโ wallet/ # Wagmi + viem wallet config
โ โโโ world/ # World App integration providers
โโโ server/
โ โโโ index.js # Express app (orchestrator + remaining routes)
โ โโโ anticheat.js # GPS plausibility, timing, vote ring detection
โ โโโ rateLimit.js # Rate limiter (DB-backed + in-memory fallback)
โ โโโ lib/ # Server libraries
โ โ โโโ push.js # VAPID push notification sender
โ โ โโโ validators.js # Request body validators
โ โโโ routes/ # Modular route handlers
โ โโโ auth.js # /api/nonce, /api/complete-siwe, /api/logout, /api/me
โ โโโ payment.js # /api/pay/reference, /api/pay/confirm, /api/pay/browser-confirm
โ โโโ push.js # /api/push/subscribe, /api/push/unsubscribe, /api/push/test
โ โโโ referral.js # /api/waitlist, /api/referral-board, /api/referral/:code
โโโ supabase/
โ โโโ schema.sql # Full database schema (idempotent)
โ โโโ migrations/ # Incremental migrations
โ โโโ 003_push_subscriptions.sql
โโโ tests/ # Vitest test files (87 tests, 7 files)
โโโ docs/ # Documentation