-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
The canonical, always-current version of this page lives in the repository: docs/ARCHITECTURE.md. This page is the short orientation.
The one idea worth carrying around: LuminaReef runs three systems at very different rates, and they talk as little as possible.
| Rate | Rules | |
|---|---|---|
| React — HUD, trophies, toasts | Rare (on campaign events) |
useReefStore(selector) is fine here |
The frame loop — useFrame in 3D components |
60 Hz | Never allocate. Never subscribe. Read via getState()
|
LightCurrentField — the player's stroke |
Continuous, outside React | A plain mutable class, passed by reference |
LightCurrentField is the blackboard between your hand and the fish. The
pointer writes to it, the fish read from it, the renderer draws from its
Float32Arrays. It is deliberately not React state — a stroke updates many
times a second, and routing that through setState would re-render the tree at
60 Hz.
The whole game is this loop:
-
CurrentControllerray-picks a fish on pointer down and pins it as the sole responder, then feeds pointer positions into the field. -
FishSchoolreports every lost fish's live position into the field each frame, which is what step 1 picks against. - The pinned fish asks
assessRescuePath()whether the stroke means it — starts at me, continuous, directional, reaches the Heart, doesn't wander. - If valid, the fish rides
sampleFlow(): a target ahead of it on the stroke, plus the stroke's tangent. - It's rescued only after physically travelling ≥66% of the path and arriving within 3 units of the Heart.
-
claimRescue()caps it at one fish per stroke, thenstore.rescueFish()computes the combo, Lumina, achievements and journal line.
Step 6 is why the frame loop stays clean: everything gamified happens once, in an event, not sixty times a second.
app/page.tsx Dynamically imports the scene (ssr: false) + the HUD
components/3d/ Canvas, water, shafts, corals, fish, current, finale
components/ui/ HUD, trophies, toasts, journal, intro, finale card
lib/achievements.ts Tide marks + combo and run-time rules
lib/restoration.ts The five named gardens
stores/useReefStore.ts One zustand store: campaign, combo, records, saving
docs/adr/ 9 ADRs — the design history
public/models/fish/ Web-optimized *_web.glb only (~270 KB each)
R3F can't be server-rendered, so the scene is client-only behind a deep-sea gradient placeholder — there's never a white flash.
Never enable vertexColors on the fish material. Their GLBs ship no
COLOR_0 attribute, and an unbound colour attribute reads (0,0,0): black
fish. Per-instance tinting comes from instanceColor.
- Performance — where the frames actually go
- Customizing — the data files that shape the reef
- ADRs — why any of this is the way it is
Playing
Building
Repository