Something went wrong. Try again.
bayes for days
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232"""Joining a win rate to what was in the match.
A win rate says better or worse. These tests are about the part that says atwhat - and about the two ways a join like this lies: counting a match whosescenario it could not find, and reporting a group of three matches as thoughthe number meant something."""
import jsonimport shutilimport tempfileimport unittestfrom pathlib import Path
from sds.correlate import MIN_MATCHES, Group, band, by, join, report, sds_won
def result(scenario, winner_kind, outcome="victory"): return { "scenario": f"/work/scenarios/{scenario}.mms", "outcome": outcome, "victoryTeam": 1 if winner_kind == "sds" else 2, "players": [ {"team": 1, "kind": "sds", "name": "sds North", "role": "sds North"}, {"team": 2, "kind": "princess", "name": "princess South", "role": "princess South"}, ], }
def meta(scenario, tier=8000, era="jihad", gunnery=4, piloting=5, mass=55, clan=0): return { "scenario": scenario, "size": 4, "era": era, "seed": 1, "map": "Map Set 2/16x17 BattleTech", "target_bv": tier, "bv": tier, "bv_miss": 0, "mean_gunnery": gunnery, "mean_piloting": piloting, "units": [ { "name": f"Design {i}", "gunnery": gunnery, "piloting": piloting, "bv": tier // 4, "bv_base": tier // 4, "mass": mass, "role": "Brawler", "weight_class": "Medium", "walk_mp": 4, "run_mp": 6, "jump_mp": 0, "total_armor": 150, "weapon_count": 6, "heat_efficiency": 1.0, "firepower": 20.0, "max_range": 15, "clan": clan, "engine": "Fusion", "armor": "Standard", "year": 3050, } for i in range(4) ], }
class CorrelateTest(unittest.TestCase): def setUp(self): self.run = Path(tempfile.mkdtemp()) self.suite = Path(tempfile.mkdtemp())
def tearDown(self): shutil.rmtree(self.run, ignore_errors=True) shutil.rmtree(self.suite, ignore_errors=True)
def write(self, scenario, winner_kind, outcome="victory", **kwargs): (self.run / f"{scenario}.json").write_text( json.dumps(result(scenario, winner_kind, outcome)) ) (self.suite / f"{scenario}.meta.json").write_text(json.dumps(meta(scenario, **kwargs)))
class TestJoining(CorrelateTest): def test_a_match_with_no_metadata_is_reported_not_counted(self): """The failure that would silently shrink every group.""" (self.run / "orphan.json").write_text(json.dumps(result("orphan", "sds"))) joined = join(self.run, self.suite) self.assertEqual(joined.joined, []) self.assertEqual(joined.missing_meta, {"orphan"})
def test_an_undecided_match_is_excluded(self): self.write("a", "sds", outcome="round-limit") joined = join(self.run, self.suite) self.assertEqual(joined.undecided, 1) self.assertEqual(joined.joined, [])
def test_the_winner_is_read_by_kind_not_by_seat(self): self.assertTrue(sds_won(result("x", "sds"))) self.assertFalse(sds_won(result("x", "princess"))) self.assertIsNone(sds_won(result("x", "sds", outcome="stalled")))
class TestGrouping(CorrelateTest): def test_win_rate_is_grouped_by_the_chosen_key(self): for i in range(6): self.write(f"hi{i}", "sds", tier=10000) for i in range(4): self.write(f"lo{i}", "princess", tier=4000) joined = join(self.run, self.suite) groups = by(joined.joined, lambda m: m["target_bv"]) self.assertEqual(groups["10000"].won, 6) self.assertEqual(groups["10000"].rate, 1.0) self.assertEqual(groups["4000"].won, 0)
def test_a_list_key_counts_the_match_in_every_group(self): """A force has four machines and the match is evidence about each.""" self.write("m", "sds") joined = join(self.run, self.suite) groups = by(joined.joined, lambda m: sorted({u["name"] for u in m["units"]})) self.assertEqual(len(groups), 4) self.assertTrue(all(g.total == 1 for g in groups.values()))
def test_thin_groups_are_summed_rather_than_given_an_interval(self): for i in range(3): self.write(f"t{i}", "sds", era="dark-age") for i in range(MIN_MATCHES + 2): self.write(f"f{i}", "princess", era="jihad") text = report(self.run, self.suite) self.assertIn("groups under", text) self.assertNotIn("| dark-age |", text)
def test_bands_put_a_number_in_a_row(self): self.assertEqual(band(35, (40, 60, 80)), "<40") self.assertEqual(band(60, (40, 60, 80)), "<80") self.assertEqual(band(95, (40, 60, 80)), ">=80")
class TestReport(CorrelateTest): def test_the_overall_line_matches_the_groups(self): for i in range(MIN_MATCHES): self.write(f"w{i}", "sds") for i in range(MIN_MATCHES): self.write(f"l{i}", "princess") text = report(self.run, self.suite) self.assertIn(f"**{MIN_MATCHES}-{MIN_MATCHES}, 50.0%**", text)
def test_it_says_mirrored_so_the_reader_knows_what_a_group_means(self): self.write("a", "sds") self.assertIn("mirrored", report(self.run, self.suite))
def test_an_empty_join_says_so_rather_than_dividing_by_zero(self): self.assertIn("Nothing to correlate", report(self.run, self.suite))
class TestGroupArithmetic(unittest.TestCase): def test_an_empty_group_claims_nothing(self): g = Group("x") self.assertEqual(g.total, 0) self.assertEqual(g.rate, 0.0) self.assertEqual(g.interval(), (0.0, 1.0))
class TestTerrainGrouping(unittest.TestCase): """A board name is not a group; the kind of ground on it is.
Eighty-nine boards across four hundred matches gives five matches a board, and five matches is a story rather than a measurement. Terrain aggregates across boards, so "half the fights on heavy woods" gets an interval. """
def test_a_board_that_cannot_be_found_groups_as_nothing(self): """Not as a board with no woods on it.""" from sds.correlate import terrain_band
self.assertIsNone(terrain_band({"map": "no such board"}, "woods", (0.1,))) self.assertIsNone(terrain_band({}, "woods", (0.1,))) self.assertIsNone(terrain_band({"map": ["a", "list"]}, "woods", (0.1,)))
def test_real_boards_land_in_the_bands_their_terrain_implies(self): from sds.correlate import terrain_band
heavy = terrain_band({"map": "Map Set 4/16x17 Heavy Forest 1"}, "woods", (0.1, 0.25, 0.5)) open_ = terrain_band({"map": "Map Set 5/16x17 Open Terrain 1"}, "woods", (0.1, 0.25, 0.5)) if heavy is None or open_ is None: self.skipTest("no MegaMek board data on this machine") self.assertEqual(heavy, "woods >=0.5") self.assertEqual(open_, "woods <0.1")
def test_terrain_summary_reads_fractions_not_counts(self): from sds.board import blank, terrain_summary
empty = terrain_summary(blank()) self.assertEqual(empty["woods"], 0.0) self.assertEqual(empty["water"], 0.0) self.assertEqual(empty["elevation_spread"], 0.0)
def test_an_empty_board_summarises_to_nothing(self): from sds.board import Board, terrain_summary
self.assertEqual(terrain_summary(Board(width=0, height=0)), {})
class TestUnattributedVictory(unittest.TestCase): """A victory MegaMek did not attribute is not a loss.
Eleven matches in a 418-game run came back `victory` with `victoryTeam: 0` while the players were on teams 1 and 2. Reading the seat map for team 0 gives None, and `None == "sds"` is False - so every one was counted as a defeat, and the run's rate came out 1.5 points below the baseline's. """
def test_a_victory_for_a_team_with_no_players_is_undecided(self): document = result("x", "sds") document["victoryTeam"] = 0 self.assertIsNone(sds_won(document))
def test_a_normal_victory_still_resolves(self): self.assertTrue(sds_won(result("x", "sds"))) self.assertFalse(sds_won(result("x", "princess")))
def test_it_agrees_with_the_summary_counter(self): """The two counters disagreeing is what surfaced this.""" from sds.stats import winner
document = result("x", "sds") document["victoryTeam"] = 0 self.assertIsNone(winner(document, "role")) self.assertIsNone(sds_won(document))