A meditative 3D rescue game. Draw currents of light with your finger, guide twelve lost fish home to the Heart Coral, and wake five sleeping coral gardens beneath the moon.
No menus, no tutorials, no timers pressuring you — just a dark ocean, twelve distress beacons, and a current that answers your hand.
No env vars. No database. Clone → build → play.
Most idle/zen apps are screensavers; most games are noisy. LuminaReef sits between: a finite, hand-crafted campaign (12 rescues → 5 gardens → 1 finale) wrapped in a genuinely calm place. Every rescue physically changes the 3D world — corals ignite, new species emerge from the dark, and the reef ends the campaign as one living constellation.
The game
- 🐟 Lost-school rescue campaign — 12 fish stranded in the dark, each with a visible distress beacon. Begin a current beside one and draw it home.
- 🖐️ Gesture-driven light current — a spline of light sculpted by your pointer; fish that touch its glow follow it. It is the only tool you have.
- 🏝️ Five named gardens — Moonwell, Silverfin Hollow, Violet Nursery, Lantern Reach, Crown Reef. Each wakes at a campaign milestone and permanently transforms the scene.
- 💛 Heart Constellation finale — an 8-second choreographed ritual when the last fish comes home. Replayable.
The feel
- ✨ Rescue juice — floating
+Lumina, combo punch, Heart Coral kick on every return home. - 🏝️ Garden wake ritual — each garden names itself with a short overlay and a deeper chime before tide-mark toasts play.
- 📜 Keeper's tide summary — after the finale: time, max chain, Lumina, new tide marks.
- 🏆 8 tide marks — from First Light to Keeper's Return; they survive
resets. Combos (
25 × chainwithin 25 s) and a speed-run clock (Swift Tide under 6 minutes) keep replays interesting. - 📒 Keeper's record panel — trophies, Lumina, best run, runs completed.
The craft
- 🌫️ Moonlit sanctuary visuals: tapered Heart Coral, garden colour identity, soft current ribbon, stone arch mid-ground, living seagrass.
- 🐠 Instanced fish with procedural swim cycles (GPU vertex shader) and species GLBs (~270 KB each).
- 📈 Adaptive quality: lite-first, promotes to shadows + bloom only when frames hold; thrashing devices pin to lite.
- 🔊 All audio synthesized at runtime — zero audio assets.
- 💾 Progress in
localStorage; malformed saves degrade gracefully.
- Find a lost fish — look for pulsing distress beacons out in the dark.
- Begin a current beside it — press and drag; a ribbon of light follows.
- Draw the current into the Heart Coral — the fish rides your light home.
- Chain rescues for combo Lumina, wake all five gardens, reveal the Heart Constellation.
| Input | Action |
|---|---|
| Left drag | Draw a light current |
| Right drag | Orbit the camera |
| Wheel | Zoom |
| One finger (touch) | Draw a current |
| Two fingers (touch) | Orbit / zoom |
| Rescues | World response |
|---|---|
| 1 | 🌕 The Moonwell wakes |
| 3 | 🐟 Silverfin Hollow — the silverfin school returns |
| 6 | 💜 The Violet Nursery pulses to life |
| 9 | 🏮 Lantern Reach — lantern fish emerge from the deep |
| 12 | 👑 Crown Reef + the Heart Constellation finale |
| Achievement | How |
|---|---|
| First Light | Guide your first fish home |
| Tide Turner | 3 rescues |
| Chain of Light | ×3 rescue combo (each within 25 s) |
| Halfway Home | 6 rescues |
| Garden Keeper | 9 rescues — four gardens awake |
| Heart Constellation | Complete the campaign |
| Swift Tide | Full run under 6 minutes |
| Keeper's Return | Complete the campaign twice |
Every image above is generated by
npm run shotsfrom a pinned campaign state, so they can't quietly drift away from the game.
npm install
npm run dev # open the printed http://localhost:PORTView it in a real, visible browser tab. The scene runs on
requestAnimationFrame, which browsers pause for hidden/background tabs — headless preview panes will show a blank canvas.
npm run build # production build
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm run perf # measure fps against a running dev server
npm run shots # regenerate the screenshots aboveperf and shots drive a real browser with Playwright and expect npm run dev
to already be running in another terminal — Next allows only one dev server per
directory.
No secrets, no backend. Pick one:
| Vercel (recommended) | |
| Netlify | netlify.toml |
| CLI | npx vercel or npm run build && npm start on any Node 20 host |
Optional: set NEXT_PUBLIC_SITE_URL to your production origin so Open Graph
URLs resolve correctly.
app/ App Router, icon/apple-icon, metadata
components/3d/ Canvas, sanctuary, fish, current, finale
components/ui/ HUD, juice, garden wake, trophies, intro, summary
lib/achievements.ts Tide-mark definitions + combo/run-time rules
lib/restoration.ts The five named gardens and their promises
stores/useReefStore.ts Single zustand store: campaign, combo, records, juice
scripts/ measure-perf.mjs, capture-shots.mjs (Playwright)
docs/adr/ 9 architecture decision records
docs/plans/ Design docs and implementation plans
docs/media/ Generated screenshots — `npm run shots`
wiki/ Source for the GitHub wiki
public/ favicon, OG image, optimized fish GLBs
| Where | What |
|---|---|
| Wiki | Start here — playing, running, extending |
| How to Play | Controls, the rescue loop, gardens, tide marks |
| FAQ | Blank canvas, black fish, lost progress |
| docs/ARCHITECTURE.md | How the code fits together, and the rescue end to end |
| docs/PERFORMANCE.md | Where the frames go, with measurements |
| docs/CUSTOMIZING.md | Add a fish, rename a garden, change the mood |
| docs/adr/ | 9 ADRs — why any of this is the way it is |
| CONTRIBUTING.md | What fits in this game, and what doesn't |
| CHANGELOG.md | What changed |
Targets a steady 60 fps on integrated graphics — nothing breaks calm like a dropped frame. Full detail and measurements in docs/PERFORMANCE.md.
- The cost is fill rate, not simulation. Twelve fish are nothing; two
screen-filling planes are everything. The seabed and water ceiling run
animated caustics per pixel, so their
fbm()noise lattice is baked once into a tileable texture — sampling withLinearFilteris the bilinear interpolation the hand-written value noise was doing, so an octave costs one texture fetch instead of foursin()-hashes. On an integrated Radeon RX Vega 10 at 2560×1440 that took the reef from 33.6 fps (lite) to 45.9 fps in full quality — faster while rendering 38% more pixels and affording bloom and shadows it previously couldn't. - Lite-first rendering — no shadows/bloom until the device proves itself;
demotes again on frame drops (
PerformanceMonitor, 2 flip-flops max), andonFallbackpins a thrashing device to lite for good. - Static shadow maps — the reef redraws its shadow map for 8 frames after
a garden wakes, then freezes it (
shadowMap.autoUpdate = false). - Instancing everywhere — corals and fish are
InstancedMesh; swim animation runs in the vertex shader, not on the CPU. - Store writes only on events — combos, achievements and scores are computed inside the rescue action; nothing gamified touches the frame loop.
- Asset diet — heavy source GLBs are gitignored; only web-optimized models (< 300 KB) ship. All shaders and audio are procedural.
Every major decision is an ADR in docs/adr/ — from the moonlit art direction to the rescue core loop and the heart constellation finale.
Next.js 16 · React 19 · TypeScript 5 · Tailwind v4 · three r185 · @react-three/fiber · @react-three/drei · @react-three/postprocessing · zustand · framer-motion · lucide-react





