@tileflow/core 0.1.0-alpha.23 → 0.1.0-alpha.25

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/README.md CHANGED
@@ -1,1282 +1,219 @@
1
1
  # @tileflow/core
2
2
 
3
- Typed Tileflow map contracts, semantic modules, and deterministic MapLibre style compilation.
3
+ Define, validate, and compile Tileflow maps into MapLibre Style JSON. Core owns the map language;
4
+ it does not start a server, prepare files on disk, render a browser map, or deploy a hosted map.
4
5
 
5
- Every public `tileflow.config.ts` exports one map. Most maps import an existing map and override only
6
- the design fields they own:
6
+ > Related packages and guides: [documentation index](https://raw.githubusercontent.com/tileflow/tileflow-sdk/main/llms.txt).
7
7
 
8
- ```ts
9
- import {
10
- defineMap,
11
- defineTheme,
12
- fixed,
13
- labels,
14
- poi,
15
- roads,
16
- token,
17
- water,
18
- zoom,
19
- } from '@tileflow/core';
20
- import {streets, streetsThemes} from '@tileflow/maps';
21
-
22
- const madridDark = defineTheme(streetsThemes.dark, {
23
- id: 'madrid-dark',
24
- version: 1,
25
- colorScheme: 'dark',
26
- tokens: {color: {'surface.land': '#0d1320', 'surface.water': '#081e2e'}},
27
- });
8
+ ## Install
28
9
 
29
- export default defineMap({
30
- id: 'madrid',
31
- name: 'Madrid',
32
- version: 1,
33
- extends: streets,
34
- themes: {light: streetsThemes.light, dark: madridDark},
35
- defaultTheme: 'light',
36
- systemThemes: {light: 'light', dark: 'dark'},
37
- projection: 'globe',
38
- modules: {
39
- water: water({
40
- bodies: {
41
- fill: {
42
- opacity: fixed(0.95, {reason: 'Madrid keeps this water density in every theme'}),
43
- },
44
- },
45
- }),
46
- roads: roads({
47
- detail: 'streets',
48
- hierarchy: 'strong',
49
- classes: {
50
- primary: {
51
- surface: {
52
- fill: {
53
- color: fixed('#E4A85B', {reason: 'Madrid brand roads stay warm in every theme'}),
54
- width: zoom.linear([
55
- [7, fixed(0.6, {reason: 'Madrid keeps its primary-road hierarchy across themes'})],
56
- [16, fixed(8, {reason: 'Madrid keeps its primary-road hierarchy across themes'})],
57
- ]),
58
- },
59
- },
60
- },
61
- secondary: {surface: {fill: {color: token.color('roads.secondary')}}},
62
- },
63
- }),
64
- labels: labels({
65
- language: 'local',
66
- places: 'all',
67
- roads: 'streets',
68
- styles: {
69
- places: {
70
- city: {
71
- text: {
72
- size: fixed(18, {reason: 'Madrid keeps its city-label scale across themes'}),
73
- haloWidth: fixed(1.5, {reason: 'Madrid keeps its city-label halo across themes'}),
74
- },
75
- },
76
- },
77
- },
78
- }),
79
- poi: poi({categories: ['food-drink', 'arts-entertainment', 'transport'], color: 'category'}),
80
- },
81
- view: {center: [-3.7038, 40.4168], pitch: 35, zoom: 12},
82
- });
10
+ ```sh
11
+ npm install @tileflow/core@alpha @tileflow/maps@alpha
83
12
  ```
84
13
 
85
- Map identity and scenes belong to the leaf and do not inherit. Hosted browser policy is configured
86
- in Tileflow and does not enter cartographic authoring or compilation.
14
+ `@tileflow/maps` is needed for the official map used below, not for every Core application. Use
15
+ ES module imports. For config loading and asset preparation, use the Node.js 22+ CLI or a build
16
+ integration. Browser applications should use a framework adapter with prepared assets.
87
17
 
88
- Set `projection: 'globe'` for MapLibre's adaptive globe preset. Global zooms render as a sphere,
89
- then transition to Mercator between zoom 10 and 12 so detailed streets remain planar. Omit the
90
- property, or set it to `'mercator'`, for a consistently flat map.
18
+ ## Define a map
91
19
 
92
- Terrain keeps the original `'none' | 'hillshade' | '3d'` shorthand. The object form can style every
93
- MapLibre hillshade paint control, or generate vector contours in the browser from an explicit DEM
94
- tile template. Contours require `demUrl`, `demMaxZoom`, and zoom-indexed `[minor, index]`
95
- `thresholds`; the raster `url` remains the separate TileJSON endpoint. Set `mode: 'none'` to compile
96
- contours without adding a raster source, hillshade, or 3D terrain:
97
-
98
- ```ts
99
- terrain: {
100
- mode: 'none',
101
- encoding: 'terrarium',
102
- contours: {
103
- demUrl: 'https://terrain.example.test/{z}/{x}/{y}.webp',
104
- demMaxZoom: 13,
105
- maxZoom: 15,
106
- overzoom: 2,
107
- thresholds: {
108
- 9: [100, 500],
109
- 11: [50, 250],
110
- 13: [20, 100],
111
- 15: [10, 50],
112
- },
113
- minor: {
114
- color: token.color('terrain.contour.minor'),
115
- width: token.number('terrain.contour.minor-width'),
116
- },
117
- index: {
118
- color: token.color('terrain.contour.index'),
119
- width: token.number('terrain.contour.index-width'),
120
- },
121
- labels: {
122
- color: token.color('terrain.contour.label'),
123
- haloColor: token.color('terrain.contour.halo'),
124
- },
125
- },
126
- }
127
- ```
20
+ Create `tileflow.config.ts`. Every public config exports one map:
128
21
 
129
- Declare those `terrain.*` color and number roles with the same keys in every map theme. The
130
- compiled source is ordinary MapLibre vector data with source layer `contours`; its tile URL
131
- contains the safely encoded DEM template and complete generation parameters. Register the generic
132
- protocol before MapLibre reads the style. It lazily initializes the pinned, locally bundled
133
- `maplibre-contour@0.1.0` browser module on the first contour tile; no public CDN runtime is needed.
134
- `overzoom` may not exceed the lowest threshold zoom, so generated DEM requests never use a negative
135
- zoom. Every index interval must be a whole multiple of its minor interval. Contour labels emit the
136
- scaled numeric elevation without assuming a display unit. Source and layer visibility cannot begin
137
- before the first configured threshold zoom. To bound main-thread work, effective minor intervals
138
- must be at least 250 units at z0–4, 100 at z5–7, 50 at z8–10, 20 at z11–12, and 10 from z13 onward.
139
- Use a trusted, size-bounded DEM tile service; the protocol intentionally accepts author-supplied
140
- HTTP(S) templates rather than proxying or republishing terrain data.
22
+ <!-- docs:check -->
141
23
 
142
24
  ```ts
143
- import {registerTileflowContourProtocol} from '@tileflow/core/browser';
144
-
145
- registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
146
- ```
147
-
148
- ## Authoring model
149
-
150
- Tileflow exposes one authoring concept and one constructor: `defineMap()`. A complete map omits
151
- `extends`; an inherited map sets `extends` to another imported map object. All ten first-party
152
- maps are independent standalone maps. They use the sole semantic compiler while defining
153
- their complete designs and asset providers directly; no official map imports or extends another
154
- official map. Applications can extend any of those maps through the same public API. Streets
155
- itself owns coordinated light and dark themes.
156
- There is no public compiler selector or alternate compiler. The authoring contract has no recipe or
157
- cartographic compatibility alias.
158
-
159
- ### Machine-readable V1 surface
160
-
161
- `tileflowAuthoringManifest` is the deterministic, deeply frozen description of the public semantic
162
- language. Its schema version is `tileflowAuthoringManifestSchemaVersion`. The manifest's `domains`
163
- array is generated from the compiler's closed registry and records exact compile order, module and
164
- service dependencies, and provided services. `operations` lists only integrated public operations:
165
- defining/extending a map, directly replacing, refining, disabling, or resetting a keyed domain,
166
- and adding owner-local render passes or target refinements. Direct declaration is the sole
167
- replacement syntax; there is no redundant `set()` helper.
168
-
169
- Agents and other tools can discover this contract without importing executable configuration:
170
- `tileflow language manifest --json` emits the manifest and `tileflow language schema --json` emits
171
- the packaged generated authoring/resolved JSON Schema. Both commands are deterministic, read-only,
172
- and network-free. The manifest links every domain to its authoring, options, patch, and resolved
173
- schema definitions, and enumerates the closed expression and render-selector vocabulary with its
174
- finite limits.
175
-
176
- The manifest also describes the exact structured compiler contract. A successful
177
- `createStyleResult()` has `{ok: true, diagnostics, report, style}`; a failure has
178
- `{ok: false, diagnostics, report}` and forbids `style`. Report schema version 1 requires
179
- `domains`, `map`, `planner`, `schemaVersion`, and sorted semantic `targets`; `theme`, source
180
- `requirements`, and physical `provenance` are optional. Pass `{inspection: true}` only when the
181
- read-only physical-output provenance sidecar is needed. Its IDs and indexes are diagnostic
182
- observations, never stable or addressable authoring targets.
183
-
184
- `diffTileflowMaps(before, after)` resolves both inputs before comparing them and returns semantic
185
- diff schema version 1:
186
-
187
- ```ts
188
- {
189
- schemaVersion: 1,
190
- from: {id, version},
191
- to: {id, version},
192
- summary: {add, remove, change, total},
193
- changes: [
194
- {kind: 'add', path: '/projection', after: 'globe'},
195
- {kind: 'remove', path: '/modules/water', before: {/* ... */}},
196
- {kind: 'change', path: '/view/zoom', before: 12, after: 13},
197
- ],
198
- }
199
- ```
200
-
201
- Paths are RFC 6901 JSON Pointers. Object keys compare recursively in code-unit order; arrays compare
202
- atomically because map inheritance replaces arrays. Leaf identity (`id`, `name`, `version`) and
203
- delivery metadata identify the endpoints but are not cartographic changes.
204
-
205
- Custom build tooling can opt into a separate, read-only diagnostic sidecar from the explicit build
206
- entrypoint:
207
-
208
- ```ts
209
- import {createStyleWithInspection} from '@tileflow/core/build';
210
-
211
- const {style, inspection} = createStyleWithInspection(map);
212
- ```
213
-
214
- `inspection.layers` stays aligned with the final Style layer order and records the semantic
215
- `owner`, `slot`, `target`, and ordered render passes/refinements represented by each layer, including
216
- layers merged by the physical planner. `createStyleFromCatalogWithInspection` and
217
- `createStylesFromCatalogWithInspection` provide the corresponding catalog-oriented forms. The
218
- ordinary Style bytes are identical to compilation without inspection; private compiler metadata is
219
- stripped before finalization, and the sidecar never enters a Style, runtime manifest, or production
220
- artifact unless a caller deliberately stores it.
221
-
222
- Resolution happens before validation, asset preparation, compilation, capture, build, or deploy.
223
- Only `view` deep-merges (its nested arrays and expressions still replace). The `themes` collection
224
- replaces atomically when declared and clears an inherited `systemThemes` mapping; omission inherits
225
- the collection. `defaultTheme` may independently choose one inherited concrete theme, while an
226
- explicit `systemThemes` replaces the complete light/dark mapping. Every resolved selector must name
227
- a theme in the final collection.
228
- `modules` merges by domain name: an omitted domain remains inherited, while declaring `roads(...)`
229
- or another domain replaces that inherited module request and every compiler-owned contribution
230
- attached to it as a unit. `data`, `projection`, `terrain`, `marine`, `icons`, and the text provider
231
- are atomic; identity and tooling metadata are leaf-owned. `disable()` removes the domain and
232
- its complete inherited render stack. The resolved result is a standalone map with no runtime dependency on
233
- TypeScript imports.
234
-
235
- `icons` is an intentional exception to implicit array composition: omission inherits the exact
236
- parent array, any declaration replaces it atomically, and `[]` disables map icons. Compose with a
237
- spread when the child should keep a parent's directories. `fonts` and `glyphs` are mutually
238
- exclusive text providers; declaring either atomically replaces an inherited provider of either
239
- kind. Unknown keys and former compatibility shapes are rejected.
240
-
241
- Most authors should extend an existing official or application map. A complete standalone map uses
242
- the same `defineMap()` call without `extends`:
243
-
244
- ```ts
245
- import {defineMap} from '@tileflow/core';
246
- import {streetsIcons, streetsThemes} from '@tileflow/maps';
247
-
248
- const companyBase = defineMap({
249
- id: 'company-base',
250
- name: 'Company base',
251
- version: 1,
252
- glyphs: {
253
- kind: 'url',
254
- url: 'https://api.tileflow.dev/fonts/{fontstack}/{range}.pbf',
255
- fontStacks: ['Noto Sans Regular', 'Noto Sans Bold'],
256
- },
257
- icons: [streetsIcons],
258
- themes: {light: streetsThemes.light, dark: streetsThemes.dark},
259
- defaultTheme: 'light',
260
- systemThemes: {light: 'light', dark: 'dark'},
261
- });
25
+ import {defineMap, labels, roads} from '@tileflow/core';
26
+ import {streets} from '@tileflow/maps';
262
27
 
263
28
  export default defineMap({
264
- id: 'company-navigation',
29
+ id: 'madrid',
30
+ name: 'Madrid',
265
31
  version: 1,
266
- extends: companyBase,
267
- projection: 'globe',
268
- });
269
- ```
270
-
271
- The standalone map above is complete as written. A map always owns its complete text provider; Core does not
272
- obtain fonts or sprites from World and never invents a fallback URL. The URL-backed first-party
273
- `streets`, `baedeker`, `ferraris`, `harad`, `soundings`, `verdant`, and `sanFrancisto` maps declare their glyph
274
- providers directly, while `siegfried` declares packaged fonts, so ordinary imports and derived
275
- maps compile without out-of-band release metadata.
276
-
277
- ```mermaid
278
- flowchart LR
279
- A["tileflow.config.ts exports one map"] --> B["Resolve imported map lineage"]
280
- B --> C["Complete semantic design + closed domain registry"]
281
- C --> D["Domain IR + assembly + render stacks"]
282
- D --> E["Physical planner"]
283
- E --> F["Single MapLibre lowering"]
284
- F --> G["Validated Style JSON + compilation report"]
285
- ```
286
-
287
- Modules are keyed by domain, so object order never controls rendering and a domain can appear only
288
- once. A map extending Streets inherits its complete module set. Use `disable()` to remove a domain
289
- deliberately. The public domains are `land`, `water`, `nautical`, `roads`, `buildings`, `boundaries`,
290
- `labels`, `poi`, `aeroways`, `transit`, `vegetation`, `addresses`, and `landforms`.
291
- The closed domain registry is exhaustive over that domain set and owns defaults, dependency order,
292
- services, and compiler orchestration. Contract tests keep its keys and type tags in lockstep with
293
- the strict resolved schema, generated authoring/options/patch/resolved schema aliases, and public
294
- exports. Disabling `roads` also removes dependent road names, shields, and junction references;
295
- independent place, water, aerodrome, and POI labels remain under the labels module.
296
-
297
- Every styling module supports semantic shortcuts and exact semantic targets. Visual style values
298
- accept typed token refs, documented `fixed(value, {reason})` values, closed data expressions through
299
- `expr.*` plus `field(...)`, and zoom functions through `zoom.step(...)`, `zoom.linear(...)`, or
300
- `zoom.exponential(...)`. Refs and fixed leaves also work inside typed expressions and zoom stops.
301
- Structural controls such as presets, visibility, and zoom gates remain ordinary literals. Exact
302
- controls address stable concepts such as `roads.classes.primary.surface.fill`, not renderer layer
303
- IDs.
304
-
305
- The `land` module exposes stable land-use targets for `cemetery`, `civic`, `commercial`,
306
- `education`, `government`, `industrial`, `medical`, `military`, `parking`, `railway`,
307
- `recreation`, and `residential`. Its land-cover taxonomy distinguishes physical cover from authored
308
- urban green: `farmland`, `flowerbed`, `grass`, `ice`, `meadow`, `protected`,
309
- `recreationGround`, `rock`, `sand`, `scrub`, `urbanPark`, `villageGreen`, `wetland`, and
310
- `wood`. The plain `grass` branch excludes every typed grass subclass, so one source feature cannot
311
- receive two opaque green fills. Parking areas are polygons from the configured land-use source;
312
- parking access aisles remain road features under `roads.serviceTypes.parkingAisle`. The
313
- `commercial` target recognizes ordinary `commercial` and `retail` values plus Tileflow's derived
314
- `business_area` ground class. Its `globalLandcover` fill styles the optional low-zoom global
315
- land-cover extension without a raw layer patch. The `water.bathymetry` fill does the same for the
316
- typed Tileflow World V1 depth bands. An explicit `water({bathymetryContours: {}})` traces the
317
- submerged edges between those discrete polygon bands; it is an approximate visual contour, not a
318
- surveyed isoline. `water({bathymetryLabels: {}})` separately opts into numeric metre values derived
319
- from each polygon's absolute minimum band depth. Existing maps gain neither detail by default, and
320
- sources without bathymetry bindings omit both requested layers. These labels describe coarse band
321
- floors, not measured survey soundings, and must not be presented as hydrographic sounding data.
322
-
323
- `addresses` renders the standard OpenMapTiles `housenumber` points at detailed zooms and exposes a
324
- single semantic `labels` style. `landforms` renders the standard `mountain_peak` classes (`peak`,
325
- `volcano`, `saddle`, `ridge`, `cliff`, and `arete`) with rank-aware collision priority and optional
326
- metric elevation. Both source-layer and field names remain remappable through `openMapTiles(...)`;
327
- landform names share the language selected by `labels({language: ...})`.
328
-
329
- `aeroways.runwayRef` owns high-zoom runway designators from the remappable OpenMapTiles `ref`
330
- field. Aerodrome names stay under `labels.styles.aerodrome`; `labels.aerodromeCodes` controls whether
331
- they append no code, IATA only, or IATA with an ICAO fallback (`'none' | 'iata' | 'all'`).
332
-
333
- The `vegetation` module binds individual-tree points from the optional OpenMapTiles `tree`
334
- extension. Its default `mode: '3d'` emits a portable circle fallback plus metadata for runtimes
335
- that can upgrade the points to instanced 3D trees. `mode: 'flat'` keeps the MapLibre circles, and
336
- `flat` exposes the complete `CircleStyle` fallback. `threeDimensional` controls bark color,
337
- broadleaf and conifer palettes, and independent height and crown scales. The legacy `minZoom`
338
- shortcut remains available; `flat.minZoom` takes precedence when both are present. The binding
339
- includes height, crown diameter, genus, leaf type, and species fields so compatible runtimes can
340
- preserve source measurements and botanical form without hard-coding raw property names. The
341
- pitched-scene stack follows physical height: pedestrian and transport surfaces, transport markings
342
- and road names, buildings, then vegetation. Place, water, aerodrome, and POI annotations remain
343
- last so geographic names stay readable without making street paint or text float over 3D geometry.
344
-
345
- ## Shared visual primitives
346
-
347
- The same small visual language is reused across every geographic domain. This keeps an agent from
348
- having to learn different spellings for the same MapLibre behavior:
349
-
350
- - `BackgroundStyle`: color, opacity, pattern, visibility, and zoom range.
351
- - `FillStyle`: color, opacity, pattern, antialiasing, visibility, and zoom range.
352
- - `LineStyle`: color, width, opacity, dash, blur, gap, offset, pattern, caps, joins, and zoom range.
353
- - `LineHatchStyle`: repeated diagonal detail with color, opacity, spacing, size, angle, and zoom
354
- range for a road structure.
355
- - `TextStyle`: field, exact font face, exact fallback faces, size, color, halo, spacing metrics, transform,
356
- collision policy, rotation, fixed/variable anchoring, line constraints, and zoom range.
357
- - `IconStyle`: image, size, color, halo, rotation/alignment, collision policy, and zoom range.
358
- - `CircleStyle`: radius, fill/stroke appearance, blur, opacity, pitch behavior, and zoom range.
359
- - `ExtrusionStyle`: color, height, base, opacity, pattern, vertical gradient, and zoom range.
360
-
361
- Compounds express common cartographic structures: `AreaStyle` has `fill` and `outline`,
362
- `LineStackStyle` has `shadow`, `casing`, and `fill`, and `SymbolStyle` combines placement with
363
- optional `text`, `icon`, and `marker`. Geographic selection remains owned by semantic modules;
364
- ordinary visual styles intentionally do not accept raw source filters.
365
-
366
- ## Compiled-style performance
367
-
368
- The semantic compiler resolves semantic modules and their owned contributions first, then compacts equivalent
369
- physical MapLibre layers. At high-detail zooms, road classes become a small set of data-driven
370
- cohorts; equivalent road labels and tunnel hatches share compatible buckets; and land-cover,
371
- land-use, and waterway classes share compatible buckets. Compaction selects
372
- cohorts from compiler-owned semantic targets, not by parsing physical IDs; its temporary
373
- owner/slot/target provenance is stripped from the public Style JSON.
374
-
375
- After physical planning, Core embeds a bounded private `tileflow:interaction-manifest` lookup for the
376
- final POI representations. `@tileflow/interactions/maplibre` validates and consumes that metadata
377
- so applications can bind to `domain: 'poi'` without depending on physical layer IDs. The lookup is
378
- paired atomically with the exact style it describes; it is not a public style-authoring API.
379
-
380
- Use the exported structural sweep to enforce budgets without loading tiles or a browser:
381
-
382
- ```ts
383
- import {analyzeTileflowStylePerformance, createStyle} from '@tileflow/core';
384
- import map from './tileflow.config';
385
-
386
- const report = analyzeTileflowStylePerformance(createStyle(map));
387
- console.log(report.zooms[16]);
388
- ```
389
-
390
- The report covers z0–z22 by default and includes active layers, conservative bucket estimates,
391
- symbols, and active layers per source-layer. It is intended for regression gates; browser traces
392
- remain the source of truth for decode, upload, placement, and frame time.
393
-
394
- A `SymbolStyle` root zoom range governs its text/icon layer and is inherited by its marker layer.
395
- Because a marker is materialized as a separate circle layer, an explicit marker range may refine
396
- that inherited default. Text and icon ranges must agree because MapLibre renders them together.
397
-
398
- The road module distinguishes the path family semantically:
399
-
400
- ```ts
401
- roads({
402
- extras: {paths: true},
403
- crossings: {image: token.image('roads.crosswalk')},
404
- sidewalks: {
405
- surface: {color: token.color('roads.city.casing'), minZoom: 17},
406
- pattern: {
407
- pattern: token.image('roads.sidewalkPattern'),
408
- minZoom: 17,
409
- opacity: fixed(0.6, {reason: 'Sidewalk texture strength is invariant'}),
410
- },
411
- },
412
- roundabouts: {
413
- casing: {strokeColor: token.color('roads.default.casing')},
414
- fill: {strokeColor: token.color('roads.default')},
415
- },
416
- areas: {
417
- pedestrian: {
418
- fill: {color: token.color('surface.land')},
419
- outline: {
420
- color: token.color('roads.default.casing'),
421
- width: fixed(1, {reason: 'Pedestrian-area outline weight is invariant'}),
422
- },
423
- },
424
- },
425
- classes: {
426
- primary: {
427
- tunnel: {
428
- casing: {
429
- color: token.color('roads.city.tunnelCasing'),
430
- width: fixed(10, {reason: 'Primary-tunnel hierarchy is invariant'}),
431
- },
432
- fill: {
433
- color: token.color('roads.city.tunnel'),
434
- width: fixed(8, {reason: 'Primary-tunnel hierarchy is invariant'}),
435
- },
436
- hatch: {
437
- color: token.color('roads.city.tunnelCasing'),
438
- opacity: fixed(0.25, {reason: 'Tunnel hatch strength is invariant'}),
439
- spacing: fixed(10, {reason: 'Tunnel hatch rhythm is invariant'}),
440
- },
441
- },
442
- },
443
- pedestrian: {
444
- surface: {
445
- fill: {
446
- color: token.color('roads.path.transition'),
447
- width: fixed(6, {reason: 'Pedestrian-road hierarchy is invariant'}),
448
- },
449
- },
450
- },
451
- footway: {
452
- surface: {
453
- fill: {
454
- color: token.color('roads.path'),
455
- width: fixed(1.2, {reason: 'Path hierarchy is invariant'}),
456
- },
457
- },
458
- },
459
- cycleway: {
460
- surface: {
461
- fill: {
462
- color: token.color('roads.cycleway'),
463
- width: fixed(1.5, {reason: 'Cycleway hierarchy is invariant'}),
464
- },
465
- },
466
- },
467
- steps: {
468
- surface: {
469
- fill: {
470
- color: token.color('roads.path.casing'),
471
- width: fixed(1.4, {reason: 'Step hierarchy is invariant'}),
472
- },
473
- },
474
- },
475
- pathway: {
476
- surface: {
477
- fill: {
478
- color: token.color('roads.path'),
479
- width: fixed(1.2, {reason: 'Path hierarchy is invariant'}),
480
- },
481
- },
482
- },
483
- },
484
- modifiers: {
485
- expressway: {widthScale: 1.06},
486
- ramp: {widthScale: 0.7},
487
- unpaved: {surface: {fill: {color: token.color('roads.path')}}},
488
- construction: {
489
- surface: {
490
- fill: {opacity: fixed(0.7, {reason: 'Construction emphasis is invariant'})},
491
- },
492
- },
493
- indoor: {
494
- surface: {fill: {opacity: fixed(0.4, {reason: 'Indoor-road emphasis is invariant'})}},
495
- },
496
- },
497
- restrictions: {
498
- access: {
499
- surface: {fill: {opacity: fixed(0.55, {reason: 'Restricted-road emphasis is invariant'})}},
500
- },
501
- toll: {
502
- surface: {
503
- casing: {color: fixed('#C5B7D8', {reason: 'Toll ink is regulatory and invariant'})},
504
- },
505
- },
506
- },
507
- serviceTypes: {
508
- driveway: {widthScale: 0.75},
509
- parkingAisle: {widthScale: 0.6},
510
- },
511
- });
512
- ```
513
-
514
- These targets are non-overlapping translations of the OpenMapTiles road class and subclass. The
515
- same names are available under `labels().roadClasses` and `labels().styles.roads`. Surface, tunnel,
516
- and bridge phases can be controlled independently for each target; each structure has
517
- `shadow`/`casing`/`fill` and an optional `hatch`. Glyph hatch marks inherit the resolved fill width
518
- when `size` is omitted and never participate in label collision. Setting `hatch.pattern` instead
519
- emits a repeated sprite texture clipped to the resolved fill width. No raw source filter or generated
520
- layer ID is needed. `hatch.patternWidths` can list intrinsic sprite heights named
521
- `${pattern}-${width}`; the compiler selects the nearest height from the resolved road width so the
522
- texture's marks remain approximately constant in screen pixels across classes, ramps, and zooms.
523
-
524
- `classes.pedestrian` styles line-like pedestrian ways. Polygon pedestrian plazas are a distinct
525
- geometry and use `areas.pedestrian`, including optional fill opacity, outline color, or sprite
526
- pattern. This prevents plazas from degrading into a thin polygon outline.
527
-
528
- Road treatments refine a class without creating another class or exposing source fields.
529
- `construction`, `expressway`, `indoor`, `official`, `ramp`, and `unpaved` live under `modifiers`;
530
- general, bicycle, foot, horse, and toll restrictions live under `restrictions`; `alley`,
531
- `crossover`, `driveway`, `parkingAisle`, and `yard` live under `serviceTypes`; and exact
532
- mountain-bike difficulty values live under `mountainBike`. Treatments reuse
533
- surface/tunnel/bridge and casing/fill/shadow paint controls, plus a relative `widthScale`. The
534
- compiler emits feature-driven paint expressions inside the existing stable class layers rather
535
- than multiplying every possible combination.
536
-
537
- Road names, shields, and motorway junctions remain label concerns:
538
-
539
- ```ts
540
- labels({
541
- roads: 'all',
542
- shields: 'major',
543
- junctions: true,
544
- styles: {
545
- shields: {
546
- default: {
547
- icon: {
548
- image: token.image('roads.shield.rectangleNeutral'),
549
- optional: false,
550
- pitchAlignment: 'viewport',
551
- rotationAlignment: 'viewport',
552
- textFit: 'width',
553
- textFitPadding: [0, 4, 0, 4],
554
- },
555
- text: {
556
- color: token.color('labels.shieldDark'),
557
- font: token.font('places'),
558
- optional: false,
559
- pitchAlignment: 'viewport',
560
- rotationAlignment: 'viewport',
561
- },
562
- },
563
- overview: {minZoom: 6, maxZoom: 11, placement: 'point'},
564
- detail: {minZoom: 11, placement: 'line', spacing: 400},
565
- kinds: {
566
- 'rectangle-neutral': {image: token.image('roads.shield.rectangleNeutral')},
567
- 'rectangle-blue': {image: token.image('roads.shield.rectangleBlue')},
568
- },
569
- textColors: {
570
- dark: {color: token.color('labels.shieldDark')},
571
- light: {color: token.color('labels.shieldLight')},
572
- },
573
- },
574
- junctions: {
575
- text: {
576
- color: token.color('labels.strong'),
577
- haloColor: token.color('labels.halo'),
578
- haloWidth: fixed(2, {reason: 'Junction halo weight is invariant'}),
579
- },
580
- },
32
+ extends: streets,
33
+ modules: {
34
+ roads: roads({detail: 'streets', hierarchy: 'clear'}),
35
+ labels: labels({roads: 'major'}),
581
36
  },
37
+ view: {center: [-3.7038, 40.4168], zoom: 12},
582
38
  });
583
39
  ```
584
40
 
585
- `icon.textFit` and `icon.textFitPadding` let a symbol background grow with its text. Setting both
586
- icon and text `optional: false` keeps a route shield atomic during collision placement: neither an
587
- empty badge nor a detached reference can render alone. `overview` renders producer-selected points
588
- at low zoom and `detail` switches to line placement at street zooms; exclusive `maxZoom: 11` and
589
- inclusive `minZoom: 11` make that handoff atomic. Keeping both icon and text aligned to `viewport`
590
- makes the badge horizontal regardless of road bearing.
591
-
592
- `kinds` and `textColors` are closed presentation tables. The vector producer supplies semantic
593
- `shield_kind`, `shield_text_color`, `shield_text`, and `shield_rank` values; Core only matches them
594
- against the authored tables and falls back to `default`. Country, route-prefix, and network-shape
595
- rules therefore stay in the data authority instead of leaking into a map theme or multiplying
596
- runtime layers.
597
-
598
- Tileflow World owns POI classification, editorial zoom eligibility, and cross-source ranking. Its
599
- canonical `poi` layer exposes `category`, `type`, `icon`, `min_zoom`, `filter_rank`, and `size_rank`;
600
- Core validates that complete contract and never reconstructs those decisions from OpenMapTiles
601
- `class`, `subclass`, or `rank`. `filter_rank` is an
602
- integer from 0 through 5, with lower values reserved for the strongest candidates. The numeric
603
- `density` option is an inclusive threshold from 1 through 5 and defaults to 3. `size_rank` is an
604
- integer from 0 through 16 used to order eligible candidates before MapLibre performs final
605
- collision placement.
606
-
607
- The closed category vocabulary is `arts-entertainment`, `education`, `food-drink`, `landmark`,
608
- `lodging`, `medical`, `park-nature`, `public-services`, `religion`, `retail`, `sport-leisure`,
609
- `transport`, and `visitor-amenity`. Producer `type` and `icon` values use stable snake_case names.
610
- When an `icon` sprite exists it is used directly; an unknown or absent icon safely falls back to the
611
- category's themed image role. `labels` and `icons` are booleans. Category styles control only
612
- presentation and may hide or delay candidates, never promote them above the producer's selection.
613
- `placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement.
614
-
615
- ```ts
616
- poi({
617
- categories: ['food-drink', 'landmark', 'transport'],
618
- density: 3,
619
- icons: true,
620
- labels: true,
621
- });
622
- ```
623
-
624
- The same contract drives regular POI layers, custom HUD treatments, semantic interactions, and
625
- feature inspection. There is intentionally no compatibility path for the former class mapping,
626
- rank ceilings, or named density/detail presets.
627
-
628
- ## Themes and module styles
629
-
630
- A map declares complete named appearances in `themes`, chooses one deterministic
631
- `defaultTheme`, and may map the browser's light/dark preference through `systemThemes`. `system` is
632
- never a compiled theme name: it is a runtime request that resolves immediately to one concrete
633
- name. Disk builds write `styles/<map>/<theme>.json` beneath their output root. Public and
634
- content-addressed Style URLs belong to the runtime manifest and may include a deployment prefix;
635
- capture scenes and receipts store the concrete name rather than `system`.
41
+ Coordinates use `[longitude, latitude]`. The map inherits Streets' themes and asset providers.
42
+ A complete root map uses the same `defineMap()` call without `extends`; most applications should
43
+ extend an existing map instead of rebuilding its complete visual contract.
636
44
 
637
- Themes are flat, JSON-safe documents with `colorScheme`, identity, typography, lighting, and four
638
- typed token catalogs: `color`, `font`, `image`, and `number`. Every theme on one map must expose the
639
- same token keys, so changing a theme can never change map structure or silently lose a semantic
640
- role. `defineTheme(base, definition)` is an authoring convenience that returns a fully materialized
641
- document; it does not leave an inheritance edge in the resolved config.
642
-
643
- The key in the `themes` record is its concrete runtime selector and output-path segment. The
644
- theme's own `id` and `version` are its editorial identity and build provenance, so they need not
645
- equal that key. Concrete keys may be names such as `day` and `night`; every `systemThemes.light` or
646
- `systemThemes.dark` value must name an existing theme whose `colorScheme` matches that branch.
647
-
648
- ```ts
649
- import {color, defineMap, defineTheme, fixed, roads, token} from '@tileflow/core';
650
- import {streets, streetsThemes} from '@tileflow/maps';
45
+ Validate and preview the file with the project-local CLI:
651
46
 
652
- const dark = defineTheme(streetsThemes.dark, {
653
- id: 'company-dark',
654
- version: 1,
655
- colorScheme: 'dark',
656
- tokens: {
657
- color: {
658
- 'surface.land': '#101722',
659
- 'labels.primary': color.mix('#eef3fb', token.color('surface.background'), {amount: 0.08}),
660
- },
661
- },
662
- });
663
-
664
- export default defineMap({
665
- id: 'company',
666
- version: 1,
667
- extends: streets,
668
- themes: {light: streetsThemes.light, dark},
669
- defaultTheme: 'light',
670
- systemThemes: {light: 'light', dark: 'dark'},
671
- modules: {
672
- roads: roads({
673
- classes: {
674
- primary: {
675
- surface: {
676
- fill: {
677
- color: fixed('#ff2d78', {reason: 'Regulatory primary-road ink is invariant'}),
678
- },
679
- },
680
- },
681
- secondary: {
682
- surface: {
683
- fill: {
684
- color: token.color('roads.secondary'),
685
- },
686
- },
687
- },
688
- },
689
- }),
690
- },
691
- });
47
+ ```sh
48
+ npm install --save-dev --save-exact tileflow@alpha
49
+ npx tileflow validate --json
50
+ npx tileflow preview
692
51
  ```
693
52
 
694
- Semantic modules own structure and behavior; visual fields accept typed token references.
695
- Use a visual literal only inside a theme. If a module value intentionally must not vary, wrap it in
696
- `fixed(value, {reason})`. `tileflow inspect --json` reports stable `THEME_IMPLICIT_FIXED`
697
- diagnostics: implicit color, font, and image literals block Style compilation, while direct visual
698
- number literals are warnings so an agent can document their invariance deliberately. Structural
699
- numbers such as zoom bounds and ranks are not visual-theme diagnostics. `color.alpha()` and
700
- deterministic OKLCH `color.mix()` keep derived palette logic inspectable. Unknown refs, cycles,
701
- cross-category refs, token-schema drift, and unresolved visual nodes fail before Style JSON is
702
- emitted.
53
+ Loading a config executes its imports. Treat it as trusted code, not as sandboxed JSON.
703
54
 
704
- There is no physical-layer override for the Tileflow basemap or separate official-map authoring
705
- surface. Basemap behavior belongs to a typed control or owner-local render stack. Every
706
- official and application map therefore follows the same schema, inheritance rules, diagnostics,
707
- and lowering path. `createStyle()` validates the final planned Style JSON with the MapLibre style
708
- spec and never returns an invalid style.
55
+ ## Understand inheritance
709
56
 
710
- ## Data is separate from design
57
+ Omitted fields inherit, but declaring a field does not imply a recursive merge:
711
58
 
712
- Team data uses named logical sources and validated MapLibre overlays. The hosted identity never
713
- contains a CDN URL; `local` is the explicit account-free PMTiles resolution for Dev and Capture:
59
+ - `view` deep-merges; nested arrays replace.
60
+ - `modules` merges by domain. Declaring `roads(...)` replaces that domain's inherited request and
61
+ render contributions. Use `refine(...)` to patch it, `disable()` to remove it, and `reset()` for
62
+ an explicit reset inside a supported patch.
63
+ - `themes` replaces the complete collection and clears an inherited `systemThemes` mapping.
64
+ `defaultTheme` can select an inherited concrete theme; `systemThemes` replaces its complete mapping.
65
+ - `icons` replaces the ordered contributor list. Omission inherits it; `[]` disables it. Local and
66
+ package directories can be combined with explicit locked `iconSet('@team/set')` descriptors.
67
+ Spread the parent's list explicitly when composing contributors. Later contributors win for an
68
+ exact icon ID.
69
+ - `fonts` and `glyphs` are mutually exclusive text providers. Declaring either replaces the inherited
70
+ provider. `data`, `projection`, `terrain`, and `marine` are also atomic.
714
71
 
715
- ```ts
716
- import {defineMap, hostedTileset, maplibreOverlay} from '@tileflow/core';
717
- import {streets} from '@tileflow/maps';
72
+ Identity (`id`, `name`, `version`) and tooling `scenes` belong to the leaf map and do not inherit.
73
+ Module object order does not determine rendering order. See the
74
+ [map inheritance contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/map-inheritance.md)
75
+ for the complete merge rules.
718
76
 
719
- const stores = hostedTileset({
720
- tileset: 'stores',
721
- local: './data/stores.pmtiles',
722
- attribution: 'Store data © Example',
723
- });
77
+ ## Team Icon Sets foundations
724
78
 
725
- export default defineMap({
726
- id: 'store-locator',
727
- version: 1,
728
- extends: streets,
729
- sources: {stores},
730
- overlays: {
731
- stores: maplibreOverlay({
732
- source: 'stores',
733
- placement: 'above-roads',
734
- layers: [
735
- {
736
- id: 'stores-points',
737
- type: 'circle',
738
- 'source-layer': 'store_locations',
739
- paint: {
740
- 'circle-color': ['match', ['get', 'category'], 'restaurant', '#ef4444', '#64748b'],
741
- },
742
- },
743
- ],
744
- }),
745
- },
746
- });
747
- ```
79
+ `iconSet('@team/set')` is an explicit icon contributor alongside local and package-owned directories.
80
+ It performs no I/O. A resolved map accepts at most 32 contributors, and the same set reference may
81
+ appear only once. Shared sets use the same explicit array order and later-wins semantics as ordinary
82
+ icon directories.
748
83
 
749
- Tileflow automatically registers only protocols in namespaces that Tileflow owns.
750
- `tileflow-pmtiles://` is reserved for PMTiles resources managed by Tileflow. Tileflow never
751
- automatically registers, removes, or replaces the global `pmtiles://` handler. Explicit PMTiles
752
- sources supplied by an application remain the application's responsibility.
84
+ An exact `tileflow.icons.lock.json` identifies immutable set revisions and generated artifacts. The
85
+ lock parser rejects duplicate JSON keys, stale reference sets, mixed Teams, invalid IDs, floating
86
+ versions, unsafe URLs, and manifest/hash mismatches. Serialization is canonical. Hashes are internal
87
+ integrity machinery, not identifiers that application authors manage.
753
88
 
754
- For local resolution, the Style URL is based on the logical tileset ID and remains stable when Dev
755
- creates a new immutable snapshot. Physical generation IDs and local content hashes never enter the
756
- Style representation. Each Dev PMTiles request retains the snapshot it resolved until its response
757
- is prepared, and strong generation ETags prevent one cached PMTiles read from silently combining
758
- ranges from different generations. Standalone Capture retains one snapshot for its complete render.
759
- Application Capture does not freeze every local dataset across the complete screenshot window; the
760
- application's development server remains authoritative for its per-read generation.
89
+ Shared-set content identifies the effective published artifact. Git owns original artwork history;
90
+ source-only SVG edits producing the same artifact are not shared content changes. No original-source
91
+ hash, source format, or filesystem path is added to a shared package. Local/package originals retain
92
+ their existing source identity. Shared winners use explicit `rendered-icon` identity, while an
93
+ ordered `tileflow-icon-composition-v1` receipt records consumed revisions separately, including
94
+ fully shadowed sets and dependency changes that preserve rendered pixels.
761
95
 
762
- `local` does not make build output own or publish the archive. Production builds reject unresolved
763
- local PMTiles. Explicitly publish the tileset through Tileflow's managed upload, validation,
764
- versioning, and hosting flow, or serve it from infrastructure owned by the application.
96
+ Map revision hashing preserves the existing v1 result for maps without shared sets. Maps with sets
97
+ require a complete receipt and use the domain-separated v2 revision contract. Build-manifest map
98
+ entries identify that contract with `mapRevisionSchemaVersion: 2`; omission means legacy v1. The
99
+ generated icon package remains `tileflow-icon-package-v1` and the asset-set hash is unchanged.
765
100
 
766
- One overlay owns one ordered group of ordinary MapLibre Style Layers. Public layer IDs are retained
767
- for `map.on(...)`, feature queries, and application state. Placement is one of `above-water`,
768
- `below-roads`, `above-roads`, `above-buildings`, `below-labels`, or `above-labels`; overlays sharing
769
- a placement sort by overlay ID. A map accepts up to 16 Team sources in addition to Tileflow World
770
- and terrain. These semantic boundaries do not expose generated basemap layer IDs.
771
- `sources` and `overlays` merge by key through `extends`, declarations replace atomically, and
772
- `remove()` deletes one inherited key. Removing a still-used source fails validation. There is no
773
- interaction DSL or parallel expression language.
101
+ These are SDK foundation APIs. The explicit `@tileflow/dev` composition port can consume verified
102
+ fixture or cached artifacts without a registry. Registry publication, managed deployment, and normal
103
+ CLI/framework wiring are separate integrations, not implied by importing `iconSet`.
774
104
 
775
- Omitting `data` selects the compiler-owned Tileflow World `v1` compatibility generation:
105
+ ## Style with themes and semantic modules
776
106
 
777
- ```ts
778
- import {streets} from '@tileflow/maps';
107
+ Themes are complete appearance documents, not loose color overrides. Every theme must supply the
108
+ same token schema. Use `token.color(...)`, `token.number(...)`, `token.image(...)`, and typed font
109
+ roles for theme-dependent values. Use `fixed(value, {reason})` for an intentionally invariant visual
110
+ value. Structural controls, such as visibility and zoom gates, remain ordinary literals.
779
111
 
780
- export default streets;
781
- ```
112
+ Extend a theme without changing the inherited map structure:
782
113
 
783
- Theme typography can set `fallbacks`, `letterSpacing`, and `transform` globally or per label
784
- domain, while the font token catalog gives module styles a stable semantic target. Text delivery is
785
- explicit and atomic: a map may declare either ordered `fonts` directories
786
- for browser font files or a `glyphs` provider for PBF glyphs, never both. Omitting both inherits the
787
- parent's provider; declaring either replaces an inherited provider of either kind. After resolution,
788
- a map that emits text must have exactly one provider. A text-free standalone map may omit both. On a derived
789
- map, omission inherits the parent provider while `fonts: []` explicitly removes it; that empty array
790
- is valid only when the resolved map emits no text.
114
+ <!-- docs:check -->
791
115
 
792
116
  ```ts
793
117
  import {defineMap, defineTheme} from '@tileflow/core';
794
118
  import {streets, streetsThemes} from '@tileflow/maps';
795
119
 
796
- const brandLight = defineTheme(streetsThemes.light, {
797
- id: 'brand-light',
798
- version: 1,
799
- colorScheme: 'light',
800
- tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
801
- });
802
- const brandDark = defineTheme(streetsThemes.dark, {
803
- id: 'brand-dark',
120
+ const dark = defineTheme(streetsThemes.dark, {
121
+ id: 'madrid-dark',
804
122
  version: 1,
805
123
  colorScheme: 'dark',
806
- tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
124
+ tokens: {color: {'surface.land': '#0d1320', 'surface.water': '#081e2e'}},
807
125
  });
808
126
 
809
127
  export default defineMap({
810
- id: 'brand-map',
128
+ id: 'madrid',
811
129
  version: 1,
812
130
  extends: streets,
813
- fonts: ['./fonts'],
814
- themes: {light: brandLight, dark: brandDark},
131
+ themes: {light: streetsThemes.light, dark},
815
132
  defaultTheme: 'light',
816
133
  systemThemes: {light: 'light', dark: 'dark'},
817
134
  });
818
135
  ```
819
136
 
820
- Node preparation reads TTF, OTF, and WOFF2 files, uses each OpenType full name as its exact
821
- `text-font` ID, and requires `LICENSE.txt` in every contributing directory. Directories apply left
822
- to right; a later exact name replaces an earlier face and case-only collisions fail. Only primary
823
- faces used by the final style become content-addressed assets and strict `tileflow:fontFaces`
824
- metadata. `font` names an exact OpenType full name or glyph face; local `fallbacks` name exact faces
825
- or explicit CSS generic families. Tileflow never appends a weight suffix or derives one face from
826
- another. Browser adapters load local faces before MapLibre. Native releases use a locked PBF
827
- `glyphs` URL provider instead, which enumerates the exact comma-joined MapLibre request keys in
828
- `fontStacks`.
829
-
830
- Streets, Ferraris, Härad, Soundings, Verdant, and San Francisto each declare
831
- `https://api.tileflow.dev/fonts/{fontstack}/{range}.pbf` with the exact `Noto Sans Regular` and
832
- `Noto Sans Bold` stacks. That compatibility URL is canonical but not content-addressed; responses revalidate and
833
- do not make a resolved map byte-reproducible. Exact official glyph identity belongs to the
834
- separately published `/base/<assetSetSha256>/glyphs/...` global base-asset contract. In that URL,
835
- `assetSetSha256` identifies the standalone glyph collection; it is not the same-domain value as the
836
- per-map `assetSetSha256` in `build-manifest.json`.
837
- Cyberpunk and Matrix each replace the URL provider with their own packaged Oxanium directory and
838
- reference the exact local faces `Oxanium Medium` and `Oxanium SemiBold`. Baedeker and Siegfried
839
- each own a packaged directory containing Cormorant Garamond Regular, SemiBold, and Italic.
840
-
841
- Name the official generation deliberately, or use another OpenMapTiles-compatible vector source:
842
-
843
- ```ts
844
- import {openMapTiles, tileflowWorld, vectorTiles} from '@tileflow/core';
845
-
846
- const official = tileflowWorld();
847
- const external = vectorTiles({
848
- tiles: ['pmtiles://./test/fixtures/world.pmtiles'],
849
- revision: 'fixture-1',
850
- attribution: '© Example © OpenStreetMap contributors',
851
- schema: openMapTiles(),
852
- });
853
- ```
854
-
855
- The resolved map controls how data is drawn; `data` controls where compatible features come from.
856
- For Tileflow World, the compiler emits the selector TileJSON URL
857
- `https://api.tileflow.dev/tiles/world/tiles.json`. Omitted data or `tileflowWorld()` selects
858
- `world-v1/current`; `tileflowWorld({release: {releaseId, descriptorSha256}})` binds an exact release
859
- through query parameters. Runtime and capture resolve `current` once per session or job and then
860
- use the immutable release described by that response. `openMapTiles({layers, fields})` can bind renamed
861
- source-layers or properties while preserving the versioned semantic contract. External browser
862
- credentials do not belong in public Style JSON; supply them through the framework's
863
- `transformRequest` integration.
864
-
865
- External exact-test sources may use one TileJSON `url` or a bounded direct `tiles` list with optional
866
- `bounds`, `minzoom`, and `maxzoom`. `pmtiles://` is supported for a checked-in or bring-your-own
867
- archive. Public vector URLs are HTTPS, root-relative, or HTTP only on loopback development hosts.
868
- A PMTiles URL names one `.pmtiles` archive through an HTTPS, loopback HTTP, root-relative, or safe
869
- repository-relative target; credentials, fragments, traversal, and other protocols are rejected at
870
- both authoring validation and compilation. An external `revision` participates in capture identity,
871
- so changing fixture bytes cannot silently reuse a baseline.
872
-
873
- Tileflow's OpenMapTiles contract also recognizes the optional `globallandcover` extension. The
874
- official Streets map maps its `barren`, `crop`, `grass`, `shrub`, `snow`, `trees`, and
875
- `urban` classes beneath the OSM land layers. Tileflow World V1 supplies native generalized
876
- geometry through z10; Streets fades it progressively while detailed `landcover`/`landuse` gains
877
- opacity and removes it at z11. This is a macro bridge, not local semantic detail. Sources with a
878
- different layer name can use `openMapTiles({layers: {globalLandcover: 'my_landcover'}})`; archives
879
- without the extension remain compatible and simply render no features for that style layer. Use
880
- `land({globalLandcover: {...}})` to customize its fill, opacity, visibility, and zoom range. `crop`
881
- shares the theme's semantic `landcover.farmland` color with detailed farmland, while `barren` uses
882
- `landcover.rock`; neither class borrows a road color.
883
-
884
- The normalized schema also records the meaning of the OpenMapTiles `park` layer. Generic
885
- `openMapTiles()` defaults to `semantics.parkLayer: 'mixed'` and compiles a private compatibility
886
- branch for ordinary legacy parks. `tileflowWorldV1Schema()` fixes it to `'protected-only'`: that
887
- layer is a protection tint, while urban parks and gardens come from
888
- `landcover.class=grass/subclass=park|garden`. The ambiguous legacy `park` class is therefore not
889
- a public land-cover target.
890
-
891
- Tileflow World V1 makes `bathymetry`, `globallandcover`, `circular_feature`, `sidewalk`, and
892
- `street_furniture` mandatory. Use `tileflowWorldV1Schema()` for that product instead of making raw
893
- layer names part of a style. Publication tooling calls `validateTileflowWorldV1Tilejson(...)` and
894
- fails closed unless each layer occurs exactly once, declares its contracted native zooms, and
895
- advertises the typed fields consumed by the maps. This includes bathymetry z0–z9, global land cover
896
- z0–z10, the three detailed-city layers at native z15, and numeric
897
- `transportation.clearance_extra_px_z15` for butt-capping procedural-roundabout approaches. Generic
898
- OpenMapTiles sources keep the detailed extensions optional and the compiler omits unsupported
899
- detail. Resolving omitted data or
900
- `tileflowWorld(...)` uses the strict V1 schema directly, and `water({bathymetry: {...}})` styles the
901
- emitted depth bands without raw layer IDs. Use `water({bathymetryContours: {...}})` to customize the
902
- opt-in companion line layer; an empty style selects subtle defaults, and the zero-depth band is
903
- excluded so the ordinary coastline remains authoritative. The lines follow discrete band polygon
904
- edges and are therefore approximate rather than surveyed depth contours. Use
905
- `water({bathymetryLabels: {...}})` to customize the separate opt-in symbol layer; an empty style
906
- selects the defaults, whose text is the absolute band-minimum number in metres with no unit suffix.
907
- It remains a band-floor annotation, not a survey sounding.
908
-
909
- For independent ocean products, `marine` composes stable auxiliary vector sources without changing
910
- the primary World source:
911
-
912
- ```ts
913
- import {bathymetry, defineMap, fixed, nautical} from '@tileflow/core';
914
- import {streets} from '@tileflow/maps';
915
-
916
- export default defineMap({
917
- id: 'chart',
918
- version: 1,
919
- extends: streets,
920
- marine: 'chart', // 'none' | 'bathymetry' | 'nautical' | 'chart'
921
- modules: {
922
- nautical: nautical({
923
- soundings: {minZoom: 13},
924
- aids: {minZoom: 11},
925
- lights: {minZoom: 12},
926
- hazards: {minZoom: 12},
927
- hazardAreas: {
928
- fill: {opacity: fixed(0.1, {reason: 'Chart hazard wash is invariant'})},
929
- },
930
- wrecks: {minZoom: 12},
931
- wreckAreas: {
932
- outline: {opacity: fixed(0.6, {reason: 'Chart wreck outline is invariant'})},
933
- },
934
- reefs: {fill: {opacity: fixed(0.3, {reason: 'Chart reef wash is invariant'})}},
935
- navigationAreas: {
936
- fill: {opacity: fixed(0.05, {reason: 'Chart navigation-area wash is invariant'})},
937
- },
938
- coverage: {outline: {visible: false}},
939
- labels: {
940
- navigationAreas: {minZoom: 11},
941
- reefs: {minZoom: 13},
942
- },
943
- }),
944
- },
945
- });
946
- ```
947
-
948
- `bathymetry` resolves to vector source `tileflow-bathymetry` and supplies the 13-stop
949
- `bathymetry-v1` band contract to `water(...)`. `nautical` resolves to `tileflow-nautical`; the
950
- `nautical(...)` module
951
- styles its `sounding`, `aid`, `light`, `hazard`, `wreck`, `reef`, `navigation_area`, and `coverage`
952
- layers. The schema also names the product's provider-neutral provenance fields (`provenance`,
953
- `provider`, `cell`, `edition`, `update`, `scale`, `coverage`, and `licence`). `chart` enables both.
954
- Point features label depth, name, or light character as applicable. The `lighthouses` symbol style
955
- targets `aid` points whose canonical `class` is `lighthouse`, leaving other aids under `aids`.
956
- Polygon reefs, navigation
957
- areas, hazards, wrecks, and named/provider coverage have separate collision-aware labels under
958
- `nautical({labels: {...}})`; unnamed coverage does not emit empty text.
959
- Core does not hard-code a nautical provider or attribution: the selected TileJSON owns its actual
960
- coverage and attribution, and derived vector tiles are not represented as a certified ENC for
961
- navigation. The V1 geometry contract preserves `sounding`, `aid`, and `light` as points;
962
- `reef`, `navigation_area`, and `coverage` as polygons; and both point and polygon geometries for
963
- `hazard` and `wreck`. Point symbols and polygon areas compile separately, without fabricated
964
- centroids.
965
- Advanced callers can override either TileJSON URL, attribution, or source ID and can set the other
966
- member to `false`:
967
-
968
- ```ts
969
- marine: {
970
- bathymetry: {url: 'https://tiles.example.test/depth/tiles.json'},
971
- nautical: false,
972
- }
973
- ```
974
-
975
- Use the `bathymetry(...)` helper when the map needs relief or product-level styling. Bathymetry is
976
- one logical product with two independently loaded physical sources:
977
-
978
- ```ts
979
- marine: {
980
- bathymetry: bathymetry({
981
- display: 'hybrid',
982
- bands: {
983
- opacity: fixed(0.72, {reason: 'Keep chart symbols above the depth wash'}),
984
- },
985
- contours: {visible: true},
986
- relief: {
987
- multidirectional: true,
988
- opacity: fixed(0.18, {reason: 'Relief stays legible beneath bands and labels'}),
989
- },
990
- }),
991
- nautical: false,
992
- }
993
- ```
994
-
995
- `display: 'bands'` loads only vector TileJSON `/tiles/bathymetry/tiles.json` as
996
- `tileflow-bathymetry`. `display: 'relief'` loads only `/tiles/bathymetry/dem/tiles.json` as
997
- `tileflow-bathymetry-dem` and emits continuous `color-relief` plus hillshade. `display: 'hybrid'`
998
- loads both. The hosted DEM contract is 512 px, Terrarium-encoded, lossless WebP; advanced compatible
999
- sources may override `url`, `sourceId`, `encoding`, or `tileSize` inside `relief`.
1000
- Multidirectional illumination uses directions 270°, 315°, 0°, and 45°. The simple
1001
- `marine: 'chart'` shorthand deliberately stays vector-only; DEM relief is an explicit advanced
1002
- opt-in.
1003
-
1004
- The vector palette uses thresholds −11 000, −8 000, −6 000, −4 000, −2 000, −1 000, −500, −200,
1005
- −100, −50, −20, −10, and 0 metres. `bands`, `contours`, and `labels` remain separate controls.
1006
- Until native contour fields are contracted, contours are a visual fallback drawn from band polygon
1007
- edges, not surveyed isolines. The V1 schema exposes optional source-layer names
1008
- `bathymetry_contour`, `seafloor_landform`, `water_name`, and `bathymetry_coverage`, but promises
1009
- fields only for required `bathymetry.min_depth` and `bathymetry.sort_key`.
1010
-
1011
- The DEM enables future client-side sampling and depth profiles, but Core intentionally exposes no
1012
- profile or remote sampling API yet. Rendered relief must not be interpreted as navigation-safe
1013
- depth data.
1014
-
1015
- Omitting `marine` preserves the transitional Tileflow World V1 bathymetry fallback. Declaring
1016
- `marine: 'none'` suppresses that fallback, while a selected bathymetry sidecar always takes
1017
- precedence. Compiled styles retain the singular `tileflow:data` metadata for existing consumers and
1018
- add `tileflow:sources` plus `tileflow:sourceRequirements` for independent product validation.
1019
-
1020
- Detailed city datasets can bind `sidewalk`, `streetFurniture`, and `circularFeature` source layers
1021
- through `openMapTiles({layers, fields})`. When present, `roads.sidewalks` owns source-backed
1022
- pedestrian polygons, `roads.crossings` owns oriented crossing icons, and `roads.roundabouts` owns
1023
- metric circular road rings. These bindings are optional: a generic OpenMapTiles source remains
1024
- valid and the compiler omits unsupported detail instead of inventing geometry. Crossing icons are
1025
- explicit because the data contract cannot assume a sprite name. They require positive physical
1026
- `markings` evidence and reject `crossing=no`; both fields are semantic bindings, so custom schemas
1027
- can remap them without weakening that rule.
1028
-
1029
- The optional `business_corridor` extension contains activity-selected source footprints below
1030
- buildings. Its `activity_score`, `rank`, `min_zoom`, and `confidence` fields control a quiet local
1031
- warm tint without drawing POI-radius circles. Current building footprints may carry sparse
1032
- `building_tone=commercial|destination|active`; absence means the neutral base building. The three
1033
- values preserve why the warm tone was selected even when a theme maps them to the same color.
1034
- Building visibility is fixed by the layer zoom contract and never by optional semantics. Immutable
1035
- V8.9 archives carrying `building_kind` or `has_business` remain readable as a defensive
1036
- compatibility path, but new candidates do not emit those fields. The land module also exposes
1037
- `medical`, `education`, and `government` independently instead of collapsing their already distinct
1038
- source classes into one civic color.
1039
-
1040
- World and text assets are independent contracts. `tileflowWorld()` selects `world-v1/current` or an
1041
- exact `releaseId + descriptorSha256`; a `glyphs` declaration contains its own complete URL. Ordinary
1042
- imports of Streets, Baedeker, Ferraris, Härad, Siegfried, Soundings, Cyberpunk, Matrix, Verdant,
1043
- and San Francisto
1044
- remain usable because each official map owns or inherits a URL or packaged-font provider.
1045
- URL-backed maps become exact-byte reproducible
1046
- when the immutable global base-asset set is published and their explicit URL is updated to its
1047
- `assetSetSha256` path; no World or compiler fallback participates in that rollout. The global
1048
- base-asset manifest's hash and a map build manifest's identically named `assetSetSha256` use
1049
- different contracts and must never be substituted for one another.
1050
-
1051
- Upstream data attribution remains in the MapLibre source. `Map by Tileflow` is a separate product
1052
- credit/trademark surface and must not replace, obscure, or be presented as upstream attribution.
1053
-
1054
- ## Capture scenes
1055
-
1056
- Commit named `scenes` on the singular exported map. A scene implicitly targets that map, so it does
1057
- not repeat a `map` selector. Scene metadata does not inherit and is removed before cartographic
1058
- compilation; it affects visual evidence, not style or manifest identity.
1059
-
1060
- ```ts
1061
- import {defineMap} from '@tileflow/core';
1062
- import {streets} from '@tileflow/maps';
1063
-
1064
- export default defineMap({
1065
- id: 'madrid',
1066
- name: 'Madrid',
1067
- version: 1,
1068
- extends: streets,
1069
- scenes: {
1070
- 'madrid-desktop': {
1071
- theme: 'dark',
1072
- camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
1073
- viewport: {width: 1280, height: 800, dpr: 1},
1074
- },
1075
- },
1076
- });
1077
- ```
1078
-
1079
- Use `tileflow preview --scene madrid-desktop` (`tileflow dev` remains an alias) for live review and
1080
- `tileflow capture madrid-desktop` for exact pixels and a schema-version-4 receipt containing the
1081
- concrete theme plus Streets, data, style, renderer, and image identities. World capture resolves the selected TileJSON
1082
- once and records the exact `world-v1` release plus descriptor/archive/data-contract hashes;
1083
- external fixtures may identify their explicit revision.
1084
-
1085
- Every exact World V1 release ID is already canonical at the producer boundary: 12–128 characters
1086
- matching `^world-v1-[a-z0-9][a-z0-9._-]*[a-z0-9]$`. Core validates the literal value and never trims,
1087
- lowercases, or upgrades it to another World generation.
1088
-
1089
- ## Public API and browser subpath
1090
-
1091
- Config, data, modules, compilation, validation, runtime resolution, and capture-scene APIs remain
1092
- available from the package root. Capture scenes, manifests, and browser-runtime surfaces also have
1093
- explicit `@tileflow/core/capture`, `@tileflow/core/manifest`, and `@tileflow/core/runtime`
1094
- subpaths. Node build integrations import the small deterministic map-identity contract from
1095
- `@tileflow/core/build`: `collectTileflowMapBuildLineage`, `createTileflowMapBuildManifest`,
1096
- `hashTileflowMapRevision`, `hashTileflowAssetSet`, and `hashTileflowAssetSetIdentities`. The latter
1097
- reproduces the same asset-set v1 hash from exact per-file identities after another system has
1098
- independently confirmed the immutable bytes; package or bundle hashes are not substitutes. The
1099
- previous aggregate-wrapper and recipe-selector APIs, `renderer`, top-level `tiles`/`tileset`, module
1100
- arrays, and top-level raw `layers` are not part of this API. Raw MapLibre layers are accepted only
1101
- inside a named `maplibreOverlay()` bound to a named Team source.
1102
-
1103
- Advanced cartography remains in the same public language: `withRenderStack()`, `renderPass()`, and
1104
- `refineRenderTarget()` address owner-local semantic targets without exposing physical layer IDs,
1105
- sources, source layers, raw filters, or before/after anchors. Typed `field()` references and
1106
- `expr.*` builders stay semantic until the compiler's single lowering boundary. There is no recipe
1107
- subpath or alternate compiler integration API.
1108
-
1109
- `mapRevisionSha256` hashes only resolved cartography: the effective design after `extends`, the
1110
- semantic language, compiler-owned effective contributions, and exact source icon/font identities.
1111
- Leaf `id`, `name`, editorial `mapVersion`, default `view`, capture `scenes`, and `delivery` policy do
1112
- not change that content identity. They remain explicit on the map, manifest, Style, capture receipt,
1113
- or Hosted deployment fingerprint that owns them. Compiler ABI, package versions, generated assets,
1114
- and the concrete release selected by World `current` likewise have separate identities.
1115
-
1116
- Runtime manifest version 1 has one shape for local and Hosted delivery. Every map entry owns
1117
- `defaultTheme`, optional `systemThemes`, and a `themes` record whose leaves contain
1118
- `colorScheme`, `styleUrl`, and optional font/deployment identity. Hosted-only identity is additive
1119
- metadata on the same map entry; there is no delivery discriminator or parallel `styles` alias.
1120
- `parseTileflowRuntimeManifest()` accepts only this canonical shape. The strict bounded schema uses
1121
- the same portable map and theme IDs as authoring, rejects `system` as a published name, duplicate
1122
- font identities, unsafe owner-relative URLs, prototype-bearing input, unknown fields, and JSON
1123
- larger than 1 MiB.
1124
-
1125
- ```json
1126
- {
1127
- "version": 1,
1128
- "maps": {
1129
- "madrid": {
1130
- "defaultTheme": "light",
1131
- "systemThemes": {"light": "light", "dark": "dark"},
1132
- "themes": {
1133
- "light": {"colorScheme": "light", "styleUrl": "/styles/madrid/light.json"},
1134
- "dark": {"colorScheme": "dark", "styleUrl": "/styles/madrid/dark.json"}
1135
- }
1136
- }
1137
- }
1138
- }
1139
- ```
1140
-
1141
- Runtime fetches share a successful result for 30 seconds, never cache failures, use
1142
- `cache: 'no-store'`, enforce a 10-second timeout (configurable up to 60 seconds), compose an
1143
- external abort signal, and can be invalidated with `clearTileflowManifestCache()`.
1144
-
1145
- Named map `view` values travel in the manifest. `resolveTileflowRuntimeTheme()` turns an omitted,
1146
- explicit, or browser `system` request into one concrete published entry; unknown names fail closed.
1147
- `resolveTileflowRuntimeView()` defines the shared
1148
- precedence as explicit runtime values, then the manifest view, then the single exported
1149
- `defaultTileflowRuntimeView` (`[0, 20]`, zoom 2, bearing/pitch 0). Browser delivery is one
1150
- discriminated `TileflowRuntimeSource`: `kind: 'tileflow'` resolves a named map only through its
1151
- published manifest, while `kind: 'maplibre'` accepts a direct style object or URL. The runtime
1152
- subpath does not import the config compiler, invent a localhost style URL, or change image mode by
1153
- environment.
1154
-
1155
- Framework adapters import the browser-only lifecycle kernel explicitly:
1156
-
1157
- ```ts
1158
- import {
1159
- attachTileflowFairUseNotice,
1160
- attachTileflowMapLifecycle,
1161
- createTileflowSessionStarter,
1162
- createTileflowTransformRequest,
1163
- registerTileflowWorldRequestBridge,
1164
- } from '@tileflow/core/browser';
1165
- ```
1166
-
1167
- The browser entry is not re-exported from the root. It is SSR-safe to import, has no MapLibre
1168
- runtime dependency, and reads no browser global during module evaluation. See the
1169
- [framework browser runtime contract](../../docs/contracts/framework-browser-runtime.md) and the
1170
- [cartographic authoring contract](../../docs/contracts/cartographic-authoring.md). Exact field
1171
- resolution and asset rules live in the
1172
- [map inheritance contract](../../docs/contracts/map-inheritance.md).
1173
-
1174
- ## Hosted session authorization
1175
-
1176
- The framework adapters use `createTileflowSessionController` to obtain one short-lived session
1177
- grant before eligible hosted style, tile, font, or sprite requests. Concurrent resources share the
1178
- same preflight, grants are attached only to reviewed HTTP(S) origins, and an expired unused session
1179
- rotates once when the server requests it. A controller also rotates after six hours or 10,000
1180
- eligible requests. The preflight has a 10-second client timeout by default; direct controller users
1181
- can set `grantTimeoutMs` from 1 to 120,000 milliseconds.
1182
-
1183
- `analytics.enabled: false` disables only the optional analytics beacon. It does not disable this
1184
- server-owned commercial authorization. User `transformRequest` callbacks still run first; Tileflow
1185
- then decorates only the resulting eligible URL and preserves the other request options.
1186
-
1187
- Set `analytics.surfaceId` to a stable product location when the same Map appears in several places:
1188
-
1189
- ```ts
1190
- const analytics = {surfaceId: 'store-locator'};
1191
- ```
1192
-
1193
- A Surface ID is 1–64 lowercase ASCII letters, digits, `.`, `_`, or `-`; it starts and ends with a
1194
- letter or digit. Missing or invalid values become `default`. Use a durable integration name such as
1195
- `checkout` or `dealer-search`, not a URL, branch, preview, random component instance, or user ID.
1196
- Surface labels do not change authorization, entitlement, or Session identity.
1197
-
1198
- For exact Tileflow World release tiles, framework adapters also use the browser request bridge to
1199
- observe the response's safe fair-use state without adding identity, query parameters, credentials,
1200
- or user headers to the public World URL. The bridge requires the immutable release path and exactly
1201
- one lowercase descriptor digest; the retired mutable World template is never intercepted. Early
1202
- `GRACE` stays silent; signed late `GRACE` creates a compact
1203
- accessible owner-action pill, and `MANAGED_REQUIRED` creates a stronger in-map banner. A missing
1204
- header, absent tile, MapLibre error, or failed response cannot erase an existing manage action; a
1205
- later successful `OPEN` response can clear it. Shaped empty tiles remain render-safe while the owner
1206
- action stays available. Successful World tile bodies are streamed with a 16 MiB maximum: an
1207
- oversized `Content-Length` is rejected before reading, and chunked responses are cancelled as soon
1208
- as their accumulated bytes cross the same bound. Abort signals cancel an active reader; errors do
1209
- not include the remote response body.
1210
-
1211
- Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
1212
- details and can change during alpha releases.
1213
-
1214
- Official map definitions and the source assets they own ship together in `@tileflow/maps`, not in
1215
- Core. That package exports the maps and their reusable directory descriptors:
1216
-
1217
- ```ts
1218
- import {
1219
- baedeker,
1220
- baedekerFonts,
1221
- baedekerIcons,
1222
- cyberpunk,
1223
- cyberpunkFonts,
1224
- cyberpunkIcons,
1225
- ferraris,
1226
- ferrarisIcons,
1227
- harad,
1228
- haradIcons,
1229
- matrix,
1230
- matrixFonts,
1231
- matrixIcons,
1232
- sanFrancisto,
1233
- sanFrancistoIcons,
1234
- siegfried,
1235
- siegfriedFonts,
1236
- siegfriedIcons,
1237
- soundings,
1238
- soundingsIcons,
1239
- streets,
1240
- streetsIcons,
1241
- streetsThemes,
1242
- verdant,
1243
- verdantIcons,
1244
- } from '@tileflow/maps';
1245
- ```
1246
-
1247
- All ten official maps are complete standalone maps. The sole semantic compiler is implicit; none
1248
- imports or extends another official map, and each declares its own icon directory.
1249
- Streets declares `[streetsIcons]` and exposes its complete coordinated appearances as
1250
- `streetsThemes.light` and `streetsThemes.dark`; image tokens select the matching sidewalk pattern
1251
- without changing the asset collection. Baedeker declares `[baedekerIcons]`, whose eight original
1252
- patterns support its travel-atlas design, and `[baedekerFonts]`, its own Cormorant Garamond
1253
- directory; it derives contours in the browser from unpackaged Mapterhorn terrain tiles. Cyberpunk
1254
- declares `[cyberpunkIcons]` and `[cyberpunkFonts]`; Matrix
1255
- independently declares `[matrixIcons]` and `[matrixFonts]`. San Francisto declares
1256
- `[sanFrancistoIcons]` for its four technical hatches and schematic POI node, derives contours from
1257
- unpackaged Mapterhorn tiles, and uses the canonical Noto Sans glyph provider. The same asset
1258
- operation is available to applications:
1259
-
1260
- ```ts
1261
- export default defineMap({
1262
- id: 'brand-map',
1263
- version: 1,
1264
- extends: streets,
1265
- icons: [...streets.icons, './icons'],
1266
- });
1267
- ```
1268
-
1269
- `icons` is a `readonly TileflowIconDirectory[]`. Omission inherits the parent's exact array,
1270
- declaration replaces it atomically, and `[]` means no icons. Directories apply left to right.
1271
- `<id>.<ext>` publishes an icon as `<id>`; `<id>.pattern.<ext>` publishes an intrinsic-size pattern
1272
- as `<id>`. The published ID must already be canonical lower-kebab; a later exact ID wins and a
1273
- case-only collision fails. There is no built-in selector, source object, external
1274
- sprite selector, icon mapping, icon-specific inheritance, additive command, or compatibility alias.
1275
-
1276
- `@tileflow/dev` resolves local and package directory descriptors, verifies real-path containment,
1277
- and prepares ordinary public artifacts without serializing installation paths. It compiles one
1278
- deterministic sprite from the final icon composition and validates every literal `icon-image`,
1279
- `fill-pattern`, and `line-pattern` in the final style against it. It also prepares any declared font
1280
- directories generically; `baedekerFonts`, `cyberpunkFonts`, `matrixFonts`, and `siegfriedFonts` are
1281
- ordinary package descriptors rather than pipeline special cases. Calling the pure compiler for a map whose style needs unprepared
1282
- assets fails instead of emitting broken runtime references.
137
+ `system` is a browser selection policy, not a stored theme. Builds, captures, static scenes, and
138
+ receipts must use a concrete theme such as `light` or `dark`.
139
+
140
+ The semantic domains are `land`, `water`, `nautical`, `roads`, `buildings`, `boundaries`, `labels`,
141
+ `poi`, `aeroways`, `transit`, `vegetation`, `addresses`, and `landforms`. Style stable geographic
142
+ targets rather than compiler-generated MapLibre layer IDs. Shared primitives cover fills, lines,
143
+ text, icons, circles, and extrusions. `zoom.*` supplies zoom curves; `expr.*` and `field(...)` supply
144
+ the closed data-expression language. `renderPass`, `refineRenderTarget`, and `withRenderStack` add
145
+ owner-local rendering behavior without bypassing the compiler.
146
+
147
+ ## Compile and inspect
148
+
149
+ `validateTileflowMap(input)` returns `{valid, messages}`. `parseTileflowMap(map)` resolves and
150
+ validates the authoring input and throws on failure. `createStyle(map, options)` returns a style or
151
+ throws; `createStyleResult(map, options)` returns structured diagnostics and a compilation report.
152
+ Only a successful result contains `style`.
153
+
154
+ Core compilation is not asset preparation. A map that references package or local icon/font
155
+ directories needs the CLI, a build plugin, or
156
+ [`@tileflow/dev/artifacts`](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md)
157
+ to produce deployable sprites and font files. Browser components consume the resulting manifest;
158
+ they do not import executable configs.
159
+
160
+ Use these commands to discover the installed language and inspect a prepared map:
161
+
162
+ ```sh
163
+ npx tileflow language manifest --json
164
+ npx tileflow language schema --json
165
+ npx tileflow inspect --json
166
+ npx tileflow explain --theme dark --json
167
+ npx tileflow semantic-diff --from-config before/tileflow.config.ts --to-config after/tileflow.config.ts --json
168
+ ```
169
+
170
+ The language manifest is also exported as `tileflowAuthoringManifest`, with
171
+ `tileflowAuthoringManifestSchemaVersion`. The generated schema distinguishes authoring input from
172
+ resolved compiler input. `diffTileflowMaps(before, after)` returns deterministic semantic changes
173
+ at JSON Pointer paths. Physical inspection IDs and indexes are diagnostic observations, not stable
174
+ application targets.
175
+
176
+ ## Data, terrain, and browser resources
177
+
178
+ Tileflow World is the hosted primary-data path. `vectorTiles()` with `openMapTiles()` supports
179
+ compatible externally supplied vector data for local/self-hosted use; it does not make that source
180
+ eligible for hosted deployment. `hostedTileset()` and semantic overlay placement add named data
181
+ sources. Local PMTiles inputs are for local tooling, not implicit production data publication.
182
+
183
+ `projection` accepts `mercator` or `globe`. Terrain supports `none`, `hillshade`, `3d`, and an object
184
+ form for explicit hillshade and contour configuration. Browser-derived contours require a DEM tile
185
+ template, zoom limits, thresholds, and the Tileflow contour protocol. Framework adapters and local
186
+ tooling prepare browser resources; direct MapLibre integrations must register required protocols
187
+ before loading a style. See the
188
+ [browser runtime contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/framework-browser-runtime.md).
189
+
190
+ A map with text must declare exactly one text provider. Streets, Ferraris, Härad, Soundings,
191
+ Verdant, and San Francisto use URL glyph providers. Baedeker and Siegfried use packaged Cormorant
192
+ fonts; Cyberpunk and Matrix use packaged Oxanium fonts. Hosted deployment currently rejects
193
+ package/local font bundles; use self-hosted output or explicitly replace the provider with compatible
194
+ public glyphs. Tile URLs and font providers do not establish redistribution rights.
195
+
196
+ ## Reference
197
+
198
+ Use the [generated language schema](https://github.com/tileflow/tileflow-sdk/blob/main/docs/modules-api-reference.json),
199
+ [cartographic authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/cartographic-authoring.md),
200
+ and [public exports](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/src/index.ts)
201
+ for exact options. Prefer the installed CLI's generated contracts when using a published release;
202
+ `main` can contain unreleased changes. Report defects in the
203
+ [issue tracker](https://github.com/tileflow/tileflow-sdk/issues).
204
+
205
+ ## Detailed guides
206
+
207
+ These guides are also included in the installed package under `docs/`. They describe this source
208
+ revision; prefer the installed copy when working with an older release.
209
+
210
+ - [Map and terrain examples](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/map-and-terrain-examples.md)
211
+ - [Authoring model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/authoring-model.md)
212
+ - [Shared visual primitives](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/shared-visual-primitives.md)
213
+ - [Compiled-style performance](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/compiled-style-performance.md)
214
+ - [Themes and module styles](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/themes-and-module-styles.md)
215
+ - [Data is separate from design](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/data-is-separate-from-design.md)
216
+ - [Capture scenes](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/capture-scenes.md)
217
+ - [Public API and browser subpath](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/public-api-and-browser-subpath.md)
218
+ - [Hosted session authorization](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/hosted-session-authorization.md)
219
+ - [Native resource URLs](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/native-resource-urls.md)