@chrtco/sdk 0.10.45 → 0.10.47

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
@@ -1,6 +1,6 @@
1
1
  import { F as FileSystemAdapter } from './fs-adapter-D5iwdb1Q.cjs';
2
- import { C as ColorTableName } from './symbol-families-D2Kx20HJ.cjs';
3
- export { S as S52_COLORS, a as S52_DAY, b as S52_DUSK, c as S52_NIGHT, d as SYMBOL_FAMILIES, e as SYMBOL_FAMILY_META, f as SymbolFamily, g as SymbolFamilyMeta, h as getColorTable, i as iconExpressionForFamily, p as parseSymbolFamily, r as resolveColor } from './symbol-families-D2Kx20HJ.cjs';
2
+ import { C as ColorTableName } from './symbol-families-De7sN-p5.cjs';
3
+ export { E as EINK_COLOR_TABLES, a as EINK_MONO_GREYS, S as S52_COLORS, b as S52_DAY, c as S52_DUSK, d as S52_EINK_COLOR, e as S52_EINK_MONO, f as S52_EINK_NIGHT, g as S52_NIGHT, h as SYMBOL_FAMILIES, i as SYMBOL_FAMILY_META, j as SymbolFamily, k as SymbolFamilyMeta, l as getColorTable, m as iconExpressionForFamily, n as isEinkColorTable, o as isEinkGreyTable, p as parseSymbolFamily, r as resolveColor } from './symbol-families-De7sN-p5.cjs';
4
4
  import maplibregl from 'maplibre-gl';
5
5
  import { ExpressionSpecification } from '@maplibre/maplibre-gl-style-spec';
6
6
 
