Skip to content

feat(origin): where a packet started and how each area heard it (Data tab 2/6) - #15

Closed
A13xB0 wants to merge 6 commits into
masterfrom
feat/data-origin
Closed

A13xB0 wants to merge 6 commits into
masterfrom
feat/data-origin

Conversation

@A13xB0

@A13xB0 A13xB0 commented Oct 10, 2026

Copy link
Copy Markdown
Owner

This is the second of six PRs for the Data tab (plan).

Stacked on #14. This branch contains #14's four commits. Review #14 first; once it is merged, this PR's diff shrinks to the two internal/origin commits.

What it adds

A new package, internal/origin (its own module, depending only on areamap). It works out where a packet started and how each area heard it. It is plain functions over a packet, its observations and the node positions. Nothing calls it yet; PR 3 (the data index) will.

Where it started. The first rule that gives an answer wins:

  1. The advert's own position, if it has one that is not 0,0.
  2. The sender's node record, for an advert without a position.
  3. The first relay, by majority across observations, using CoreScope's resolved key, then the prefix.
    • A 1-byte hop never places anything.
    • A shared prefix places only when every holder with a known position stands in one area.
  4. The observer's own area, for a packet heard with no relay at all.
  5. Otherwise unknown. This is counted, never guessed.

How each area heard it, one entry per receiving area:

  • Observers: distinct observers there, matched case-insensitively.
  • Relayed: one of the area's own repeaters carried it in.
  • Across: it came straight from the starting area, with no repeater of the receiving area involved.
  • From: the relay an observer there heard it from.
  • Gate: the home-area relay at the boundary. This only applies when Home is set.

Via: every area whose placeable repeaters passed the packet on.

Observers without a position are placed by their own node record, then by the areas' iata lists. Areas come from one areamap layer.

Tests

  • Red commit 53b7510: a stub returning zero values, so every test fails on its assertions. Green commit 2108e69.
  • The tests port meshcore.scotmesh.net's peering rules, covering:
    • prefixes that agree or conflict;
    • companions that never act as hops;
    • IATA placement, matched whatever the case;
    • relayed vs heard across;
    • the gate when leaving and when arriving;
    • every origin rule, including majority voting and an observer that relays its own copy;
    • each observer counted once per area;
    • layers kept independent;
    • a nil area map.
  • An allocation test holds Derive to at most 2 allocations per packet.
  • Ran scripts/allmod.sh vet and test (18 of 18 modules) and the Dockerfile COPY invariant check. gofmt is clean.

Benchmark

BenchmarkDerive-12   1.10 µs/op   224 B/op   2 allocs/op   (3 runs: 1110, 1101, 1100 ns)

Lookups lower-case into a stack buffer, and per-packet tallies live in fixed arrays. The only allocations are the record's Heard and Via slices.

Gate: agreement with the running implementation

I ran the last 24 hours of pre-prod packets through both meshcore.scotmesh.net's internal/peering and this package:

  • Data: 7,091 transmissions, 44,672 observations and 1,078 nodes, taken as a read-only extract.
  • Areas: the same outlines, built as an areamap. Scotland is cut out as a hole wherever peering tests it first.
Differences
Node placement 0 of 1,078
Origin 0 of 7,091
Heard (per area: observers, relayed, across, from, gate) 0 of 7,091
Via 0 of 7,091

The plan's threshold was within 1%. On that data, Derive averaged 4.9 µs per packet.

The only intended difference: an advert placed by the sender's node record reports basis node. meshcored reported it as advert.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H

A13xB0 and others added 6 commits October 10, 2026 13:49
A new internal package that places a position in a named area drawn in a
GeoJSON file: Polygon and MultiPolygon with holes, [lon, lat] order, a
layer per feature, the smallest area of a layer winning where areas
overlap, airport codes for observers without a position, and unique codes
across layers. Stubs only; every test fails on its assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
ParseGeoJSON reads a FeatureCollection (Polygon and MultiPolygon, holes
respected, [lon, lat] order); each feature's properties give its code,
label, layer (default "region") and the airport codes of observers that
report no position. New refuses a code used twice, in any layer.

Locate checks each polygon's bounding box first, then the even-odd test on
its outer ring and holes, and returns the smallest containing area of the
layer, so nested areas resolve to the most specific. Size is the shoelace
area with longitude scaled by latitude: enough to order overlaps.

BenchmarkLocate (six areas of 2,000 vertices): 2.1 us/op, 0 allocs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
areasFile names a GeoJSON FeatureCollection next to config.json. Its areas
join config.json's (converted from their [lat, lon] order) in one map kept
on the config; a code in both, a missing file or bad JSON are refused.
/api/config/areas lists file areas with their layer, and area= accepts a
file area's code. Stubs only: the tests fail on their assertions.

Also wires internal/areamap into the server module and the Dockerfile, and
adds Map.Has to the package (red there too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
config.json's areas are one polygon or box each, which cannot hold a
coastline, islands or a hole. areasFile names a GeoJSON FeatureCollection
beside config.json whose features become areas too: Polygon and
MultiPolygon with holes, a layer per feature, airport codes for observers
that report no position.

- LoadAreaMap builds one map from config.json's areas (layer "region",
  converted from their [lat, lon] order) and the file, and keeps it on the
  config. A code in both, a missing file or bad GeoJSON stops startup with
  the reason, rather than running with areas silently missing.
- /api/config/areas lists file areas after config.json's, each entry now
  carrying its layer ("region" for config.json areas). Additive: existing
  fields unchanged.
- area= accepts a file area's code, matched by node position against its
  geometry through the same cache. config.json areas keep their existing
  polygon and box path unchanged.

Documented in config.example.json (areasFile + _comment_areasFile),
docs/api-spec.md, and an example file, docs/examples/areas.example.geojson,
which a test loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
Ports the meshcore.scotmesh.net peering rules to a generic package placed by
areamap areas: advert position, the sender's node record, the first relay of
2 bytes or more (a shared prefix only when every holder agrees), the zero-hop
observer, otherwise unknown. Stub returns zero values so every test fails on
its assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
Generic port of the meshcore.scotmesh.net peering rules, placed by areamap
areas in one layer:

- origin: advert position, else the sender's node record, else the majority
  first relay of 2 bytes or more (a shared prefix only when every placed
  holder stands in one area), else the zero-hop observer's area, else unknown
- heard: one entry per receiving area with distinct observers, relayed in by
  its own repeater or heard straight across, the relay it came from, and an
  optional home-area boundary relay (gate)
- via: every area whose placeable repeaters passed it on

Lookups lower-case into a stack buffer and per-packet state lives in fixed
arrays, so Derive allocates only the record's two slices:
BenchmarkDerive 1.1 µs/op, 224 B/op, 2 allocs/op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaR4xu8gVykQBkH8yG8m6H
@A13xB0

A13xB0 commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #16, which carries this as its own commits alongside the rest of the Data tab (one PR, as asked).

@A13xB0 A13xB0 closed this Oct 10, 2026
@A13xB0
A13xB0 deleted the feat/data-origin branch October 10, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant