diff --git a/CHANGELOG.md b/CHANGELOG.md index a770fbd35..e3075234c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Tool string length validation counts Unicode characters correctly, including characters outside the basic multilingual plane. - Director timeline seeks cross the dateline along the short longitude arc. +- Frequently reused terrain batches retain their cached heights ahead of unused cells. - Treat explicitly current, complete empty vessel coverage as a successful update, clearing obsolete contacts while retaining stale or incomplete snapshots. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 1f794a5b5..246f3067c 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5326,3 +5326,8 @@ datacenters, dams and the Nepal locator retain that shared credit while displaye Military fragments stitch by source identity. Different mapped parcels merge only when their footprints touch or contain one another, never across a seam tolerance gap. + + +### fix(terrain): refresh cache recency when resolving warm batches + +Use the existing LRU read path for batch lookups; provider requests, fallback cooldowns and bounded residency retain their current contracts. diff --git a/src/services/terrain-warm-batch-lru.test.mjs b/src/services/terrain-warm-batch-lru.test.mjs new file mode 100644 index 000000000..ac718b998 --- /dev/null +++ b/src/services/terrain-warm-batch-lru.test.mjs @@ -0,0 +1,24 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { createTerrainHeights } from './terrainHeights.js'; +test('warm batch consumers retain their terrain cells ahead of unused cells', async () => { + let calls = 0; + const terrain = createTerrainHeights({ + maxCacheEntries: 2, + source: { + async getHeights(points) { + calls++; + return points.map((p) => ({ ellipsoid: 100 + p.lon })); + }, + }, + }); + const p = (lon) => ({ lat: 0, lon }); + await terrain.resolveEllipsoidalGround([p(1)]); + await terrain.resolveEllipsoidalGround([p(2)]); + await terrain.resolveEllipsoidalGround([p(1)]); + assert.equal(calls, 2); + await terrain.resolveEllipsoidalGround([p(3)]); + assert.equal(terrain.cachedEllipsoidalGround(0, 1), 101); + assert.equal(terrain.cachedEllipsoidalGround(0, 2), null); + assert.equal(calls, 3); +}); diff --git a/src/services/terrainHeights.js b/src/services/terrainHeights.js index a6502aeb5..124777150 100644 --- a/src/services/terrainHeights.js +++ b/src/services/terrainHeights.js @@ -273,7 +273,7 @@ export function createTerrainHeights({ const seenKeys = new Set(); const now = Date.now(); for (const item of work) { - const entry = cache.get(item.key); + const entry = cacheTouch(item.key); const fallbackCooling = entry?.source === 'geoid-fallback' && Number.isFinite(entry.retryAt) &&