From 7022d871591cc0c927df47012d289c86c9fae3c4 Mon Sep 17 00:00:00 2001 From: Will Shields <136547209+d3mocide@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:46:56 -0700 Subject: [PATCH] Revert "Fix terminator line rendering artifact on the situation globe" --- .../2026-07-10-globe-terminator-chord-fix.md | 58 ------------------- .../components/map/TerminatorLayer.test.ts | 48 --------------- .../src/components/map/TerminatorLayer.tsx | 30 ++-------- 3 files changed, 6 insertions(+), 130 deletions(-) delete mode 100644 agent_docs/tasks/2026-07-10-globe-terminator-chord-fix.md delete mode 100644 frontend/src/components/map/TerminatorLayer.test.ts diff --git a/agent_docs/tasks/2026-07-10-globe-terminator-chord-fix.md b/agent_docs/tasks/2026-07-10-globe-terminator-chord-fix.md deleted file mode 100644 index 00459e9..0000000 --- a/agent_docs/tasks/2026-07-10-globe-terminator-chord-fix.md +++ /dev/null @@ -1,58 +0,0 @@ -# Global Situation Globe — Terminator Line Rendering Bug - -## Issue - -On the dashboard's Global Situation view (`SituationGlobe`), the night-side -terminator overlay rendered as a jagged, misplaced patch near the pole -instead of a smooth shadow across the night hemisphere. The same -`getTerminatorLayer()` geometry renders correctly on the flat 2D maps -(`TacticalMap`, `OrbitalMap`). - -Root cause: `computeTerminator()` builds the night-side polygon by sampling -the wavy terminator curve at 1° longitude steps, then closing the ring by -jumping straight from the curve to the pole with only two vertices -(`coords.push([180, ±90])`, `coords.push([-180, ±90])`). On a flat/Mercator -projection a single long edge is harmless — it just renders as a straight -vertical line. On the globe (MapLibre's native globe projection, which bends -existing vertices onto the sphere but interpolates *linearly between* them), -that same edge — which can span 100+ degrees of latitude in one hop — becomes -a straight 3D chord cutting across the visible sphere instead of following -its curvature, producing the stray wedge seen near the pole while most of -the actual night region went unrendered. - -## Solution - -Densify the two pole-closing edges the same way the terminator curve itself -is already densified: step along the fixed-longitude meridian (lon = ±180) -from the curve to the pole in ~2° latitude increments instead of a single -jump. The pole-to-pole edge (`lon=180` → `lon=-180`, both at the same pole -latitude) is left as a single edge — it's a true zero-length edge in 3D since -every longitude maps to the same point at the pole, so no subdivision is -needed there. This changes only the number of vertices along an already -straight line in flat projections (no visual change on the working 2D maps) -while giving the globe projection enough points to hug the sphere's surface. - -## Changes - -- `frontend/src/components/map/TerminatorLayer.tsx` — `computeTerminator()` - replaces the two-point pole closure with a stepped ramp (`LAT_STEP_DEG = - 2`) along each boundary meridian, symmetric on both sides so the - pole-to-pole edge still lands on the exact same 3D point at both ends. -- `frontend/src/components/map/TerminatorLayer.test.ts` (new) — regression - test asserting no edge of the generated night polygon exceeds a 5° lat/lon - step, except the expected zero-length pole-to-pole edge. - -## Verification - -- `pnpm run lint` — clean (0 warnings). -- `pnpm run typecheck` — clean. -- `pnpm run test` — 279/279 passed (278 existing + 1 new). - -## Benefits - -- The night-side overlay on the Global Situation globe now follows the - sphere's curvature correctly instead of showing a stray artifact near the - pole, matching the correct behavior already present on the flat map views. -- The regression test catches any future reintroduction of long, - undersampled edges in the terminator polygon before it reaches the globe - renderer. diff --git a/frontend/src/components/map/TerminatorLayer.test.ts b/frontend/src/components/map/TerminatorLayer.test.ts deleted file mode 100644 index bebf214..0000000 --- a/frontend/src/components/map/TerminatorLayer.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Regression test for the globe-view terminator rendering bug: the polygon's - * pole-closing edges must stay finely sampled, or they render as long straight - * chords cutting across the sphere on globe/3D projections (harmless on flat - * Mercator maps, which don't care about edge length). - */ - -import { describe, expect, it } from "vitest"; - -vi.mock("@deck.gl/layers", () => { - class GeoJsonLayer { - id: string; - props: Record; - constructor(props: Record) { - this.id = props.id as string; - this.props = props; - } - } - return { GeoJsonLayer }; -}); - -import { vi } from "vitest"; -import { getTerminatorLayer } from "./TerminatorLayer"; - -describe("getTerminatorLayer", () => { - it("keeps every edge of the night polygon within a small lat/lon step", () => { - const layer = getTerminatorLayer(true) as unknown as { - props: { data: { features: [{ geometry: { coordinates: number[][][] } }] } }; - }; - const ring = layer.props.data.features[0].geometry.coordinates[0]; - - const MAX_STEP_DEG = 5; - for (let i = 1; i < ring.length; i++) { - const [lon0, lat0] = ring[i - 1]; - const [lon1, lat1] = ring[i]; - const latStep = Math.abs(lat1 - lat0); - expect(latStep).toBeLessThanOrEqual(MAX_STEP_DEG); - - // A lon jump is only harmless when both endpoints sit at the same pole - // (every longitude maps to the same 3D point there, so it's a - // zero-length edge regardless of the lon delta). - const bothAtSamePole = Math.abs(lat0) === 90 && lat0 === lat1; - if (!bothAtSamePole) { - expect(Math.abs(lon1 - lon0)).toBeLessThanOrEqual(MAX_STEP_DEG); - } - } - }); -}); diff --git a/frontend/src/components/map/TerminatorLayer.tsx b/frontend/src/components/map/TerminatorLayer.tsx index 95624b7..e1a0f43 100644 --- a/frontend/src/components/map/TerminatorLayer.tsx +++ b/frontend/src/components/map/TerminatorLayer.tsx @@ -76,31 +76,13 @@ function computeTerminator(date: Date) { // To make a polygon representing the *night* side, we need to connect the terminator // to either the north or south pole, depending on season (subSolarLat). // If sun is in north hemisphere (subSolarLat > 0), night covers south pole. - const poleLat = subSolarLat > 0 ? -90 : 90; - - // These two closing edges run along a fixed meridian (lon = ±180) from the - // terminator curve to the pole. On flat/Mercator maps a single long edge is - // fine (it renders as a straight vertical line), but on the 3D globe the - // renderer only bends existing vertices onto the sphere and interpolates - // linearly *between* them — an edge spanning ~100+ degrees of latitude in - // one hop becomes a straight chord that cuts across the visible globe - // instead of following its curvature. Sample every few degrees so the - // edge hugs the sphere on both projections. - const LAT_STEP_DEG = 2; - const lastLat = coords[coords.length - 1][1]; // terminator lat at lon = 180 - const firstLat = coords[0][1]; // terminator lat at lon = -180 - - const rampSteps = Math.max(1, Math.round(Math.abs(poleLat - lastLat) / LAT_STEP_DEG)); - for (let i = 1; i <= rampSteps; i++) { - coords.push([180, lastLat + (poleLat - lastLat) * (i / rampSteps)]); - } - // Start this ramp exactly at the pole (i = returnSteps) so the pole-to-pole - // edge above lands on the same point in 3D at both ends, then step back - // down to the terminator curve's start. - const returnSteps = Math.max(1, Math.round(Math.abs(poleLat - firstLat) / LAT_STEP_DEG)); - for (let i = returnSteps; i >= 1; i--) { - coords.push([-180, firstLat + (poleLat - firstLat) * (i / returnSteps)]); + if (subSolarLat > 0) { + coords.push([180, -90]); + coords.push([-180, -90]); + } else { + coords.push([180, 90]); + coords.push([-180, 90]); } // Close the polygon