Inspiration
Most mixed reality games ask you to stand up, clear a play space and pick up controllers. But a lot of headset time happens sitting down: at a desk, on a sofa, during a break. I wanted a game that respects that moment. You sit where you already are, your real desk becomes the board, and your bare hands do everything. It should pass the "airplane seat test" (everything within a two-foot reach) and the "one bus stop test" (a full, satisfying puzzle in a minute or two).
Light puzzles are a natural fit for hands. Pinching a little mirror and dropping it in place feels tactile, and watching a beam snap around a corner gives instant, readable feedback with no text needed.
What it does
Prism Desk puts a small puzzle board on your real table. An emitter fires a beam of light, and your job is to steer it into the receiver by placing mirror tiles on the grid.
- Pinch a mirror from the tray and drop it on the grid. A glowing ring previews exactly which square it will land on, and the beam re-routes live as soon as it snaps in.
- Mirrors lean two ways, so every placement is a choice. Rocks block the light, fixed grey mirrors must be worked around, and purple gems must all be lit before the receiver counts.
- Fewest mirrors wins a Perfect ★. Use more and the game tells you the par, so there is always a reason to replay.
- Eight hand-made levels, from a one-mirror tutorial to a six-mirror finale. Every level is proven solvable by a brute-force solver, and par is checked against it.
- It lives on your desk. On entering mixed reality, the board looks for a detected table near you and settles onto it, keeping its whole footprint on the surface. With no table, it appears at seated desk height in front of you. Pinch the bar behind the board to slide it anywhere.
- Clean in and out. A title screen with a big Start button, saved progress, and a pause message when you take the headset off or open the system menu.
How I built it
Prism Desk is a WebXR app built with Meta's Immersive Web SDK (IWSDK), TypeScript and Three.js, and hosted on GitHub Pages, so there is nothing to install: open the link in the Quest browser and tap Enter XR.
- Puzzle core: a pure, renderer-free module traces the beam across the grid (reflections, blockers, gems, loop guard). Because it has no 3D dependencies, a small Node script can brute-force every level, prove it solvable, and verify its par. A companion generator searched for layouts when hand-designed levels turned out to be unsolvable.
- Game system: an IWSDK entity-component system drives the board, tiles, buttons, beam and level flow. All geometry is procedural (under 100 simple meshes even on the largest level, no shadows, no texture downloads) and nothing is allocated per frame, to stay well inside the frame budget on Quest.
- Hands-first interaction: buttons are pokeable with a fingertip. Tiles are grabbed from the true pinch point, the midpoint of the thumb tip and index tip, read straight from the WebXR hand-joint poses. The board's move handle uses the SDK's grab system.
- Real-world placement: IWSDK scene understanding provides the room's detected planes. The game picks a horizontal surface at desk height (or one labelled table or desk) within reach, then shifts the board so its footprint stays on that surface.
- Feedback: an additive glowing beam with flares at each bend, a spark burst and receiver flare on solve, tiles that glow while held and pop when placed, and synthesized Web Audio sound effects (no audio files at all).
- Testing without a headset: I developed and tested in IWER, the SDK's browser-based Quest 3 emulator, driving emulated hands with real pinch and poke gestures and checking results in the ECS state. The demo video was recorded the same way, in the emulator's furnished home-office room.
Challenges I ran into
- Grabbing the wrong tile. The SDK's near-grab measures from the palm, not the fingertips. When a right hand reached across the tray for the leftmost mirror, its palm sat over the neighbouring tile, which got picked up instead. I replaced tile grabbing with my own pinch-point grab using WebXR hand joints, and now the tile between your fingers is the one you get.
- Puzzles that could not be solved. Three of my first eight hand-designed levels turned out to be impossible. That is why every level is now verified by a solver, with a generator to find replacements that require a specific number of mirrors.
- Rooms I have never seen. Tables come in all sizes and some rooms have none. The board looks for a table within seated reach, keeps its footprint on it, and falls back to a sensible desk-height position.
- Fat fingers. Early on, Next and Reset sat side by side, and a finger reaching for one pressed the other on the way. They now sit on opposite sides of the board.
Accomplishments that I'm proud of
- A complete loop (title, onboarding, eight levels, ratings, replay) that is fully playable with bare hands while seated.
- Placing content on a real detected table, so the room actually changes the experience.
- Puzzles that are provably solvable, with a provable par.
- A tiny, fast build that runs from a single link.
What I learned
- Hands-first design is about tolerances. Pinch targets, spacing between objects, and where the grab is measured from matter far more than with controllers.
- Keeping puzzle logic pure pays off: it made automated verification and level generation almost free.
- An emulator with scriptable hands is good for catching real interaction bugs early, even before touching a headset.
What's next for Prism Desk
- Real-device tuning: hand-tracking feel, comfort and performance on Quest hardware.
- A daily puzzle and more level packs, with new pieces such as beam splitters, colour filters and portals.
- A level editor built into the board, so players can design and share puzzles.
- Shared desks: two players solving the same board together with shared spatial anchors.
- Target launch: March 2027 on the Meta VR Store, packaged as an installable web app.
Built With
- github
- immersive-web-sdk
- iwer
- javascript
- meta-quest
- three.js
- typescript
- vite
- web-audio-api
- webxr