fix(bikeshare): let one dead city feed fail without failing the layer

The layer recorded failure in a single `_error` string. Every city feed is
fetched independently, so one retired operator set that flag for the whole
layer — and the only code that cleared it ran on a successful *activation*,
which `runProximityCheck` skips for a city that already has rendered points.
A dead feed that re-entered camera range therefore set the flag on every
camera move and nothing ever cleared it: a permanent LOAD FAILED over a map
drawing every other city's stations.

Failure is now tracked per city in `_failedCityIds`, and `getStats()` reports
it only when the failures left nothing to draw. A city that recovers removes
its own entry rather than waiting for a neighbour to succeed.

This was reachable only because Buffalo sits 95 km from Toronto, inside the
100 km activation radius — the one dead city in the registry with a healthy
neighbour in range. The other four failed alone, where LOAD FAILED with an
empty map reads as correct, which is why the latch went unnoticed.

Probing all 33 registry systems found five dead. Three had moved and are
repointed: Reddy Bikeshare (Buffalo) to Social Bicycles, Tugo (Tucson) and
Trinity Metro (Fort Worth) to PBSC, all verified serving stations. Two are
dropped because the systems wound down rather than moved — Broward B-cycle
404s at every documented endpoint, and CoGo's replacement Lyft feed publishes
zero stations with status last reported in March 2025. `gbfs.cogobikeshare.com`
no longer resolves, so its allowlist entry goes too.

All 31 remaining systems were verified live, and every registry URL was
checked against the proxy host and path allowlists.
This commit is contained in:
Cliff Flood committed 2026-10-13 18:55:20 -04:00
1 parent b8ccfd1648
commit 001043f74e
10 files changed
+144 -38

No files matched your search

+1
View File
@@ -3586,6 +3586,7 @@ test('actual Traffic, Bikeshare and Transit lifecycles retain sensitivity in eve
_pointCollection: {},
_overlayHost: { setVisible: noop },
_cityRuntime: new Map(),
_failedCityIds: new Set(),
},
services: {
sprites: { restoreSpriteOrder: noop },