feat(sim): analytical Left HUD redesign + affect channel + no-cache

Left-brain HUD look-and-feel pass (judged by eye in the sim):
- corner-bracket targeting reticles (capped arms) replace plain boxes
- translucent label chips, legible over any frame
- two sensor channels: cyan detections (with deterministic confidence
  scores) vs amber measurements (center crosshair)
- bbox-sized "ANALYSIS . L{n} . {k} OBJ" status tag, escalating with Left

Affect channel (emotions in the HUD), per
docs/superpowers/specs/2026-06-22-affect-channel-hud-design.md:
- surfaces only when BOTH knobs up; strength = min(left, right)
- stable per-scene palette, more words appear with combined strength
- soft glowing violet words on their own opacity layer (the dream
  leaking into the machine's read); math in player/alteration.py
- forest/cosmos/abyss palettes in the manifest; 5 new unit tests

Simulator no-cache middleware so by-eye iteration never serves a stale
app.js / api response (fixes the latent "plan has affect but clip.affect
empty" stale-/api/clips bug).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-22 23:11:02 -07:00
parent 9cf6324bb4
commit 6adb93407b
9 changed files with 315 additions and 16 deletions
@@ -0,0 +1,85 @@
# Affect channel — emotions in the Left-brain HUD
**Status:** approved (session 0013, 2026-06-22)
**Surface:** simulator preview (`simulator/`, `player/alteration.py`)
**Builds on:** the machine-altered-perception design SPEC; the Left HUD look-and-feel
pass (same session).
## 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 appear only when *both* the analytical (Left) and dreamlike (Right) knobs are
up — the dream leaking into the machine's 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 = `min(left, right)` (04). Either knob
at 0 → no emotions at all. Opacity scales with that strength × `overlay_gain`.
- **Escalation.** A **stable emotional palette per scene** — higher combined
strength reveals *more* words at higher opacity. Each word carries a `min_level`
(the same mechanic the detection channel uses), but compared against the
*combined* `min(left, right)` strength rather than Left alone.
- **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 = min(left, right)`; `intensity = clamp(overlay_gain *
min(left,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: `min(left,right) > 0` implies `left > 0`, which already
makes `overlay.level > 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).