An interactive spatial-rotation game in the style of the Purdue Spatial Visualization Test (PSVT:R), plus the geometry pipeline used to build and verify it.
▶ Play it here: https://angusforbes.github.io/Rotatron/
The game is a single self-contained index.html — no build step or server needed — served by
GitHub Pages from this repo. You can also just open the file locally in a browser.
Each level shows an example block and the same block after a rotation. You work out the rotation, apply it mentally (or with the on-screen controls) to a different puzzle block, and click which of five options it becomes.
- 20 levels. Level 1 is a real printed puzzle whose shapes were measured from the scan. Levels 2–10 use voxel blocks; 11–15 add slanted faces; 16–20 combine through-holes, large cutouts and slants.
- Every level is verified. Each shape has no rotational symmetry (all 24 poses are distinct), so the example rotation is uniquely determined. Each level's five options are five distinct poses with exactly one correct, and no option equals the puzzle block's starting pose.
- Controls. Six 90° turns (tip / spin / roll, each way) about fixed screen axes, with icons showing the axis and direction of each; rotate both blocks or either alone; reset; and a hint that plays the rotation.
- Rendering. Colors (faces tinted by their starting direction, slants blended), grayscale shading, or outlines only. Outlines are exact creases rendered as antialiased tube geometry with an adjustable width slider.
- Answers are graded only when you click — the option panels never react to your rotations before you commit, so the controls let you practice without giving the answer away.
See also HANDOFF.md for architecture, workflows, verification guarantees and pitfalls.
The example block is shown before and after a rotation. Work out the rotation, then apply the same one to the puzzle block and pick the matching option. In Colors mode the faces are tinted by the direction they face at the start, and the tints travel with the block — so you can follow where every face goes.
(Level 16: roll counter-clockwise, then tip toward you.)
Blocks range from simple voxel cuts to slanted faces, through-holes and channel cutouts. The
images on this page are produced by pipeline/render_modes.py with the same projection, colors
and crease rules as the game.
index.html the game (single self-contained file)
tools/rotation_tool.html earlier verification tool for the original printed puzzle
pipeline/ geometry & verification code (Python, plus one JS module)
polyshapes.py convex-union solids: exact faces, crease detection, per-vertex
outline shifts, rotational-symmetry check
slant_shapes.py shape definitions for levels 11–15
combo_shapes.py shape definitions for levels 16–20 (holes + cutouts + slants)
hiddenline.py exact hidden-line projection (used to overlay predictions on scans)
raycast.py, csgcast.py raster previews for voxel and CSG solids
axis_icon.js generator for the axis / rotation-arrow button icons
build_mesh_levels.py rebuilds levels 11-20 (meshes + verified option sets) into index.html
check_index.py pre-flight: syntax, stubbed execution, level-data audit
render_modes.py renders README images (three modes, level walkthrough, gallery)
docs/ images used by this README
data/ level data (rotations, option poses) and the 24 rotation matrices
verification/ overlay and preview images
The original puzzle's drawings were reconstructed by extracting line segments from the scan,
lifting vertices into 3D using the isometric axis directions, and testing candidate solids by
projecting exact hidden-line drawings back onto the scans — every predicted line has to land on
ink (verification/full_verification.png).
For generated levels, shapes are built as unions of convex pieces (intersections of half-spaces). True creases are found by probing the solid in a ring around each candidate edge; candidates include face–face intersections between pieces, so a chimney meeting a roof gets its line. Extents are exact because edges are split at every plane crossing before testing. Rotational symmetry is checked by comparing occupancy on a rotation-closed sample grid across all 24 orientations.
Define two shapes — a voxel rule string for blocky shapes (see EXTRA_LEVELS in index.html)
or a Shape of convex pieces for slanted ones (see pipeline/combo_shapes.py) — choose a
step list such as ['xp','yp'], and let the generator produce distractors. The generator
rejects any option equal to the correct pose or to the identity and requires five distinct
poses. Run the symmetry check on both shapes first; a notch placed on a shape's symmetry axis
will not break the symmetry.




