tile-cache-and-loading · git:20261026.79a684e · 2026-10-26 · sha256 982ba1565eb620fc

tile-cache-and-loading git:20261026.79a684eA

Immutable. This exact content never changes and is served at /api/v1/blob/982ba1565eb620fc.

---
name: tile-cache-and-loading
description: How map engines schedule, cancel, prioritise, cache and evict tiles or byte ranges, and what they reverted, distilled for a map-from-scratch renderer. Load it when a request's data does not fit in one baked file fetched once (streamed tiles, HTTP Range reads into PMTiles, GeoParquet or FlatGeobuf, live or refetched data), when writing a fetch queue, a tile or index cache, a memory budget or a persistent (Cache API, IndexedDB) cache, or when a bench shows request floods, blank tiles while panning, memory growth or refetch churn.
---

# Tile cache and loading: what to fetch, in what order, and what to keep

A streaming renderer repeatedly decides which tiles or byte ranges to
request, how many at once, in what order, which in-flight requests to drop
when the view moves, what to draw while they load, how many bytes of
decoded tiles to keep, and under which key a cached copy is still valid.
Each of these decisions has caused request floods, leaked memory, blank or
stale tiles, or a cache that thrashed in at least one of the 47 engines in
`ref-repos/`. Most map-from-scratch requests avoid all of this: every
example so far bakes its data and loads it whole [2]. Load this skill when
that stops being true. Per-engine detail, with more numbers, is in
[references/engines.md](references/engines.md). Neighbouring skills: the
archive layout and Range mechanics are in `data-range-requests`, parent and
child placeholders and overzoom in `tile-lod-and-overzoom`.

## First: do you need any of it?

- **One baked file that fits the memory budget, fetched once.** You need no
  tile cache, scheduler or persistent store. Give the file a stable or
  versioned URL and let the browser's HTTP cache work. maplibre-rs leaves
  all caching to the HTTP layer so that expiry headers are honoured and
  there is no cache format to maintain [4]. MapLibre GL JS built its own
  Cache API layer only because a per-load query parameter defeated HTTP
  caching, and then fixed a Chrome slowdown, a Safari leak and a Firefox
  SecurityError [5].
- **Data larger than the budget, read by Range from a static archive.** Use
  the index, request and memory sections below.
- **Live data, refetched or edited.** Read the section on invalidation and
  live updates.

The harness's static server sends `cache-control: no-store`, so every bench
iteration pays a first visit; it answers a single `bytes=a-b` range with
206 and sends the whole file for a multi-range header; and it sets COEP
`require-corp`, so remote data must come with CORS [1]. Measure a repeat
visit or a persistent cache explicitly, and merge neighbouring ranges into
one span rather than sending multi-range headers.

## Requests: cap, re-check, abort, order

- **Cap in-flight requests per server, sized by transport.** deck.gl moved
  its default from 8 to 6 to match the per-domain HTTP/1.1 limit and kept a
  single knob; on HTTP/2 its OSM example was "much much faster" with no
  request limit, and the merged example sets `maxRequests: 20` [6]. Cesium kept throttling on HTTP/2 but raised the limit from 6 to 18
  (3 subdomains x 6), because unlimited requests were wasted during camera
  flights [7]. RustGIS caps 8 per server in the browser (4 for OSM) and 16
  natively; on a high-latency source a 1920x1080 render went from about
  2.25 s to 1.3 s with more concurrency [8].
- **Decide at dispatch, not at enqueue.** deck.gl's scheduler asks a tile
  whether it is still selected when a slot opens, so tiles panned past are
  never fetched; a priority callback that returned a boolean failed to
  cancel anything until it returned 1/-1 [9]. RustGIS takes the newest
  waiting tile first and drops a waiting tile not asked for in 1.5 s [8].
- **Abort what is in flight, and make the abort release memory.** deck.gl
  passes an AbortSignal to the loader and aborts unselected tiles' requests
  [6]. MapLibre GL JS had to make aborted promises reject, stop
  dispatching tiles dropped during an async step, and remove unloaded tiles
  from every worker, because aborts "happen constantly during map
  movement" and leaked tiles [10]. Cesium leaked payloads through retained
  cancel functions and deferreds [11].
- **A cancelled tile is unloaded, not empty.** deck.gl cached cancelled
  tiles with `data: null`, and they stayed blank after zooming back out
  [12].
- **Centre first, by simple distance.** deck.gl orders queued tiles
  selected first, then by squared screen distance to the view centre [13].
  Cesium's foveated, motion-aware ordering cut camera-tour load time by
  about 67% and load count by about 44% [14]. navara orders index fetches
  by SSE, then distance, so the view centre resolves first under HTTP/1.1
  connection limits [15]. See the reversals below before inventing more.
- **Measure before tuning the queue.** RustGIS's scheduler cut requests by
  about 10% (168 to 152) on a fast machine with no change in frame times:
  the stalls came from drawing [8]. Cesium's global scheduler was a "big
  win" on slow links but slightly slower on HTTP/2 at normal speeds [16].
- **Throttle work, not only requests, while the camera moves.** MapLibre GL
  JS limits raster image requests per frame while moving; the first version
  regressed because the throttled queue did not advance by itself [17].
  deck.gl's `debounceTime` saves requests for tiles passed over, but it is
  opt-in (default 0) and delays the first tile of a settled view [18].
- **Clamp the tile cover.** QGIS found that a view whose extent failed to
  reproject (geographic CRS touching +/-180 or +/-90) handed the vector
  tile renderer a maximal extent and fetched every tile of the zoom level;
  it now derives a minimal extent or fails [55]. Cap the count a cover
  computation may return.

## Memory: a byte budget, not a tile count

- **Count bytes.** In ortho-earth a dense z16 city tile is about 100 KB
  and an offshore z8 tile tens of bytes, so the old 256-tile cap meant
  nothing; the budget now follows `navigator.deviceMemory` (16 GB about
  96 MB, 4 GB about 48 MB, 2 GB about 24 MB), and tiles in view are always
  kept [19]. deck.gl reads a `byteLength` that the tile data reports [20].
  RustGIS bounds streamed windows at 128 MiB on the desktop and 48 MiB in
  the browser, because vertices per feature vary by orders of magnitude
  [21].
- **Plan for the view alone exceeding the budget.** navara evicts the
  oldest and farthest pooled tiles down to 85% of the budget; if visible
  tiles alone exceed it, a load gate stops new fetches and the SSE
  threshold rises by 1.25 every 15 stalled frames; budgets are a quarter of
  device memory capped at 2 GB on desktop, 512 or 256 MB on mobile [22].
  Cesium split its limit into `cacheBytes` and `maximumCacheOverflowBytes`
  and raises the SSE when memory is exceeded [23]. A coarser view instead
  of a dead tab: navara notes that tabs are killed when memory runs out
  (especially on iOS), and that a tilted view over Tokyo could allocate
  until the tab died [22].
- **Reserve at dispatch.** navara reserves an estimated cost when a fetch
  is sent (EMA mean plus 0.5 SD, clamped to 64 KB-16 MB), because a fixed
  hint undercounted terrain about 10x and bursts slipped past the gate
  [22].
- **A budget smaller than the working set thrashes.** QGIS raised its 3D
  chunk budget from 100 to 500 MB because tiles were constantly unloaded
  and reloaded [24]. MapLibre Native sizes its in-memory cache from tiles
  per screen and removed the user setter [25].
- **Small traps.** Prune tiles that are neither visible nor selected, not
  merely invisible, or loading tiles are evicted and refetched; a size of 0
  must mean 0, not "use the default" (`??`, not `||`) [20].

## Invalidation, keys and live data

