Files
tft/backend/tests/test_rating.py
2026-09-07 07:44:09 +02:00

80 lines
3.0 KiB
Python

"""Boardbewertung: Merkmale, Baumdurchlauf, Grenzfälle."""
import pytest
from tft.model import rating
STATIC = {
"units": {
"U_TANK": {"cost": 1, "traits": ["T_BRAWL"], "name": "Tank"},
"U_CARRY": {"cost": 4, "traits": ["T_BRAWL", "T_SHOT"], "name": "Carry"},
"U_FIVE": {"cost": 5, "traits": ["T_SHOT"], "name": "Five"},
},
"traits": {
"T_BRAWL": {"breakpoints": [{"min_units": 2}, {"min_units": 4}]},
"T_SHOT": {"breakpoints": [{"min_units": 2}]},
},
}
def board(*specs):
return [{"api_name": a, "stars": s, "items": list(i)} for a, s, i in specs]
@pytest.fixture
def art():
return {"static": STATIC, "component_pool": ["C_SWORD"], "item_pool": ["I_BLADE"],
"rating": {"tables": {}}}
def test_trait_counts_and_tiers():
b = board(("U_TANK", 1, []), ("U_CARRY", 1, []), ("U_FIVE", 1, []))
counts = rating.trait_counts(b, STATIC["units"])
assert counts == {"T_BRAWL": 2, "T_SHOT": 2}
assert rating.trait_tiers(counts, STATIC["traits"]) == {"T_BRAWL": 1, "T_SHOT": 1}
# Eine Unit unter dem Breakpoint aktiviert nichts.
single = rating.trait_counts(board(("U_TANK", 1, [])), STATIC["units"])
assert rating.trait_tiers(single, STATIC["traits"]) == {}
def test_features_are_stable_in_length(art):
empty = rating.features([], 1, art)
full = rating.features(board(("U_CARRY", 3, ["I_BLADE", "C_SWORD"])), 9, art)
assert len(empty) == len(full)
assert all(isinstance(x, (int, float)) for x in empty + full)
def test_unknown_units_are_ignored(art):
known = rating.features(board(("U_TANK", 1, [])), 5, art)
mixed = rating.features(board(("U_TANK", 1, []), ("NOPE", 3, [])), 5, art)
assert known == mixed
def test_no_model_means_no_prediction(art):
assert rating.predict_placement(board(("U_TANK", 1, [])), 5, art) is None
assert rating.board_rating(board(("U_TANK", 1, [])), 5, art) is None
def test_tree_walk_and_clamping(art):
# Ein Baum, der nur auf die Unit-Zahl schaut (Merkmal 4).
art["rating"] = {"tables": {}, "base": 4.0, "lr": 1.0,
"trees": [[4, 1.5, 2.0, -2.0]]}
few = rating.predict_placement(board(("U_TANK", 1, [])), 3, art)
many = rating.predict_placement(
board(("U_TANK", 1, []), ("U_CARRY", 1, []), ("U_FIVE", 1, [])), 9, art)
assert few == 6.0 and many == 2.0
assert rating.board_rating(board(("U_TANK", 1, [])), 3, art) == pytest.approx(-1.5)
# Ausreißer werden auf 1 bis 8 gestutzt — es gibt keinen Platz 0.
art["rating"]["trees"] = [[4, 1.5, 99.0, -99.0]]
assert rating.predict_placement(board(("U_TANK", 1, [])), 3, art) == 8.0
assert rating.predict_placement(
board(("U_TANK", 1, []), ("U_CARRY", 1, [])), 9, art) == 1.0
def test_learned_tables_reach_the_features(art):
art["rating"]["tables"] = {"unit": {"U_CARRY": 1.5}}
plain = rating.features(board(("U_TANK", 1, [])), 5, art)
good = rating.features(board(("U_CARRY", 1, [])), 5, art)
assert good != plain