Home/Work/Rosegold XR
INDEPENDENT / WORK IN PROGRESS · 2026

ROSEGOLD
XR

A browser-based 3D Pac-Man with swipe and keyboard controls, and an optional tabletop AR mode.
01 — About the projectScroll to explore ↓
XR
Rosegold XR — the maze placed on a real surface.

Rosegold XR started as a question about how little a game needs: no framework, no backend, just rules, a renderer and a phone.

It is a vanilla TypeScript app. Vite builds it, Three.js draws it, and Zod checks the data that crosses between modules. Play it on a desktop with the arrow keys, on a phone with swipes, or place the board on a table in AR.

The game is about 2,900 lines in total.

Rules that know
nothing about
drawing.

All of the game rules live in one function that holds the state and hands back a small interface. Nothing outside can change that state, because reading it returns copies.

That split keeps the rules testable, and it means the same game can be shown on a flat screen or on a tabletop without changing a line of logic.

RULES

Every tick
does four things.

01Move playerINPUT
02Eat pelletSCORE
03Move ghostSEARCH
04Check contactLIVES

Small details
that make it
play like Pac-Man.

The player's next turn is remembered: if a turn is blocked, the current direction continues, so an early swipe is not lost. Swipes under 24 pixels are ignored so taps do not steer.

The ghost runs a breadth-first search to the player each tick and takes only the first step of the shortest route.

TURNS
01
Rules
02
Maze as text
03
Input
04
Rendering
05
AR placement

One interface,
two AR backends.

Where the browser supports WebXR AR, such as Chrome on Android, the game uses it with hit testing and plane detection. On iOS Safari it falls back to Zappar's camera tracking, loaded only when needed.

Both sit behind the same session interface, so the game code asks for a surface and never needs to know which one answered. The board is a single group, so placing it on a table just moves, rotates and scales that group.

AR
04 / THE PRINCIPLE

Keep the rules small
enough to hold
in your head.

No framework.
No backend.
No excuses.

TypeScript
Strict typing across game, input, render and AR.
Three.js
Instanced meshes: one draw call per wall kind.
Zod
Maze layouts and game state validated at the edges.
WebXR / Zappar
Two AR backends behind one interface.
Vite
Build and dev server.
const step = (): void => {
  if (isGameOver) return;

  const playerMove = moveCharacter({ grid, position: playerPosition, wantedDirection, currentDirection });
  playerPosition = playerMove.position;
  currentDirection = playerMove.direction;
  score += eatPellet({ grid, position: playerPosition });
  ghostPosition = findNextGhostPosition({ grid, ghostPosition, playerPosition });
  // ...contact check
};

The board always uses one fixed maze, scaled to fit the surface. Generating a maze to fit the room is the obvious next step, and the code for it is written but not yet wired in.

Credits

Studio
Independent
Role
Design / Frontend / Game logic
Year
2026
Repository
andymutale/rosegold-xr
NEXT PROJECT

UI LIBERRY

↗
06 / CONTACT

HAVE SOMETHING INTERESTING?