- **Key Range reads by URL and range.** All PMTiles requests share one
  URL, so MapLibre Native appends `_mlnRange=start-end` to the cache key
  and navara dedups on URL plus range [27]. Keep ranges deterministic
  (whole index blocks, whole row groups) so keys repeat [27].
- **Detect a replaced archive.** Replacing a PMTiles archive in place while
  MapLibre Native ran crashed it until ETag was tracked [28]. ortho-earth
  keys IndexedDB row groups by `URL|ETag|row group|mode|column`; a second
  session needed 2 Range requests (64 KB) instead of 32 (7.1 MB) [29].
  Immutable, versioned file names avoid the problem.
- **Put everything that moves pixels in every cache layer's key.**
  reearth-papers keys rendered tiles by style, renderer version, data date
  and flavour; a key without the data date invalidated nothing on a new
  monthly snapshot, and a renderer bump that rotated R2 but not the edge
  cache kept serving old tiles [30]. ortho-earth had to key baked geometry
  by projection mode after an offline pack built in one mode showed
  nothing in the other [31].
- **A failed fetch is not "absent" and must not replace a good copy.**
  MapLibre Native caches only 200 and 404, because other errors "will
  likely evict perfectly good content" [32]; an offline error overwrote
  cached glyphs [33]; reearth-papers settles a glyph as absent only from a
  block that arrived and parsed [34].
- **Unchanged refetch, no repaint.** MapLibre GL JS compares the ETag
  before parsing and skips the parse and the data event when it is the
  same [26].
- **Live edits: diff by id, latest wins.** GeoJSON `updateData` keyed by
  feature id took an update from 11.5 ms to 1.8 ms and reloads only
  affected tiles, falling back to a full reload above 1,000 changes [35].
  When updates arrive faster than the worker, keep only the latest: GL JS
  and Native workers coalesce to the newest state, and dropped requests
  must still resolve their callbacks [36].
- **Keep old content until the new load lands.** deck.gl keeps stale tile
  content during a reload and discards results of superseded loads by a
  loader id [37]; reearth-visualizer compares features and keeps old ones
  to stop blinking [38].

## Range-read archives: the index is a cache too

- One bootstrap read large enough for the header and root index: navara
  reads 16 KiB first [15]. Load deeper index levels lazily: Cesium's
  progressive availability sped up load and cut memory [39].
- Bound the parsed-index cache: navara 32 MB of leaf directories, because
  all leaves of a global archive cost 50-500 MB [15]; MapLibre Native 100
  directories per archive [40]. Tiles waiting on the same leaf share one
  fetch (RustGIS) [41].
- GeoParquet: read the footer with two CORS-safelisted Range requests
  (`bytes=0-0` for the total length, then an explicit range for the last
  64 KB), not a suffix range: `bytes=-N` is outside the safelist, so it
  triggers a preflight and fails on hosts that do not answer OPTIONS. Then
  prune row groups by bbox statistics and coalesce column-chunk ranges. A
  Tokyo view of an 8,000-feature file touched 32 of 63
  row groups and 110 KB of 505 KB [42]. ortho-earth streams only above
  8 MB; below it, one GET is simpler [42].
- Cache the archive's metadata (PMTiles header, Parquet footer) in memory
  by URL, so reopening a layer costs no request; Cesium does this for
  imagery metadata and endpoint lookups within a session [56].
- Avoid per-request custom headers on cross-origin tiles: each costs a
  preflight, and Cesium's Accept-header trick to avoid one was closed by a
  spec change and reverted [43].

## Upload and per-frame work

- Upload each tile or chunk once and draw it in place. ortho-earth's WebGPU
  backend re-merged all visible tiles (33 ms for 35 tiles, 9-11 hitches
  over 4 ms per 9 s pan); a per-tile pool made it 0 hitches [44]. deck.gl:
  appending 100K rows to 1M rebuilds 1.1M rows; one buffer per chunk does
  not [45].
