feat(sim): emotions are a right-brain output (Right knob alone)

The affect channel previously gated on BOTH knobs (strength = min(left, right)),
so the analytical Left brain co-controlled the emotion words. Per operator
direction, emotions are now driven by the Right (dreamlike) knob ALONE — Left no
longer gates them. This sharpens the split: Left = measurement (detections +
measurements), Right = the dream AND the feeling it evokes.

- player/alteration.py: `_affect_strength(right) = right` (was min(left,right));
  `_affect_intensity` keyed off Right. AffectOverlay + is_identity docs updated
  (is_identity still holds: right>0 ⇒ dream.strength>0).
- simulator/static/app.js: renderAffect comments (gating is Right via `strength`);
  the client logic already keyed min_level/tier off the passed strength + right.
- Affect design spec: revision note + every min(left,right) → right.
- Tests rewritten: affect is Right-driven (full Right + Left 0 surfaces affect;
  Right 0 + Left 4 surfaces none).

271 passed, 2 skipped. Live API verified: left=0/right=4 → strength 4; left=4/
right=0 → strength 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-26 04:29:21 -07:00
parent 5bb762ae53
commit c3aa419c53
4 changed files with 71 additions and 56 deletions
+6 -5
View File
@@ -456,10 +456,11 @@ function renderOverlay(level, intensity) {
lastOverlay = { level, intensity };
}
// Affect channel: soft, glowing emotion-words surfaced only when BOTH knobs are
// up. `strength` = min(left, right); `intensity` is the layer opacity. Words are
// placed at authored scene points (no boxes — feelings are scene-level) and read
// softer than the clinical reticles — the dream leaking into the machine's read.
// Affect channel: soft, glowing emotion-words — a RIGHT-brain output, surfaced by
// the Right (dreamlike) knob alone (`strength` = right; the Left analytical knob no
// longer gates them). `intensity` is the layer opacity. Words are placed at authored
// scene points (no boxes — feelings are scene-level) and read softer than the
// clinical reticles — feeling, not measurement.
function renderAffect(strength, intensity, right) {
const clip = activeClip();
if (!affectLayer) return; // tolerate a stale page missing the affect layer
@@ -472,7 +473,7 @@ function renderAffect(strength, intensity, right) {
const [x, y] = f.at.map((n) => n * 100);
const t = svg("text", { x, y, "text-anchor": "middle", class: "hud-affect" }, affectLayer);
// RIGHT emotion tier: the vocabulary escalates basic -> compound as the Right
// knob rises (appearance still gated by min(left,right) above).
// knob rises (appearance is gated by the Right knob via `strength` above).
const raw = strings[f.key];
t.textContent = pickTier(raw !== undefined ? raw : f.key, clamp(right || 0, 1, tierCount(raw)));
}