From c6f437ed527210527c01db85800bbe767f66f375 Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:16:02 +0530 Subject: [PATCH 1/2] fix(traffic): stop a few matched roads reading as "no matches" The live flow status derived its wording from the rounded coverage percent, so 1 matched dot among 300 rounded to 0% and read "SIMULATED ... (no matches)", and 1 simulated dot among 300 rounded to 100% and dropped "Unmatched: simulated". Coverage now stays within 1-99% whenever both matched and simulated dots are shown. --- docs/CURRENT-STATE.md | 8 +++++--- src/layers/traffic/controls.js | 15 +++++++++++++-- src/layers/traffic/source.test.mjs | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c55031a6a..f6e61360f 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5810,9 +5810,11 @@ easier to meet (detection is now on more often), but does not create it. OSM choice), so Hybrid fill is never replayed as OSM roads. Failed flow refreshes clear old matches and report simulation. `flowCoveragePct` is the percentage of shown dots on roads with flow, including all unmatched dots in - the denominator and excluding dots hidden by closures. The OSM keyed status - reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`. Road fetch - coverage follows a 5×5 ellipsoid sample of the central screen square. + the denominator and excluding dots hidden by closures. It reads 1–99 whenever + both matched and simulated dots are shown, so rounding never turns a few + matches into `(no matches)` or a few simulated dots into full coverage. The + OSM keyed status reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`. + Road fetch coverage follows a 5×5 ellipsoid sample of the central screen square. Horizon rays are capped at 10 km from nadir (reduced if necessary to fit 16 z12 tiles); polygon-intersecting tiles load nearest first. Up to 16 z14 tiles provide near detail, with z12 roads beyond them. Cached tiles serve diff --git a/src/layers/traffic/controls.js b/src/layers/traffic/controls.js index d33e5a076..1a8eea4d6 100644 --- a/src/layers/traffic/controls.js +++ b/src/layers/traffic/controls.js @@ -259,7 +259,8 @@ export function createControls({ state: layerState, services, parts, source }) { * `error` carries this instant's health, so a live-configured layer whose * flow feed went down reads DEGRADED with the reason instead of a stale * LIVE coverage number. `flowCoveragePct` is shown dots on matched roads / - * all shown dots (0–100 int), excluding closures; `tilesFetched` counts flow-tile requests + * all shown dots (0–100 int, kept within 1–99 while both kinds are shown), + * excluding closures; `tilesFetched` counts flow-tile requests * issued to the proxy this session (decode-cache hits excluded). * @returns {{count:number, lastUpdate:number|null, loading:boolean, * mode:'live'|'sim', error:string|null, flowCoveragePct:number, @@ -304,8 +305,18 @@ export function createControls({ state: layerState, services, parts, source }) { Boolean(layerState._heightPending); const { free, slow, jam, sim } = layerState._bucketCounts; const matched = free + slow + jam; + // The label reads 0 as "no matches" and 100 as nothing simulated, so + // rounding must not reach either while both kinds of dot are shown. const flowCoveragePct = - matched + sim > 0 ? Math.round((100 * matched) / (matched + sim)) : 0; + matched + sim > 0 + ? Math.min( + sim > 0 ? 99 : 100, + Math.max( + matched > 0 ? 1 : 0, + Math.round((100 * matched) / (matched + sim)), + ), + ) + : 0; const feed = parts.model.trafficFeedPresentation({ liveMode: layerState._liveMode, fetching: loading, diff --git a/src/layers/traffic/source.test.mjs b/src/layers/traffic/source.test.mjs index 927dc416e..51b24b7df 100644 --- a/src/layers/traffic/source.test.mjs +++ b/src/layers/traffic/source.test.mjs @@ -516,6 +516,26 @@ test('late closures hide and stop dots; coverage counts shown matched dots, and assert.equal(controls.getStats().flowCoveragePct, 80); }); +test('coverage never rounds to "no matches" or full coverage while both kinds of dot are shown', async () => { + const { state, controls } = await matchingContext(async () => []); + state._bucketCounts = { free: 1, slow: 0, jam: 0, sim: 300 }; + assert.equal(controls.getStats().flowCoveragePct, 1); + assert.equal( + controls.getStats().loadingLabel, + 'LIVE · Roads: OpenStreetMap · Flow: TomTom · 1% cov · Unmatched: simulated', + ); + state._bucketCounts = { free: 300, slow: 0, jam: 0, sim: 1 }; + assert.equal(controls.getStats().flowCoveragePct, 99); + assert.equal( + controls.getStats().loadingLabel, + 'LIVE · Roads: OpenStreetMap · Flow: TomTom · 99% cov · Unmatched: simulated', + ); + state._bucketCounts = { free: 300, slow: 0, jam: 0, sim: 0 }; + assert.equal(controls.getStats().flowCoveragePct, 100); + state._bucketCounts = { free: 0, slow: 0, jam: 0, sim: 300 }; + assert.equal(controls.getStats().flowCoveragePct, 0); +}); + test('refresh failure removes cached matches and reports simulated flow', async () => { const { flow, state, model, controls } = await matchingContext(async () => { throw new Error('HTTP 429'); From 22b4169a8979a13c90d78151faea1a2c54cd58db Mon Sep 17 00:00:00 2001 From: Aditya <205600203+Rohilalala@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:24:09 +0530 Subject: [PATCH 2/2] docs(changelog): add #1442 fragment --- changelog.d/1442-traffic-coverage-edges.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog.d/1442-traffic-coverage-edges.md diff --git a/changelog.d/1442-traffic-coverage-edges.md b/changelog.d/1442-traffic-coverage-edges.md new file mode 100644 index 000000000..63890cc35 --- /dev/null +++ b/changelog.d/1442-traffic-coverage-edges.md @@ -0,0 +1,4 @@ +- Keep Street Traffic's flow status honest at the edges. A few TomTom-matched + dots among hundreds rounded to 0% and read "(no matches)", and a few + simulated dots rounded to "100% cov"; coverage now stays within 1–99% while + both are shown (Aditya, @Rohilalala, #1442).