A very small world. A proper little puzzle game.
Guide a small, leaf-headed robot around a world of exactly nine tiles. Push the sun, moon, and star stones onto their matching sockets. Step or push off an edge and arrive on the opposite side of the same row or column.
Twelve authored puzzles introduce the rules gently, then grow into compact spatial challenges. The final puzzle has a shortest solution of 21 moves. There are no timers, lives, ads, accounts, or external runtime services.
- Procedural 3D diorama, original robot and stone models, soft shadows, and animated edge transitions.
- Unlimited undo, restart, shortest-path hints, deadlock detection, and perfect-solution scores.
- Saved unlocks, personal bests, and sound preference in your browser.
- Keyboard, mouse, and touch controls; responsive desktop and mobile layouts.
- Symbols accompany colors, reduced motion is respected, dialogs support the keyboard, and board positions are announced for screen readers.
- A fully playable top-down board automatically takes over if WebGL is unavailable.
- Original synthesized sounds and bundled fonts. Everything is served from the same origin.
Requires Node.js 24 or newer.
npm ci
npm run devOpen the local URL printed by Vite, usually http://localhost:5173. The production build is npm run build; serve it locally with npm run preview.
| Action | Controls |
|---|---|
| Move / push | Arrow keys, WASD, direction buttons, or tap a tile in the robot’s row or column |
| Undo | Z or Backspace |
| Restart | R |
| Hint | H |
| Close a dialog | Escape |
The direction buttons follow the board’s isometric axes. Each stone has its own matching symbol. You can push one stone at a time; you cannot pull. Rocks never move. Moves, stones, and rows wrap on both axes.
The game uses TypeScript, Three.js, and Vite, without a UI framework. Game state is immutable and independent of rendering.
src/
game/
rules.ts Pure movement rules and breadth-first solver
levels.ts Puzzle definitions and tutorial copy
progress.ts Validated, versioned, failure-tolerant browser storage
rules.test.ts Rule invariants and shortest-path checks for every level
scene.ts Procedural models, rendering, picking, and animation
audio.ts Small Web Audio synthesizer, initialized on user interaction
ui/icons.ts Inline vector icons
main.ts Game controller, dialogs, input, and accessible 2D fallback
style.css Responsive interface and reduced-motion behavior
tests/game.spec.ts Desktop/mobile playthroughs and accessibility checks
Cells are numbered row-first from 0 to 8. A level’s goals[i] is the target for initial.stones[i]: identities must be preserved. Add a Level to src/game/levels.ts and its expected shortest path length to the unit test to extend the journey. The interface builds its navigation and puzzle totals from that array.
The offline design tool enumerates solvable starts for an arrangement and reports the most distant candidates:
npm run levels:explore -- --walls=4 --goals=2,5,8npm run check # Rules, all puzzle solutions, TypeScript, production build
npx playwright install chromium
npm run test:e2e # Full desktop/mobile journeys, UI, storage, fallback, motion, accessibilityThe end-to-end suite uses real Chromium with software WebGL and automatically starts its own dev server. To test a deployed build, set GAME_URL to its URL instead. Tests never expose gameplay or solver internals on window.
GitHub Actions runs the same checks before deploying dist/ to GitHub Pages. Pull requests run checks without publishing. Vite uses relative asset URLs, so the build also works under a repository subpath or another static host.
Game code, procedural art, icon drawings, and synthesized sounds: MIT. Three.js is MIT licensed. DM Sans and Fraunces are distributed under the SIL Open Font License; their full notices are included in public/licenses. See THIRD_PARTY_NOTICES.md.
