# 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) ``` 1. **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`.) 2. **Authoritative state.** `server/sync-server.js` runs 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. 3. **Rendering.** Each device renders its own scene locally. `public/js/viewer.js` is 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 ```bash 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 - [x] Authoritative sync server (hover + patrol motion solvers, 20 Hz broadcast) - [x] God-view desktop viewer - [x] Scene + anchor + spawn data model (mm, corner origin) - [x] ArUco 4×4 marker system + LEGO build maps ("Newbury Crests") - [x] 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/`.