๐ฑ๐ฅ๐ถ Tanker Street A cozy artillery duel where a cat and a dog lob whimsical ordnance across a cherry-blossom night sky.
๐ก Inspiration We grew up on Pocket Tanks, Worms, and Gunbound โ that perfect loop of tweak the angle, sweat the wind, fire, and watch the dirt fly. But every modern take we tried was either a hardcore desktop sim or a free-to-play slot machine. We wanted the opposite: something warm, instantly playable, and adorable โ a one-thumb mobile game you could open on a bus and feel something in five seconds.
So the pitch became a sentence: "Pocket Tanks, but it's a kitten and a puppy slinging fireworks at each other under cherry trees." The cat (P1) and dog (P2) gave us a built-in rivalry, a readable color language (cool blue vs. warm red), and a tone that let us name weapons things like Healing Tulip, Acorn Mine, and Cozy Cloud without anyone rolling their eyes.
๐ฎ What it does Tanker Street is a turn-based artillery game built on a real physics sandbox:
Slingshot aiming โ pull back to set angle + power, with a live dashed trajectory that teaches ballistics instead of hiding them. Fully destructible terrain โ every shot carves the world; tanks slide and settle on the new surface. A growing arsenal of physics-driven weapons (bouncers, diggers, drifters, mines, healing flowers), each with its own gravity, wind behavior, and impact rules. Worlds, an economy, and a weapon store โ win levels, earn coins and diamonds, unlock and buy new ordnance. One codebase, three homes โ it ships to the web on itch.io, to Android via Capacitor, and to CrazyGames with full SDK integration (cloud saves, session analytics, monetization) โ each degrading gracefully when a platform's features aren't there. ๐ ๏ธ How we built it The stack is deliberately lean: Phaser 3.90 + Vite, vanilla JS, a synchronous localStore over a single JSON blob, and Web Audio for sound.
Sound, synthesized Rather than ship megabytes of audio, most SFX are procedurally generated with oscillators and noise buffers at runtime โ different timbres per weapon class โ so they cost almost nothing to download.
๐ง Challenges we ran into
Phones can't hear sub-bass. Our first firing sounds were rich, punchyโฆ and silent on phone speakers. We measured the spectra with an OfflineAudioContext and found ~88% of the energy sat below 500 Hz, where a phone speaker simply can't reproduce it. We re-synthesized every weapon around the 600 Hzโ3 kHz band a phone can push, and verified ~47% of energy moved above 500 Hz. Lesson: audio is only "loud" if the hardware can radiate it.
Cross-origin iframe audio. On CrazyGames the game runs in a sandboxed iframe where the AudioContext starts suspended and a parent-frame click doesn't unlock it โ the gesture and resume() must happen inside the iframe. We also hit a brutal class of bug where a fragile mute "latch" could pin the master gain at $0$ for the whole session, looking exactly like an autoplay failure. The fix was deterministic baselines โ unmuted โก gain 1, no capture-and-restore โ so a missed edge can never strand the game silent.
98 MB โ 15 MB. CrazyGames rewards builds under 20 MB (and caps initial download at 50 MB). We were at 98 MB. We wrote a one-off sharp pipeline to palette-compress 46 PNGs in place โ โ85%, zero code changes, visually identical โ stripped 14 MB of stray .bak files, and deleted dead duplicate spritesheets. Final build: 15 MB, verified pixel-for-pixel against the original menus and gameplay.
The platform maze. We learned the hard way that on CrazyGames Full Launch is earned, not toggled โ you ship Basic Launch first, accrue engagement metrics, and get invited. We chased a "SDK not detected" ghost (the detector is event-driven โ it only sees gameplayStart() once you actually enter a match), fixed a muteAudio property-path bug (SDK.game.settings.muteAudio, not SDK.game.muteAudio), and got a submission rejected for "ads detected" in Basic โ which forced a clean ADS_ENABLED master switch that gates every single ad request behind one flag (verified with a spy: requestAd is never called) and flips on the day we reach Full Launch.
A backslash that broke everything. PowerShell's Compress-Archive writes ZIP entry paths with \, and Linux static hosts serve those as literal filenames โ every asset 403'd on itch and CrazyGames. We replaced it with a manual ZipArchive loop that rewrites paths to forward slashes, and now verify each package has index.html at the root with zero backslashes before it ships.
๐ What we learned Verification beats vibes. Spectral analysis for "the sound is too quiet," a requestAd spy for "are ads really off," a 1-px miss check for "does the solver work." Every claim got an observation behind it. Constraints are platform-shaped. "Make it smaller," "make it audible," "make it pass review" all turned out to be physics or policy problems with exact thresholds, not vague polish. Graceful degradation is a design, not an afterthought. One build, three platforms, each detecting what it can do and quietly skipping what it can't โ Android never loads the ad SDK, itch self-disables it, CrazyGames lights it up. Read requests narrowly. "Remove the button" meant just the button โ keep the feature and auto-trigger it. A small lesson that saved a feature from deletion. ๐ What's next Pass Basic Launch, earn our way to Full Launch, and flip the one flag that turns the cherry-blossom battlefield into a living, monetized world โ then keep filling the armory with the silliest physics weapons we can dream up. ๐ธ
Log in or sign up for Devpost to join the conversation.