Fix Data Library share restore, dense tile layers and EPC partition reads

- Share links: the compact /share/<code> format carries the switched-on
  library entries (field 29), so Copy Link keeps them; a share opened in the
  same tab restores them; any share decides what is on, so one with every
  library entry off no longer switches on the recipient's stored entries.
- A tile entry too dense at its minimum zoom (Norway cadastre z14, depth z11)
  is hidden below that scale via minimumTerrainLevel instead of allocating
  millions of tiles when zoomed out.
- EPC partitions keep the full extent for exclusion and the 98 % extent for
  ranking only; the old trimmed-only extents are retired.
- Partition index footers that fail transiently are retried; an incomplete
  index is never cached as complete, a complete prior index is kept, and it
  is rebuilt after five minutes.
- Views that leave touching partitions unread are partial; listing and
  footer bodies are capped and ranges validated.
- The Browse dialog relabels after a language switch.
This commit is contained in:
Said Turksever committed 2026-11-12 22:37:34 +00:00
1 parent 871b7f0fcc
commit 24ccdd4dc3
20 files changed
+1220 -162

No files matched your search

+18
View File
@@ -585,6 +585,24 @@ the ledger (append-only with `--base-ref`, run in CI on pull requests) and the
generated DATA_SOURCES.md section; `npm run library:probe` hits every upstream
once; `npm run qa:data-library` is the browser gate (fixture-backed).
Shares: the compact `/share/<code>` link carries the switched-on entries in
field 29 (docs/SHARE-LINK-FORMAT.md), so Copy Link keeps them, and a share
opened later in the same tab (pasted address, Back/Forward) restores them too.
Any share decides what is on, as for core layers: one with no library entry
on switches the recipient's stored ones off and leaves them listed. A tile
entry whose coverage needs more than 256 tiles at its minimum zoom (Norway's
cadastre from z14, sea depths from z11) is hidden below that scale through
Cesium's `minimumTerrainLevel`, so zoomed out it requests nothing. EPC
partitions learn two extents (`gev:library:extent:v2:`): every point's box
rules a partition out, the 98 % box only ranks it, so homes at a partition's
edge are found on later visits; the v1 trimmed-only key is dropped. A
partition index whose footers keep failing after two retries is
`complete: false`: neither the server nor the browser keeps it as whole, a
complete earlier index is served instead, and it is rebuilt after five
minutes. A view that leaves partitions touching it unread is PARTIAL. Listing
pages and footer reads are size-capped and ranges are validated. The Browse
dialog rebuilds on a language switch, keeping its filters.
## Utilities panel — October 29, 2026
UTILITIES is its own left-side panel, between Data Layers and Scenes, with
+37 -31
View File
@@ -117,36 +117,37 @@ probability of about 255 in 256 (many of them already fail as base64url).
## Field registry (top level)
| # | State key | Type | Default | Encoding |
| --- | ---------------------------- | ----- | ----------- | ----------------------------------------------------------------- |
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
| 4 | `alt` | sint | 800 | whole metres |
| 5 | `heading` | angle | 0 | whole degrees, see angles |
| 6 | `pitch` | sint | −35 | whole degrees |
| 7 | `roll` | angle | 0 | whole degrees |
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
| 9 | booleans | flags | 0 | bit set when the value differs from default |
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
| 13 | thing | LEN | none | sub-message, see thing views |
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
| 16 | `hudVariant` | enum | style | tactical, operator (retired), minimal, cyber |
| 17 | `bloomIntensity` | uint | style | |
| 18 | `bloomVersion` | uint | 2 | |
| 19 | `sharpenIntensity` | uint | style | |
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
| 23 | `detectionFadePct` | uint | style | 0–40 |
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
| 28 | `worldTime` | epoch | none (live) | zigzag seconds since 2026-01-01T00:00:00Z |
| # | State key | Type | Default | Encoding |
| --- | ---------------------------- | ----- | ----------- | --------------------------------------------------------------------- |
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
| 4 | `alt` | sint | 800 | whole metres |
| 5 | `heading` | angle | 0 | whole degrees, see angles |
| 6 | `pitch` | sint | −35 | whole degrees |
| 7 | `roll` | angle | 0 | whole degrees |
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
| 9 | booleans | flags | 0 | bit set when the value differs from default |
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
| 13 | thing | LEN | none | sub-message, see thing views |
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
| 16 | `hudVariant` | enum | style | tactical, operator (retired), minimal, cyber |
| 17 | `bloomIntensity` | uint | style | |
| 18 | `bloomVersion` | uint | 2 | |
| 19 | `sharpenIntensity` | uint | style | |
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
| 23 | `detectionFadePct` | uint | style | 0–40 |
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
| 28 | `worldTime` | epoch | none (live) | zigzag seconds since 2026-01-01T00:00:00Z |
| 29 | `library` | LEN | none | Data Library ledger tokens, two base-36 characters each, concatenated |
Field 1 absent means `thing` when field 13 is present and `place` otherwise,
so v1 never writes it; a later view kind writes its code.
@@ -164,6 +165,11 @@ Field 28 is the world time the link was made at, written only when it was away
from live. A link without it opens live. Unlike field 8 it may lie ahead of the
link's creation (a forecast moment), and it opens paused at that time.
Field 29 lists the Data Library entries switched on, by their permanent
tokens in `src/library/ledger.json` (the long form's `lib=ab.cd`). A share
without it switches no library entry on; the recipient's added entries stay
in their panel, off. Tokens this build does not know are skipped.
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
@@ -391,7 +397,7 @@ the app that can open it.
offers keeps its code: `snow` (6) is retired, so a link that names it
opens as Normal and the parameters written for it are dropped.
5. **Add, don't change.** A new setting takes the next free field number
(28 onwards; fields 1–15 have one-byte tags and are all taken). A new
(30 onwards; fields 1–15 have one-byte tags and are all taken). A new
boolean takes the next flag bit instead, which costs nothing when it is at
its default.
6. **New view kinds and entity kinds** get the next code. A new entity kind's