---
title: "Kart Racer"
slug: kart-racer
version: 1.0.0
updated: 2026-10-20
sha256: d84d46b701036f47edb2096c963ead0c7cdc56d4fff31340a5ad027b943e7a7f
category: fun
category_name: "Fun & Games"
source: https://promptbai.bonq.foo/r/kart-racer.md
check: https://promptbai.bonq.foo/r/kart-racer.json
---

# Kart Racer — a promptbai recipe (v1.0.0)

> Race go-karts with items and drift boosts on retro 3D tracks, Mario Kart-style

## Instructions for the AI agent (read first)

You are an AI coding agent. A person (who is probably **not** a programmer) has asked you to build this for them by following this recipe from promptbai (https://promptbai.bonq.foo).

1. **Read the whole recipe before you start.** Then follow it section by section.
2. **Talk to the person in plain language.** Avoid jargon. When you need a decision from them, ask one short question at a time and offer a sensible default.
3. **Ask when something is unclear** instead of guessing on things that matter to them (names, colors, personal details). Use your judgment on purely technical details. If the person doesn't answer or isn't available, use the defaults the recipe gives and keep going.
4. **Respect the Constraints section.** If the person's setup makes a constraint impossible, explain why and suggest the closest alternative.
5. **Check your work** against the Acceptance checks before you say you are done.
6. **Write a file named `promptbai.json`** in the root of the project folder with these values (formatting may differ), so the build can be updated later:

```json
{
  "slug": "kart-racer",
  "version": "1.0.0",
  "sha256": "d84d46b701036f47edb2096c963ead0c7cdc56d4fff31340a5ad027b943e7a7f",
  "source": "https://promptbai.bonq.foo/r/kart-racer.md",
  "check": "https://promptbai.bonq.foo/r/kart-racer.json"
}
```

7. **If you can't create files yourself** (for example, you are a chat assistant), give the person each file in full, one at a time. Tell them exactly what to name it, where to save it, and how to save it with an app they already have.
8. **Finish with the Hand-off section:** tell the person, simply, where their new thing is, how to open it, and how to use it. Include a short "your choices" note listing the options you used (theirs or the defaults) and how to change them later.
9. **Where practical, keep core logic** (dates, calculations, parsing) in plain functions you can test outside a browser, for example with `node`. In a single-file app, put them in their own `<script id="core">` block and load that block into node for tests.

If anything in this recipe turns out to be unclear or wrong, ask the person whether you may tell the promptbai editors, and see https://promptbai.bonq.foo/agents.md for how.

To check for updates later, fetch https://promptbai.bonq.foo/r/kart-racer.json and compare its `version` with the one in `promptbai.json`. The full update protocol is at https://promptbai.bonq.foo/agents.md. The `sha256` is the SHA-256 of everything after the marker line below (UTF-8, LF line endings).

---

<!-- promptbai:recipe-body -->
## Goal

Build a kart racing game in the spirit of Super Mario Kart (SNES), as a single web page that opens by double-clicking. It uses the classic "Mode 7" look: a flat track texture drawn in perspective, with sprites for karts and objects. There are 8 racers, items, drifting with mini-turbo boosts, 4 tracks, a Grand Prix, Time Trial with ghosts, and Quick Race. All art, sound and music are original and generated in code.

Mode 7 rendering on a 2D canvas is simple, fast and authentic. Follow the rendering method below rather than reaching for a 3D library.

## Before you start, ask the person

1. Should the racers be original animal characters, or named after people the player knows? (Default: 8 original animal racers, for example a fox, penguin, frog, bear, bee, cat, owl and turtle.)
2. Two players on one keyboard with split screen as well as single player? (Default: single player only.)
3. Who's playing? Add an **auto-accelerate** option for young kids, so they only need to steer. (Default: available in Options, off.)

## Features

- **Screen:** an internal resolution of 384 × 216, scaled to fill the window with crisp pixels (`image-rendering: pixelated`). Game logic runs at a fixed 60 steps per second with an accumulator. Split screen (if chosen) stacks two 384 × 108 views.
- **Mode 7 renderer:**
  - The horizon is at row 70. Above it, draw a sky gradient and two parallax background layers (far mountains, near hills or trees) that scroll sideways by `cameraAngle / 2π × layerWidth`.
  - Below the horizon, for each row `y`: `z = camHeight · f / (y − horizon)` with `camHeight = 18` and `f = 180`. The row samples the track texture along a line: `start = cam + forward·z − right·(W/2)·z/f`, `step = right·z/f`.
  - Write texels into a `Uint32Array` view of an `ImageData` in a tight loop. Texels outside the track texture use a tiled outside pattern (grass, sand, snow or lava per theme).
  - Darken rows slightly toward the horizon.
- **Camera:** sits 34 units behind the player's kart, looking the way the kart faces. The camera angle eases toward the kart's angle by 15 % per frame, so drifts read clearly. Hits and spins don't spin the camera.
- **Sprites:** project every object with the same camera: `d = obj − cam`, `z = d·forward`, `x = d·right`. Skip it if `z < 2`. Otherwise `screenX = W/2 + x·f/z`, the ground row is `horizon + camHeight·f/z`, and the scale is `f/z`. Draw from far to near, standing on their ground row, and clip sprites that would cover the HUD.
- **Kart sprites:** at startup, build each racer's kart as a handful of coloured 3D boxes (chassis, 4 wheels, seat, driver body, head with simple animal features, and a helmet in the racer's colour). Render it with a tiny painter's-algorithm box renderer into 16 yaw angles on 48 × 48 offscreen canvases. Each frame, pick the angle from the kart's heading relative to the camera. Add a 2-frame wheel wobble, dust when off-road and coloured sparks when drifting.
- **Tracks:** 4 closed loops, each defined as about 12–20 control points in a 1024 × 1024 world. The centreline is a Catmull-Rom spline sampled every 4 units, with cumulative distance, tangent and normal at each sample.
  - Paint the track texture once with Canvas 2D strokes along the spline: the outside pattern, then curbs (road width + 10, red and white dashes), then asphalt (road width 64), a chequered start line, item box rows and boost pads.
  - Paint a matching **surface map** (a `Uint8Array`, one value per texel, drawn the same way in flat colours) that physics reads: road, off-road, wall, pit, boost pad and ice.
  - Walls run along the outside of the off-road margin, which is 40 units wide.
  - The four themes:
    1. **Meadow Loop:** wide, gentle and easy.
    2. **Sandy Shores:** water pits on the inside of two corners.
    3. **Frost Valley:** ice patches with low grip.
    4. **Volcano Run:** narrower road (52), lava pits and tight hairpins.
