An open API service indexing awesome lists of open source software.

https://github.com/lalomorales22/surf-js

surf-js โ€” a single-file open-world surf sim. PHP + SQLite + three.js, everything procedural. Paddle out at Penguin Point.
https://github.com/lalomorales22/surf-js

game php procedural-generation sqlite surfing threejs webgl

Last synced: 1 day ago
JSON representation

surf-js โ€” a single-file open-world surf sim. PHP + SQLite + three.js, everything procedural. Paddle out at Penguin Point.

Awesome Lists containing this project

README

          

# ๐Ÿ„ surf-js

**A single-file, open-world surf sim.** One `index.php` โ€” PHP + SQLite leaderboard on the back, a full three.js ocean on the front. No build step, no assets, no node_modules. Drop it on shared hosting or a Raspberry Pi and paddle out at **Penguin Point**. ๐Ÿง

```
git clone https://github.com/lalomorales22/surf-js
cd surf-js
php -S localhost:8000
# open http://localhost:8000 and paddle out
```

Everything you see โ€” waves, sand, surfer, sounds โ€” is generated procedurally at runtime.

## What's in the water

- **Real-feel waves** โ€” analytic swell model with wave classes from 1โ€“2ft to 8โ€“10ft double-overhead faces, rare **BOMB SETS** every few minutes, lefts / rights / A-frames / closeouts, and celerity, steepness, and consequence that scale with size. Approaching sets visibly **stand up and lean forward** before they throw: the shore-facing wall darkens to green glass, streaks as water draws up the face, and the whole unbroken crest pitches toward the beach as it hits the bar.
- **A live character menu** โ€” the start screen shows the actual selected procedural rider holding the selected board, not a placeholder avatar. Kai and Mara have distinct silhouettes, faces, suit cuts, and hair, and all six rider/board combinations update live and persist between sessions.
- **Pick your quiver** โ€” three boards with real physics trade-offs: the 5'10" shortboard (quick rails, hard paddling, late-drop friendly), the 7'2" funboard (balanced), and the 9'1" log (glides into anything early, but slow to turn and loves to pearl). On land, the board is tucked under the arm with hand-to-rail IK instead of floating beside the rider.
- **Visible wave faces and barrels** โ€” crest highlights, trough drawdown, darker standing faces, breaking-lip foam, and pooled overhanging water shells make an incoming set readable from the lineup. Hollow waves open into a rendered tube with an interior surface, mouth, exit light, vignette, and tube-aware chase camera. Stall into the pocket, track the tube (time in the barrel scores 3ร—), and get spat out with the mist.
- **Water that reflects you** โ€” the ocean samples a planar mirror pass, so the rider, board, and pitching lip actually appear in the surface instead of a sky-only cube map. Sun glitter is a GGX lobe that stretches into a broad glint path across the chop rather than a single hot pixel, and depth colour is per-channel Beer-Lambert โ€” red extinguishes first, so the shallows grade cyan โ†’ blue โ†’ navy on their own.
- **A beach worth landing on** โ€” you start a short walk from the waterline beside a striped umbrella and a lounge chair, with sunbathers scattered up the dry sand: some sitting on towels, some standing, each breathing and slowly turning their head on its own phase offset so the crowd never moves in lockstep. All procedural, like everything else.
- **Manual takeoffs** โ€” no auto-catch. Read the lineup radar, sit in the zone, turn shoreward, paddle until the face takes you, then hit SPACE. Graded takeoffs: early bogs, sweet ones fly, late ones airdrop with a real pearl risk.
- **Momentum surfing** โ€” your velocity lives on the moving face: gravity pulls you down-slope, rails carve with a grip limit (overpush and you slide), and pumps must be timed to the face. The rider lands side-on in a compressed stance with both feet IK-planted to the deck, then crouches deeper under pump and carve load; snaps / cutbacks / floaters are graded with speed-scaled spray.
- **A living shoreline** โ€” dedicated swash simulation: uprush/backwash sheets with lace foam and bubble decay, backwash colliding with the next bore, sediment stirred into the drain, and wet sand that darkens, turns glossy, and mirrors the sky and the surfer before drying out.
- **Consequences** โ€” overhead wipeouts mean 5โ€“9 second two-stage hold-downs, ragdoll physics with per-limb water drag, and a second wave that can reset the tumble.
- **A rider who actually sits** โ€” waiting in the lineup means straddling the board for real: tail sunk, nose up, shins in the water kicking slow ripples while the head tracks incoming sets.
- **Full post-FX chain** โ€” bloom, god rays, SMAA, film grade, underwater pass โ€” in three hot-swappable quality tiers, tuned for 60fps at medium on an M1 MacBook Air.
- **Procedural audio** โ€” ocean bed, per-class break booms, spray hiss, barrel-interior reverb, bomb-set rumble. Zero audio files.
- **Local leaderboard** โ€” self-provisioning SQLite, CSRF-guarded saves, rate-limited, top-10 board on L.

