POUNCE// README
Building a browser cat platformer with a hand-built tile level · play the game →
About Pounce
Pounce is a free browser side-scrolling platformer. The player picks one of four cats and bounds through a 240-tile hand-built level on the way to a cozy bed. Bonk red boxes for cat-food cans (grow bigger) or magic fish (unlock fishbone projectiles), stomp dogs and crawling kids, shoot or pounce the wasps that fly above. Vanilla HTML, CSS, and JavaScript on top of an HTML5 Canvas with Web Audio for both sound effects and a chiptune music loop. No build step, no framework, no runtime dependency beyond Google Fonts and Supabase for the global high-score leaderboard.
The project shares its DNA with
cat-ski: same author, same
plain-prose voice, same free deploy toolchain (GitHub → Vercel),
same four cat palettes. Source is at
github.com/ashtonmorrow/bros.
The production URL is pounce.mike-lee.me.
Pounce sits alongside the other small browser experiments at mike-lee.me: cat-ski (a SkiFree tribute), pear, and go. They all share the same vanilla stack and ship-on-push deploy pattern.
How to play
The cat starts on a flat patch of grass. Walking right takes you through a 240-tile level structured in four acts: a tutorial, a development section with the first floating-platform challenges, a climax in the air across disconnected platforms, and a final stretch that recovers the pace before the goal staircase. The level ends at a cozy bed on a raised wooden plateau.
| Input | Action |
|---|---|
| A / D or Left / Right | Move |
| W, Up Arrow, or Space | Jump |
| S or Down Arrow (in mid-air) | Down-pounce — fast slam, instant kill on impact |
| X | Throw a fishbone (only in shooter state, after eating a magic fish) |
| P | Pause |
| M | Toggle music on / off (also a bezel button) |
| R, Enter, or Space | Restart after a game over or win |
| Left / Right (title screen) | Cycle the cat picker |
| Click a swatch (title screen) | Select that cat directly |
The cat starts small. Bonking a red ?-style box from
below pops a cat-food can; eating it grows the cat to big, which
absorbs one extra hit before death. Bonking another box while big pops a
magic fish; eating it grants the shooter state and the X-key
fishbone projectile that arcs, bounces off the floor up to four times, and
one-shots any enemy on contact. Hits revert the cat all the way back to
small, Mario-style.
Three obstacle animals oppose the cat. Dogs walk and patrol the ground, can be stomped from above or shot. Crawling children are shorter and slower; their hitbox is low enough that a normal jump usually clears them. Wasps fly in a sine wave and cannot be stomped from a regular jump — you have to either shoot them with a fishbone or come down on them with a down-pounce. Down-pounce one-shots anything, even wasps, and pays out 200 points instead of the standard 100 stomp bonus.
Score adds up across treats (10 each), yarn balls (50 each), pop-up cans (100), magic fish (500), stomps (100), pound-stomps (200), and projectile kills (200). Time bonus on the level-complete screen at 5 points per second remaining. A pit fall costs a life and respawns the cat at the start; the cat starts with three lives.
The top three high scores are global, backed by a Supabase table with a localStorage cache so the strip paints instantly even before the network has responded. If your final score cracks the top three, a five-character name prompt appears.
How the game is built
The game is split across four small JavaScript files plus an
index.html entry point. There is no build step, no framework,
and no runtime dependency. The constraint of staying on a vanilla stack is
deliberate: it keeps the deploy pipeline as small as possible (a
git push triggers an automatic Vercel rebuild that takes
seconds), and it forces honest engineering decisions because no library is
going to paper over a bad choice.
| Component | Implementation |
|---|---|
| Rendering | HTML5 Canvas at 800 × 480, image-rendering: pixelated |
| Game loop | Vanilla JS, requestAnimationFrame, dt clamped to 50 ms |
| Physics | Per-axis tilemap collision; gravity 0.5 px / frame² |
| Variable jump | Reduced gravity (0.275) while ascending and jump is held |
| Forgiveness | 0.10 s coyote time, 0.10 s jump buffer |
| Down-pounce | vy snaps to 14 (faster than gravity terminal velocity), vx locked to 0; instant kill on impact, +200 score |
| World sprites | Procedural pixel art via ctx.fillRect |
| Cat sprites | Vector primitives, four palettes shared with cat-ski; small & big size sets baked at startup |
| SFX | Web Audio API, OscillatorNode tones |
| Music | Web Audio chiptune loop — square-wave melody + triangle bass over an I–V–vi–IV progression in C, 132 BPM, 4 bars; 1.4× panic-mode tempo at the last 30 seconds |
| Leaderboard | Supabase (anon key, RLS-protected) for the global top-3; localStorage cache so the strip paints instantly |
| State persistence | localStorage for selected cat, music preference, leaderboard cache, last-used name |
| PWA | manifest.json + sw.js; network-first for HTML, cache-first for assets, bumped CACHE_NAME on each release |
| Hosting | Vercel, deployed from GitHub on push |
Designing the level
The level is 240 tiles wide and 15 tiles tall. Each tile is 32 × 32 pixels in the world, so the playable area is 7680 × 480. The horizontal camera scrolls to follow the cat and clamps at the world's left and right edges so the player can't walk off-screen behind the start or past the goal.
The level data is not a hand-typed ASCII grid. It is built programmatically
in js/level.js through a thin builder API
(ground, plat, ent) that writes
features into a 2D char array. The final grid is exported as an array of
240-character strings for game.js to consume. This makes the
level easier to reason about and to refactor when something needs to shift.
The structure follows the four-act kishōtenketsu framework popularised by Koichi Hayashida at Nintendo (intro → development → twist → conclusion), and each section is annotated with which of the six 2D platformer level design patterns it uses (Guidance, Foreshadowing, Safe Zone, Layering, Branching, Pace Breaking).
| Act | Cols | What it teaches |
|---|---|---|
| 1. Introduction | 0–49 | Walking and trivial 3-tile jumps. A treat trail (Guidance) and one slow enemy with an optional high path (Branching) carrying a bonus yarn ball. |
| 2. Development | 50–123 | Wider 4–5 tile pits, a three-layer platform showcase (Layering), and a small mid-air stepping stone that previews the bigger gaps in Act 3 (Foreshadowing). Ends with a clean three-step staircase up. |
| 3. Twist | 124–184 | Disconnected platforms with no ground below, three back-to-back enemies on flat, the level's only 7-tile pit (with two stepping platforms at different heights), and a vertical tower section. |
| 4. Conclusion | 185–239 | An enemy-free recovery flat (Safe Zone — the calm before the goal), one easy pit, one final bonus yarn, and a four-step goal staircase ending at the cozy bed. The bed is visible from col 218 onward so the player always knows where they're going. |
The cat's running jump can clear about five tiles flat (or six with a full variable-jump hold) and reach about five tiles vertically. Every pit in the level was sized to stay inside those bounds, with mid-air stepping platforms placed in any gap wider than five tiles. A static check verifies every entity has a solid tile underneath and no platform sits directly above another solid tile (a stacking trap that would have the cat clipping into a tile while standing on the one below).
The four cats
The player picks a cat on the title screen. Four palettes are available: SHADOW (black), WHISKERS (tabby), PATCHES (calico), and GINGER (orange). The drawing code is the same vector function used in cat-ski, parameterised by a palette object. A single set of geometry produces all four cats.
| Cat | Palette character |
|---|---|
| SHADOW (black) | Pure-black fur, gold eyes, no markings |
| WHISKERS (tabby) | Warm orange fur with mackerel stripes and a forehead M |
| PATCHES (calico) | White base with orange and black irregular patches; cool blue eyes |
| GINGER (orange) | Bright orange tabby with green eyes |
The cat is drawn front-facing rather than as a side-view sprite, so it doesn't flip when you change direction. That is a deliberate trade-off: the vector geometry doesn't survive a horizontal flip cleanly because markings and patches mirror with it. A front-facing chibi cat reads correctly to the player without the flip.
Working with Claude
The project was built with Claude as a coding collaborator, in roughly the
same workflow used to build cat-ski: a long prompt at the start that lays
out the constraints (vanilla stack, no copyrighted assets, original art,
keep it small enough to swap in better art later), then iterating in short
cycles where each cycle adds one feature, runs a verification step
(node --check on every JS file plus a programmatic level
sanity check), and ships.
The character picker is a useful example. The first version of the cat
sprite was procedural pixel art drawn with fillRect. It read
as "small orange thing" but not unmistakably as a cat. Rather than redo
the pixel art, the fix was to share the existing vector
drawCat function from cat-ski. Claude pulled the function and
its four palettes from the cat-ski source, baked each
palette × state combination into a small canvas at startup, and wired
a swatch-and-arrow picker into the title screen. The change took one
iteration because the constraint — "the cat needs to look like a cat
at all four colours without forking the drawing code" — was the same
constraint cat-ski had already solved.
How you can build this yourself
You don't need much. The toolchain Pounce uses is free or open source.
| What for | Tool |
|---|---|
| Editor | Any text editor — VS Code, Sublime, even TextEdit will work |
| Coding collaborator | Claude |
| Version control | GitHub (free for public repos) |
| Hosting | Vercel (free tier; auto-deploys from GitHub) |
| Local preview | python3 -m http.server or npx serve |
| Browser dev tools | Built into Chrome / Safari / Firefox |
The single most useful constraint is to start without a framework. The surface area of vanilla HTML / Canvas / Web Audio is small enough to learn in an afternoon, and the resulting game is small enough that you can deploy a change in under a minute and reload to see it. If a feature starts asking for a framework, that's signal that the feature is too big for this kind of project; pick a smaller version of it.