# Affect channel — emotions in the Left-brain HUD

**Status:** approved (session 0013, 2026-06-22); **revised session 0018, 2026-06-26**
**Surface:** simulator preview (`simulator/`, `player/alteration.py`)
**Builds on:** the machine-altered-perception design SPEC; the Left HUD look-and-feel
pass (same session).

> **Revision (session 0018) — affect is a RIGHT-brain output.** The original design
> gated emotions on *both* knobs (`strength = min(left, right)`). Per operator
> direction, **emotions are now controlled by the Right (dreamlike) knob alone** —
> the analytical Left knob no longer gates them. This sharpens the left/right split:
> Left = measurement (detections + measurements), Right = the dream **and** the
> feeling it evokes. Every `min(left, right)` below now reads `right`. The data
> shape, rendering, and per-word `min_level` mechanic are unchanged — only the knob
> the strength is keyed off.

## Idea

A third HUD channel beside the cyan *detections* and amber *measurements*: soft,
glowing **emotion-words** that surface the feelings the experience may invoke.
They are a **right-brain output: driven by the dreamlike Right knob alone** (the
analytical Left knob does not gate them) — the dream surfacing as the machine's
felt reading. This is the uncanny edge of the thesis: the machine trying to
quantify not just what is there, but how you are meant to feel.

## Behaviour

- **Trigger / intensity.** Affect strength = `right` (0–4). Right at 0 → no
  emotions at all, regardless of Left. Opacity scales with that strength ×
  `overlay_gain`.
- **Escalation.** A **stable emotional palette per scene** — higher Right reveals
  *more* words at higher opacity. Each word carries a `min_level` (the same
  mechanic the detection channel uses), compared against the `right` strength.
- **Rendering.** Floating words at authored scene positions — **no boxes or
  reticles**, lowercase, semi-transparent with a soft glow, in a distinct **violet**
  register so they read as affective and clearly *softer* than the hard clinical
  reticles.

## Where the math lives

Consistent with the existing split (alteration math in Python, label-selection in
the client):

- `player/alteration.py` gains an `AffectOverlay(strength, intensity)` on the
  `RenderPlan`. `strength = right`; `intensity = clamp(overlay_gain * right /
  KNOB_MAX, 0, 1)`. Returned in `render_plan_to_dict` under `"affect"`. Pure,
  unit-tested.
- The simulator client picks *which* words to show by comparing each affect
  entry's `min_level` to `plan.affect.strength`, and renders them at
  `plan.affect.intensity` opacity.

`is_identity` is unaffected: `affect.strength == right`, and `right > 0` already
makes `dream.strength > 0`.

## Data

New optional per-clip `affect` list in the manifest, parallel to `annotations`:

```json
"affect": [
  {"key": "feel.awe", "at": [0.5, 0.4], "min_level": 1},
  {"key": "feel.serenity", "at": [0.2, 0.62], "min_level": 2}
],
"strings": {"en": {"feel.awe": "awe", "feel.serenity": "serenity"}}
```

`at` is a normalized `[x, y]` anchor point (not a box — feelings are scene-level).
The words live in the existing `strings.en` map. `Clip` gains an `affect` field
(defaulting to `[]`) threaded through `_clip_from_dict` and `to_dict`.

Starter palettes (words are art-direction, easily tuned):

| scale  | palette (min_level 1 → 4)                     |
|--------|-----------------------------------------------|
| forest | awe · serenity · reverence · stillness        |
| cosmos | wonder · vastness · insignificance · longing  |
| abyss  | unease · fascination · isolation · dread      |

All three scales carry the data; forest is fully realized (it has the real Right
variants).

## Testing

- Python unit tests: affect `strength = min(left, right)` across knob combos;
  intensity scaling and `overlay_gain`; `0` when either knob is 0; presence in the
  serialized plan.
- By-eye in the live simulator (forest, Left & Right both up) — the established
  judge-by-eye loop for this surface.

## Out of scope

- No drift-toward-unsettling arc (rejected in favour of a stable palette).
- No per-object emotion tags; no animated drift (words are statically placed for
  v1; gentle motion can come later if wanted).
