fix(street-level): KartaView review P2s; budget the gate's pick search

- Viewer: resizes while a sharper photo loads start no second load (the
  last size is looked at once it lands); decoded bitmaps nobody will draw
  (an overtaken preview or upgrade) are closed.
- Cones: a photo opened while the drive's first page loads is the one
  they centre on.
- A sequence page that lands while a lost WebGL context recovers keeps
  its cones: no pose is emitted without a renderer, and a viewer error
  never rejects page data.
- Source: an answer turned away unread (HTTP error, 429, length past the
  cap) aborts its request and cancels its body, freeing the connection.
- Gate: findPickPoint no longer picks each centre five times at radius 0,
  aims step 13's cone pick at the drawn billboards, and caps each
  evaluation at 4 s, resuming from a cursor on the next poll, so a slow
  machine cannot spend a whole wait in one evaluation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-14 13:09:40 +01:00
1 parent e9a4dbb062
commit 04b3231534
7 files changed
+350 -30

No files matched your search

@@ -145,8 +145,10 @@ export function createKartaViewSource({
),
timeoutMs,
);
let response = null;
let read = false;
try {
const response = await fetchImpl(url, { signal: controller.signal });
response = await fetchImpl(url, { signal: controller.signal });
if (response.status === 429)
throw new KartaViewSourceError('KartaView is rate-limiting requests', {
status: 429,
@@ -166,6 +168,7 @@ export function createKartaViewSource({
// KartaView answers chunked, without a length: count while reading.
const reader = response.body?.getReader?.();
if (!reader) {
read = true;
try {
return await response.json();
} catch (error) {
@@ -175,6 +178,7 @@ export function createKartaViewSource({
});
}
}
read = true;
const chunks = [];
let bytes = 0;
for (;;) {
@@ -209,6 +213,13 @@ export function createKartaViewSource({
} finally {
clearTimeout(timer);
signal?.removeEventListener('abort', onAbort);
// An answer turned away unread (429, an error, too long) still holds
// its connection, one of the six the browser has: let it go.
if (response && !read) {
controller.abort();
if (response.body && !response.body.locked)
response.body.cancel?.().catch?.(() => {});
}
}
}