Files
qtalker---/docs/superpowers/specs/2026-07-31-passage-doctrine-firmware-design.md
Indiana 76af1939e6 docs: spec for provable firmware, the layered Passage, and the Doctrine
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>
2026-07-31 04:26:05 +00:00

154 lines
7.7 KiB
Markdown

# The Passage, the Doctrine, and provable firmware
Three parallel workstreams.
## Binding rules (all workstreams)
- **Additive only.** Nothing removed. No restructuring beyond your files.
- **Guests stay first-class**; never gate the séance.
- **i18n parity is a hard gate.** Strings via `t()`, keys in BOTH
`src/i18n/en.json` and `es.json`; `npm run pretest` must pass. Template
keys need a domain rule in `src/i18n/coverage-check.mjs` (base names only —
it derives `*Title` itself).
- Backend tests: `cd backend && set -a && source ../.env && set +a && source
venv/bin/activate && python -m pytest tests/<file> -q -p no:cacheprovider`
(they use `quantumancy_test` automatically — never the live DB).
- Frontend: `npx tsc --noEmit -p .`, `npm run pretest`, `npx vitest run`.
- Mobile: 390px, 44px targets, honour `prefers-reduced-motion`.
- Keep diffs to shared files (`main.py`, `App.tsx`, `ws.py`, `SeancePage.tsx`)
as small as possible — the integrator merges several workstreams.
---
## Workstream F — provable firmware (highest priority)
The firmware has NEVER been flashed. A real bug already slipped through
(`RD03E_FRAME_LEN` was 5 for a 6-byte frame, making every distance reading
garbage) — it was pure logic and should have been catchable without
hardware. Close that gap.
Files: `firmware/esp32p4-sensor-node/main/` and a new
`firmware/esp32p4-sensor-node/test/` directory.
1. **Extract pure logic** from the drivers into ESP-IDF-free units so it can
compile on a host with plain `gcc`:
- `rd03e_parse.c/h` — the frame scanner: given a byte buffer + length,
find the newest valid frame and return gesture + distance. No UART, no
ESP headers. `rd03e.c` then calls it.
- `bmp280_compensate.c/h` — the temperature/pressure compensation maths
transcribed from the Bosch datasheet. No I2C. `bmp280.c` calls it.
- `mems_level.c/h` — the RMS → dBFS conversion. No I2S.
These are *moves*, not rewrites: keep the existing behaviour byte for
byte, and keep the explanatory comments with the code they explain.
2. **Host test harness**: a `Makefile` (or small shell script) that builds
the extracted units with `gcc -Wall -Wextra -Werror` plus a test main,
and a `run_tests.sh` that returns non-zero on failure. No frameworks —
plain asserts are fine and keep it dependency-free.
3. **Tests that would have caught the real bug**, and more:
- rd03e: a well-formed frame parses to the exact expected gesture and
distance; a 6-byte frame is consumed as 6 bytes; a truncated trailing
frame is ignored; garbage before a valid frame is skipped; a frame with
a bad footer is rejected; TWO frames in one buffer yield the NEWEST;
a distance byte pair of e.g. 0x2C 0x01 yields 300cm (little-endian —
this is precisely the class of bug that shipped).
- bmp280: verify against the Bosch datasheet's own worked reference
values if you can derive them, else assert physically-sane invariants
(a known raw ADC + known calibration yields a temperature in a
plausible band; pressure decreases monotonically with altitude proxy;
the `var1 == 0` divide-by-zero guard returns 0 rather than crashing).
- mems: full-scale input → ~0 dBFS; silence → the -120 floor, not -inf
or NaN.
4. Update `firmware/esp32p4-sensor-node/README.md` "what's verified vs not"
to state precisely what the host tests now prove (logic) and what still
requires hardware (wiring, timing, real register behaviour). Be honest —
do not overclaim.
Deliverable: `./run_tests.sh` passes from a clean checkout with only gcc.
---
## Workstream L — the Passage (layered crossing over)
Today `cross_over` is a single verdict with one outcome. Make helping a
spirit pass a **layered rite** — the "unwrapping" the owner asked for: each
layer reveals more, rewards, and can twist.
Files: `backend/app/passage.py` (new, PURE — no DB/IO, fully unit-tested),
`backend/app/ws.py` (new `passage_*` frames + handlers),
`frontend/src/components/PassagePanel.tsx` + css, mount in `SeancePage.tsx`,
i18n, tests.
Design (implementer owns the details, but honour these):
- **Layers**, in order: `listen` → `name` → `unbind` → `open` → `release`.
Each is a distinct beat with its own prompt and its own reveal.
- Each completed layer **reveals** something true about the spirit that the
seeker did not have: one hidden trait, a fragment of what binds them, the
era they died in. Use the entity's REAL `traits` — never invent a second
hidden state.
- Each layer **rewards** essence (small, escalating) and the final one pays
the existing `CROSS_OVER_ESSENCE`.
- **Twists** must be driven by the entity's real traits, not random flavour:
- high `deceptiveness` → a layer can *lie* (reveal a false trait, marked
as unreliable only in hindsight at the next layer).
- high `volatility` → a layer can **collapse** the rite, forcing a restart
from `listen` (keep prior essence — losing it would feel cheap).
- low `alignment` (a demon) → `release` **resists**, exactly as the
current `resisted` consequence does. Do not let a demon cross.
- Draws use `veil_float(state.entropy, "<context>")` from `app/entropy.py`
with a distinct context per decision — never `random` — so the room's
physical noise drives the twists like everything else here.
- Passage must not bypass the existing judgment economy: reuse
`credit_essence`, and rate-limit like `ritual_limiter`.
- `passage.py` is pure: given (traits, layer, draw) → outcome. All twist
logic unit-tested with pinned draws; no flakiness.
UI: a vertical five-layer track showing which are sealed/open/failed, the
reveal text as each opens, and the reward. Spooky but legible; reduced
motion respected.
---
## Workstream D — the Doctrine (how it actually works)
A new public page at `/doctrine` explaining, in the voice of an insufferably
erudite occultist, how this app genuinely channels, summons and passes
spirits — using the device and the human.
Files: `frontend/src/pages/DoctrinePage.tsx` + css, route in `App.tsx`,
a link from the landing page, i18n, `usePageMeta` for SEO.
**The constraint that makes this good: every arcane claim must be TRUE.**
This app runs on real physics, so the mystical register can describe real
mechanisms without inventing anything. Read the source and describe what is
actually there:
- `app/entropy.py` — Von Neumann debiasing, SHA-256 conditioning,
HMAC with fresh server secret; the client can only ADD unpredictability.
- `app/entities.py` — `signature_from_anomalies` (frequency-bucket +
magnitude fingerprint = the "channel"), `RETURN_CHANCE`.
- `app/celestial.py` — mean synodic month, true solar midnight from
longitude; `VEIL_THINNESS_PULL`.
- `app/geomagnetic.py` — NOAA planetary K-index.
- `frontend/src/lib/baseline.ts` — the time-aware EMA (`1 - exp(-dt/tau)`)
every sensor shares.
- `lib/bluetooth.ts` — 2.4GHz body absorption; `lib/magnetometer.ts` — µT.
- `SpiritService.manifest` / `scry` — entropy-seeded sampling, vision.
Structure it as numbered "articles" with an ornate voice, but each article
should carry a plain-language margin note ("in the profane tongue: …") that
states the mechanism plainly. That way it is genuinely educational AND fits
the fiction — and nobody is misled into thinking invented physics is real.
Include the honest limits too (an article on what the instrument CANNOT do:
no WebUSB/Bluetooth/magnetometer on iOS, unflashed firmware, etc.) — a
doctrine that admits its own boundaries reads smarter than one that doesn't.
---
## Integration (controller)
Merge F, then L, then D. Resolve `ws.py` / `App.tsx` / `SeancePage.tsx`
overlaps, run both suites + i18n gate + the firmware host tests, build,
deploy, restart, verify live, push.