Inspiration
"The Last Lantern / 2197" began with a simple but haunting question: What if memory itself was the only thing that could bring a place back to life?
We were drawn to the idea of an abandoned village frozen in time—not as a place of horror, but as a meditation on loss, legacy, and the traces we leave behind. The concept of ten scattered lanterns became our narrative device: each one a portal to a different memory, a different voice, a different perspective on what was and what could have been.
The project emerged from a desire to create something deeply interactive and atmospheric—not just a game you play, but an experience you inhabit. We wanted players to feel the chill of an empty world while simultaneously feeling connected to the lives that once filled it through the voices and memories locked within lanterns.
What it does
- **React Three Fiber as a Bridge Between World We discovered that React Three Fiber is far more powerful than a simple WebGL abstraction. By bringing React's declarative model to Three.js, we could manage complex 3D state intuitively—tracking which lanterns were lit, managing the player's position, and triggering narrative sequences felt natural rather than requiring imperative scene graph manipulation.
2. Audio as a Core Mechanic
Creating procedural audio directly in the browser (using Web Audio API) taught us that sound is story. The ambient wind, the chiming lanterns, the musical undertones—these aren't decorative. They're essential to the experience. We learned to respect the silence between sounds as much as the sounds themselves.
3. Mobile Accessibility is Non-Negotiable
We initially designed for desktop first-person controls but quickly realized that mobile players needed an equally compelling experience. Implementing touch-based movement and look controls forced us to think differently about spatial navigation—constraints often lead to innovation.
4. Narrative State Management is Complex
Managing multiple narrative threads (prologue, ten memories, multiple endings) through React state taught us that careful abstraction matters. By separating game state (lit, memory, ending, playerName) from rendering logic, we kept the system maintainable as complexity grew.
5. Performance Matters for Immersion
A stuttering frame rate breaks the spell immediately. We learned to be ruthless about optimization: lazy-loading assets, using efficient shaders, and minimizing re-renders. Smooth 60fps feels like a feature, not a technical detail.
How we built it
Architecture The project follows a clean separation of concerns:
App.jsx— The orchestrator. Owns all game state (which lanterns are lit, the player's name, which memory is active) and handles Web Audio setup and lifecycle management.components/ArchiveScene.jsx— The 3D world. Manages the Three.js scene, lantern placement, player movement bounds, and rendering the village environment.components/Interface.jsx— The narrative layer. Renders the boot screen, prologue, HUD, memory overlays, and the ending sequence.styles.css— All styling and animations. Kept in one place for easy design adjustments.
Tech Stack
- React 19 — For state management and UI composition
- Vite 7 — For blazingly fast development and production builds
- Three.js — The 3D engine
- React Three Fiber — React bindings for Three.js
- @react-three/drei — High-level Three.js utilities (lights, materials, controls)
Development Flow
- Start with
npm run dev— Vite's HMR makes iteration seamless - Develop in components — each piece (scene, interface, audio) can be worked on independently
- Test on mobile early — touch controls need real-world testing, not emulation
- Use
npm run buildfor production — Vite produces optimized bundles automatically - Deploy to Vercel — seamless GitHub integration and instant previews
Challenges we ran into
1. Web Audio Quirks
The browser's Web Audio API requires a user gesture to initiate audio context. We solved this by starting audio from the boot screen on first interaction, not during render. This taught us to respect browser security models rather than fight them.
2. Bridging 2D UI and 3D Movement
Creating an immersive first-person experience while layering 2D narrative UI on top required careful event handling. We had to ensure that clicking a memory overlay didn't accidentally move the camera, and that keyboard input was captured only when appropriate.
3. Memory Overlay Timing and State
Deciding when to show a memory (only when near a lantern? always available?), how long to keep it visible, and how to transition back to exploration required iterative design. Too rigid and it felt restrictive; too loose and it felt arbitrary.
4. Mobile Touch Controls in 3D Space
First-person camera movement with touch is inherently difficult. We implemented a dual-stick approach, but this consumed screen real estate. Finding the balance between usability and immersion meant multiple rounds of playtesting.
5. Performance on Lower-End Devices
Maintaining 60fps on mobile devices while rendering a full 3D village with lighting and effects required careful optimization: simplifying geometry, using instancing where possible, and deferring non-critical updates.
6. Procedural Audio at Scale
Generating all audio procedurally (ambient wind, chimes, music) rather than using pre-recorded files meant optimizing Web Audio for continuous playback without memory leaks or performance degradation. We learned to clean up oscillators properly and manage the audio context lifecycle carefully.
7. Narrative Flow in an Interactive Medium
Unlike a linear story, players explore in their own order. Some will light all lanterns before the ending; others will rush. We had to design narrative checkpoints and ending conditions that felt meaningful regardless of the sequence. The solution: make the ending depend on all ten lanterns being lit, not on a specific sequence.
Accomplishments that we're proud of
Option A: Technical Focus
We built a fully playable interactive 3D experience in the browser with zero external asset files—all audio is procedurally generated, all visuals are renderer in real-time. We engineered a responsive system that runs smoothly on both desktop and mobile, handling complex state management for narrative progression without ever sacrificing frame rate. The architecture proved flexible enough that adding new memories or adjusting lantern positions requires only data changes, not code refactoring.
Option B: Creative Focus
We created a cohesive emotional experience where every element—from the sparse, haunting landscape to the procedural wind sounds to the voice-like quality of the chimes—reinforces a single mood. We wrote ten distinct memory sequences that can be discovered in any order but feel like a unified narrative. Most proudly, we built something that makes players pause—that creates space for reflection rather than demanding constant interaction.
Option C: Accessibility Focus
We shipped a game that works on desktop, tablet, and phone without compromise. We implemented touch controls that feel natural, managed audio carefully so it works in browsers with strict autoplay policies, and built UI that scales gracefully. We ensured that the story remains compelling even if a player never finds all ten lanterns, and that the ending feels earned rather than arbitrary.
Option D: Design Maturity
We resisted feature creep and shipped a focused experience. Every asset serves the story. Every mechanic has purpose. We learned to say no to scope expansion and yes to polish. The result is a game that feels complete even though it's small—it knows what it is.
What we learned
Option A: Technical Insights
We learned that constraints are features. Limiting ourselves to procedural audio eliminated file bloat and forced us to think deeply about sound design. Building with Vite and React Three Fiber taught us that modern web tooling can deliver experiences that rival native apps. We discovered that managing complex state in React isn't harder in 3D—it's actually cleaner because the component model naturally represents spatial hierarchies.
Option B: Design Insights
We learned that less is more in interactive storytelling. Players don't need dozens of lanterns; they need enough to feel agency without feeling lost. We learned that silence matters as much as sound, and that pacing in an interactive medium is about respecting the player's pace rather than imposing your own. We discovered that ambiguity in narrative can be more powerful than clarity—players fill the gaps with their own memories.
Option C: Player Experience Insights
We learned that context matters above story structure. Players don't remember which lantern they lit first; they remember how it felt. We learned that accessibility isn't a feature—it's essential. A player on mobile deserves the same emotional impact as one on desktop. We discovered that players want to understand the world, not just navigate it; every environmental detail should hint at the story.
Option D: Process Insights
We learned that building something small teaches you more than building something big. Every feature decision has visible consequences. We learned to prototype interactively rather than planning exhaustively. We discovered that playtesting with a diverse group (different ages, devices, comfort levels with games) catches problems that metrics never reveal.
What's next for the-last-lantern/2197
Option A: Narrative Expansion
We want to weave deeper threads between the ten memories. Currently, they're independent; imagine if discovering lanterns in certain sequences unlocked hidden dialogue, or if the ending changed subtly based on which lanterns you'd lit. We're considering a "New Game+" mode where returning players see the world differently—certain details shift, suggesting that memory isn't fixed but layered. We'd love to expand the prologue to make the mystery richer before exploration even begins.
Option B: World Expansion
The village feels alive, but it's small. We want to add new areas—a library with books that tell fragments of the story, a workshop with tools frozen mid-use, a garden slowly reclaimed by nature. Each new location would be home to one or two lanterns, expanding the play time to 30-40 minutes without diluting the experience. We'd introduce subtle environmental puzzles: you might need to find something in one location to activate a lantern elsewhere.
Option C: Technical Enhancements
We want to optimize further for VR/AR experiences. The game is already first-person, so bringing it to a VR headset feels natural. We're exploring WebXR support and spatial audio that changes as the player moves. We'd also like to implement dynamic day/night cycles, weather systems, and more sophisticated lighting that reacts to lantern placement.
Option D: Community & Iteration
We want to make this a living project. We're considering open-sourcing certain components so other developers can build memory experiences in their own villages. We'd love to collect player stories—short accounts of what the lanterns meant to them—and display them on a companion website. We're thinking about a developer toolkit that lets creators build new memories and worlds using our engine.
Option E: Multi-Sensory Direction
We're experimenting with haptic feedback for controllers (the rumble of finding a lantern, the subtle pulse of memory activation). We're exploring scent design partnerships—imagine walking into an archive that smells like old paper and dust. We're interested in commissioning an ambient score that evolves based on which lanterns are lit, so repeat playthroughs genuinely sound different.
Built With
- api
- css3
- git
- html5
- javascript
- jsx
- node.js
- npm
- opencode
- react
- react-native
- reactthreefiber
- three.js
- vercel
- vite7
- web
Log in or sign up for Devpost to join the conversation.