Skip to content
thesithunyeinPublic

About

3D voxel dinosaur arcade runner in the browser. Swipe between three lanes, tail-whip critters to refill stamina, and chain the Primal Roar up to 16x. Built with React 19 and Three.js, procedural meshes and synthesized audio, for Digit7 Jam 2026.

Topics

Resources

Code of conduct

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Swiposaur: Tail Frenzy
Swiposaur sealed build

A 3D voxel dinosaur runner for the browser

Swipe between three lanes, tail-whip prehistoric critters to refill stamina, charge the Primal Roar and outrun the stampede.

Play now Demo video

React 19 Three.js Vite 8 TypeScript License

Solo Canvas · Digit7 Jam 2026 · HTML arcade game · built by Sithu Nyein


What it is

Swiposaur is a three-lane endless runner that runs entirely in the browser: no plugins, no sprite sheets and no server requirement. The whole world (dinosaur, critters, biomes, weather and sound effects) is generated in code at runtime, with one exception: the tail-whip stamina refund plays a real finger swipe recorded by the developer (public/audio/swip.mp3, 9.5 KB), and only when the hit actually moves the stamina bar. The only other binary files in the build are the app icons and favicons.

Game title Swiposaur — Tail Frenzy
Team name Solo Canvas (solo entry, one member)
Team leader / main contact Sithu Nyein · sithunyein.mailto@gmail.com · GitHub · Telegram @thesithunyein
Submission build commit a435f75 on main · release identifier 1.1.0 at that commit · final jam build with 12 biomes and Burmese localization
Current build 1.1.4 on main, deployed 31 Oct 2026: the submitted game plus one recorded swipe cue that plays only when a tail-whip refunds stamina, a roar multiplier that only builds while you are dodging, and one swing per press with no queued follow-up. Nothing else was changed after submission
Repository access https://github.com/thesithunyein/swiposaur/ is public — no invite or access request is needed to read the source at that commit
Live build https://swiposaur.sithunyein.com/ (checked 29 Oct 2026; deployed from main, though the exact deployed commit is not asserted)
Demo video 2-minute narrated walkthrough on YouTube — hook, the code-generated world, the Primal Roar loop, the stampede, mobile controls. Recorded before the recorded swipe cue was added, so it describes the synthesised whoosh
How to play Open the live URL in any browser with WebGL, wait for the load gate, press ကစားမည် / PLAY, then Space for a tail-whip on desktop or tap the WHIP button on a phone
Orientation / resolution Phones: portrait from 360×640 (9:16 works). Desktop: any orientation from 1280×720. The canvas fills the window at any aspect ratio and the camera re-derives its aspect on resize
Network requirements No backend is needed to play. The browser fetches web fonts, the 9.5 KB swipe recording from the same origin, and on this deployment probes /api/leaderboard; if the font or the recording is unavailable the game falls back to system fonts and the synthesized whoosh, and if the API is unavailable it uses the on-device Hall of Fame. No third-party API key or account
Earlier snapshot v1.0.0 — six biomes, before the Burmese localization; not the build described below
Engine React 19 + Three.js, custom voxel meshes, Web Audio (synthesized effects plus one recorded swipe)
Entry point index.html → src/main.tsx
Submission documents Technical document · Originality and AI use · Team profile
Production footprint dist/: 1,406,461 bytes on disk · JS+CSS: 1,241,598 bytes raw / 314,391 bytes gzipped (299,967 B JS + 14,424 B CSS; about 307 KiB) · one 9.5 KB recorded swipe, no sprite or texture files

Screenshots

Swiposaur title screen in Burmese, with the voxel city street and main menu

Title screen

Live run on the voxel street, with the dinosaur, obstacles and gameplay HUD

Gameplay

Game over results screen showing the run score, stats and retry options

Run results

Map selection modal with illustrated biomes and unlock states

Biome selection

Hall of Fame leaderboard showing player ranks and scores

Hall of Fame


