Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
Describe a game or a short video and watch it come to life as Frankie builds it with you. Play, share, remix, download.
GamesJavaScript · HTMLGitHub @JorgeOchoaReyesOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Vibe code games
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Polly Studio (named after Polly, a black, gold and white chihuahua) is where people make
games and short videos by describing them. They sign in, say what they want, and
build it in the studio with Frankie, the AI chat that plugs in as a React SDK
(@frankieai/react). They watch it change live, then publish it, remix other people's
work, download a game as a zip, or record a video as an MP4 for Shorts, TikTok and Reels.
Kit.film: 3D (Three.js) or 2D scenes with pop captions,
recorded in the browser at 720p as 9:16, 16:9 or 1:1. With an ElevenLabs key (each user
adds their own in Settings), 🎙 Voiceover narrates each scene's caption.Every game and video follows the practices behind Ember, the reference game in
games/ember: one readable index.html, a vendor/ folder beside it, no network,
procedural art and synthesized sound.
Live: https://game-01-eta.vercel.app
A Turborepo with pnpm workspaces:
apps/
web/ Next.js site + API: accounts, studio (Frankie embedded), game and video pages, game API
firebase.json, firestore.rules, firestore.indexes.json Firebase config, emulators, rules, indexes
relay/ WebSocket relay for online multiplayer (Kit.net rooms)
packages/
game-kit/ vendor/game-kit.js, the runtime every game and video loads
practices/ the house rules, game-kit reference and agent workflow (what Frankie reads)
templates/ starters: Slime Arena, Star Catcher, Campfire 3D, Coin Rush, How Did the Ancients Cook? (video)
games/
ember/ Ember, the reference game (its own build, verify and package scripts)
docs/
FRANKIE.md connecting a Frankie workspace to Polly Studio
.github/workflows/
ci.yml build + all tests (Firebase emulators included) on every push
firestore.yml deploys Firestore rules and indexes when they change (keyless)
/ and /studio). Pick 🎮 Game or 🎬 Video, describe it, and Polly Studio
makes it from the closest starter and opens the studio with that idea already sent to
Frankie. Videos also pick a format: Short 9:16 (default), Landscape 16:9 or Square 1:1.
Signed-out visitors go through sign-up and land back in the studio with their idea./studio/<id>). Frankie's chat sits beside a sandboxed preview that reloads
whenever a new version is saved. Frankie edits without approval cards (approvals="auto";
deleting games isn't an allowed action). If a game throws, Ask Frankie to fix it sends
the error to the chat. The toolbar has version history, Restart, public/private, download,
a room code for multiplayer games, and for videos: the format switch, 🎙 Voiceover and
⏺ Record video.Kit.film(scenes): each scene has a duration, a caption
and a draw(ctx, t, p). Kit.film keeps to the real clock, crossfades between scenes, adds
a vignette and film grain, and draws pop captions a few words at a time
(*starred* words are highlighted in gold).CFG.format sets the shape;
the camera keeps the subject big, centred and a little above the captions.Kit.record: the canvas plus every Kit sound, as MP4 where the browser supports it (else
WebM), at 720×1280 / 1280×720 whatever the window size. The studio downloads the file.POST /api/games/<id>/narration) reads each scene's caption with the
owner's ElevenLabs key and voice. Clips are cached in games/{id}/voice by voice and
text, so unchanged lines aren't paid for twice. The studio hands them to the film, which
plays each clip as its scene starts and stretches the scene to fit; recordings include it.POST /api/auth/session for a 14-day session
cookie, sending its timezone for streaks. API clients can send a Firebase ID token as
Authorization: Bearer./settings). Connect ElevenLabs with your own API key and pick a voice.
The key is checked with ElevenLabs, stored in users/{uid}/private/elevenlabs, and
never sent back to the browser (the page shows its last 4 characters).users/{uid}.streak.firestore.rules denies browsers everything.
users/{uid}: name, email, timezone, streak; users/{uid}/private/elevenlabsgames/{id}: the current file, version, visibility, play count;
games/{id}/versions/{n} (every save) and games/{id}/voice/{hash} (voiceover clips)/api/v1, documented at /api/v1/openapi.json), built for an AI agent and
kept inside Frankie's limits (every response under 8,000 characters):
format), rename/publish, delete/api/auth/session, /api/auth/logout,
/api/auth/me, /api/frankie/token, /api/me/voice (GET/PUT/DELETE ElevenLabs
settings), /api/games/<id>/narration, /api/games/<id>/play (play count + streak).POST /api/frankie/token turns the signed-in user into a short-lived JWT for
the SDK. Frankie's tool calls run in the user's browser against /api/v1 of whichever
site the studio is on, with that site's cookie, so it builds as the user, locally or in
production. Setup: docs/FRANKIE.md.createGame)
checks the plan in lib/server/billing.ts; over the limit the API answers
402 payment_required and the site sends people to /pricing.STRIPE_SECRET_KEY and STRIPE_PRICE_ID billing is off
and nobody is capped, so a deploy never locks anyone out before Stripe is set up./pricing → POST /api/billing/checkout (Stripe Checkout, subscription mode) →
back to /pricing?checkout=<session>, which saves the subscription straight away.
POST /api/billing/webhook keeps it current afterwards (renewals, cancellations, failed
payments). POST /api/billing/portal opens Stripe's customer portal from Settings.
GET /api/me/plan returns the plan.users/{uid}.billing: Stripe customer and subscription ids, status, renewal
date, cancelAtPeriodEnd, and comp (Pro given by an admin, no subscription).Make the product and price (safe to run again; it finds them by lookup key
polly_pro_monthly). With --webhook it also registers the webhook and prints its
signing secret once:
cd apps/web
STRIPE_SECRET_KEY=sk_live_... node scripts/stripe-setup.mjs --webhook https://www.pollystudio.app/api/billing/webhook
# prints STRIPE_PRICE_ID=price_... and STRIPE_WEBHOOK_SECRET=whsec_...
Point the webhook at the domain the site is served from (www): Stripe doesn't follow
redirects for webhooks. The endpoint listens for checkout.session.completed and
customer.subscription.created, .updated and .deleted.
Set the variables in Vercel (Production; mark the secrets Sensitive), then redeploy:
STRIPE_SECRET_KEY, STRIPE_PRICE_ID, STRIPE_WEBHOOK_SECRET, and make sure
POLLY_PUBLIC_URL is the live origin, since Checkout returns people there.
In the Stripe dashboard: turn on the customer portal (Settings → Billing → Customer portal), and check the public business name and support email, which Checkout and receipts show.
Check it: open /pricing signed in, subscribe, and confirm Settings shows Pro. In
Stripe → Webhooks the endpoint should show 200s.
For local development use a test key (sk_test_...) in apps/web/.env.local, run the
same script without --webhook for a test price, and set any STRIPE_WEBHOOK_SECRET; the
return from Checkout saves the subscription without the webhook. To try the webhook
locally, forward events with the Stripe CLI (stripe listen --forward-to localhost:3100/api/billing/webhook) and use the secret it prints. Test card: 4242 4242 4242 4242, any future date and CVC.
An admin has the Firebase custom claim admin: true. They have no plan limit, can open and
edit anyone's game (in the studio, through the API, and through Frankie acting for them),
and get /admin: headline numbers, a live activity feed, recent people and games, and a
page per person (/admin/users/<uid>) with their plan, games and history, where they can
give or take back free Pro. Activity is logged to events (sign-ups, games started, saves
and their notes, publishing, restores, plays, voiceovers, checkout and subscription changes),
marked "via frankie" when it came through Frankie.
cd apps/web
node scripts/set-admin.mjs [email protected] # local emulator
GOOGLE_CLOUD_PROJECT=polly-7140d node scripts/set-admin.mjs [email protected] # production, after `gcloud auth application-default login`
node scripts/set-admin.mjs [email protected] --remove
The claim reaches the site at the next sign-in: log out and back in.
Game violence is fine; sexual content, nudity, slurs and vulgar language are not (/rules).
Frankie is told so in the game guide. lib/server/moderation.ts checks a game's title and
on-screen text (quoted strings and page text, not comments) when it's published and on every
save while it's public, and refuses with 422 content_not_allowed; the studio shows why.
Anyone can report a public game from its page (POST /api/games/<id>/report); reports
appear in the /admin feed, where an admin can open the game and unpublish it.
/blog (posts in lib/posts.tsx), /faq (lib/faqs.ts, also on the home page),
/roadmap, /rules, three guides (/ai-game-maker, /ai-video-maker,
/mobile-game-maker), and sitemap.xml, robots.txt and llms.txt generated from them.
/g/<id> is a game or video's page: cover art, a big ▶ Play, ■ Stop playing, full
screen, play count, Remix and Download. Anyone can open public ones; only the owner can
open private ones. /play/<id> does the same for the starters./games (Discover) lists public work, New or Popular, with search.Kit.input.setPlayers(2) gives two players on one keyboard.
Kit.net.connect() joins an online room on the relay; the first player in is the host,
and invite links carry ?room=CODE.Kit.canvas (landscape 1280×720 by default, letterboxed, DPR aware), Kit.loop,
Kit.input (keys, axis, touch drag, tap, mouse() for aiming and clicking),
Kit.audio (synthesized tones, noise and sfx), Kit.fx (particles, text, shake),
Kit.store, Kit.ui, Kit.net, Kit.film and Kit.record. The full list Frankie works
from is in packages/practices/src/index.ts and at
/practices; saves warn about any Kit.* name that doesn't exist.
pnpm install
pnpm --filter @polly/web emulators # Firebase Auth + Firestore emulators (needs Java 21), UI on :4000
pnpm dev # web on :3000, relay on :8787
Locally the web app talks to the Firebase emulators (project demo-polly) whenever no
Firebase variables are set, so you don't need a Firebase project. If port 3000 is busy,
run pnpm build once, then pnpm --filter @polly/web dev --port 3100.
For Frankie in local dev, copy apps/web/.env.example to apps/web/.env.local
(git-ignored) and fill in NEXT_PUBLIC_FRANKIE_PUBLISHABLE_KEY and FRANKIE_JWT_SECRET.
The Frankie workspace allows localhost, and the studio points Frankie's tool calls at the
site it runs on, so local edits change your local games. ElevenLabs needs no environment
variable: sign in and add your key in Settings.
Other tasks:
pnpm build # builds game-kit, templates, Ember, then the site
pnpm test # game-kit, relay and web tests (web starts the Firebase emulators itself)
pnpm --filter @polly/web deploy:firestore # deploy Firestore rules and indexes by hand
pnpm --filter @polly/ember package # Ember's original submission zip
If the emulators are already running (for pnpm dev), run the web tests against them
instead of letting the test script start its own:
cd apps/web
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099 GCLOUD_PROJECT=demo-polly npx vitest run
apps/web)| Variable | What |
|---|---|
NEXT_PUBLIC_FIREBASE_API_KEY, _AUTH_DOMAIN, _PROJECT_ID, _APP_ID | the Firebase web app config (empty locally means the emulators) |
FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, FIREBASE_PRIVATE_KEY | an Admin SDK service account (required in production; the key's newlines may be written as \n) |
POLLY_PUBLIC_URL | public origin for links and the OpenAPI servers URL (defaults to the request origin) |
FRANKIE_JWT_SECRET | shared with the Frankie workspace; signs the user tokens Frankie verifies |
NEXT_PUBLIC_FRANKIE_PUBLISHABLE_KEY | the Frankie workspace key; without it the studio shows a setup note instead of the chat |
NEXT_PUBLIC_FRANKIE_URL | only when self-hosting Frankie |
NEXT_PUBLIC_RELAY_URL | relay address for previews and downloads (default ws://localhost:8787) |
STRIPE_SECRET_KEY | Stripe secret key (sk_live_… in production, sk_test_… locally); unset means billing is off |
STRIPE_PRICE_ID | the Pro monthly price, printed by scripts/stripe-setup.mjs |
STRIPE_WEBHOOK_SECRET | the webhook's signing secret (whsec_…) |
NEXT_PUBLIC_SITE_URL | canonical origin for SEO tags, the sitemap and social cards (default https://www.pollystudio.app) |
The relay reads PORT and MAX_ROOM.
apps/web deploys to Vercel from the repo's default branch. In the Vercel project
settings, set Root Directory to apps/web; apps/web/vercel.json builds the
workspace packages through Turbo before next build. Set the variables above for
Production and Preview, and redeploy after changing them.pollystudio.app
and www.pollystudio.app) to Auth's authorized domains, and create a service account key
for the FIREBASE_* variables.apps/web/firestore.rules and
firestore.indexes.json. .github/workflows/firestore.yml deploys them when they change,
without a stored key: GitHub's OIDC token is exchanged for the github-deploy service
account through Workload Identity Federation (pool github, limited to this repo).apps/relay needs a host that keeps WebSockets open (Fly.io, Render, Railway or a small
VM). Set NEXT_PUBLIC_RELAY_URL=wss://your-relay on the web app.| Piece | Where |
|---|---|
| Site | Vercel project game-01 (team jorgeochoareyes-projects), https://www.pollystudio.app (the bare domain redirects to www; https://game-01-eta.vercel.app still serves) |
| Billing | Stripe: product Polly Studio Pro, $10/month, lookup key polly_pro_monthly |
| Firebase | project polly-7140d: Auth (email, Google) and Firestore in nam5 |
| Frankie | workspace ws-402c89bc689e, OpenAPI from /api/v1/openapi.json, 11 of 12 actions allowed (not delete), localhost allowed |
| CI | GitHub Actions: ci.yml on every push, firestore.yml for rules and indexes |
Drop an index.html-style file in packages/templates/templates/, give it a title,
emoji, kind and blurb in packages/templates/scripts/build.js, and follow
the practices. It then shows up on Home, Discover, the
Create page and in the API's listStarters. A starter whose kind is Animation is listed
as a video. Games should be landscape unless they're meant for phones; videos should use
Kit.film and a CFG.format.
code.test.ts, line numbering) fails on Windows checkouts because template
files get CRLF line endings; it passes on Linux and in CI.