@@ -862,8 +862,10 @@ interface OfflineHeartbeatOptions {
862
862
  * from `GET /v1/offline/styles/{region}`.
863
863
  */
864
864
  interface OfflineFetchStyleOptions {
865
- /** S-52 colour palette. Default: `"DAY"`. */
866
- palette?: "DAY" | "DUSK" | "NIGHT";
865
+ /** Colour palette. Default: `"DAY"`. `"EINK_COLOR"` / `"EINK_MONO"` are
866
+ * the e-paper variants (colour and monochrome e-ink); `"EINK_NIGHT"` is
867
+ * e-ink at night. */
868
+ palette?: "DAY" | "DUSK" | "NIGHT" | "EINK_COLOR" | "EINK_MONO" | "EINK_NIGHT";
867
869
  /**
868
870
  * URL scheme baked into the source. Default: `"mbtiles"` (iOS/Android via
869
871
  * MLN Native). Use `"file"` for desktop (Win/macOS/Linux).
@@ -894,7 +896,7 @@ interface OfflineFetchStyleOptions {
894
896
  */
895
897
  interface OfflineStyleResponse {
896
898
  region: string;
897
- palette: "DAY" | "DUSK" | "NIGHT";
899
+ palette: "DAY" | "DUSK" | "NIGHT" | "EINK_COLOR" | "EINK_MONO" | "EINK_NIGHT";
898
900
  protocol: "mbtiles" | "file";
899
901
  /** Resolved symbol-artwork family (lowercased), echoed from `?symbols=`. */
900
902
  symbols?: ChrtSymbolSet;
@@ -1558,12 +1560,20 @@ declare class TileAuth {
1558
1560
 
1559
1561
  /** Symbol-artwork families served by the Style API. */
1560
1562
  type ChrtSymbolSet = "s52" | "s101" | "chrt" | "raster";
1561
- /** Palette themes served by the Style API (S-52 colour tables). */
1562
- type ChrtStyleTheme = "day" | "dusk" | "night";
1563
+ /**
1564
+ * Palette themes served by the Style API: the S-52 colour tables (`day`,
1565
+ * `dusk`, `night`) and CHRT's e-paper variants — `eink_color` for colour
1566
+ * e-ink (E Ink Kaleido 3) and `eink_mono` for monochrome e-ink panels and
1567
+ * monitors, `eink_night` for any e-ink screen at night (a black page). The
1568
+ * e-ink themes change portrayal as well as colour (heavier
1569
+ * lines, a heavy safety contour, depth steps by pattern in mono).
1570
+ */
1571
+ type ChrtStyleTheme = "day" | "dusk" | "night" | "eink_color" | "eink_mono" | "eink_night";
1563
1572
  interface StyleUrlOptions {
1564
- /** Palette: "day" (default), "dusk", or "night". Omit to get the style's
1565
- * default — for keys with a bound CHRTstyle custom style, that's the
1566
- * style's default palette. */
1573
+ /** Palette: "day" (default), "dusk", "night", "eink_color", "eink_mono"
1574
+ * or "eink_night". Omit to get the style's default — for keys with a bound
1575
+ * CHRTstyle custom style, that's the style's default palette (the e-ink
1576
+ * themes are always available, bound style or not). */
1567
1577
  theme?: ChrtStyleTheme;
1568
1578
  /** Symbol-artwork family: `s52` (default PresLib set), `s101` (official
1569
1579
  * S-101 Portrayal Catalogue symbols), `chrt` (the CHRT house set), or
@@ -2822,1167 +2832,1379 @@ declare class InteractClient {
2822
2832
  }
2823
2833
 
2824
2834
  /**
2825
- * Light arithmetic for the chart palettes: how much light a colour emits, and
2826
- * how to dim a colour without changing its hue.
2827
- *
2828
- * Everything here works in LINEAR light (WCAG 2.x relative luminance, sRGB
2829
- * primaries). That is the quantity a dark-adapted eye responds to, and it is
2830
- * the one `sounding-legibility.test.ts` already measures soundings in. Scaling
2831
- * the sRGB-ENCODED channels instead would dim dark colours far more than
2832
- * bright ones and shift every hue it touched.
2835
+ * Post-consolidation (2026-04): one consolidated PMTiles per region, all
2836
+ * S-57 classes as distinct MVT layers inside. The MapLibre style emits one
2837
+ * vector source (id = region) and N paint layers that reference it via
2838
+ * `source-layer` = S-57 class name.
2833
2839
  *
2834
- * The one derivation the palettes rely on, and the reason this file exists:
2835
- * NIGHT's deeper sounding SNDG1 (#897350) is SNDG2 (#c8aa78) at 43% of its
2836
- * linear light. `labels.ts` builds its NIGHT secondary text the same way, so
2837
- * the two stay one family by construction rather than by two hand-typed hexes
2838
- * that happen to agree.
2839
- */
2840
- /** WCAG 2.x relative luminance of a `#rrggbb` colour, 0 (black) to 1 (white). */
2841
- declare function relativeLuminance(hex: string): number;
2842
- /** WCAG 2.x contrast ratio between two `#rrggbb` colours, 1 to 21. */
2843
- declare function contrastRatio(a: string, b: string): number;
2844
- /**
2845
- * The same hue at `factor` times its linear light. `factor` above 1 can clip a
2846
- * channel, which shifts the hue; nothing in the palettes brightens, so callers
2847
- * pass 0–1.
2840
+ * `buildSources()` returns either the legacy Mapbox MTS shape (preserved for
2841
+ * demos / external style export — see generateStyle comments) or the PMTiles
2842
+ * shape pointing at a single tile URL per region.
2848
2843
  */
2849
- declare function scaleLinearLight(hex: string, factor: number): string;
2844
+ /** Vector source emitted for the legacy Mapbox MTS path. */
2845
+ interface MapboxVectorSource {
2846
+ type: "vector";
2847
+ url: string;
2848
+ }
2850
2849
  /**
2851
- * `hex` dimmed, hue kept, until its luminance is at most `ceiling`. A colour
2852
- * already at or under the ceiling comes back unchanged (lower-cased).
2850
+ * Vector source emitted for the tiles.chrt.co PMTiles path. Uses an explicit
2851
+ * `tiles` array (not a TileJSON `url`) because the tile-worker serves raw MVT
2852
+ * bytes without a TileJSON descriptor.
2853
2853
  */
2854
- declare function capLuminance(hex: string, ceiling: number): string;
2854
+ interface PmtilesVectorSource {
2855
+ type: "vector";
2856
+ tiles: string[];
2857
+ minzoom: number;
2858
+ maxzoom: number;
2859
+ /** The credit a renderer shows for this source (MapLibre's attribution control). */
2860
+ attribution?: string;
2861
+ }
2855
2862
  /**
2856
- * NIGHT's two ink levels, shared by every family's text and marks.
2857
- *
2858
- * `NIGHT_INK` S52_NIGHT.CHBLK — the palette's linework and navigation
2859
- * text. The brightest thing NIGHT draws on purpose.
2860
- * `NIGHT_MUTED_INK` that ink at 43% of its linear light, which is how
2861
- * S52_NIGHT.SNDG1 (the deeper soundings) was derived.
2862
- * Everything that orients rather than navigates is drawn
2863
- * at or under this, and `night-luminance.test.ts` holds
2864
- * every visible NIGHT colour to it unless an exemption
2865
- * names a reason.
2863
+ * Terrarium elevation, served as PNG from `CHRT_RELIEF`. Separate from the
2864
+ * vector shapes because MapLibre needs `type: "raster-dem"` and an explicit
2865
+ * `encoding` — a raster-dem source read as a plain raster renders the encoded
2866
+ * RGB as a garish false-colour image rather than as elevation.
2866
2867
  */
2867
- declare const NIGHT_INK: string;
2868
- declare const NIGHT_MUTED_INK: string;
2869
-
2870
- /** S-52 display categories – controls default visibility */
2871
- type DisplayCategory = "BASE" | "STANDARD" | "OTHER" | "MARINERS" | "META";
2872
- /** Mapbox GL layer type we'll use for this entry */
2873
- type GeomType = "fill" | "line" | "circle" | "symbol";
2874
- /** Nautical groupings (loosely follows Chart No. 1 sections) */
2875
- type NauticalCategory = "Depths" | "Land" | "Navigation Aids" | "Hazards" | "Regulated Areas" | "Infrastructure" | "Meta";
2876
- /** Paint properties – a subset we actually use */
2877
- interface AtlasStyle {
2878
- type: GeomType;
2879
- paint: Record<string, unknown>;
2880
- layout?: Record<string, unknown>;
2881
- /** Nautical symbol ID for symbol-type layers */
2882
- iconImage?: string;
2883
- }
2884
- /** One row in the S-52 lookup table */
2885
- interface LookupEntry {
2886
- /** S-57 object class acronym */
2887
- objectClass: string;
2888
- /** Human-readable name */
2889
- name: string;
2890
- /** Chart No. 1 section */
2891
- category: NauticalCategory;
2892
- /** S-52 display category */
2893
- displayCategory: DisplayCategory;
2894
- /** Default style using S-52 color tokens (resolved later) */
2895
- style: AtlasStyle;
2896
- /** Whether conditional symbology may override the style */
2897
- hasConditional?: boolean;
2898
- /** Attributes that affect styling */
2899
- conditionalAttributes?: string[];
2900
- /** INT1 / Chart No. 1 reference (e.g. "Q130.1") */
2901
- int1Ref?: string;
2902
- /** S-52 viewing group number */
2903
- viewingGroup?: number;
2904
- /** Minimum compilation scale for display (SCAMIN) */
2905
- scamin?: number;
2906
- /**
2907
- * Mark this class as deliberately invisible (label-only or metadata-only)
2908
- * so the layer-coverage audit doesn't flag its geometry-type gaps as bugs.
2909
- *
2910
- * Examples:
2911
- * - SOUNDG: rendered as depth numbers via labels.ts, not as visible dots
2912
- * - LNDRGN: region NAME labels only, no shape
2913
- * - M_NPUB / M_NSYS / M_QUAL / M_SREL / DSID: S-57 metadata classes;
2914
- * present for click-query attributes, never drawn
2915
- *
2916
- * Without this flag, `scripts/audit-layer-coverage.ts` reports the
2917
- * thousands of features in these classes as "gaps" every week even
2918
- * though the invisibility is intentional.
2919
- */
2920
- auditIgnore?: boolean;
2921
- }
2922
- /** Resolved style ready for Mapbox GL (tokens → hex) */
2923
- interface ResolvedStyle {
2924
- type: GeomType;
2925
- paint: Record<string, unknown>;
2926
- layout?: Record<string, unknown>;
2927
- minzoom?: number;
2928
- maxzoom?: number;
2868
+ interface RasterDemSource {
2869
+ type: "raster-dem";
2870
+ tiles: string[];
2871
+ encoding: "terrarium";
2872
+ tileSize: number;
2873
+ minzoom: number;
2874
+ maxzoom: number;
2875
+ /** The credit a renderer shows for this source (MapLibre's attribution control). */
2876
+ attribution?: string;
2929
2877
  }
2930
-
2878
+ type VectorSourceSpec = MapboxVectorSource | PmtilesVectorSource;
2879
+ type AnySourceSpec = VectorSourceSpec | RasterDemSource;
2880
+ /** CHRTland's vector land base. ADR 0023: its own archive, never chart data. */
2881
+ declare const LAND_REGION = "CHRT_LAND";
2882
+ /** CHRTland's terrarium relief. */
2883
+ declare const RELIEF_REGION = "CHRT_RELIEF";
2884
+ /** The world basemap under everything (completing-the-world-map, 2026-08-27). */
2885
+ declare const BASE_REGION = "CHRT_BASE";
2931
2886
  /**
2932
- * S-52 lookup table covering all S-57 object classes found in NOAA ENCs.
2933
- * Colours reference S-52 tokens (resolved at render time).
2934
- * INT1 references per NOAA Chart No. 1 (Ed 13).
2887
+ * CHRT_BASE's zoom range. 11 is the deepest zoom the archive's detail tier
2888
+ * carries honestly — the planet's inland water exists from z9 and is read at
2889
+ * z10 — and the world tier is tiled to the same depth so no layer vanishes
2890
+ * from a tile the detail tier creates. Above 11 the client overzooms, and
2891
+ * overzoomed inland water looks soft DELIBERATELY: it is "roughly where the
2892
+ * lakes are", not a surveyed shoreline. Must equal the builder's MAXZOOM
2893
+ * (tools/chrtbase-build/build.mjs).
2935
2894
  */
2936
- declare const S52_LOOKUP: Record<string, LookupEntry>;
2937
-
2895
+ declare const BASE_MINZOOM = 0;
2896
+ declare const BASE_MAXZOOM = 11;
2938
2897
  /**
2939
- * S-52 draw order: layers listed bottom → top.
2940
- * Layers not in this list are inserted before point features.
2898
+ * The credit the ONLINE CHRT_BASE source carries (owner wording, 2026-09-16).
2899
+ *
2900
+ * OpenStreetMap is named because the online archive's z9–11 detail tier IS
2901
+ * OSM-derived inland water (ODbL, `public/chrtbase-derivation.json`). The
2902
+ * app-embeddable offline bundle is a z0–6 cut that contains no OSM data and
2903
+ * carries `BASE_BUNDLE_ATTRIBUTION` instead (base-layers.ts) — one credit per
2904
+ * artefact, each naming exactly the data it holds.
2941
2905
  */
2942
- declare const S52_DRAW_ORDER: string[];
2943
- /** Get numeric draw priority for a layer (lower = drawn first / below) */
2944
- declare function getDrawPriority(layerName: string): number;
2945
-
2946
- /** S-52 display modes */
2947
- type DisplayMode = "BASE" | "STANDARD" | "ALL";
2948
- /** Check if a layer's display category is visible in the given mode */
2949
- declare function isVisibleInMode(displayCategory: DisplayCategory, mode: DisplayMode): boolean;
2906
+ declare const BASE_ATTRIBUTION = "\u00A9 CHRT \u00B7 Made with Natural Earth \u00B7 \u00A9 OpenStreetMap contributors";
2950
2907
  /**
2951
- * Convert S-52 SCAMIN (scale minimum) to Mapbox GL minzoom.
2908
+ * The credit the CHRT_LAND source carries. CHRTland's land, land use, roads,
2909
+ * places, peaks and marinas are OpenStreetMap via the Protomaps planet, and the
2910
+ * ODbL requires this notice wherever the map is shown (.chrt/legal.md
2911
+ * "Required notices"). Its other inputs — the ENC's charted land, USGS 3DEP
2912
+ * contours, GNIS names — are US federal public domain and owe none. Same
2913
+ * string as the offline land bundle's (offline-worker `LAND_PROVENANCE`).
2952
2914
  */
2953
- declare function scaminToMinZoom(scamin?: number): number | undefined;
2954
- declare function getCollisionSettings(viewingGroup?: number): {
2955
- iconAllowOverlap: boolean;
2956
- iconOptional: boolean;
2957
- symbolSortKey?: number;
2958
- };
2915
+ declare const LAND_ATTRIBUTION = "\u00A9 OpenStreetMap contributors";
2959
2916
  /**
2960
- * Get the resolved Mapbox GL style for a given S-57 layer.
2917
+ * The credit the CHRT_RELIEF source carries. USGS 3DEP elevation, public
2918
+ * domain (17 U.S.C. § 105) — no notice is required, so this is a courtesy that
2919
+ * names the source. There is no OpenStreetMap in it.
2961
2920
  */
2962
- declare function resolveStyle(layerName: string, geomKind?: GeomType, ct?: ColorTableName, opts?: {
2963
- safetyContour?: number;
2964
- }): ResolvedStyle;
2965
- declare function getLookupEntry(layerName: string): LookupEntry;
2966
-
2921
+ declare const RELIEF_ATTRIBUTION = "Elevation: USGS 3D Elevation Program";
2922
+ /** Terrarium tiles are 512 px; the default 256 would halve the apparent relief. */
2923
+ declare const RELIEF_TILE_SIZE = 512;
2967
2924
  /**
2968
- * S-52 Conditional Symbology Procedures.
2969
- * Each function takes feature properties and returns paint overrides (hex colours).
2970
- * All expression builders accept an optional colorTable parameter.
2925
+ * How `CHRT_RELIEF`'s PNGs encode elevation. MapLibre's raster-dem default is
2926
+ * `mapbox`, which decodes terrarium RGB into nonsense heights — so every
2927
+ * surface that describes this source (this registry, the tile-worker's
2928
+ * TileJSON) has to say it.
2971
2929
  */
2930
+ declare const RELIEF_ENCODING = "terrarium";
2931
+ type SourceMode = "mapbox" | "pmtiles";
2972
2932
  /**
2973
- * Default safety contour in metres — the depth below which a feature is
2974
- * "dangerous to my hull" for portrayal purposes.
2933
+ * Query parameter carrying the resolved build id on every tile URL.
2975
2934
  *
2976
- * This is the single source of truth for the *default* used by every
2977
- * safety-contour-driven conditional symbology rule, so the rules can't
2978
- * silently disagree with each other:
2935
+ * ── Why the build has to be IN THE URL ────────────────────────────────────
2979
2936
  *
2980
- * - §SOUNDG02 — soundings shoaler than the contour get the
2981
- * high-contrast shade (see `getLabelConfig` in labels.ts).
2982
- * - §CSYM02 — isolated dangers shoaler than the contour get the
2983
- * DNGCIR01 ring (see `buildLayersForClass` in layer-builder.ts).
2984
- * - §DEPARE04 — the MS/MD shade boundary (owner decision 2026-08-21,
2985
- * replacing a hardcoded 5 m middle cut; see the DEPARE block below
2986
- * for the full IHO correspondence).
2937
+ * The Worker reports the build it served in the `X-Build-Id` RESPONSE HEADER
2938
+ * and sends `Cache-Control: private, max-age=3600`. A header is not part of a
2939
+ * cache key, so every build shares one URL: after a cutover, tiles the client
2940
+ * already fetched keep coming from its own HTTP cache — the old build — while
2941
+ * newly-panned-to tiles come from the new one, for up to the full hour. The
2942
+ * client renders TWO BUILDS AT ONCE.
2987
2943
  *
2988
- * All are overridden by the user's setting (sounding panel slider,
2989
- * `safetyContour` in the style options); this is only the fallback when
2990
- * none is supplied. It matches `custom-style.ts`'s config default.
2991
- */
2992
- declare const DEFAULT_SAFETY_CONTOUR_M = 10;
2993
- declare function depareColor(props: Record<string, unknown>, ct?: ColorTableName, safetyContourM?: number): string;
2994
- /**
2995
- * The colour token of each `case` branch of `depareFillExpression`, in branch
2996
- * order, then the fallback. `depth-area-opaque.ts` names the layers it splits
2997
- * the expression into from this list, so the two read from one place.
2998
- */
2999
- declare const DEPARE_FILL_BRANCH_TOKENS: readonly ["DEPIT", "DEPVS", "DEPMS", "DEPMD"];
3000
- declare const DEPARE_FILL_FALLBACK_TOKEN = "DEPDW";
3001
- declare function depareFillExpression(ct?: ColorTableName, safetyContourM?: number): unknown[];
3002
- declare function lightColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3003
- declare function lightFillExpression(ct?: ColorTableName): unknown[];
3004
- declare function lateralColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3005
- declare function lateralFillExpression(ct?: ColorTableName): unknown[];
3006
- declare function hazardColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3007
- declare function hazardFillExpression(ct?: ColorTableName): unknown[];
3008
- declare function daymarkColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3009
-
3010
- /**
3011
- * Label configurations for S-57 layers.
3012
- * Defines which layers get text labels and how to extract label text.
3013
- */
3014
-
3015
- /**
3016
- * Feature-class hierarchy for SEAARE labels. Returns a tier number 1-5 per
3017
- * feature:
2944
+ * On a nautical chart that is not a cosmetic seam. Measured live 2026-08-25,
2945
+ * cutting `CHRT_LAND` from `463b265e` to `dbb69074` (a build that deliberately
2946
+ * changes land extent): adjacent tiles disagreed about whether land was there,
2947
+ * at the seam between them. Putting the build in the key gives each build its
2948
+ * own cache namespace — old entries age out untouched, and the next style
2949
+ * build flips the whole viewport at once instead of tile by tile.
3018
2950
  *
3019
- * Exported (with the other tier builders below) for the build-side parity
3020
- * test: CHRTbaremetal replicates these taxonomies as data in
3021
- * tools/chrt-baremetal/server/src/tilebuild/placeNameTiers.ts to stamp
3022
- * `tier` onto PLACENAME index features, and
3023
- * placename-tier-parity.test.ts evaluates the two against shared
3024
- * fixtures. Change a branch here and that test names the drift.
2951
+ * ── Why a QUERY PARAMETER and not a path segment ──────────────────────────
3025
2952
  *
3026
- * 1 — Ocean (OBJNAM contains "<X> Ocean" — CATSEA has no
3027
- * ocean code; the attribute is 1/unset on oceans)
3028
- * 2 — Sea / Gulf (OBJNAM "Gulf of …" / "… Sea")
3029
- * 3 — Bay / Sound / (CATSEA in {5 bay, 12 narrows} OR OBJNAM
3030
- * Strait / Narrows " Bay" / " Sound" / " Strait")
3031
- * 4 — Anchorage detail (OBJNAM contains "Anchorage Area")
3032
- * 5 — Relief + default (CATSEA relief codes — authoritative — or
3033
- * relief-term names, or anything uncategorised;
3034
- * suppressed below z9 by the size curve)
2953
+ * A path segment would be the tidier-looking URL and it is the wrong trade
2954
+ * here, for two reasons that are both about other people's code:
3035
2955
  *
3036
- * Order matters: tier-4/5 demotions (anchorages, relief) run BEFORE the
3037
- * promotion shims so "Bear Seamount" (contains " Sea") and "…Anchorage
3038
- * Area" compound names can't bubble up into headline tiers.
3039
- * `coalesce(CATSEA, catsea)` handles post-tippecanoe case variation
3040
- * (matches `s52-conditional.ts` precedent).
2956
+ * 1. COMPATIBILITY IS PERMANENT, NOT "one release". iOS and desktop are
2957
+ * prod-only clients that ship on their own cadence. The Worker routes on
2958
+ * `url.pathname` (`TILE_ROUTE_PATH`) and reads only `?build=`, so a URL
2959
+ * carrying this parameter routes BYTE-IDENTICALLY to one without it. A
2960
+ * new path segment would instead need a second route regex kept alive
2961
+ * indefinitely.
2962
+ * 2. FIVE SEPARATE PATTERNS ALREADY MATCH THIS URL, and every one of them
2963
+ * ends `(?:\?.*)?$` — `src/lib/map/tile-token-client.ts` TILE_URL_PATH
2964
+ * and `src/site/chart/sail-tile-token.ts` (which decide whether a tile
2965
+ * gets its Bearer), `packages/sdk/src/tile-auth.ts` TILE_URL_PATH,
2966
+ * `public/chrt-tile-sw.js` TILE_URL_RE (which also reads the region out of
2967
+ * the URL to pick its per-region cache), and
2968
+ * `src/site/offline/pack-protocol.ts` TILE_URL (which rewrites a tile to
2969
+ * a downloaded pack). Their optional PATH segment, by contrast, is
2970
+ * `[a-z][a-z0-9-]*` — letter-first so it cannot be confused with the
2971
+ * numeric `{z}` — and build ids are hex (`463b265e`, `4de67273`). A build
2972
+ * in the path would miss every one of them, and a tile those patterns
2973
+ * miss does not merely go uncached: it goes out with NO BEARER and comes
2974
+ * back 401, which MapLibre records as a permanently errored tile. That is
2975
+ * the exact failure the `.png` drift caused on staging-sail (see
2976
+ * `chrt-tile-sw.js`).
2977
+ *
2978
+ * The query string is also what the caches this fix targets actually key on:
2979
+ * the browser HTTP cache, MapLibre's own tile cache, and the Service Worker's
2980
+ * `new Request(req.url)` entry key all include it.
2981
+ *
2982
+ * `v` rather than `build`: `?build=` is a taken name with a strict meaning on
2983
+ * the Worker (`?build=candidate`, employee-only, 400 on any other value).
3041
2984
  */
3042
- declare function buildSeaAreaTierExpression(): ExpressionSpecification;
2985
+ declare const BUILD_PARAM = "v";
3043
2986
  /**
3044
- * Built-up area tier (BUAARE): major city / town / neighborhood.
3045
- * 1 — Major city (CATBUA=1 urban, or OBJNAM length <= 8)
3046
- * 2 — Town (CATBUA=5 village, or OBJNAM length <= 14)
3047
- * 3 — Neighborhood (longer compound names)
2987
+ * One tile-URL template — the single place a CHRT tile URL is spelled.
3048
2988
  *
3049
- * Tier 1/2 may fire via CATBUA when populated; OBJNAM length is the
3050
- * fallback so unpopulated CATBUA still produces a usable hierarchy.
2989
+ * `buildId` is encoded rather than interpolated raw: MapLibre substitutes
2990
+ * `{z}`/`{x}`/`{y}` by brace-matching the template, so an id containing a
2991
+ * brace would corrupt every tile request. Empty / whitespace-only ids emit no
2992
+ * parameter at all instead of a bare `?v=` that would key its own useless
2993
+ * cache namespace.
3051
2994
  */
3052
- declare function buildBuiltUpAreaTierExpression(): ExpressionSpecification;
2995
+ declare function tileTemplate(base: string, region: string, extension: "mvt" | "png", buildId?: string): string;
2996
+ /** Canonical source id used in `generateStyle()` — matches the region name
2997
+ * of the one consolidated PMTiles served by the tile-worker for this region. */
2998
+ declare const DEFAULT_REGION = "NOAA_ENC";
2999
+ interface BuildSourcesOptions {
3000
+ /** Which tile path to emit. Default: "pmtiles" (the supported path post-Mapbox). */
3001
+ mode?: SourceMode;
3002
+ /** Chart region. Default: "NOAA_ENC". */
3003
+ region?: string;
3004
+ /**
3005
+ * Build id of the CHART region (`region`) — the `build_tag` a
3006
+ * `mint-tile-token` response hands back.
3007
+ *
3008
+ * Emitted as the {@link BUILD_PARAM} query parameter on that region's tile
3009
+ * template. See {@link tileTemplate} for why a build discriminator has to be
3010
+ * in the URL at all.
3011
+ *
3012
+ * ⚠ THIS COVERS THE CHART REGION ONLY. A token is minted for one chart
3013
+ * region and `CHRT_LAND` / `CHRT_RELIEF` ride it, so the JWT's `build_tag`
3014
+ * says nothing about a land cutover — the same blind spot that made the
3015
+ * Worker's edge-cache key wrong until `TILE_CACHE_VERSION` v3
3016
+ * (cloudflare/tile-worker/src/cache-encoding.ts). Pass {@link buildTags} to
3017
+ * discriminate the basemap sources too.
3018
+ */
3019
+ buildTag?: string;
3020
+ /**
3021
+ * Resolved build id per region — `{ NOAA_ENC: "…", CHRT_LAND: "…" }`.
3022
+ *
3023
+ * The complete form of {@link buildTag}: every source this style declares
3024
+ * gets its own discriminator, so a `CHRT_LAND` promotion turns over the land
3025
+ * URLs without touching the chart's (and vice versa). An entry here wins
3026
+ * over `buildTag` for the same region.
3027
+ *
3028
+ * This is the mint response's `build_tags` map verbatim (claim contract
3029
+ * v1.7) — `TileTokenResponse.buildTags` in both `lib/map/tile-token-client.ts`
3030
+ * and the SDK's `tile-auth.ts`. The same map already keys the tile Service
3031
+ * Worker's per-region caches; passing it here puts the same identity in the
3032
+ * URL, so the layer above the Service Worker stops mixing builds too.
3033
+ *
3034
+ * A region absent from the map simply gets no discriminator — the URL it
3035
+ * has always had. That is a deliberate soft edge: an older mint response, or
3036
+ * a region with no servable build, must degrade to today's behaviour rather
3037
+ * than emit a URL pinned to a build nobody resolved.
3038
+ */
3039
+ buildTags?: Readonly<Record<string, string>>;
3040
+ /** Override tile-worker origin (e.g. staging). Default: DEFAULT_TILE_BASE_URL. */
3041
+ tileBaseUrl?: string;
3042
+ /**
3043
+ * Emit the CHRTland vector land base as a second source (ADR 0023). Off by
3044
+ * default: it costs a source, and the region must be granted on the token.
3045
+ */
3046
+ land?: boolean;
3047
+ /**
3048
+ * Emit CHRTland's terrarium relief as a raster-dem source. Separate from
3049
+ * `land` because relief needs a raster-capable worker AND a grant that
3050
+ * `land-regions.ts` still withholds.
3051
+ */
3052
+ relief?: boolean;
3053
+ /**
3054
+ * Emit the CHRT_BASE world basemap as a third vector source — the dark
3055
+ * geography under everything, the tan jurisdiction fill, and the rough
3056
+ * inland water inside countries CHRT carries. Its layers sit UNDER the
3057
+ * no-coverage hatch's companions and everything else; see baseLayers().
3058
+ */
3059
+ base?: boolean;
3060
+ }
3053
3061
  /**
3054
- * Land region tier (LNDRGN): major region vs. local point.
3055
- * 1 — Major region (OBJNAM length <= 12 AND no comma — "Long Island", "Cape Cod")
3056
- * 2 — Local point (else — "Bird Island", "Cottage Park")
3062
+ * Build the "sources" block for a CHRT style.
3057
3063
  *
3058
- * No CATLND analog for region scope, so OBJNAM is the only signal.
3059
- * The comma-absence test is a heuristic: NOAA compound names like
3060
- * "Boston Harbor, MA Castle Island" carry a comma and belong at deeper
3061
- * zoom regardless of length.
3064
+ * - `mode: "mapbox"`: one mapbox:// url (kept for external style export only).
3065
+ * - `mode: "pmtiles"` (default): one vector tile endpoint for the region,
3066
+ * routed by the Worker to the one consolidated PMTiles:
3067
+ * `${tileBaseUrl}/v1/tiles/{region}/{z}/{x}/{y}.mvt`
3068
+ *
3069
+ * Pass `buildTag` / `buildTags` to pin each source's URL to the build it is
3070
+ * serving — without that, one URL is shared by every build and a cutover
3071
+ * leaves the client rendering two of them at once. See {@link BUILD_PARAM}.
3062
3072
  */
3063
- declare function buildLandRegionTierExpression(): ExpressionSpecification;
3073
+ declare function buildSources(opts?: BuildSourcesOptions): Record<string, AnySourceSpec>;
3074
+
3064
3075
  /**
3065
- * Land area tier (LNDARE): major vs. local.
3066
- * 1 — Major (OBJNAM length <= 12)
3067
- * 2 — Local
3076
+ * DEPTH NUMBERS — how big the chart draws its depth figures.
3068
3077
  *
3069
- * No category attribute. OBJNAM length is the only signal.
3078
+ * Asked for from the helm (chip 68, 2026-10-05), by a captain running CHRTsail
3079
+ * desktop on a Raspberry Pi: *"I still need the depth numbers to be bolder
3080
+ * which may be a setting. I just can't sail the boat and adjust all that stuff
3081
+ * at the same time."* A set-and-forget choice, then, not a slider.
3082
+ *
3083
+ * ===========================================================================
3084
+ * WHAT "LARGE" CHANGES, AND WHAT IT IS NOT ALLOWED TO TOUCH
3085
+ * ===========================================================================
3086
+ *
3087
+ * It changes the SIZE of the glyphs (and, where a class was Regular, the
3088
+ * WEIGHT, to the Semibold soundings already use), the halo a little — see
3089
+ * `LARGE` for why less than the glyph — and the sounding collision padding —
3090
+ * see `LARGE_SOUNDING_PADDING_PX` for why that one moves.
3091
+ *
3092
+ * It does not touch, and `depth-label-size.test.ts` holds every one of these
3093
+ * equal to the Standard style in every palette and every symbol family:
3094
+ *
3095
+ * - `text-field` — the number itself, its unit conversion, its rounding.
3096
+ * ENC content is never altered; a bigger "4" is still the cell's "4".
3097
+ * - `text-color` / `text-halo-color` — §SOUNDG02's two shades against the
3098
+ * safety contour, and the palette that draws them (red night included:
3099
+ * it IS the NIGHT chart). A depth number shoaler than the contour stays
3100
+ * the loud one.
3101
+ * - `symbol-sort-key` — shallowest wins a collision, at any size.
3102
+ * - `filter`, `minzoom`, `maxzoom`, layer ids and layer order — so the
3103
+ * resident layer set the desktop builds at boot is the same set, and a
3104
+ * switch is `setLayoutProperty` / `setPaintProperty` on layers already
3105
+ * there. No layer and no draw call is added; what costs more is GPU fill,
3106
+ * because a bigger glyph covers more pixels (about 6–7 % more blended
3107
+ * fragments per pixel on the chart bench — the PR has the numbers).
3108
+ *
3109
+ * ===========================================================================
3110
+ * WHICH LABELS ARE "DEPTH NUMBERS"
3111
+ * ===========================================================================
3112
+ *
3113
+ * Exactly the ones the depth-unit switch converts (`buildLabelLayer` in
3114
+ * `layer-builder.ts`): soundings, contour values, and the depth printed beside
3115
+ * a rock, wreck or obstruction. The unit switch already treats those three as
3116
+ * one claim — its own note says a rock's figure "is the SAME depth claim as a
3117
+ * sounding" — and a size switch that enlarged the soundings around a rock but
3118
+ * left the rock's own depth small would make the hazard the quietest number
3119
+ * on that part of the chart.
3120
+ *
3121
+ * Not the dredged-channel note (`DRGNOTE`): it is a sentence fitted inside
3122
+ * its own channel polygon at a computed size, and scaling it would stop it
3123
+ * fitting. Not the bridge note: a clearance is a height, not a depth.
3124
+ *
3125
+ * ===========================================================================
3126
+ * WHY NOT A BOLD FACE
3127
+ * ===========================================================================
3128
+ *
3129
+ * The chart's glyph sets are Open Sans Regular, Semibold and Italic — the tile
3130
+ * worker's `/v1/glyphs` (`cloudflare/tile-worker/glyphs/GLYPHS.json`) and the
3131
+ * offline chart-assets pack serve those three and nothing else, and a
3132
+ * fontstack no glyph host serves is the failure `labels.ts`'s SOUNDG note
3133
+ * describes (Native lost every sounding). Soundings are already Semibold. So "bolder" here is
3134
+ * size: stroke width scales with the glyph, and at 1x device pixels (a Pi's
3135
+ * panel) a 14 px Semibold numeral is both heavier and crisper than a 10 px one.
3070
3136
  */
3071
- declare function buildLandAreaTierExpression(): ExpressionSpecification;
3137
+
3138
+ /** The two choices, in presentation order. */
3139
+ declare const DEPTH_LABEL_SIZES: readonly ["standard", "large"];
3140
+ type DepthLabelSize = (typeof DEPTH_LABEL_SIZES)[number];
3072
3141
  /**
3073
- * Lake tier (LAKARE): lake vs. reservoir/pond.
3074
- * 1 — Lake (CATLAK=1, or OBJNAM length <= 12)
3075
- * 2 — Reservoir/pond (CATLAK in {2,3}, or longer name)
3142
+ * Standard is the chart as it has always been drawn, and it is what a style
3143
+ * generated without the option gets: byte-identical, so the served style, the
3144
+ * native-render goldens and every client that never asks are unchanged.
3076
3145
  */
3077
- declare function buildLakeTierExpression(): ExpressionSpecification;
3146
+ declare const DEFAULT_DEPTH_LABEL_SIZE: DepthLabelSize;
3147
+ /** Case-insensitive; anything unrecognised is `null`, never a throw. */
3148
+ declare function parseDepthLabelSize(raw: string | null | undefined): DepthLabelSize | null;
3149
+ /** The S-57 classes whose label is a depth figure. See the header. */
3150
+ declare const DEPTH_LABEL_CLASSES: ReadonlySet<string>;
3078
3151
  /**
3079
- * River tier (RIVERS): major vs. local.
3080
- * 1 — Major river (OBJNAM length <= 15 AND no comma)
3081
- * 2 — Local creek (else)
3152
+ * Sounding collision padding at Large, px. Standard is 6.
3153
+ *
3154
+ * ⚠ THIS IS THE ONE THAT DECIDES HOW MANY SOUNDINGS SURVIVE. MapLibre drops a
3155
+ * sounding whose box (glyphs + padding) overlaps one already placed — the
3156
+ * shallowest places first, so what drops is the deeper figure. A 1.4x numeral
3157
+ * inside Standard's 6 px padding lost a quarter of the soundings: bigger
3158
+ * numbers, fewer of them, which is not what was asked for. Trimming the
3159
+ * padding gives the box back.
3160
+ *
3161
+ * Measured 2026-10-05 on the chart bench (`scripts/chart-bench`, Boston, DAY,
3162
+ * 1280x800 at 1x), soundings placed, Standard → Large at this padding:
3163
+ *
3164
+ * z11.5 515 → 539 (+5%) z12.3 434 → 415 (−4%)
3165
+ * z13 374 → 366 (−2%) z14 326 → 311 (−5%)
3166
+ *
3167
+ * and at Standard's 6 px: 391 / 246 at z11.5 / z14 (−24% / −25%). 3 px kept
3168
+ * z14 level but put 17% MORE figures on the coastal view, which reads as
3169
+ * clutter rather than as the same chart drawn bigger.
3082
3170
  */
3083
- declare function buildRiverTierExpression(): ExpressionSpecification;
3171
+ declare const LARGE_SOUNDING_PADDING_PX = 3.5;
3084
3172
  /**
3085
- * Airport tier (AIRARE): major airport vs. small airstrip.
3086
- * 1 — Major (OBJNAM length <= 25 — e.g. "Logan International Airport")
3087
- * 2 — Airstrip
3173
+ * Apply a depth-label size to one generated label layer.
3174
+ *
3175
+ * Standard — and any class that is not a depth figure — returns the SAME
3176
+ * object, untouched. Large returns a copy with only the properties listed in
3177
+ * the header changed.
3088
3178
  */
3089
- declare function buildAirportTierExpression(): ExpressionSpecification;
3090
- interface LabelLayerConfig {
3091
- /** Mapbox source layer type */
3092
- type: "symbol";
3093
- /** Text field expression or string */
3094
- textField: ExpressionSpecification | string;
3095
- /** Text size in pixels */
3096
- textSize: number | ExpressionSpecification;
3097
- /** Text color (hex) or data-driven expression */
3098
- textColor: string | ExpressionSpecification;
3099
- /** Halo color for contrast */
3100
- textHaloColor: string;
3101
- /** Halo width — pixels or zoom-interpolation expression */
3102
- textHaloWidth: number | ExpressionSpecification;
3103
- /** Text offset [x, y] in ems */
3104
- textOffset: [number, number];
3105
- /** Font stack */
3106
- textFont?: string[];
3107
- /** Additional layout properties */
3179
+ declare function applyDepthLabelSize(objectClass: string, layer: StyleLayer, size: DepthLabelSize | undefined): StyleLayer;
3180
+
3181
+ /**
3182
+ * Builds Mapbox GL Style layer specs from S-52 lookup entries.
3183
+ * Wraps the existing atlas resolveStyle() and adds source/source-layer/filter.
3184
+ */
3185
+
3186
+ interface StyleLayer {
3187
+ id: string;
3188
+ type: string;
3189
+ source: string;
3190
+ "source-layer": string;
3191
+ filter?: unknown[];
3192
+ paint: Record<string, unknown>;
3108
3193
  layout?: Record<string, unknown>;
3109
- /** Minimum zoom level for this label */
3110
3194
  minzoom?: number;
3195
+ maxzoom?: number;
3196
+ metadata?: Record<string, unknown>;
3111
3197
  }
3112
3198
  /**
3113
- * Optional palette-and-data-driven options for label config.
3199
+ * Build all Mapbox GL layers for a given S-57 object class.
3200
+ * Returns multiple layers (fill, outline, symbol, label, etc.).
3114
3201
  *
3115
- * Currently used by SOUNDG to pick palette-aware text/halo colors and to
3116
- * drive the S-52 safety-contour two-shade rule. Other classes ignore
3117
- * these options.
3202
+ * Post-consolidation: all S-57 classes live as MVT layers inside one
3203
+ * consolidated PMTiles per region; `source` is always the region name.
3204
+ *
3205
+ * `opts.safetyContour` is the mariner's safety depth in metres — it drives
3206
+ * the §CSYM02 isolated-danger ring on hazard classes. Defaults to
3207
+ * `DEFAULT_SAFETY_CONTOUR_M`, the same default the SOUNDG two-shade rule
3208
+ * uses, so the ring and the sounding colour always agree.
3118
3209
  */
3119
- interface LabelConfigOptions {
3120
- /** S-52 colour table — drives palette-aware text and halo colors. */
3121
- colorTable?: ColorTableName;
3122
- /**
3123
- * Safety contour depth in metres. Soundings shoaler than this render in
3124
- * `SNDG2` (high-contrast); deeper soundings render in `SNDG1` (muted).
3125
- * S-52 §SOUNDG02 conditional symbology.
3126
- */
3210
+ declare function buildLayersForClass(objectClass: string, ct: ColorTableName, sourceId?: string, opts?: {
3127
3211
  safetyContour?: number;
3128
- /**
3129
- * When false, all soundings render in `SNDG2` (single-shade). Default
3130
- * true — emit the safety-contour case expression.
3131
- */
3132
- twoShadeSoundings?: boolean;
3133
- /**
3134
- * When "semibold", SOUNDG labels use Open Sans Semibold for crisper
3135
- * numerals at small sizes. Default "semibold".
3136
- */
3137
- soundingFontWeight?: "regular" | "semibold";
3138
- }
3212
+ }): StyleLayer[];
3139
3213
  /**
3140
- * Two-tier label rendering strategy for the band-priority style.
3141
- *
3142
- * `feature-band-gated` — emit one variant per source band, each gated to
3143
- * its band's zoom window (the existing `makeBandVariants` behaviour). Right
3144
- * for dense feature labels where seeing the same buoy designation or
3145
- * sounding twice across overlapping bands is visual noise.
3146
- *
3147
- * `orientation-cross-band` — emit a SINGLE label layer with no band
3148
- * filter. The per-class `minzoom` is honoured. Right for orientation
3149
- * "where am I" labels (sea-area names, land regions, cities) — these
3150
- * must persist as the user zooms in, even if the broader-scope cell
3151
- * carrying the OBJNAM is from a coarser band than the one rendering
3152
- * features at the current zoom. Collision arbitration via
3153
- * `ORIENTATION_BAND_SORT_KEY` gives the broader-scope (lower-band) copy
3154
- * priority where the same OBJNAM exists in multiple bands.
3214
+ * Get the full ordered list of object classes for style generation.
3215
+ * Uses S52_DRAW_ORDER as base, then adds any missing classes from the
3216
+ * S-52 lookup table so a newly-ingested class automatically gets styled
3217
+ * without touching this file.
3155
3218
  */
3156
- type LabelBehavior = "feature-band-gated" | "orientation-cross-band";
3157
- declare const LABEL_BEHAVIOR: Record<string, LabelBehavior>;
3158
- /** Returns the rendering strategy for an S-57 class. Unknown classes default to band-gated. */
3159
- declare function getLabelBehavior(layerName: string): LabelBehavior;
3219
+ declare function getOrderedClasses(): string[];
3220
+
3160
3221
  /**
3161
- * Class-based label priority ladder — THE single collision-priority model
3162
- * for feature labels (portrayal-excellence P4). Converges the previous
3163
- * per-mechanism patches (SAFETY_CRITICAL_LABELS allow-overlap, the
3164
- * band-5/6-only Harbor collision discipline) into one explicit ranking.
3165
- *
3166
- * Rank semantics: LOWER rank = higher priority. The rank drives two
3167
- * mechanisms that must stay in agreement:
3222
+ * Dominance fan-out collapse (P8 Phase 3.3 / D4 "axis 1").
3168
3223
  *
3169
- * 1. `symbol-sort-key` — every feature label carries
3170
- * `rank*1000 + within-class-key`, so if label layers are ever merged
3171
- * (the P8 style fan-out reduction), placement priority survives the
3172
- * merge unchanged.
3173
- * 2. Label LAYER ORDER — MapLibre places symbols topmost-layer-first, so
3174
- * `style-generator.ts` emits feature-label layers lowest-priority
3175
- * first: safety-critical classes end up topmost and claim collision
3176
- * space before anything else. (`symbol-sort-key` alone cannot do
3177
- * this — it only orders placement WITHIN a layer.)
3224
+ * `makeBandVariants()` historically emitted, per (class, band): one
3225
+ * "not-dominated" layer plus up to five `-domBy{D}` copies that differ ONLY
3226
+ * in their dominance filter and a `maxzoom` of `dominatedVariantMaxzoom(D)`.
3227
+ * That fan existed because MapLibre filters could not read `["zoom"]`. They
3228
+ * can now (style-spec 24.8.5; buckets evaluate at the tile's `overscaledZ`),
3229
+ * so the whole fan folds into ONE layer per (class, band) whose filter
3230
+ * carries the zoom gate.
3178
3231
  *
3179
- * The ladder (safety-critical > nav-aids > depth field > structures >
3180
- * ambient notes):
3232
+ * **Construction principle — read before changing anything here.** The
3233
+ * collapsed filter is the literal `["any"]` UNION of the clauses the fan
3234
+ * already encoded: every variant the legacy emitter would have produced
3235
+ * contributes exactly one `["all", <its dominance filter>, ["<", ["zoom"],
3236
+ * <its maxzoom>]]` clause, and the dead-variant skip is mirrored too.
3237
+ * Equivalence is therefore *structural*, not re-derived — which matters,
3238
+ * because the two "obvious" hand-written forms are both wrong:
3181
3239
  *
3182
- * 100 — Safety-critical: light characteristics (LIGHTS), hazard depth
3183
- * callouts (UWTROC/OBSTRN/WRECKS VALSOU), overhead clearances
3184
- * (CBLOHD/PIPOHD VERCLR). Irreducible mariner-safety text.
3185
- * 200 — Nav-aid designations: buoy / beacon `R "4"` labels.
3186
- * 250 — Auxiliary nav-aid info: fog signal type (FOGSIG).
3187
- * 300 — Depth field: soundings, contour values. Individually droppable —
3188
- * a hidden sounding has visible neighbors.
3189
- * 400 — Structures: bridges, landmarks, piers, moorings, buildings.
3190
- * 500 — Ambient annotation: seabed nature, magnetic variation, kelp.
3191
- * 900 — Orientation names (SEAARE etc.) — not part of this ladder;
3192
- * they keep their settled watermark model (cell-band fan +
3193
- * dominance composition + `orientationLabelLayout`) and always
3194
- * paint underneath feature labels.
3240
+ * 1. A `["match", ["get","_dominated_by_min"], 2,5, 3,7, 4,9, 5,11, 6,11, 24]`
3241
+ * threshold (the form the design doc proposed) uses band 6's *eligibility*
3242
+ * zoom 11. The real ceiling is `dominatedVariantMaxzoom("6") = 13` — band
3243
+ * 6's DATA floor. Using 11 reopens the z11–z12 void window that
3244
+ * `band-priority.ts` documents closing.
3245
+ * 2. That same form silently drops the legacy `_dominated: true` boolean,
3246
+ * which `dominatedByMinFilter("6")` still honours. A legacy-tagged feature
3247
+ * has no `_dominated_by_min`, so `match` falls to its default and the
3248
+ * feature renders FOREVER instead of dropping at z13.
3195
3249
  *
3196
- * Safety guardrail: a lower rung never blocks a higher rung — placement
3197
- * order guarantees the safety-critical label got the space first. Density
3198
- * relief comes from dropping the LOWEST rung in a pile-up, never the top.
3199
- */
3200
- declare const LABEL_PRIORITY: Record<string, number>;
3201
- /**
3202
- * Resolve the priority rank for an S-57 class's label. Orientation classes
3203
- * report 900 regardless of ladder membership (PRDARE etc.) so the
3204
- * layer-order sort in style-generator.ts can treat priority uniformly.
3205
- */
3206
- declare function getLabelPriority(layerName: string): number;
3207
- /**
3208
- * Compose a `symbol-sort-key` from a class rank and an optional
3209
- * within-class key expression. The within-class key is clamped to
3210
- * [0, 999] so it can never bleed into a neighboring rank's band.
3211
- */
3212
- declare function labelSortKey(rank: number, within?: unknown): ExpressionSpecification | number;
3213
- /**
3214
- * Extract the trailing designation token from a nav-aid OBJNAM.
3250
+ * Deriving the gate from the emitter's own clauses avoids both by construction.
3251
+ * `band-collapse.test.ts` re-proves it on every CI run by differentially
3252
+ * evaluating legacy-vs-collapsed through MapLibre's real three gates.
3215
3253
  *
3216
- * NOAA names channel aids "<waterway> <type> Buoy|Beacon <designation>"
3217
- * where the designation is 1-4 alphanumerics: "4", "18A", "B", "BG".
3218
- * Returns null when the name has no designation (trailing token is a
3219
- * stopword type-name, or longer than 4 chars).
3254
+ * **Scope.** For FEATURE layers (fills, lines, feature symbols) this is
3255
+ * axis 1 ONLY: the per-band layers survive, with identical ids and identical
3256
+ * positions, so the band-major draw order is untouched. "Axis 2" (collapsing
3257
+ * the per-band copies into one layer per class) was evaluated and **KILLED**
3258
+ * for feature layers — it is unsound for the opaque-fill classes
3259
+ * (`DOMINANCE_EXEMPT_FILL_CLASSES` ≡ `OPAQUE_FILL_LAYERS`), where band-major
3260
+ * order is the ONLY thing separating the bands, and 15/15 class pairs are
3261
+ * provably unsatisfiable with one layer each. Do not reintroduce it there.
3262
+ * Evidence: `.chrt/perf/2026-07-14-p8-d4-spike.md`.
3220
3263
  *
3221
- * This is the TS twin of `navAidDesignationTextExpression` — the GeoJSON
3222
- * preprocessing path uses this function, the PMTiles path uses the GL
3223
- * expression. Keep the two in agreement (see label-priority.test.ts).
3224
- */
3225
- declare function extractNavAidDesignation(objnam: string): string | null;
3226
- /** Get label config for a given S-57 layer, or null if no labels */
3227
- declare function getLabelConfig(layerName: string, opts?: LabelConfigOptions): LabelLayerConfig | null;
3228
- /**
3229
- * Build a Mapbox symbol layer spec from a label config.
3230
- */
3231
- declare function buildLabelLayer(layerId: string, sourceId: string, config: LabelLayerConfig, visible: boolean, overrides?: {
3232
- textScale?: number;
3233
- minzoom?: number | null;
3234
- allowOverlap?: boolean;
3235
- }): maplibregl.LayerSpecification;
3236
- /**
3237
- * Pre-process SOUNDG GeoJSON to extract Z coordinates as `depth` properties.
3238
- * Soundings are MultiPoint with Z values in geometry.coordinates[][2].
3239
- * This explodes MultiPoints into individual Points with a `depth` property.
3240
- */
3241
- declare function preprocessSoundings(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3242
- /**
3243
- * Pre-process LIGHTS GeoJSON to derive compact light characteristic labels.
3244
- * Adds `_lightLabel` property (e.g., "Fl(2) WR 4s 5M").
3264
+ * LABEL layers are different (CHRTsail #121 — MapLibre Native iOS silently
3265
+ * collapses its whole render pass above 482 symbol layers, and the served
3266
+ * style carried 575): `makeCollapsedCrossBandLabel` folds a label's ×6 band
3267
+ * fan into ONE layer per class, and `collapsedOrientationGate` folds the
3268
+ * orientation fan's per-dominator variants into its per-cell layers. That is
3269
+ * NOT the killed axis 2, because what the fill argument protects — paint
3270
+ * order as the sole band separator — does not apply to labels: their
3271
+ * load-bearing order is the cross-class P4 priority ladder (each class keeps
3272
+ * exactly one layer in its ladder slot), and within-class placement priority
3273
+ * rides the `rank*1000 + within-key` symbol-sort-key that labels.ts
3274
+ * documents as designed to survive precisely this merge.
3245
3275
  *
3246
- * KEEP IN STEP WITH `buildLightLabelExpression()` in layer-builder.ts — that
3247
- * is the same label built as a GL expression for the tile path, which has no
3248
- * JS preprocessing step. `light-label-parity.test.ts` asserts the two agree
3249
- * on real tile features; if you change one, change both.
3250
- */
3251
- declare function preprocessLights(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3252
- /** List of layers that have labels */
3253
- declare const LABELED_LAYERS: Set<string>;
3254
- /**
3255
- * Pre-process buoy/beacon GeoJSON to derive concise mariner-style labels.
3256
- * Adds `_navAidLabel` property (e.g., 'R "8"', 'G "5"').
3257
- */
3258
- declare function preprocessNavAids(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3259
- /** Check if a layer name needs nav aid preprocessing */
3260
- declare function isNavAidLayer(layerName: string): boolean;
3261
- /**
3262
- * Pre-process water body polygon GeoJSON into centroid points for label placement.
3263
- * Computes a simple average-coordinate centroid for each polygon feature that has OBJNAM.
3264
- */
3265
- declare function preprocessWaterBodies(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3266
- /** MVT layer the build-side place-name index emits. */
3267
- declare const PLACENAME_SOURCE_LAYER = "PLACENAME";
3268
- /** Orientation classes whose names render italic (water voice). */
3269
- declare const PLACENAME_WATER_CLASSES: readonly string[];
3270
- /** Orientation classes whose names render upright (land voice). */
3271
- declare const PLACENAME_LAND_CLASSES: readonly string[];
3272
- /**
3273
- * The 2 PLACENAME symbol layers. Layout contract (design §2.2): collision
3274
- * ON (`allow-overlap: false`, `ignore-placement: false`,
3275
- * `optional: true`), padding 4, `text-variable-anchor`, and
3276
- * `symbol-sort-key` straight from the build-side `rank` — MapLibre places
3277
- * LOWER sort keys first, and rank is minted lower-is-more-important
3278
- * (tier major, bbox area minor), so when space runs out the biggest names
3279
- * survive. That ranking is the per-zoom density governor P4 asked for.
3276
+ * **Load-bearing invariant.** A collapsed per-band FEATURE layer MUST keep
3277
+ * the `-band{B}` id segment: `custom-style.ts` `bandFloor()` parses
3278
+ * `/-band(\d)/` to clamp customer minzoom/scamin overrides to the band's
3279
+ * data floor, on the live public Style API path. Renaming the id silently
3280
+ * unclamps those overrides. Cross-band LABEL layers are the deliberate
3281
+ * exception: they carry every band floor INSIDE the filter (tile-zoom
3282
+ * clauses a customer override cannot touch), which supersedes the clamp —
3283
+ * `bandFloor()` returning 0 for a bare label id is correct, not a gap.
3284
+ * Both halves asserted in `band-collapse.test.ts`.
3280
3285
  */
3281
- declare function buildPlaceNameLayers(sourceId: string, ct?: ColorTableName): maplibregl.LayerSpecification[];
3282
3286
 
3283
3287
  /**
3284
- * Spatial grid-based feature decimation for performance at low zoom levels.
3285
- * Thins dense GeoJSON feature sets by keeping only one representative
3286
- * feature per grid cell per layer.
3287
- */
3288
- interface DecimationOptions {
3289
- /** Grid resolution (cells per axis). Default 80. */
3290
- gridSize?: number;
3291
- /** Zoom level below which decimation kicks in. Default 12. */
3292
- zoomThreshold?: number;
3293
- }
3294
- /**
3295
- * Zoom-adaptive grid resolution for decimation.
3296
- */
3297
- declare function getGridSizeForZoom(zoom: number): number;
3298
- /**
3299
- * Decimate a GeoJSON FeatureCollection using spatial grid thinning.
3300
- * Only runs when zoom < zoomThreshold. Returns the original FC otherwise.
3301
- *
3302
- * @param fc - Input FeatureCollection
3303
- * @param layerName - S-57 layer name (affects priority logic)
3304
- * @param currentZoom - Current map zoom level
3305
- * @param bounds - Current viewport bounds
3306
- * @param opts - Decimation options
3288
+ * Which emitter the style generator uses for the dominance fan.
3289
+ * `"legacy"` exists so CI can differentially prove the two equivalent (it is
3290
+ * the oracle in `band-collapse.test.ts`) and as a one-line rollback. It is an
3291
+ * internal knob — deliberately NOT on the exported `StyleGeneratorOptions`,
3292
+ * so it cannot reach `@chrtco/sdk` consumers via `ChrtStyleOptions`.
3307
3293
  */
3308
- declare function decimateFeatures(fc: GeoJSON.FeatureCollection, layerName: string, currentZoom: number, bounds: {
3309
- west: number;
3310
- south: number;
3311
- east: number;
3312
- north: number;
3313
- }, opts?: DecimationOptions): GeoJSON.FeatureCollection;
3294
+ type BandFanMode = "collapsed" | "legacy";
3295
+
3314
3296
  /**
3315
- * Get the max number of cells to load based on zoom level.
3297
+ * Generates a complete Mapbox GL Style Specification v8 JSON
3298
+ * from the S-52 atlas definitions.
3316
3299
  */
3317
- declare function getMaxCellsForZoom(zoom: number): number;
3318
3300
 
3319
- /**
3320
- * IHO S-52 / NOAA Chart No. 1 / INT1 nautical symbol SVGs.
3321
- *
3322
- * Complete symbology suite following:
3323
- * - IHO S-52 Presentation Library (Ed 4.0)
3324
- * - IALA Maritime Buoyage System R1001 (Region B for US waters)
3325
- * - NOAA Chart No. 1 (Ed 13) / INT1
3326
- *
3327
- * Buoy shape terminology (US / Chart No. 1):
3328
- * - CAN (Q20): Flat-top cylindrical buoy — Port mark in IALA-B (red)
3329
- * - NUN (Q21): Conical/pointed-top buoy — Starboard mark in IALA-B (green)
3330
- * - PILLAR: Tall narrow buoy — used for cardinal, isolated danger, etc.
3331
- * - SPAR: Tall thin pole buoy
3332
- * - SPHERICAL: Round buoy — safe water marks
3333
- * - BARREL: Barrel/tun shaped buoy
3334
- * - SUPER: Large navigation buoy / LANBY
3335
- *
3336
- * Cardinal mark topmark orientations (IALA):
3337
- * - North: both cones point UP ▲▲
3338
- * - South: both cones point DOWN ▼▼
3339
- * - East: cones base-to-base (diamond) ◆
3340
- * - West: cones point-to-point (hourglass) ⊗
3341
- *
3342
- * Each symbol is a compact SVG at 32×32 for Mapbox GL `map.addImage()`.
3343
- */
3344
- /** Color used in the SVG with its S-52 token name, hex, and usage */
3345
- interface SymbolColor {
3346
- token: string;
3347
- hex: string;
3348
- usage: string;
3301
+ interface MapboxStyle {
3302
+ version: 8;
3303
+ name: string;
3304
+ metadata: Record<string, unknown>;
3305
+ sources: Record<string, unknown>;
3306
+ sprite?: string;
3307
+ glyphs: string;
3308
+ layers: StyleLayer[];
3349
3309
  }
3350
- /** Line style used in the SVG */
3351
- interface SymbolLineStyle {
3352
- element: string;
3353
- width: number;
3354
- pattern?: string;
3355
- color: string;
3310
+ interface StyleGeneratorOptions extends BuildSourcesOptions {
3311
+ /**
3312
+ * When set, emit a "paper chart" style centered on the selected scale
3313
+ * band, with graceful coverage fallback: the requested band's layers
3314
+ * draw on top where its cells have data; coarser bands (driven by
3315
+ * `CHART_CASCADE_CODES`) fill gaps where the requested band has no
3316
+ * cells. Per-feature SCAMIN minzooms are preserved on every cascade
3317
+ * variant; `maxzoom` is dropped so the user can zoom in indefinitely.
3318
+ *
3319
+ * When omitted (default), continuous mode emits one layer copy per
3320
+ * source band with strict per-band zoom windows so exactly one band
3321
+ * renders at any given continuous zoom.
3322
+ *
3323
+ * `lockedBounds` (optional) restricts chart-mode rendering to a
3324
+ * specific bbox via a `["within"]` filter — applied ONLY to the
3325
+ * requested band's variants. Cascade fallback bands render across the
3326
+ * full viewport so a chart-Harbor user panning offshore still sees
3327
+ * Approach/Coastal data. The frontend pairs this with a translucent
3328
+ * grey overlay outside the bbox so the lock boundary is still visible
3329
+ * to the user (the fallback data is dimmed by the overlay but not
3330
+ * filtered out).
3331
+ */
3332
+ chartMode?: {
3333
+ band: ScaleBand$1;
3334
+ lockedBounds?: BBox;
3335
+ };
3336
+ /**
3337
+ * Display unit for depth labels (SOUNDG soundings, DEPCNT contour
3338
+ * values). Source data is always meters (S-57 / VALSOU / VALDCO);
3339
+ * "feet" wraps the text-field expression with a unit conversion +
3340
+ * round so the rendered numbers match. Default "meters".
3341
+ */
3342
+ depthUnits?: "meters" | "feet";
3343
+ /**
3344
+ * Which symbol artwork the style references. "s52" (default) uses the
3345
+ * PresLib-derived S-52 set; "s101" rewrites icon references to the
3346
+ * official S-101 Portrayal Catalogue symbols (s101: image namespace)
3347
+ * where a counterpart exists — see s101-style.ts. "chrt" applies the
3348
+ * CHRT house ATON/LIGHTS glyphs with colour-true tinting — see
3349
+ * chrt-style.ts. Selection logic differences are documented per module.
3350
+ */
3351
+ symbolSet?: "s52" | "s101" | "chrt" | "raster";
3352
+ /**
3353
+ * The mariner's safety contour in metres — the depth below which a
3354
+ * feature is "dangerous to my hull". Drives both safety-contour
3355
+ * conditional symbology rules:
3356
+ *
3357
+ * - §SOUNDG02 — soundings shoaler than this render in the
3358
+ * high-contrast color; deeper soundings render in the muted color.
3359
+ * - §CSYM02 — isolated dangers (rocks / wrecks / obstructions)
3360
+ * shoaler than this get the DNGCIR01 danger ring.
3361
+ *
3362
+ * Default `DEFAULT_SAFETY_CONTOUR_M` (10 m).
3363
+ *
3364
+ * Does not affect the DEPARE depth-area shading, whose 2/5/10 m bands
3365
+ * are a separate S-52 concept.
3366
+ */
3367
+ safetyContour?: number;
3368
+ /**
3369
+ * When false, all soundings render in a single shade. Default true.
3370
+ */
3371
+ twoShadeSoundings?: boolean;
3372
+ /**
3373
+ * Label model for the 9 orientation place-name classes (SEAARE, LNDRGN,
3374
+ * LAKARE, RIVERS, CANALS, BUAARE, AIRARE, LNDARE, PRDARE).
3375
+ *
3376
+ * "legacy" (default) — the per-cell-band orientation fan
3377
+ * (fanOrientationLabelByCellBand): one label per feature per band,
3378
+ * deconflicted only by `_label_anchor` tags and watermark opacity.
3379
+ *
3380
+ * "placename" — 2 PLACENAME symbol layers over the build-side
3381
+ * place-name index (CHRTbaremetal placeNameIndex.ts): one collision-
3382
+ * managed label per named place with a per-feature zoom window; the
3383
+ * orientation fan for those classes is NOT emitted. Requires tiles
3384
+ * built with the PLACENAME side index — on older tiles the layers
3385
+ * simply draw nothing (soft-fail is index-side), which is why the
3386
+ * default stays "legacy" until L2c's rebuild + Maine-coast visual
3387
+ * verification flips it (docs/design/chart-label-deconfliction.md
3388
+ * §2.3). Feature-label classes are untouched either way.
3389
+ */
3390
+ labels?: "legacy" | "placename";
3391
+ /**
3392
+ * Font weight for SOUNDG labels. Default "semibold" — crisper numerals
3393
+ * at small sizes on screen.
3394
+ */
3395
+ soundingFontWeight?: "regular" | "semibold";
3396
+ /**
3397
+ * How big the depth figures are drawn — soundings, contour values and the
3398
+ * depth beside a rock, wreck or obstruction. "standard" (default) is
3399
+ * byte-identical to omitting it; "large" enlarges those labels only, and
3400
+ * never their text, colours, sort keys, filters or layer set. See
3401
+ * depth-label-size.ts.
3402
+ */
3403
+ depthLabelSize?: DepthLabelSize;
3356
3404
  }
3357
- /** Fill element used in the SVG */
3358
- interface SymbolFillElement {
3359
- element: string;
3360
- color: string;
3361
- opacity?: number;
3405
+ declare function generateStyle(palette?: ColorTableName, opts?: StyleGeneratorOptions & {
3406
+ bandFan?: BandFanMode;
3407
+ }): MapboxStyle;
3408
+ declare function generateStyleJSON(palette?: ColorTableName, opts?: StyleGeneratorOptions): string;
3409
+ /** Get summary stats about the generated style */
3410
+ declare function getStyleStats(style: MapboxStyle): {
3411
+ totalLayers: number;
3412
+ fillLayers: number;
3413
+ lineLayers: number;
3414
+ symbolLayers: number;
3415
+ circleLayers: number;
3416
+ sources: number;
3417
+ objectClasses: Set<string>;
3418
+ };
3419
+
3420
+ /**
3421
+ * The e-ink portrayal pass — what `EINK_COLOR`, `EINK_MONO` and `EINK_NIGHT`
3422
+ * change beyond their colour tables. Night is mono inverted: everything below
3423
+ * applies, the patterns and safety contour are drawn LIGHT (the patterns are
3424
+ * themed per palette, the contour takes the table's DEPSC), and the chart
3425
+ * background gets the no-coverage crosshatch, because a black no-data hole
3426
+ * on a black-water page would read as deep water.
3427
+ *
3428
+ * A colour table can only move colours. E-paper needs more than that (owner
3429
+ * decision 2026-10-05: CHRT serves the e-ink chart, clients never recolour it;
3430
+ * requirements in CHRTsail's e-ink product spec §3c):
3431
+ *
3432
+ * 1. MEANING UNCHANGED. Nothing here adds, removes or reorders a feature
3433
+ * class or changes a filter that decides WHICH features draw. It changes
3434
+ * how they look, and adds two kinds of OVERLAY layer (depth patterns,
3435
+ * the safety contour) that draw features the chart already draws.
3436
+ * 2. MONO STANDS ON ITS OWN. Depth steps become fill PATTERNS (mono only);
3437
+ * danger areas get a cross-hatch; the safety contour gets its own heavy
3438
+ * line; restricted/caution areas keep their dashes, now heavier.
3439
+ * 3. DEPTH IN AT MOST FOUR STEPS. The tables do the colour side (DEPMD ==
3440
+ * DEPDW). Mono shows three: drying (hatch), shallower than the safety
3441
+ * contour (stipple), safe (paper).
3442
+ * 4. SAFETY CONTOUR AND DANGERS EMPHASISED: a heavy black safety-contour
3443
+ * line; danger symbols drawn larger; danger areas cross-hatched.
3444
+ * 5. COVERAGE PORTRAYAL SURVIVES: the no-coverage crosshatch goes to full
3445
+ * strength rather than being dropped with the other translucency.
3446
+ * 6. ATTRIBUTION/CURRENCY METADATA: untouched — this pass never edits
3447
+ * `sources` or the metadata other than adding `chrt:eink`.
3448
+ * 7. SIZES FOR 150 ppi COLOUR / 300 ppi MONO: every line ×1.5 (min 1 px),
3449
+ * text ×1.15 with a solid paper halo, no translucency, no blur, no fades.
3450
+ *
3451
+ * Translucency is the thing e-paper cannot draw: a 10% magenta wash over pale
3452
+ * blue is a fifth grey in mono and nothing at all on Kaleido 3. So faint washes
3453
+ * (< 0.3) are dropped — every one of them is a CHRT addition over a feature
3454
+ * whose boundary and symbol S-52 already draws — and stronger ones are made
3455
+ * solid.
3456
+ *
3457
+ * Pure: takes a generated style, returns a new one. `generateStyle` applies it
3458
+ * for the e-ink tables, so the served style, the offline style and the SDK's
3459
+ * local `generateStyle` all carry the same portrayal.
3460
+ */
3461
+
3462
+ /** The three e-ink fill patterns (house glyphs, registered with the world hatches). */
3463
+ declare const EINK_DOTS_PATTERN = "chrt:eink-dots";
3464
+ declare const EINK_HATCH_PATTERN = "chrt:eink-hatch";
3465
+ declare const EINK_DANGER_PATTERN = "chrt:eink-danger";
3466
+ /** The VALDCO values that ARE the safety contour, one per ladder. */
3467
+ declare function safetyContourValues(safetyContourM: number): number[];
3468
+ interface EinkPortrayalOptions {
3469
+ /** The mariner's safety contour, metres — the same value the generator used. */
3470
+ safetyContour?: number;
3362
3471
  }
3363
- interface NauticalSymbol {
3364
- id: string;
3365
- label: string;
3366
- /** INT1 / Chart No. 1 reference */
3367
- int1Ref: string;
3368
- /** Chart No. 1 section letter */
3369
- section: string;
3370
- getSvg: () => string;
3371
- /** Generate SVG with theme-specific colors (for bridge icons etc.) */
3372
- getThemedSvg?: (chblk: string, movable: string) => string;
3373
- /** What this symbol represents on a chart */
3374
- description?: string;
3375
- /** S-52 Presentation Library symbol name */
3376
- s52PlRef?: string;
3377
- /** S-57 object class (e.g. "LNDMRK") when it can't be derived from s52PlRef's prefix */
3378
- s57Class?: string;
3379
- /** Grouping key for family clustering */
3380
- group?: string;
3381
- /** IDs of related variant symbols */
3382
- variants?: string[];
3383
- /** S-57 attribute that selects this variant */
3384
- attributeKey?: string;
3385
- /** Value of that attribute for this variant */
3386
- attributeValue?: number;
3387
- /** Example label strings seen on charts */
3388
- labelExamples?: string[];
3389
- /** What the color represents */
3390
- colorMeaning?: string;
3391
- /** Colors used in the SVG */
3392
- colors?: SymbolColor[];
3393
- /** Line/stroke styles used in the SVG */
3394
- lineStyles?: SymbolLineStyle[];
3395
- /** Fill regions in the SVG */
3396
- fillElements?: SymbolFillElement[];
3397
- /** Expanded navigational usage context (2-4 sentences) */
3398
- usageContext?: string;
3399
- /** Render size for documentation */
3400
- renderSize?: string;
3472
+ /**
3473
+ * Apply the e-ink portrayal for `palette`. Returns the style unchanged for a
3474
+ * non-e-ink palette, so a caller may apply it unconditionally.
3475
+ */
3476
+ declare function applyEinkPortrayal(style: MapboxStyle, palette: ColorTableName, opts?: EinkPortrayalOptions): MapboxStyle;
3477
+
3478
+ /**
3479
+ * Light arithmetic for the chart palettes: how much light a colour emits, and
3480
+ * how to dim a colour without changing its hue.
3481
+ *
3482
+ * Everything here works in LINEAR light (WCAG 2.x relative luminance, sRGB
3483
+ * primaries). That is the quantity a dark-adapted eye responds to, and it is
3484
+ * the one `sounding-legibility.test.ts` already measures soundings in. Scaling
3485
+ * the sRGB-ENCODED channels instead would dim dark colours far more than
3486
+ * bright ones and shift every hue it touched.
3487
+ *
3488
+ * The one derivation the palettes rely on, and the reason this file exists:
3489
+ * NIGHT's deeper sounding SNDG1 (#897350) is SNDG2 (#c8aa78) at 43% of its
3490
+ * linear light. `labels.ts` builds its NIGHT secondary text the same way, so
3491
+ * the two stay one family by construction rather than by two hand-typed hexes
3492
+ * that happen to agree.
3493
+ */
3494
+ /** WCAG 2.x relative luminance of a `#rrggbb` colour, 0 (black) to 1 (white). */
3495
+ declare function relativeLuminance(hex: string): number;
3496
+ /** WCAG 2.x contrast ratio between two `#rrggbb` colours, 1 to 21. */
3497
+ declare function contrastRatio(a: string, b: string): number;
3498
+ /**
3499
+ * The same hue at `factor` times its linear light. `factor` above 1 can clip a
3500
+ * channel, which shifts the hue; nothing in the palettes brightens, so callers
3501
+ * pass 0–1.
3502
+ */
3503
+ declare function scaleLinearLight(hex: string, factor: number): string;
3504
+ /**
3505
+ * `hex` dimmed, hue kept, until its luminance is at most `ceiling`. A colour
3506
+ * already at or under the ceiling comes back unchanged (lower-cased).
3507
+ */
3508
+ declare function capLuminance(hex: string, ceiling: number): string;
3509
+ /**
3510
+ * NIGHT's two ink levels, shared by every family's text and marks.
3511
+ *
3512
+ * `NIGHT_INK` S52_NIGHT.CHBLK — the palette's linework and navigation
3513
+ * text. The brightest thing NIGHT draws on purpose.
3514
+ * `NIGHT_MUTED_INK` that ink at 43% of its linear light, which is how
3515
+ * S52_NIGHT.SNDG1 (the deeper soundings) was derived.
3516
+ * Everything that orients rather than navigates is drawn
3517
+ * at or under this, and `night-luminance.test.ts` holds
3518
+ * every visible NIGHT colour to it unless an exemption
3519
+ * names a reason.
3520
+ */
3521
+ declare const NIGHT_INK: string;
3522
+ declare const NIGHT_MUTED_INK: string;
3523
+
3524
+ /**
3525
+ * Is this MapLibre map still there to be written to?
3526
+ *
3527
+ * ===========================================================================
3528
+ * ⚠ ASK AFTER EVERY AWAIT. A MAP CAN BE REMOVED WHILE YOU WERE WAITING.
3529
+ * ===========================================================================
3530
+ *
3531
+ * `map.remove()` deletes `map.style`, and after that nearly every method that
3532
+ * touches the style throws rather than returning: `hasImage`, `addImage`,
3533
+ * `updateImage`, `getSource`, `addLayer` all fail with "Cannot read properties
3534
+ * of undefined (reading 'getImage')" and its siblings. Code that was handed a
3535
+ * map, awaited a decode, an IndexedDB read or a fetch, and then wrote to the
3536
+ * map, throws from wherever it resumed — usually a promise nobody is awaiting,
3537
+ * so the throw is an uncaught error with no caller to explain it.
3538
+ *
3539
+ * Measured in CHRTdesktop (chip 63, 2026-10-04): with the page's main thread
3540
+ * held at 6× CPU, the chart removed its map in the middle of a pan, and the
3541
+ * S-52 image loader resumed onto it:
3542
+ *
3543
+ * TypeError: Cannot read properties of undefined (reading 'getImage')
3544
+ * at Map.hasImage
3545
+ * at addImageToMap (s52-image-cache.ts)
3546
+ *
3547
+ * The rule, for anything in this package or the app that takes a map: after
3548
+ * an `await` (or in a `.then`, a frame callback, an `onload`), check this
3549
+ * before the first call on the map, and stop quietly if it says no. A removed
3550
+ * map is not an error to report — whoever removed it is building the next one.
3551
+ *
3552
+ * ⚠ `_removed`, NOT `style`. maplibre sets `_removed = true` as the last step
3553
+ * of `remove()` (typed on `Map` in maplibre-gl 4 and 5), so it is the fact,
3554
+ * where `style === undefined` is a consequence. Test doubles that never had a
3555
+ * `style` read as live, which is what they are. The app pins the maplibre side
3556
+ * of this (`map-live.test.ts`), so an upgrade that renames the field fails a
3557
+ * test rather than silently making every check say yes.
3558
+ *
3559
+ * Typed `object` so the structural map shapes the app passes around (a tile
3560
+ * census, an errored-tile tracker) can be asked too, not only `maplibregl.Map`.
3561
+ */
3562
+ declare function isMapLive(map: object): boolean;
3563
+
3564
+ /** S-52 display categories – controls default visibility */
3565
+ type DisplayCategory = "BASE" | "STANDARD" | "OTHER" | "MARINERS" | "META";
3566
+ /** Mapbox GL layer type we'll use for this entry */
3567
+ type GeomType = "fill" | "line" | "circle" | "symbol";
3568
+ /** Nautical groupings (loosely follows Chart No. 1 sections) */
3569
+ type NauticalCategory = "Depths" | "Land" | "Navigation Aids" | "Hazards" | "Regulated Areas" | "Infrastructure" | "Meta";
3570
+ /** Paint properties – a subset we actually use */
3571
+ interface AtlasStyle {
3572
+ type: GeomType;
3573
+ paint: Record<string, unknown>;
3574
+ layout?: Record<string, unknown>;
3575
+ /** Nautical symbol ID for symbol-type layers */
3576
+ iconImage?: string;
3401
3577
  }
3402
- /** A logical grouping of related symbols */
3403
- interface SymbolGroup {
3404
- id: string;
3405
- label: string;
3406
- section: string;
3407
- description: string;
3408
- symbolIds: string[];
3578
+ /** One row in the S-52 lookup table */
3579
+ interface LookupEntry {
3580
+ /** S-57 object class acronym */
3581
+ objectClass: string;
3582
+ /** Human-readable name */
3583
+ name: string;
3584
+ /** Chart No. 1 section */
3585
+ category: NauticalCategory;
3586
+ /** S-52 display category */
3587
+ displayCategory: DisplayCategory;
3588
+ /** Default style using S-52 color tokens (resolved later) */
3589
+ style: AtlasStyle;
3590
+ /** Whether conditional symbology may override the style */
3591
+ hasConditional?: boolean;
3592
+ /** Attributes that affect styling */
3593
+ conditionalAttributes?: string[];
3594
+ /** INT1 / Chart No. 1 reference (e.g. "Q130.1") */
3595
+ int1Ref?: string;
3596
+ /** S-52 viewing group number */
3597
+ viewingGroup?: number;
3598
+ /** Minimum compilation scale for display (SCAMIN) */
3599
+ scamin?: number;
3600
+ /**
3601
+ * Mark this class as deliberately invisible (label-only or metadata-only)
3602
+ * so the layer-coverage audit doesn't flag its geometry-type gaps as bugs.
3603
+ *
3604
+ * Examples:
3605
+ * - SOUNDG: rendered as depth numbers via labels.ts, not as visible dots
3606
+ * - LNDRGN: region NAME labels only, no shape
3607
+ * - M_NPUB / M_NSYS / M_QUAL / M_SREL / DSID: S-57 metadata classes;
3608
+ * present for click-query attributes, never drawn
3609
+ *
3610
+ * Without this flag, `scripts/audit-layer-coverage.ts` reports the
3611
+ * thousands of features in these classes as "gaps" every week even
3612
+ * though the invisibility is intentional.
3613
+ */
3614
+ auditIgnore?: boolean;
3409
3615
  }
3410
- /** Chart No. 1 section metadata */
3411
- interface ChartSection {
3412
- id: string;
3413
- label: string;
3414
- description: string;
3616
+ /** Resolved style ready for Mapbox GL (tokens → hex) */
3617
+ interface ResolvedStyle {
3618
+ type: GeomType;
3619
+ paint: Record<string, unknown>;
3620
+ layout?: Record<string, unknown>;
3621
+ minzoom?: number;
3622
+ maxzoom?: number;
3415
3623
  }
3416
- declare const NAUTICAL_SYMBOLS: NauticalSymbol[];
3417
- /** S-57 object class → default symbol ID mapping.
3418
- * Values are S-52 PL symbol names (matching SVG filenames in s52/point/).
3419
- * Conditional expressions in getIconExpression() override these for classes
3420
- * with attribute-dependent variants.
3624
+
3625
+ /**
3626
+ * S-52 lookup table covering all S-57 object classes found in NOAA ENCs.
3627
+ * Colours reference S-52 tokens (resolved at render time).
3628
+ * INT1 references per NOAA Chart No. 1 (Ed 13).
3629
+ */
3630
+ declare const S52_LOOKUP: Record<string, LookupEntry>;
3631
+
3632
+ /**
3633
+ * S-52 draw order: layers listed bottom → top.
3634
+ * Layers not in this list are inserted before point features.
3421
3635
  */
3422
- declare const SYMBOL_MAP: Record<string, string>;
3423
- /** Get the Mapbox icon-image expression for data-driven symbol selection.
3424
- * Returns S-52 PL symbol names that correspond to SVG files in s52/point/.
3636
+ declare const S52_DRAW_ORDER: string[];
3637
+ /** Get numeric draw priority for a layer (lower = drawn first / below) */
3638
+ declare function getDrawPriority(layerName: string): number;
3639
+
3640
+ /** S-52 display modes */
3641
+ type DisplayMode = "BASE" | "STANDARD" | "ALL";
3642
+ /** Check if a layer's display category is visible in the given mode */
3643
+ declare function isVisibleInMode(displayCategory: DisplayCategory, mode: DisplayMode): boolean;
3644
+ /**
3645
+ * Convert S-52 SCAMIN (scale minimum) to Mapbox GL minzoom.
3425
3646
  */
3426
- declare function getIconExpression(layerName: string): string | unknown[];
3647
+ declare function scaminToMinZoom(scamin?: number): number | undefined;
3648
+ declare function getCollisionSettings(viewingGroup?: number): {
3649
+ iconAllowOverlap: boolean;
3650
+ iconOptional: boolean;
3651
+ symbolSortKey?: number;
3652
+ };
3427
3653
  /**
3428
- * Browser-only image loaders (`loadSymbolImages`, `loadThemedBridgeImages`)
3429
- * live in `./symbols-browser.ts` (sub-export `@chrtco/sdk/atlas/browser`) —
3430
- * they require a DOM (use `new Image()` + `map.addImage()`).
3654
+ * Get the resolved Mapbox GL style for a given S-57 layer.
3431
3655
  */
3432
- /** Get inline SVG string for a symbol by ID (for the Atlas legend) */
3433
- declare function getSymbolSvgString(symbolId: string): string | null;
3434
- declare const CHART_SECTIONS: ChartSection[];
3435
- declare const SYMBOL_GROUPS: SymbolGroup[];
3436
- /** Generate a single SVG sprite sheet containing all symbols as <symbol> elements */
3437
- declare function generateSpriteSheet(): string;
3438
- /** Generate a JSON manifest mapping symbol IDs to metadata */
3439
- declare function generateSpriteManifest(): Record<string, {
3440
- label: string;
3441
- int1Ref: string;
3442
- section: string;
3443
- group?: string;
3444
- s52PlRef?: string;
3445
- description?: string;
3446
- attributeKey?: string;
3447
- attributeValue?: number;
3448
- }>;
3656
+ declare function resolveStyle(layerName: string, geomKind?: GeomType, ct?: ColorTableName, opts?: {
3657
+ safetyContour?: number;
3658
+ }): ResolvedStyle;
3659
+ declare function getLookupEntry(layerName: string): LookupEntry;
3449
3660
 
3450
3661
  /**
3451
- * IHO S-101 ENC Product Specification — Feature Catalogue
3662
+ * S-52 Conditional Symbology Procedures.
3663
+ * Each function takes feature properties and returns paint overrides (hex colours).
3664
+ * All expression builders accept an optional colorTable parameter.
3665
+ */
3666
+ /**
3667
+ * Default safety contour in metres — the depth below which a feature is
3668
+ * "dangerous to my hull" for portrayal purposes.
3452
3669
  *
3453
- * S-101 replaces S-57 as the data model for Electronic Navigational Charts.
3454
- * This catalogue maps S-101 feature classes to:
3455
- * - S-57 equivalents (where they exist)
3456
- * - Chart No. 1 sections (same organizational structure)
3457
- * - Portrayal catalogue references (S-100 Portrayal Model)
3670
+ * This is the single source of truth for the *default* used by every
3671
+ * safety-contour-driven conditional symbology rule, so the rules can't
3672
+ * silently disagree with each other:
3458
3673
  *
3459
- * Key S-101 changes from S-57:
3460
- * - CamelCase feature class names (DepthArea vs DEPARE)
3461
- * - Complex/structured attributes (nested objects vs flat)
3462
- * - New feature types for AIS, quality, under-keel clearance
3463
- * - Removed/merged features for simplification
3674
+ * - §SOUNDG02 — soundings shoaler than the contour get the
3675
+ * high-contrast shade (see `getLabelConfig` in labels.ts).
3676
+ * - §CSYM02 — isolated dangers shoaler than the contour get the
3677
+ * DNGCIR01 ring (see `buildLayersForClass` in layer-builder.ts).
3678
+ * - §DEPARE04 — the MS/MD shade boundary (owner decision 2026-08-21,
3679
+ * replacing a hardcoded 5 m middle cut; see the DEPARE block below
3680
+ * for the full IHO correspondence).
3464
3681
  *
3465
- * Reference: IHO S-101 Product Specification (Ed 1.1.0)
3466
- * IHO S-100 Universal Hydrographic Data Model
3682
+ * All are overridden by the user's setting (sounding panel slider,
3683
+ * `safetyContour` in the style options); this is only the fallback when
3684
+ * none is supplied. It matches `custom-style.ts`'s config default.
3685
+ */
3686
+ declare const DEFAULT_SAFETY_CONTOUR_M = 10;
3687
+ declare function depareColor(props: Record<string, unknown>, ct?: ColorTableName, safetyContourM?: number): string;
3688
+ /**
3689
+ * The colour token of each `case` branch of `depareFillExpression`, in branch
3690
+ * order, then the fallback. `depth-area-opaque.ts` names the layers it splits
3691
+ * the expression into from this list, so the two read from one place.
3692
+ */
3693
+ declare const DEPARE_FILL_BRANCH_TOKENS: readonly ["DEPIT", "DEPVS", "DEPMS", "DEPMD"];
3694
+ declare const DEPARE_FILL_FALLBACK_TOKEN = "DEPDW";
3695
+ declare function depareFillExpression(ct?: ColorTableName, safetyContourM?: number): unknown[];
3696
+ declare function lightColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3697
+ declare function lightFillExpression(ct?: ColorTableName): unknown[];
3698
+ declare function lateralColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3699
+ declare function lateralFillExpression(ct?: ColorTableName): unknown[];
3700
+ declare function hazardColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3701
+ declare function hazardFillExpression(ct?: ColorTableName): unknown[];
3702
+ declare function daymarkColor(props: Record<string, unknown>, ct?: ColorTableName): string;
3703
+
3704
+ /**
3705
+ * Label configurations for S-57 layers.
3706
+ * Defines which layers get text labels and how to extract label text.
3467
3707
  */
3468
- interface S101Feature {
3469
- /** S-101 camelCase feature class name */
3470
- featureClass: string;
3471
- /** Human-readable name */
3472
- name: string;
3473
- /** S-57 6-char equivalent acronym, if any */
3474
- s57Equivalent?: string;
3475
- /** Chart No. 1 section letter */
3476
- section: string;
3477
- /** Group key for clustering */
3478
- group: string;
3479
- /** Feature category */
3480
- category: "geo" | "meta" | "info";
3481
- /** Description of the feature */
3482
- description: string;
3483
- /** S-100 Portrayal Catalogue reference */
3484
- portrayalRef?: string;
3485
- /** True if this feature has no S-57 equivalent */
3486
- isNewInS101: boolean;
3487
- /** Key attributes specific to this feature */
3488
- attributes?: string[];
3489
- }
3490
- declare const S101_FEATURES: S101Feature[];
3491
- declare const S101_CHART_SECTIONS: {
3492
- id: string;
3493
- label: string;
3494
- description: string;
3495
- }[];
3496
- declare const S101_SYMBOL_GROUPS: {
3497
- id: string;
3498
- label: string;
3499
- section: string;
3500
- description: string;
3501
- }[];
3502
3708
 
3503
3709
  /**
3504
- * S-101 Portrayal Catalogue — New Symbol SVGs
3710
+ * Feature-class hierarchy for SEAARE labels. Returns a tier number 1-5 per
3711
+ * feature:
3712
+ *
3713
+ * Exported (with the other tier builders below) for the build-side parity
3714
+ * test: CHRTbaremetal replicates these taxonomies as data in
3715
+ * tools/chrt-baremetal/server/src/tilebuild/placeNameTiers.ts to stamp
3716
+ * `tier` onto PLACENAME index features, and
3717
+ * placename-tier-parity.test.ts evaluates the two against shared
3718
+ * fixtures. Change a branch here and that test names the drift.
3719
+ *
3720
+ * 1 — Ocean (OBJNAM contains "<X> Ocean" — CATSEA has no
3721
+ * ocean code; the attribute is 1/unset on oceans)
3722
+ * 2 — Sea / Gulf (OBJNAM "Gulf of …" / "… Sea")
3723
+ * 3 — Bay / Sound / (CATSEA in {5 bay, 12 narrows} OR OBJNAM
3724
+ * Strait / Narrows " Bay" / " Sound" / " Strait")
3725
+ * 4 — Anchorage detail (OBJNAM contains "Anchorage Area")
3726
+ * 5 — Relief + default (CATSEA relief codes — authoritative — or
3727
+ * relief-term names, or anything uncategorised;
3728
+ * suppressed below z9 by the size curve)
3729
+ *
3730
+ * Order matters: tier-4/5 demotions (anchorages, relief) run BEFORE the
3731
+ * promotion shims so "Bear Seamount" (contains " Sea") and "…Anchorage
3732
+ * Area" compound names can't bubble up into headline tiers.
3733
+ * `coalesce(CATSEA, catsea)` handles post-tippecanoe case variation
3734
+ * (matches `s52-conditional.ts` precedent).
3735
+ */
3736
+ declare function buildSeaAreaTierExpression(): ExpressionSpecification;
3737
+ /**
3738
+ * Built-up area tier (BUAARE): major city / town / neighborhood.
3739
+ * 1 — Major city (CATBUA=1 urban, or OBJNAM length <= 8)
3740
+ * 2 — Town (CATBUA=5 village, or OBJNAM length <= 14)
3741
+ * 3 — Neighborhood (longer compound names)
3742
+ *
3743
+ * Tier 1/2 may fire via CATBUA when populated; OBJNAM length is the
3744
+ * fallback so unpopulated CATBUA still produces a usable hierarchy.
3745
+ */
3746
+ declare function buildBuiltUpAreaTierExpression(): ExpressionSpecification;
3747
+ /**
3748
+ * Land region tier (LNDRGN): major region vs. local point.
3749
+ * 1 — Major region (OBJNAM length <= 12 AND no comma — "Long Island", "Cape Cod")
3750
+ * 2 — Local point (else — "Bird Island", "Cottage Park")
3751
+ *
3752
+ * No CATLND analog for region scope, so OBJNAM is the only signal.
3753
+ * The comma-absence test is a heuristic: NOAA compound names like
3754
+ * "Boston Harbor, MA Castle Island" carry a comma and belong at deeper
3755
+ * zoom regardless of length.
3756
+ */
3757
+ declare function buildLandRegionTierExpression(): ExpressionSpecification;
3758
+ /**
3759
+ * Land area tier (LNDARE): major vs. local.
3760
+ * 1 — Major (OBJNAM length <= 12)
3761
+ * 2 — Local
3762
+ *
3763
+ * No category attribute. OBJNAM length is the only signal.
3764
+ */
3765
+ declare function buildLandAreaTierExpression(): ExpressionSpecification;
3766
+ /**
3767
+ * Lake tier (LAKARE): lake vs. reservoir/pond.
3768
+ * 1 — Lake (CATLAK=1, or OBJNAM length <= 12)
3769
+ * 2 — Reservoir/pond (CATLAK in {2,3}, or longer name)
3770
+ */
3771
+ declare function buildLakeTierExpression(): ExpressionSpecification;
3772
+ /**
3773
+ * River tier (RIVERS): major vs. local.
3774
+ * 1 — Major river (OBJNAM length <= 15 AND no comma)
3775
+ * 2 — Local creek (else)
3776
+ */
3777
+ declare function buildRiverTierExpression(): ExpressionSpecification;
3778
+ /**
3779
+ * Airport tier (AIRARE): major airport vs. small airstrip.
3780
+ * 1 — Major (OBJNAM length <= 25 — e.g. "Logan International Airport")
3781
+ * 2 — Airstrip
3782
+ */
3783
+ declare function buildAirportTierExpression(): ExpressionSpecification;
3784
+ interface LabelLayerConfig {
3785
+ /** Mapbox source layer type */
3786
+ type: "symbol";
3787
+ /** Text field expression or string */
3788
+ textField: ExpressionSpecification | string;
3789
+ /** Text size in pixels */
3790
+ textSize: number | ExpressionSpecification;
3791
+ /** Text color (hex) or data-driven expression */
3792
+ textColor: string | ExpressionSpecification;
3793
+ /** Halo color for contrast */
3794
+ textHaloColor: string;
3795
+ /** Halo width — pixels or zoom-interpolation expression */
3796
+ textHaloWidth: number | ExpressionSpecification;
3797
+ /** Text offset [x, y] in ems */
3798
+ textOffset: [number, number];
3799
+ /** Font stack */
3800
+ textFont?: string[];
3801
+ /** Additional layout properties */
3802
+ layout?: Record<string, unknown>;
3803
+ /** Minimum zoom level for this label */
3804
+ minzoom?: number;
3805
+ }
3806
+ /**
3807
+ * Optional palette-and-data-driven options for label config.
3808
+ *
3809
+ * Currently used by SOUNDG to pick palette-aware text/halo colors and to
3810
+ * drive the S-52 safety-contour two-shade rule. Other classes ignore
3811
+ * these options.
3812
+ */
3813
+ interface LabelConfigOptions {
3814
+ /** S-52 colour table — drives palette-aware text and halo colors. */
3815
+ colorTable?: ColorTableName;
3816
+ /**
3817
+ * Safety contour depth in metres. Soundings shoaler than this render in
3818
+ * `SNDG2` (high-contrast); deeper soundings render in `SNDG1` (muted).
3819
+ * S-52 §SOUNDG02 conditional symbology.
3820
+ */
3821
+ safetyContour?: number;
3822
+ /**
3823
+ * When false, all soundings render in `SNDG2` (single-shade). Default
3824
+ * true — emit the safety-contour case expression.
3825
+ */
3826
+ twoShadeSoundings?: boolean;
3827
+ /**
3828
+ * When "semibold", SOUNDG labels use Open Sans Semibold for crisper
3829
+ * numerals at small sizes. Default "semibold".
3830
+ */
3831
+ soundingFontWeight?: "regular" | "semibold";
3832
+ }
3833
+ /**
3834
+ * Two-tier label rendering strategy for the band-priority style.
3505
3835
  *
3506
- * These symbols represent features introduced in S-101 that have no
3507
- * direct S-57/S-52 equivalent. The existing S-52 symbols are reused
3508
- * where S-101 retains visual continuity with the S-57 portrayal.
3836
+ * `feature-band-gated` — emit one variant per source band, each gated to
3837
+ * its band's zoom window (the existing `makeBandVariants` behaviour). Right
3838
+ * for dense feature labels where seeing the same buoy designation or
3839
+ * sounding twice across overlapping bands is visual noise.
3509
3840
  *
3510
- * Reference: IHO S-101 Portrayal Catalogue (Ed 1.1.0)
3841
+ * `orientation-cross-band` — emit a SINGLE label layer with no band
3842
+ * filter. The per-class `minzoom` is honoured. Right for orientation
3843
+ * "where am I" labels (sea-area names, land regions, cities) — these
3844
+ * must persist as the user zooms in, even if the broader-scope cell
3845
+ * carrying the OBJNAM is from a coarser band than the one rendering
3846
+ * features at the current zoom. Collision arbitration via
3847
+ * `ORIENTATION_BAND_SORT_KEY` gives the broader-scope (lower-band) copy
3848
+ * priority where the same OBJNAM exists in multiple bands.
3511
3849
  */
3512
-
3513
- declare const S101_NEW_SYMBOLS: NauticalSymbol[];
3514
-
3850
+ type LabelBehavior = "feature-band-gated" | "orientation-cross-band";
3851
+ declare const LABEL_BEHAVIOR: Record<string, LabelBehavior>;
3852
+ /** Returns the rendering strategy for an S-57 class. Unknown classes default to band-gated. */
3853
+ declare function getLabelBehavior(layerName: string): LabelBehavior;
3515
3854
  /**
3516
- * Post-consolidation (2026-04): one consolidated PMTiles per region, all
3517
- * S-57 classes as distinct MVT layers inside. The MapLibre style emits one
3518
- * vector source (id = region) and N paint layers that reference it via
3519
- * `source-layer` = S-57 class name.
3855
+ * Class-based label priority ladder — THE single collision-priority model
3856
+ * for feature labels (portrayal-excellence P4). Converges the previous
3857
+ * per-mechanism patches (SAFETY_CRITICAL_LABELS allow-overlap, the
3858
+ * band-5/6-only Harbor collision discipline) into one explicit ranking.
3520
3859
  *
3521
- * `buildSources()` returns either the legacy Mapbox MTS shape (preserved for
3522
- * demos / external style export — see generateStyle comments) or the PMTiles
3523
- * shape pointing at a single tile URL per region.
3860
+ * Rank semantics: LOWER rank = higher priority. The rank drives two
3861
+ * mechanisms that must stay in agreement:
3862
+ *
3863
+ * 1. `symbol-sort-key` — every feature label carries
3864
+ * `rank*1000 + within-class-key`, so if label layers are ever merged
3865
+ * (the P8 style fan-out reduction), placement priority survives the
3866
+ * merge unchanged.
3867
+ * 2. Label LAYER ORDER — MapLibre places symbols topmost-layer-first, so
3868
+ * `style-generator.ts` emits feature-label layers lowest-priority
3869
+ * first: safety-critical classes end up topmost and claim collision
3870
+ * space before anything else. (`symbol-sort-key` alone cannot do
3871
+ * this — it only orders placement WITHIN a layer.)
3872
+ *
3873
+ * The ladder (safety-critical > nav-aids > depth field > structures >
3874
+ * ambient notes):
3875
+ *
3876
+ * 100 — Safety-critical: light characteristics (LIGHTS), hazard depth
3877
+ * callouts (UWTROC/OBSTRN/WRECKS VALSOU), overhead clearances
3878
+ * (CBLOHD/PIPOHD VERCLR). Irreducible mariner-safety text.
3879
+ * 200 — Nav-aid designations: buoy / beacon `R "4"` labels.
3880
+ * 250 — Auxiliary nav-aid info: fog signal type (FOGSIG).
3881
+ * 300 — Depth field: soundings, contour values. Individually droppable —
3882
+ * a hidden sounding has visible neighbors.
3883
+ * 400 — Structures: bridges, landmarks, piers, moorings, buildings.
3884
+ * 500 — Ambient annotation: seabed nature, magnetic variation, kelp.
3885
+ * 900 — Orientation names (SEAARE etc.) — not part of this ladder;
3886
+ * they keep their settled watermark model (cell-band fan +
3887
+ * dominance composition + `orientationLabelLayout`) and always
3888
+ * paint underneath feature labels.
3889
+ *
3890
+ * Safety guardrail: a lower rung never blocks a higher rung — placement
3891
+ * order guarantees the safety-critical label got the space first. Density
3892
+ * relief comes from dropping the LOWEST rung in a pile-up, never the top.
3524
3893
  */
3525
- /** Vector source emitted for the legacy Mapbox MTS path. */
3526
- interface MapboxVectorSource {
3527
- type: "vector";
3528
- url: string;
3529
- }
3894
+ declare const LABEL_PRIORITY: Record<string, number>;
3530
3895
  /**
3531
- * Vector source emitted for the tiles.chrt.co PMTiles path. Uses an explicit
3532
- * `tiles` array (not a TileJSON `url`) because the tile-worker serves raw MVT
3533
- * bytes without a TileJSON descriptor.
3896
+ * Resolve the priority rank for an S-57 class's label. Orientation classes
3897
+ * report 900 regardless of ladder membership (PRDARE etc.) so the
3898
+ * layer-order sort in style-generator.ts can treat priority uniformly.
3534
3899
  */
3535
- interface PmtilesVectorSource {
3536
- type: "vector";
3537
- tiles: string[];
3538
- minzoom: number;
3539
- maxzoom: number;
3540
- /** The credit a renderer shows for this source (MapLibre's attribution control). */
3541
- attribution?: string;
3542
- }
3900
+ declare function getLabelPriority(layerName: string): number;
3543
3901
  /**
3544
- * Terrarium elevation, served as PNG from `CHRT_RELIEF`. Separate from the
3545
- * vector shapes because MapLibre needs `type: "raster-dem"` and an explicit
3546
- * `encoding` — a raster-dem source read as a plain raster renders the encoded
3547
- * RGB as a garish false-colour image rather than as elevation.
3902
+ * Compose a `symbol-sort-key` from a class rank and an optional
3903
+ * within-class key expression. The within-class key is clamped to
3904
+ * [0, 999] so it can never bleed into a neighboring rank's band.
3548
3905
  */
3549
- interface RasterDemSource {
3550
- type: "raster-dem";
3551
- tiles: string[];
3552
- encoding: "terrarium";
3553
- tileSize: number;
3554
- minzoom: number;
3555
- maxzoom: number;
3556
- /** The credit a renderer shows for this source (MapLibre's attribution control). */
3557
- attribution?: string;
3558
- }
3559
- type VectorSourceSpec = MapboxVectorSource | PmtilesVectorSource;
3560
- type AnySourceSpec = VectorSourceSpec | RasterDemSource;
3561
- /** CHRTland's vector land base. ADR 0023: its own archive, never chart data. */
3562
- declare const LAND_REGION = "CHRT_LAND";
3563
- /** CHRTland's terrarium relief. */
3564
- declare const RELIEF_REGION = "CHRT_RELIEF";
3565
- /** The world basemap under everything (completing-the-world-map, 2026-08-27). */
3566
- declare const BASE_REGION = "CHRT_BASE";
3906
+ declare function labelSortKey(rank: number, within?: unknown): ExpressionSpecification | number;
3567
3907
  /**
3568
- * CHRT_BASE's zoom range. 11 is the deepest zoom the archive's detail tier
3569
- * carries honestly — the planet's inland water exists from z9 and is read at
3570
- * z10 — and the world tier is tiled to the same depth so no layer vanishes
3571
- * from a tile the detail tier creates. Above 11 the client overzooms, and
3572
- * overzoomed inland water looks soft DELIBERATELY: it is "roughly where the
3573
- * lakes are", not a surveyed shoreline. Must equal the builder's MAXZOOM
3574
- * (tools/chrtbase-build/build.mjs).
3908
+ * Extract the trailing designation token from a nav-aid OBJNAM.
3909
+ *
3910
+ * NOAA names channel aids "<waterway> <type> Buoy|Beacon <designation>"
3911
+ * where the designation is 1-4 alphanumerics: "4", "18A", "B", "BG".
3912
+ * Returns null when the name has no designation (trailing token is a
3913
+ * stopword type-name, or longer than 4 chars).
3914
+ *
3915
+ * This is the TS twin of `navAidDesignationTextExpression` — the GeoJSON
3916
+ * preprocessing path uses this function, the PMTiles path uses the GL
3917
+ * expression. Keep the two in agreement (see label-priority.test.ts).
3575
3918
  */
3576
- declare const BASE_MINZOOM = 0;
3577
- declare const BASE_MAXZOOM = 11;
3919
+ declare function extractNavAidDesignation(objnam: string): string | null;
3920
+ /** Get label config for a given S-57 layer, or null if no labels */
3921
+ declare function getLabelConfig(layerName: string, opts?: LabelConfigOptions): LabelLayerConfig | null;
3578
3922
  /**
3579
- * The credit the ONLINE CHRT_BASE source carries (owner wording, 2026-09-16).
3580
- *
3581
- * OpenStreetMap is named because the online archive's z9–11 detail tier IS
3582
- * OSM-derived inland water (ODbL, `public/chrtbase-derivation.json`). The
3583
- * app-embeddable offline bundle is a z0–6 cut that contains no OSM data and
3584
- * carries `BASE_BUNDLE_ATTRIBUTION` instead (base-layers.ts) — one credit per
3585
- * artefact, each naming exactly the data it holds.
3923
+ * Build a Mapbox symbol layer spec from a label config.
3586
3924
  */
3587
- declare const BASE_ATTRIBUTION = "\u00A9 CHRT \u00B7 Made with Natural Earth \u00B7 \u00A9 OpenStreetMap contributors";
3925
+ declare function buildLabelLayer(layerId: string, sourceId: string, config: LabelLayerConfig, visible: boolean, overrides?: {
3926
+ textScale?: number;
3927
+ minzoom?: number | null;
3928
+ allowOverlap?: boolean;
3929
+ }): maplibregl.LayerSpecification;
3588
3930
  /**
3589
- * The credit the CHRT_LAND source carries. CHRTland's land, land use, roads,
3590
- * places, peaks and marinas are OpenStreetMap via the Protomaps planet, and the
3591
- * ODbL requires this notice wherever the map is shown (.chrt/legal.md
3592
- * "Required notices"). Its other inputs — the ENC's charted land, USGS 3DEP
3593
- * contours, GNIS names — are US federal public domain and owe none. Same
3594
- * string as the offline land bundle's (offline-worker `LAND_PROVENANCE`).
3931
+ * Pre-process SOUNDG GeoJSON to extract Z coordinates as `depth` properties.
3932
+ * Soundings are MultiPoint with Z values in geometry.coordinates[][2].
3933
+ * This explodes MultiPoints into individual Points with a `depth` property.
3595
3934
  */
3596
- declare const LAND_ATTRIBUTION = "\u00A9 OpenStreetMap contributors";
3935
+ declare function preprocessSoundings(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3597
3936
  /**
3598
- * The credit the CHRT_RELIEF source carries. USGS 3DEP elevation, public
3599
- * domain (17 U.S.C. § 105) — no notice is required, so this is a courtesy that
3600
- * names the source. There is no OpenStreetMap in it.
3937
+ * Pre-process LIGHTS GeoJSON to derive compact light characteristic labels.
3938
+ * Adds `_lightLabel` property (e.g., "Fl(2) WR 4s 5M").
3939
+ *
3940
+ * KEEP IN STEP WITH `buildLightLabelExpression()` in layer-builder.ts — that
3941
+ * is the same label built as a GL expression for the tile path, which has no
3942
+ * JS preprocessing step. `light-label-parity.test.ts` asserts the two agree
3943
+ * on real tile features; if you change one, change both.
3601
3944
  */
3602
- declare const RELIEF_ATTRIBUTION = "Elevation: USGS 3D Elevation Program";
3603
- /** Terrarium tiles are 512 px; the default 256 would halve the apparent relief. */
3604
- declare const RELIEF_TILE_SIZE = 512;
3945
+ declare function preprocessLights(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3946
+ /** List of layers that have labels */
3947
+ declare const LABELED_LAYERS: Set<string>;
3605
3948
  /**
3606
- * How `CHRT_RELIEF`'s PNGs encode elevation. MapLibre's raster-dem default is
3607
- * `mapbox`, which decodes terrarium RGB into nonsense heights — so every
3608
- * surface that describes this source (this registry, the tile-worker's
3609
- * TileJSON) has to say it.
3949
+ * Pre-process buoy/beacon GeoJSON to derive concise mariner-style labels.
3950
+ * Adds `_navAidLabel` property (e.g., 'R "8"', 'G "5"').
3610
3951
  */
3611
- declare const RELIEF_ENCODING = "terrarium";
3612
- type SourceMode = "mapbox" | "pmtiles";
3952
+ declare function preprocessNavAids(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3953
+ /** Check if a layer name needs nav aid preprocessing */
3954
+ declare function isNavAidLayer(layerName: string): boolean;
3613
3955
  /**
3614
- * Query parameter carrying the resolved build id on every tile URL.
3615
- *
3616
- * ── Why the build has to be IN THE URL ────────────────────────────────────
3617
- *
3618
- * The Worker reports the build it served in the `X-Build-Id` RESPONSE HEADER
3619
- * and sends `Cache-Control: private, max-age=3600`. A header is not part of a
3620
- * cache key, so every build shares one URL: after a cutover, tiles the client
3621
- * already fetched keep coming from its own HTTP cache — the old build — while
3622
- * newly-panned-to tiles come from the new one, for up to the full hour. The
3623
- * client renders TWO BUILDS AT ONCE.
3624
- *
3625
- * On a nautical chart that is not a cosmetic seam. Measured live 2026-08-25,
3626
- * cutting `CHRT_LAND` from `463b265e` to `dbb69074` (a build that deliberately
3627
- * changes land extent): adjacent tiles disagreed about whether land was there,
3628
- * at the seam between them. Putting the build in the key gives each build its
3629
- * own cache namespace — old entries age out untouched, and the next style
3630
- * build flips the whole viewport at once instead of tile by tile.
3631
- *
3632
- * ── Why a QUERY PARAMETER and not a path segment ──────────────────────────
3633
- *
3634
- * A path segment would be the tidier-looking URL and it is the wrong trade
3635
- * here, for two reasons that are both about other people's code:
3636
- *
3637
- * 1. COMPATIBILITY IS PERMANENT, NOT "one release". iOS and desktop are
3638
- * prod-only clients that ship on their own cadence. The Worker routes on
3639
- * `url.pathname` (`TILE_ROUTE_PATH`) and reads only `?build=`, so a URL
3640
- * carrying this parameter routes BYTE-IDENTICALLY to one without it. A
3641
- * new path segment would instead need a second route regex kept alive
3642
- * indefinitely.
3643
- * 2. FIVE SEPARATE PATTERNS ALREADY MATCH THIS URL, and every one of them
3644
- * ends `(?:\?.*)?$` — `src/lib/map/tile-token-client.ts` TILE_URL_PATH
3645
- * and `src/site/chart/sail-tile-token.ts` (which decide whether a tile
3646
- * gets its Bearer), `packages/sdk/src/tile-auth.ts` TILE_URL_PATH,
3647
- * `public/chrt-tile-sw.js` TILE_URL_RE (which also reads the region out of
3648
- * the URL to pick its per-region cache), and
3649
- * `src/site/offline/pack-protocol.ts` TILE_URL (which rewrites a tile to
3650
- * a downloaded pack). Their optional PATH segment, by contrast, is
3651
- * `[a-z][a-z0-9-]*` — letter-first so it cannot be confused with the
3652
- * numeric `{z}` — and build ids are hex (`463b265e`, `4de67273`). A build
3653
- * in the path would miss every one of them, and a tile those patterns
3654
- * miss does not merely go uncached: it goes out with NO BEARER and comes
3655
- * back 401, which MapLibre records as a permanently errored tile. That is
3656
- * the exact failure the `.png` drift caused on staging-sail (see
3657
- * `chrt-tile-sw.js`).
3658
- *
3659
- * The query string is also what the caches this fix targets actually key on:
3660
- * the browser HTTP cache, MapLibre's own tile cache, and the Service Worker's
3661
- * `new Request(req.url)` entry key all include it.
3662
- *
3663
- * `v` rather than `build`: `?build=` is a taken name with a strict meaning on
3664
- * the Worker (`?build=candidate`, employee-only, 400 on any other value).
3956
+ * Pre-process water body polygon GeoJSON into centroid points for label placement.
3957
+ * Computes a simple average-coordinate centroid for each polygon feature that has OBJNAM.
3958
+ */
3959
+ declare function preprocessWaterBodies(fc: GeoJSON.FeatureCollection): GeoJSON.FeatureCollection;
3960
+ /** MVT layer the build-side place-name index emits. */
3961
+ declare const PLACENAME_SOURCE_LAYER = "PLACENAME";
3962
+ /** Orientation classes whose names render italic (water voice). */
3963
+ declare const PLACENAME_WATER_CLASSES: readonly string[];
3964
+ /** Orientation classes whose names render upright (land voice). */
3965
+ declare const PLACENAME_LAND_CLASSES: readonly string[];
3966
+ /**
3967
+ * The 2 PLACENAME symbol layers. Layout contract (design §2.2): collision
3968
+ * ON (`allow-overlap: false`, `ignore-placement: false`,
3969
+ * `optional: true`), padding 4, `text-variable-anchor`, and
3970
+ * `symbol-sort-key` straight from the build-side `rank` — MapLibre places
3971
+ * LOWER sort keys first, and rank is minted lower-is-more-important
3972
+ * (tier major, bbox area minor), so when space runs out the biggest names
3973
+ * survive. That ranking is the per-zoom density governor P4 asked for.
3665
3974
  */
3666
- declare const BUILD_PARAM = "v";
3975
+ declare function buildPlaceNameLayers(sourceId: string, ct?: ColorTableName): maplibregl.LayerSpecification[];
3976
+
3667
3977
  /**
3668
- * One tile-URL template — the single place a CHRT tile URL is spelled.
3669
- *
3670
- * `buildId` is encoded rather than interpolated raw: MapLibre substitutes
3671
- * `{z}`/`{x}`/`{y}` by brace-matching the template, so an id containing a
3672
- * brace would corrupt every tile request. Empty / whitespace-only ids emit no
3673
- * parameter at all instead of a bare `?v=` that would key its own useless
3674
- * cache namespace.
3978
+ * Spatial grid-based feature decimation for performance at low zoom levels.
3979
+ * Thins dense GeoJSON feature sets by keeping only one representative
3980
+ * feature per grid cell per layer.
3675
3981
  */
3676
- declare function tileTemplate(base: string, region: string, extension: "mvt" | "png", buildId?: string): string;
3677
- /** Canonical source id used in `generateStyle()` — matches the region name
3678
- * of the one consolidated PMTiles served by the tile-worker for this region. */
3679
- declare const DEFAULT_REGION = "NOAA_ENC";
3680
- interface BuildSourcesOptions {
3681
- /** Which tile path to emit. Default: "pmtiles" (the supported path post-Mapbox). */
3682
- mode?: SourceMode;
3683
- /** Chart region. Default: "NOAA_ENC". */
3684
- region?: string;
3685
- /**
3686
- * Build id of the CHART region (`region`) — the `build_tag` a
3687
- * `mint-tile-token` response hands back.
3688
- *
3689
- * Emitted as the {@link BUILD_PARAM} query parameter on that region's tile
3690
- * template. See {@link tileTemplate} for why a build discriminator has to be
3691
- * in the URL at all.
3692
- *
3693
- * ⚠ THIS COVERS THE CHART REGION ONLY. A token is minted for one chart
3694
- * region and `CHRT_LAND` / `CHRT_RELIEF` ride it, so the JWT's `build_tag`
3695
- * says nothing about a land cutover — the same blind spot that made the
3696
- * Worker's edge-cache key wrong until `TILE_CACHE_VERSION` v3
3697
- * (cloudflare/tile-worker/src/cache-encoding.ts). Pass {@link buildTags} to
3698
- * discriminate the basemap sources too.
3699
- */
3700
- buildTag?: string;
3701
- /**
3702
- * Resolved build id per region — `{ NOAA_ENC: "…", CHRT_LAND: "…" }`.
3703
- *
3704
- * The complete form of {@link buildTag}: every source this style declares
3705
- * gets its own discriminator, so a `CHRT_LAND` promotion turns over the land
3706
- * URLs without touching the chart's (and vice versa). An entry here wins
3707
- * over `buildTag` for the same region.
3708
- *
3709
- * This is the mint response's `build_tags` map verbatim (claim contract
3710
- * v1.7) — `TileTokenResponse.buildTags` in both `lib/map/tile-token-client.ts`
3711
- * and the SDK's `tile-auth.ts`. The same map already keys the tile Service
3712
- * Worker's per-region caches; passing it here puts the same identity in the
3713
- * URL, so the layer above the Service Worker stops mixing builds too.
3714
- *
3715
- * A region absent from the map simply gets no discriminator — the URL it
3716
- * has always had. That is a deliberate soft edge: an older mint response, or
3717
- * a region with no servable build, must degrade to today's behaviour rather
3718
- * than emit a URL pinned to a build nobody resolved.
3719
- */
3720
- buildTags?: Readonly<Record<string, string>>;
3721
- /** Override tile-worker origin (e.g. staging). Default: DEFAULT_TILE_BASE_URL. */
3722
- tileBaseUrl?: string;
3723
- /**
3724
- * Emit the CHRTland vector land base as a second source (ADR 0023). Off by
3725
- * default: it costs a source, and the region must be granted on the token.
3726
- */
3727
- land?: boolean;
3728
- /**
3729
- * Emit CHRTland's terrarium relief as a raster-dem source. Separate from
3730
- * `land` because relief needs a raster-capable worker AND a grant that
3731
- * `land-regions.ts` still withholds.
3732
- */
3733
- relief?: boolean;
3734
- /**
3735
- * Emit the CHRT_BASE world basemap as a third vector source — the dark
3736
- * geography under everything, the tan jurisdiction fill, and the rough
3737
- * inland water inside countries CHRT carries. Its layers sit UNDER the
3738
- * no-coverage hatch's companions and everything else; see baseLayers().
3739
- */
3740
- base?: boolean;
3982
+ interface DecimationOptions {
3983
+ /** Grid resolution (cells per axis). Default 80. */
3984
+ gridSize?: number;
3985
+ /** Zoom level below which decimation kicks in. Default 12. */
3986
+ zoomThreshold?: number;
3741
3987
  }
3742
3988
  /**
3743
- * Build the "sources" block for a CHRT style.
3744
- *
3745
- * - `mode: "mapbox"`: one mapbox:// url (kept for external style export only).
3746
- * - `mode: "pmtiles"` (default): one vector tile endpoint for the region,
3747
- * routed by the Worker to the one consolidated PMTiles:
3748
- * `${tileBaseUrl}/v1/tiles/{region}/{z}/{x}/{y}.mvt`
3989
+ * Zoom-adaptive grid resolution for decimation.
3990
+ */
3991
+ declare function getGridSizeForZoom(zoom: number): number;
3992
+ /**
3993
+ * Decimate a GeoJSON FeatureCollection using spatial grid thinning.
3994
+ * Only runs when zoom < zoomThreshold. Returns the original FC otherwise.
3749
3995
  *
3750
- * Pass `buildTag` / `buildTags` to pin each source's URL to the build it is
3751
- * serving — without that, one URL is shared by every build and a cutover
3752
- * leaves the client rendering two of them at once. See {@link BUILD_PARAM}.
3996
+ * @param fc - Input FeatureCollection
3997
+ * @param layerName - S-57 layer name (affects priority logic)
3998
+ * @param currentZoom - Current map zoom level
3999
+ * @param bounds - Current viewport bounds
4000
+ * @param opts - Decimation options
3753
4001
  */
3754
- declare function buildSources(opts?: BuildSourcesOptions): Record<string, AnySourceSpec>;
3755
-
4002
+ declare function decimateFeatures(fc: GeoJSON.FeatureCollection, layerName: string, currentZoom: number, bounds: {
4003
+ west: number;
4004
+ south: number;
4005
+ east: number;
4006
+ north: number;
4007
+ }, opts?: DecimationOptions): GeoJSON.FeatureCollection;
3756
4008
  /**
3757
- * Builds Mapbox GL Style layer specs from S-52 lookup entries.
3758
- * Wraps the existing atlas resolveStyle() and adds source/source-layer/filter.
4009
+ * Get the max number of cells to load based on zoom level.
3759
4010
  */
4011
+ declare function getMaxCellsForZoom(zoom: number): number;
3760
4012
 
3761
- interface StyleLayer {
3762
- id: string;
3763
- type: string;
3764
- source: string;
3765
- "source-layer": string;
3766
- filter?: unknown[];
3767
- paint: Record<string, unknown>;
3768
- layout?: Record<string, unknown>;
3769
- minzoom?: number;
3770
- maxzoom?: number;
3771
- metadata?: Record<string, unknown>;
3772
- }
3773
4013
  /**
3774
- * Build all Mapbox GL layers for a given S-57 object class.
3775
- * Returns multiple layers (fill, outline, symbol, label, etc.).
4014
+ * IHO S-52 / NOAA Chart No. 1 / INT1 nautical symbol SVGs.
3776
4015
  *
3777
- * Post-consolidation: all S-57 classes live as MVT layers inside one
3778
- * consolidated PMTiles per region; `source` is always the region name.
4016
+ * Complete symbology suite following:
4017
+ * - IHO S-52 Presentation Library (Ed 4.0)
4018
+ * - IALA Maritime Buoyage System R1001 (Region B for US waters)
4019
+ * - NOAA Chart No. 1 (Ed 13) / INT1
3779
4020
  *
3780
- * `opts.safetyContour` is the mariner's safety depth in metres — it drives
3781
- * the §CSYM02 isolated-danger ring on hazard classes. Defaults to
3782
- * `DEFAULT_SAFETY_CONTOUR_M`, the same default the SOUNDG two-shade rule
3783
- * uses, so the ring and the sounding colour always agree.
4021
+ * Buoy shape terminology (US / Chart No. 1):
4022
+ * - CAN (Q20): Flat-top cylindrical buoy — Port mark in IALA-B (red)
4023
+ * - NUN (Q21): Conical/pointed-top buoy — Starboard mark in IALA-B (green)
4024
+ * - PILLAR: Tall narrow buoy — used for cardinal, isolated danger, etc.
4025
+ * - SPAR: Tall thin pole buoy
4026
+ * - SPHERICAL: Round buoy — safe water marks
4027
+ * - BARREL: Barrel/tun shaped buoy
4028
+ * - SUPER: Large navigation buoy / LANBY
4029
+ *
4030
+ * Cardinal mark topmark orientations (IALA):
4031
+ * - North: both cones point UP ▲▲
4032
+ * - South: both cones point DOWN ▼▼
4033
+ * - East: cones base-to-base (diamond) ◆
4034
+ * - West: cones point-to-point (hourglass) ⊗
4035
+ *
4036
+ * Each symbol is a compact SVG at 32×32 for Mapbox GL `map.addImage()`.
3784
4037
  */
3785
- declare function buildLayersForClass(objectClass: string, ct: ColorTableName, sourceId?: string, opts?: {
3786
- safetyContour?: number;
3787
- }): StyleLayer[];
4038
+ /** Color used in the SVG with its S-52 token name, hex, and usage */
4039
+ interface SymbolColor {
4040
+ token: string;
4041
+ hex: string;
4042
+ usage: string;
4043
+ }
4044
+ /** Line style used in the SVG */
4045
+ interface SymbolLineStyle {
4046
+ element: string;
4047
+ width: number;
4048
+ pattern?: string;
4049
+ color: string;
4050
+ }
4051
+ /** Fill element used in the SVG */
4052
+ interface SymbolFillElement {
4053
+ element: string;
4054
+ color: string;
4055
+ opacity?: number;
4056
+ }
4057
+ interface NauticalSymbol {
4058
+ id: string;
4059
+ label: string;
4060
+ /** INT1 / Chart No. 1 reference */
4061
+ int1Ref: string;
4062
+ /** Chart No. 1 section letter */
4063
+ section: string;
4064
+ getSvg: () => string;
4065
+ /** Generate SVG with theme-specific colors (for bridge icons etc.) */
4066
+ getThemedSvg?: (chblk: string, movable: string) => string;
4067
+ /** What this symbol represents on a chart */
4068
+ description?: string;
4069
+ /** S-52 Presentation Library symbol name */
4070
+ s52PlRef?: string;
4071
+ /** S-57 object class (e.g. "LNDMRK") when it can't be derived from s52PlRef's prefix */
4072
+ s57Class?: string;
4073
+ /** Grouping key for family clustering */
4074
+ group?: string;
4075
+ /** IDs of related variant symbols */
4076
+ variants?: string[];
4077
+ /** S-57 attribute that selects this variant */
4078
+ attributeKey?: string;
4079
+ /** Value of that attribute for this variant */
4080
+ attributeValue?: number;
4081
+ /** Example label strings seen on charts */
4082
+ labelExamples?: string[];
4083
+ /** What the color represents */
4084
+ colorMeaning?: string;
4085
+ /** Colors used in the SVG */
4086
+ colors?: SymbolColor[];
4087
+ /** Line/stroke styles used in the SVG */
4088
+ lineStyles?: SymbolLineStyle[];
4089
+ /** Fill regions in the SVG */
4090
+ fillElements?: SymbolFillElement[];
4091
+ /** Expanded navigational usage context (2-4 sentences) */
4092
+ usageContext?: string;
4093
+ /** Render size for documentation */
4094
+ renderSize?: string;
4095
+ }
4096
+ /** A logical grouping of related symbols */
4097
+ interface SymbolGroup {
4098
+ id: string;
4099
+ label: string;
4100
+ section: string;
4101
+ description: string;
4102
+ symbolIds: string[];
4103
+ }
4104
+ /** Chart No. 1 section metadata */
4105
+ interface ChartSection {
4106
+ id: string;
4107
+ label: string;
4108
+ description: string;
4109
+ }
4110
+ declare const NAUTICAL_SYMBOLS: NauticalSymbol[];
4111
+ /** S-57 object class → default symbol ID mapping.
4112
+ * Values are S-52 PL symbol names (matching SVG filenames in s52/point/).
4113
+ * Conditional expressions in getIconExpression() override these for classes
4114
+ * with attribute-dependent variants.
4115
+ */
4116
+ declare const SYMBOL_MAP: Record<string, string>;
4117
+ /** Get the Mapbox icon-image expression for data-driven symbol selection.
4118
+ * Returns S-52 PL symbol names that correspond to SVG files in s52/point/.
4119
+ */
4120
+ declare function getIconExpression(layerName: string): string | unknown[];
3788
4121
  /**
3789
- * Get the full ordered list of object classes for style generation.
3790
- * Uses S52_DRAW_ORDER as base, then adds any missing classes from the
3791
- * S-52 lookup table so a newly-ingested class automatically gets styled
3792
- * without touching this file.
4122
+ * Browser-only image loaders (`loadSymbolImages`, `loadThemedBridgeImages`)
4123
+ * live in `./symbols-browser.ts` (sub-export `@chrtco/sdk/atlas/browser`) —
4124
+ * they require a DOM (use `new Image()` + `map.addImage()`).
3793
4125
  */
3794
- declare function getOrderedClasses(): string[];
4126
+ /** Get inline SVG string for a symbol by ID (for the Atlas legend) */
4127
+ declare function getSymbolSvgString(symbolId: string): string | null;
4128
+ declare const CHART_SECTIONS: ChartSection[];
4129
+ declare const SYMBOL_GROUPS: SymbolGroup[];
4130
+ /** Generate a single SVG sprite sheet containing all symbols as <symbol> elements */
4131
+ declare function generateSpriteSheet(): string;
4132
+ /** Generate a JSON manifest mapping symbol IDs to metadata */
4133
+ declare function generateSpriteManifest(): Record<string, {
4134
+ label: string;
4135
+ int1Ref: string;
4136
+ section: string;
4137
+ group?: string;
4138
+ s52PlRef?: string;
4139
+ description?: string;
4140
+ attributeKey?: string;
4141
+ attributeValue?: number;
4142
+ }>;
3795
4143
 
3796
4144
  /**
3797
- * Dominance fan-out collapse (P8 Phase 3.3 / D4 "axis 1").
3798
- *
3799
- * `makeBandVariants()` historically emitted, per (class, band): one
3800
- * "not-dominated" layer plus up to five `-domBy{D}` copies that differ ONLY
3801
- * in their dominance filter and a `maxzoom` of `dominatedVariantMaxzoom(D)`.
3802
- * That fan existed because MapLibre filters could not read `["zoom"]`. They
3803
- * can now (style-spec 24.8.5; buckets evaluate at the tile's `overscaledZ`),
3804
- * so the whole fan folds into ONE layer per (class, band) whose filter
3805
- * carries the zoom gate.
3806
- *
3807
- * **Construction principle — read before changing anything here.** The
3808
- * collapsed filter is the literal `["any"]` UNION of the clauses the fan
3809
- * already encoded: every variant the legacy emitter would have produced
3810
- * contributes exactly one `["all", <its dominance filter>, ["<", ["zoom"],
3811
- * <its maxzoom>]]` clause, and the dead-variant skip is mirrored too.
3812
- * Equivalence is therefore *structural*, not re-derived — which matters,
3813
- * because the two "obvious" hand-written forms are both wrong:
3814
- *
3815
- * 1. A `["match", ["get","_dominated_by_min"], 2,5, 3,7, 4,9, 5,11, 6,11, 24]`
3816
- * threshold (the form the design doc proposed) uses band 6's *eligibility*
3817
- * zoom 11. The real ceiling is `dominatedVariantMaxzoom("6") = 13` — band
3818
- * 6's DATA floor. Using 11 reopens the z11–z12 void window that
3819
- * `band-priority.ts` documents closing.
3820
- * 2. That same form silently drops the legacy `_dominated: true` boolean,
3821
- * which `dominatedByMinFilter("6")` still honours. A legacy-tagged feature
3822
- * has no `_dominated_by_min`, so `match` falls to its default and the
3823
- * feature renders FOREVER instead of dropping at z13.
3824
- *
3825
- * Deriving the gate from the emitter's own clauses avoids both by construction.
3826
- * `band-collapse.test.ts` re-proves it on every CI run by differentially
3827
- * evaluating legacy-vs-collapsed through MapLibre's real three gates.
4145
+ * IHO S-101 ENC Product Specification — Feature Catalogue
3828
4146
  *
3829
- * **Scope.** For FEATURE layers (fills, lines, feature symbols) this is
3830
- * axis 1 ONLY: the per-band layers survive, with identical ids and identical
3831
- * positions, so the band-major draw order is untouched. "Axis 2" (collapsing
3832
- * the per-band copies into one layer per class) was evaluated and **KILLED**
3833
- * for feature layers — it is unsound for the opaque-fill classes
3834
- * (`DOMINANCE_EXEMPT_FILL_CLASSES` ≡ `OPAQUE_FILL_LAYERS`), where band-major
3835
- * order is the ONLY thing separating the bands, and 15/15 class pairs are
3836
- * provably unsatisfiable with one layer each. Do not reintroduce it there.
3837
- * Evidence: `.chrt/perf/2026-07-14-p8-d4-spike.md`.
4147
+ * S-101 replaces S-57 as the data model for Electronic Navigational Charts.
4148
+ * This catalogue maps S-101 feature classes to:
4149
+ * - S-57 equivalents (where they exist)
4150
+ * - Chart No. 1 sections (same organizational structure)
4151
+ * - Portrayal catalogue references (S-100 Portrayal Model)
3838
4152
  *
3839
- * LABEL layers are different (CHRTsail #121 — MapLibre Native iOS silently
3840
- * collapses its whole render pass above 482 symbol layers, and the served
3841
- * style carried 575): `makeCollapsedCrossBandLabel` folds a label's ×6 band
3842
- * fan into ONE layer per class, and `collapsedOrientationGate` folds the
3843
- * orientation fan's per-dominator variants into its per-cell layers. That is
3844
- * NOT the killed axis 2, because what the fill argument protects — paint
3845
- * order as the sole band separator — does not apply to labels: their
3846
- * load-bearing order is the cross-class P4 priority ladder (each class keeps
3847
- * exactly one layer in its ladder slot), and within-class placement priority
3848
- * rides the `rank*1000 + within-key` symbol-sort-key that labels.ts
3849
- * documents as designed to survive precisely this merge.
4153
+ * Key S-101 changes from S-57:
4154
+ * - CamelCase feature class names (DepthArea vs DEPARE)
4155
+ * - Complex/structured attributes (nested objects vs flat)
4156
+ * - New feature types for AIS, quality, under-keel clearance
4157
+ * - Removed/merged features for simplification
3850
4158
  *
3851
- * **Load-bearing invariant.** A collapsed per-band FEATURE layer MUST keep
3852
- * the `-band{B}` id segment: `custom-style.ts` `bandFloor()` parses
3853
- * `/-band(\d)/` to clamp customer minzoom/scamin overrides to the band's
3854
- * data floor, on the live public Style API path. Renaming the id silently
3855
- * unclamps those overrides. Cross-band LABEL layers are the deliberate
3856
- * exception: they carry every band floor INSIDE the filter (tile-zoom
3857
- * clauses a customer override cannot touch), which supersedes the clamp —
3858
- * `bandFloor()` returning 0 for a bare label id is correct, not a gap.
3859
- * Both halves asserted in `band-collapse.test.ts`.
3860
- */
3861
-
3862
- /**
3863
- * Which emitter the style generator uses for the dominance fan.
3864
- * `"legacy"` exists so CI can differentially prove the two equivalent (it is
3865
- * the oracle in `band-collapse.test.ts`) and as a one-line rollback. It is an
3866
- * internal knob — deliberately NOT on the exported `StyleGeneratorOptions`,
3867
- * so it cannot reach `@chrtco/sdk` consumers via `ChrtStyleOptions`.
4159
+ * Reference: IHO S-101 Product Specification (Ed 1.1.0)
4160
+ * IHO S-100 Universal Hydrographic Data Model
3868
4161
  */
3869
- type BandFanMode = "collapsed" | "legacy";
4162
+ interface S101Feature {
4163
+ /** S-101 camelCase feature class name */
4164
+ featureClass: string;
4165
+ /** Human-readable name */
4166
+ name: string;
4167
+ /** S-57 6-char equivalent acronym, if any */
4168
+ s57Equivalent?: string;
4169
+ /** Chart No. 1 section letter */
4170
+ section: string;
4171
+ /** Group key for clustering */
4172
+ group: string;
4173
+ /** Feature category */
4174
+ category: "geo" | "meta" | "info";
4175
+ /** Description of the feature */
4176
+ description: string;
4177
+ /** S-100 Portrayal Catalogue reference */
4178
+ portrayalRef?: string;
4179
+ /** True if this feature has no S-57 equivalent */
4180
+ isNewInS101: boolean;
4181
+ /** Key attributes specific to this feature */
4182
+ attributes?: string[];
4183
+ }
4184
+ declare const S101_FEATURES: S101Feature[];
4185
+ declare const S101_CHART_SECTIONS: {
4186
+ id: string;
4187
+ label: string;
4188
+ description: string;
4189
+ }[];
4190
+ declare const S101_SYMBOL_GROUPS: {
4191
+ id: string;
4192
+ label: string;
4193
+ section: string;
4194
+ description: string;
4195
+ }[];
3870
4196
 
3871
4197
  /**
3872
- * Generates a complete Mapbox GL Style Specification v8 JSON
3873
- * from the S-52 atlas definitions.
4198
+ * S-101 Portrayal Catalogue — New Symbol SVGs
4199
+ *
4200
+ * These symbols represent features introduced in S-101 that have no
4201
+ * direct S-57/S-52 equivalent. The existing S-52 symbols are reused
4202
+ * where S-101 retains visual continuity with the S-57 portrayal.
4203
+ *
4204
+ * Reference: IHO S-101 Portrayal Catalogue (Ed 1.1.0)
3874
4205
  */
3875
4206
 
3876
- interface MapboxStyle {
3877
- version: 8;
3878
- name: string;
3879
- metadata: Record<string, unknown>;
3880
- sources: Record<string, unknown>;
3881
- sprite?: string;
3882
- glyphs: string;
3883
- layers: StyleLayer[];
3884
- }
3885
- interface StyleGeneratorOptions extends BuildSourcesOptions {
3886
- /**
3887
- * When set, emit a "paper chart" style centered on the selected scale
3888
- * band, with graceful coverage fallback: the requested band's layers
3889
- * draw on top where its cells have data; coarser bands (driven by
3890
- * `CHART_CASCADE_CODES`) fill gaps where the requested band has no
3891
- * cells. Per-feature SCAMIN minzooms are preserved on every cascade
3892
- * variant; `maxzoom` is dropped so the user can zoom in indefinitely.
3893
- *
3894
- * When omitted (default), continuous mode emits one layer copy per
3895
- * source band with strict per-band zoom windows so exactly one band
3896
- * renders at any given continuous zoom.
3897
- *
3898
- * `lockedBounds` (optional) restricts chart-mode rendering to a
3899
- * specific bbox via a `["within"]` filter — applied ONLY to the
3900
- * requested band's variants. Cascade fallback bands render across the
3901
- * full viewport so a chart-Harbor user panning offshore still sees
3902
- * Approach/Coastal data. The frontend pairs this with a translucent
3903
- * grey overlay outside the bbox so the lock boundary is still visible
3904
- * to the user (the fallback data is dimmed by the overlay but not
3905
- * filtered out).
3906
- */
3907
- chartMode?: {
3908
- band: ScaleBand$1;
3909
- lockedBounds?: BBox;
3910
- };
3911
- /**
3912
- * Display unit for depth labels (SOUNDG soundings, DEPCNT contour
3913
- * values). Source data is always meters (S-57 / VALSOU / VALDCO);
3914
- * "feet" wraps the text-field expression with a unit conversion +
3915
- * round so the rendered numbers match. Default "meters".
3916
- */
3917
- depthUnits?: "meters" | "feet";
3918
- /**
3919
- * Which symbol artwork the style references. "s52" (default) uses the
3920
- * PresLib-derived S-52 set; "s101" rewrites icon references to the
3921
- * official S-101 Portrayal Catalogue symbols (s101: image namespace)
3922
- * where a counterpart exists — see s101-style.ts. "chrt" applies the
3923
- * CHRT house ATON/LIGHTS glyphs with colour-true tinting — see
3924
- * chrt-style.ts. Selection logic differences are documented per module.
3925
- */
3926
- symbolSet?: "s52" | "s101" | "chrt" | "raster";
3927
- /**
3928
- * The mariner's safety contour in metres — the depth below which a
3929
- * feature is "dangerous to my hull". Drives both safety-contour
3930
- * conditional symbology rules:
3931
- *
3932
- * - §SOUNDG02 — soundings shoaler than this render in the
3933
- * high-contrast color; deeper soundings render in the muted color.
3934
- * - §CSYM02 — isolated dangers (rocks / wrecks / obstructions)
3935
- * shoaler than this get the DNGCIR01 danger ring.
3936
- *
3937
- * Default `DEFAULT_SAFETY_CONTOUR_M` (10 m).
3938
- *
3939
- * Does not affect the DEPARE depth-area shading, whose 2/5/10 m bands
3940
- * are a separate S-52 concept.
3941
- */
3942
- safetyContour?: number;
3943
- /**
3944
- * When false, all soundings render in a single shade. Default true.
3945
- */
3946
- twoShadeSoundings?: boolean;
3947
- /**
3948
- * Label model for the 9 orientation place-name classes (SEAARE, LNDRGN,
3949
- * LAKARE, RIVERS, CANALS, BUAARE, AIRARE, LNDARE, PRDARE).
3950
- *
3951
- * "legacy" (default) — the per-cell-band orientation fan
3952
- * (fanOrientationLabelByCellBand): one label per feature per band,
3953
- * deconflicted only by `_label_anchor` tags and watermark opacity.
3954
- *
3955
- * "placename" — 2 PLACENAME symbol layers over the build-side
3956
- * place-name index (CHRTbaremetal placeNameIndex.ts): one collision-
3957
- * managed label per named place with a per-feature zoom window; the
3958
- * orientation fan for those classes is NOT emitted. Requires tiles
3959
- * built with the PLACENAME side index — on older tiles the layers
3960
- * simply draw nothing (soft-fail is index-side), which is why the
3961
- * default stays "legacy" until L2c's rebuild + Maine-coast visual
3962
- * verification flips it (docs/design/chart-label-deconfliction.md
3963
- * §2.3). Feature-label classes are untouched either way.
3964
- */
3965
- labels?: "legacy" | "placename";
3966
- /**
3967
- * Font weight for SOUNDG labels. Default "semibold" — crisper numerals
3968
- * at small sizes on screen.
3969
- */
3970
- soundingFontWeight?: "regular" | "semibold";
3971
- }
3972
- declare function generateStyle(palette?: ColorTableName, opts?: StyleGeneratorOptions & {
3973
- bandFan?: BandFanMode;
3974
- }): MapboxStyle;
3975
- declare function generateStyleJSON(palette?: ColorTableName, opts?: StyleGeneratorOptions): string;
3976
- /** Get summary stats about the generated style */
3977
- declare function getStyleStats(style: MapboxStyle): {
3978
- totalLayers: number;
3979
- fillLayers: number;
3980
- lineLayers: number;
3981
- symbolLayers: number;
3982
- circleLayers: number;
3983
- sources: number;
3984
- objectClasses: Set<string>;
3985
- };
4207
+ declare const S101_NEW_SYMBOLS: NauticalSymbol[];
3986
4208
 
3987
4209
  declare const S101_SOURCE_COMMIT = "05063500e898544c90078ab78a61efc72b7e62f3";
3988
4210
 
@@ -4693,7 +4915,11 @@ declare const DEFAULT_CUSTOM_STYLE_CONFIG: CustomStyleConfig;
4693
4915
  * fields fall back to the platform defaults. Never throws.
4694
4916
  */
4695
4917
  declare function sanitizeCustomStyleConfig(raw: unknown): CustomStyleConfig;
4696
- /** Normalize a `?theme=` value to a colour table name; null for anything else. */
4918
+ /**
4919
+ * Normalize a `?theme=` value to a colour table name; null for anything else.
4920
+ * Case-insensitive; `-` is accepted for `_` (`eink-mono` = `eink_mono`), so
4921
+ * the e-ink names survive either spelling a caller guesses.
4922
+ */
4697
4923
  declare function normalizeTheme(raw: string | null | undefined): ColorTableName | null;
4698
4924
  interface ResolvedTheme {
4699
4925
  palette: ColorTableName;
@@ -5061,7 +5287,8 @@ declare class OfflineError extends Error {
5061
5287
 
5062
5288
  /** Options for `chrt.style()`. Extends the atlas style generator options. */
5063
5289
  interface ChrtStyleOptions extends StyleGeneratorOptions {
5064
- /** S-52 colour palette: "DAY" (default), "DUSK", or "NIGHT". */
5290
+ /** Colour palette: "DAY" (default), "DUSK", "NIGHT", or the e-paper
5291
+ * palettes "EINK_COLOR" / "EINK_MONO" / "EINK_NIGHT" (see `EINK_COLOR_TABLES`). */
5065
5292
  palette?: ColorTableName;
5066
5293
  }
5067
5294
  declare class Chrt {
@@ -5350,4 +5577,4 @@ declare class Chrt {
5350
5577
  warmTiles(region?: string): Promise<TileTokenResponse>;
5351
5578
  }
5352
5579
 
5353
- 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_CODE_ALIASES, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, BASE_ATTRIBUTION, BASE_BUNDLE_ATTRIBUTION, BASE_BUNDLE_ID, BASE_BUNDLE_MAXZOOM, BASE_BUNDLE_SCHEMA, BASE_BUNDLE_SOURCE_LAYERS, BASE_CLIENT_RASTER_BEFORE_ID, BASE_MAXZOOM, BASE_MINZOOM, BASE_REGION, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, BUILD_PARAM, 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, DEPARE_FILL_BRANCH_TOKENS, DEPARE_FILL_FALLBACK_TOKEN, DRAPEABLE_LAYER_TYPES, 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, LAMP_CORE_LAYER_ID, LAMP_GLOW_LAYER_ID, LAMP_LAYER_IDS, LAMP_TICK_MS, LAND_ATTRIBUTION, LAND_BUILD_MAXZOOM, LAND_REGION, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LampLayer, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, type MarineAlert, type MarineAlertsOptions, type MarineAlertsResponse, type MarineConditionsObservation, type MarineConditionsOptions, type MarineConditionsResponse, type MarineConditionsSeries, type MarineForecastOptions, type MarineForecastPeriod, type MarineForecastResponse, type MarineSeas, type MarineStation, type MarineSwellTrain, type MarineWind, type MarineZoneRef, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NIGHT_INK, NIGHT_MUTED_INK, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type NearbyMarineStation, OPAQUE_SPLIT_OF, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, PLACENAME_LAND_CLASSES, PLACENAME_SOURCE_LAYER, PLACENAME_WATER_CLASSES, type ParsedSigseqTerms, type PmtilesVectorSource, type Poi, type PoiCoverage, type PoiDataset, type PoiOptions, type PoiResponse, type QueryFeatureProperties, type QueryOptions, type QueryResponse, RASTER_ATON_CLASSES, RASTER_GLYPH_NAMES, RASTER_ICON_PREFIX, RELIEF_ATTRIBUTION, RELIEF_ENCODING, RELIEF_REGION, RELIEF_TILE_SIZE, type ResolvedStyle, type ResolvedTheme, type S101ArtworkProvenance, type S101Feature, S101_AREA_FILL_NAMES, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_LINE_STYLE_NAMES, S101_NEW_SYMBOLS, S101_NO_OFFICIAL_COUNTERPART, 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, SIGPER_TOLERANCE_SECONDS, SIGSEQ_MAX_TERMS, SOUNDING_LAYERS, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SearchCoverageNotice, type SearchHit, type SearchOptions, type SearchResponse, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SigseqRefusal, type SigseqTerm, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, 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, WeatherEndpoint, type WeatherReading, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyBaseBundle, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyRasterSymbolSet, applyS101SymbolSet, atonIconExpression, bandFilterExpression, buildAirportTierExpression, buildBuiltUpAreaTierExpression, buildFlashPattern, buildLabelLayer, buildLakeTierExpression, buildLampLayers, buildLandAreaTierExpression, buildLandRegionTierExpression, buildLayersForClass, buildOfflineStyle, buildPlaceNameLayers, buildRiverTierExpression, buildSeaAreaTierExpression, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, capLuminance, cellBandCharacters, cellBandMatch, chrtAtonTintExpression, chrtHouseColor, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, contrastRatio, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, depthAreaClassLayerId, depthAreaSplitIds, deriveMarinerName, drapeHalves, drapedFirst, 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, isDepthAreaClassLayerId, isDrapeable, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lampRhythmColour, lampRhythmOpacity, lateralColor, lateralFillExpression, lightColor, lightFillExpression, lightsIconExpression, normalizeStyleMode, normalizeTheme, opaqueSplitBaseOf, paginate, parseListCodes, parseSigseqTerms, parseWebhook, patternSpriteId, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, rasterAtonIconExpression, relativeLuminance, resolveCustomTheme, resolveRasterImage, resolveS101Color, resolveStyle, resolveTargetClass, rhythmProperties, s101ArtworkProvenance, sanitizeCustomStyleConfig, scaleLinearLight, scaleSizeValue, scaminToMinZoom, sectorLimitToArcBearing, splitOpaqueDepthAreas, styleModeBand, terrainStackCount, themeChrtGlyph, tileTemplate, uncoveredSectorArcs, withinBboxFilter, worldMinusBboxPolygon };
5580
+ 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_CODE_ALIASES, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, BASE_ATTRIBUTION, BASE_BUNDLE_ATTRIBUTION, BASE_BUNDLE_ID, BASE_BUNDLE_MAXZOOM, BASE_BUNDLE_SCHEMA, BASE_BUNDLE_SOURCE_LAYERS, BASE_CLIENT_RASTER_BEFORE_ID, BASE_MAXZOOM, BASE_MINZOOM, BASE_REGION, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, BUILD_PARAM, 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_DEPTH_LABEL_SIZE, 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, DEPARE_FILL_BRANCH_TOKENS, DEPARE_FILL_FALLBACK_TOKEN, DEPTH_LABEL_CLASSES, DEPTH_LABEL_SIZES, DRAPEABLE_LAYER_TYPES, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DepthLabelSize, type DisplayCategory, type DisplayMode, EINK_DANGER_PATTERN, EINK_DOTS_PATTERN, EINK_HATCH_PATTERN, 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, LAMP_CORE_LAYER_ID, LAMP_GLOW_LAYER_ID, LAMP_LAYER_IDS, LAMP_TICK_MS, LAND_ATTRIBUTION, LAND_BUILD_MAXZOOM, LAND_REGION, LARGE_SOUNDING_PADDING_PX, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LampLayer, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, type MarineAlert, type MarineAlertsOptions, type MarineAlertsResponse, type MarineConditionsObservation, type MarineConditionsOptions, type MarineConditionsResponse, type MarineConditionsSeries, type MarineForecastOptions, type MarineForecastPeriod, type MarineForecastResponse, type MarineSeas, type MarineStation, type MarineSwellTrain, type MarineWind, type MarineZoneRef, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NIGHT_INK, NIGHT_MUTED_INK, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type NearbyMarineStation, OPAQUE_SPLIT_OF, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, PLACENAME_LAND_CLASSES, PLACENAME_SOURCE_LAYER, PLACENAME_WATER_CLASSES, type ParsedSigseqTerms, type PmtilesVectorSource, type Poi, type PoiCoverage, type PoiDataset, type PoiOptions, type PoiResponse, type QueryFeatureProperties, type QueryOptions, type QueryResponse, RASTER_ATON_CLASSES, RASTER_GLYPH_NAMES, RASTER_ICON_PREFIX, RELIEF_ATTRIBUTION, RELIEF_ENCODING, RELIEF_REGION, RELIEF_TILE_SIZE, type ResolvedStyle, type ResolvedTheme, type S101ArtworkProvenance, type S101Feature, S101_AREA_FILL_NAMES, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_LINE_STYLE_NAMES, S101_NEW_SYMBOLS, S101_NO_OFFICIAL_COUNTERPART, 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, SIGPER_TOLERANCE_SECONDS, SIGSEQ_MAX_TERMS, SOUNDING_LAYERS, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SearchCoverageNotice, type SearchHit, type SearchOptions, type SearchResponse, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SigseqRefusal, type SigseqTerm, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, 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, WeatherEndpoint, type WeatherReading, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyBaseBundle, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyDepthLabelSize, applyEinkPortrayal, applyRasterSymbolSet, applyS101SymbolSet, atonIconExpression, bandFilterExpression, buildAirportTierExpression, buildBuiltUpAreaTierExpression, buildFlashPattern, buildLabelLayer, buildLakeTierExpression, buildLampLayers, buildLandAreaTierExpression, buildLandRegionTierExpression, buildLayersForClass, buildOfflineStyle, buildPlaceNameLayers, buildRiverTierExpression, buildSeaAreaTierExpression, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, capLuminance, cellBandCharacters, cellBandMatch, chrtAtonTintExpression, chrtHouseColor, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, contrastRatio, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, depthAreaClassLayerId, depthAreaSplitIds, deriveMarinerName, drapeHalves, drapedFirst, 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, isDepthAreaClassLayerId, isDrapeable, isLabelLayerVisible, isMapLive, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lampRhythmColour, lampRhythmOpacity, lateralColor, lateralFillExpression, lightColor, lightFillExpression, lightsIconExpression, normalizeStyleMode, normalizeTheme, opaqueSplitBaseOf, paginate, parseDepthLabelSize, parseListCodes, parseSigseqTerms, parseWebhook, patternSpriteId, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, rasterAtonIconExpression, relativeLuminance, resolveCustomTheme, resolveRasterImage, resolveS101Color, resolveStyle, resolveTargetClass, rhythmProperties, s101ArtworkProvenance, safetyContourValues, sanitizeCustomStyleConfig, scaleLinearLight, scaleSizeValue, scaminToMinZoom, sectorLimitToArcBearing, splitOpaqueDepthAreas, styleModeBand, terrainStackCount, themeChrtGlyph, tileTemplate, uncoveredSectorArcs, withinBboxFilter, worldMinusBboxPolygon };