## Controls

| Key | On sand | On the board | Riding |
|---|---|---|---|
| W A S D | move | paddle / turn | pump / rail left / stall / rail right |
| SHIFT | run | duck dive | kick out |
| SPACE | hop on board | **pop up** (time it!) | โ€” |
| E | โ€” | sit up / lie down | โ€” |
| C | camera 1st โ†” 3rd | โ† | โ† |
| F | fullscreen | โ† | โ† |
| L / M / P | leaderboard / mute / pause | โ† | โ† |
| drag / wheel | look / zoom | โ† | โ† |

**Start screen:** pick a rider and a board (click, or 1/2/3 for the board and R to switch rider).

### Gamepad

A PS5 DualSense (or any standard-mapping pad) works over Bluetooth or USB-C with no driver and no helper app โ€” the browser's Gamepad API reads it directly. Pair it and it's picked up automatically; the d-pad and โœ• also drive the start screen.

| Control | Does |
|---|---|
| Left stick | paddle / steer โ€” **analog**, so rail lean is continuous instead of the keyboard's hard left/right, and you can hold a carve just under the grip limit |
| Right stick | look |
| โœ• | pop up |
| L1 / R1 | duck dive ยท kick out |
| โ—‹ / โ–ณ / โ–ก | sit up ยท camera ยท leaderboard |
| Options | pause |

Keyboard and pad can be used at the same time; neither clears the other's state.

**HUD:** wave-select cards (bottom-left) call each set โ€” face height, LEFT/RIGHT/A-FRAME/CLOSEOUT, a purple **HOLLOW** tag when a wave will barrel over the bar, and seconds out. The **lineup radar** (bottom-right) shows every crest with its ETA, hollow waves in purple, where it's breaking (white), and the takeoff pockets (pulsing dots).

## Requirements

- PHP 7.4+ with `pdo_sqlite` (any shared host, or `php -S` locally)
- A WebGL2 browser โ€” Chrome or Safari on desktop recommended
- Internet access for the pinned three.js r160 CDN modules (the game itself ships no assets)

Served statically (no PHP)? The game still runs โ€” the leaderboard just marks itself offline.

## Architecture (one file, on purpose)

```
index.php
โ”œโ”€ PHP: sqlite provisioning (./data, web-access denied), CSRF, rate limit,
โ”‚ ?action=leaderboard / ?action=save_ride, CSP + security headers
โ”œโ”€ CSS: sun-bleached poster UI system
โ””โ”€ ES module (~3.8k lines):
config โ†’ shared wave/swash math (authored in JS, mirrored into GLSL โ€” one
source of truth for physics AND rendering) โ†’ post-fx tiers โ†’ sky + env maps โ†’
wet sand โ†’ terrain โ†’ palms/rocks/beach props + crowd โ†’ refraction/depth prepass โ†’
ocean (planar reflection + GGX glint + Beer-Lambert depth) + forming-wall shading +
crest ribbons + pooled barrel shells โ†’ swash strip โ†’ GPU spray โ†’ swell/bore lifecycle โ†’ rider presets โ†’
24-bone skinned surfer โ†’ pose library โ†’ IK / secondary motion / ragdoll โ†’
board quiver (physics table) โ†’ state machine โ†’ camera โ†’ input (keyboard + gamepad) โ†’
procedural audio โ†’ HUD/radar/cards โ†’ leaderboard client โ†’ main loop
```

The core invariant: `h(x, z, t)` and friends are written once in JavaScript and injected into GLSL with the same constants. The board you ride and the wave you see are the same equation.

A second one worth knowing if you're hacking on it: the gamepad is polled from the render loop, never from `simulateStep()`, so `advanceTime()` stays deterministic and automated playthroughs remain reproducible.

## Deploying

Upload `index.php`. That's it. The `data/` directory (SQLite + deny rules) creates itself on first save. Keep the CSP header intact โ€” it's part of the security posture (nonce'd scripts/styles, pinned CDN, no inline handlers).