Skip to content

Repository files navigation

🪸 LuminaReef

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.

CI Wiki License: MIT Next.js React Three.js R3F TypeScript

No menus, no tutorials, no timers pressuring you — just a dark ocean, twelve distress beacons, and a current that answers your hand.

The reef at rest — twelve distress beacons in the dark

Deploy in one click

Deploy with Vercel   Deploy to Netlify

No env vars. No database. Clone → build → play.


🌊 Why this exists

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.

✨ Features

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 × chain within 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.

🎮 How to play

  1. Find a lost fish — look for pulsing distress beacons out in the dark.
  2. Begin a current beside it — press and drag; a ribbon of light follows.
  3. Draw the current into the Heart Coral — the fish rides your light home.
  4. Chain rescues for combo Lumina, wake all five gardens, reveal the Heart Constellation.

Controls

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

Progression

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

Tide marks

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

📸 Screenshots

Drawing a current Drawing a current of light from a lost fish toward the Heart Coral A ribbon of light follows your pointer. The fish reads your intention — where the stroke starts, whether it travels toward the Heart, whether it wanders.

Nine rescues in Four gardens awake and the reef has colour Four gardens awake. Every rescue physically changes the world: corals ignite and new species emerge from the dark.

The Heart Constellation The finale — the school forms a constellation and dawn breaks The last fish comes home and the school forms a constellation. Eight seconds, choreographed, then dawn.

The keeper's record The trophy panel — tide marks, Lumina, best run Tide marks, Lumina total, best run, runs completed. It survives a reef reset.

LuminaReef on a phone — one finger draws, two fingers orbit

One finger draws, two fingers orbit.

Every image above is generated by npm run shots from a pinned campaign state, so they can't quietly drift away from the game.

🚀 Quick start

npm install
npm run dev     # open the printed http://localhost:PORT

View 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 above

perf 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.

☁️ Deploy

No secrets, no backend. Pick one:

Vercel (recommended) Deploy with Vercel — zero config for Next.js 16
Netlify Deploy to Netlify — uses 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.

🗂️ Project structure

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

📖 Docs & wiki

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

⚡ Performance notes

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 with LinearFilter is the bilinear interpolation the hand-written value noise was doing, so an octave costs one texture fetch instead of four sin()-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), and onFallback pins 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.

📚 Design history

Every major decision is an ADR in docs/adr/ — from the moonlit art direction to the rescue core loop and the heart constellation finale.

🛠️ Tech stack

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

📄 License

MIT

About

A meditative 3D rescue game — draw currents of light, guide twelve lost fish home, and wake five coral gardens beneath the moon.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages