update
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import json
|
||||
|
||||
from tft.model.learn import learn
|
||||
from tft.model.learn import MEAN_PLACEMENT, learn
|
||||
|
||||
# Bester Fall: alle Boards auf Platz 1, also 3.5 Plätze über dem Schnitt.
|
||||
MEAN_PLACEMENT_DELTA = MEAN_PLACEMENT - 1
|
||||
|
||||
|
||||
def board_row(placement, unit="TFT17_A", item="TFT_Item_X", trait_tier=1):
|
||||
@@ -18,9 +21,19 @@ def test_strong_item_gets_multiplier_above_1():
|
||||
|
||||
|
||||
def test_shrinkage_with_thin_data():
|
||||
"""Drei Boards dürfen den Multiplikator kaum bewegen.
|
||||
|
||||
Der Vergleich läuft gegen DELTA_SCALE, nicht gegen eine feste Zahl:
|
||||
3 gegen 30 Pseudo-Boards lassen höchstens ein Zehntel des vollen
|
||||
Effekts durch, egal wie stark die Skala eingestellt ist.
|
||||
"""
|
||||
from tft.model.learn import DELTA_SCALE
|
||||
|
||||
rows = [board_row(1, item="TFT_Item_Rare") for _ in range(3)]
|
||||
learned = learn(rows)
|
||||
assert abs(learned["items"]["TFT_Item_Rare"] - 1.0) < 0.06
|
||||
thin = learn(rows)["items"]["TFT_Item_Rare"]
|
||||
full = learn([board_row(1, item="TFT_Item_Rare") for _ in range(2000)])
|
||||
assert abs(thin - 1.0) < 0.1 * MEAN_PLACEMENT_DELTA * DELTA_SCALE
|
||||
assert full["items"]["TFT_Item_Rare"] > thin
|
||||
|
||||
|
||||
def test_trait_keys_match_score_format():
|
||||
@@ -29,20 +42,38 @@ def test_trait_keys_match_score_format():
|
||||
assert "TFT17_T@3" in learned["traits"]
|
||||
|
||||
|
||||
def test_pair_lift_for_cooccurring_units():
|
||||
def pair_row(placement):
|
||||
units = [
|
||||
{"character_id": "TFT17_A", "tier": 2, "itemNames": []},
|
||||
{"character_id": "TFT17_B", "tier": 2, "itemNames": []},
|
||||
]
|
||||
def test_pair_surplus_finds_real_synergy():
|
||||
"""Zwei Units, die einzeln mittelmäßig sind, aber zusammen gewinnen."""
|
||||
def row(placement, *ids):
|
||||
units = [{"character_id": i, "tier": 1, "itemNames": []} for i in ids]
|
||||
return (placement, json.dumps(units), json.dumps([]), json.dumps([]))
|
||||
|
||||
# A und B stehen immer zusammen in Top-4-Boards; C ist überall.
|
||||
rows = [pair_row(1) for _ in range(100)]
|
||||
solo = [{"character_id": "TFT17_C", "tier": 1, "itemNames": []}]
|
||||
rows += [(1, json.dumps(solo), json.dumps([]), json.dumps([])) for _ in range(100)]
|
||||
learned = learn(rows)
|
||||
assert learned["pairs"]["TFT17_A|TFT17_B"] > 0.5
|
||||
rows = []
|
||||
# A und B einzeln mit Füllern: Mittelfeld.
|
||||
rows += [row(4, "A", "X") for _ in range(120)]
|
||||
rows += [row(5, "A", "Y") for _ in range(120)]
|
||||
rows += [row(4, "B", "X") for _ in range(120)]
|
||||
rows += [row(5, "B", "Y") for _ in range(120)]
|
||||
# Zusammen: Platz 1.
|
||||
rows += [row(1, "A", "B") for _ in range(120)]
|
||||
pairs = learn(rows)["pairs"]
|
||||
assert pairs["A|B"]["surplus"] > pairs["A|X"]["surplus"]
|
||||
assert pairs["A|B"]["surplus"] > 0
|
||||
|
||||
|
||||
def test_pair_surplus_cannot_separate_units_that_never_split():
|
||||
"""Bekannte Grenze: stehen zwei Units immer zusammen, zählt der
|
||||
Einzelwert beide Male dieselbe Stärke — der Überschuss kippt ins Minus.
|
||||
|
||||
In echten Daten fällt das kaum ins Gewicht: nur 9 von 1784 Paaren
|
||||
hängen zu über 50 % aneinander, der Median liegt bei 2 %.
|
||||
"""
|
||||
def row(placement):
|
||||
units = [{"character_id": i, "tier": 1, "itemNames": []} for i in ("A", "B")]
|
||||
return (placement, json.dumps(units), json.dumps([]), json.dumps([]))
|
||||
|
||||
pairs = learn([row(1) for _ in range(120)])["pairs"]
|
||||
assert pairs["A|B"]["surplus"] < 0
|
||||
|
||||
|
||||
def test_empty_rows():
|
||||
@@ -86,3 +117,62 @@ def test_augment_spec_builder():
|
||||
|
||||
junk = build_spec({"desc": "", "effects": {}}, units)
|
||||
assert junk["offerable"] is False
|
||||
|
||||
|
||||
def test_conditional_clause_does_not_leak_into_atoms():
|
||||
"""Nur der unbedingte Satz zählt — effects["Gold"] ist der bedingte Nachschlag."""
|
||||
from tft.model.augments import build_spec
|
||||
|
||||
units = {}
|
||||
survivor = build_spec(
|
||||
{"desc": "Gain 4 gold. After 3 players are eliminated, gain 100 more gold.",
|
||||
"effects": {"Gold": 100.0, "InitialGold": 4.0, "PlayersDead": 3.0}}, units)
|
||||
assert survivor["atoms"] == [{"kind": "gold_now", "amount": 4}]
|
||||
|
||||
avenge = build_spec(
|
||||
{"desc": "After 3 allies die, your team gains 28% Attack Damage.",
|
||||
"effects": {"AD": 28.0}}, units)
|
||||
assert avenge["atoms"] == []
|
||||
|
||||
# Wiederkehrend, aber unbedingt: bleibt erhalten.
|
||||
each_round = build_spec(
|
||||
{"desc": "Your team gains 2% Attack Damage per level. "
|
||||
"Gain 2 XP each round for the rest of the game.", "effects": {}}, units)
|
||||
assert each_round["atoms"] == [{"kind": "xp_per_round", "amount": 2}]
|
||||
|
||||
|
||||
def test_pair_surplus_isolates_the_synergy():
|
||||
"""Ein Paar zählt nur, was es über seine zwei Units hinaus bringt.
|
||||
|
||||
A ist stark, B schwach. Zusammen laufen sie genau so, wie die Summe
|
||||
erwarten lässt — also kein Überschuss.
|
||||
"""
|
||||
from tft.model.learn import pair_surplus, value
|
||||
|
||||
a_only = [1] * 100
|
||||
b_only = [8] * 100
|
||||
together = [1, 8] * 50
|
||||
units = {"A": value(a_only + together[::2]), "B": value(b_only + together[1::2])}
|
||||
pairs = pair_surplus({("A", "B"): together}, units)
|
||||
assert abs(pairs["A|B"]["surplus"]) < 0.5
|
||||
assert pairs["A|B"]["boards"] == 100
|
||||
|
||||
|
||||
def test_pair_surplus_needs_enough_boards():
|
||||
from tft.model.learn import PAIR_MIN_N, pair_surplus, value
|
||||
|
||||
units = {"A": 0.0, "B": 0.0}
|
||||
thin = pair_surplus({("A", "B"): [1] * (PAIR_MIN_N - 1)}, units)
|
||||
thick = pair_surplus({("A", "B"): [1] * PAIR_MIN_N}, units)
|
||||
assert thin == {}
|
||||
assert "A|B" in thick
|
||||
# Unbekannte Units fliegen raus statt still als 0 zu zählen.
|
||||
assert pair_surplus({("A", "X"): [1] * PAIR_MIN_N}, units) == {}
|
||||
|
||||
|
||||
def test_value_shrinks_towards_average():
|
||||
from tft.model.learn import value
|
||||
|
||||
assert value([1] * 3) < value([1] * 300) # wenige Boards -> naher an 0
|
||||
assert value([8] * 300) < 0 < value([1] * 300)
|
||||
assert abs(value([])) < 1e-9
|
||||
|
||||
Reference in New Issue
Block a user