Skip to content

validator: judge road-link gaps by lane-boundary contact, not reference-line distance - #107

Merged
kosuke55 merged 1 commit into
mainfrom
fix/road-link-gap-lane-contact
Aug 22, 2026
Merged

validator: judge road-link gaps by lane-boundary contact, not reference-line distance#107
kosuke55 merged 1 commit into
mainfrom
fix/road-link-gap-lane-contact

Conversation

@kosuke55

Copy link
Copy Markdown
Owner

Problem

geom.road-link-gap judged two linked roads by a proxy: the distance between their reference-line endpoints, minus |laneOffsetA - laneOffsetB| as slack.

The proxy breaks whenever the lane offset varies with s, or the two roads meet at an angle. On a measured real map, all ten of its reports were false positives — the lane boundaries in those cases touched to 0.000 m. It is the same failure mode as the soderleden 66 m case, just with the sign the other way.

Fix

Measure what the rule claims to measure. At each contact end, build the road's lane-boundary cross section in world coordinates:

  • centre line = reference line shifted laterally by <laneOffset> evaluated at that station,
  • plus the running sum of lane widths outward along the reference-line normal (-sin hdg, cos hdg) — left lanes in +t, right lanes in -t.

The gap is the minimum distance between road A's cross section and road B's. Lane offset is handled by construction, so the slack term is gone.

Details:

  • the lane section covering the contact station is the one used (multi-section roads),
  • <width> evaluation picks the record covering the station, so sOffset spans are respected,
  • a link with no contactPoint tries both of the target's ends and takes the nearer, rather than inventing a defect the document never asserted.

Comparing minimum distance asks whether the two sections touch, not whether they are congruent. That is deliberate: lane counts and widths differ across a link at merges, ramps and junction connectors (91 of 156 links on one measured map), so requiring congruence would trade one class of false positive for another. A road whose lanes are wholly displaced from its neighbour's is still reported.

Default threshold is 0.3 m, down from 0.5, now that the measurement is direct rather than a proxy with slack.

Evidence

  • False-positive pin: a synthetic fixture where road B's reference line is pushed 5 m aside by a laneOffset ramping along s while the lanes meet exactly — the old rule warned, the new one reports nothing.
  • Detection pin: the same construction with the lanes genuinely 1.5 m apart still warns, and the message reports the lane distance.
  • Further pins for multi-section roads, multi-record <width> spans, differing lane counts, a missing contactPoint, and a caller-supplied threshold.
  • Mutation matrix still 10/10.
  • False-positive gate: 0 MAP_DEFECT errors across the 6 checked-in fixtures and the 26-map external corpus.
  • soderleden road 7 keeps its warning — its lanes really are tens of metres from the road it declares as predecessor, so the finding survives on lane distance and not just on the proxy.

Suite: 438 passed / 6 skipped, tsc --noEmit clean.

geom.road-link-gap compared the two roads' reference-line endpoints and
subtracted |laneOffsetA - laneOffsetB| as slack. That proxy breaks whenever
the lane offset varies with s or the roads meet at an angle: on a measured
real map all ten of its reports were false positives whose lane boundaries
in fact touched to 0.000 m.

Measure the thing the rule claims to measure instead. At each contact end,
build the road's lane-boundary cross section in world coordinates - the
centre line (reference line shifted by <laneOffset> at that station) plus
the running sum of lane widths outward along the reference-line normal -
and report the minimum distance between the two sections. Lane offset is
handled by construction, so the slack term is gone. The lane section and
the <width> record covering the contact station are the ones evaluated, and
a link with no contactPoint takes the nearer of the target's two ends.

Comparing minimum distance asks whether the sections touch, not whether
they are congruent: lane counts differ across a link at merges, ramps and
junction connectors (91 of 156 links on one map), so requiring congruence
would trade one class of false positive for another. A road wholly
displaced from its neighbour is still reported.

Default threshold 0.3 m, down from 0.5, now that the measurement is direct.

Mutations still 10/10; no MAP_DEFECT error on any fixture or on the 26-map
external corpus. soderleden road 7 keeps its warning: its lanes really are
tens of metres from the road it declares as predecessor.
@kosuke55 kosuke55 added the run-tests Trigger CI test execution label Aug 22, 2026
@kosuke55
kosuke55 merged commit 7ee5770 into main Aug 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-tests Trigger CI test execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant