- Add voice button and voice flow services for speech interaction - Add text-to-speech (TTS) service and narration - Add fullscreen button component - Update dice logic with hardened numDice fallback - Add voice integration tests - Update themes, game routes, and intent classifier - Add developer notes and QA directory
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
"""
|
|
Tests for the single-d6 dice engine.
|
|
|
|
Formula: target = 6 - stat + penalty - talent_bonus (clamped 1-6)
|
|
"""
|
|
|
|
import random
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
from app.game.dice import roll_d6, resolve_test, DIFFICULTY_PENALTY
|
|
|
|
|
|
class TestRollD6:
|
|
def test_single_die_range(self):
|
|
for _ in range(100):
|
|
assert 1 <= roll_d6(random.Random(42)) <= 6
|
|
|
|
def test_deterministic(self):
|
|
assert roll_d6(random.Random(123)) == roll_d6(random.Random(123))
|
|
|
|
|
|
class TestDifficultyPenalty:
|
|
def test_values(self):
|
|
assert DIFFICULTY_PENALTY == {"let": 0, "normal": 1, "svaert": 2}
|
|
|
|
|
|
class TestResolveTest:
|
|
def _rate(self, pony, stat, diff, n=100):
|
|
return sum(
|
|
1 for i in range(n)
|
|
if resolve_test(pony, stat, diff, random.Random(i))["passed"]
|
|
)
|
|
|
|
def test_single_die(self):
|
|
r = resolve_test({"krop": 4}, "krop", "let", random.Random(42))
|
|
assert len(r["dice"]) == 1
|
|
|
|
def test_default_stat(self):
|
|
r = resolve_test({}, "krop", "let", random.Random(42))
|
|
assert len(r["dice"]) == 1
|
|
|
|
# stat 5, let: target = 6-5+0-0 = 1 -> 100%
|
|
def test_stat5_let_always(self):
|
|
assert self._rate({"krop": 5}, "krop", "let") == 100
|
|
|
|
# stat 5 + talent, let: target = 6-5+0-1 = 0 -> clamped to 1 -> 100%
|
|
def test_stat5_talent_let(self):
|
|
assert self._rate({"krop": 5, "talent": "krop"}, "krop", "let") == 100
|
|
|
|
# stat 4, let: target = 6-4 = 2 -> roll 2+ = 83%
|
|
def test_stat4_let(self):
|
|
assert 70 <= self._rate({"krop": 4}, "krop", "let") <= 95
|
|
|
|
# stat 3, let: target = 6-3 = 3 -> roll 3+ = 67%
|
|
def test_stat3_let(self):
|
|
assert 50 <= self._rate({"krop": 3}, "krop", "let") <= 85
|
|
|
|
# stat 2, let: target = 6-2 = 4 -> roll 4+ = 50%
|
|
def test_stat2_let(self):
|
|
assert 35 <= self._rate({"krop": 2}, "krop", "let") <= 65
|
|
|
|
# stat 3, normal: target = 6-3+1 = 4 -> roll 4+ = 50%
|
|
def test_stat3_normal(self):
|
|
assert 35 <= self._rate({"krop": 3}, "krop", "normal") <= 65
|
|
|
|
# stat 2, normal: target = 6-2+1 = 5 -> roll 5+ = 33%
|
|
def test_stat2_normal(self):
|
|
assert 20 <= self._rate({"krop": 2}, "krop", "normal") <= 50
|
|
|
|
# stat 3, svaert: target = 6-3+2 = 5 -> roll 5+ = 33%
|
|
def test_stat3_svaert(self):
|
|
assert 20 <= self._rate({"krop": 3}, "krop", "svaert") <= 50
|
|
|
|
# stat 2, svaert: target = 6-2+2 = 6 -> roll 6 = 17%
|
|
def test_stat2_svaert(self):
|
|
assert 5 <= self._rate({"krop": 2}, "krop", "svaert") <= 30
|
|
|
|
# stat 4 + talent, svaert: target = 6-4+2-1 = 3 -> roll 3+ = 67%
|
|
def test_talent_helps_hard(self):
|
|
assert 50 <= self._rate({"krop": 4, "talent": "krop"}, "krop", "svaert") <= 85
|
|
|
|
# stat 3 + talent, normal: target = 6-3+1-1 = 3 -> roll 3+ = 67%
|
|
def test_talent_helps_normal(self):
|
|
assert 50 <= self._rate({"krop": 3, "talent": "krop"}, "krop", "normal") <= 85
|
|
|
|
# talent on wrong stat: no bonus
|
|
def test_no_talent_wrong_stat(self):
|
|
pony = {"krop": 3, "talent": "sind"}
|
|
assert self._rate(pony, "krop", "let") == self._rate({"krop": 3}, "krop", "let") |