How it works

Boot, then a fair first run

Launch passes through a short loading gate before the title menu: the logo, one progress bar and one rotating tip over a daylight sky with drifting clouds and pollen. The bar is gated by three real stages (typeface, arena, progress), so it parks at 96% until the work actually reports in rather than faking a finish. The first load in a browser session holds for 8 seconds — about 8.8 s including the hold and fade — so the wordmark is fully painted before the menu; reloads in the same session take about 2.8 s, because the player has already seen the brand moment. An ease-out curve, not a linear tick, drives the fill; a guard releases the screen if a stage stalls, and prefers-reduced-motion drops the animation.

The run then opens gently: a 6-second clear runway, a first obstacle that can never be a blind death, and three hits' worth of stamina before the stampede can end it.

The loop

flowchart LR
    start(["Tap / SPACE"]) --> range{"Critter inside<br/>whip reach?"}
    range -- yes --> hit["TAIL WHIP LANDS<br/>+22 stamina (+26 touch) · combo +1"]
    range -- no --> whiff["WHIFF<br/>no stamina refund"]
    hit --> roar["Roar meter charges<br/>while dodging<br/>1x → 2x → 4x → 8x → 16x"]
    whiff --> dodge{"Obstacle in lane?"}
    roar --> dodge
    dodge -- "hit" --> dmg["−35 to −45 stamina<br/>stampede chases for 6 s"]
    dodge -- clean --> score["Distance · coins · vaults · near-misses<br/>× roar multiplier"]
    dmg --> alive{"Stamina left?"}
    score --> alive
    alive -- yes --> dodge
    alive -- no --> over(["GAME OVER<br/>grade from distance and combo"])
Loading
  1. Opening cinematic. A rival blocks the road; the first tap lands a cinematic mega tail-whip and puts the camera behind the dino.
  2. Safe start (6 s). The road is clear, the mob is off-camera and a mistake cannot end the run: the first obstacle is never a blind death.
  3. Three-hit stamina. A stumble costs 35–45 stamina and each hit spawns the chasing stampede for 6 seconds unless you run clean. Running also burns stamina once the opening is over, and the drain climbs with the clock, so no run is infinite.
  4. Whip to survive. A landed whip refunds +22 stamina (+26 on touch), has an 18-frame cooldown and scores at the roar multiplier, enough to keep the loop alive, never enough to erase the fatigue clock on its own.
  5. Roar multiplier. The meter steps 1x → 2x → 4x → 8x → 16x and only charges while you are dodging: a lane change or jump arms it for two seconds. A player standing still and mashing whip still scores at the current tier, but cannot raise it. Every point of score rides on the meter, so moving beats button mashing.

Scoring and grades

Score mixes distance, coins, whips, vaults, near-misses and escaped chasers, all scaled by the current roar tier, so a clean 16× chain can out-score a much longer run. The grade is deliberately separate: it is earned on distance and whip chaining in the same run, so points cannot buy a better letter.

Grade Title Threshold (in one run)
S+ Apex Primal Legend 2,000 m and a 15× roar
S Master Dino Drifter 1,400 m and a 10× roar
A Speedway Champion 800 m
B Rising Roarer 350 m
C Stampede Rookie under 350 m

Systems map

