diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 8a42a9e..6f10539 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -42,8 +42,8 @@ not called even though `SdsClient` inherits it. Sent before the first observation. ```json -{"type": "board", "width": 16, "height": 17, - "hexes": [{"x": 3, "y": 4, "level": 1, "terrain": {"woods": 1}}]} +{"type": "board", "width": 16, "height": 17, "kinds": 24, + "hexes": [{"x": 3, "y": 4, "level": 1, "terrain": {"woods": 1}, "kind": 2}]} ``` Sparse: a hex at level 0 with no terrain is omitted. Coordinates are MegaMek's @@ -52,11 +52,17 @@ zero-based `(x, y)`, not the one-based numbers printed on a map sheet. Terrain that changes mid-fight — fire, smoke, a collapsed building — is **not** tracked. A bot that cares must currently do without. -`level` and `terrain` are everything movement needs from a hex: `crates/sds-core/src/pathfind.rs` -derives MegaMek's per-hex movement cost from the terrain names and their levels, -and its floor from `level` less the `water` depth. What movement cannot get from -the board is a property of the *unit*, which is why `movementMode` and -`maxElevationChange` are on the observation. +`kind` is which of the board's distinct hexes this is: hexes with the same +terrain share one, `kinds` is how many there are, and **kind 0 is the clear hex** +— which is what every hex the sparse message leaves out is. It exists so a +unit's `terrain` answers can be one entry per kind rather than one per hex; a +thousand-hex board is a few dozen kinds. A hex's *level* is not part of its +kind, because neither answer reads it. + +`level` and `terrain` are what a hex costs geometrically: `crates/sds-core/src/pathfind.rs` +takes its floor from `level` less the `water` depth. What terrain *costs*, and +whether a mover may enter it at all, is a property of the mover and is on the +unit as `terrain` — see below. ### `observation` — host to bot, before each decision @@ -69,6 +75,7 @@ the board is a property of the *unit*, which is why `movementMode` and "weight": 55.0, "bv": 1272, "bvOriginal": 1387, "walkMp": 5, "runMp": 8, "jumpMp": 5, "movementMode": "BIPED", "maxElevationChange": 2, + "terrain": {"mp": [0, 1, 2], "barred": [false, false, true]}, "armor": 128, "armorMax": 144, "internal": 91, "internalMax": 91, "heat": 4, "heatCapacity": 10, "heatBuildup": 2, "explodableAmmo": true, @@ -156,6 +163,24 @@ them throws more than one shot: `Single`/`Ultra` for an Ultra autocannon, alternatives `modes` carries — one declaration, more projectiles, rather than a different projectile — and `currentMode` says which the mount is set to now. +`terrain` is what this board's hexes cost this mover and which of them are shut +to it, one entry per board `kind`, in kind order. `mp` is +`Hex.movementCost(entity)` — MegaMek's own per-mover branch, so a tracked tank +pays two for the swamp a Mek pays one for and a hovercraft pays nothing for +either. `barred` is `Entity.isLocationProhibited`, which is the half a cost +model cannot express: heavy woods to a tracked tank, any woods to a hovercraft, +ultra-heavy woods to a Mek, water at any depth to a battle armor squad. Every +one of those hexes costs less than the mover's walk allowance, so a bot that +reads only `mp` proposes the hex and the server answers with a refused move. + +The answers are per whole hex and not per terrain, because they do not +decompose: water bars a squad at any depth, and water with ice on it does not. +A unit with no `terrain` — an observation recorded before the field existed — is +read as the ported biped Mek column, which is what every bot had before. +Movement is the same rule the weapons follow: a mover is its properties, never +its name, and a movement mode nobody has thought about comes out right because +the question was put to it. + `locations` is armour and structure where damage actually lands. The totals above it cannot answer a fire decision — a shot hits one location, so "does this allocation open the centre torso" needs the centre torso. `rearArmor` and