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.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```json
|
||||
{"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.
|
||||
Reference in New Issue
Block a user