flowchart TD
    subgraph UI["React layer"]
        APP["App.tsx<br/>screen state · persistence · leaderboard"]
        MENU["StartScreen<br/>menu · settings · rewards"]
        HUD["HUD · TouchControls<br/>score, stamina, roar"]
        OVER["GameOverModal · GradeStamp<br/>results and rating"]
        MODALS["Skins · Maps · Missions<br/>Achievements · Settings · Guide"]
    end

    subgraph SIM["Simulation layer"]
        ENGINE["GameEngine.ts<br/>run loop · collisions · lanes · stamina"]
        RULES["constants · maps · levels<br/>missions · achievements · rewards"]
    end

    subgraph VIS["Presentation layer"]
        RENDER["ThreeGameRenderer.ts<br/>scene · camera · voxel world · weather"]
        DINO["DinoMeshBuilder.ts<br/>rigged voxel dinosaur"]
        SOUND["SoundEngine.ts<br/>synthesized SFX"]
    end

    STORE[("localStorage")]
    API{{"optional<br/>/api/leaderboard"}}

    MENU --> APP
    MODALS --> APP
    APP -- "start / pause / restart" --> ENGINE
    ENGINE -- "stats each frame" --> HUD
    ENGINE -- "position, collisions, chase" --> RENDER
    ENGINE -- "events" --> SOUND
    RULES -- "spawn tables, pacing" --> ENGINE
    RENDER --> DINO
    ENGINE -- "run result" --> OVER
    APP <--> STORE
    APP -. "VITE_LEADERBOARD_API = true" .-> API
Loading

A single requestAnimationFrame loop owns the simulation: GameEngine advances the world with a variable delta (capped at 32 ms), resolves lane changes, jumps and whip hitboxes, then hands camera and entity transforms to ThreeGameRenderer, which draws them with instanced voxel meshes on a single scene graph. React never re-renders per frame; it only receives throttled stat snapshots, so the DOM stays cheap while the canvas runs at full speed.


Controls

Action Desktop Mobile
Change lane A / D, ← / → LEFT / RIGHT buttons, swipe left / right, or tap the left / right third
Jump W / ↑ JUMP button or swipe up
Tail-whip Space or the whip button 96 px WHIP button, swipe down, or tap the middle 40%
Pause P / Esc Pause button

On touch the engine also auto-whips a glowing critter only while the dino is airborne, so the one-thumb jump-and-whip clash never costs a target. Ground whips stay manual, which keeps stamina meaningful — a run can still end.


Quick start

npm install
npm run dev        # http://localhost:5173

Production build for the current 1.1.4 version from main (the submitted jam build was 1.1.0 at a435f75; the v1.0.0 tag remains an earlier snapshot):

npm run build      # outputs ./dist
npm run preview    # serve ./dist locally

Type check and tests:

npm run typecheck
npm test

The suite covers the run rules, obstacle spacing, mission rotation and boosts, leaderboard validation, storage and API behaviour, and the copy the UI renders. It runs headless — no browser, no network and no saved state required.

The optional Express/leaderboard server is not needed to play. To run the full-stack dev server (API + Vite middleware) instead:

npm run dev:api    # http://localhost:3000

Testing notes

  • No setup to play. The deployed build is static files plus one optional serverless endpoint: no account, key or download step. Open the URL, wait for the boot gate, press PLAY.
  • The 8-second first-load gate is deliberate (see Boot, then a fair first run); a reload later in the same session takes about 2 seconds. A slow first load is the brand moment, not a hang.
  • A board that says "Saved on this device" is the documented fallback, not a failure. It means the deployment has no durable store or the probe did not answer; reloading lets it probe again. See Configuration for the storage variables.
  • Automated checks: npm ci && npm run typecheck && npm test runs 172 tests across 17 files with no browser, network or saved state. If a low-memory machine struggles with the default worker pool, npx vitest run --no-file-parallelism runs the same suite in one worker.
  • Testing the online board locally: npm run dev has no API route, so the board stays local by design. Use npm run dev:api to exercise the real endpoint, or point LEADERBOARD_KV_REST_URL / LEADERBOARD_KV_REST_TOKEN at a Redis REST store.

Configuration

The game is playable with no backend. The global board works when the deployment provides a durable Redis REST store:

Variable Default Purpose
VITE_LEADERBOARD_API on Set false to force the local-only Hall of Fame
LEADERBOARD_KV_REST_URL unset Redis REST endpoint for durable online scores (Vercel KV / Upstash)
LEADERBOARD_KV_REST_TOKEN unset Bearer token for the same store
VITE_ANALYTICS_URL unset (off) POST endpoint for optional gameplay telemetry (off by default)

