The QTH Locator port keeps the decompiled range tables, which close both
adjacent cells (`-20.0..0.0` then `0.0..20.0`). Kotlin's `when` takes the first
match, so any coordinate landing exactly on a field, square or subsquare
boundary was attributed to the previous cell:
(0, 0) II99xx99 should be JJ00aa00
(1, 1) JJ00lx99 should be JJ01ma00
(22, 108) OL31xx99 should be OL42aa00
(22.5, 108.5) OL42fl99 should be OL42gm00
(22.25, 108.25) OL42cf99 should be OL42dg00
At the field level the locator is wrong by a whole 20 deg x 10 deg field, and
the 3x3 neighbour grid plus the red position marker are derived from the same
characters, so the whole Roaming screen pointed at the wrong square.
Cross-checking the port against core/domain positionToQth over the grid:
before: 65,341 sampled points, 4 agreed
after: 65,341 sampled points, all agree
The independent converter was confirmed correct first: it reproduces the
user-verified reference sample OL42ih45, and hand-computing lon=-179.75
(0.25 deg into the field, x12 -> subsquare index 3 = 'd') and lat=-90
(subsquare 'a', extended digit 0) matches it rather than the port.
Rather than rewriting the faithful lookup tables, nudge the input by 1e-10 so
the closed ranges behave like the standard half-open [low, high) cells, keeping
+90/+180 inside the final R cell. Seven real-world city samples and all existing
ported-behaviour tests, including (90, 180) -> RR99xx99, are unchanged.
Regression tests added for the boundary cases and for cross-implementation
agreement. Reverting the fix fails both; with the fix
:feature:roaming:testDebugUnitTest is green.