- Time-slice with `performance.now()`: Cesium's 5 ms load budget overran
  to over 10 ms with `Date.now()` [46]. A time-sliced queue needs a bound or
  memoisation, or it grows on slow devices [47].
- Decode and lay out in workers when data arrives at run time; MapLibre GL
  JS then had to shrink worker-to-main transfers [48]. Cesium made
  `createImageBitmap` the default and reverted it in the next patch
  release (flipY, browser bugs) [49].
- If the bake writes many tiles, measure encoding apart from rendering:
  in QGIS's XYZ export PNG encoding cost about twice a simple render, and
  moving encoding to a thread pool with one batched MBTiles writer (WAL,
  `synchronous=OFF`, acceptable for a rebuildable output) took 75,560
  tiles from 268 s to 21 s [54].

## Tried and reverted

- Cesium's global distance-sorted scheduler (#3476) was taken off terrain
  and imagery: a closure per tile, a sort per frame, and important tiles
  loaded late; with the reversal, cancelled requests fell from 755 to 0 in
  one view (a Grand Canyon horizon), and loading was 1.4x to 2.3x faster
  with about 33% less data overall [16].
- navara committed and reverted a custom fetch priority, keeping
  OrderByDistance [50].
- deck.gl added and removed a `throttleRequests` flag and rejected a second
  max-requests prop [9][6].
- MapLibre GL JS reverted, then re-landed, setData coalescing (abandoned
  callbacks) [36]; moving `updateData` bounds to the worker gave no gain and
  was reverted [35].
- maplibre-rs disabled an IndexedDB tile store and a Workbox cache [4].
- QGIS removed a geometry cache that was rebuilt on every redraw [51].
- navara removed a `scene_ready` flag mirrored across JS and WASM; it went
  out of sync on eviction [52].

## Guidance for a renderer here

1. Prefer the baked single file. Reach for tiles or Range streaming only
   when the measured data exceeds the budget in `spec.yaml` or the first
   frame budget cannot be met by one fetch, and say so in
   `architecture_selection`.
2. If streaming: lay the archive out so that one bootstrap read gets the
   header and root index, blocks are spatially ordered, and ranges are
   deterministic; cache the index with a byte or count bound; key every
   cache by (URL, offset, length) plus a version (ETag or versioned name).
3. Fetch queue: per-origin cap (6 on HTTP/1.1, higher on HTTP/2, measured),
   visibility re-checked at dispatch, AbortSignal on fetch, centre-first by
   distance, cancelled means unloaded. Do not build a cross-source priority
   scheduler.
4. Memory: a byte budget from the target device, eviction with hysteresis,
   visible tiles pinned, and a declared degradation (coarser LOD or stop
   refining) when the view alone exceeds it.
5. Draw from parents while children load only if zooming over streamed data
   is in the request (the `tile-lod-and-overzoom` skill).
6. Bench under `benchmarks.network` [3] with budgets on requests, bytes and
   peak memory, and add verification checks that fail when a pan back
   refetches cached tiles, an aborted load keeps memory, a cancelled tile
   stays blank, or a tile is re-baked every frame (Cesium's `makeClean` bug
   re-baked every frame) [53].

## Sources

