# The wire format One byte-array tuple over AppMessage, defined here and implemented twice — in `src/pkjs/index.js` (writer) and `src/c/main.c` (reader). **If you change one, change the other and the version byte.** A silent mismatch between them decodes into garbage rooms rather than failing, which is why the version byte exists at all. ## Why a byte array and not a dictionary Pebble's guaranteed AppMessage buffers are small (124 in / 636 out documented minimum, ~2 KB in practice for a JS-backed app), and a dictionary costs ~7 bytes of overhead per tuple. A ten-room plan as one tuple per room would spend more on keys than on rooms. As one packed array it is a few hundred bytes with room to spare. ## Coordinates are quantised to a byte Room polygons are already stored normalised 0.0–1.0 by `identity`. Multiply by 255 and round: a vertex costs 2 bytes and the error is under half a percent of the plan's width, which on a 170 px inscribed box is sub-pixel. The phone does the projection into the watch's inscribed box; the watch does no floating point at all. ## Layout ``` header u8 version 1 u8 room_count u8 unplaced_count u8 flags bit0: positions are available on this level per room (room_count times) u8 name_len u8[] name UTF-8, truncated to 24 bytes by the writer u8 state 0 = empty, 1 = occupied, 2 = unknown (drawn, never reported) u8 vertex_count u16[] vertices x,y each u8, so 2 bytes per vertex u8 target_count anonymous radar targets — somebody is there, nobody knows who u16[] targets x,y each u8 u8 occupant_count per occupant u8 colour_index 0..7 into identity's PERSON_COLORS, 255 = unknown u8 initial ASCII u8 occ_flags bit0: has a fused position u16 position x,y each u8 — PRESENT ONLY when bit0 is set u8 name_len u8[] name UTF-8, truncated to 16 bytes per unplaced person (unplaced_count times) u8 colour_index u8 initial u8 name_len u8[] name ``` ## Truncation, and why the writer does it The writer builds the payload and, if it exceeds the inbox size, sheds in this order: 1. **Occupant names** — the detail screen loses full names and falls back to initials. Worst outcome of the three and still legible. 2. **Rooms with no occupants** — an empty room is the least informative thing on the plan. 3. **Whole levels** — refuse, and say so on the watch. It never silently sends a partial structure. A payload that decodes half-way is worse than one that does not arrive, because the watch cannot tell the difference between "three rooms" and "three rooms and then the buffer ran out".