Files
qtalker---/docs/superpowers/specs/2026-07-23-possession-presentation-design.md
Indiana bf8f352910 Add Possession Presentation Layer design spec
First sub-project of the "make contact feel real" arc (candle rituals,
quantum RNG, tuning, progression, escalation to follow as separate specs).
Defines the stability-score contract shared between the backend audio
degradation and frontend text-glitch halves so they can build in parallel.
2026-07-23 05:24:41 +00:00

3.7 KiB
Raw Blame History

Possession Presentation Layer — design

First sub-project of a larger "make contact feel real" arc (candle rituals, quantum RNG, tuning controls, account progression, session escalation — each gets its own later spec). This one: make a successful Direct Contact reply feel like a spirit is fighting through static to possess the channel, rather than a chat bubble appearing. Cosmetic only — the stored transcript and reply text are unaffected; only the live presentation (text stream + voice) carries the effect.

Contract

A single number, stability (0.05–0.98, higher = cleaner possession), computed once per reply on the backend and shared by both halves of this build:

# backend/app/possession.py
def compute_stability(rarity: str, magnitude: float, rng: Callable[[], float] = random.random) -> float
  • Base by rarity: common=0.35, uncommon=0.5, rare=0.65, mythic=0.8 (rarer spirits hold the channel more steadily).
  • + min(0.15, magnitude / 100) — the anomaly magnitude that most recently triggered contact; a stronger signal, a cleaner line.
  • + rng() jitter scaled to ±0.1.
  • Clamped to [0.05, 0.98].
  • rng is injectable specifically so the later quantum-RNG sub-project can swap in real entropy without touching call sites.

_handle_question in backend/app/ws.py computes this once per question (using state.entity.rarity_tier and the magnitude of the most recent anomaly in state.anomalies, defaulting to 50 if none yet) and adds it to the existing reply_start frame:

{"type": "reply_start", "stability": 0.62}

Backend half (audio degradation)

synthesize_spirit_voice(text, voice, voice_profile, instability=0.0) in backend/app/tts/piper.py gains an instability param (0.0–1.0, i.e. 1 - stability) that scales up the noise_level and bitcrush_bits already passed to apply_effects — do not touch effects.py itself, only the params synthesize_spirit_voice feeds it. Only the "reply" kind utterance in _speak (the Direct Contact answer) passes a non-zero instability; greetings/fragments/ambient whispers are unaffected by this spec.

Frontend half (text glitch renderer)

frontend/src/lib/possession.ts — pure function(s) that take the true-so-far streamed text, the stability score, and a tick/frame counter, and return a display string with corruption bursts and brief stutters that self-correct. Lower stability = more frequent/longer corruption; stability above ~0.85 is nearly clean (a subtle flicker only). Must be deterministic given its inputs (seed any internal randomness from the tick counter, not Math.random()) so it's unit-testable.

Wire into frontend/src/state/seance.tsx: extend replyStreaming with a stability: number field, populated from reply_start's stability (default 1.0 if absent). Wire into frontend/src/components/Transcript.tsx: render the streaming line through the glitch function instead of raw text, driven by a ticking interval while streaming.active.

Testing

  • Backend: unit tests for compute_stability (rarity ordering, magnitude bonus, clamping, injected rng determinism) and for the instability→params scaling in synthesize_spirit_voice. One WS integration test asserting reply_start carries a stability field in range.
  • Frontend: unit tests for the glitch renderer's pure functions at known low/high stability values (corruption rate, self-correction, determinism for a fixed tick sequence).

Explicitly out of scope here

New WS frame types for interleaved glitch tokens, structural UI takeover, candle rituals, quantum RNG source, tuning dial, account progression, session escalation — all separate later specs per the arc this belongs to.