Follow-up from v0.53.0 (#120, PR #123): route lines now unwrap longitudes across ±180 and render continuously, but plane markers still use the snapshot's wrapped lat/lon (planesGeoJson ← positionAt, lib/game/state.ts:124). A plane mid-crossing teleports 360° on its marker at the instant it passes ±180 while its route line stays continuous.
Fix direction (from PR #123 review): pass airport coordinates into planesGeoJson so the client can unwrap each plane's position relative to its leg endpoints (signature change + Map.tsx call site). Unit-testable via lib/mapData; e2e-observable with a crossing package from /api/test/create-package.
Follow-up from v0.53.0 (#120, PR #123): route lines now unwrap longitudes across ±180 and render continuously, but plane markers still use the snapshot's wrapped lat/lon (`planesGeoJson` ← `positionAt`, lib/game/state.ts:124). A plane mid-crossing teleports 360° on its marker at the instant it passes ±180 while its route line stays continuous.
Fix direction (from PR #123 review): pass airport coordinates into `planesGeoJson` so the client can unwrap each plane's position relative to its leg endpoints (signature change + Map.tsx call site). Unit-testable via lib/mapData; e2e-observable with a crossing package from /api/test/create-package.
planesGeoJson now takes the airport index and unwraps each flying plane's longitude against its departure airport via a new module-private unwrapLon shared with unwrapLons — the same anchor greatCircleArc keeps for its first sample. A marker is unwrapped iff its route line is drawn (both leg endpoints exist); grounded planes and non-crossing legs are byte-identical to before.
Bonus product fix shipped in this release: the SSE silence watchdog now re-arms on every message (d0aec35) — a stream that goes quiet after the first event falls back to polling instead of freezing client state until reload.
Tests
Unit +6 in lib/mapData.test.ts (incl. the key invariant: every arc sample fed back as a wrapped snapshot position comes out exactly on that arc's own sample).
E2E +1 in e2e/map-interactions.spec.ts: chain-relocates the starter to NZAA, flies the hook-created NZAA→NFTF crossing at ×1000, samples marker lons from the planes source — no consecutive pair may differ by ≥ 180°, and some sample must sit on a world copy beyond ±180 (either side).
Debugging note: five CI runs were needed. The final failure was not in the product — runtime sometimes renders the continuous series in the −360° world copy (e.g. lons [−184.75 … −175.24] instead of [+175.26 … +183.5]), which MapLibre wraps identically on screen, so it was visually correct all along; only the test's assumption about which copy appeared was wrong. The assertion is now copy-agnostic (Math.abs(lon) > 180), and the run-180 diagnostics (server truth per phase + sampled lon/lat arrays + Δlon discriminator) are kept in the test as a tripwire — they're invisible in green runs and would localize any future regression instantly.
Fixed in **v0.58.0** — PR #132, merge commit `c5413af`, deployed to https://flight.dev.kkhost.pl (verified `/api/version` = 0.58.0).
**What changed**
- `planesGeoJson` now takes the airport index and unwraps each flying plane's longitude against its departure airport via a new module-private `unwrapLon` shared with `unwrapLons` — the same anchor `greatCircleArc` keeps for its first sample. A marker is unwrapped iff its route line is drawn (both leg endpoints exist); grounded planes and non-crossing legs are byte-identical to before.
- Bonus product fix shipped in this release: the SSE silence watchdog now re-arms on every message (`d0aec35`) — a stream that goes quiet after the first event falls back to polling instead of freezing client state until reload.
**Tests**
- Unit +6 in `lib/mapData.test.ts` (incl. the key invariant: every arc sample fed back as a wrapped snapshot position comes out exactly on that arc's own sample).
- E2E +1 in `e2e/map-interactions.spec.ts`: chain-relocates the starter to NZAA, flies the hook-created NZAA→NFTF crossing at ×1000, samples marker lons from the planes source — no consecutive pair may differ by ≥ 180°, and some sample must sit on a world copy beyond ±180 (either side).
**Debugging note**: five CI runs were needed. The final failure was not in the product — runtime sometimes renders the continuous series in the −360° world copy (e.g. lons [−184.75 … −175.24] instead of [+175.26 … +183.5]), which MapLibre wraps identically on screen, so it was visually correct all along; only the test's assumption about *which* copy appeared was wrong. The assertion is now copy-agnostic (`Math.abs(lon) > 180`), and the run-180 diagnostics (server truth per phase + sampled lon/lat arrays + Δlon discriminator) are kept in the test as a tripwire — they're invisible in green runs and would localize any future regression instantly.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up from v0.53.0 (#120, PR #123): route lines now unwrap longitudes across ±180 and render continuously, but plane markers still use the snapshot's wrapped lat/lon (
planesGeoJson←positionAt, lib/game/state.ts:124). A plane mid-crossing teleports 360° on its marker at the instant it passes ±180 while its route line stays continuous.Fix direction (from PR #123 review): pass airport coordinates into
planesGeoJsonso the client can unwrap each plane's position relative to its leg endpoints (signature change + Map.tsx call site). Unit-testable via lib/mapData; e2e-observable with a crossing package from /api/test/create-package.Fixed in v0.58.0 — PR #132, merge commit
c5413af, deployed to https://flight.dev.kkhost.pl (verified/api/version= 0.58.0).What changed
planesGeoJsonnow takes the airport index and unwraps each flying plane's longitude against its departure airport via a new module-privateunwrapLonshared withunwrapLons— the same anchorgreatCircleArckeeps for its first sample. A marker is unwrapped iff its route line is drawn (both leg endpoints exist); grounded planes and non-crossing legs are byte-identical to before.d0aec35) — a stream that goes quiet after the first event falls back to polling instead of freezing client state until reload.Tests
lib/mapData.test.ts(incl. the key invariant: every arc sample fed back as a wrapped snapshot position comes out exactly on that arc's own sample).e2e/map-interactions.spec.ts: chain-relocates the starter to NZAA, flies the hook-created NZAA→NFTF crossing at ×1000, samples marker lons from the planes source — no consecutive pair may differ by ≥ 180°, and some sample must sit on a world copy beyond ±180 (either side).Debugging note: five CI runs were needed. The final failure was not in the product — runtime sometimes renders the continuous series in the −360° world copy (e.g. lons [−184.75 … −175.24] instead of [+175.26 … +183.5]), which MapLibre wraps identically on screen, so it was visually correct all along; only the test's assumption about which copy appeared was wrong. The assertion is now copy-agnostic (
Math.abs(lon) > 180), and the run-180 diagnostics (server truth per phase + sampled lon/lat arrays + Δlon discriminator) are kept in the test as a tripwire — they're invisible in green runs and would localize any future regression instantly.