- **Kart physics** (units per frame at 60 steps per second):
  - Each kart has a position, a heading, a velocity vector and a scalar speed.
  - Accelerate adds `accel` (light 0.050, medium 0.040, heavy 0.032) up to `topSpeed` (light 2.30, medium 2.40, heavy 2.50). Braking takes off 0.08 and continues into a slow reverse (max −0.8). With no throttle, speed is multiplied by 0.985 each frame.
  - Engine class multiplies top speed: 50cc ×0.85, 100cc ×1.0, 150cc ×1.15.
  - Steering turn rate is `0.045 · steer · min(1, |speed| / 0.8)`, reversed when going backwards.
  - Grip: each frame `velocity = lerp(velocity, forward·speed, grip)`, where grip is 0.25 on road and off-road, 0.12 while drifting, and 0.06 on ice, which gives a natural slide.
  - Off-road caps top speed at 50 % (karts ease down to it, they don't stop dead).
  - Hitting a wall pushes the kart back out along the wall's normal, keeps 30 % of its speed and plays a bump sound.
- **Hop and drift:**
  - Pressing hop does a 10-frame hop. Landing while steering at speed > 1.2 with hop still held starts a drift in that direction.
  - While drifting, the turn rate is `dir · (0.035 + 0.025 · steer · dir)`, so steering into the drift tightens it and steering out widens it, but it never flips direction.
  - Charge builds while drifting (twice as fast while steering into the drift): blue sparks at 45 frames, orange at 100.
  - Releasing hop ends the drift. A blue charge gives a 30-frame boost and an orange one 60 frames.
  - A boost raises top speed by 30 % and sets speed to at least the boosted top speed. Boost pads give 60 frames.
- **Start:** a countdown (3, 2, 1, GO, 60 frames each) with karts on a staggered 2-wide grid behind the line, player starting last. Holding accelerate during the last 10 frames before GO gives a 40-frame rocket start. Holding it since before "2" makes the wheels spin in place for 30 frames.
- **Laps and positions:**
  - Races are 3 laps.
  - Progress is the kart's nearest centreline sample, found by searching ±30 samples around its last one (search everything only when respawning).
  - The track is split into 8 equal checkpoint segments. A lap only counts when the kart crosses the start line forward having passed every segment in order. Crossing it backwards takes the lap back.
  - Race distance is `lapsDone · trackLength + distanceAlongTrack`. Before a kart's first crossing it is `distanceAlongTrack − trackLength`, which is negative.
  - Positions sort by finish order first, then by race distance.
- **Items:**
  - Item boxes sit in rows of 4 across the road and reappear 3 seconds after being driven through. A kart holding an item (or with its roulette running) can't take another.
  - The roulette spins for 1 second.
  - The items:
    - **Peel:** drop it behind you, or throw it forward if up is held. It spins out the kart that hits it for 50 frames: speed falls to 0 and the driver can't steer.
    - **Bouncer:** a shot that goes straight at 4.5 units/frame. It bounces off walls up to 5 times, lasts 8 seconds and makes a kart it hits tumble for 70 frames.
    - **Seeker:** homes in on the racer one place ahead, follows the centreline until close, then turns up to 0.08 rad/frame toward the target. It makes the target tumble.
    - **Turbo:** a 60-frame boost.
    - **Shield Star:** 6 seconds of invincibility with top speed ×1.15. Karts it touches tumble.
  - Odds by race position (percent):

| Position | Peel | Bouncer | Seeker | Turbo | Shield Star |
| --- | --- | --- | --- | --- | --- |
| 1st | 60 | 40 | 0 | 0 | 0 |
| 2nd–3rd | 30 | 35 | 20 | 15 | 0 |
| 4th–5th | 0 | 25 | 35 | 30 | 10 |
| 6th–8th | 0 | 0 | 35 | 35 | 30 |

- **Kart contact:** karts are circles with radius 6. When they overlap, push them apart along the line between their centres in proportion to the other kart's weight (light 1, medium 1.3, heavy 1.7) and trade a little velocity, so heavy karts barge light ones.
- **Pits:** driving into water, lava or off the world fades the kart out. A little rescue drone carries it back to the nearest centreline sample behind where it fell, facing along the track. That costs 90 frames, followed by 60 frames of blinking invulnerability.
- **Computer drivers (7):**
  - Steering: each drives toward a point on the centreline `40 + speed·12` units ahead, shifted sideways by its own lane offset (−20 to 20, drifting slowly with smooth noise). It eases off the throttle when the angle to the target is over 0.6 rad. It shifts lanes to avoid peels within 60 units ahead and 15 units to the side, and drifts on corners sharp enough to need it.
  - Items: drop peels when someone is within 40 units behind, fire bouncers at a kart within 200 units ahead and a 25° cone, fire seekers right away when not in 1st, use turbos where the road ahead is straight, and use shields right away.
  - Catch-up: top speed is multiplied by `1 + clamp((playerDistance − aiDistance) / 600, −0.08, 0.10)` (no catch-up in Time Trial).
  - Difficulty follows the engine class.
  - If a computer driver's speed stays under 0.3 for 2 seconds, the rescue drone puts it back on the track.
- **Race end:** after the player finishes, the computer drivers race on until they finish or 20 seconds pass. Anyone unfinished is then placed by race distance. The results show positions and times.
- **Modes:**
  - **Grand Prix:** pick an engine class and a racer, then all 4 tracks. Points by finishing place are 15, 12, 10, 8, 6, 4, 2, 1. Show standings after each race (ties go to whoever placed better in the last race). Finishing in the top 3 earns a gold, silver or bronze trophy, saved per engine class and shown on the title screen.
  - **Time Trial:** race alone with 3 Turbos. Save the best race and best lap per track. Record a ghost every 2 frames (x, y, heading, quantised to 16-bit and base64-encoded) and replay the best one as a see-through kart.
  - **Quick Race:** any track and engine class.
- **HUD:** big position number (1st–8th) with a colour flash when it changes, lap counter (`LAP 2/3`, with a fanfare and "FINAL LAP" on the last lap), race time, item slot with roulette, and a minimap of the track outline with a dot per racer in their colour. Lap times appear briefly after each lap.
- **Controls:**
  - Player 1: ↑ or W to accelerate, ↓ or S to brake and reverse, ← → or A D to steer, Space or Shift to hop and drift, X or E to use an item. Esc or P pauses.
  - Player 2 (if chosen): I J K L to drive, U to hop, O for items.
  - Game controller (standard mapping): right trigger or bottom face button to accelerate, left trigger or left face button to brake, either shoulder to hop, right face button for items.
  - Auto-accelerate (Options) keeps the throttle down unless braking.
- **Sound and music:**
  - A Web Audio engine hum for the player: a sawtooth plus a square through a low-pass filter, with pitch following speed. Short effects: countdown beeps, item roulette ticks, hits, drift sparks, boost whoosh, wall bump and splash.
  - An original looping chiptune for each track with a lookahead scheduler, playing 1.1× faster on the final lap.
  - Don't use or imitate music from existing games.
- **Saving:** Time Trial records and ghosts, trophies, unlocked 150cc (unlocked by any 100cc trophy) and options are kept in `localStorage` under `kart-racer-v1`, with a `schemaVersion`.

## Constraints

- One self-contained `index.html` with inline CSS and JavaScript: no libraries, no external requests, no image or audio files. It works offline when opened from the file system.
- **Everything original:** don't use Nintendo's characters, names, track names, sprites, sounds or music (no Mario, no Koopas, no "Rainbow Road"), and don't use any other company's either.
- Keep the Mode 7 inner loop allocation-free. The game must hold 60 fps at 384 × 216 on an ordinary laptop, including split screen.
- Put the spline, surface map lookups, kart physics, lap and checkpoint logic, item logic and computer drivers in a `<script id="core">` block that uses no DOM, so a whole race can be simulated in `node` without graphics. The track texture is painted outside core, and the surface map is built in core by rasterising the same shapes, so node gets the same physics.
- Expose `window.game` for testing: `startRace({track, cc, mode})`, `karts` (position, speed, lap, place, item), `setInput(player, {...})` and `step(frames)`.

## Build steps

1. Core in node: the spline, surface map, kart physics and lap/checkpoint logic. Simulate one kart driving the centreline.
2. Canvas, scaling and the fixed-step loop. The Mode 7 renderer for Meadow Loop with the camera following a keyboard-driven kart.
3. Kart sprites from the box renderer, sprite projection and sorting, and the backgrounds.
4. Drifting, mini-turbos, boost pads, walls, off-road, pits and rescue.
5. Computer drivers, kart contact, countdown and rocket start, positions, race end and results.
6. Item boxes, roulette, all five items, and computer drivers using items.
7. HUD and minimap, then the other three tracks and their surfaces.
8. Grand Prix, Time Trial with ghosts, Quick Race, menus, options and saving.
9. Sound and music, gamepad, split screen (if chosen), and polish.

## Acceptance checks

- **Full race (node):** simulate a 3-lap Quick Race on every track at 100cc with only computer drivers. All 8 finish within 3 × (the fastest finisher's time). No kart is ever inside a wall for more than 1 frame. The drone rescues fewer than 3 karts per race on Meadow Loop.
- **Laps (node):** a kart driving the first half of the track, turning around and crossing the start line backwards doesn't gain a lap. Driving forward over the line after skipping a checkpoint segment doesn't count either. A normal lap counts exactly once.
- **Positions (node):** with karts at known race distances, places come out right, including karts that haven't crossed the line for the first time yet.
- **Drift (node):** holding a drift for 100 frames and releasing gives a 60-frame boost. 45 frames gives 30 frames. Fewer than 45 gives nothing.
- **Items (node):** over 10,000 rolls per position the odds match the table within 2 percentage points. A peel hit spins out for 50 frames, and a Shield Star kart is immune.
- In the browser: the track reads clearly in perspective without warping or flicker, sprites stay planted on the road while the camera turns, and karts show the correct side as they turn.
- Drifting shows blue then orange sparks, and releasing gives a visible boost. Off-road visibly slows the kart and kicks up dust.
- A Grand Prix runs through all 4 tracks with standings after each race and a trophy at the end, and the trophy is still there after a reload. A Time Trial ghost replays the saved best race.
- 60 fps holds in a full 8-kart race (and in split screen, if chosen). No console errors.

## Hand-off to the person

Tell them where `index.html` is and to double-click it (Chrome or Edge works best). Give them the controls in a short table, and explain the one trick that matters: hold the hop button while turning to drift, keep holding until the sparks turn orange, then let go for a speed boost. Mention auto-accelerate in Options for young kids. Records, ghosts and trophies are saved in this browser only. Plugging in a game controller works too.