1. `harness/lib/serve.ts` (this repository)
2. `README.md` and `skills/map-from-scratch/SKILL.md` (this repository)
3. `skills/map-from-scratch/references/spec-yaml.md` (this repository)
4. maplibre-rs caching: `ref-repos/maplibre__maplibre-rs/docs/src/development-documents/caching.md`, https://github.com/maplibre/maplibre-rs/blob/main/docs/src/development-documents/caching.md, https://github.com/maplibre/maplibre-rs/commit/7e11f85, https://github.com/maplibre/maplibre-rs/commit/fad6b35, https://github.com/maplibre/maplibre-rs/commit/1f6e31a, https://github.com/maplibre/maplibre-rs/commit/52cc15b, https://github.com/maplibre/maplibre-rs/commit/151ee9f
5. MapLibre GL JS Cache API tile cache: https://github.com/maplibre/maplibre-gl-js/commit/500320f89, https://github.com/maplibre/maplibre-gl-js/commit/20c90905a, https://github.com/maplibre/maplibre-gl-js/commit/6b424fee4, https://github.com/maplibre/maplibre-gl-js/commit/70087e3f8, https://github.com/maplibre/maplibre-gl-js/commit/83ab6b72d
6. deck.gl abort and concurrency: https://github.com/visgl/deck.gl/pull/4838, https://github.com/visgl/deck.gl/commit/8046edd2, https://github.com/visgl/deck.gl/commit/0adfe055, https://github.com/visgl/deck.gl/commit/19af42d9
7. Cesium per-server limit: https://github.com/CesiumGS/cesium/commit/26fc56183, https://github.com/CesiumGS/cesium/pull/6445, https://github.com/CesiumGS/cesium/issues/11627, https://github.com/CesiumGS/cesium/issues/6426, `ref-repos/CesiumGS__cesium/CHANGES.md`
8. RustGIS fetch scheduler: https://github.com/opengeos/RustGIS/pull/636, https://github.com/opengeos/RustGIS/issues/635, https://github.com/opengeos/RustGIS/commit/b8c77f7, https://github.com/opengeos/RustGIS/commit/5788729
9. deck.gl request scheduler: https://github.com/visgl/deck.gl/commit/1ca25a6b, https://github.com/visgl/deck.gl/commit/b2fcd89b, https://github.com/visgl/deck.gl/commit/e7261715, https://github.com/visgl/deck.gl/commit/05ca54e0, https://github.com/visgl/deck.gl/commit/a75dc388
10. MapLibre GL JS abort paths: https://github.com/maplibre/maplibre-gl-js/commit/1d3d433d9, https://github.com/maplibre/maplibre-gl-js/commit/368a604f1, https://github.com/maplibre/maplibre-gl-js/commit/95c703172
11. Cesium cancelled-request leak: https://github.com/CesiumGS/cesium/commit/1f3be6cd8, https://github.com/CesiumGS/cesium/issues/8843
12. deck.gl cancelled tiles cached empty: https://github.com/visgl/deck.gl/pull/4645
13. deck.gl centre-first priority: https://github.com/visgl/deck.gl/commit/a75dc388, `ref-repos/visgl__deck.gl/modules/geo-layers/src/tileset-2d/tileset-2d.ts`
14. Cesium foveated prioritisation: https://github.com/CesiumGS/cesium/pull/7774, `ref-repos/CesiumGS__cesium/CHANGES.md`
15. navara PMTiles reader: https://github.com/maplibre/navara/blob/main/crates/pmtiles/src/archive.rs, https://github.com/maplibre/navara/blob/main/crates/navara_pmtiles/src/source.rs, https://github.com/maplibre/navara/blob/main/docs/src/content/docs/three/Source/vector-tile-source.md
16. Cesium global scheduler and its reversal: https://github.com/CesiumGS/cesium/pull/3476, https://github.com/CesiumGS/cesium/pull/7061
17. MapLibre GL JS raster throttle while moving: https://github.com/maplibre/maplibre-gl-js/commit/99ad3d7d0, https://github.com/maplibre/maplibre-gl-js/commit/af0254e20
18. deck.gl debounceTime: https://github.com/visgl/deck.gl/commit/401e7a6d
19. ortho-earth byte-bounded tile cache: https://github.com/kenjiyoshidahome2026-bit/ortho-earth/commit/45a8edb1
20. deck.gl cache by bytes and pruning: https://github.com/visgl/deck.gl/issues/4230, https://github.com/visgl/deck.gl/commit/f00e3ffc, https://github.com/visgl/deck.gl/commit/68533ae0
21. RustGIS byte-bounded streamed views: `ref-repos/opengeos__RustGIS/docs/user-guide/data.md`, https://github.com/opengeos/RustGIS/commit/cfeef0c
22. navara memory management: https://github.com/maplibre/navara/blob/main/guide/MEMORY_MANAGEMENT.md, `ref-repos/maplibre__navara/guide/MEMORY_MANAGEMENT.md`, https://github.com/maplibre/navara/pull/692
23. Cesium cacheBytes and memory-adjusted SSE: https://github.com/CesiumGS/cesium/pull/11310, https://github.com/CesiumGS/cesium/commit/0fa7440b0463e4b87ebca23d93b4b60a27d1b30e, https://github.com/CesiumGS/cesium/commit/a8ddd9749cd7e5a79c4a9c42d7e329ffab060faf, https://github.com/CesiumGS/cesium/commit/5de34c26a5b880ccf5d289d40bf90d2c608ee0c5, https://github.com/CesiumGS/cesium/commit/0bf03df1fb74caa0353f093fda38b8d399311f13
24. QGIS 3D chunk budget: https://github.com/qgis/QGIS/commit/07af5ea9a7
25. MapLibre Native cache sized from viewport: https://github.com/maplibre/maplibre-native/commit/44b960fb9, https://github.com/maplibre/maplibre-native/commit/68440ee6c, https://github.com/maplibre/maplibre-native/commit/b0a928dcb, https://github.com/maplibre/maplibre-native/commit/20189db67, https://github.com/maplibre/maplibre-native/commit/8fa078c08, https://github.com/maplibre/maplibre-native/commit/f40519e78
26. MapLibre GL JS ETag skip: https://github.com/maplibre/maplibre-gl-js/pull/6972, https://github.com/maplibre/maplibre-gl-js/pull/7074
27. Range cache keys: https://github.com/maplibre/maplibre-native/commit/a2eb4409a, `ref-repos/maplibre__maplibre-native/platform/default/src/mln/storage/offline_database.cpp`, `ref-repos/maplibre__maplibre-native/test/storage/offline_database.test.cpp`, `ref-repos/maplibre__maplibre-native/test/storage/pmtiles_file_source.test.cpp`, https://github.com/maplibre/navara/commit/20d30a41
28. MapLibre Native PMTiles ETag invalidation: https://github.com/maplibre/maplibre-native/pull/4290, https://github.com/maplibre/maplibre-native/pull/4111
29. ortho-earth IndexedDB row-group cache: https://github.com/kenjiyoshidahome2026-bit/ortho-earth/commit/c5492461
30. reearth-papers cache keys: https://github.com/reearth/reearth-papers/commit/fec9e11, https://github.com/reearth/reearth-papers/commit/9e53d86, https://github.com/reearth/reearth-papers/commit/eb31c89, `ref-repos/reearth__reearth-papers/src/ezu.ts`, `ref-repos/reearth__reearth-papers/CONTRIBUTING.md`
31. ortho-earth projection-mode cache key: https://github.com/kenjiyoshidahome2026-bit/ortho-earth/issues/43
32. MapLibre Native disk cache eviction: https://github.com/maplibre/maplibre-native/commit/a2ceeb4a5, https://github.com/maplibre/maplibre-native/commit/a06f28ddb, https://github.com/maplibre/maplibre-native/commit/6d0b01c5f
33. MapLibre Native glyph cache overwritten offline: https://github.com/maplibre/maplibre-native/issues/2410
34. reearth-papers negative glyph cache: https://github.com/reearth/reearth-papers/commit/6af6885
35. MapLibre GL JS updateData: https://github.com/maplibre/maplibre-gl-js/pull/1605, https://github.com/maplibre/maplibre-gl-js/pull/6562
36. Latest-wins coalescing: https://github.com/maplibre/maplibre-gl-js/commit/fa95c19c0, https://github.com/maplibre/maplibre-gl-js/commit/725f2666b, https://github.com/maplibre/maplibre-gl-js/commit/e5497caf1, `ref-repos/maplibre__maplibre-native/docs/mdbook/src/design/geometry-tile-worker.md`
37. deck.gl stale content during reload: https://github.com/visgl/deck.gl/commit/9b263690, https://github.com/visgl/deck.gl/commit/1798a333
38. reearth-visualizer blink fix: https://github.com/reearth/reearth-visualizer/commit/c10a6713
39. Cesium progressive availability: https://github.com/CesiumGS/cesium/pull/7196, `ref-repos/CesiumGS__cesium/CHANGES.md`
40. MapLibre Native PMTiles directory cache: https://github.com/maplibre/maplibre-native/commit/cf9f41cf6, `ref-repos/maplibre__maplibre-native/platform/default/src/mln/storage/pmtiles_file_source.cpp`
41. RustGIS PMTiles leaf single-flight: https://github.com/opengeos/RustGIS/commit/f9a9343, https://github.com/opengeos/RustGIS/commit/4d0d1ff
42. ortho-earth GeoParquet over Range: https://github.com/kenjiyoshidahome2026-bit/ortho-earth/blob/main/packages/geopbf/README.md, `ref-repos/kenjiyoshidahome2026-bit__ortho-earth/packages/geopbf/README.md`, `ref-repos/kenjiyoshidahome2026-bit__ortho-earth/DATA.md`, https://github.com/kenjiyoshidahome2026-bit/ortho-earth/commit/551a2aa5
43. Cesium Accept-header token: https://github.com/CesiumGS/cesium/commit/c67111c60, https://github.com/CesiumGS/cesium/commit/d6b06e5c4, https://github.com/CesiumGS/cesium/pull/8561
44. ortho-earth WebGPU tile pool: https://github.com/kenjiyoshidahome2026-bit/ortho-earth/blob/main/packages/ortho-core/vt%20perf%20plan.md, `ref-repos/kenjiyoshidahome2026-bit__ortho-earth/packages/ortho-core/vt perf plan.md`
45. deck.gl incremental loading: https://github.com/visgl/deck.gl/blob/master/docs/developer-guide/performance.md
46. Cesium performance.now budget: https://github.com/CesiumGS/cesium/commit/2c162b1df
47. Cesium bounded height-update queue: https://github.com/CesiumGS/cesium/commit/142b1d6c545bed0119493b12e4d969be100e7d2b
48. MapLibre GL JS worker pipeline: `ref-repos/maplibre__maplibre-gl-js/developer-guides/life-of-a-tile.md`, `ref-repos/maplibre__maplibre-gl-js/ARCHITECTURE.md`, https://github.com/mapbox/mapbox-gl-js/pull/7124
49. Cesium ImageBitmap revert: https://github.com/CesiumGS/cesium/pull/7579, `ref-repos/CesiumGS__cesium/CHANGES.md`
50. navara fetch priority reverted: https://github.com/maplibre/navara/commit/20d30a41
51. QGIS geometry cache removed: `ref-repos/qgis__QGIS/ChangeLog` (2017-05-05 Remove QgsGeometryCache class)
52. navara drape readiness: https://github.com/maplibre/navara/blob/main/guide/VECTOR_TILE_DRAPING.md
53. Cesium per-frame re-bake: https://github.com/CesiumGS/cesium/commit/1bcefa194c5f047bbf43ada6201094af8948d29a
54. QGIS tile export pipeline: https://github.com/qgis/QGIS/commit/6439498f90, https://github.com/qgis/QGIS/commit/607e65f42d, https://github.com/qgis/QGIS/commit/a96513a36a
55. QGIS whole-zoom fetch guard: `ref-repos/qgis__QGIS/ChangeLog` (2025-04-23 "[vectortiles] Never try to request every tile in a zoom level", line 53299)
56. Cesium metadata cache: https://github.com/CesiumGS/cesium/commit/4874f8ffe