One JSON file per area, as named in spec §15.1: frames.json (encode/decode), gateway.json (frame → APRS-IS line or drop), inject.json (APRS-IS → mesh), text.json (compact text profile). Profile 0 (advert) vectors stay in kelly-page/bridge/igate_vectors.json. Every implementation runs the same files. The model is kelly-page/bridge/igate_vectors.json, which js/igate.js and bridge/kelly_igate.py both pass.
Each vector:
{
"id": "pos-001",
"spec": "§5.1",
"about": "a fresh advert with a valid position from a trusted key is gated",
"input": { "kind": "advert", "key": "…", "name": "VK4AZ Tracker", "lat": -27.6152, "lon": 152.7603, "rxAt": 1793255251994 },
"config": { "trusted": ["…"], "igate": "VK4AZ-10" },
"expect": { "out": "VK4AZ-7>APZKPG,qAO,VK4AZ-10:!2736.91S/15245.62E>…" }
}
A dropped input expects { "drop": "<reason>" }, using the reason names from spec §15.2.
frames.json (14 frames, 23 drops) and gateway.json (39 vectors, stateless pipeline steps 1-4 + rendering) are written, for header version 1. Every worked example in the spec is in them.kelly-page/js/meshprs.js (kelly-page/tests/meshprs-tests.html) and kelly-page/bridge/meshprs.py (kelly-page/bridge/test_meshprs.py).kelly-page/tests/meshprs-tests.html on a fake clock, not yet as shared JSON.inject.json (APRS-IS -> mesh), text.json (compact text), and the optional subtypes (weather, bulletin, neighbour report).