# 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/ -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, "")` 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.