KV_REST_API_URL / KV_REST_API_TOKEN and UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN are also accepted. On a deployment without those credentials the endpoint answers 503 with storage: "unavailable", and the client quietly keeps the on-device Hall of Fame instead of showing a fake global board. On a purely static host the probe fails the same way. With VITE_LEADERBOARD_API=false the app makes no leaderboard/API requests. With VITE_ANALYTICS_URL unset (as in the submitted build), no gameplay telemetry is sent. The browser still loads font CSS/files from Google Fonts and jsDelivr; when the leaderboard option is enabled, it also probes /api/leaderboard.

Each browser gets one stable player id and a generated name (for example NEON_RAPTOR42), so a new player appears on the board without filling anything in. The name can be changed in Settings → Leaderboard Name, which renames your existing row immediately. The global board keeps one row per player: a better run replaces your row, a worse run changes nothing.

Online submissions are validated server-side: player and run ids, whole scores from 1 to 5,000,000, combos from 0 to 250, shipped skin ids, names sanitised to 16 characters, and at most 6 submissions per minute per client. The same run id may be retried, but only ever creates one record.


Features

  • Custom Three.js renderer: voxel dinosaur built from primitives, 12 skins, 12 biomes, dynamic shadows, weather and camera shake.
  • Web Audio SFX, all synthesized at runtime (whips, blasts, coin chimes), except the tail-whip stamina refund, which is one 0.78 s recording made by the developer (public/audio/swip.mp3) and plays only when that hit raises the stamina bar. Nothing is licensed from a sample pack. The arcade sequencer is not started during a run, so the effects carry the audio.
  • A staged boot gate before the title menu: logo, one bar, one tip, drifting clouds and pollen, with an 8-second first-load progress bar and a 2-second reload progress bar.
  • Persistence: high score, coins, skins, levels, achievements, daily rewards, missions and banked stamina are stored locally (localStorage).
  • Twelve biomes and twelve dino skins unlock as you level: the original six between levels 1 and 35, then Sakura Heights, Ember Canyon, Aurora Ridge, Starlight Shibuya, Monsoon Coast and Moonlit Bazaar at 45, 55, 65, 75, 85 and 95, each with the dino skin that belongs to it. The new habitats reuse the tuned renderer styles and obstacle pools rather than adding untested geometry.
  • Five daily missions, drawn from a rotating twelve-mission pool and reset at 00:00 Myanmar time (UTC+06:30). A day always mixes difficulties — two easy, two medium and one hard — chosen from different metrics so the set never repeats an objective. Each one pays 80-150 coins and banks +20 stamina on the next run when claimed.
  • English and Burmese, switched in Settings and remembered per device. Burmese is the default; every menu, map, skin, mission and toast has a translation, set in Aka 08 so the Myanmar script stays legible where a Latin display face would break.
  • Login eggs grow on consecutive days and step back a single stage after a missed day instead of resetting the streak.
  • The results card names the one thing that was close — the next grade in metres, the next Roar tier, or the daily mission — instead of a generic "good job", and offers a +20 stamina retry once every 3.5 hours.
  • Mobile-first layout: safe-area insets, 360×640 minimum viewport, four thumb-sized controls (96 px whip) and a full-screen swipe layer.
  • Touch runs get a 12% wider three-lane street, a camera that sits higher and further back to show more of the road ahead, +26 stamina per whip (desktop +22), and a gentler obstacle curve — 55/80/100% of rows by distance band instead of 70/90/100%.
  • Airborne-only auto-whip on touch: the engine swings at a glowing critter while the dino is in the air (where one thumb cannot jump and whip), never on the ground, so the run still has a failure state.
  • Jumping clears low street clutter (crates, barrels, barriers, spikes). Carts, shifting blocks, gates and other solid props must be dodged instead.
  • Ramps are optional boost pads in the open lane and only launch a grounded runner.
  • The open lane never moves more than one lane between segments, so the corridor is always reachable at the current speed.
  • Obstacle rows arrive about one second apart at every speed: the world-unit gap widens with the runner (300 units at the start, ~520-620 at the speed cap), so distance adds density without the warning window collapsing.
  • Global leaderboard of every player's highest score, one row per player. A new device gets a generated name such as IRON_TALON77; players can rename themselves in Settings at any time.
  • Score plausibility: anything above 50,000 points must arrive with the run's own length and must fit inside 2,500 points per second, so an impossible number is refused by the API rather than printed on the board.
  • Local Hall of Fame fallback that works with no server, labelled "Saved on this device" with a local rank. It stores one record only: this device's highest score.
  • Share card: the results screen renders the run as a 1080×1350 PNG and opens the phone's share sheet; browsers without one download the image instead.

