Skip to content

About

Browser geometric-optics puzzle game with mirrors, prisms, and lenses.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Photon Maze

Photon Maze is a polished, browser-based geometric-optics puzzle game. Players place, move, and rotate mirrors, triangular prisms, convex lenses, and concave lenses to guide live simulated light into spectral receivers. The complete local campaign contains 12 experiments across Reflection, Refraction, and Focus, with progressive hints, stars, persistent records, responsive touch controls, procedural audio, and accessibility preferences.

Photon Maze title screen

Photon Maze gameplay

Features

  • 12 data-driven levels in three chapters, each with three progressive hints.
  • Verified authored solutions for every level; untouched levels are also checked against accidental completion.
  • Deterministic, UI-independent TypeScript optics engine.
  • Finite double-sided mirrors with reflectivity loss.
  • Convex polygon prisms using Snell's law, spectral dispersion, and total internal reflection.
  • Positive and negative thin lenses with bounded paraxial behavior.
  • Finite-aperture RGB receivers driven only by traced ray contributions.
  • Continuous beam retracing during movement and rotation.
  • Dragging, rotation ring, wheel, keyboard nudging, inventory placement, return, reset, undo, and redo.
  • Complete title, level select, manual, settings, pause, completion, and campaign-finale states.
  • Local progress, stars, best move counts, best times, and settings persistence.
  • Procedural Web Audio ambience and cues with independent master, ambience, and effects levels.
  • Reduced motion, high contrast, color assistance, visible focus, keyboard navigation, and large touch controls.
  • Developer author mode with optical IDs, authored-solution application, and current-state JSON export.

Requirements and setup

  • Node.js 20.19+ or 22.12+ (Node.js 24 is also supported)
  • npm 10+
  • A modern browser with Canvas or WebGL support
npm install
npm run dev

Vite prints the local development URL. No backend, login, API key, remote font, or network service is required after dependencies are installed.

Commands

npm run dev          # Start the Vite development server
npm run build        # Type-check and create the production build
npm run preview      # Preview the production build
npm run test         # Run Vitest in watch mode
npm run test:run     # Run all tests once
npm run typecheck    # Run strict TypeScript checks
npm run lint         # Run ESLint with zero warnings allowed
npm run format       # Format the repository with Prettier
npm run format:check # Check formatting without writing

Controls

Desktop

Action Input
Select or move Click / drag an optical element
Continuous rotation Drag the selected element's rotation ring
Rotate by 5° Mouse wheel, Q, or E
Snap rotation to 15° Hold Shift while using Q / E or the ring
Nudge Arrow keys
Larger nudge Shift + arrow keys
Undo / redo Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, or Ctrl/Cmd+Y
Return inventory item Delete / Backspace
Reset R or the reset button
Hint H or the hint button
Pause / close Escape

Bench-element buttons provide a keyboard-accessible way to select placed optics.

Touch

Tap an inventory item to place it, drag elements directly on the board, and use the visible Rotate left, Rotate right, and Return controls after selection. Interactive HTML controls are at least 44 CSS pixels. On narrow screens the board remains fitted while the receiver, inventory, and selected-instrument panels move below it.

Optical model

The simulation lives under src/optics/ and does not depend on Phaser or the DOM. Renderer pixels never determine receiver state.

  • Every ray carries a normalized direction, RGB spectral band, intensity, refractive medium, stable ID, and depth.
  • Every segment records its start and end, collision type and ID, band, intensity, and termination reason.
  • Nearest-hit selection uses stable type/ID tie-breaking.
  • Epsilon offsets, a 48-interaction depth cap, intensity cutoff, quantized loop detection, and invalid-vector checks guarantee bounded traces.
  • Mirrors are finite and double-sided. Reflection uses the standard vector law and 98% reflectivity.
  • White light is represented by three co-located RGB bands. They overlap visually as white and separate when refracted.
  • Prisms use oriented-edge Snell refraction and total internal reflection. The refractive indices are deliberately exaggerated to 1.49 / 1.52 / 1.56 for red, green, and blue so dispersion remains readable on a compact board. This is a gameplay model, not precision color science.
  • Lenses change direction at their center plane using thetaOut = thetaIn - height / focalLength. Angles and very short focal lengths are bounded to keep the paraxial approximation stable.
  • Receivers accumulate actual ray contributions by band, apply a small numerical tolerance, and require both spectral and total-intensity thresholds.

