Browser-based 3D powerboat simulation. Vanilla Three.js (vendored, no build step), hand-rolled physics.
The welcome screen provides the browser gesture required to unlock Web Audio; it does not force fullscreen. Progress, quality preferences, the active camera, sea state and selected vessel are stored locally in the browser.
- Docker and Docker Compose (the app is served as a static nginx container).
- A modern browser with WebGL2 (recent Chrome, Firefox, Safari, or Edge).
- Node.js 22 or newer for tests and validation.
- Python 3.12 or newer only when generating mobile boat variants.
docker compose up -d
The nginx container compresses JavaScript, JSON, GLB and HDR responses and sends ETag validators. Assets are revalidated because their URLs are stable rather than content-hashed, so a deployment cannot leave an old model or script in cache.
- Z/W or Up arrow: throttle (the lever stays put)
- S or Down arrow: ease off / reverse
- Q/A and D or arrows: steering (self-centering)
- Space: throttle to zero
- C: cycle cameras (chase, helm, top-down, cinematic)
- 1-4: sea state (after the sea controls have been unlocked)
- 1:
0.35 m / 4.5 s - 2:
0.9 m / 5.4 s - 3:
2.4 m / 6.1 s - 4:
5.2 m / 6.4 s
- 1:
- B / Shift+B: next / previous boat
- R: reset
- L: achievement log
Auto-detected (pointer: coarse): a gesture control appears at the bottom
center and the keyboard shortcuts are hidden.
- Touch down inside the circle: the contact point becomes neutral.
- Drag up / down: forward / reverse; drag left / right: turn. The two axes combine and return to neutral on release.
- When not steering: one finger on the water to orbit, two fingers to pinch-zoom.
- Double-tap the water: cycle camera.
- While steering: a second finger on the water orbits horizontally, and its vertical motion adjusts zoom.
- Boat selector: full-width strip at the bottom, left/right arrows and horizontal swipe on mobile. Wave intensity: top right.
The render budget is trimmed on touch devices (fewer pixels, reduced shadows and MSAA) to preserve the framerate. For textures, two safeguards:
- runtime cap at 1024² before the GPU upload (lowers sustained VRAM);
- mobile variants
assets/boats-mobile/*.glb(textures ≤1024², geometry and materials unchanged), loaded first on touch devices with a fallback to the full model if missing.
The variant is essential for large GLBs: a model with 4096² textures is decoded
at full resolution by GLTFLoader (up to 512 MB transient for 8 textures)
before the runtime cap kicks in, so the tab crashes on mobile. The variant
removes that spike (~32 MB instead of 512).
⚠️ Any new boat with textures > 1024² must get its variant, or it will crash on mobile. Generate one with:python3 tools/glb_shrink.py site/assets/boats/<x>.glb site/assets/boats-mobile/<x>.glb
#auto at the end of the URL: self-driving demo.
Quality is auto-selected from an initial profile, then adjusted with hysteresis based on CPU/GPU frame times. The controller drives the internal resolution, MSAA, bloom, shadows, the reflection and refraction passes, ocean density, particles, rain, and the physics rate.
?quality=low|medium|high|ultralocks a profile for comparison or testing.?perfshows real-time metrics (frame/CPU/GPU, draw calls, triangles).?masksrenders the anti-water volumes as opaque neon green to spot where they poke out of the hulls.?debugexposes the documented runtime inspection API aswindow.openWater.- Parameters combine, e.g.
?quality=low&perf.
The in-game quality selector remembers the choice in localStorage. A quality
parameter in the URL takes priority for benchmarks and shared links.
Without quality, the mode stays automatic: downgrades are quick when the budget
is exceeded and upgrades deliberately slow to avoid oscillation.
For reproducible CPU measurements of the wave hot paths, run
npm run benchmark. Archive a machine-readable reference with
npm run --silent benchmark -- --json > baseline.json, then compare a later run with
npm run benchmark -- --baseline baseline.json --max-regression 15. The command
returns a non-zero status when a median regression exceeds the allowed percentage.
Use --quick for a short smoke run; performance comparisons should use the full run.
On touch devices, the initial load uses a 1K half-float HDR, downloads audio only after the start gesture, and loads only the active preset's wildlife. Wildlife GLBs are decoded sequentially to avoid Safari/iOS memory spikes. Very heavy boats without a mobile variant are not restored automatically, but stay manually selectable.
A local achievement log tracks progress (speed, distance travelled, jumps,
dolphin escorts, whale/turtle encounters, etc.). Each unlock shows as a small
banner, and a panel summarizes explicit tiers plus navigation, play-time, jump
and cumulative air-time records. Achievements progressively unlock the fleet and
the sea-state controls; a fresh profile starts with Smolbot only. State persists
in localStorage: no account, no network calls.
- Ocean: deterministic JONSWAP spectrum of 16 Gerstner waves, normalized to
significant wave height
Hsand peak periodTp, with their own phases and a secondary cross swell. Shared CPU/GPU computation, smoothed weather transitions, deterministic spatial gusts and a slowly meandering surface current. Wind-driven noise micro-ripples, procedural crest foam, normal flattening plus rising roughness with distance (specular anti-aliasing). PBR material extended viaonBeforeCompile, lit by a prefiltered HDR environment map. - Boat: 6-DOF rigid body at a fixed step (240 Hz). Each model has its own physics sheet (mass, inertias, dimensions, propulsion, cameras, and effect anchors). Buoyancy over 8 hull points sampling the real wave (height, normal, and 3D orbital velocity), vectored thrust at the transom (cut when the propeller ventilates), drag computed relative to the water, planing lift with a moving center of pressure, heel in turns, and hydrodynamic roll restoring. Surface current is included in every relative-water velocity sample; apparent wind applies vessel-scaled aerodynamic drag above the center of gravity, so crosswind produces both leeway and heel.
- Effects: hull spray spread across the physical contacts of the front third, transom turbulence and jets emitted from each real propeller (per-droplet size/alpha, fading on water contact), mist, turn spray thrown off the outer flank based on the maneuver's energy, impact plumes, and a persistent foam wake. Hull water displacement in the ocean shader (bow wave, trough, Kelvin V-wake). A bounded world-space wake field persists through turns, drifts with the surface current and is shared by GPU displacement plus CPU height, normal and vertical-water-velocity queries, so a hull physically reacts when it crosses an older wake.
- Sky: 4k Poly Haven HDRI (CC0) as backdrop plus IBL; sun direction and haze color extracted automatically from the file's pixels.
- Rendering: planar reflection of the boat in the water (mirror camera, dedicated layer) plus screen-space refraction of the submerged hull; PCF cast shadows; post-processing (4x MSAA, bloom, ACES output).
- Audio: hybrid Web Audio sound design. CC0 engine banks (shared between
similar hulls), rpm and load interpolated in real time, spatialized engine and
cavitation, multi-intensity rain and waves, real thunder sorted by distance
with physical delay, ducking and limiter. Spatialized CC0 gull calls triggered
by the presence of birds (none at the birdless sea background). In calm
weather, parrots have their own real CC0 calls (see
LICENSES.md), spatialized per bird on a dedicated bus, with a synthesized procedural fallback if the files are missing. Wind, impacts, and the fallback layers stay procedural. Starts on the first interaction. - Wildlife (per sea state): rigged GLB gulls (
SkeletonUtilsclones, flap/glide blended byAnimationMixer) that cross the sky in light seas, flee out to sea once it storms, and reflect in the water. Below the surface: coastal fish (boids-style schools plus solitary swimmers), rendered on the refraction layer to stay visible through the water, swimming animated by their GLB clip. Off-screen spawn/despawn, with density and species driven by the sea preset. In rough seas (Rough), escort dolphins come up from astern and ride the bow (bow-riding) as long as the boat holds 10-20 knots, then peel off when it slows. In a storm (Storm), a whale occasionally passes in the distance, its back breaking the surface ringed with foam, and blows a column of spray from its blowhole. - Lagoon (calm seas only): in
Calm, the shallow, clear water reveals a sandy bottom (large plane rendered on the refraction layer, tinted by absorption, procedural rippling plus caustics) that follows the boat and blends into deep water via a radial gradient. Underwater: starfish resting on the sand (world-anchored, seeded in colonies of varied sizes, recycled off-screen to emerge smoothly), turtles, and manta rays gliding above, beating fins/wings. In the sky: small squadrons of parrots in flight, a bird system separate from the gulls (no gull calls). Everything fades out or heads offshore outside theCalmpreset. - Moving parts: the outboard and its existing propeller follow the RIB's steering and throttle; the Sea-Doo's handlebar and key use their GLB bones directly; the Ivory Horizon's twin propellers spin on their hubs and its existing flag becomes a French ensign on a continuous cloth grid, pinned along its whole blue edge to the mount then unfurled by the relative wind, with no overlaid visual prop. The Smolbot's outboard follows the steering, the Zodiac steers its two outboards (submerged propellers); on the S.S. Minnow III, the original propeller, rudder, and wheel are articulated despite the lack of pivots or bones in its GLB export. Frickie's Yacht flies a bitcoin stern ensign unfurled by the wind.
- Fleet (in-game label: source model; attribution and URL kept in each GLB's
metadata):
- Azure Comet: "ZEFIRO" by angelo raffaele catalano (Sketchfab, CC-BY-4.0).
Also serves as the fallback model (
assets/boat.glb) if the boat index is unreachable. - Blackfin Vanguard: "Assault Boat" by tnnv (Sketchfab, CC-BY-4.0). Mobile variant provided.
- Neon Manta: "2012 Sea-Doo GTI SE 130" by BoatUS Foundation (Sketchfab, CC-BY-4.0). Mobile variant provided.
- Ivory Horizon: "motoryacht 35" by angelo raffaele catalano (Sketchfab, CC-BY-4.0). Animated twin propellers and French ensign.
- Smolbot: "Boat" by Firdaus Sahak (Sketchfab, CC-BY-4.0).
- S.S. Minnow III: "SS Minnow III" by gogiart (Sketchfab, CC-BY-4.0). A 38-foot Wheeler Playmate; propeller, rudder, wheel, and radar antenna articulated with no bones or pivots in the export.
- Zodiac: "Zodiac boat" by RedC130 (Sketchfab, CC-BY-4.0). Small RIB ~5.5 m, lightweight SketchUp export (0 textures, ~93k vertices), no mobile variant needed.
- Frickie's Yacht: "Frickie's Yacht" by KurumA (Sketchfab, CC-BY-NC-4.0, non-commercial use only). Superyacht ~78 m, the largest in the fleet; heavy geometry (43 interior meshes) with no mobile variant, bitcoin stern ensign.
- Azure Comet: "ZEFIRO" by angelo raffaele catalano (Sketchfab, CC-BY-4.0).
Also serves as the fallback model (
- Heavy hulls without a mobile variant (Ivory Horizon, S.S. Minnow III, Frickie's Yacht) are not restored automatically on touch startup, but stay manually selectable.
- An articulated procedural outboard (
buildOutboard) is available as a fallback, disabled by default; each model keeps its own powerplant. - Wildlife, gull: "Flying Seagull" by geminga (Sketchfab, CC-BY-SA). Fish:
Quaternius pack (CC0, poly.pizza). Attributions/URL kept in each GLB's
metadata; specGloss models are converted to metallic-roughness for the vendored
loader.
- Turtle: "Model 50A - Hawksbill Sea Turtle" by DigitalLife3D (CC-BY-NC-4.0).
- Manta ray: "Manta Ray (Birostris) animated" by Violaine (CC-BY-NC-SA-4.0).
- Starfish: "Asteroid Starfish / Seastar" by Digital Atlas of Ancient Life (CC0-1.0). Procedural fallback if missing.
- Whale: "Blue Whale - Textured" by Bohdan Lvov (CC-BY-4.0).
- Dolphin: "Model 61A - Bottlenose Dolphin" by DigitalLife3D (CC-BY-NC-4.0).
- Parrot: "Scarlet Macaw" by Mateus Schwaab (CC-BY-4.0).
⚠️ Non-commercial wildlife (turtle, manta, dolphin: NC). - Sea/gull sounds: see
site/assets/audio/LICENSES.md.
site/ Static app served by nginx
index.html Entry point (HUD, boot)
js/ ES modules, loaded directly (no build step)
main.js Composition root and explicit frame pipeline
controllers/ Drive, camera, gesture/view input and vessel workflows
simulation/ Boat physics, vessel specs/rigs and wave spectrum
rendering/ Ocean, environment, weather, effects and water passes
fauna/ Wildlife lifecycle, behavior and shared fauna math
runtime/ Audio, deferred loading and adaptive quality
ui/ HUD, first-voyage guide and achievements
README.md JavaScript architecture and dependency conventions
vendor/ Vendored Three.js (three.module.js + addons)
assets/
boats/ Vessel GLBs + index.json
boats-mobile/ Downscaled (<=1024 texture) variants
animals/ Wildlife GLBs (fish/, birds, sea life)
audio/ CC0 samples + LICENSES.md
*.hdr Sky HDRIs (1k / 4k)
ogimage.png Social / hero image
tools/
glb_shrink.py Generate mobile texture variants
tests/ Node unit and project-integrity tests
Install the pinned validation dependencies and run the complete local gate:
npm ci
python3 -m pip install -r requirements-tools.txt
npm run check
python3 -m ruff check toolsThe suite contains more than 100 automated checks and enforces coverage thresholds for the unit-testable physics, configuration, and progression modules. GitHub Actions runs JavaScript, HTML, Python, integrity, and coverage checks on every push and pull request.
See CONTRIBUTING.md for review and asset-provenance rules,
and SECURITY.md for private vulnerability reporting.
The source code in this repository is released under the MIT License (see
LICENSE).
The repository is a mixed-license distribution. The application source is MIT, while bundled media retain their respective licenses:
- Non-commercial (CC-BY-NC / CC-BY-NC-SA): the turtle, manta ray, dolphin, and Frickie's Yacht. Fine for personal, non-commercial use only.
- Other media use CC0, CC-BY, or CC-BY-SA terms. Attribution is kept in each
GLB's metadata and in
site/assets/audio/LICENSES.md.
See THIRD_PARTY_NOTICES.md for the complete asset and
vendored-code inventory before reusing media outside this project.
