Inspiration

Problem: ~39 friends wanted a competitive physics racer with real scores — not screenshots in the group chat. No accounts, no app store, no friction.

Why DynamoDB: Every access pattern is a keyed lookup — top scores by track, personal bests by player, run history by player. No joins, predictable reads, and a single-table model that still works if 39 friends becomes 39,000 players. That’s why I chose Amazon DynamoDB over Aurora for Gotham Knights.

The H0 stack (v0 + Vercel + AWS Databases) let me ship a Stonkrider-style game over a weekend while keeping a production data layer from day one.

What it does

Gotham Knights is a neon 2D physics Batmobile racer:

  • 39 operatives — choose your rider (saved on device, no login)
  • 10 Gotham districts (sigils 0–9) + 4 villain chase tracks
  • Canvas physics — wheelies, jumps, air tricks, stunts, collectibles
  • Live leaderboards — top scores per track; compare friends (e.g. drmark vs prez)
  • Share to X — posts your score and the district #1 rival
  • District-themed visuals — unique skylines, colors, and Knightwatch lore per district
  • Mobile + desktop — touch controls, compact HUD, inline top-5 on track cards

Live app: https://gotham-rider-game.vercel.app

How I built it

Frontend (Vercel)

  • Scaffolded with v0.app, then extended in Next.js 16, React, and TypeScript
  • Custom Canvas game engine — terrain generation, physics, camera, district backgrounds
  • Deployed on Vercel with serverless API routes

Database: Amazon DynamoDB

Connected via the Vercel Marketplace AWS integration. API routes authenticate with Vercel OIDC (awsCredentialsProvider) to assume an AWS IAM role at request time — no long-lived access keys in the repo.

Single-table composite key design:

PK SK Purpose
PLAYER#<slug> RUN#<timestamp>#<runId> Run history
PLAYER#<slug> BEST#<trackId> Personal best per track
LEADERBOARD#<trackId> PLAYER#<slug> Leaderboard row (query by track)

When a run finishes, POST /api/runs writes the run, checks the player’s best, and updates the leaderboard row only if the score improved.

API routes:

  • POST /api/runs — persist run, update best + leaderboard when improved
  • GET /api/leaderboard?track=<id> — top scores for a track (Query on LEADERBOARD#<trackId>)
  • GET /api/leaderboard?slug=<slug> — player bests across tracks (Query on PLAYER#<slug>)

Architecture

Browser / Mobile → Next.js on Vercel → API routes → Vercel OIDC (assume IAM role) → Amazon DynamoDB

Challenges I ran into

  1. DynamoDB composite keys — Early deploys wrote items but leaderboards stayed empty. Root cause: PK/SK misalignment with the marketplace table schema. Fixed by matching composite keys and switching from Scan to Query.

  2. Deploy pipeline — Hit v0 credit limits mid-build. Overrode the build command in vercel.json and continued shipping via Vercel CLI.

  3. Mobile UX — Accidental text selection on HUD labels, overlapping controls, camera jitter on big jumps. Fixed with user-select: none, HUD layout passes, and simplified camera follow.

  4. 39 operative avatars — Processed custom bat-cowl PNGs, mapped slugs to display names, served from /public/heads/.

Accomplishments I'm proud of

  • A playable, deployed game friends can open on their phones today
  • Real persistent leaderboards on DynamoDB — not localStorage
  • Per-district theming with Gotham Knightwatch lore on track select and start screens
  • Production patterns from a hackathon build: OIDC to AWS, composite DynamoDB keys, serverless API
  • Share flow that names the district #1 rival when you post to X

What I learned

  • v0 is strong for UI scaffolding; game physics and database correctness still need hands-on iteration
  • DynamoDB single-table design fits leaderboards + history cleanly when you model PK/SK around how you query, not how tables look in SQL
  • Vercel + AWS Marketplace OIDC is a credible hackathon → production path
  • Small UX details (mute, mobile HUD, aligned track cards) matter as much as the core loop for a social game

What's next for Gotham Knights

  • Longer tracks, branching paths, and trap mechanics
  • Optional auth (magic link or passkey) for verified leaderboards
  • Seasonal villain events and weekly district rotations
  • OpenGraph share cards per track with live #1 score

Built With

Share this project:

Updates