bayes for days
sds plan protocol.md
13 kB
Markdown


id: protocol title: The observation carries what a bot needs to reason status: open dependsOn: [] exitCriterion: > Every feature in the basis can be computed from what the wire carries, without the bot reaching into MegaMek. #

protocol #

The observation is the interface. What is not in it does not exist for any bot. Adding a field is deliberate, with a reason in the commit message.

Fixed already: WeaponType.getDamage() returns -2 for cluster weapons, so the bot valued every missile below nothing. There is now no plain damage field. The armour getters have the same shape of trap - ARMOR_NA, ARMOR_DOOMED and ARMOR_DESTROYED are -1, -2 and -3 - and the bridge converts them to zero.

The corpus tests the arithmetic and never the arguments #

Three bugs in one week where the differential passed and real play failed. They are one bug.

  • MASC. The observation sent getRunMP(), which folds in a boost this bot never declares. A Stalking Spider with walk 5 was offered a run of 10 where the rules give 8.
  • Deployment elevation. Units were deployed at a hard-coded elevation 0. A Mek in depth-1 water belongs at -1, and from an elevation isElevationValid refuses, MegaMek refuses every path out of the hex for the rest of the match.
  • Carried MP. MovePath.getMpUsed starts at Entity.mpUsed rather than at zero, so the allowance the bot was handed was larger than the one MegaMek enforced, by whatever the unit had already spent.

The movement corpus could not see any of the three, and the reason is the same each time: it is handed the inputs and asked about the arithmetic. A prone case is --prone-case x,y,facing,budget. The budget is an argument to the fixture and a derived quantity in a match, so the fixture can prove "given allowance B we agree with MegaMek about what B reaches" and can never prove "B is the allowance". Every one of the three bugs was in B.

The tell is in the fixture itself. SdsPathfind.search calls unit.fixElevation(), unit.setDone(false), unit.setHullDown(false) and, until this epic, left mpUsed at whatever a freshly loaded scenario has. Each of those lines normalises a piece of entity state that a live movement phase does not normalise - the second bug is literally the harness repairing the state the host creates. A line in a fixture that cleans up entity state is a class of bug that fixture can no longer see, and there are four of them in eleven lines.

The same split runs through the Rust side. pathfind_differential.rs builds its Walker as a struct literal, so it tests search, which was correct, and never Walker::running(&unit), which was not: mpUsed had been on the wire for months, documented, and read by the standing plan's key, and nothing ever read it for the budget. A field can be carried, parsed, and dropped, and no test here was shaped to notice.

What to do about it, cheapest first.

Not yet needed: the observation is sent per decision, ~450KB a round at 8v8. Sending it once per phase would remove most of that. Measured at 291us against ~10ms of thinking, so it is tidiness, not performance.