# 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 storm` as 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 sampling `seed` derives 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. `/shop` remains 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/guest` mints a real `wanderer-xxxx` account 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.webmanifest` makes 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/session` séance and `/ws/device-feed` sockets, 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_crowded` error 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): ```bash 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_test` is dropped and recreated by every test run. Never point > the app's own `DATABASE_URL` at it. **2. Configuration:** ```bash cp .env.example .env # edit .env: confirm DATABASE_URL and OLLAMA_BASE_URL ``` **3. Backend:** ```bash 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](https://huggingface.co/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:** ```bash cd frontend npm install npm run build # produces frontend/dist/, served by the backend ``` **6. Run.** By hand: ```bash 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`): ```bash sudo cp deploy/quantumancy.service /etc/systemd/system/ sudo systemctl enable --now quantumancy ``` Then visit `http://: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): ```bash 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): ```bash 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`): ```bash 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 of `firmware/` 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_ORDER` in `LandingPage.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.md` is 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/`