What is Blast Tanks?
Blast Tanks is a multiplayer tank battle game that runs entirely inside a Reddit post — no app download, no external site, no login beyond your Reddit account. Tap Play on the animated splash, pick your tank, and you're in a 3-minute arena in seconds.
Inspiration
Reddit is where communities form around shared interests. We wanted to build something that makes the post itself the game — where the comments section becomes the place you share your room code with your opponent, where your Reddit flair shows your rank, and where beating someone in a wager match gets announced to the whole community.
What We Built
- Quick Play — drop into an offline arena against 4 AI bots instantly. No wait, no matchmaking.
- Live Multiplayer — host a lobby, share a 6-character room code via comments or DM, battle live with up to 6 players using Devvit Realtime.
- XP Wager System — attach an XP stake to any 1v1 lobby. XP is held in server-side Redis escrow and paid to the winner (2×). If the match fails to complete, both players are refunded automatically.
- Daily Challenge — a new objective every UTC day (kill count, survival time, wager wins, etc.) seeded deterministically — no scheduler needed.
- 8-Tier Rank Progression — Recruit → General, tracked in Redis. Ranking up triggers a Reddit flair update and a community announcement comment via the Reddit API.
- Day Streak — consecutive daily play builds a streak tracked with Redis TTL keys.
- Premium Tank Skins — four unlockable skins purchasable with Reddit Gold via the native Devvit payments system.
- Mobile Controls — full dual-stick virtual gamepad with multi-touch support so the game is fully playable on the Reddit mobile app.
How We Built It
The game is built on Devvit 0.13.8 using the web-first architecture — two HTML entrypoints served inside Reddit iframes:
splash.html— an animated Three.js scene (two tanks facing off, orbiting camera, live bullet fire) that serves as the inline post preview and comeback hook, showing your last match result.game.html— the full expanded game built with React, Three.js, and Enable3d (Ammo.js physics).
The server runs on @devvit/web/server with Hono routing, using Devvit Redis for all persistent state — player XP, streaks, leaderboard sorted sets, wager escrow, and rate-limit counters. Multiplayer uses Devvit Realtime pub/sub channels for live game events.
Navigation inside the expanded iframe is handled with React Router (MemoryRouter), state with Zustand, and animations with Framer Motion.
Challenges
Devvit's iframe sandbox has no access to window.location for real URLs — we discovered this when the room code copy button was generating broken https://www.reddit.com/join/ABC123 links. Fixed by copying just the room code and telling players to share it via comments.
Redis consistency — wager XP mutations had to sync both a player hash (player:${username}) and a leaderboard sorted set (lb:xp) atomically. Getting create/join/cancel/settle all consistent took careful sequencing.
Physics on mobile — Ammo.js is heavy. We tuned the Three.js renderer pixel ratio cap, shadow map resolution, and bot update frequency to keep the game playable on mid-range phones.
AI bots that feel fair — TankBot needed anti-stuck reversal, auto-right when flipped, and void teleport recovery so offline matches always feel complete rather than broken.
PeerJS blocked by Reddit's Content Security Policy — The game worked perfectly in local dev but showed only a blank background inside Reddit. The cause was subtle: store.ts statically imported ClientNetwork and ServerNetwork, which both do import Peer from 'peerjs'. PeerJS uses eval() / new Function() internally, which Reddit's iframe CSP blocks. This crashed the entire JS bundle during module evaluation — before React could mount — so even our ErrorBoundary never fired. The fix was a React.lazy() dynamic import split: in Devvit mode the app loads a DevvitApp chunk that never references PeerJS; in standalone mode it loads FullApp which includes PeerJS for multiplayer. The Devvit chunk shrank from ~650 KB to 7.8 KB and PeerJS is now confirmed absent from every chunk that Reddit's iframe loads.
Devvit JSX renderer constraints — Reddit's servers run their own unpatched copy of @devvit/public-api, so local node_modules patches have no effect in production. We discovered three breaking rules the hard way: (1) bare string literals as JSX children (<text>string</text>) cause a processBlock crash — all strings must be wrapped in {}; (2) string interpolation that produces multiple children (<text>prefix: {expr}</text>) crashes the BlocksReconciler — template literals ({prefix: ${expr}}) are required; (3) function components inside the render return cause element.children.flat() to throw on unpatched servers because element.children is undefined for self-closing components. Every piece of server-side Devvit JSX had to be inlined with no helper components.
Asset paths in the CDN iframe — All public-folder asset references using absolute paths (/sounds/, /glb/, /images/) resolve to Reddit's domain root rather than the app's webroot. Every path had to be changed to ./ relative, and Vite's base: './' had to be set so the built index.html itself references ./assets/… rather than /assets/….
Silent React crashes with no error surface — Before adding an ErrorBoundary, any render-time exception would silently unmount the entire React tree, leaving only the CSS body background visible — identical to the CSP bundle crash. Without the boundary there was no stack trace, no console error visible to us, and no way to distinguish "module failed to load" from "React crashed during render". The ErrorBoundary made crashes visible and the bundle split eliminated the underlying CSP crash.
useWebView silently removed in @devvit/public-api 0.13.x — CLI 0.13.7 ships with @devvit/public-api 0.13.x, which completely removed the useWebView and useAsync exports. Despite package.json specifying "^0.11.19", yarn silently resolved to 0.13.8 because npm's caret semantics for 0.y.z allow any higher minor version. The build appeared to work locally (no TypeScript errors, since src/main.tsx was excluded from tsconfig.json) but esbuild failed during devvit upload with "No matching export" errors. Fix: yarn add @devvit/public-api@0.11.19 --exact to force the pinned version into node_modules.
Devvit CLI 0.13.7 dropped WebView asset uploads entirely — Even with webview: rootDir: dist correctly set in devvit.yaml and the bundle building successfully, CLI 0.13.7 never uploads the dist/ folder contents. The upload output shows "done" with no asset count, and the platform responds with status code 36 ("webview asset could not be found") when the post is opened. The CLI internally hardcodes webviewAssetIds: {}, so useWebView({ url: 'index.html' }) can never resolve the asset at runtime regardless of what's in devvit.yaml.
CLI 0.12.x switched to remote build, abandoning local asset upload — Downgrading to CLI 0.12.24 hoping to restore local WebView asset uploads revealed that 0.12.x uses a remote build server: it sends source files to Reddit's infrastructure, which compiles and bundles them server-side. The local dist/ folder is irrelevant. This silently breaks the webview: rootDir: dist workflow because the remote build server doesn't have the locally-built Vite output.
Devvit Web (devvit.json) upload hangs on 22 MB asset bundle — The correct long-term fix was migrating to the new devvit.json / Devvit Web architecture (Node.js HTTP server + fetch bridge replacing useWebView postMessage). The migration was implemented correctly, but devvit upload consistently hung at the "Finishing upload" stage and was eventually killed (exit code 137) because the final app-creation RPC timed out on Reddit's servers when the WebView asset payload was 22 MB. The upload of individual assets succeeded; only the final commit step failed. Reducing the bundle size (removing duplicate Ammo.js variants, compressing audio) would be required to complete this migration.
What We Learned
Devvit's constraint of "no external servers, no WebSockets outside Realtime" forced genuinely creative solutions — the daily challenge system needing no scheduler, wager escrow living entirely in Redis, rank announcements piggybacking on Reddit's own comment system. The platform constraints made the game feel more Reddit-native, not less capable.
Built With
- ammo.js
- devvit
- enable3d
- framer-motion
- hono
- react
- react-router
- tailwindcss
- three.js
- typescript
- vite
- zustand
Log in or sign up for Devpost to join the conversation.