A browser endless runner. Dash through a procedural cave, collect fireflies, dive-kill stingers, fight bosses, navigate biome shifts.
Play: https://echobat.xyz
| Input | Action |
|---|---|
↑ / ↓ or W / S |
Steer up / down |
Space |
Dash — vertical burst in input direction, dive-kills enemies |
| Mobile: tilt the device | Steer |
| Mobile: quick tap | Dash |
The world scrolls horizontally on its own. The bat only moves up and down — ← / → are intentionally unbound.
- Fireflies — stationary gold pickups, builds combo
- Moths — moving amber pickups, bigger points
- Golden fireflies — rare, pink-gold, massive combo payoff
- Stingers — red hazards, dive/dash through to kill for points
- Bosses — appear every 3500 m, HP pips above the body, fire hostile ping-waves; dash into them to damage
- Wall crystals — cyan crystal clusters on walls, shatter for bonuses
- Chambers — wide sections denser with fireflies
- Biomes — cave palette + background shifts every 1500 m (abyss → ember → void → verdant)
- Biome boom — coordinated flash + screen shake + audio sting + shockwave ring at the moment of biome transition; the next biome's backdrop crossfades over the previous one across the last 200 m of each tier
- Power-ups — slow-mo (cyan hourglass) and magnet (gold) — rare drops
- Combos — chain any pickup/kill within 2.6 s for multiplier up to x5
- Near-miss bonus — flying close to walls scores extra
- Milestones — banner every 100 m
- Dynamic sonar reveal — walls always faintly lit, ping (via dash) flashes them bright
- Painted backgrounds — three painted parallax variants per biome, mirror-tiled and cycled to break visible repetition
- Vanilla TypeScript, Vite, HTML5 Canvas 2D
- No frameworks, no game engine
- Audio is all WebAudio-synthesized (no audio assets)
- Background and hero-character PNGs are AI-generated via Replicate (Flux Schnell + Flux Dev), pre-baked offline into
public/assets/ - The bat, boss, crystals, and power-ups draw painted sprites; everything else (caves, fireflies, moths, stingers, sonar rings, sparks, HUD) is drawn procedurally with
ctx.beginPath/fill
npm install
npm run dev # http://localhost:5173
npm run build # production build → dist/Echo Bat ships as a real native iOS app via Capacitor — the same TypeScript/Canvas game runs inside a WKWebView, with native CoreMotion replacing the flaky Safari DeviceOrientation.requestPermission flow.
npm run ios:sync # build web + copy into ios/
npm run ios:open # build, sync, open in Xcode
npm run ios:run # build, sync, run on connected device / simulatorFirst-time setup in Xcode:
npm run ios:open.- In the project navigator, select the App target → Signing & Capabilities.
- Pick your Team (free Apple ID works for sideload to your own device; paid Developer account needed for App Store).
- Pick a simulator (iPhone 16 etc.) or a connected device, hit Run.
The first launch on a real device prompts for Motion & Fitness access — that's the native CoreMotion permission, granted once and remembered (unlike Safari's per-session prompt). After that, tilt-to-steer just works.
Web app and iOS app share src/ — src/tilt.ts runtime-checks Capacitor.isNativePlatform() and routes to @capacitor/motion natively, falls back to window.deviceorientation in browsers. To change game logic, edit the TS, npm run ios:sync, then re-run from Xcode.
Bundle id: xyz.echobat.app (change in capacitor.config.ts if you fork).
Sprites and parallax backgrounds are pre-generated by short Node scripts that
call Replicate's Flux models. They're not part of the
runtime — the game just loads the resulting PNGs from public/assets/.
Set REPLICATE_API_TOKEN in a local .env (gitignored), then:
node scripts/generate-backgrounds.mjs # missing only
node scripts/generate-backgrounds.mjs --force # regenerate all
node scripts/generate-backgrounds.mjs abyss # one biome only
node scripts/generate-sprites.mjs # missing only
node scripts/generate-sprites.mjs --force bat # one sprite, force-regenerateTo change a backdrop or sprite: edit the prompt in the script, run with
--force <name>, refresh the dev server. Cost per generation is roughly
$0.003 (Flux Schnell) or $0.025 (Flux Dev — used for the bat and boss).
PNGs are committed so contributors don't need a Replicate token to run the game.
Sprite PNGs come back with a pure black background. The game draws them with
ctx.globalCompositeOperation = "lighter", which adds source-pixel RGB to the
destination — so black contributes nothing and effectively becomes transparent
without any preprocessing or alpha channel.
This works well for brightly luminous subjects (bat, crystals, boss, hourglass, magnet). For darker subjects you'd need explicit alpha-keying at load time.
Auto-deploys to Vercel on every push to main.