Supported environments

  • Built for Chrome, Edge, Firefox and Safari (current stable), desktop and mobile.
  • Verified in Chrome at 360×640, 390×844 and 1280×720, in Firefox on Windows desktop, in Safari on iPhone and macOS, and in Edge on Windows. All four engines run the same build.
  • Targets Android 10+, iOS 16+, Windows 10/11 and recent macOS. Requires WebGL.
  • Recommended: portrait on phones, any orientation on desktop.

Known limitations

  • No service worker / offline install. The app ships a web manifest so "Add to Home Screen" creates a shortcut, but there is no offline cache and no install prompt.
  • The global board needs durable storage (see Configuration). Without Redis REST credentials the game stays on the local Hall of Fame rather than inventing a board. Online scores are self-reported and not cheat-proof — the server validates shape, rate limits and plausibility (a score must fit inside the run's own length), but does not verify a signed run or replay.
  • Clearing browser storage gives that device a new player id, so it appears as a new player on the global board.
  • External font requests: the Google Fonts stylesheet and font files from fonts.googleapis.com / fonts.gstatic.com (Fredoka, Lilita One, Titan One, Sniglet, Press Start 2P and Noto Sans Myanmar), plus Aka 08 from cdn.jsdelivr.net. If blocked, display faces fall back to system sans-serif and Burmese text uses Noto Sans Myanmar, Myanmar Text, Padauk or the platform Myanmar font when available.
  • Browser checks are manual, not automated: Chrome (desktop and mobile), Firefox, Edge and Safari (macOS and iPhone) were played through by hand. There is no cross-browser CI run, so an engine-specific regression could go unnoticed between releases.
  • Very low-end Android devices with weak GPUs may still drop frames; the renderer caps the pixel ratio at 1.5 on phone-width viewports and 2.0 on desktop to leave headroom.

Project structure

index.html                      page shell, fonts, meta tags
src/main.tsx                    React entry
src/App.tsx                     screen state, persistence, HUD wiring
src/components/                 Start screen, HUD, rating stamp, modals
src/components/BootScreen.tsx   loading gate shown once before the title menu
src/game/useBootSequence.ts     staged boot progress, 8 s first load / 2 s reloads
src/game/GameEngine.ts          run loop, collisions, whip, stamina, chase, scoring
src/game/ThreeGameRenderer.ts   scene, camera, track and world rendering
src/game/DinoMeshBuilder.ts     voxel dinosaur construction
src/game/SoundEngine.ts         Web Audio synthesis + swipe sample
src/game/grades.ts              grade tiers shared by the results card, missions and coach
src/game/i18n.ts                English/Burmese copy, language store and document lang
src/game/dailyMissions.ts       deterministic five-a-day missions on Myanmar time
src/game/runBoosts.ts           banked stamina and the retry-boost cooldown
src/game/coach.ts               the single actionable line on the results card
src/game/obstacleDensity.ts     desktop vs touch obstacle curve
src/game/leaderboard.ts         local Hall of Fame storage and online client
src/game/shareCard.ts          1080x1350 run card drawn on a canvas
src/game/leaderboardCore.ts     shared validation and ranking rules
src/game/*.ts                   constants, maps, levels, missions, rewards, achievements
server/leaderboardStore.ts      durable file / Redis REST stores, idempotent by runId
api/leaderboard.ts              optional serverless endpoint (disabled without a store)
server.ts                       optional Express dev server with the same endpoints
tests/                          Vitest coverage for rules, storage, API and UI copy
docs/TECHNICAL.md               engineering write-up (architecture, systems, limits)
docs/ORIGINALITY.md             originality and AI-use declaration
docs/TEAM.md                    team profile
docs/brand/                     English and Burmese README wordmarks (PNG/SVG) and seal artwork
docs/screenshots/               screenshots used by this README

Asset credits

Asset Source License
All 3D models, textures and UI art Generated in code with Three.js primitives and inline SVG Original work, Apache-2.0
Logo and icons Inline SVG in src/components/GameLogo.tsx, exported to docs/brand/ as transparent PNG and source SVG Original work, Apache-2.0
Music and sound effects Synthesized at runtime with the Web Audio API; the tail-whip stamina refund cue is a recording made by the developer (public/audio/swip.mp3), played only when that hit raises the stamina bar Original work, Apache-2.0
Fonts: Fredoka, Lilita One, Titan One, Sniglet, Press Start 2P, Noto Sans Myanmar Google Fonts SIL Open Font License 1.1
Burmese font: Aka 08 saturngod/myanmar-unicode-fonts, served by jsDelivr License not stated in the repository metadata; verify the font's redistribution terms with its author before redistribution
React, React DOM react.dev MIT
Three.js threejs.org MIT
Vite, Tailwind CSS vite.dev, tailwindcss.com MIT
lucide-react lucide.dev ISC
TypeScript typescriptlang.org Apache-2.0
Express (optional dev server) expressjs.com MIT

No third-party art, audio, sprites or templates are included. No paid assets.

AI use

AI tools were used during development and are disclosed here as required:

  • Google AI Studio (Gemini): initial project scaffolding, component drafts and UI copy. Affected code: the first project skeleton and the component shells under src/components/, plus copy strings that were edited by hand and now live in src/game/i18n.ts.
  • An AI coding assistant: refactors, gameplay tuning, documentation and code review. Affected code: the run simulation and pacing (src/game/GameEngine.ts, src/game/constants.ts), the leaderboard client and serverless handler, the test suite under tests/, and the prose in README.md and docs/.
  • No generated assets. No AI-generated images, 3D models, music or voice appear in the game: every visual is drawn in code and every sound is synthesised at runtime, apart from the one swipe cue the developer recorded by hand.

Human review: every file was read, tested and modified by the developer; behaviour was verified in Chrome at 360×640, 390×844 and 1280×720, and played through in Firefox, Edge and Safari (macOS desktop and iPhone); no generated art or audio is used (all visuals are code-drawn, and the one recorded sound was captured by the developer). The developer is responsible for the submitted code and can explain any part of it.

License

Copyright 2026 Sithu Nyein.

The game — source code, code-generated art, synthesized audio and the recorded swipe cue — is licensed under Apache-2.0. The full license text in LICENSE is the canonical statement, and package.json declares the same SPDX identifier (Apache-2.0).

Bundled dependencies keep their own licenses: React, React DOM, Three.js, Vite, Tailwind CSS and Express are MIT, lucide-react is ISC, and TypeScript is Apache-2.0. The Google Fonts families, including Noto Sans Myanmar, are SIL OFL 1.1. Aka 08 is credited to its author in the asset table; the source repository does not declare a license, so its redistribution terms remain unverified.

About

3D voxel dinosaur arcade runner in the browser. Swipe between three lanes, tail-whip critters to refill stamina, and chain the Primal Roar up to 16x. Built with React 19 and Three.js, procedural meshes and synthesized audio, for Digit7 Jam 2026.

Topics

Resources

Code of conduct

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages