fix(street-level): keep cones on low ground, and gekh's P3s

From gekh's review of db79027, each with a test that fails before:

- P2: image cones and the photo marker no longer disappear where the
  ground lies below the WGS84 ellipsoid (NYC -22 m, Colombo -95 m). The
  horizon cull's occluder is WGS84 shrunk by 1 km (HORIZON_CULL_DEPTH_M),
  below any real surface, so a camera near or under the ellipsoid no
  longer treats every lower point as behind the horizon; the far side of
  the Earth is still hidden from orbit (tested).
- Mesh range across the date line: the camera's longitude is taken to the
  box's side of +-180 before clamping (meshBoxInRange), so a tile just
  across the line is not measured from its far edge.
- MapillaryJS no longer tracks window resizes (trackResize: false): the
  panel's ResizeObserver resizes the viewer and skips it while hidden, so
  a window resize after closing a photo cannot ask for a tile at z=NaN.
- A key-less install sends no tile requests before the key status is
  known: coverage waits for the first status answer and the provider
  refreshes it once the status lands.
- constructCatalog takes the source method list from MAPILLARY_SOURCE_
  METHODS; sequences.js imports view.js once; the provider's unused
  re-exports are gone.

Verified: unit 5970 pass; strict fixture gate on a production build 28 ok
+ 2 "no Google 3D" skips and the panel gate 6/6 (QA_FAIL_ON_RETRY=1);
live gate with a real token 25 ok + 3 fixture-only skips.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 18:28:14 +01:00
1 parent db79027998
commit c65feda4b1
10 files changed
+208 -19

No files matched your search

@@ -95,6 +95,24 @@ export function sequenceIdFromPick(pickId) {
* and tree tops. A tile is drawn draped first and swapped for its cast lines
* once the terrain heights are in, so coverage never waits on the terrain.
*/
/**
* Whether any point of a line's bounding `box` is within the mesh sampler's
* range of the camera ground point `centre`, measured the short way round
* the date line.
*/
export function meshBoxInRange(box, centre) {
// Take the camera's longitude to the box's side of the date line first:
// clamping 179.999 into a box at -179.99 would pick its far edge.
const lon =
centre.lon +
360 * Math.round(((box.west + box.east) / 2 - centre.lon) / 360);
const nearest = {
lon: Math.min(box.east, Math.max(box.west, lon)),
lat: Math.min(box.north, Math.max(box.south, centre.lat)),
};
return metresBetween(centre, nearest) <= MESH_SAMPLE_RADIUS_M;
}
export function createCoverage({ state, source }) {
const { render } = state.services;
@@ -268,13 +286,6 @@ export function createCoverage({ state, source }) {
}
/** Whether any of a {west, south, east, north} box is within the sampler's range. */
function inMeshRange(box, centre) {
const nearest = {
lon: Math.min(box.east, Math.max(box.west, centre.lon)),
lat: Math.min(box.north, Math.max(box.south, centre.lat)),
};
return metresBetween(centre, nearest) <= MESH_SAMPLE_RADIUS_M;
}
/**
* A tile's drawn lines with their bounding boxes, built once per filter;
@@ -310,10 +321,11 @@ export function createCoverage({ state, source }) {
const sampler = state.context.meshSampler;
if (!sampler || entry.kind !== 'sequence' || !terrainMode()) return;
const centre = meshCentre();
if (!centre || !entry.bounds || !inMeshRange(entry.bounds, centre)) return;
if (!centre || !entry.bounds || !meshBoxInRange(entry.bounds, centre))
return;
const points = [];
for (const line of meshParts(entry)) {
if (!inMeshRange(line.box, centre)) continue;
if (!meshBoxInRange(line.box, centre)) continue;
line.points ||= densifyLine(line.part, MESH_DENSIFY_DEG);
for (const point of line.points) points.push(point);
}
@@ -668,6 +680,7 @@ export function createCoverage({ state, source }) {
function refresh() {
const viewer = state.viewer;
if (!viewer || !state.context.isActive() || state.keyRequired) return;
if (state.statusKnown === false) return;
if (state.keyRejected || state.coverage.holdUntil > Date.now()) return;
const ground = groundUnderCamera(viewer, {
groundAt: state.context.groundCaster?.groundAt,