ultimatedarktowerdisplay
v0.9.0
Published
Composable text, 2D, and 3D renderers for Return to Dark Tower tower state, with optional skull physics.
Maintainers
Readme
What this is, what it isn't
This package is the visual layer. It does not open a BLE connection, decode packets, or construct TowerState objects. Pair it with ultimatedarktower (UDT) for the BLE side, or feed it hand-built states for testing and demos.
The physical tower talks BLE to ultimatedarktower, which decodes packets into a TowerState. This package consumes that state and renders it as any combination of a text readout, a 2D SVG side view, and a 3D Three.js model.
flowchart LR
Tower[Physical tower] -- BLE --> UDT[ultimatedarktower]
UDT -- TowerState --> Display[TowerDisplay]
Display --> Readout[readout<br/>DOM text]
Display --> Side[side-view<br/>SVG]
Display --> Three[3d-view<br/>Three.js]
Readout --> DOM[(your DOM)]
Side --> DOM
Three --> DOMFor the full mental model see docs/ARCHITECTURE.md.
Install
npm install ultimatedarktowerdisplay ultimatedarktowerultimatedarktower is a peer dependency. For the 3D renderer also install three and gsap (peer dependencies). For optional skull physics see docs/PHYSICS.md.
Quick start
The recommended entry point is TowerRenderView — a single class that wraps a TowerDisplay with sensible 3D defaults and optional header chrome (title, subtitle, status badges, action row).
import { TowerRenderView } from 'ultimatedarktowerdisplay';
import towerModelUrl from 'ultimatedarktowerdisplay/dist/3d/assets/tower.glb?url';
import { createDefaultTowerState } from 'ultimatedarktower';
const container = document.getElementById('tower');
if (!container) throw new Error('Missing #tower container');
const view = new TowerRenderView({ container, modelUrl: towerModelUrl });
view.applyState(createDefaultTowerState());
// Later, when a new state arrives:
// view.applyState(nextState);
// Tear down:
// view.dispose();<div id="tower"></div>TowerRenderView accepts every TowerDisplay option (renderers, lighting, camera, audio, callbacks). Advanced 3D config that isn't forwarded on the facade is reachable via view.display.* and view.view3D. The default renderer is '3d-view'; pass renderers: ['readout', 'side-view'] (or any subset) to opt out of the 3D pipeline.
Composable alternative
If you'd rather wire renderers yourself — or skip the .trv-root wrapper entirely — instantiate TowerDisplay directly:
import { TowerDisplay } from 'ultimatedarktowerdisplay';
const display = new TowerDisplay({ container });
display.applyState(createDefaultTowerState());That renders the default composition: a text readout plus a 2D side view.
Renderers
| Capability | readout | side-view | 3d-view |
| ------------------------ | -------------------- | ------------------ | -------------------------------------------------- |
| Rendering tech | DOM text grid | Inline SVG | Three.js + WebGL2 |
| Shows LED layers | All 6, all 4 sides | One side at a time | On the 3D model |
| Shows drum positions | Numeric + glyph | Rotated SVG | Rotating meshes |
| Shows audio info | Sample name + volume | No | Plays the sample (bundled default pack, swappable) |
| Shows beam + skull count | Yes | No | No |
| Side-aware | No | Yes | Yes |
| Clickable seals | Optional | Yes | No (clicks land in 2D) |
| Animations | None | LED tweens | Full (LEDs, drums, bloom) |
| Bundle cost (rough) | <5 KB gzip | <10 KB gzip | ~150 KB gzip + 22 MB GLB + 20 MB audio |
Both TowerRenderView and TowerDisplay accept any subset of ['readout', 'side-view', '3d-view'] via the renderers option. TowerRenderView defaults to '3d-view'; TowerDisplay defaults to ['readout', 'side-view']. Full comparison and per-renderer details in docs/RENDERERS.md.
Where to go next
- First integration → docs/GETTING_STARTED.md — prerequisites,
TowerStateshape, framework patterns, UDT wiring. - Mental model → docs/ARCHITECTURE.md — data flow, composition, lifecycle, subsystem map.
- Pick a renderer → docs/RENDERERS.md — feature matrix and per-renderer deep dives.
- Explore the demo → docs/EXAMPLE.md — guided tour of every panel in
example/. - Full API reference → docs/API.md — every public class, method, option, and type.
- Tune the 3D scene → docs/LIGHTING.md — three-point rig, bloom, skybox, ground disc, tuning recipes.
- Add skull physics → docs/PHYSICS.md — opt-in subpath with Rapier-driven dynamics.
- Build a scene plugin → docs/SCENE_PLUGINS.md — own your own 3D content (boards, tokens, effects) on the generalized seam.
- Author LED sequences → docs/SEQUENCE_AUTHORING.md — JSON schema, every track kind.
- Run in Electron → docs/ELECTRON.md — BrowserWindow setup, CSP, BLE picker.
- Stuck? → docs/TROUBLESHOOTING.md — predictable failure modes with fixes.
For the full documentation index see docs/README.md.
Development
npm install
npm run dev:example # Vite dev server + open example/index.html
npm run typecheck
npm run lint
npm test
npm run build
npm run ci # full pipeline (typecheck + lint + test + build)See CONTRIBUTING.md for the development workflow and release process.
License
MIT. See LICENSE.
