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

118 lines
4.7 KiB
Python

"""Carries: Kontrollen, Zielstufe, Zerlegung."""
from tft.model import carries
UNITS = {a: {"cost": 3, "traits": ["T"]} for a in ("A", "B", "CARRY", "TANK")}
UNITS["TEUER"] = {"cost": 5, "traits": ["T"]}
TRAITS = {"T": {"breakpoints": [{"min_units": 1}, {"min_units": 3}]}}
def board(place, level, items_total, *ents):
"""ents: (api, sterne, itemzahl)"""
return {"place": place, "level": level, "items": items_total,
"entities": list(ents)}
def prepared(boards):
return carries.prepare(boards, UNITS, TRAITS)
def test_other_three_stars_are_part_of_the_comparison_group():
"""Wer eine Unit auf 3★ hat, hatte Gold übrig — das Board ist deshalb gut.
Die eigenen 3★ zählen dabei nicht mit, sonst würde man den Effekt
misst, den man erklären will.
"""
b = prepared([board(1, 9, 3, ("A", 3, 3), ("B", 3, 0))])[0]
assert carries.cell(b, 3) == (9, 3, 1) # eigene 3★ abgezogen
assert carries.cell(b, 2) == (9, 3, 2) # aus Sicht einer 2★-Unit beide
def test_reroll_carries_are_shown_on_three_stars():
"""Auf 2★ negativ, auf 3★ positiv: so spielt man diesen Champion."""
boards = prepared([board(6, 9, 3, ("A", 2, 3)) for _ in range(60)]
+ [board(1, 9, 3, ("A", 3, 3)) for _ in range(60)])
base = carries.baseline(boards)
out = carries.values(boards, base, UNITS, min_boards=10)
assert out["A"]["stars"] == 3 and out["A"]["edge"] > 0
def test_expensive_units_are_never_shown_on_three_stars():
"""Ein 5-Kosten auf 3★ heißt: das Spiel war schon gewonnen.
In den Daten holen 5-Kosten auf 3★ in 98 % der Fälle Platz 1.
"""
boards = prepared([board(6, 9, 3, ("TEUER", 2, 3)) for _ in range(60)]
+ [board(1, 9, 3, ("TEUER", 3, 3)) for _ in range(60)])
base = carries.baseline(boards)
out = carries.values(boards, base, UNITS, min_boards=10)
assert out["TEUER"]["stars"] == 2
def test_units_below_the_rate_are_kept_but_not_carries():
"""Ein Tank ohne Items bleibt in der Tabelle — er ist der Maßstab.
Der Buchstabe im Reiter vergleicht mit allen Units, nicht nur mit
den Carries.
"""
boards = prepared([board(1, 9, 3, ("CARRY", 2, 3), ("TANK", 2, 0))
for _ in range(60)]
+ [board(4, 9, 3, ("CARRY", 2, 3), ("TANK", 2, 3))
for _ in range(20)])
base = carries.baseline(boards)
out = carries.values(boards, base, UNITS, min_boards=10)
assert out["CARRY"]["carry"] and not out["TANK"]["carry"]
def test_rare_units_are_dropped():
boards = prepared([board(1, 9, 3, ("A", 2, 3)) for _ in range(9)])
base = carries.baseline(boards)
assert carries.values(boards, base, UNITS, min_boards=10) == {}
def test_reasons_separate_unit_from_the_rest():
"""Zwei Carries, gleiche Traits, aber einer steht neben besseren Units.
Der Unterschied muss im Topf "rest" landen, nicht in "unit".
"""
boards = prepared(
[board(2, 9, 3, ("A", 2, 3), ("TANK", 2, 0)) for _ in range(400)]
+ [board(2, 9, 3, ("B", 2, 3), ("CARRY", 2, 0)) for _ in range(400)]
+ [board(7, 9, 3, ("TANK", 2, 3)) for _ in range(400)]
+ [board(1, 9, 3, ("CARRY", 2, 3)) for _ in range(400)])
base = carries.baseline(boards)
beta, idx, own_of = carries._weights(boards, base, UNITS, carries.RIDGE)
why = carries.reasons(boards, base, own_of, idx, beta, min_boards=100)
a, b = why[("A", 2)], why[("B", 2)]
# B steht neben CARRY (gut), A neben TANK (schlecht).
assert b["rest"] > a["rest"]
def test_build_is_empty_without_data():
assert carries.build([], UNITS, TRAITS, min_boards=10) == {}
def test_enabler_measures_the_same_carry_with_and_without():
"""Stützt die Unit fremde Carries — oder steht sie nur daneben?
CARRY läuft mit B besser als ohne. B trägt selbst nie Items, kann den
Vorsprung also nicht selbst erklären.
"""
boards = prepared(
[board(2, 9, 3, ("CARRY", 2, 3), ("B", 2, 0)) for _ in range(400)]
+ [board(6, 9, 3, ("CARRY", 2, 3), ("TANK", 2, 0)) for _ in range(400)])
base = carries.baseline(boards)
lift = carries.enabler_edge(boards, base, UNITS, min_pair=100, min_total=100)
assert lift["B"] > 0 > lift["TANK"]
def test_supported_is_relative_to_comparable_boards():
"""Ein Carry mit starken Stützern liegt über dem Üblichen."""
boards = prepared(
[board(2, 9, 3, ("A", 2, 3), ("B", 2, 0)) for _ in range(400)]
+ [board(6, 9, 3, ("CARRY", 2, 3), ("TANK", 2, 0)) for _ in range(400)])
base = carries.baseline(boards)
lift = {"B": 0.4, "TANK": -0.4}
prop = carries.supported(boards, base, lift, min_boards=100)
assert prop[("A", 2)] > prop[("CARRY", 2)]