@chrtco/sdk 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -107,6 +107,195 @@ declare class OfflineStyleError extends Error {
107
107
  */
108
108
  declare function buildOfflineStyle(onlineStyle: MapLibreStyle, mbtilesPath: string, opts?: BuildOfflineStyleOptions): MapLibreStyle;
109
109
 
110
+ /**
111
+ * Strict scale-band priority for continuous-mode rendering.
112
+ *
113
+ * Each ENC feature in the served PMTiles carries `_cell_id` (added at
114
+ * baremetal NDJSON export time). NOAA's cell-id format encodes the source
115
+ * scale band as the third character: `US5MA85M` is band 5 (Harbor).
116
+ *
117
+ * MapLibre layer filters can't read `["zoom"]`, so to enforce "only one
118
+ * band renders at any given zoom" we emit one layer copy per band, each
119
+ * with a static band filter and per-band `minzoom`/`maxzoom`. The ranges
120
+ * below are non-overlapping (`minzoom` inclusive, `maxzoom` exclusive),
121
+ * so exactly one band is visible at any continuous zoom.
122
+ *
123
+ * Long-term cleanup: baremetal should emit a normalized `_band: 1..6`
124
+ * integer in NDJSON so this works for non-NOAA chart sources too. The
125
+ * `["slice", _cell_id, 2, 3]` extraction here is NOAA-specific.
126
+ */
127
+ /**
128
+ * UI-facing scale-band names (matches the app's `ScaleBand` type in
129
+ * `src/lib/map/constants.ts`). Defined locally so the SDK doesn't have
130
+ * to depend on app code.
131
+ */
132
+ type ScaleBand$1 = "Overview" | "General" | "Coastal" | "Approach" | "Harbor";
133
+ /** S-57 source bands. NOAA cell ids use these as the third character. */
134
+ type BandCode = "1" | "2" | "3" | "4" | "5" | "6";
135
+ interface BandPriorityRange {
136
+ /** MapLibre `minzoom` (inclusive). */
137
+ minzoom: number;
138
+ /** MapLibre `maxzoom` (exclusive). `undefined` means no upper cap. */
139
+ maxzoom?: number;
140
+ }
141
+ /**
142
+ * Per-band minzoom thresholds for continuous mode. Bands cascade as
143
+ * fallbacks for one another — every band whose `minzoom` is satisfied
144
+ * may render. The deeper-numbered band's features render preferentially
145
+ * (drawn last in the z-stack, so its opaque fills cover lower bands),
146
+ * and lower bands fill in where the higher band has no coverage.
147
+ *
148
+ * Cross-band visual overlap (doubled coastlines, duplicate labels) is
149
+ * suppressed by the `_dominated_by_min` per-feature tag added at NDJSON
150
+ * export time (see `tools/chrt-baremetal/server/src/tilebuild/dominanceIndex.ts`):
151
+ * every feature whose centroid sits inside a higher-band cell's M_COVR
152
+ * coverage polygon is tagged with the LOWEST band code (2..6) that
153
+ * dominates it, and `makeBandVariants()` in the style generator emits
154
+ * per-(featureBand, dominatorBand) layer variants gated by `maxzoom =
155
+ * dominatedVariantMaxzoom(D)`. The dominated feature renders until
156
+ * exactly the zoom where its dominator becomes eligible AND has data in
157
+ * the tiles — solving the low-zoom "black silhouettes" bug where a
158
+ * band-3 LNDARE dominated by a band-5 cell was dropped at z7 even
159
+ * though band 5 wasn't yet rendering at z7.
160
+ *
161
+ * Opaque base-area FILLS are exempt from dominance gating entirely (see
162
+ * `DOMINANCE_EXEMPT_FILL_CLASSES` in the style generator): band-major
163
+ * draw order already makes the most-detailed band's opaque fill win at
164
+ * any covered pixel, and the coarser band shows through wherever the
165
+ * detailed band has no data — which is exactly the fallback semantics
166
+ * the cascade wants. The 2026-07 E1/E2 void diagnosis
167
+ * (docs/validation/portrayal-p1-p3-void-diagnosis-2026-07.md) proved
168
+ * per-feature dominance gating on fills structurally unsound: the tag is
169
+ * point-sampled but hides the WHOLE feature, so a Gulf-spanning band-1
170
+ * DEPARE tagged dominated-by-2 left dark voids across every part of the
171
+ * Gulf that band 2 doesn't actually cover.
172
+ *
173
+ * Bands 5 and 6 both start at z11; band 6 (Berthing) is drawn AFTER
174
+ * band 5 (Harbor) so where Berthing exists it visually wins, and
175
+ * Harbor shows through everywhere else.
176
+ *
177
+ * Band 1 starts at z0: the P3 low-zoom contract (charts never disappear
178
+ * from world view down) needs Overview-band data renderable as soon as
179
+ * tiles exist. Builds older than the z0 floor simply have no tiles below
180
+ * z3, so the widened range is a no-op against them.
181
+ */
182
+ declare const BAND_PRIORITY_RANGES: Record<BandCode, BandPriorityRange>;
183
+ /**
184
+ * Order in which band layers are emitted (and thus drawn — earlier = lower
185
+ * in the z-stack). Band 6 last so Berthing draws on top of Harbor.
186
+ */
187
+ declare const BAND_DRAW_ORDER: BandCode[];
188
+ /**
189
+ * UI scale-band names → cell-id band codes that should render in chart mode
190
+ * for that selection. The UI doesn't expose Berthing separately, so picking
191
+ * Harbor includes both `5` and `6` cells.
192
+ *
193
+ * `CHART_BAND_CODES` is the *requested* band set (what the user picked);
194
+ * `CHART_CASCADE_CODES` below adds the coarser bands that fall back where
195
+ * the requested band has no cells.
196
+ */
197
+ declare const CHART_BAND_CODES: Record<ScaleBand$1, readonly BandCode[]>;
198
+ /**
199
+ * UI scale-band names → ordered cascade of cell-id band codes for chart-mode
200
+ * rendering with graceful coverage fallback. Bands are listed coarsest-first
201
+ * so the style generator can use array order as draw order: lower bands draw
202
+ * earlier in the z-stack, the requested band draws last on top.
203
+ *
204
+ * The chart-mode implementation emits one filtered layer per cascade band per
205
+ * S-57 class. Where the requested band's cells provide data, its opaque fills
206
+ * cover the lower bands underneath; where the requested band has no cells
207
+ * (e.g. Chart→Harbor over Massachusetts Bay offshore), the next-coarser band
208
+ * shows through. Different cells in the same viewport may end up rendering at
209
+ * different effective bands.
210
+ *
211
+ * The per-feature `_dominated_by_min` tag baked at baremetal NDJSON-export
212
+ * time (see `tools/chrt-baremetal/server/src/tilebuild/dominanceIndex.ts`)
213
+ * suppresses lower-band features inside higher-band cell coverage so cell
214
+ * boundaries don't render with doubled coastlines or contours.
215
+ *
216
+ * Lock scope: when chart-Harbor's viewport-lock greyout is active, the
217
+ * `["within"]` bbox filter is applied ONLY to the requested band's variants
218
+ * — cascade fallback bands render across the full viewport. The greyout
219
+ * overlay still cues the lock boundary visually; the user picks Harbor and
220
+ * gets Harbor inside the lock plus dimmed Approach/Coastal context outside.
221
+ *
222
+ * See `.chrt/ux/chart-mode-band-fallback-2026-05-05.md` for the design
223
+ * rationale and the geographies (Boston Harbor, Massachusetts Bay offshore)
224
+ * that motivated this.
225
+ */
226
+ declare const CHART_CASCADE_CODES: Record<ScaleBand$1, readonly BandCode[]>;
227
+ /**
228
+ * A served-style render mode — the wire vocabulary of the Style API's
229
+ * `?mode=` parameter, and the exact value the generated document echoes in
230
+ * `metadata["chrt:render-mode"]`. One string, one meaning: what a consumer
231
+ * requests is what the style reports.
232
+ *
233
+ * `continuous` (the default) is the free-zoom portrayal: one layer copy per
234
+ * source band with strict per-band zoom windows. `chart:{band}` locks the
235
+ * portrayal to one scale band (paper-chart semantics) with cascade fallback
236
+ * to coarser bands where the requested band has no cells.
237
+ */
238
+ type ChrtStyleMode = "continuous" | `chart:${ScaleBand$1}`;
239
+ /**
240
+ * Every valid render mode, in presentation order. Derived from
241
+ * `CHART_BAND_CODES`, so a band is a valid chart mode exactly when it has a
242
+ * chart-mode band config — Berthing deliberately has none (no NOAA band
243
+ * ranges are defined for it) and is rejected like any other invalid value,
244
+ * and a future band that gains codes becomes requestable with no route
245
+ * change. Single source of truth for both style routes' validation, their
246
+ * 400 payloads, and the SDK's `StyleUrlOptions["mode"]`.
247
+ */
248
+ declare const CHRT_STYLE_MODES: readonly ChrtStyleMode[];
249
+ /**
250
+ * Normalize a raw `?mode=` value to its canonical `ChrtStyleMode`, or null
251
+ * if invalid. Case-insensitive, same contract as `normalizeTheme`: absent /
252
+ * empty means the default (`continuous`); a bare `chart` (no band) or an
253
+ * unknown band is invalid — the caller should answer with a typed 400
254
+ * listing `CHRT_STYLE_MODES`, never silently fall back to continuous.
255
+ */
256
+ declare function normalizeStyleMode(raw: string | null | undefined): ChrtStyleMode | null;
257
+ /** The band a mode locks to — null for `continuous`. */
258
+ declare function styleModeBand(mode: ChrtStyleMode): ScaleBand$1 | null;
259
+ /**
260
+ * Build a MapLibre filter expression that matches features in the given
261
+ * band code(s) by extracting the band character from `_cell_id`.
262
+ */
263
+ declare function bandFilterExpression(codes: BandCode | readonly BandCode[]): unknown[];
264
+ /**
265
+ * Combine a base filter (may be undefined) with a band filter via `["all"]`.
266
+ * If the base filter is itself an `["all", ...]`, flatten so we don't end
267
+ * up with `["all", ["all", ...], ...]` (which is legal but ugly).
268
+ */
269
+ declare function combineWithBandFilter(baseFilter: unknown[] | undefined, bandFilter: unknown[]): unknown[];
270
+ /**
271
+ * Geographic bounding box used for chart-mode viewport locking.
272
+ * Coordinates in WGS84 lng/lat. `sw` is south-west corner, `ne` is north-east.
273
+ */
274
+ interface BBox {
275
+ sw: [number, number];
276
+ ne: [number, number];
277
+ }
278
+ /**
279
+ * Build a MapLibre `["within"]` filter expression that matches features
280
+ * inside the given bbox. Used by chart mode to spatially limit which
281
+ * features render — when locked to a Harbor area, only features inside
282
+ * the locked bbox draw, even though the tile pyramid covers more.
283
+ *
284
+ * The polygon is closed (first and last vertex repeated) per the GeoJSON
285
+ * spec; without that, MapLibre throws on style validation.
286
+ */
287
+ declare function withinBboxFilter(bbox: BBox): unknown[];
288
+ /**
289
+ * Build a GeoJSON Polygon for the world MINUS a given bbox — i.e. an
290
+ * outer ring covering the whole world with a single hole punched at
291
+ * the bbox. Used by the frontend as the source for a translucent grey
292
+ * overlay so the user sees clearly where the chart is "locked."
293
+ *
294
+ * Outer ring uses the standard antimeridian-safe world extent
295
+ * (lng [-180, 180], lat [-85, 85] — Web Mercator's safe range).
296
+ */
297
+ declare function worldMinusBboxPolygon(bbox: BBox): GeoJSON.Polygon;
298
+
110
299
  interface GeoJsonGeometry {
111
300
  type: string;
112
301
  coordinates: unknown;
@@ -266,7 +455,7 @@ interface QueryResponse extends FeatureCollection<QueryFeatureProperties> {
266
455
  * `Harbor` (<1:22k). A closed set: CHRT assigns these itself rather than taking them
267
456
  * from the issuing office.
268
457
  */
269
- type ScaleBand$1 = "Overview" | "General" | "Coastal" | "Approach" | "Harbor";
458
+ type ScaleBand = "Overview" | "General" | "Coastal" | "Approach" | "Harbor";
270
459
  interface CellsOptions {
271
460
  /**
272
461
  * Filter by chart-source **id**, e.g. `"NOAA_ENC"` — not the agency name
@@ -277,7 +466,7 @@ interface CellsOptions {
277
466
  /** Filter by status, e.g. "CURRENT". Case-insensitive — the server upper-cases it. */
278
467
  status?: string;
279
468
  /** Filter by scale band. Matched exactly, and **case-sensitively** — `"harbor"` matches nothing. */
280
- scaleBand?: ScaleBand$1;
469
+ scaleBand?: ScaleBand;
281
470
  /**
282
471
  * Only cells whose bounds intersect this box, as [west, south, east, north]
283
472
  * in decimal degrees. Intersection, not containment — a cell that merely
@@ -405,7 +594,7 @@ interface CoverageRouteOptions {
405
594
  * bands are words, not band numbers, so `"5"` matches nothing and returns an empty
406
595
  * `cells` list rather than an error.
407
596
  */
408
- scaleBand?: ScaleBand$1;
597
+ scaleBand?: ScaleBand;
409
598
  limit?: number;
410
599
  /** AbortSignal to cancel the request */
411
600
  signal?: AbortSignal;
@@ -628,6 +817,20 @@ interface OfflineFetchStyleOptions {
628
817
  * MLN Native). Use `"file"` for desktop (Win/macOS/Linux).
629
818
  */
630
819
  protocol?: "mbtiles" | "file";
820
+ /**
821
+ * Symbol-artwork family: `"s52"` (default), `"s101"`, `"chrt"`, or
822
+ * `"raster"` — same lever as the online Style API's `?symbols=`. Offline
823
+ * clients load the matching image set from the chart-assets pack
824
+ * (`GET /v1/offline/assets`), same as online clients do from the SDK.
825
+ */
826
+ symbols?: ChrtSymbolSet;
827
+ /**
828
+ * Render mode: `"continuous"` (default) or `"chart:{band}"` — same lever
829
+ * as the online Style API's `?mode=`, so an app's chart-mode switch works
830
+ * identically offshore. The style's `metadata["chrt:render-mode"]` echoes
831
+ * the resolved mode.
832
+ */
833
+ mode?: ChrtStyleMode;
631
834
  /** AbortSignal to cancel the request. */
632
835
  signal?: AbortSignal;
633
836
  }
@@ -641,6 +844,10 @@ interface OfflineStyleResponse {
641
844
  region: string;
642
845
  palette: "DAY" | "DUSK" | "NIGHT";
643
846
  protocol: "mbtiles" | "file";
847
+ /** Resolved symbol-artwork family (lowercased), echoed from `?symbols=`. */
848
+ symbols?: ChrtSymbolSet;
849
+ /** Resolved render mode (canonical casing), echoed from `?mode=`. */
850
+ mode?: ChrtStyleMode;
644
851
  build_id: string;
645
852
  build_tag: string;
646
853
  /** Full MapLibre style JSON. Pass directly to `Map({ style: ... })`. */
@@ -810,6 +1017,14 @@ interface StyleUrlOptions {
810
1017
  * raster charts are scans of). Load the matching images client-side
811
1018
  * from `@chrtco/sdk/atlas/browser`. */
812
1019
  symbols?: ChrtSymbolSet;
1020
+ /** Render mode: `"continuous"` (default — free zoom, per-band zoom
1021
+ * windows) or `"chart:{band}"` to lock the portrayal to one scale band
1022
+ * with cascade fallback to coarser bands where that band has no cells —
1023
+ * the paper-chart semantics of the app's Map Mode toggle. Bands:
1024
+ * `Overview | General | Coastal | Approach | Harbor` (see
1025
+ * `CHRT_STYLE_MODES`). The served document echoes the resolved mode in
1026
+ * `metadata["chrt:render-mode"]` and the `X-Style-Mode` header. */
1027
+ mode?: ChrtStyleMode;
813
1028
  }
814
1029
  interface FetchStyleOptions extends StyleUrlOptions {
815
1030
  signal?: AbortSignal;
@@ -2410,163 +2625,6 @@ declare function buildLayersForClass(objectClass: string, ct: ColorTableName, so
2410
2625
  */
2411
2626
  declare function getOrderedClasses(): string[];
2412
2627
 
2413
- /**
2414
- * Strict scale-band priority for continuous-mode rendering.
2415
- *
2416
- * Each ENC feature in the served PMTiles carries `_cell_id` (added at
2417
- * baremetal NDJSON export time). NOAA's cell-id format encodes the source
2418
- * scale band as the third character: `US5MA85M` is band 5 (Harbor).
2419
- *
2420
- * MapLibre layer filters can't read `["zoom"]`, so to enforce "only one
2421
- * band renders at any given zoom" we emit one layer copy per band, each
2422
- * with a static band filter and per-band `minzoom`/`maxzoom`. The ranges
2423
- * below are non-overlapping (`minzoom` inclusive, `maxzoom` exclusive),
2424
- * so exactly one band is visible at any continuous zoom.
2425
- *
2426
- * Long-term cleanup: baremetal should emit a normalized `_band: 1..6`
2427
- * integer in NDJSON so this works for non-NOAA chart sources too. The
2428
- * `["slice", _cell_id, 2, 3]` extraction here is NOAA-specific.
2429
- */
2430
- /**
2431
- * UI-facing scale-band names (matches the app's `ScaleBand` type in
2432
- * `src/lib/map/constants.ts`). Defined locally so the SDK doesn't have
2433
- * to depend on app code.
2434
- */
2435
- type ScaleBand = "Overview" | "General" | "Coastal" | "Approach" | "Harbor";
2436
- /** S-57 source bands. NOAA cell ids use these as the third character. */
2437
- type BandCode = "1" | "2" | "3" | "4" | "5" | "6";
2438
- interface BandPriorityRange {
2439
- /** MapLibre `minzoom` (inclusive). */
2440
- minzoom: number;
2441
- /** MapLibre `maxzoom` (exclusive). `undefined` means no upper cap. */
2442
- maxzoom?: number;
2443
- }
2444
- /**
2445
- * Per-band minzoom thresholds for continuous mode. Bands cascade as
2446
- * fallbacks for one another — every band whose `minzoom` is satisfied
2447
- * may render. The deeper-numbered band's features render preferentially
2448
- * (drawn last in the z-stack, so its opaque fills cover lower bands),
2449
- * and lower bands fill in where the higher band has no coverage.
2450
- *
2451
- * Cross-band visual overlap (doubled coastlines, duplicate labels) is
2452
- * suppressed by the `_dominated_by_min` per-feature tag added at NDJSON
2453
- * export time (see `tools/chrt-baremetal/server/src/tilebuild/dominanceIndex.ts`):
2454
- * every feature whose centroid sits inside a higher-band cell's M_COVR
2455
- * coverage polygon is tagged with the LOWEST band code (2..6) that
2456
- * dominates it, and `makeBandVariants()` in the style generator emits
2457
- * per-(featureBand, dominatorBand) layer variants gated by `maxzoom =
2458
- * dominatedVariantMaxzoom(D)`. The dominated feature renders until
2459
- * exactly the zoom where its dominator becomes eligible AND has data in
2460
- * the tiles — solving the low-zoom "black silhouettes" bug where a
2461
- * band-3 LNDARE dominated by a band-5 cell was dropped at z7 even
2462
- * though band 5 wasn't yet rendering at z7.
2463
- *
2464
- * Opaque base-area FILLS are exempt from dominance gating entirely (see
2465
- * `DOMINANCE_EXEMPT_FILL_CLASSES` in the style generator): band-major
2466
- * draw order already makes the most-detailed band's opaque fill win at
2467
- * any covered pixel, and the coarser band shows through wherever the
2468
- * detailed band has no data — which is exactly the fallback semantics
2469
- * the cascade wants. The 2026-07 E1/E2 void diagnosis
2470
- * (docs/validation/portrayal-p1-p3-void-diagnosis-2026-07.md) proved
2471
- * per-feature dominance gating on fills structurally unsound: the tag is
2472
- * point-sampled but hides the WHOLE feature, so a Gulf-spanning band-1
2473
- * DEPARE tagged dominated-by-2 left dark voids across every part of the
2474
- * Gulf that band 2 doesn't actually cover.
2475
- *
2476
- * Bands 5 and 6 both start at z11; band 6 (Berthing) is drawn AFTER
2477
- * band 5 (Harbor) so where Berthing exists it visually wins, and
2478
- * Harbor shows through everywhere else.
2479
- *
2480
- * Band 1 starts at z0: the P3 low-zoom contract (charts never disappear
2481
- * from world view down) needs Overview-band data renderable as soon as
2482
- * tiles exist. Builds older than the z0 floor simply have no tiles below
2483
- * z3, so the widened range is a no-op against them.
2484
- */
2485
- declare const BAND_PRIORITY_RANGES: Record<BandCode, BandPriorityRange>;
2486
- /**
2487
- * Order in which band layers are emitted (and thus drawn — earlier = lower
2488
- * in the z-stack). Band 6 last so Berthing draws on top of Harbor.
2489
- */
2490
- declare const BAND_DRAW_ORDER: BandCode[];
2491
- /**
2492
- * UI scale-band names → cell-id band codes that should render in chart mode
2493
- * for that selection. The UI doesn't expose Berthing separately, so picking
2494
- * Harbor includes both `5` and `6` cells.
2495
- *
2496
- * `CHART_BAND_CODES` is the *requested* band set (what the user picked);
2497
- * `CHART_CASCADE_CODES` below adds the coarser bands that fall back where
2498
- * the requested band has no cells.
2499
- */
2500
- declare const CHART_BAND_CODES: Record<ScaleBand, readonly BandCode[]>;
2501
- /**
2502
- * UI scale-band names → ordered cascade of cell-id band codes for chart-mode
2503
- * rendering with graceful coverage fallback. Bands are listed coarsest-first
2504
- * so the style generator can use array order as draw order: lower bands draw
2505
- * earlier in the z-stack, the requested band draws last on top.
2506
- *
2507
- * The chart-mode implementation emits one filtered layer per cascade band per
2508
- * S-57 class. Where the requested band's cells provide data, its opaque fills
2509
- * cover the lower bands underneath; where the requested band has no cells
2510
- * (e.g. Chart→Harbor over Massachusetts Bay offshore), the next-coarser band
2511
- * shows through. Different cells in the same viewport may end up rendering at
2512
- * different effective bands.
2513
- *
2514
- * The per-feature `_dominated_by_min` tag baked at baremetal NDJSON-export
2515
- * time (see `tools/chrt-baremetal/server/src/tilebuild/dominanceIndex.ts`)
2516
- * suppresses lower-band features inside higher-band cell coverage so cell
2517
- * boundaries don't render with doubled coastlines or contours.
2518
- *
2519
- * Lock scope: when chart-Harbor's viewport-lock greyout is active, the
2520
- * `["within"]` bbox filter is applied ONLY to the requested band's variants
2521
- * — cascade fallback bands render across the full viewport. The greyout
2522
- * overlay still cues the lock boundary visually; the user picks Harbor and
2523
- * gets Harbor inside the lock plus dimmed Approach/Coastal context outside.
2524
- *
2525
- * See `.chrt/ux/chart-mode-band-fallback-2026-05-05.md` for the design
2526
- * rationale and the geographies (Boston Harbor, Massachusetts Bay offshore)
2527
- * that motivated this.
2528
- */
2529
- declare const CHART_CASCADE_CODES: Record<ScaleBand, readonly BandCode[]>;
2530
- /**
2531
- * Build a MapLibre filter expression that matches features in the given
2532
- * band code(s) by extracting the band character from `_cell_id`.
2533
- */
2534
- declare function bandFilterExpression(codes: BandCode | readonly BandCode[]): unknown[];
2535
- /**
2536
- * Combine a base filter (may be undefined) with a band filter via `["all"]`.
2537
- * If the base filter is itself an `["all", ...]`, flatten so we don't end
2538
- * up with `["all", ["all", ...], ...]` (which is legal but ugly).
2539
- */
2540
- declare function combineWithBandFilter(baseFilter: unknown[] | undefined, bandFilter: unknown[]): unknown[];
2541
- /**
2542
- * Geographic bounding box used for chart-mode viewport locking.
2543
- * Coordinates in WGS84 lng/lat. `sw` is south-west corner, `ne` is north-east.
2544
- */
2545
- interface BBox {
2546
- sw: [number, number];
2547
- ne: [number, number];
2548
- }
2549
- /**
2550
- * Build a MapLibre `["within"]` filter expression that matches features
2551
- * inside the given bbox. Used by chart mode to spatially limit which
2552
- * features render — when locked to a Harbor area, only features inside
2553
- * the locked bbox draw, even though the tile pyramid covers more.
2554
- *
2555
- * The polygon is closed (first and last vertex repeated) per the GeoJSON
2556
- * spec; without that, MapLibre throws on style validation.
2557
- */
2558
- declare function withinBboxFilter(bbox: BBox): unknown[];
2559
- /**
2560
- * Build a GeoJSON Polygon for the world MINUS a given bbox — i.e. an
2561
- * outer ring covering the whole world with a single hole punched at
2562
- * the bbox. Used by the frontend as the source for a translucent grey
2563
- * overlay so the user sees clearly where the chart is "locked."
2564
- *
2565
- * Outer ring uses the standard antimeridian-safe world extent
2566
- * (lng [-180, 180], lat [-85, 85] — Web Mercator's safe range).
2567
- */
2568
- declare function worldMinusBboxPolygon(bbox: BBox): GeoJSON.Polygon;
2569
-
2570
2628
  /**
2571
2629
  * Dominance fan-out collapse (P8 Phase 3.3 / D4 "axis 1").
2572
2630
  *
@@ -2600,20 +2658,37 @@ declare function worldMinusBboxPolygon(bbox: BBox): GeoJSON.Polygon;
2600
2658
  * `band-collapse.test.ts` re-proves it on every CI run by differentially
2601
2659
  * evaluating legacy-vs-collapsed through MapLibre's real three gates.
2602
2660
  *
2603
- * **Scope.** This is axis 1 ONLY: the per-band layers survive, with identical
2604
- * ids and identical positions, so the band-major draw order is untouched.
2605
- * "Axis 2" (collapsing the per-band copies into one layer per class) was
2606
- * evaluated and **KILLED** — it is unsound for the opaque-fill classes
2661
+ * **Scope.** For FEATURE layers (fills, lines, feature symbols) this is
2662
+ * axis 1 ONLY: the per-band layers survive, with identical ids and identical
2663
+ * positions, so the band-major draw order is untouched. "Axis 2" (collapsing
2664
+ * the per-band copies into one layer per class) was evaluated and **KILLED**
2665
+ * for feature layers — it is unsound for the opaque-fill classes
2607
2666
  * (`DOMINANCE_EXEMPT_FILL_CLASSES` ≡ `OPAQUE_FILL_LAYERS`), where band-major
2608
2667
  * order is the ONLY thing separating the bands, and 15/15 class pairs are
2609
- * provably unsatisfiable with one layer each. Do not reintroduce it.
2668
+ * provably unsatisfiable with one layer each. Do not reintroduce it there.
2610
2669
  * Evidence: `.chrt/perf/2026-07-14-p8-d4-spike.md`.
2611
2670
  *
2612
- * **Load-bearing invariant.** The collapsed layer MUST keep the `-band{B}`
2613
- * id segment: `custom-style.ts` `bandFloor()` parses `/-band(\d)/` to clamp
2614
- * customer minzoom/scamin overrides to the band's data floor, on the live
2615
- * public Style API path. Renaming the id silently unclamps those overrides.
2616
- * Asserted in `band-collapse.test.ts`.
2671
+ * LABEL layers are different (CHRTsail #121 — MapLibre Native iOS silently
2672
+ * collapses its whole render pass above 482 symbol layers, and the served
2673
+ * style carried 575): `makeCollapsedCrossBandLabel` folds a label's ×6 band
2674
+ * fan into ONE layer per class, and `collapsedOrientationGate` folds the
2675
+ * orientation fan's per-dominator variants into its per-cell layers. That is
2676
+ * NOT the killed axis 2, because what the fill argument protects — paint
2677
+ * order as the sole band separator — does not apply to labels: their
2678
+ * load-bearing order is the cross-class P4 priority ladder (each class keeps
2679
+ * exactly one layer in its ladder slot), and within-class placement priority
2680
+ * rides the `rank*1000 + within-key` symbol-sort-key that labels.ts
2681
+ * documents as designed to survive precisely this merge.
2682
+ *
2683
+ * **Load-bearing invariant.** A collapsed per-band FEATURE layer MUST keep
2684
+ * the `-band{B}` id segment: `custom-style.ts` `bandFloor()` parses
2685
+ * `/-band(\d)/` to clamp customer minzoom/scamin overrides to the band's
2686
+ * data floor, on the live public Style API path. Renaming the id silently
2687
+ * unclamps those overrides. Cross-band LABEL layers are the deliberate
2688
+ * exception: they carry every band floor INSIDE the filter (tile-zoom
2689
+ * clauses a customer override cannot touch), which supersedes the clamp —
2690
+ * `bandFloor()` returning 0 for a bare label id is correct, not a gap.
2691
+ * Both halves asserted in `band-collapse.test.ts`.
2617
2692
  */
2618
2693
 
2619
2694
  /**
@@ -2662,7 +2737,7 @@ interface StyleGeneratorOptions extends BuildSourcesOptions {
2662
2737
  * filtered out).
2663
2738
  */
2664
2739
  chartMode?: {
2665
- band: ScaleBand;
2740
+ band: ScaleBand$1;
2666
2741
  lockedBounds?: BBox;
2667
2742
  };
2668
2743
  /**
@@ -3637,4 +3712,4 @@ declare class Chrt {
3637
3712
  warmTiles(region?: string): Promise<void>;
3638
3713
  }
3639
3714
 
3640
- export { ALL_NAV_AID_LAYERS, ALL_WATER_BODY_LAYERS, AREA_QUERY_LAYERS, ATON_EXPRESSION_CLASSES, type AheadCoverageCell, type AheadFeatureProperties, type AheadOptions, type AheadResponse, type AlongRouteFeatureProperties, type AlongRouteOptions, type AlongRouteResponse, type AreaInspectResult, type AtlasStyle, type AttachOptions, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BridgeInspectResult, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, CHRTRASTER_REF_CROPS, CHRTRASTER_SVGS, CHRT_ATON_COLORS, CHRT_GLYPH_NAMES, CHRT_GLYPH_SVGS, CHRT_ICON_EXPRESSIONS, CHRT_ICON_PREFIX, CHRT_SDF_GLYPHS, type CandidateQueryOptions, type CandidatesEventContext, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleOptions, type ChrtStyleTheme, type ChrtSymbolSet, type ClickAnchor, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageRouteCell, type CoverageRouteOptions, type CoverageRouteResponse, type CoverageSource, type CustomStyleConfig, DEFAULT_CLICK_TOLERANCE, DEFAULT_CUSTOM_STYLE_CONFIG, DEFAULT_INSPECT_CLASS_RANK, DEFAULT_LABEL_SETTINGS, DEFAULT_NAV_AID_SETTINGS, DEFAULT_REGION, DEFAULT_SAFETY_CONTOUR_M, DEFAULT_SOUNDING_SETTINGS, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, DEFAULT_WATER_BODY_SETTINGS, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, type FetchStyleOptions, FileSystemAdapter, type FlashPattern, type FlashPhase, type GenerateCustomStyleOptions, type GeneratedCustomStyle, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, INSPECT_CLASS_RANK, INSPECT_RANK_DEVIATIONS, type InspectCandidate, type InspectCandidateSet, type InspectEnrichment, type InspectEventContext, type InspectNotice, type InspectOptions, type InspectResult, type InspectTargetClass, type InspectableFeature, InteractClient, type InteractEntitlement, InteractError, type InteractErrorCode, type InteractHandle, type InteractMapLike, type InteractMapMouseEvent, LABELED_LAYERS, LABEL_BEHAVIOR, LABEL_LAYER_GROUPS, LABEL_PRIORITY, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryFeatureProperties, type QueryOptions, type QueryResponse, RASTER_ATON_CLASSES, RASTER_GLYPH_NAMES, RASTER_ICON_PREFIX, type ResolvedStyle, type ResolvedTheme, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_NEW_SYMBOLS, S101_SOURCE_COMMIT, S101_SYMBOL_GROUPS, S101_SYMBOL_NAMES, S52_ATON_LOOKUPS, S52_ATON_LOOKUPS_PAPER, S52_DRAW_ORDER, S52_LOOKUP, S52_PATTERN_SPRITE_PREFIX, SOUNDING_LAYERS, SYMBOL_FAMILIES, SYMBOL_FAMILY_META, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand$1 as ScaleBand, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, type SymbolFamily, type SymbolFamilyMeta, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, type VesselProfile, WATER_BODY_LAYER_GROUPS, type WaterBodySettings, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyRasterSymbolSet, applyS101SymbolSet, atonIconExpression, bandFilterExpression, buildFlashPattern, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, chrtAtonTintExpression, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, deriveMarinerName, extractNavAidDesignation, featureIdentity, generateCustomStyle, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLabelPriority, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getRasterSvg, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS101Svg, getThemedS52Svg, getTopmarkMeaning, hazardColor, hazardFillExpression, iconExpressionForFamily, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lateralColor, lateralFillExpression, lightColor, lightFillExpression, lightsIconExpression, normalizeTheme, paginate, parseListCodes, parseSymbolFamily, parseWebhook, patternSpriteId, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, rasterAtonIconExpression, resolveCustomTheme, resolveRasterImage, resolveS101Color, resolveStyle, resolveTargetClass, sanitizeCustomStyleConfig, scaleSizeValue, scaminToMinZoom, withinBboxFilter, worldMinusBboxPolygon };
3715
+ export { ALL_NAV_AID_LAYERS, ALL_WATER_BODY_LAYERS, AREA_QUERY_LAYERS, ATON_EXPRESSION_CLASSES, type AheadCoverageCell, type AheadFeatureProperties, type AheadOptions, type AheadResponse, type AlongRouteFeatureProperties, type AlongRouteOptions, type AlongRouteResponse, type AreaInspectResult, type AtlasStyle, type AttachOptions, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BridgeInspectResult, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, CHRTRASTER_REF_CROPS, CHRTRASTER_SVGS, CHRT_ATON_COLORS, CHRT_GLYPH_NAMES, CHRT_GLYPH_SVGS, CHRT_ICON_EXPRESSIONS, CHRT_ICON_PREFIX, CHRT_SDF_GLYPHS, CHRT_STYLE_MODES, type CandidateQueryOptions, type CandidatesEventContext, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleMode, type ChrtStyleOptions, type ChrtStyleTheme, type ChrtSymbolSet, type ClickAnchor, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageRouteCell, type CoverageRouteOptions, type CoverageRouteResponse, type CoverageSource, type CustomStyleConfig, DEFAULT_CLICK_TOLERANCE, DEFAULT_CUSTOM_STYLE_CONFIG, DEFAULT_INSPECT_CLASS_RANK, DEFAULT_LABEL_SETTINGS, DEFAULT_NAV_AID_SETTINGS, DEFAULT_REGION, DEFAULT_SAFETY_CONTOUR_M, DEFAULT_SOUNDING_SETTINGS, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, DEFAULT_WATER_BODY_SETTINGS, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, type FetchStyleOptions, FileSystemAdapter, type FlashPattern, type FlashPhase, type GenerateCustomStyleOptions, type GeneratedCustomStyle, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, INSPECT_CLASS_RANK, INSPECT_RANK_DEVIATIONS, type InspectCandidate, type InspectCandidateSet, type InspectEnrichment, type InspectEventContext, type InspectNotice, type InspectOptions, type InspectResult, type InspectTargetClass, type InspectableFeature, InteractClient, type InteractEntitlement, InteractError, type InteractErrorCode, type InteractHandle, type InteractMapLike, type InteractMapMouseEvent, LABELED_LAYERS, LABEL_BEHAVIOR, LABEL_LAYER_GROUPS, LABEL_PRIORITY, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryFeatureProperties, type QueryOptions, type QueryResponse, RASTER_ATON_CLASSES, RASTER_GLYPH_NAMES, RASTER_ICON_PREFIX, type ResolvedStyle, type ResolvedTheme, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_NEW_SYMBOLS, S101_SOURCE_COMMIT, S101_SYMBOL_GROUPS, S101_SYMBOL_NAMES, S52_ATON_LOOKUPS, S52_ATON_LOOKUPS_PAPER, S52_DRAW_ORDER, S52_LOOKUP, S52_PATTERN_SPRITE_PREFIX, SOUNDING_LAYERS, SYMBOL_FAMILIES, SYMBOL_FAMILY_META, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, type SymbolFamily, type SymbolFamilyMeta, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, type VesselProfile, WATER_BODY_LAYER_GROUPS, type WaterBodySettings, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyRasterSymbolSet, applyS101SymbolSet, atonIconExpression, bandFilterExpression, buildFlashPattern, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, chrtAtonTintExpression, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, deriveMarinerName, extractNavAidDesignation, featureIdentity, generateCustomStyle, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLabelPriority, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getRasterSvg, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS101Svg, getThemedS52Svg, getTopmarkMeaning, hazardColor, hazardFillExpression, iconExpressionForFamily, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lateralColor, lateralFillExpression, lightColor, lightFillExpression, lightsIconExpression, normalizeStyleMode, normalizeTheme, paginate, parseListCodes, parseSymbolFamily, parseWebhook, patternSpriteId, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, rasterAtonIconExpression, resolveCustomTheme, resolveRasterImage, resolveS101Color, resolveStyle, resolveTargetClass, sanitizeCustomStyleConfig, scaleSizeValue, scaminToMinZoom, styleModeBand, withinBboxFilter, worldMinusBboxPolygon };