Something went wrong. Try again.
bayes for days
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212"""What terrain costs each movement mode, and what it bars, read off the probe.
`sds move-probe` asks MegaMek itself - a real board, one real specimen permovement mode, a hex painted with a named stack of terrain - and writes`scenarios/move/observed.txt`. These tests read that table rather than arulebook, so what they pin is behaviour somebody observed on a named MegaMekrelease.
Two things are being pinned. The first is that `pathfind`'s terrain column isone branch of MegaMek's and not the only one: a hovercraft is refused the woodsa Mek walks through, a tracked tank pays two for the swamp a Mek pays one for,and a hex a mover may not enter costs it well under its walk allowance, so thebudget check the search already does never sees it.
The second is the fix's own model. The bot cannot ask `MovePath.isMoveLegal` -it wants a path, and a path is a candidate that has not been proposed yet - soit is handed `Hex.movementCost` and `Entity.isLocationProhibited` per hex andholds them against its own elevation and budget rules. Whether those threetogether come to MegaMek's verdict is the question the `model=` column answers,and `test_the_model_never_offers_a_hex_megamek_refuses` is what makes it atest."""
import reimport unittestfrom pathlib import Path
OBSERVED = Path(__file__).resolve().parent.parent / "scenarios" / "move" / "observed.txt"
# Modes the movement search is ever run for. A helicopter is not one of them:# `pathfind` is ground movement and jumps, and a VTOL's elevation is its own# question - see the module docs there, and `plan/candidates.md`.GROUND_MODES = {"BIPED", "QUAD", "TRACKED", "HOVER", "WHEELED", "INF_LEG", "INF_JUMP"}
def rows() -> list[dict]: """One dict per cell, straight off the probe's lines.""" out = [] for line in OBSERVED.read_text().splitlines(): if not line.startswith("move "): continue out.append(dict(re.findall(r"([\w/]+)=(\S+)", line))) return out
class TestObservedMovement(unittest.TestCase): def setUp(self) -> None: self.rows = rows() self.assertTrue(self.rows, f"{OBSERVED} has no cells; run `sds move-probe`")
def cell(self, mode: str, case: str) -> dict: found = [r for r in self.rows if r["mode"] == mode and r["case"] == case] self.assertTrue(found, f"no {mode} cell for {case}") return found[0]
def test_every_movement_mode_is_covered(self) -> None: """One specimen per branch MegaMek takes, or the table speaks for one of them.""" modes = {row["mode"] for row in self.rows} self.assertEqual(modes, GROUND_MODES | {"VTOL"}, modes)
def test_the_column_is_not_the_same_for_every_mover(self) -> None: """The defect, stated as a table: four terrains that answer per mode.
A Mek's discount in the swamp is "less one for having legs" and it is the Mek's alone; heavy industrial costs a Mek and nobody else; thin snow costs a foot platoon and a wheeled tank; a hovercraft is over the ground hazards rather than in them. """ self.assertEqual(self.cell("BIPED", "swamp")["column"], "1") for mode in ("TRACKED", "WHEELED", "INF_LEG", "INF_JUMP"): self.assertEqual(self.cell(mode, "swamp")["column"], "2", mode) self.assertEqual(self.cell("HOVER", "swamp")["column"], "0")
self.assertEqual(self.cell("BIPED", "industrial")["column"], "1") for mode in ("TRACKED", "HOVER", "WHEELED", "INF_LEG", "INF_JUMP"): self.assertEqual(self.cell(mode, "industrial")["column"], "0", mode)
self.assertEqual(self.cell("INF_LEG", "snow-thin")["column"], "1") self.assertEqual(self.cell("WHEELED", "snow-thin")["column"], "1") for mode in ("BIPED", "QUAD", "TRACKED", "HOVER", "INF_JUMP"): self.assertEqual(self.cell(mode, "snow-thin")["column"], "0", mode)
for case in ("mud", "ice", "snow-heavy", "rapids-2"): self.assertEqual(self.cell("HOVER", case)["column"], "0", case)
def test_some_terrain_is_refused_rather_than_priced(self) -> None: """The half `pathfind` did not have at all.
Every one of these costs less than the mover's walk allowance, so a search that reads only the column offers the hex and the refusal arrives at the server as an illegal order. """ # A Mek: ultra-heavy woods and ultra-heavy jungle, and nothing lighter. for mode in ("BIPED", "QUAD"): self.assertEqual(self.cell(mode, "woods-ultra")["prohibited"], "yes", mode) self.assertEqual(self.cell(mode, "jungle-ultra")["prohibited"], "yes", mode) self.assertEqual(self.cell(mode, "woods-heavy")["prohibited"], "no", mode) # A tracked tank takes light woods and nothing heavier, and no jungle. self.assertEqual(self.cell("TRACKED", "woods-light")["prohibited"], "no") self.assertEqual(self.cell("TRACKED", "woods-heavy")["prohibited"], "yes") self.assertEqual(self.cell("TRACKED", "jungle-light")["prohibited"], "yes") # A hovercraft takes none of it, and no water it would have to leave. self.assertEqual(self.cell("HOVER", "woods-light")["prohibited"], "yes") # Rough 2 and rubble 6 are shut to every vehicle and open to a Mek. for case in ("rough-2", "rubble-6"): self.assertEqual(self.cell("BIPED", case)["prohibited"], "no", case) for mode in ("TRACKED", "HOVER", "WHEELED"): self.assertEqual(self.cell(mode, case)["prohibited"], "yes", (mode, case)) # Infantry and battle armor: water at any depth, magma at any level. for mode in ("INF_LEG", "INF_JUMP"): for case in ("water-1", "water-2", "water-3", "magma-crust", "magma-liquid"): self.assertEqual(self.cell(mode, case)["prohibited"], "yes", (mode, case))
def test_a_hex_is_not_the_sum_of_its_terrains(self) -> None: """Why the answers are per hex and not per terrain.
Water bars a squad at any depth. Water with ice on it does not: the squad walks over the ice. A profile assembled terrain by terrain would have to refuse it, and a search that refuses every frozen lake on the board is not describing the game. """ for mode in ("INF_LEG", "INF_JUMP"): self.assertEqual(self.cell(mode, "water-1")["prohibited"], "yes", mode) self.assertEqual(self.cell(mode, "ice-over-water")["prohibited"], "no", mode)
def test_the_model_never_offers_a_hex_megamek_refuses(self) -> None: """The acceptance test for the fix, in the direction that matters.
A hex the model refuses that MegaMek allows costs a candidate. A hex the model offers that MegaMek refuses is an illegal order and a forfeited turn, and there must be none of those on any mode the search is run for. """ offered = [ row for row in self.rows if row["mode"] in GROUND_MODES and row["legal"] == "no" and row["model"] == "allows" ] self.assertEqual(offered, [], offered)
def test_what_the_model_gives_up_is_written_down(self) -> None: """And the other direction, which is a stated limit rather than a bug.
One rule the model does not carry, and it costs candidates rather than turns: infantry may take a step they cannot afford - a foot platoon walks one and pays two for heavy woods, and MegaMek allows it. See `plan/candidates.md` for the rules question it is waiting on. """ lost = [ row for row in self.rows if row["mode"] in GROUND_MODES and row["legal"] == "yes" and row["model"] == "refuses" ] modes = {row["mode"] for row in lost} self.assertEqual(modes, {"INF_LEG", "INF_JUMP"}, modes)
class TestWalkedBoards(unittest.TestCase): """Every one-step move on the corpus's four boards, for every specimen.
The painted cells above ask about a hex. These ask about a step, which is what the search actually makes, and they ask it of a real board - whose hexes carry terrain no case list would think to paint, the incline markers MegaMek writes at a slope among them. """
def setUp(self) -> None: self.walks = [ dict(re.findall(r"([\w/-]+)=(\S+)", line)) | {"unit": line.split()[1], "mode": line.split()[2]} for line in OBSERVED.read_text().splitlines() if line.startswith("walk ") ] self.assertTrue(self.walks, f"{OBSERVED} has no board walks; run `sds move-probe`")
def test_no_mover_is_offered_a_step_megamek_refuses(self) -> None: """The acceptance test for the gate, over every step of four boards.
Nine specimens, four boards, both directions of every hexside: not one step that the model allows and `MovePath.isMoveLegal` refuses. This is the assertion the fix exists for, and it is the one that must never be relaxed - a hex offered and refused is a forfeited turn. """ offered = [w for w in self.walks if w["offered-and-refused"] != "0"] self.assertEqual(offered, [], offered)
def test_the_mek_is_exact_and_the_movers_are_not_yet(self) -> None: """What is left, per mover, in the two directions that cost candidates.
A Mek agrees with MegaMek on every step of every board, in legality and in price. A vehicle's legality agrees and its *price* does not - it pays two points a level where the port charges one - and infantry pay the terrain rather than the step. Both are stated in `plan/candidates.md`; both cost candidates, and the price one also costs a route, which is what the pinned refusals in `pathfind_routes.jsonl` are. """ for walk in self.walks: if walk["mode"] in ("BIPED", "QUAD"): self.assertEqual(walk["refused-and-legal"], "0", walk) self.assertEqual(walk["mispriced"], "0", walk) vehicles = [w for w in self.walks if w["mode"] in ("TRACKED", "HOVER")] self.assertTrue(vehicles) for walk in vehicles: self.assertEqual(walk["refused-and-legal"], "0", walk) # And the price is where they part company, on every board with a slope # on it. self.assertTrue(any(int(w["mispriced"]) > 0 for w in vehicles))
if __name__ == "__main__": unittest.main()