# Slide Rush A mobile-first 3D waterslide racing game, packaged as an offline-capable PWA and built for GitHub Pages. You race 12 AI rivals down a giant waterslide with banked turns, ramp jumps, a gap, and a risky shortcut. It uses Three.js with Vite. There is no server and no analytics, and it downloads nothing from a CDN at runtime. It's inspired by the genre (waterslide.io), but all code, art, the track layout and the characters are original and procedural. The game has no image, model or audio files. The icons are generated by a script and the sound is synthesised with WebAudio. ## Play | | Mobile | Desktop | |---|---|---| | Steer | Floating thumb stick (bottom-left; bottom-right when left-handed) | `A`/`D` or `←`/`→` (gamepad left stick works too) | | Jump | Big JUMP button | `Space` / `↑` / `W` (gamepad A) | | Pause | ❚❚ button | `Esc` / `P` | - **Race mode:** if you fly off the slide and don't land on another slide, you're out. Try to finish first out of 13. - **Practice mode:** when you fall, you respawn at the last checkpoint. - **Ramps:** press jump right at a ramp lip (just before it, or up to about 0.14 s after) for a boosted jump. The JUMP button pulses when a lip is coming up. - **Shortcut:** the purple **SHORTCUT** wedge ramp sits at the start of the big detour. Only a boosted jump clears the gap to the purple chute, which skips about 120 m of track. - **Also:** glowing chevrons are boost pads, inside lines through turns are shorter, and pressing jump at the moment of GO gives you a "Great start". ## Develop ```bash npm install npm run dev # http://localhost:5173 (also exposed on your LAN for phone testing) npm run build # production build in dist/ (+ generated dist/sw.js) npm run preview # serve dist/ ``` These tools don't need a browser: ```bash npm run sim -- 30 race 0.7 # headless races: finish times, falls per section, shortcut success npm run check-track # reports places where one slide passes over another, with clearances npm run plot-track -- out.png # top-down PNG of the layout, coloured by height npm run icons # regenerates the PWA icons in public/icons/ ``` Requires Node `^20.19` or `>=22.12` (Vite 8). ## Deploy to GitHub Pages 1. Push to a GitHub repository on the `main` branch. 2. Go to **Settings → Pages → Build and deployment** and set **Source: GitHub Actions**. 3. The workflow in `.github/workflows/deploy.yml` builds with `BASE_PATH=//` and deploys `dist/`. For a `.github.io` repository it uses `/`. The game then runs at `https://.github.io//`. Base path handling: `vite.config.js` uses `process.env.BASE_PATH`. It defaults to `./` (relative), so a plain `npm run build` also works when copied into any sub-folder of a static host. ## PWA and offline use - `public/manifest.webmanifest` uses relative `id`, `start_url` and `scope` (`./`), so they resolve to `//`. It includes 192/512 and maskable icons. - `build/sw-plugin.js` runs after each build. It lists every emitted file and writes `dist/sw.js` from `pwa/sw-template.js`, including a precache list and a content-hash version. - The service worker is registered with `scope: //`. It precaches the app shell and all built assets on install, so the game loads offline after the first visit. - **It's safe to host alongside your other PWAs on the same `github.io` origin:** - It only handles requests inside its own scope. - Its cache names are prefixed `slide-rush://:`, and cleanup only deletes this app's own old caches. - Its `localStorage` key is namespaced the same way (`slide-rush://:save-v1`). - Updates: a new deploy installs in the background. The menu then shows "Update ready — tap to reload". ## Storage (local only) Everything is kept in a single versioned, validated `localStorage` record: - settings: graphics, shadows, post-processing, effects, steering sensitivity, handedness, rival difficulty, SFX/music, practice default and an FPS readout - best race and practice times, best placement, wins, and the number of races - the selected palette, accessory, skin tone and hair colour - the list of unlocked items (the Sun Cap for finishing, Goggles for a podium, the Float Ring and Gold suit for a win, and the Snorkel and Midnight suit for landing the shortcut) ## How it works ### Track (`src/track/`) - The layout (`layout.js`) is written as turtle-graphics pieces: straights, turns, kickers and gaps. Curvature ramps in and out smoothly (clothoid-like), and turns bank automatically. - `TrackBuilder.js` resamples every segment at a uniform 0.5 m arc length. Each sample stores position, tangent, right and up (up includes banking), half width, a curvature vector, signed curvature and a `solid` flag, which is false inside gaps. - The shortcut is a separate segment built from a Hermite spline. It starts beyond the wedge ramp's launch direction and drops onto the main line after the detour. - The trough cross-section is analytic: `h(u) = H·(|u|/W)^p`. The rendered mesh is generated from this same function. - `Track.raycastDown()` casts a vertical ray against that surface. It finds candidates through a spatial hash, refines (s, u) with a few Newton iterations, and rejects any solve that doesn't converge. It's exact with respect to the mesh and needs no triangle tests. - Progress: a racer's arc length on the main line (or its mapped equivalent on the shortcut) divided by the finish distance, which gives a value in [0, 1]. Standings are ordered by finished time first, then progress, with speed as the tie-breaker. ### Physics (`src/sim/Racer.js`) This is a track-constraint model, not a rigid-body simulation. Each racer has `s`, `u`, `speed`, lateral velocity, a world-space velocity when airborne, and a state: `sliding / airborne / falling / finished / out`. It runs in fixed 1/120 s sub-steps. - **Sliding:** along the track, speed changes with gravity along the tangent, plus water-flow thrust, minus linear and quadratic drag. Sideways, the racer feels effective gravity (`g − v²·κ`, i.e. gravity plus centrifugal force) projected onto the trough wall, plus steering, minus damping. Banking and the U-profile push racers back toward the middle, while speed in a turn pushes them up the outer wall. Inside lines advance `s` faster. Racers are snapped to the surface. - **Edge:** past `|u| > W − margin` while moving outward, the racer goes over the lip and becomes airborne. - **Ramp lips** launch racers along the ramp. A buffered or late jump press adds a boost. - **Airborne:** ballistic flight with limited air steering. A downward raycast checks for a landing each step. With no slide below for a grace period (and after dropping well below the last point of contact), the racer is **falling**. In race mode they splash down and are eliminated; in practice mode they respawn at the last checkpoint. ### AI (`src/sim/AI.js`) Each NPC has a `skill`, a `risk` and an `aggression` value. - They choose a lane that wanders slowly, take the inside of upcoming turns, aim for boost pads, and overtake slower racers. - Steering is PD control plus feed-forward against the centrifugal push; skill sets how accurate that feed-forward is. - In the decision zone before the wedge, each NPC decides once whether to try the shortcut. That depends on risk, current placement and skill, and it's less likely after a fall in practice. - Their jump timing has a skill-scaled error, so low-skill NPCs sometimes miss the boost and fall into the gap. - Mild rubber-banding keeps the pack close to the player. The *Rivals* setting (Chill / Normal / Pro) shifts skill. ### Rendering (`src/render/`) - About 25–30 draw calls in total. All slide shells are one merged mesh, and the water channels are another. Pillars, palms, clouds and characters are instanced. - **Characters:** procedural humanoids made from capsules, spheres, hemispheres, cylinders and tori. - Each racer has an invisible bone rig. Every frame the posed part matrices are written into five shared `InstancedMesh`es using one toon material, so all 13 racers take about 6 draw calls, blob shadows included. - Animation: bob, lean into turns, a tuck or star pose in the air, flailing while falling, and floating or celebrating in the pool. - Accessories: cap, goggles, float ring and snorkel, plus 8 swimsuit palettes, 6 skin tones and 7 hair colours. - **Visuals:** hemisphere and directional light, fog, and a gradient sky dome; glossy Phong slides; and a scrolling water-streak texture. - **Effects:** pooled GPU particles (spray, splash, confetti), additive splash rings, camera-space speed lines, and an optional post-processing pass (grade, vignette and speed blur). - **Camera:** a smooth chase camera with look-ahead. It uses a wider field of view and a longer, higher follow distance in portrait, and ducks under slides that pass overhead. ### Performance - The pixel ratio is capped: 1.5 by default, 1.0 on Low and 2.0 on High. In *Auto*, the resolution adapts between 0.85 and 1.5 based on frame time. - Real-time shadows are off by default on touch devices, and post-processing is off by default everywhere. - The simulation, animation, effects and HUD cost about 0.2 ms of CPU per frame on a desktop. ## Project layout ``` index.html entry (UI is built by src/ui/UI.js) vite.config.js base path + SW generator plugin pwa/sw-template.js service worker template build/sw-plugin.js writes dist/sw.js with the precache list public/ manifest + icons scripts/ sim harness, track plot/clearance checks, icon generator src/ App.js state machine, event → FX/audio/UI routing, main loop core/ config, math, storage, quality/adaptive DPR track/ layout, builder, Track (frames, raycast, progress) sim/ Racer physics, AI, Race manager render/ World, TrackMesh, Crowd (characters), Effects, CameraRig, PostFX input/ ui/ audio/ pwa/ controls, DOM overlay, WebAudio synth, SW registration data/ cosmetics + NPC roster ``` ## Completion data | Metric | Amount | |---|---:| | **Total elapsed session time** | **1 hr 8 min 6.016 sec** | | **Input tokens processed** | **44,076,203** | | └ Cache-read input | **43,582,512** | | └ Cache-creation input | **493,431** | | └ Direct/non-cache input | **260** | | **Output tokens** | **365,717** | | └ Thinking/reasoning output | **179,819** | | └ Non-thinking output | **185,898** | | **Total tokens processed** | **44,441,920** | - Model: opus-5.5 - Effort: extra-high - Cost: $18.50 - Site: https://kjlkurt.github.io/waterslide-game-opus-5.5/