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:
@@ -55,42 +55,44 @@ def test_left_and_right_stack_not_cancel():
|
||||
assert plan.dream.intensity == 1.0
|
||||
|
||||
|
||||
def test_affect_needs_both_knobs_up():
|
||||
# affect-channel design (session 0013): emotion-words surface only when BOTH
|
||||
# the Left and Right knobs are up — either at 0 means no affect at all.
|
||||
def test_affect_is_driven_by_the_right_knob_alone():
|
||||
# Right-brain affect (session 0018): emotion-words are controlled by the Right
|
||||
# knob alone — Right at 0 means no affect regardless of Left, and Left no longer
|
||||
# gates the channel (full Right with Left at 0 still surfaces affect).
|
||||
assert plan_alteration(_coord(left=4, right=0)).affect.strength == 0
|
||||
assert plan_alteration(_coord(left=0, right=4)).affect.strength == 0
|
||||
assert plan_alteration(_coord(left=4, right=0)).affect.intensity == 0.0
|
||||
assert plan_alteration(_coord(left=0, right=4)).affect.intensity == 0.0
|
||||
assert plan_alteration(_coord(left=0, right=4)).affect.strength == 4
|
||||
assert plan_alteration(_coord(left=0, right=4)).affect.intensity == 1.0
|
||||
|
||||
|
||||
def test_affect_strength_is_the_smaller_knob():
|
||||
# strength = min(left, right) — the smaller knob gates the channel.
|
||||
assert plan_alteration(_coord(left=2, right=4)).affect.strength == 2
|
||||
def test_affect_strength_is_the_right_knob():
|
||||
# strength = right — Left is irrelevant to the affect channel.
|
||||
assert plan_alteration(_coord(left=2, right=4)).affect.strength == 4
|
||||
assert plan_alteration(_coord(left=4, right=2)).affect.strength == 2
|
||||
assert plan_alteration(_coord(left=4, right=4)).affect.strength == 4
|
||||
assert plan_alteration(_coord(left=3, right=1)).affect.strength == 1
|
||||
assert plan_alteration(_coord(left=0, right=3)).affect.strength == 3
|
||||
assert plan_alteration(_coord(left=4, right=1)).affect.strength == 1
|
||||
|
||||
|
||||
def test_affect_intensity_scales_with_strength_over_full_scale():
|
||||
for lo in range(5):
|
||||
plan = plan_alteration(_coord(left=lo, right=4))
|
||||
assert plan.affect.intensity == pytest.approx(lo / 4)
|
||||
assert plan_alteration(_coord(left=4, right=4)).affect.intensity == 1.0
|
||||
def test_affect_intensity_scales_with_right_over_full_scale():
|
||||
for r in range(5):
|
||||
plan = plan_alteration(_coord(left=0, right=r))
|
||||
assert plan.affect.intensity == pytest.approx(r / 4)
|
||||
assert plan_alteration(_coord(left=0, right=4)).affect.intensity == 1.0
|
||||
|
||||
|
||||
def test_affect_intensity_honors_overlay_gain():
|
||||
# affect rides the same overlay_gain as the analytical HUD it belongs to.
|
||||
# affect text rides the overlay-opacity gain, but is keyed off the Right knob.
|
||||
cal = Calibration(overlay_gain=0.5)
|
||||
assert plan_alteration(_coord(left=4, right=4), cal).affect.intensity == pytest.approx(0.5)
|
||||
assert plan_alteration(_coord(left=0, right=4), cal).affect.intensity == pytest.approx(0.5)
|
||||
clamp = Calibration(overlay_gain=10.0)
|
||||
assert plan_alteration(_coord(left=4, right=4), clamp).affect.intensity == 1.0
|
||||
assert plan_alteration(_coord(left=0, right=4), clamp).affect.intensity == 1.0
|
||||
|
||||
|
||||
def test_affect_active_implies_non_identity():
|
||||
# min(left,right) > 0 implies left > 0, so an active affect plan is never identity.
|
||||
plan = plan_alteration(_coord(left=2, right=3))
|
||||
assert plan.affect.strength == 2
|
||||
# affect.strength == right, and right > 0 makes dream.strength > 0, so an active
|
||||
# affect plan (even with Left at 0) is never identity.
|
||||
plan = plan_alteration(_coord(left=0, right=3))
|
||||
assert plan.affect.strength == 3
|
||||
assert not plan.is_identity
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user