Files
gods-eye-view/src/services/groundFloor.js
T
Aditya 2c61d9e2b7 fix(geo): wrap longitude differences across the antimeridian
The flights ground-floor pipeline subtracted longitudes without wrapping.
approxDistanceKm, and its copy in the mesh-floor sampler, measured two
points 0.2° apart either side of 180° (21 km at Fiji) as about 38,000 km,
so low and grounded aircraft just across the line from the viewer were
never floored or mesh-sampled. corridorFloorCells walked a segment across
180° the long way round, filling its cell budget with ground behind the
contact and never requesting the cells it was about to cross, and
stickyFloorCell dropped its hysteresis for a contact on the seam. Each now
takes the short way round, and corridor points are normalised back into
[-180, 180].

SEARCH NEARBY SITES centred its Google Places search on the mean of west
and east, which for a view across 180° (west > east) is longitude 0, and
sized the radius from the view's height alone. It now measures the view's
width through 180° and searches its real centre.
2026-10-16 16:48:37 +05:30

674 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Construct an instance-owned groundFloor service with explicit dependencies. */
export function createGroundFloor({ terrain, signal }) {
const { cachedRealEllipsoidalGround, resolveEllipsoidalGround } = terrain;
signal?.throwIfAborted();
// src/data/groundFloor.js — coarse ground-floor clamp for entity render
// heights (field-test round 2026-07-06).
//
// Two owner findings drove this module:
// - RS46 (military H60): baro-only low-altitude contacts near steep terrain
// render INSIDE the hillside (no alt_geom → baro+N is off by more than the
// local relief).
// - WAKE01 (military B212): trail waypoints flagged 'ground' rendered at a
// fixed 50 m ellipsoidal sentinel — ~1.5 km underground at Kirtland AFB
// (field elevation ~1590 m ellipsoidal).
//
// The fix everywhere is the same: never render below the local ellipsoidal
// surface. This module provides the shared floor: a COARSE (3-decimal,
// ~111 m) grid over Task 3's terrainHeights resolver/cache, so that moving
// aircraft and multi-hundred-point trails collapse onto a small set of cells
// with heavy cache reuse (the exact 5-decimal grid would mint a new cache key
// every fix — the taxiing-cache lesson from the adversarial review).
// A ~111 m cell means the floor can be off by the relief within the cell in
// steep terrain; the clamp is a visual floor, not a survey — lifting an icon
// a few metres short of a ridge line still beats burying it.
//
// Zero-network on the render path: reads are synchronous cache lookups;
// `warmGroundFloor` batches the misses through resolveEllipsoidalGround
// (single-flight, fire-and-forget) exactly like flights.js's grounded-surface
// warm — results land for later polls/appends.
/** @constant {number} Metres added above the resolved floor so a clamped
* billboard/waypoint sits ON the surface instead of z-fighting it. */
const GROUND_FLOOR_LIFT_M = 1.5;
/** Accepted rendered-mesh window around a real ellipsoidal DEM prior. */
const MESH_FLOOR_BELOW_PRIOR_M = 15;
const MESH_FLOOR_ABOVE_PRIOR_M = 80;
/**
* Snaps a coordinate to the coarse (3-decimal, ~111 m) floor grid.
* @param {number} lat
* @param {number} lon
* @returns {{lat: number, lon: number}} Grid-cell coordinate.
*/
function coarseFloorCoord(lat, lon) {
return { lat: Number(lat.toFixed(3)), lon: Number(lon.toFixed(3)) };
}
/**
* Pure floor clamp.
* - finite alt + finite ground → max(alt, ground + lift)
* - finite alt + unknown ground → alt (no data, no clamp)
* - null alt + finite ground → ground + lift (the 'ground'-waypoint case:
* the point IS on the surface; put it there)
* - null alt + unknown ground → null (caller applies its own fallback)
* @param {number|null|undefined} altM - Proposed ellipsoidal render height.
* @param {number|null|undefined} groundM - Local ellipsoidal ground, if known.
* @param {number} [liftM] - Clearance above the floor.
* @returns {number|null}
*/
function floorAltitudeM(altM, groundM, liftM = GROUND_FLOOR_LIFT_M) {
const hasAlt = Number.isFinite(altM);
const hasGround = Number.isFinite(groundM);
if (hasAlt && hasGround) return Math.max(altM, groundM + liftM);
if (hasAlt) return altM;
if (hasGround) return groundM + liftM;
return null;
}
/**
* Floor decision at the point of DISPLAY (2026-08-19).
*
* `floorAltitudeM` above answers "what height should this FIX render at" and
* runs once per poll. But what the user sees is the DEAD-RECKONED position:
* a Cartesian interpolation between two fixes for the whole segment, and a
* velocity extrapolation of the newest fix while a contact coasts through its
* stale-feed grace. Both drift the rendered point horizontally away from the
* fix whose cell supplied the height — across a graded apron (KAUS spans
* ~119–140 m ellipsoidal) a taxiing sprite therefore renders under the mesh it
* has drifted over (measured −15.5 m; `scripts/qa-floor-verify.mjs`).
*
* This is the same "never below the visible surface" rule applied at the
* displayed coordinate instead of the fix coordinate. It returns `null` rather
* than the unchanged height whenever nothing should move, so the per-frame
* caller can skip rebuilding a Cartesian entirely — the common case by far.
*
* @param {number|null|undefined} displayHeightM - Ellipsoidal height the
* dead-reckoned display position currently carries.
* @param {number|null|undefined} floorM - Ellipsoidal floor at the DISPLAYED
* coordinate (`cachedGroundFloor`), or null when that cell is not warm.
* @param {number} [liftM] - Clearance above the floor.
* @returns {number|null} The height to render at, or null for "leave it alone".
*/
function displayFloorHeightM(
displayHeightM,
floorM,
liftM = GROUND_FLOOR_LIFT_M,
) {
if (!Number.isFinite(displayHeightM) || !Number.isFinite(floorM))
return null;
const lifted = floorM + liftM;
return displayHeightM < lifted ? lifted : null;
}
/** @constant {number} Pitch of the coarse floor grid in degrees — the spacing
* `coarseFloorCoord` rounds to, and therefore the step between adjacent cells. */
const FLOOR_CELL_DEG = 0.001;
/** @constant {number} Resolved neighbours required before one may be borrowed.
* A single reading cannot be checked against anything: a hangar roof and an
* apron are both "a warm cell 111 m away", and there is no way to tell them
* apart from one number. Two readings at least agree or disagree. */
const NEIGHBOR_FLOOR_MIN_SAMPLES = 2;
/**
* A floor for `cell` borrowed from the eight cells ADJACENT to it.
*
* Used only as a stand-in while `cell` itself is unresolved (2026-08-21: the
* Re:Earth proxy timed out four times in a row and a grounded contact standing
* on a cold cell rendered at the geoid — tens of metres under the mesh it was
* parked on). A neighbour is ~111 m away; on the aprons and taxiways where
* grounded contacts live that is the same surface, and it is a MEASURED height
* rather than an invented one.
*
* Leans to the LOWEST resolved neighbour, and refuses to answer at all from
* fewer than NEIGHBOR_FLOOR_MIN_SAMPLES of them.
*
* An earlier cut leaned HIGH, reasoning from the locked "never below the
* visible surface" principle. That principle is about a contact's OWN measured
* ground; applied to a BORROWED cell it inverts, and an owner playtest found
* why — planes floating in midair at terminal gates. The two errors are not
* symmetric:
* - Too LOW is inert. `displayFloorHeightM` only ever RAISES a position, so a
* floor under the contact simply does not lift it — the same "no clamp" the
* cold-cell case already has, bounded by one cell of grade (metres).
* - Too HIGH actively invents a position. It is bounded by BUILDING HEIGHT,
* not by grade, and a parked contact holds it: measured on the A/B rig,
* a lone roof neighbour put a contact 29.5 m up and left it there
* permanently when its own cell never warmed, and 6.3 s of visible
* stair-stepping when it did.
* A plane at a gate is on the apron, never on the terminal roof, so on a
* structure edge the LOW neighbour is the answer and the high one is an
* artefact of the photogrammetric mesh.
*
* The honest residual: on a genuine SLOPE the lowest neighbour under-reads by
* the grade across ~111 m, so the clamp lifts a little less than it could. That
* shows up as no lift rather than as a wrong one, and the contact's own cell
* corrects it as soon as it warms.
*
* @param {{lat: number, lon: number}|null|undefined} cell - Coarse cell.
* @returns {number|null} Ellipsoidal floor, or null when too few neighbours are
* warm to borrow from.
*/
function neighborFloorM(cell) {
if (!Number.isFinite(cell?.lat) || !Number.isFinite(cell?.lon)) return null;
let lowest = null;
let resolved = 0;
for (let dLat = -1; dLat <= 1; dLat += 1) {
for (let dLon = -1; dLon <= 1; dLon += 1) {
if (dLat === 0 && dLon === 0) continue;
const h = cachedGroundFloor(
cell.lat + dLat * FLOOR_CELL_DEG,
cell.lon + dLon * FLOOR_CELL_DEG,
);
if (h == null) continue;
resolved += 1;
if (lowest == null || h < lowest) lowest = h;
}
}
return resolved >= NEIGHBOR_FLOOR_MIN_SAMPLES ? lowest : null;
}
/** @constant {number} Cap on cells one corridor may contribute — a fast rollout
* or a spliced fix must not turn one contact into an unbounded warm batch.
* Sized for the worst realistic ground segment: a 27 m/s rollout coasts ~810 m
* (≈7 cells) in one poll interval, leaving headroom plus the endpoint slot. */
const CORRIDOR_MAX_CELLS = 12;
/** @constant {number} Step along a corridor leg when collecting cells (~14 m,
* an eighth of a cell). Half-cell steps were only cell-complete on LONG legs:
* a short leg rounds to a single step, so the walk degenerates to testing the
* leg's endpoints and a cell clipped between them is skipped. At this step any
* cell the path occupies for ~14 m of ground or more contains a sample — the
* guarantee this walk actually makes. The loop still exits the moment the cell
* budget fills, so a finer step costs iterations only on short corridors. */
const CORRIDOR_WALK_STEP_DEG = 0.000125;
/** @constant {number} Degrees added around a cell before a contact is allowed
* to adopt the next one (~22 m). A dead-reckoned position that jitters across
* a 0.001° edge would otherwise alternate floors at fleet-tick rate and pop
* the sprite between two heights. */
const CELL_HYSTERESIS_DEG = 0.0002;
/**
* The floor cell a contact should read, with boundary hysteresis.
*
* Cells are 0.001° squares, so a display position sitting on an edge (parked
* GPS jitter, the tracked reconciliation's decaying correction) flips between
* two cells frame to frame — and their floors can differ by the local grade or,
* next to a building, several metres. The contact keeps its previous cell until
* it is CELL_HYSTERESIS_DEG clear of that cell's bounds, which costs two
* comparisons and no allocation on the sticky path.
*
* @param {number} lat @param {number} lon - Displayed coordinate.
* @param {{lat: number, lon: number}|null|undefined} previousCell - The cell
* this contact read last tick, if any.
* @returns {{lat: number, lon: number}} The cell to read.
*/
function stickyFloorCell(lat, lon, previousCell) {
if (previousCell) {
// The short way round, so a contact on the 180° seam keeps its cell.
let dLon = lon - previousCell.lon;
dLon -= 360 * Math.round(dLon / 360);
if (
Math.abs(lat - previousCell.lat) <= 0.0005 + CELL_HYSTERESIS_DEG &&
Math.abs(dLon) <= 0.0005 + CELL_HYSTERESIS_DEG
) {
return previousCell;
}
}
return coarseFloorCoord(lat, lon);
}
/**
* The coarse cells a contact's DISPLAY is about to walk through.
*
* The dead-reckoned display runs one render delay behind and advances toward
* the newest fix, so the segment [display → newest fix] is exactly the ground
* it will render over during the next poll or two. Warming only the cell it is
* standing on warms the cell it is LEAVING: at 10 m/s a contact crosses a
* ~111 m cell every ~11 s while the warm batch runs once per 30 s poll, so it
* stays permanently ahead of its own floor data and the display clamp has
* nothing to read (KAUS: sprites 2–4 m under the mesh with a cold cell for the
* whole observation).
*
* Walks in half-cell steps so no cell between the ends is skipped. The step
* COUNT follows the segment length (never a fixed cap — a capped count over a
* long segment samples sparsely and leaves holes in the middle of ground the
* contact does cross). Length is bounded instead, and honestly: the walk emits
* a GAP-FREE PREFIX from `from`, and the destination cell always takes the last
* slot. A corridor longer than CORRIDOR_MAX_CELLS is therefore truncated —
* contiguous near ground plus the far endpoint — rather than thinned; the
* ground in between is picked up on a later poll as the display advances into
* it. Loop work stays bounded because the walk exits as soon as the cell budget
* fills, whatever the segment length.
*
* Takes the display PATH as a polyline, not two endpoints: while the contact
* is extrapolating, that path is an arc (the dead-reckon integrates a
* constant-rate turn), and walking a straight chord between its ends would
* warm ground the contact never crosses while its actual arc stayed cold.
*
* @param {Array<{lat: number, lon: number}>} points - Display path, current
* position first, destination last.
* @returns {Array<{lat: number, lon: number}>} Deduped coarse cells, start
* first, destination last.
*/
function corridorFloorCells(points) {
if (!Array.isArray(points) || !points.length) return [];
const first = points[0];
if (!Number.isFinite(first?.lat) || !Number.isFinite(first?.lon)) return [];
const start = coarseFloorCoord(first.lat, first.lon);
const out = [start];
const seen = new Set([`${start.lat},${start.lon}`]);
let destination = null;
for (let i = points.length - 1; i >= 1; i--) {
if (Number.isFinite(points[i]?.lat) && Number.isFinite(points[i]?.lon)) {
destination = coarseFloorCoord(points[i].lat, points[i].lon);
break;
}
}
if (!destination) return out;
const destKey = `${destination.lat},${destination.lon}`;
// One slot is reserved for the destination, appended after the prefix.
const prefixLimit = CORRIDOR_MAX_CELLS - 1;
walk: for (let s = 0; s < points.length - 1; s++) {
const a = points[s];
const b = points[s + 1];
if (![a?.lat, a?.lon, b?.lat, b?.lon].every(Number.isFinite)) continue;
// The short way round, so a segment across 180° walks across it.
let dLon = b.lon - a.lon;
dLon -= 360 * Math.round(dLon / 360);
const steps = Math.ceil(
Math.max(Math.abs(b.lat - a.lat), Math.abs(dLon)) /
CORRIDOR_WALK_STEP_DEG,
);
for (let i = 1; i <= steps; i++) {
if (out.length >= prefixLimit) break walk;
const t = i / steps;
let lon = a.lon + dLon * t;
if (lon > 180) lon -= 360;
else if (lon < -180) lon += 360;
const cell = coarseFloorCoord(a.lat + (b.lat - a.lat) * t, lon);
const key = `${cell.lat},${cell.lon}`;
if (seen.has(key) || key === destKey) continue;
seen.add(key);
out.push(cell);
}
}
if (!seen.has(destKey)) out.push(destination);
return out;
}
/**
* Shares one poll's corridor-cell budget across the contacts that need it.
*
* The first cut walked contacts in Map insertion order and decremented the
* budget BEFORE deduping, so parked contacts burned slots on cells the poll had
* already collected and the contacts actually outrunning their floor data
* starved. Three rules fix that:
* 1. Cells already collected are FREE — a parked contact, whose corridor is
* its own fix cell, never competes for budget at all.
* 2. Candidates are ordered by how many of their cells are genuinely cold,
* then by speed (whoever leaves their floor behind soonest).
* 3. Allocation runs in widening rounds — one cell each, then `fairShare`
* each, then the remainder — so no contact is left with NOTHING while
* another takes seconds. The nearest cell is the one a contact needs first
* anyway; the far end of its corridor can wait a poll.
*
* @param {Array<{cells: Array<{lat: number, lon: number}>, cold: number, speedMps: number}>} candidates
* @param {Set<string>} seen - Keys ("lat,lon") already collected; MUTATED with
* everything this call allocates, so the caller's dedupe stays consistent.
* Ranking alone still starves a TIE longer than the budget: 80 equally needy
* movers means the same stable prefix wins every poll and the tail never moves.
* The `epoch` rotates each run of equal-ranked candidates, so a tie cycles
* round-robin across polls while a genuinely needier contact is never demoted
* below a less needy one (rotation happens WITHIN a rank, never across ranks).
*
* `seen` alone is NOT a sufficient free-list: in production it holds only the
* CURRENT poll's batch, so a cell that warmed on an earlier poll looked new and
* spent budget again, letting a contact's near cells consume its whole share
* poll after poll while the ground ahead of it stayed cold. Budget is therefore
* charged only for cells with no floor yet (`isWarm`) — but warm cells are
* still EMITTED, because the mesh sampler must see a cell again after its DEM
* prior lands or it never latches a rendered-surface height there.
*
* That gives a service bound derivable from the policy rather than a magic
* number: the first round charges at most one cell per candidate, so at least
* `min(candidates, budget)` candidates are served per poll; a candidate that
* misses out keeps its cold count while every served candidate's drops, so it
* outranks them next poll. Every candidate is therefore served within
* `ceil(candidates / budget)` polls once the warms land.
*
* @param {number} budget - Max cold cells to charge for.
* @param {number} fairShare - Per-candidate cap for the middle round.
* @param {number} [epoch] - Poll counter; rotates equal-ranked runs.
* @param {(cell: {lat: number, lon: number}) => boolean} [isWarm] - Whether a
* cell already has a floor (defaults to the shared cache).
* @returns {Array<{lat: number, lon: number}>} Cells to add, allocation order.
*/
function allocateCorridorCells(
candidates,
seen,
budget,
fairShare,
epoch = 0,
isWarm = (cell) => cachedGroundFloor(cell.lat, cell.lon) != null,
) {
const out = [];
if (!Array.isArray(candidates) || !candidates.length || !(budget > 0))
return out;
const ranked = [...candidates].sort(
(a, b) => b.cold - a.cold || b.speedMps - a.speedMps,
);
if (Number.isFinite(epoch) && epoch !== 0) {
for (let i = 0; i < ranked.length;) {
let j = i + 1;
while (
j < ranked.length &&
ranked[j].cold === ranked[i].cold &&
ranked[j].speedMps === ranked[i].speedMps
)
j += 1;
const runLength = j - i;
if (runLength > 1) {
const shift = ((epoch % runLength) + runLength) % runLength;
const run = ranked.slice(i, j);
for (let m = 0; m < runLength; m += 1)
ranked[i + m] = run[(m + shift) % runLength];
}
i = j;
}
}
// Cursor per candidate, carried ACROSS rounds: a later round resumes where
// the previous one stopped instead of re-walking cells already handled.
const cursor = new Array(ranked.length).fill(0);
let left = budget;
const round = (limit) => {
for (let i = 0; i < ranked.length; i++) {
const cells = ranked[i].cells || [];
while (cursor[i] < cells.length && cursor[i] < limit) {
const cell = cells[cursor[i]];
const key = `${cell.lat},${cell.lon}`;
if (seen.has(key)) {
cursor[i] += 1;
continue;
} // already in this batch
if (isWarm(cell)) {
// Free: it has a floor already. Still emitted, so the mesh sampler
// gets another look now that its DEM prior exists.
cursor[i] += 1;
seen.add(key);
out.push(cell);
continue;
}
if (left <= 0) return;
cursor[i] += 1;
seen.add(key);
out.push(cell);
left -= 1;
}
}
};
round(1); // nobody goes home empty-handed
round(fairShare); // then a fair slice each
round(CORRIDOR_MAX_CELLS); // then the remainder, neediest first
return out;
}
// --- Mesh-floor cells (round 4, owner-approved design) ---------------------
// The Re:Earth DEM is BARE EARTH; the visible world in the google-3d regime
// is the photogrammetric MESH, which sits above it (measured ~17 m at the
// Austin airport apron). DEM-flooring therefore still buried sprites/trails
// inside the mesh. These cells hold the RENDERED surface height, sampled
// one-shot per coarse cell near the viewer (scene.sampleHeight — the same
// source groundSnap uses, which is why 3D models always looked right) and
// reported here by meshFloorSampler.js. Design principle (owner): NEVER
// below the visible surface; slightly above is always fine. The DEM stays
// the instant global prior/fallback and the sanity gate for samples.
/** @type {Map<string, number>} coarse cell key -> rendered-surface height (m, ellipsoidal). */
const _meshCells = new Map();
/** @type {boolean} Whether mesh cells apply — true only in the google-3d
* regime (photoreal stack). On globe stacks the rendered surface IS the
* terrain provider (Re:Earth on keyless), so the DEM is already correct
* there and mesh heights would float. Kept current by meshFloorSampler's
* 'gev:map-stack-changed' listener. Photoreal is the boot default. */
let _meshPreferred = true;
/** @param {boolean} preferred - google-3d regime active. */
function setMeshFloorPreferred(preferred) {
_meshPreferred = !!preferred;
}
/** @returns {boolean} Whether mesh-floor cells currently apply. */
function meshFloorPreferred() {
return _meshPreferred;
}
/**
* Records a sampled rendered-surface height for the cell containing the
* coordinate (one-shot latch — first accepted sample wins).
* @param {number} lat @param {number} lon @param {number} heightM
*/
function reportMeshFloorCell(lat, lon, heightM) {
if (
!Number.isFinite(lat) ||
!Number.isFinite(lon) ||
!Number.isFinite(heightM)
)
return;
const c = coarseFloorCoord(lat, lon);
const key = `${c.lat},${c.lon}`;
if (!_meshCells.has(key)) _meshCells.set(key, heightM);
}
/** Return whether a rendered-mesh sample is plausible against a real DEM prior. */
function meshFloorSampleWithinPrior(heightM, priorM) {
return (
Number.isFinite(heightM) &&
Number.isFinite(priorM) &&
heightM >= priorM - MESH_FLOOR_BELOW_PRIOR_M &&
heightM <= priorM + MESH_FLOOR_ABOVE_PRIOR_M
);
}
/**
* Validate and record a rendered-mesh floor through the single shared gate.
* Callers must not bypass this with raw `reportMeshFloorCell`: a coarse-LOD,
* rooftop, or aircraft hit would otherwise win the session-long first-write
* latch and poison every floor consumer in the cell.
* @returns {boolean} Whether the sample passed and was reported.
*/
function reportValidatedMeshFloorCell(lat, lon, heightM) {
if (![lat, lon, heightM].every(Number.isFinite)) return false;
const cell = coarseFloorCoord(lat, lon);
const prior = cachedRealEllipsoidalGround(cell.lat, cell.lon);
if (!meshFloorSampleWithinPrior(heightM, prior)) return false;
reportMeshFloorCell(cell.lat, cell.lon, heightM);
return true;
}
/**
* Synchronous mesh-cell read — null when the cell is unsampled OR the
* google-3d regime is inactive (globe stacks must floor on the DEM).
* @param {number} lat @param {number} lon
* @returns {number|null}
*/
function cachedMeshFloor(lat, lon) {
if (!_meshPreferred) return null;
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
const c = coarseFloorCoord(lat, lon);
const h = _meshCells.get(`${c.lat},${c.lon}`);
return h != null ? h : null;
}
/** Test hook: clears mesh cells (never called at runtime — cells are valid all session). */
function _clearMeshFloorCellsForTest() {
_meshCells.clear();
}
/**
* Synchronous coarse-cell ground read (warm cache only — never a fetch).
* Prefers the RENDERED-surface mesh cell (google-3d regime) and falls back
* to the Re:Earth DEM cell — the single choke point every ground-adjacent
* consumer (fleet clamp, grounded surface chain, trail floors) reads, so
* they all agree on one surface.
* @param {number} lat
* @param {number} lon
* @returns {number|null} Ellipsoidal floor of the cell, or null if not warm.
*/
function cachedGroundFloor(lat, lon) {
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
const mesh = cachedMeshFloor(lat, lon);
if (mesh != null) return mesh;
const c = coarseFloorCoord(lat, lon);
return cachedRealEllipsoidalGround(c.lat, c.lon);
}
/**
* Awaitable variant for one-shot consumers (trail backfills): dedupes the
* points to unique coarse cells and resolves them, so `cachedGroundFloor`
* reads immediately after are warm. The underlying resolver chunks (≤200/req)
* and falls back per-point, so any trace length is safe. Never throws.
* @param {Array<{lat: number, lon: number}>} points
* @returns {Promise<void>}
*/
/** @constant {number} Max ms a one-shot consumer (trail backfill) waits for
* the floor resolve before painting with whatever cells are warm. Owner
* field test 2026-07-06 round 2: an UNBOUNDED await here held trail paints
* hostage to cold Re:Earth lookups spanning a whole flight path (seconds to
* the 30 s proxy timeout) — trails showed up late, short, or not at all.
* The resolve keeps running past the deadline and lands in the cache for
* the next paint/select. */
const FLOOR_RESOLVE_DEADLINE_MS = 1200;
/**
* Bounded-latency variant of resolveGroundFloorCells: waits at most
* FLOOR_RESOLVE_DEADLINE_MS, then returns so the caller can paint with the
* cells that ARE warm. The underlying resolve continues in the background
* and fills the cache for later reads. Never throws.
* @param {Array<{lat: number, lon: number}>} points
* @returns {Promise<void>}
*/
function resolveGroundFloorCellsBounded(points) {
return Promise.race([
resolveGroundFloorCells(points),
new Promise((resolve) => setTimeout(resolve, FLOOR_RESOLVE_DEADLINE_MS)),
]);
}
async function resolveGroundFloorCells(
points,
{ signal: consumerSignal } = {},
) {
if (!Array.isArray(points) || !points.length) return;
const cells = new Map();
for (const p of points) {
if (!Number.isFinite(p?.lat) || !Number.isFinite(p?.lon)) continue;
const c = coarseFloorCoord(p.lat, p.lon);
const key = `${c.lat},${c.lon}`;
if (cells.has(key)) continue;
if (cachedRealEllipsoidalGround(c.lat, c.lon) != null) continue;
cells.set(key, c);
}
if (!cells.size) return;
try {
await resolveEllipsoidalGround([...cells.values()], {
signal: consumerSignal,
});
} catch {
/* best-effort — unresolved cells simply don't clamp */
}
}
/** @type {boolean} Single-flight guard for the warm batch. */
let _floorBatchInFlight = false;
/** @type {Map<string, {lat: number, lon: number}>} Cells requested while a
* batch was in flight — flushed as the NEXT batch the moment it completes.
* Round 6: the old guard silently DROPPED contending calls (flights every
* 30 s, military every 15 s, trail backfills ad hoc), so arriving at a new
* airport could take minutes of lost polls before a given cell resolved —
* the owner's "you can't just go to a place and start inspecting it". */
const _pendingFloorCells = new Map();
/** Kicks the resolver for a cell map, chaining any cells queued meanwhile. */
function _resolveFloorCells(cells) {
_floorBatchInFlight = true;
resolveEllipsoidalGround([...cells.values()])
.catch(() => {
/* best-effort — unclamped this tick, floor lands later */
})
.finally(() => {
_floorBatchInFlight = false;
if (!signal?.aborted && _pendingFloorCells.size) {
const next = new Map(_pendingFloorCells);
_pendingFloorCells.clear();
_resolveFloorCells(next);
}
});
}
/**
* Fire-and-forget batch warm of the coarse floor cells for the given points.
* Dedupes to unique cells, skips already-warm ones, and never blocks or
* throws into the caller. Contending calls QUEUE (never drop): their cells
* ride the next batch as soon as the in-flight one completes. Results are
* picked up by later `cachedGroundFloor` reads.
* @param {Array<{lat: number, lon: number}>} points
*/
function warmGroundFloor(points) {
if (signal?.aborted) return;
if (!Array.isArray(points) || !points.length) return;
const cells = new Map();
for (const p of points) {
if (!Number.isFinite(p?.lat) || !Number.isFinite(p?.lon)) continue;
const c = coarseFloorCoord(p.lat, p.lon);
const key = `${c.lat},${c.lon}`;
if (cells.has(key) || _pendingFloorCells.has(key)) continue;
if (cachedRealEllipsoidalGround(c.lat, c.lon) != null) continue; // already warm
cells.set(key, c);
}
if (!cells.size) return;
if (_floorBatchInFlight) {
for (const [key, c] of cells) _pendingFloorCells.set(key, c);
return;
}
_resolveFloorCells(cells);
}
signal?.addEventListener(
'abort',
() => {
_meshCells.clear();
_pendingFloorCells.clear();
},
{ once: true },
);
return {
GROUND_FLOOR_LIFT_M,
MESH_FLOOR_BELOW_PRIOR_M,
MESH_FLOOR_ABOVE_PRIOR_M,
coarseFloorCoord,
floorAltitudeM,
displayFloorHeightM,
FLOOR_CELL_DEG,
NEIGHBOR_FLOOR_MIN_SAMPLES,
neighborFloorM,
CORRIDOR_MAX_CELLS,
CORRIDOR_WALK_STEP_DEG,
CELL_HYSTERESIS_DEG,
stickyFloorCell,
corridorFloorCells,
allocateCorridorCells,
setMeshFloorPreferred,
meshFloorPreferred,
reportMeshFloorCell,
meshFloorSampleWithinPrior,
reportValidatedMeshFloorCell,
cachedMeshFloor,
_clearMeshFloorCellsForTest,
cachedGroundFloor,
FLOOR_RESOLVE_DEADLINE_MS,
resolveGroundFloorCellsBounded,
resolveGroundFloorCells,
warmGroundFloor,
};
}