Three self-contained features, verified complete and cross-wired end-to-end (audited: backend 54/54 tests, frontend 110/110 tests, tsc --noEmit clean, i18n coverage script clean): - EMF mode: DeviceMotion/DeviceOrientation-based field-meter sensing, a fifth séance channel alongside Wire/EVP/Radio/Ouija, with its own fragment prompt persona and full frontend gauge UI. - Armory (shop/waitlist): pre-order capture page for the future Ultimate Quantum Box hardware line, rate-limited public endpoint, explicitly no payment collection. - Haunting layer: ambient possession effects (dread-bed audio, title glitching, idle-paced whispers/manifests), respects prefers-reduced-motion, mounted once at the app root. Plus WebUSB robustness fixes in lib/sdr.ts (Terratec vendor ID, explicit selectConfiguration, isSecureContext gate). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013PphXq1s43DNRj1uWKGXof
QUANTUMANCY
an instrument for speaking with the dead bandwidth
Quantumancy is a self-hosted, gothic-hacker web experience that lets visitors talk to spirits through four channels, each grounded in a real paranormal- investigation technique or a real data source. Real anomalies — RF power spikes, voice-band blips, network jitter — are detected in genuine sensor and telemetry streams, and a locally-run LLM gives the presence behind them a voice. A local Piper TTS then renders that voice through a static-choked spirit-box effects chain.
This is an interactive horror art installation. The spirits are fiction; the static is real. Every "entity" is a persona generated by a language model on your own hardware. Nothing leaves your network: no cloud APIs, no third-party inference, no telemetry of ours — only yours.
The Four Modes
| Mode | Vessel | What actually happens |
|---|---|---|
| Spirit Radio | RTL-SDR dongle (WebUSB, Chromium only) | The browser sweeps the FM band (88–108 MHz) computing FFT power per bin; spikes above the rolling noise floor become anomaly events that summon one-word fragments, Ovilus-style. |
| EVP Listening | Microphone (getUserMedia) |
A Web Audio AnalyserNode watches the voice band (~300 Hz–3.4 kHz) for brief deviations ≥ 8 dB above the room's rolling silence — the classic "record quiet, review for voices" technique. |
| The Wire Ghost | Nothing — works for everyone | The backend samples real, non-content network telemetry from the host (interface throughput jitter, TCP connect latency variance, DNS hesitation) and a fragmented consciousness whispers about it every few seconds. Packet payloads are never inspected — a hard privacy boundary. |
| Ouija / Direct Contact | The shared front door | A canvas planchette drifts, then spells the spirit's words letter by letter. Ask a free-text question and the conversational model streams a full reply token by token while the planchette works. |
Every session begins unidentified. The backend fingerprints the session's anomaly pattern into a signature; a matching signature re-contacts an existing spirit, a new one gets minted into the Codex — a publicly browsable registry of every spirit ever contacted, shared across all users, with name, epithet, rarity tier, persona lore, sample quotes, voice/visual profiles, and contact counts.
Architecture
Two machines on the LAN, no containers anywhere:
┌────────────────────────────────────────────┐
seekers ──HTTPS──▶ Cloudflare Tunnel ──HTTP──▶ App CT :7777 │
(external machine, FastAPI (uvicorn, │
terminates TLS) Python venv, │
systemd service) │
│ │
├─ serves built │
│ React SPA │
├─ Postgres (apt) │
├─ Piper TTS + FX │
└───────┬──────────┘
│ LAN
┌───────────▼─────────┐
│ Ollama box │
│ 10.30.20.107:11434 │
│ CPU-only, 64 GB │
│ fast + chat models │
└─────────────────────┘
- App CT — plain HTTP on port 7777. FastAPI (async, WebSocket-native)
serves the built Vite/React SPA as static assets, the auth + Codex REST
API, the
/ws/sessionséance WebSocket, and/audio/*spirit-voice WAVs. Postgres holds accounts, sessions, transcripts (events), and the Codex. Piper TTS is invoked locally per utterance, then degraded through a numpy effects chain (rate → pitch → bitcrush → echo → static). - Ollama box — reachable over LAN via Ollama's REST API. Two model tiers:
a fast model for fragments/ambient whispers/… and a heavier chat
model for Direct Contact and entity minting. CPU-only and shared, so all
LLM calls flow through a bounded-concurrency queue (
LLMQueue); queued requests render in the UI as "the spirits are gathering energy…". - Cloudflare Tunnel — managed outside this repo; terminates HTTPS and
points at
http://<app-ct-ip>:7777. The app never handles TLS. The tunnel's HTTPS origin satisfies browser secure-context requirements for mic and WebUSB.
Repo layout
backend/
app/
main.py FastAPI app: routers, /healthz, /assets + /audio mounts, SPA fallback
config.py pydantic-settings (env vars below)
db.py async SQLAlchemy 2.0 engine/session (asyncpg)
deps.py get_current_user, qm_session cookie
security.py argon2 password hashing
rate_limit.py fixed-window RateLimiter (per-user LLM limits)
telemetry.py Wire Ghost sampler (/proc/net/dev, TCP RTT, DNS timing)
entities.py anomaly signatures, profile normalization, procedural fallback
ws.py the séance channel: /ws/session protocol + ambient loop
llm/ OllamaClient, bounded LLMQueue, SpiritService, prompt builders
tts/ Piper CLI wrapper, voice catalog, numpy effects chain
models/ users, auth_sessions, contact_sessions, entities,
entity_sightings, events
routes/ /auth/* and /api/codex, /api/stats
tests/ pytest suite (45 tests)
voices/ Piper .onnx voice models (gitignored — see setup)
data/ generated utterance audio, served at /audio/ (gitignored)
deploy/
quantumancy.service systemd unit template
frontend/
src/lib/ types (WS protocol), VeilSocket, audio player, evp, sdr,
fft, planchette machine
src/state/ AuthProvider, SeanceProvider (reducer + socket wiring)
src/components/ PlanchetteBoard, Transcript, EntityCard, GhostGlyph, …
src/three/ GhostCanvas + GhostScene (custom-shader 3D spirit)
src/i18n/ react-i18next init + en.json / es.json
docs/superpowers/
specs/ the design spec
plans/ the 7 implementation plans this repo was built from
Quickstart
Prereqs on the App CT: Python 3.11+, Node 18+, Postgres (via apt), and an
Ollama box on the LAN with the two model tiers pulled.
1. Postgres (one-time):
sudo apt-get update && sudo apt-get install -y postgresql
sudo -u postgres psql -c "CREATE ROLE quantumancy WITH LOGIN PASSWORD 'quantumancy';"
sudo -u postgres psql -c "CREATE DATABASE quantumancy OWNER quantumancy ENCODING 'UTF8' TEMPLATE template0;"
sudo -u postgres psql -c "CREATE DATABASE quantumancy_test OWNER quantumancy ENCODING 'UTF8' TEMPLATE template0;"
quantumancy_testis dropped and recreated by every test run. Never point the app's ownDATABASE_URLat it.
2. Configuration:
cp .env.example .env
# edit .env: set a real SESSION_SECRET, confirm DATABASE_URL and OLLAMA_BASE_URL
3. Backend:
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
4. Piper voices. backend/voices/ is gitignored; the app expects these
voice models there (id → file), downloadable from the
rhasspy/piper-voices HuggingFace
repo (each .onnx plus its .onnx.json):
| id | file | language | character |
|---|---|---|---|
lessac |
en_US-lessac-low.onnx |
en | a measured American woman (default) |
amy |
en_US-amy-low.onnx |
en | a soft American woman |
ryan |
en_US-ryan-low.onnx |
en | a deep American man |
alan |
en_GB-alan-low.onnx |
en | a low British man |
hfc_male |
en_US-hfc_male-medium.onnx |
en | a worn male voice |
hfc_female |
en_US-hfc_female-medium.onnx |
en | a worn female voice |
davefx |
es_ES-davefx-medium.onnx |
es | una voz masculina grave (ES default) |
ald |
es_MX-ald-medium.onnx |
es | una voz masculina seca |
5. Frontend:
cd frontend
npm install
npm run build # produces frontend/dist/, served by the backend
6. Run. Either by hand (from backend/, with the .env values in the
environment — the app reads .env relative to its working directory, and
piper_voices_dir/data_dir are relative to backend/):
cd backend
set -a && source ../.env && set +a
venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 7777
or as a service (recommended — auto-restart, boot-start, journalctl -u quantumancy):
sudo cp deploy/quantumancy.service /etc/systemd/system/
sudo systemctl enable --now quantumancy
Then visit http://<host>:7777 — or your Cloudflare Tunnel hostname for the
secure context Spirit Radio and EVP require.
7. Ollama models (on the Ollama box, one-time):
ollama pull granite4.1:3b # fast tier: fragments, ambient whispers
ollama pull minicpm-v4.5:latest # chat tier: direct contact, entity minting
Any Ollama tag works — override with OLLAMA_FAST_MODEL / OLLAMA_CHAT_MODEL.
Configuration
All settings live in backend/app/config.py and are read from the environment
or .env (repo root when run via systemd; CWD otherwise). Required:
DATABASE_URL, OLLAMA_BASE_URL, SESSION_SECRET.
| Env var | Default | Purpose |
|---|---|---|
DATABASE_URL |
— | asyncpg connection string, e.g. postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy |
OLLAMA_BASE_URL |
— | Ollama REST endpoint, e.g. http://10.30.20.107:11434 |
SESSION_SECRET |
— | random 64-char string (session cookie signing) |
PORT |
7777 |
HTTP listen port |
OLLAMA_FAST_MODEL |
granite4.1:3b |
fast tier: fragments, wire whispers |
OLLAMA_CHAT_MODEL |
minicpm-v4.5:latest |
chat tier: direct contact, entity minting |
LLM_MAX_CONCURRENCY |
2 |
simultaneous Ollama calls (CPU-only box — keep small) |
LLM_MAX_QUEUE_DEPTH |
8 |
queued calls before "too many seekers" rejection |
LLM_COOLDOWN_SECONDS |
8.0 |
min gap between ambient whispers; user requests preempt |
PIPER_VOICES_DIR |
voices |
voice model directory (relative to backend/) |
DATA_DIR |
data |
generated audio, served at /audio/ (relative to backend/) |
The Séance Protocol
One WebSocket per contact session: /ws/session, authenticated by the
qm_session cookie (close code 4401 otherwise). JSON frames both ways.
Client → server:
| Frame | Payload | Server response |
|---|---|---|
ping |
— | pong |
set_mode |
mode: wire|evp|radio|ouija |
mode echo; persisted on the session row |
language |
language: en|es |
switches LLM reply language + Piper voice |
summon |
— | status: summoning → entity → greeting utterance |
anomaly |
source, frequency, magnitude |
anomaly_ack; auto-summons once the stream can fingerprint (≥3 anomalies), then fragment utterances |
question |
text (≤500 chars) |
status: gathering → reply_start → reply_token×N → reply_end → spoken utterance |
passive |
enabled: bool |
passive ack; starts/stops the ambient Wire Ghost loop |
Server → client (all frames flow through a single sender task so concurrent producers never interleave):
| Frame | Payload | Meaning |
|---|---|---|
session |
id |
contact session created (sent on connect) |
pong |
— | keepalive reply |
mode |
mode |
mode accepted |
status |
state: attuning|summoning|gathering |
themed loading states |
entity |
is_new, entity (name, epithet, persona, rarity, voice, visual, quotes, contact_count) |
a presence has been matched or minted |
anomaly_ack |
count |
anomalies recorded this session |
utterance |
id, kind: greeting|fragment|ambient|reply, text, entity |
words from beyond; audio follows |
audio |
id, url (/audio/<event>.wav) |
TTS + effects chain finished for that utterance |
reply_start / reply_token / reply_end |
token, final id + text |
token-streamed Direct Contact reply |
telemetry |
jitter_bytes_per_s, latency_variance_ms, latency_mean_ms, dns_ms |
live Wire Ghost vitals |
passive |
enabled |
ambient loop state |
error |
code: rate_limited|veil_crowded, message |
themed rate-limit / queue-full notices |
REST (JSON, session-cookie auth where noted; credentials: 'include'):
| Endpoint | Auth | Purpose |
|---|---|---|
POST /auth/register · POST /auth/login · POST /auth/logout · GET /auth/me |
— | argon2 username/password, qm_session cookie (14-day server-side sessions) |
GET /api/codex?rarity=&sort=recent|contacted&limit= |
public | the shared spirit registry |
GET /api/codex/{entity_id} |
public | full dossier: persona, voice profile, sighting count |
GET /api/stats |
public | live veil counters (entities, sessions, utterances, anomalies) |
GET /healthz |
— | {"status": "ok"} |
Testing
Backend (pytest, 45 tests; requires the quantumancy_test database —
dropped and recreated on every run):
cd backend && source venv/bin/activate
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
SESSION_SECRET=test-secret \
python -m pytest -v
Covers: auth + session cookies, rate limiter, config, SPA serving, LLM queue, telemetry parsing, entity signatures/profiles, prompt construction (incl. Spanish language clauses), TTS effects chain, Codex REST, and the full WS séance flow (auth, ping/pong, summon/mint/greet, streamed replies, anomaly attunement, re-contact by signature).
Frontend (Vitest + Testing Library — 71 tests across 6 files: the planchette state machine, the EVP detector core, the FFT, the reconnecting VeilSocket, the séance reducer, and the App shell):
cd frontend && npm test
Hardware-in-the-loop (cannot be unit tested, per the spec): the WebUSB
RTL-SDR sweep and the microphone EVP flow each need a manual pass with real
hardware/permissions before being called done. Spirit Radio is Chromium-only
and its driver is marked HARDWARE PASS REQUIRED in frontend/src/lib/sdr.ts.
RTL-SDR dongles are often claimed by the OS kernel driver
(dvb_usb_rtl28xxu on Linux) before WebUSB can reach them — Windows users
with Zadig/WinUSB usually work out of the box; Linux/Mac may need a driver
unbind.
The Veil's Honesty Policy
- All LLM system prompts frame the entity as a horror-fiction persona in an interactive art installation — never a genuine paranormal claim. The UI carries the "this is real" atmosphere; the model instructions carry the fiction.
- Wire Ghost telemetry reads counters and timings only. Packet payloads are never inspected or logged.
- If the Ollama box is dark, every spirit channel degrades to curated offline fallbacks — a summoning never visibly fails.
Further Reading
- Design spec:
docs/superpowers/specs/2026-07-20-quantumancy-website-design.md - Implementation plans 1–7:
docs/superpowers/plans/