fix(street-level): Panoramax IC8 round 2 — shared point budget, selected line, cone cache, DP segments

From the round-2 verification (approve with follow-ups), all follow-ups:
- The point budget is shared across the tiles in view once loading
  settles (water-filling): a sparse tile keeps what it has, a dense city
  centre takes the rest instead of drawing thinner than its suburbs. The
  selected sequence is always drawn, and selecting one a cap had left out
  draws it.
- The cones' picture-list cache keeps only finished lists: a re-centre
  while the cones loaded inherited the aborted load's tiles and drew
  none in them.
- Douglas–Peucker measures to the segment, so dead ends and U-turns keep
  their far end.
- Switching Panoramax off lets its decoded tiles and lists go.
- Picture downloads are counted while they stream (a host sending no
  length cannot fill memory before the cap).
- Comments on the via caption and the simplification tolerance match the
  code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-14 15:39:10 +01:00
1 parent 1a546bec72
commit 3ae367d0dd
11 files changed
+343 -33

No files matched your search

+92 -1
View File
@@ -76,7 +76,7 @@ function setup(
fetchTile: async (tile) => {
asked.push(tile);
if (fail) throw fail;
if (decoded) return decoded(centre);
if (decoded) return decoded(centre, tile);
return {
sequences: [
{
@@ -247,3 +247,94 @@ test('a point budget skips lines past it, and a shorter one later may still fit'
const [tile] = state.coverage.tiles.values();
assert.deepEqual([...tile.sequences.keys()], ['three', 'two']);
});
/** A line of `n` points, its id unique to the tile. */
function pointsLine(id, n, tile, centre) {
return {
id,
parts: [
Array.from({ length: n }, (_, i) => [
centre.lon - 0.001 + i * 0.00001,
centre.lat,
]),
],
capturedAt: 0,
isPano: false,
};
}
test('the point budget is shared: a sparse tile keeps its few, a dense one gets the rest', async (t) => {
const tuning = {
...TUNING,
maxTiles: 9,
maxSequences: 900,
maxPoints: 90,
keepOrder: true,
};
const { state, coverage } = setup(t, {
// Street zoom far enough in that several tiles are in view.
zoomFor: () => ({ zoom: 18, kind: 'sequence' }),
tuning,
decoded: (centre, tile) => {
const dense = tile.x % 2 === 0 && tile.y % 2 === 0;
const lines = dense
? Array.from({ length: 30 }, (_, i) =>
pointsLine(`d${tile.x}/${tile.y}/${i}`, 5, tile, centre),
)
: [pointsLine(`s${tile.x}/${tile.y}`, 2, tile, centre)];
return { sequences: lines, overview: [] };
},
});
coverage.refresh();
assert.ok(
await until(
() => state.coverage.tiles.size > 1 && !state.coverage.pending.size,
),
);
const entries = [...state.coverage.tiles.values()];
const drawn = (entry) => entry.pointsDrawn;
const total = entries.reduce((sum, entry) => sum + drawn(entry), 0);
assert.ok(total <= tuning.maxPoints, `within the budget: ${total}`);
const sparse = entries.filter((entry) => entry.pointDemand === 2);
const dense = entries.filter((entry) => entry.pointDemand === 150);
assert.ok(sparse.length && dense.length, 'both kinds in view');
for (const entry of sparse)
assert.equal(drawn(entry), 2, 'a sparse tile keeps all');
const fair = Math.floor(tuning.maxPoints / tuning.maxTiles);
const left = tuning.maxPoints - sparse.length * 2;
for (const entry of dense)
assert.ok(
drawn(entry) > fair || dense.length * fair >= left,
`a dense tile gets more than the even share: ${drawn(entry)}`,
);
assert.ok(
total > tuning.maxPoints - 5 * dense.length,
'little budget unused',
);
});
test('a selected sequence the budget left out is drawn, highlighted', async (t) => {
const { state, coverage } = setup(t, {
tuning: {
...TUNING,
maxTiles: 1,
maxSequences: 10,
maxPoints: 5,
keepOrder: true,
},
decoded: (centre, tile) => ({
sequences: [
pointsLine('long', 5, tile, centre),
pointsLine('short', 3, tile, centre),
],
overview: [],
}),
});
coverage.refresh();
assert.ok(await until(() => state.coverage.tiles.size === 1));
const [tile] = state.coverage.tiles.values();
assert.deepEqual([...tile.sequences.keys()], ['long']);
state.sequence.selectedId = 'short';
coverage.recolorSequence('short', true);
assert.deepEqual([...tile.sequences.keys()].sort(), ['long', 'short']);
});