Newbury Exhibit
Shared, view-only AR experience for a physical Newbury LEGO display (max 700 × 1500 mm). Multiple phones/tablets see the same ghosts, in the same real-world spots on the build, each from their own viewpoint — the "Hidden Side" made visible to a room full of people at once.
This is a sibling project to newbury-nights (the single-player hunt). The architecture is deliberately different: instead of ghosts spawning around one player, ghosts live at fixed world anchors on the build, and every device localises itself against printed/LEGO markers so they all agree on where those anchors are.
How it fits together
physical marker ──► world origin ──► anchors ──► ghosts ──► broadcast ──► every phone
(Newbury Crest) (one corner) (scene.json) (motion) (WebSocket) (renders identically)
- Localisation. Each device's camera detects an ArUco 4×4 marker of known size and known
world position. From the marker's four corners it solves where the device is in the build's
coordinate frame. (Proof of concept:
public/ar-test.html.) - Authoritative state.
server/sync-server.jsruns one motion loop and broadcasts ghost poses at 20 Hz over WebSocket (/sync). The server is the single source of truth, so all viewers see synchronised motion. - Rendering. Each device renders its own scene locally.
public/js/viewer.jsis the desktop "god-view" for authoring/testing; the phone AR client (next milestone) will render ghosts at anchor world-positions seen through the camera.
All coordinates are in millimetres, world origin at the front-left corner of the baseplate.
This maps 1:1 to the physical build, so tape-measure values drop straight into data/scene.json.
Markers — "Newbury Crests"
The markers are ArUco 4×4 (dictionary ARUCO_4X4_1000), chosen because a 4×4 data grid maps
cleanly onto LEGO tiles. Physically each marker is a 6×6 grid: a 1-cell black quiet border
around the 4×4 code.
Design decision — colour wheel as frame, not code. ArUco detection is brightness-based (each cell reads black or white), which is exactly what makes it robust under bad lighting and shallow angles. Encoding the Red/Yellow/Blue Hidden Side colours into the code cells would make it pretty but fragile. So:
- Detector core (6×6): black & white flat tiles — this is what the camera reads.
- Colour-wheel ring: a decorative Red/Yellow/Blue border around the core, in the zone the detector ignores. On-theme for humans, invisible to the maths.
See markers/ for printable SVGs, per-anchor LEGO build maps, and crest-01_buildplan.svg
(a visual of core + colour ring). Print a marker now to test robustness before committing to a build.
Marker → anchor mapping
markers/manifest.json maps each physical ArUco id to a scene anchor. The fiducialId in
data/scene.json must match the arucoId of the marker you place there.
| Crest | ArUco id | Ward (placeholder) |
|---|---|---|
| crest-01 | 0 | North-West |
| crest-02 | 1 | North-East |
| crest-03 | 2 | Town Square |
| crest-04 | 3 | South-West |
| crest-05 | 4 | South-East |
Proof of concept: ar-test.html
Camera → marker detection → a ghost sprite locked to the marker, bobbing above it. No server needed for this page. Use it to test detection robustness on your iPhone 15 Pro.
Must be served over HTTPS for the camera to work on iOS. Behind your existing nginx that's already handled; for the exhibit it'll sit at the same kind of URL as newbury-nights.
Drop an animated WebP at public/assets/ghost_blue.webp to use a real sprite; otherwise it draws
a procedural blue "Sad" wisp.
Run
npm install
npm start # PORT defaults to 34034
# open http://localhost:34034/ar-test.html (camera needs HTTPS on phones)
# open http://localhost:34034/ god-view viewer (viewer.js)
Env: PORT (default 34034), TICK_HZ (default 20).
Status / roadmap
- Authoritative sync server (hover + patrol motion solvers, 20 Hz broadcast)
- God-view desktop viewer
- Scene + anchor + spawn data model (mm, corner origin)
- ArUco 4×4 marker system + LEGO build maps ("Newbury Crests")
- AR PoC: camera + detection + ghost locked to marker
- Phone AR client: render anchored ghosts through the camera using marker pose
- Calibration page: scan each crest, record its measured world position
- Per-ghost colour/sprite wiring from the enriched roster
- Multi-device soak test on the real build under exhibition lighting
Licence
MIT (project). Vendored js-aruco2 is MIT (Damiano Falcioni / Juan Mellado); ArUco dictionary
data is 3-clause BSD (OpenCV). See headers in public/vendor/.