# RCWeb Maze

![Maze icon](pwa-512x512.png "Maze icon")

**Maze** is a first-person 3D labyrinth for an RCWeb display. The screen shows a torchlit stone maze drawn in WebGL, and a phone running the companion [`maze-c`](../maze-c/appinfo) controller walks the player through it while drawing a live 2D map of the route walked so far.

Open `/maze/?r=<room>` on the display and scan the on-screen QR code to join the same room from a phone.

## The game

Every level is a freshly generated labyrinth. The player starts in one corner and has to find the glowing exit portal set into the outer wall, which is always a long walk away through the corridors. Reaching it clears the level, shows the time and step count, and builds a bigger maze.

- Level 1 is a 9 x 9 grid of corridors. Each level adds a ring of corridors up to a 21 x 21 maze.
- Mazes are carved with a recursive backtracker and then partly braided, so dead ends are opened into loops and there is more than one way around.
- Each level uses a different theme: Catacombs, Ice Vault, Ember Mine, Verdant Ruin, and Void Temple, then the cycle repeats with larger grids.
- Time and steps are tracked for each level and shown on the display and on the phone.

## Movement

Movement is deliberately simple, because the player is steering a first-person camera from a phone.

- The player always walks along the centre line of a corridor, one square at a time.
- Forward and back move smoothly between squares with acceleration, head bob, and a soft stop at the next square centre, so holding forward produces continuous walking rather than a stutter step.
- Turns snap to 90 degrees and are animated over a quarter of a second. Holding a turn button keeps rotating in that direction.
- Turning while walking is allowed: the step in progress finishes along its original corridor while the view rotates, which reads as cutting the corner.
- Walking into a wall gives a short bump, a thud, and a `Wall ahead` message on the phone.

## Lighting and 3D

The maze is drawn with three.js. Everything the player sees is generated at level build time, with no downloaded art.

- Wall panels and corner pillars are drawn as two instanced meshes, so a 21 x 21 maze is still only a handful of draw calls.
- Stone, floor, and ceiling textures are painted onto canvases at runtime, in the palette of the current theme, and reused as bump maps for surface relief.
- The player carries a flickering torch. Its warm point light travels with the camera and casts real shadows on capable displays.
- Wall-mounted braziers and ceiling light shafts are placed around the maze. A pool of four point lights follows the player and lights whichever fixtures are nearest, so the maze can hold dozens of lamps at a fixed lighting cost.
- Exponential fog, drifting dust motes, glowing wall runes, and an animated energy curtain in the exit doorway complete the scene.
- Displays that report a small screen or few CPU cores fall back to a lean path: no antialiasing, no shadows, fewer dust motes, and flat HUD panels.

## Controls

- Phone: `Forward`, `Back`, `Turn left`, and `Turn right` are hold-to-act buttons on [`maze-c`](../maze-c/appinfo).
- Display keyboard: `W`/`S` walk, `A`/`D` turn, `R` builds a new maze. This is mainly for testing without a phone.

## How RCWeb connects the two apps

1. The display and the phone join the same room through `/assets/core/comms.js`. The display publishes a QR code that opens `/maze-c/` with the room already selected.
2. The phone announces itself every two seconds with `maze.registerController`. The first phone to arrive owns the controls; later phones watch the map and take over automatically if the owner leaves or stops checking in for seven seconds. A phone that refreshes keeps its stored controller identity and reclaims the controls.
3. Button presses become `maze.controlDown` and `maze.controlUp` calls carrying the RCWeb client ID, the stored controller identity, and the action name. Held actions stay pressed on the display until their release arrives, so walking and turning can overlap.
4. The display owns the simulation. It sends the maze itself once per level with `mazeControl.receiveMap` as a compact wall bitmask string, then streams position, heading, level, time, steps, and newly walked squares with `mazeControl.receiveState` about twelve times a second, skipping frames where nothing changed. The phone draws only the squares that have been walked, so the map is a record of the route rather than a solution.
5. Every few seconds the display also sends the full list of walked squares, so a phone that joins late or misses an update still draws the correct trail.

The display is authoritative: maze generation, movement, collision, lighting, and level progression all happen in the viewer browser. RCWeb carries control intent one way and map and status updates the other way.

## Screenshots

![Maze exploration after walking forward](screenshot01.png)

![Turning and advancing through the corridors](screenshot02.png)

![A fresh maze generated from the controller](screenshot03.png)
