feat: add escalation engine with logarithmic curve and stochastic timing
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import math
|
||||
import time
|
||||
|
||||
from server.escalation import EscalationEngine
|
||||
|
||||
|
||||
class TestIntensityCurve:
|
||||
def test_intensity_at_zero(self):
|
||||
"""Intensity is 0 at start."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
assert engine.get_intensity(elapsed=0.0) == 0.0
|
||||
|
||||
def test_intensity_at_40s(self):
|
||||
"""Intensity ~1.0 at 40s with default rate."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
intensity = engine.get_intensity(elapsed=40.0)
|
||||
assert abs(intensity - math.log(1 + 40 * 0.05)) < 0.01
|
||||
|
||||
def test_intensity_monotonic(self):
|
||||
"""Intensity always increases."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
prev = 0.0
|
||||
for t in [10, 30, 60, 120, 300, 600, 1800]:
|
||||
val = engine.get_intensity(elapsed=float(t))
|
||||
assert val > prev
|
||||
prev = val
|
||||
|
||||
def test_intensity_never_peaks(self):
|
||||
"""Even at 1 hour, intensity is still rising."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
at_30m = engine.get_intensity(elapsed=1800.0)
|
||||
at_60m = engine.get_intensity(elapsed=3600.0)
|
||||
assert at_60m > at_30m
|
||||
|
||||
|
||||
class TestPhaseParams:
|
||||
def test_low_intensity_params(self):
|
||||
"""Low intensity produces slow, subtle params."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
params = engine.get_phase_params(intensity=0.5)
|
||||
assert params["morph_speed"] < 0.3
|
||||
assert params["shader_severity"] < 0.3
|
||||
|
||||
def test_high_intensity_params(self):
|
||||
"""High intensity produces fast, severe params."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
params = engine.get_phase_params(intensity=4.0)
|
||||
assert params["morph_speed"] > 0.6
|
||||
assert params["shader_severity"] > 0.7
|
||||
|
||||
def test_params_are_clamped(self):
|
||||
"""Params stay in 0-1 range even at extreme intensity."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
params = engine.get_phase_params(intensity=100.0)
|
||||
for key in ["morph_speed", "shader_severity", "voice_frequency", "noise_level"]:
|
||||
assert 0.0 <= params[key] <= 1.0
|
||||
|
||||
|
||||
class TestAssetSwapInterval:
|
||||
def test_low_intensity_slow_swaps(self):
|
||||
"""Low intensity means long intervals between swaps."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
interval = engine.get_asset_swap_interval(intensity=0.5)
|
||||
assert interval >= 5.0
|
||||
|
||||
def test_high_intensity_fast_swaps(self):
|
||||
"""High intensity means short intervals."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
interval = engine.get_asset_swap_interval(intensity=5.0)
|
||||
assert interval <= 4.0
|
||||
|
||||
def test_interval_has_randomness(self):
|
||||
"""Consecutive calls produce different intervals."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
intervals = [engine.get_asset_swap_interval(intensity=2.0) for _ in range(20)]
|
||||
assert len(set(round(i, 2) for i in intervals)) > 1
|
||||
|
||||
|
||||
class TestVoiceInterval:
|
||||
def test_low_intensity_rare_voices(self):
|
||||
"""Low intensity = voices are rare."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
# Run many trials since exponential distribution is stochastic
|
||||
intervals = [engine.get_voice_interval(intensity=0.5) for _ in range(50)]
|
||||
mean = sum(intervals) / len(intervals)
|
||||
assert mean > 20.0 # Mean should be around 40
|
||||
|
||||
def test_high_intensity_frequent_voices(self):
|
||||
"""High intensity = voices are frequent."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
intervals = [engine.get_voice_interval(intensity=5.0) for _ in range(50)]
|
||||
mean = sum(intervals) / len(intervals)
|
||||
assert mean < 20.0
|
||||
|
||||
|
||||
class TestSessionTiming:
|
||||
def test_start_sets_time(self):
|
||||
"""Starting a session records the start time."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
engine.start_session()
|
||||
assert engine.session_start is not None
|
||||
|
||||
def test_elapsed_time(self):
|
||||
"""Elapsed time increases after start."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
engine.start_session()
|
||||
elapsed = engine.get_elapsed()
|
||||
assert elapsed >= 0.0
|
||||
|
||||
def test_reset_clears_session(self):
|
||||
"""Reset restarts the session."""
|
||||
engine = EscalationEngine(rate=0.05)
|
||||
engine.start_session()
|
||||
time.sleep(0.01)
|
||||
engine.reset()
|
||||
assert engine.get_elapsed() < 0.1
|
||||
Reference in New Issue
Block a user