package taste import ( "math" "testing" ) func almostEqual(a, b float64) bool { return math.Abs(a-b) < 1e-9 } func TestEngagement_Curve(t *testing.T) { p := DefaultEngagementParams() // HardSkip 0.05, Neutral 0.30, Full 0.90 cases := []struct { name string completion float64 want float64 }{ {"instant skip", 0.00, -1}, {"at hard-skip boundary", 0.05, -1}, {"neg ramp midpoint", 0.175, -0.5}, // (0.175-0.05)/(0.30-0.05)=0.5 → -0.5 {"neutral point", 0.30, 0}, {"pos ramp midpoint", 0.60, 0.5}, // (0.60-0.30)/(0.90-0.30)=0.5 {"at full boundary", 0.90, 1}, {"complete", 1.00, 1}, {"clamp above 1", 1.50, 1}, {"clamp below 0", -0.5, -1}, } for _, c := range cases { if got := Engagement(c.completion, p); !almostEqual(got, c.want) { t.Errorf("%s: Engagement(%.3f) = %.4f, want %.4f", c.name, c.completion, got, c.want) } } } func TestEngagement_MonotonicNonDecreasing(t *testing.T) { p := DefaultEngagementParams() prev := math.Inf(-1) for i := 0; i <= 100; i++ { c := float64(i) / 100 got := Engagement(c, p) if got < prev-1e-9 { t.Fatalf("engagement decreased at completion %.2f: %.4f < %.4f", c, got, prev) } prev = got } } func TestEngagement_DegenerateParamsStayInRange(t *testing.T) { // Non-increasing thresholds must collapse ramps (no divide-by-zero) and // still produce a value in [-1, 1]. p := EngagementParams{HardSkip: 0.5, NeutralCompletion: 0.5, FullCompletion: 0.5} for _, c := range []float64{0, 0.25, 0.5, 0.75, 1} { got := Engagement(c, p) if got < -1 || got > 1 { t.Errorf("Engagement(%.2f) = %.3f out of [-1,1]", c, got) } } } func TestDecay_HalfLife(t *testing.T) { if got := decay(0, 75); !almostEqual(got, 1) { t.Errorf("decay(0) = %.4f, want 1", got) } if got := decay(75, 75); !almostEqual(got, 0.5) { t.Errorf("decay(halflife) = %.4f, want 0.5", got) } if got := decay(150, 75); !almostEqual(got, 0.25) { t.Errorf("decay(2*halflife) = %.4f, want 0.25", got) } if got := decay(-5, 75); !almostEqual(got, 1) { t.Errorf("decay(negative age) = %.4f, want 1 (clamped)", got) } if got := decay(10, 0); !almostEqual(got, 1) { t.Errorf("decay(halflife=0) = %.4f, want 1 (guarded)", got) } }