Three workstreams: host-testable firmware logic (closing the gap that let the RD03E frame bug ship), crossing over as a five-layer rite with trait-driven twists, and a /doctrine page whose every arcane claim maps to a real mechanism in the source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
QUANTUMANCY
an instrument for speaking with the dead bandwidth
Quantumancy is a self-hosted séance. Point a browser at it and real sensing channels — your microphone, your phone's motion sensors and magnetometer, an RTL-SDR dongle, your own network's jitter, a paired ESP32 sensor node, or just a text box — feed real anomaly detection into a locally-run LLM that invents a spirit on the spot: a name, an epithet, a persona, a coherent voice, hidden traits, a rarity tier, and a small immortality in a shared registry called the Codex. A local Piper TTS then gives that spirit an actual voice, degraded through a hand-built static-and-echo effects chain until it sounds like it's coming through a dying radio.
And the physical world genuinely participates: every summon carries entropy harvested from your room's own noise floors, tonight's real moon phase skews who answers, NOAA's measured geomagnetic K-index thins the veil, and unprompted speech is token-sampled with a seed derived from the room — the room literally selects the words.
This is an interactive horror art installation, not a paranormal claim.
Every "entity" is fiction generated by a language model on your own
hardware — the system prompts are explicit about this (see
backend/app/llm/prompts.py). But every measurement is real: FFT power
spectra, magnetometer microtesla, /proc/net/dev counters, BLE signal
attenuation, published lunar ephemerides, NOAA space-weather data. The rule
(see docs/CHANNELS.md): the fiction may interpret a measurement however it
likes; it may never fabricate one. Nothing leaves your network except a
courteous 10-minute poll of NOAA's public Kp feed — no cloud LLM, no
analytics. If the LLM box goes dark mid-séance, a procedural fallback keeps
every mode answering so a summoning never visibly fails.
The Séance: Ways In
Every mode writes into the same pipeline: an anomaly event (source,
frequency, magnitude) feeds a per-session fingerprint; once a session has
produced at least 3 anomalies of real structure, it auto-summons — either
re-contacting a Codex entry whose signature matches (a real draw, not a
guarantee — see The Room Decides), or minting someone new.
| Mode | Vessel | What actually happens |
|---|---|---|
| Wire Ghost | Nothing — works for everyone | Real, non-content network telemetry from the host: byte-counter jitter (/proc/net/dev), TCP connect-latency variance, DNS timing, against a rolling baseline. Payloads are never inspected — a hard privacy boundary. Passive listening lets the line murmur on its own. |
| EVP Listening | Microphone (getUserMedia) |
A Web Audio AnalyserNode watches the voice band (~300 Hz–3.4 kHz) for deviations ≥ 8 dB over the room's rolling noise floor — live spectrum scope, audible anomalies, and every frame feeds the entropy pool. Works everywhere, including iPhone. |
| Spirit Radio | RTL-SDR dongle (WebUSB, Chromium only) | The browser drives the dongle directly, sweeping FM 88–108 MHz, computing power spectra against a rolling floor. Every USB call is time-boxed (withTimeout) so a sulking dongle errors instead of hanging silently. |
| Ouija / Direct Contact | The shared front door | A canvas planchette wanders the full board, brushes letters, visits YES/NO, and rests on GOODBYE every few minutes; questions stream back token by token while it spells. |
| EMF / Field Meter | A phone | Where the Generic Sensor API exists (Chromium/Android), the actual magnetometer in microtesla — a true EMF meter, baseline-subtracting Earth's constant field. Everywhere else (including iOS), the motion-fusion fallback: acceleration deviation + orientation angular velocity. Both are real physics; lib/baseline.ts gives them one shared rolling-EMA core. |
| The Vessel | ESP32-P4 sensor node (LAN) | Paired hardware POSTs temperature/pressure/mic/radar readings; the Cold Spot Detector and the séance's DeviceWhisper panel render them live, and hardware anomalies can trigger a summon in an open séance. Firmware in firmware/ — written and reviewed, never yet flashed to a real board. |
lib/bluetooth.ts (BLE RSSI as a presence field — bodies absorb 2.4 GHz)
is implemented and tested but not yet wired into a séance panel.
The Room Decides
backend/app/entropy.py · frontend/src/lib/entropy.ts ·
backend/app/celestial.py · backend/app/geomagnetic.py
- Physical entropy. The client harvests noise from live sensor frames
(mic and RF noise floors), Von Neumann–debiases it, SHA-256–conditions
it, and sends it with every summon. The server treats it as untrusted by
construction: every draw is
HMAC(fresh server secret, contribution || context), so a hostile client can only ever add unpredictability, never steer an outcome. Whether a channel's familiar spirit answers again (RETURN_CHANCE) is one of these draws. - Real astronomy. Moon phase from orbital mechanics (validated against published ephemeris dates, not against itself) and true solar midnight from the seeker's longitude (only the longitude is kept, never a full coordinate). A full moon roughly triples rare/mythic mint odds — a skew, never a gate — nudges hidden traits stronger and stranger, and erodes a familiar spirit's claim on its channel so strangers push through on thin nights.
- Real space weather. NOAA SWPC's planetary K-index, cached 10 minutes,
never blocking: a geomagnetic storm measurably thins the veil, and the
entity is told
Kp 6.33 — moderate geomagnetic stormas a fact of its room. - Generation from nothing.
SpiritService.manifest()produces unprompted speech: the prompt contains no seeker input at all — only measured room state — and Ollama's samplingseedderives from the room's entropy. Change the noise, get different words. Rendered in the transcript as an intrusion (violet edge), not a reply.
Rituals, Judgment, and the Reliquary
Every minted entity carries hidden traits (alignment / power / volatility / deceptiveness) — signature-seeded ground truth the persona prompt never sees, so the LLM cannot leak it. The séance offers:
- The ritual — a 4-sigil hold-to-charge rite; success reveals the entity's true traits, failure reveals nothing. Judgment is never gated on it: you may always judge blind.
- Judgment — trust / banish / test / cross over. Correct calls pay essence and favor; wrongly trusting a demon sharpens the haunting, wrongly banishing a benevolent spirit wounds it, and a correctly-judged stuck spirit crosses over: at peace, permanently retired from its channel, its signature freed for someone new.
- The Reliquary — essence (earned, never bought), item drops on
milestone moments, purchasable unlocks, and hand-drawn sigils.
/shopremains a waitlist for the Ultimate Quantum Box hardware — email capture only, no payment taken, and the page says so.
A Ghost Log HUD whispers entity tells app-wide, and /log is the
recallable record: your last séances with entity, counts, and echo lines.
Coming In From the Cold
- Guests:
POST /auth/guestmints a realwanderer-xxxxaccount and a normal session — the front door (/enter) offers it as "slip through as a wanderer", and the séance nudges wanderers to claim a name so their codex outlives the mist. - Conditions:
GET /api/conditions(and the séance's side-column strip) surfaces tonight's moon, veil thinness, and geomagnetic state. - First contact: each mode shows one in-fiction hint if its sensor sits unused, dismissible forever; error copy redirects instead of dead-ending (no mic? "the board needs no ear").
- PWA: a
manifest.webmanifestmakes it installable to a home screen (standalone, no service worker — an offline séance is meaningless). - Phones: dedicated ≤560px and ≤380px layouts, 44px touch targets, 16px inputs (so iOS Safari doesn't zoom-and-stick).
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
┌──────────────────┐ ┌───────────▼─────────┐
│ ESP32-P4 node(s) │ │ Ollama box │
│ (paired, HTTP │ │ 10.30.20.107:11434 │
│ telemetry in) │ │ CPU-only, 64 GB │
└──────────────────┘ │ fast + chat models │
└─────────────────────┘
...and one WAN egress:
NOAA SWPC Kp (10-min cache)
- App CT — FastAPI serves the built Vite/React SPA, the REST API, the
/ws/sessionséance and/ws/device-feedsockets, and/audio/*spirit WAVs. Postgres holds accounts, sessions, transcripts, the Codex, traits, essence/favor, inventory, sigils, devices, and the waitlist. Piper TTS is degraded through a numpy chain (rate → pitch → bitcrush → echo → static); eight voice archetypes (elder/young/child/drowned/burned/distant…) covary pitch/rate/noise/echo so two spirits sound like two different dead people, not two settings. - Ollama box — two model tiers behind a bounded-concurrency queue; a
full queue rejects with a themed
veil_crowdederror rather than hanging. - Cloudflare Tunnel — terminates HTTPS; its secure context is what unlocks mic, WebUSB, and motion/magnetometer permissions.
- systemd —
Restart=always, no start-limit surrender, wants Postgres: the veil survives reboots and crash bursts without a human.
Repo layout
backend/
app/
main.py routers, idempotent startup migrations, SPA fallback
ws.py the séance channel + rituals/judgment + manifest
entropy.py HMAC-conditioned physical randomness (the room decides)
celestial.py moon phase + true solar midnight (computed, offline)
geomagnetic.py NOAA SWPC Kp cache (never blocks a summon)
entities.py signatures, voice archetypes, moon-skewed rarity/traits
judgment.py verdict logic, tells, favor bias, ritual rolls
inventory.py essence economy, drop tables, unlocks, sigil validation
device_anomaly.py ESP32 telemetry -> anomaly/summon bridge
telemetry.py Wire Ghost sampler llm/ tts/ models/ routes/
tests/ pytest suite — 328 tests
firmware/
esp32p4-sensor-node/ ESP-IDF firmware: BMP280, MEMS mic, RD-03E radar,
WiFi via ESP32-C6 (UNVERIFIED on real hardware)
frontend/
src/pages/ Landing, Enter, Seance, Codex(+Entity), Shop,
Inventory, Devices, GhostLog
src/lib/ entropy, baseline (shared EMA core), coldSpot,
bluetooth, magnetometer, evp, sdr, emf, fft,
planchette, haunting, deviceFeed, spectrumSonify
src/components/ PlanchetteBoard, SpectrumScope, VeilConditions,
DeviceWhisper, ModeHint, Ritual/Judgment/Inventory
panels, SigilDesigner, ColdSpotPanel, GhostLog HUD…
docs/
CHANNELS.md what each channel really measures + verification status
superpowers/ design specs + implementation plans
Quickstart
Prereqs on the App CT: Python 3.11+, Node 18+, Postgres (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: 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; download each .onnx
plus its .onnx.json from
rhasspy/piper-voices:
| 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. By hand:
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). The shipped unit
(deploy/quantumancy.service) hardcodes /root/quantumancy — edit the two
paths first if your checkout lives elsewhere. A production install should
also add the persistence drop-in (Restart=always,
StartLimitIntervalSec=0, Wants=postgresql.service):
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 that mic, WebUSB, and motion/magnetometer all 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, minting, manifest
Any Ollama tag works — override with OLLAMA_FAST_MODEL / OLLAMA_CHAT_MODEL.
Configuration
All settings live in backend/app/config.py, read from the environment or
.env. Required: DATABASE_URL, OLLAMA_BASE_URL.
| Env var | Default | Purpose |
|---|---|---|
DATABASE_URL |
— (required) | asyncpg connection string |
OLLAMA_BASE_URL |
— (required) | Ollama REST endpoint |
PORT |
7777 |
HTTP listen port |
OLLAMA_FAST_MODEL |
granite4.1:3b |
fast tier |
OLLAMA_CHAT_MODEL |
minicpm-v4.5:latest |
chat tier (replies, minting, manifest) |
LLM_MAX_CONCURRENCY |
2 |
simultaneous Ollama calls |
LLM_MAX_QUEUE_DEPTH |
8 |
queued calls before themed rejection |
LLM_COOLDOWN_SECONDS |
8.0 |
min gap between ambient whispers |
PIPER_VOICES_DIR |
voices |
voice models (relative to backend/) |
DATA_DIR |
data |
generated audio, served at /audio/ |
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 | Notes |
|---|---|---|
ping |
— | pong |
set_mode |
mode: wire|evp|radio|ouija|emf |
persisted on the session |
language |
en|es |
switches LLM language + Piper voice |
summon |
optional entropy (conditioned room noise), optional longitude |
rate-limited 4/min/user; the sky + entropy shape who answers |
anomaly |
source, frequency, magnitude |
auto-summons at ≥3; then fragments (30/min/user) |
question |
text ≤500 |
streamed reply (6/min/user) |
passive |
enabled |
ambient Wire Ghost loop |
ritual_start / ritual_step |
step |
4 steps complete an attempt (6/min/user) |
judgment |
verdict: trust|banish|test|cross_over |
10/min/user |
Server → client (single sender task; frames never interleave):
| Frame | Meaning |
|---|---|
session, pong, mode, passive |
plumbing |
status |
attuning|summoning|gathering; summoning carries entropy_bits, sky (moon/thinness), geomagnetic |
entity |
matched or minted (public fields only — hidden traits never leave the server unearned) |
anomaly_ack / anomaly |
counts; server-detected events |
utterance |
kind: greeting|fragment|ambient|reply|manifest — manifest is unprompted speech |
audio |
TTS + effects finished for an utterance |
reply_start/token/end |
streamed Direct Contact |
telemetry |
live Wire Ghost vitals |
tell |
a trait-shaped whisper for the Ghost Log |
ritual_complete |
success, revealed traits on success |
judgment_result |
correct, deltas, at_peace, consequence |
item_drop |
a relic falls |
error |
rate_limited / veil_crowded, themed |
REST (session-cookie auth where noted):
| Endpoint | Auth | Purpose |
|---|---|---|
POST /auth/register · login · logout · GET /auth/me |
— | argon2 + qm_session cookie |
POST /auth/guest |
public, 5/hr/IP | mint a wanderer- account |
GET /api/codex[?rarity,sort,limit] · GET /api/codex/{id} |
public | the shared registry / full dossier |
GET /api/stats |
public | live veil counters |
GET /api/conditions[?lon] |
public | moon, veil thinness, geomagnetic |
GET /api/seances/recent |
auth | the Ghost Log: last 12 sessions + echoes |
GET/POST /api/device · POST /api/device/telemetry · WS /ws/device-feed |
auth / device token | ESP32 pairing + live readings |
GET /api/inventory/{unlocks,items,sigils} · POST /api/inventory/unlocks/{key} · POST /api/inventory/sigils |
auth | the Reliquary |
POST /api/shop/waitlist |
public, 5/hr/IP | hardware waitlist |
GET /healthz |
— | {"status": "ok"} |
Testing
Backend — pytest, 328 tests. Requires quantumancy_test (dropped
and recreated every run — never run two suites concurrently against it; the
autouse drop/create fixture will fight itself):
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 \
python -m pytest -q
Frontend — Vitest + Testing Library, 366 tests, plus a hard i18n
en/es parity gate (npm run pretest):
cd frontend && npm test
Hardware-in-the-loop (cannot be unit tested): the RTL-SDR sweep, mic
permissions, phone sensors, and everything in firmware/ need real
hardware passes. docs/CHANNELS.md tracks exactly what has and hasn't had
one.
Current State & Known Gaps
Honestly, in order of confidence:
- Solid, verified live. Auth (incl. guests), the Codex, Wire Ghost, EVP + spectrum scope + sonification, TTS + archetype voices, entropy, astronomy (validated against published ephemerides), the NOAA feed (verified against the live endpoint), rituals/judgment/essence, the Ghost Log, conditions, PWA manifest, phone layouts, the haunting layer. 328 + 366 tests pass.
- Should work, unverified on hardware.
lib/sdr.ts(WebUSB RTL-SDR) is written from librtlsdr register docs and has never met a real dongle; every entry point is time-boxed and fails soft. The magnetometer EMF path needs an Android device pass. All offirmware/compiles-by-eye: reviewed twice, never flashed. - Built but not yet reachable.
lib/bluetooth.ts(BLE presence field) has no séance panel yet. - Known drift. The landing page's marketing grid still shows four modes
(
MODE_ORDERinLandingPage.tsx) — EMF and everything since aren't in the showcase. - Cosmetic. One ~930 KB JS chunk; code-splitting would help first paint.
- Intentionally unfinished. The Ultimate Quantum Box is a waitlist — no hardware ships, no payment is taken, and the page says so.
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.
- Every number shown to a seeker is a real measurement. The fiction
interprets; it never fabricates. (
docs/CHANNELS.mdis the ledger.) - Wire Ghost telemetry reads counters and timings only; packet payloads are never inspected. Only a longitude is ever kept from a location grant.
- Client entropy is untrusted by construction — it can add unpredictability to a draw, never steer one.
- If the Ollama box is dark, every channel degrades to procedural fallbacks — a summoning never visibly fails.
Further Reading
docs/CHANNELS.md— every channel, what it really measures, and its honest verification status- Design specs:
docs/superpowers/specs/(website, character depth, ESP32 node, possession, usability wave) - Implementation plans:
docs/superpowers/plans/