Architecture

src/
  app/
    PhotonMazeApp.ts       HTML UI, state transitions, completion flow
    persistence.ts        Versioned save loading and safe migration
  game/
    audio/AudioEngine.ts  Procedural Web Audio
    rendering/LabScene.ts Phaser board/input/rendering layer
    GameSession.ts        Optical state, history, moves, validation
  levels/
    campaign.ts           12 typed campaign definitions
    schema.ts             Validation and state-to-scene conversion
  optics/
    geometry.ts           Pure vector and intersection helpers
    intersections.ts      Deterministic world collision ordering
    reflection.ts         Mirror reflection
    refraction.ts         Snell refraction and TIR
    thinLens.ts           Bounded paraxial lens transform
    receivers.ts          Spectral accumulation and evaluation
    tracer.ts             Bounded deterministic ray tracer
  ui/
    icons.ts              Inline SVG icon system
    styles.css            Responsive visual system and access modes

GameSession is the explicit boundary between authored content, the pure tracer, Phaser rendering, and the HTML interface. Optical changes retrace immediately; unchanged frames reuse the previous result.

Level data and authoring

Each LevelDefinition includes metadata, a fixed board, emitters, fixed and movable objects, inventory, par moves, three hints, an elegance goal, and a test-only authored solution. validateCampaign() runs when the campaign module loads and reports useful level IDs and validation messages.

To inspect author mode, start the game with:

http://localhost:5173/?debug=1

Author mode shows object IDs and exposes Apply solution and Copy state JSON controls. Copy the formatted element state into a level's solution data, then run npm run test:run to confirm every receiver is activated by real simulation. The authored solution is never shown in normal play.

When adding a level:

  1. Add a typed entry to src/levels/campaign.ts with stable object IDs.
  2. Keep placement clearances and receiver apertures forgiving enough for human manipulation.
  3. Add three reasoning-focused hints.
  4. Add a reachable solution state and reasonable parMoves.
  5. Run campaign solution tests and manually inspect the level.

Save data

Progress is stored as versioned JSON under the photon-maze-save local-storage key. The save includes the highest unlocked level, onboarding state, level records, and settings. Values are type-checked and clamped while loading; corrupt or incompatible content falls back safely to a fresh save. Settings includes a clearly confirmed Reset Progress action.

The game remains playable if local storage is blocked; persistence simply becomes session-only.

Accessibility

  • Native buttons, inputs, headings, labels, dialogs, and live status regions.
  • Visible keyboard focus and keyboard-only gameplay manipulation.
  • High-contrast mode.
  • Reduced-motion mode that collapses animated transitions.
  • Color assistance with persistent numerical R/G/B receiver labels.
  • No receiver requirement is represented by rendered color alone.
  • Touch targets are at least 44 CSS pixels.
  • No rapid flashing; screen shake is off by default.
  • Settings and help overlays preserve the underlying puzzle state.

Static deployment

The Vite base is relative, so the production directory can be hosted at a domain root or subdirectory:

npm run build

Upload the contents of dist/ to any static host, such as GitHub Pages, Netlify, Cloudflare Pages, an object-storage static site, or a local HTTP server. No server-side routing or environment variables are required.

Verification

The test suite covers vector math, finite ray/segment and ray/circle intersections, stable nearest-hit ordering, mirror angles, bidirectional Snell refraction, total internal reflection, positive/negative/rotated/reverse thin-lens travel, receiver truth, wall absorption, save migration, campaign schema, and all 12 authored level solutions.

The final project was also inspected in a real browser at desktop and 390×844 mobile viewports. The title, tutorial, Level 1 completion, Level 2 inventory placement, touch controls, undo, redo, reset, settings persistence, save reload, level unlocking, and browser console were checked.

Credits

  • Phaser 3 — game loop, scenes, input, scaling, and rendering.
  • Vite — development and production build tooling.
  • TypeScript — strict typed implementation.
  • Vitest — unit and campaign validation tests.

All game visuals, SVG icons, interface styling, level data, and audio are original or procedural. No third-party art or audio assets are included.

About

Browser geometric-optics puzzle game with mirrors, prisms, and lenses.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages