Merge gap-g-readme: rewrite README to match actual current build
Took Gap G's version (comprehensive rewrite) over Gap A's smaller README edit, then manually stripped the 4 remaining SESSION_SECRET mentions Gap G didn't know about (Gap A dropped that config field entirely) — README now correctly lists only DATABASE_URL and OLLAMA_BASE_URL as required.
This commit is contained in:
232
README.md
232
README.md
@@ -2,34 +2,81 @@
|
|||||||
|
|
||||||
*an instrument for speaking with the dead bandwidth*
|
*an instrument for speaking with the dead bandwidth*
|
||||||
|
|
||||||
Quantumancy is a self-hosted, gothic-hacker web experience that lets visitors
|
Quantumancy is a self-hosted séance. Point a browser at it and five different
|
||||||
**talk to spirits** through four channels, each grounded in a real paranormal-
|
sensing channels — your microphone, your phone's motion sensors, an RTL-SDR
|
||||||
investigation technique or a real data source. Real anomalies — RF power
|
dongle, your own network's jitter, or just a text box — feed real anomaly
|
||||||
spikes, voice-band blips, network jitter — are detected in genuine sensor and
|
detection into a locally-run LLM that invents a spirit on the spot: a name,
|
||||||
telemetry streams, and a locally-run LLM gives the presence behind them a
|
an epithet, a persona, a voice, a rarity tier, and a small immortality in a
|
||||||
voice. A local Piper TTS then renders that voice through a static-choked
|
shared registry called **the Codex**. A local Piper TTS then gives that
|
||||||
spirit-box effects chain.
|
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.
|
||||||
|
|
||||||
**This is an interactive horror art installation.** The spirits are fiction;
|
**This is an interactive horror art installation, not a paranormal claim.**
|
||||||
the static is real. Every "entity" is a persona generated by a language model
|
Every "entity" is fiction generated by a language model on your own
|
||||||
on your own hardware. Nothing leaves your network: no cloud APIs, no
|
hardware — the system prompts are explicit about this (see
|
||||||
third-party inference, no telemetry of ours — only yours.
|
`backend/app/llm/prompts.py`), and no amount of atmosphere changes what's
|
||||||
|
actually happening underneath: FFT power spectra, voice-band deviation
|
||||||
|
detection, device-motion EMAs, and `/proc/net/dev` counters, all real signal
|
||||||
|
processing on real data. Nothing leaves your network — no cloud LLM, no
|
||||||
|
third-party inference, no analytics. If the LLM box goes dark mid-séance, a
|
||||||
|
procedural fallback (deterministic, signature-seeded) keeps every mode
|
||||||
|
answering so a summoning never visibly fails.
|
||||||
|
|
||||||
## The Four Modes
|
## The Séance: Five 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 a spirit —
|
||||||
|
either re-contacting a Codex entry whose signature matches, or minting a
|
||||||
|
brand new one.
|
||||||
|
|
||||||
| Mode | Vessel | What actually happens |
|
| 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. |
|
| **Wire Ghost** | Nothing — works for everyone | The backend samples real, non-content network telemetry from the host: interface byte-counter jitter (`/proc/net/dev`), TCP connect-latency variance against reference hosts, DNS resolution timing. A rolling baseline (needs 6+ samples, an absolute 20 KB/s floor, a 3σ + 2.5× threshold) flags genuine surges as anomalies, throttled to one every 20s. Packet payloads are never inspected — a hard privacy boundary. "The wire knocks loudest when nobody is touching it" — flip on passive listening and let the line murmur on its own. |
|
||||||
| **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. |
|
| **EVP Listening** | Microphone (`getUserMedia`) | A Web Audio `AnalyserNode` watches the voice band (~300 Hz–3.4 kHz) for a brief deviation ≥ 8 dB above the room's rolling noise floor, throttled to one per 2s — the floor only adapts during genuinely quiet stretches so sustained speech can't mask a real spike. The classic "record silence, review for voices" technique, done live. |
|
||||||
| **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. |
|
| **Spirit Radio** | RTL-SDR dongle (WebUSB, Chromium only) | The browser drives the dongle directly — no native driver, no server round-trip — sweeping the FM band (88–108 MHz) in 1.8 MHz steps, computing a power spectrum per tune and comparing it to a rolling noise floor. Spikes ≥ 10 dB above floor become anomaly events, throttled to one per 2s: an FM sweep pushed through a lantern dragged across a dark field. |
|
||||||
| **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. |
|
| **Ouija / Direct Contact** | The shared front door | A canvas planchette drifts on its own, then spells the spirit's words letter by letter. Ask a free-text question (≤ 500 chars) and the chat-tier model streams a full reply token by token while the planchette works through it. |
|
||||||
|
| **EMF / Field Meter** | A phone (DeviceMotion + DeviceOrientation) | The seeker's own phone becomes the meter: acceleration-magnitude deviation from a slow gravity EMA, fused with orientation angular velocity, tracked against an adapting baseline. A reading past `baseline × 2.5` (and an absolute floor, so a resting phone stays silent) fires an anomaly, throttled to one per 2.5s. iOS 13+ gates this behind an explicit gesture-triggered permission grant, handled in-app. |
|
||||||
|
|
||||||
Every session begins unidentified. The backend fingerprints the session's
|
Every session begins unidentified. The backend fingerprints the anomaly
|
||||||
anomaly pattern into a **signature**; a matching signature re-contacts an
|
pattern into a **signature** (`backend/app/entities.py`); a matching
|
||||||
existing spirit, a new one gets **minted into the Codex** — a publicly
|
signature re-contacts an existing spirit and bumps its contact count, a new
|
||||||
browsable registry of every spirit ever contacted, shared across all users,
|
one gets minted by the chat-tier LLM and dropped into the Codex with a name,
|
||||||
with name, epithet, rarity tier, persona lore, sample quotes, voice/visual
|
epithet, 2–3 sentences of lore, a rarity tier (common/uncommon/rare/mythic —
|
||||||
profiles, and contact counts.
|
weighted 55/30/12/3 in the offline fallback), a voice profile (pitch, rate,
|
||||||
|
noise, echo), a visual hue/form, and two sample quotes.
|
||||||
|
|
||||||
|
**The haunting doesn't stop at the séance panel.** A framework-free
|
||||||
|
possession layer (`frontend/src/lib/haunting.ts`,
|
||||||
|
`frontend/src/components/HauntingLayer.tsx`) runs across the whole app: an
|
||||||
|
idle-aware escalator that gets bolder the longer you sit still, a gesture-armed
|
||||||
|
ambient dread-bed (brown noise through a breathing lowpass, a 55/55.7 Hz
|
||||||
|
detuned drone beat, occasional reversed-noise swells), fleeting whisper-words
|
||||||
|
drifting across the glass, and `document.title` glitches when you tab away.
|
||||||
|
Nothing stirs for the first ten seconds after load — it creeps in, it never
|
||||||
|
jumpscares on arrival — and it's fully inert under `prefers-reduced-motion`.
|
||||||
|
|
||||||
|
## The Codex
|
||||||
|
|
||||||
|
`GET /api/codex` (public, no auth) is a browsable, shared registry of every
|
||||||
|
spirit ever contacted by any seeker: filterable by rarity, sortable by
|
||||||
|
`recent` or `contacted`, each card showing name, epithet, rarity badge, a
|
||||||
|
sample quote, contact count, and who discovered it. `GET /api/codex/{id}`
|
||||||
|
opens a full dossier — persona, voice-parameter table, sighting count. It's
|
||||||
|
the collectible layer sitting on top of the anomaly-detection plumbing:
|
||||||
|
nothing about a spirit is user-authored, all of it comes from the same
|
||||||
|
signature → LLM-mint → normalize pipeline that runs live during a séance.
|
||||||
|
|
||||||
|
## The Armory
|
||||||
|
|
||||||
|
`/shop` is a waitlist page for **the Ultimate Quantum Box** — a proposed
|
||||||
|
ESP32-P4/C6 handheld séance instrument (thermal camera, EMF whisker array,
|
||||||
|
geophone, spirit-box mic preamp, OLED face) plus four standalone modules,
|
||||||
|
none of which exist yet. `POST /api/shop/waitlist` (public, rate-limited to
|
||||||
|
5/hour/IP) is pure email capture — no payment is taken, and the page says so
|
||||||
|
outright: *"the website works fully without it; hardware only knocks
|
||||||
|
louder."* Treat this section of the README the same way: real code, honest
|
||||||
|
about being pre-order vaporware for now.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -57,20 +104,23 @@ Two machines on the LAN, no containers anywhere:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **App CT** — plain HTTP on port 7777. FastAPI (async, WebSocket-native)
|
- **App CT** — plain HTTP on port 7777. FastAPI (async, WebSocket-native)
|
||||||
serves the built Vite/React SPA as static assets, the auth + Codex REST
|
serves the built Vite/React SPA as static assets, the auth + Codex + shop
|
||||||
API, the `/ws/session` séance WebSocket, and `/audio/*` spirit-voice WAVs.
|
REST API, the `/ws/session` séance WebSocket, and `/audio/*` spirit-voice
|
||||||
Postgres holds accounts, sessions, transcripts (events), and the Codex.
|
WAVs. Postgres holds accounts, sessions, transcripts (events), the Codex,
|
||||||
Piper TTS is invoked locally per utterance, then degraded through a numpy
|
and the hardware waitlist. Piper TTS is invoked locally per utterance,
|
||||||
effects chain (rate → pitch → bitcrush → echo → static).
|
then degraded through a numpy effects chain (rate → pitch → bitcrush →
|
||||||
- **Ollama box** — reachable over LAN via Ollama's REST API. Two model tiers:
|
echo → static).
|
||||||
a **fast** model for fragments/ambient whispers/… and a heavier **chat**
|
- **Ollama box** — reachable over LAN via Ollama's REST API. Two model
|
||||||
model for Direct Contact and entity minting. CPU-only and shared, so all
|
tiers: a **fast** model for fragments/ambient whispers/wire whispers, and
|
||||||
LLM calls flow through a bounded-concurrency queue (`LLMQueue`); queued
|
a heavier **chat** model for Direct Contact replies and entity minting.
|
||||||
requests render in the UI as "the spirits are gathering energy…".
|
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…", and a full queue rejects new work with a themed
|
||||||
|
`veil_crowded` error rather than hanging.
|
||||||
- **Cloudflare Tunnel** — managed outside this repo; terminates HTTPS and
|
- **Cloudflare Tunnel** — managed outside this repo; terminates HTTPS and
|
||||||
points at `http://<app-ct-ip>:7777`. The app never handles TLS. The
|
points at `http://<app-ct-ip>:7777`. The app never handles TLS. The
|
||||||
tunnel's HTTPS origin satisfies browser secure-context requirements for
|
tunnel's HTTPS origin satisfies browser secure-context requirements for
|
||||||
mic and WebUSB.
|
mic (EVP), WebUSB (Spirit Radio), and motion sensors (EMF on iOS).
|
||||||
|
|
||||||
### Repo layout
|
### Repo layout
|
||||||
|
|
||||||
@@ -82,29 +132,34 @@ backend/
|
|||||||
db.py async SQLAlchemy 2.0 engine/session (asyncpg)
|
db.py async SQLAlchemy 2.0 engine/session (asyncpg)
|
||||||
deps.py get_current_user, qm_session cookie
|
deps.py get_current_user, qm_session cookie
|
||||||
security.py argon2 password hashing
|
security.py argon2 password hashing
|
||||||
rate_limit.py fixed-window RateLimiter (per-user LLM limits)
|
rate_limit.py fixed-window RateLimiter (per-user LLM limits, per-IP waitlist limit)
|
||||||
telemetry.py Wire Ghost sampler (/proc/net/dev, TCP RTT, DNS timing)
|
telemetry.py Wire Ghost sampler (/proc/net/dev, TCP RTT, DNS timing)
|
||||||
entities.py anomaly signatures, profile normalization, procedural fallback
|
entities.py anomaly signatures, profile normalization, procedural fallback
|
||||||
ws.py the séance channel: /ws/session protocol + ambient loop
|
ws.py the séance channel: /ws/session protocol + ambient loop
|
||||||
llm/ OllamaClient, bounded LLMQueue, SpiritService, prompt builders
|
llm/ OllamaClient, bounded LLMQueue, SpiritService, prompt builders
|
||||||
tts/ Piper CLI wrapper, voice catalog, numpy effects chain
|
tts/ Piper CLI wrapper, voice catalog, numpy effects chain
|
||||||
models/ users, auth_sessions, contact_sessions, entities,
|
models/ users, auth_sessions, contact_sessions, entities,
|
||||||
entity_sightings, events
|
entity_sightings, events, waitlist_entries
|
||||||
routes/ /auth/* and /api/codex, /api/stats
|
routes/ auth.py, codex.py, shop.py
|
||||||
tests/ pytest suite (45 tests)
|
tests/ pytest suite (54 tests, 14 files)
|
||||||
voices/ Piper .onnx voice models (gitignored — see setup)
|
voices/ Piper .onnx voice models (gitignored — see setup)
|
||||||
data/ generated utterance audio, served at /audio/ (gitignored)
|
data/ generated utterance audio, served at /audio/ (gitignored)
|
||||||
deploy/
|
deploy/
|
||||||
quantumancy.service systemd unit template
|
quantumancy.service systemd unit template
|
||||||
frontend/
|
frontend/
|
||||||
src/lib/ types (WS protocol), VeilSocket, audio player, evp, sdr,
|
src/pages/ LandingPage, EnterPage, SeancePage, CodexPage,
|
||||||
fft, planchette machine
|
CodexEntityPage, ShopPage
|
||||||
|
src/lib/ types (WS protocol), ws (VeilSocket), audio player,
|
||||||
|
evp, sdr, emf, fft, planchette machine, haunting
|
||||||
src/state/ AuthProvider, SeanceProvider (reducer + socket wiring)
|
src/state/ AuthProvider, SeanceProvider (reducer + socket wiring)
|
||||||
src/components/ PlanchetteBoard, Transcript, EntityCard, GhostGlyph, …
|
src/components/ PlanchetteBoard, Transcript, EntityCard, GhostGlyph,
|
||||||
|
MatrixView, GraphView, TelemetryReadout, HauntingLayer
|
||||||
src/three/ GhostCanvas + GhostScene (custom-shader 3D spirit)
|
src/three/ GhostCanvas + GhostScene (custom-shader 3D spirit)
|
||||||
src/i18n/ react-i18next init + en.json / es.json
|
src/i18n/ react-i18next init + en.json / es.json
|
||||||
docs/superpowers/
|
docs/superpowers/
|
||||||
specs/ the design spec
|
specs/ the design spec — note the live app now exceeds it
|
||||||
|
(5 modes, not 4; the Codex, the Armory and the
|
||||||
|
haunting layer are all further along than documented)
|
||||||
plans/ the 7 implementation plans this repo was built from
|
plans/ the 7 implementation plans this repo was built from
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -129,7 +184,7 @@ sudo -u postgres psql -c "CREATE DATABASE quantumancy_test OWNER quantumancy ENC
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# edit .env: confirm DATABASE_URL and OLLAMA_BASE_URL
|
# edit .env: set a real SESSION_SECRET, confirm DATABASE_URL and OLLAMA_BASE_URL
|
||||||
```
|
```
|
||||||
|
|
||||||
**3. Backend:**
|
**3. Backend:**
|
||||||
@@ -175,7 +230,10 @@ set -a && source ../.env && set +a
|
|||||||
venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 7777
|
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`):
|
or as a service (recommended — auto-restart, boot-start, `journalctl -u quantumancy`).
|
||||||
|
The shipped unit (`deploy/quantumancy.service`) hardcodes `/root/quantumancy`
|
||||||
|
as the working directory and env file path — edit those two lines first if
|
||||||
|
your checkout lives elsewhere:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo cp deploy/quantumancy.service /etc/systemd/system/
|
sudo cp deploy/quantumancy.service /etc/systemd/system/
|
||||||
@@ -183,7 +241,7 @@ sudo systemctl enable --now quantumancy
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then visit `http://<host>:7777` — or your Cloudflare Tunnel hostname for the
|
Then visit `http://<host>:7777` — or your Cloudflare Tunnel hostname for the
|
||||||
secure context Spirit Radio and EVP require.
|
secure context Spirit Radio, EVP, and EMF (on iOS) all require.
|
||||||
|
|
||||||
**7. Ollama models** (on the Ollama box, one-time):
|
**7. Ollama models** (on the Ollama box, one-time):
|
||||||
|
|
||||||
@@ -198,12 +256,13 @@ Any Ollama tag works — override with `OLLAMA_FAST_MODEL` / `OLLAMA_CHAT_MODEL`
|
|||||||
|
|
||||||
All settings live in `backend/app/config.py` and are read from the environment
|
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:
|
or `.env` (repo root when run via systemd; CWD otherwise). Required:
|
||||||
`DATABASE_URL`, `OLLAMA_BASE_URL`.
|
`DATABASE_URL`, `OLLAMA_BASE_URL`, `SESSION_SECRET`.
|
||||||
|
|
||||||
| Env var | Default | Purpose |
|
| Env var | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `DATABASE_URL` | — | asyncpg connection string, e.g. `postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy` |
|
| `DATABASE_URL` | — (required) | 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` |
|
| `OLLAMA_BASE_URL` | — (required) | Ollama REST endpoint, e.g. `http://10.30.20.107:11434` |
|
||||||
|
| `SESSION_SECRET` | — (required) | present in `Settings` but not currently read anywhere else in the app — session auth actually runs on random per-login tokens hashed into `auth_sessions` (`backend/app/models/auth_session.py`), not a signing secret. Still required to boot; set it to any random string. |
|
||||||
| `PORT` | `7777` | HTTP listen port |
|
| `PORT` | `7777` | HTTP listen port |
|
||||||
| `OLLAMA_FAST_MODEL` | `granite4.1:3b` | fast tier: fragments, wire whispers |
|
| `OLLAMA_FAST_MODEL` | `granite4.1:3b` | fast tier: fragments, wire whispers |
|
||||||
| `OLLAMA_CHAT_MODEL` | `minicpm-v4.5:latest` | chat tier: direct contact, entity minting |
|
| `OLLAMA_CHAT_MODEL` | `minicpm-v4.5:latest` | chat tier: direct contact, entity minting |
|
||||||
@@ -223,11 +282,11 @@ One WebSocket per contact session: `/ws/session`, authenticated by the
|
|||||||
| Frame | Payload | Server response |
|
| Frame | Payload | Server response |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `ping` | — | `pong` |
|
| `ping` | — | `pong` |
|
||||||
| `set_mode` | `mode: wire\|evp\|radio\|ouija` | `mode` echo; persisted on the session row |
|
| `set_mode` | `mode: wire\|evp\|radio\|ouija\|emf` | `mode` echo; persisted on the session row |
|
||||||
| `language` | `language: en\|es` | switches LLM reply language + Piper voice |
|
| `language` | `language: en\|es` | switches LLM reply language + Piper voice |
|
||||||
| `summon` | — | `status: summoning` → `entity` → greeting `utterance` |
|
| `summon` | — | `status: summoning` → `entity` → greeting `utterance` (rate-limited: 4/min/user) |
|
||||||
| `anomaly` | `source`, `frequency`, `magnitude` | `anomaly_ack`; auto-summons once the stream can fingerprint (≥3 anomalies), then fragment `utterance`s |
|
| `anomaly` | `source: wire\|evp\|radio\|emf`, `frequency`, `magnitude` | `anomaly_ack`; auto-summons once the stream can fingerprint (≥3 anomalies), then fragment `utterance`s (rate-limited: 30/min/user) |
|
||||||
| `question` | `text` (≤500 chars) | `status: gathering` → `reply_start` → `reply_token`×N → `reply_end` → spoken `utterance` |
|
| `question` | `text` (≤500 chars) | `status: gathering` → `reply_start` → `reply_token`×N → `reply_end` → spoken `utterance` (rate-limited: 6/min/user) |
|
||||||
| `passive` | `enabled: bool` | `passive` ack; starts/stops the ambient Wire Ghost loop |
|
| `passive` | `enabled: bool` | `passive` ack; starts/stops the ambient Wire Ghost loop |
|
||||||
|
|
||||||
**Server → client** (all frames flow through a single sender task so
|
**Server → client** (all frames flow through a single sender task so
|
||||||
@@ -241,6 +300,7 @@ concurrent producers never interleave):
|
|||||||
| `status` | `state: attuning\|summoning\|gathering` | themed loading states |
|
| `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 |
|
| `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 |
|
| `anomaly_ack` | `count` | anomalies recorded this session |
|
||||||
|
| `anomaly` | `source`, `frequency`, `magnitude` | server-detected anomaly (currently only the Wire Ghost's ambient loop emits these) |
|
||||||
| `utterance` | `id`, `kind: greeting\|fragment\|ambient\|reply`, `text`, `entity` | words from beyond; audio follows |
|
| `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 |
|
| `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 |
|
| `reply_start` / `reply_token` / `reply_end` | `token`, final `id` + `text` | token-streamed Direct Contact reply |
|
||||||
@@ -256,42 +316,75 @@ concurrent producers never interleave):
|
|||||||
| `GET /api/codex?rarity=&sort=recent\|contacted&limit=` | public | the shared spirit registry |
|
| `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/codex/{entity_id}` | public | full dossier: persona, voice profile, sighting count |
|
||||||
| `GET /api/stats` | public | live veil counters (entities, sessions, utterances, anomalies) |
|
| `GET /api/stats` | public | live veil counters (entities, sessions, utterances, anomalies) |
|
||||||
|
| `POST /api/shop/waitlist` | public, rate-limited (5/hr/IP) | join the Ultimate Quantum Box waitlist — `{email, interest}`, idempotent |
|
||||||
| `GET /healthz` | — | `{"status": "ok"}` |
|
| `GET /healthz` | — | `{"status": "ok"}` |
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
**Backend** (pytest, 45 tests; requires the `quantumancy_test` database —
|
**Backend** (pytest, 54 tests across 14 files; requires the
|
||||||
dropped and recreated on every run):
|
`quantumancy_test` database — dropped and recreated on every run):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend && source venv/bin/activate
|
cd backend && source venv/bin/activate
|
||||||
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
python -m pytest -v
|
python -m pytest -v
|
||||||
```
|
```
|
||||||
|
|
||||||
Covers: auth + session cookies, rate limiter, config, SPA serving, LLM queue,
|
Covers: auth + session cookies, rate limiter, config, SPA serving, LLM queue,
|
||||||
telemetry parsing, entity signatures/profiles, prompt construction (incl.
|
telemetry parsing, entity signatures/profiles, prompt construction (incl.
|
||||||
Spanish language clauses), TTS effects chain, Codex REST, and the full WS
|
Spanish language clauses), TTS effects chain, Codex REST, the shop waitlist,
|
||||||
séance flow (auth, ping/pong, summon/mint/greet, streamed replies, anomaly
|
and the full WS séance flow (auth, ping/pong, summon/mint/greet, streamed
|
||||||
attunement, re-contact by signature).
|
replies, anomaly attunement, re-contact by signature). One test
|
||||||
|
(`test_unknown_path_serves_spa_index`) asserts the SPA fallback and needs
|
||||||
|
`frontend/dist/` to exist first — run `npm run build` in `frontend/` before
|
||||||
|
the full suite, or it 503s (correctly — that's the "frontend not built" error
|
||||||
|
path, also tested separately).
|
||||||
|
|
||||||
**Frontend** (Vitest + Testing Library — 71 tests across 6 files: the
|
**Frontend** (Vitest + Testing Library — 110 tests across 9 files: the
|
||||||
planchette state machine, the EVP detector core, the FFT, the reconnecting
|
reconnecting VeilSocket, the possession/haunting primitives, the séance
|
||||||
VeilSocket, the séance reducer, and the App shell):
|
reducer, the planchette state machine, the EVP detector core, the Matrix/Graph
|
||||||
|
board views, the FFT, the EMF field-fusion core, and the App shell):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend && npm test
|
cd frontend && npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hardware-in-the-loop** (cannot be unit tested, per the spec): the WebUSB
|
**Hardware-in-the-loop** (cannot be unit tested): the WebUSB RTL-SDR sweep,
|
||||||
RTL-SDR sweep and the microphone EVP flow each need a manual pass with real
|
the microphone EVP flow, and the phone EMF sensors each need a manual pass
|
||||||
hardware/permissions before being called done. Spirit Radio is Chromium-only
|
with real hardware/permissions before being called done.
|
||||||
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
|
## Current State & Known Gaps
|
||||||
(`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
|
Honestly, in order of confidence:
|
||||||
unbind.
|
|
||||||
|
- **Solid.** Auth, sessions, the Codex, the Wire Ghost, TTS + effects chain,
|
||||||
|
the LLM queue/fallback machinery, EVP's detection math, EMF's
|
||||||
|
motion-fusion math, i18n (en/es), and the haunting layer are all covered
|
||||||
|
by passing tests and use standard, well-supported browser/server APIs.
|
||||||
|
The full backend suite (54/54) and frontend suite (110/110) pass clean.
|
||||||
|
- **Needs a real-hardware pass.** `frontend/src/lib/sdr.ts` is a from-scratch
|
||||||
|
WebUSB driver for RTL2832U/R820T dongles, written against the public
|
||||||
|
librtlsdr register documentation — the init sequence, PLL tuning math, and
|
||||||
|
I2C repeater writes have **never been run against a real device** in this
|
||||||
|
environment (no RTL-SDR attached during development). It's explicitly
|
||||||
|
marked `HARDWARE PASS REQUIRED` throughout the file and every entry point
|
||||||
|
fails soft, but until someone plugs in a dongle and debugs the register
|
||||||
|
pokes (a logic analyzer or `librtlsdr -T` helps), treat Spirit Radio as
|
||||||
|
"should work, unverified." It's also Chromium-only (WebUSB), and on Linux
|
||||||
|
the kernel's `dvb_usb_rtl28xxu` driver usually needs to be unbound first
|
||||||
|
(the UI surfaces this with a fix suggestion when the claim fails).
|
||||||
|
- **Minor drift.** The landing page's mode showcase
|
||||||
|
(`frontend/src/pages/LandingPage.tsx`, `MODE_ORDER`) still lists four
|
||||||
|
modes (radio/evp/wire/ouija) — EMF is fully wired into the séance page,
|
||||||
|
the WebSocket protocol, and the i18n catalogs, but hasn't been added to
|
||||||
|
the landing page's marketing grid yet.
|
||||||
|
- **Cosmetic.** The frontend build ships one 871 KB JS chunk (Vite warns
|
||||||
|
about it); code-splitting would help first paint but hasn't been done.
|
||||||
|
- **Intentionally unfinished.** The Armory / Ultimate Quantum Box is a
|
||||||
|
waitlist only — no hardware exists, no payment is taken, and the page
|
||||||
|
says so.
|
||||||
|
|
||||||
## The Veil's Honesty Policy
|
## The Veil's Honesty Policy
|
||||||
|
|
||||||
@@ -301,10 +394,11 @@ unbind.
|
|||||||
fiction.
|
fiction.
|
||||||
- Wire Ghost telemetry reads counters and timings only. Packet payloads are
|
- Wire Ghost telemetry reads counters and timings only. Packet payloads are
|
||||||
never inspected or logged.
|
never inspected or logged.
|
||||||
- If the Ollama box is dark, every spirit channel degrades to curated offline
|
- If the Ollama box is dark, every spirit channel degrades to curated or
|
||||||
fallbacks — a summoning never visibly fails.
|
procedurally-generated offline fallbacks — a summoning never visibly fails.
|
||||||
|
|
||||||
## Further Reading
|
## Further Reading
|
||||||
|
|
||||||
- Design spec: `docs/superpowers/specs/2026-07-20-quantumancy-website-design.md`
|
- Design spec: `docs/superpowers/specs/2026-07-20-quantumancy-website-design.md`
|
||||||
|
(the live app now exceeds it — see Repo layout above)
|
||||||
- Implementation plans 1–7: `docs/superpowers/plans/`
|
- Implementation plans 1–7: `docs/superpowers/plans/`
|
||||||
|
|||||||
Reference in New Issue
Block a user