Something went wrong. Try again.
bayes for days
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241"""Every field the decision record keeps is one the observation actually sends.
`SdsClient.record` builds each `positions` entry by reading named fields off theobservation it just sent. Jackson's `JsonNode.path` does not fail on a name thatis not there - it returns a missing node, and `asInt()` on that is **0**,`asBoolean()` is **false**, `asText()` is **""**. So a typo does not throw anddoes not log: it writes a plausible figure into the only copy of the groundtruth, and every analysis built on it is wrong in a way nothing points at.
That is the defect this repository has paid for more than once - a `0.00`standing in for "no answer" - and it is worth a test rather than an eye.
The record is also the file three separate questions could not be answered fromin one night, so what it carries is checked here as well: the fields are listedbecause a summary that quietly narrows is how it got thin the first time."""
import reimport unittestfrom pathlib import Path
ROOT = Path(__file__).resolve().parent.parentCLIENT = ROOT / "bridge" / "sds" / "SdsClient.java"OBSERVATION = ROOT / "bridge" / "sds" / "Observation.java"
def block(path: Path, start: str, end: str) -> str: text = path.read_text() at = text.index(start) return text[at : text.index(end, at)]
def kept() -> set[str]: """Field names `record` writes into a `positions` entry.""" body = block(CLIENT, 'ArrayNode seen = line.putArray("positions")', "\n }") return set(re.findall(r'at\.put\("(\w+)"', body))
def read() -> set[str]: """Field names `record` reads off the observation to fill them.""" body = block(CLIENT, 'ArrayNode seen = line.putArray("positions")', "\n }") return set(re.findall(r'u\.path\("(\w+)"', body))
def emitted() -> set[str]: """Field names `Observation.unit` puts on the wire.""" body = block(OBSERVATION, "ObjectNode unit(", "\n }\n") return set(re.findall(r'u\.(?:put|set|putArray|putObject)\("(\w+)"', body))
def phase_counters() -> set[str]: """Counter names `SdsClient` writes into a seat's per-phase object.""" body = block(CLIENT, "ObjectNode phase = phases.putObject(", "\n }") return set(re.findall(r'phase\.put\("(\w+)"', body))
def expected_counters() -> set[str]: """Counter names `sds/runstats.py` reads back out of it.""" text = (ROOT / "sds" / "runstats.py").read_text() at = text.index("COUNTS = (") return set(re.findall(r'"(\w+)"', text[at : text.index(")", at)]))
class TestSeatCounters(unittest.TestCase): """The other half of the same seam, and it has already been wrong once.
`runstats.py` reads counters out of a `Counter`, so a name the client never writes does not raise - it reads **0**, for every match, forever. Two counters were added to `Tally` and to `COUNTS` and left out of the JSON, and nothing failed: the column would simply have been zero and read as "this never happens". """
def test_every_counter_the_stats_expect_is_one_the_client_writes(self) -> None: missing = expected_counters() - phase_counters() self.assertEqual( missing, set(), f"sds/runstats.py reads {sorted(missing)} which {CLIENT.name} never " "writes into a phase - it would read 0 rather than fail", )
def test_the_outcomes_account_for_every_decision(self) -> None: """Every decision lands in exactly one outcome, and the sum says so.
`answered`, `passed` and `neverAnswered` partition `decisions`, where `neverAnswered` is `unanswered + illegal + failed`. The point is not the arithmetic - it is that an outcome added later without a counter fails here instead of vanishing into the gap, which is exactly how `rejected` and `exhausted` once read zero forever and were believed.
Checked against the source rather than a run: every `tally.<x>++` inside a decision method has to be one of the names the partition knows. """ text = CLIENT.read_text() known = { "decisions", # the partition "answered", "passed", "unanswered", "illegal", "failed", "defaulted", # counted per rejection, deliberately outside it "rejected", # bookkeeping, not outcomes "eligible", "chose", "differed", "substituted", "prone", "pronePinned", "pinnedGyro", "pinnedNoMp", "pinnedLimb", "timedOut", # A duty carried out beside the decision rather than instead of it: # a unit can spot and shoot in the same turn, so this partitions # nothing and is counted because the cost is invisible otherwise. "spotted", # The firing mode a declared attack was made in, and the # denominator it is a share of. Counted per weapon attack rather # than per decision, so neither partitions the decisions either. "indirect", "declaredAttacks", } seen = set(re.findall(r"tally\.(\w+)\+\+", text)) self.assertEqual( seen - known, set(), "a counter is incremented that the outcome partition does not know " "about; add it to the partition or to the bookkeeping list, or it " "will not be accounted for in any match", )
def test_never_answered_is_derived_from_its_parts(self) -> None: """`neverAnswered` is computed, not counted, so it cannot drift.
A separately incremented counter is how two numbers that should agree stop agreeing. This one is written as the sum of the three things it rolls up, in both the per-phase object and the seat total. """ text = CLIENT.read_text() self.assertIn( "t.unanswered + t.illegal + t.failed", text, "the per-phase neverAnswered is no longer derived from its parts", ) self.assertIn( "unanswered + illegal + failed", text, "the seat-total neverAnswered is no longer derived from its parts", )
def test_declining_and_not_answering_are_counted_apart(self) -> None: """A bot that passed and a bot that never replied are different facts.
`defaulted` conflated them, and on one vehicle bench all 64 of its movement decisions were an immobilised unit answering correctly. An immobilised unit must not read as never-answered. """ text = CLIENT.read_text() self.assertIn("tally.unanswered++", text) self.assertIn("tally.passed++", text) # And nothing increments `defaulted` except the helper that splits it. bare = [n for n, line in enumerate(text.split("\n"), 1) if "tally.defaulted++" in line] self.assertEqual( len(bare), 1, f"defaulted is incremented outside countDeclined: lines {bare}" )
def test_the_retry_counters_reach_the_json(self) -> None: """Named, because they are the ones that were missing.
`rejected`, `passed`, `unanswered` and `neverAnswered` are what say what happened during a match. Silently zero, they would have said none of it ever happens. """ self.assertLessEqual( {"rejected", "passed", "unanswered", "neverAnswered"}, phase_counters() )
class TestDecisionRecord(unittest.TestCase): def test_every_field_the_record_reads_is_one_the_observation_sends(self) -> None: """The silent-zero guard.
A name here that the observation does not send is not an error at run time. It is a column of zeroes in the corpus. """ missing = read() - emitted() self.assertEqual( missing, set(), f"{CLIENT.name} reads {sorted(missing)} for `positions`, which " f'{OBSERVATION.name} never sends - Jackson would write 0/false/"" ' "into the record rather than fail", )
def test_the_record_still_carries_what_the_questions_needed(self) -> None: """A summary that looks complete is the reason nobody checks it.
Each of these was added because a real question could not be answered from this file: why a unit proposed nothing (the first four), what its allowance was when a move was refused (the next five), how to split a rate by mover type without matching on a name, and what the to-hit was computed from. """ required = { "immobile", "stuck", "elevationValid", "canStand", "walkMp", "runMp", "jumpMp", "jumpMpTerrain", "mpUsed", "movementMode", "gunnery", } self.assertEqual( required - kept(), set(), "the decision record has narrowed; see plan/observability.md", )
def test_the_reads_and_the_writes_line_up(self) -> None: """No field is written from a source that was never read, or vice versa.
`id`, `name` and `friendly` and the rest all come off the observation, so the two sets are the same set. A field written from a local variable instead would be the one thing this file cannot check, and there is not one. """ self.assertEqual(kept(), read())
if __name__ == "__main__": unittest.main()