docs/adr/ADR-047-psychohistory-observatory-visualization.md
Accepted (Implemented)
2026-03-04
The project has a functional tabbed dashboard UI (ui/index.html) with existing Three.js components (body model, gaussian splats, signal visualization, environment). While effective for monitoring, it lacks a cinematic, immersive visualization suitable for demonstrations and stakeholder presentations.
We need an immersive Three.js room-based visualization with practical WiFi sensing data overlays — human wireframe pose, dot-matrix body mass, vital signs HUD, signal field heatmap — powered by ESP32 CSI data (demo mode with live WebSocket path).
ui/observatory.html is a standalone full-screen entry point, separate from the tabbed dashboard. Linked via "Observatory" nav tab in ui/index.html. No build step — vanilla JS modules with Three.js r160 via CDN importmap.
Instead of abstract holographic panels, the observatory renders a practical room scene with:
| Element | Implementation | Data Source |
|---|---|---|
| Human wireframe | COCO 17-keypoint skeleton, CylinderGeometry tube bones, SphereGeometry joints with glow halos | persons[].position, vital_signs.breathing_rate_bpm |
| Dot-matrix mist | 800 Points with per-particle alpha ShaderMaterial, body-shaped distribution | persons[].position, persons[].motion_score |
| Particle trail | 200 Points with age-based fade, emitted from moving person | persons[].position, persons[].motion_score |
| Signal field | 400 floor-level Points with green→amber color ramp | signal_field.values (20×20 grid) |
| WiFi waves | 5 wireframe SphereGeometry shells, AdditiveBlending, pulsing outward | Always-on animation from router position |
| Router | BoxGeometry body, 3 CylinderGeometry antennas, pulsing LED, PointLight | Static scene element |
| Room | GridHelper floor, BoxGeometry wireframe boundary, reflective MeshStandardMaterial floor, furniture (table, bed) | Static scene element |
Glass-morphism HTML panels overlaid on the 3D canvas:
Full customization with localStorage persistence and JSON export:
| Tab | Controls |
|---|---|
| Rendering | Bloom strength/radius/threshold, exposure, vignette, film grain, chromatic aberration |
| Wireframe | Bone thickness, joint size, glow intensity, particle trail, wireframe color, joint color, aura opacity |
| Scene | Signal field opacity, WiFi wave intensity, room brightness, floor reflection, FOV, orbit speed, grid toggle, room boundary toggle |
| Data | Scenario selector (auto-cycle or fixed), cycle speed, data source (demo/WebSocket), WS URL, reset camera, export settings |
Four auto-cycling scenarios (30s default, configurable) with 2s cosine crossfade:
| Scenario | Description |
|---|---|
empty_room | Low variance, no presence, flat amplitude, stable RSSI -45dBm |
single_breathing | 1 person, breathing 16 BPM, HR 72 BPM, sinusoidal subcarrier modulation |
two_walking | 2 persons, high motion, Doppler-like shifts, moving signal field peaks |
fall_event | 2s variance spike at t=5s, then stillness, fall flag, confidence drop |
Data contract matches SensingUpdate struct from the Rust sensing server. Live WebSocket connection configurable in settings dialog.
EffectComposer chain: RenderPass → UnrealBloomPass → custom VignetteShader
| Role | Color | Hex |
|---|---|---|
| Background | Deep dark | #080c14 |
| Primary wireframe | Green glow | #00d878 |
| Warm accent | Amber | #ffb020 |
| Signal | Blue | #2090ff |
| Heart / joints | Red | #ff4060 |
| Alert | Crimson | #ff3040 |
| Decision | Rationale |
|---|---|
| Standalone page vs tab | Full-screen immersion, independent loading |
| Room-based vs abstract panels | Practical spatial context for WiFi sensing data |
| Vanilla JS + CDN, no build step | Matches existing ui/ pattern, served as static files by Axum |
| Custom ShaderMaterial for mist | Per-particle alpha, body-shaped distribution, AdditiveBlending |
| CylinderGeometry tube bones | Visible at any zoom vs thin Line geometry |
| COCO 17-keypoint skeleton | Standard pose format, 16 bone connections |
| localStorage settings | Persistent customization without server round-trip |
| Adaptive quality | 3 levels, auto-switches based on FPS measurement |
| Key | Action |
|---|---|
A | Toggle autopilot orbit |
D | Cycle demo scenario |
F | Toggle FPS counter |
S | Open/close settings |
Space | Pause/resume data |
| File | Purpose |
|---|---|
ui/observatory.html | Full-screen entry point with HUD overlay + settings dialog |
ui/observatory/js/main.js | Scene orchestrator (~1,100 lines): room, wireframe, mist, trails, settings, HUD, animation loop |
ui/observatory/js/demo-data.js | 4 scenarios with cosine crossfade, setScenario/setCycleDuration API |
ui/observatory/js/nebula-background.js | Procedural fBM nebula + star field background sphere |
ui/observatory/js/post-processing.js | EffectComposer: UnrealBloom + VignetteShader (chromatic, grain, warmth) |
ui/observatory/css/observatory.css | Foundation color scheme, glass-morphism panels, settings dialog, responsive |
ui/index.html | Modified: added Observatory nav link |
SensingUpdate contract enables seamless live WebSocket switchui/components/scene.js — Three.js scene patternui/components/gaussian-splats.js — ShaderMaterial patternui/services/sensing.service.js — WebSocket data contract