X-GIS

Reference

Mapbox Style Spec coverage.

Single source of truth for what the convertMapboxStyle pipeline handles. Each row is checked against the converter source at build time — entries marked supported must have a matching reference in the converter; new converter cases that miss a table entry fail CI. See the migration guide at Mapbox migration for narrative context and examples.

Summary

Supported
134
55% of 242
Partial
21
Lossy or runtime gap
Unsupported
80
Dropped on convert
N/A
7
No xgis equivalent

Reading this table

Supported — converter emits an xgis form AND the runtime honours it. Partial — converter emits SOMETHING but loses information (e.g. exponential interpolation folded to linear) or the runtime side has a gap. Unsupported — silently dropped or warned. N/A — Mapbox-only concept with no xgis equivalent and no plan to add.

Impact tier reflects user-visible severity in common basemap styles (OFM Bright, MapLibre demo), not effort to fix.

Top-level style properties

Fields on the root Mapbox style object.

Property Status Impact Note Source
version n/a Spec versioning; ignored.
name supported Emitted as a leading /* comment */ in the converted xgis. mapbox-to-xgis.ts
metadata unsupported low Silent drop — informational only in Mapbox.
center supported Applied by the demo-runner Mapbox importer after `runSource()` via `Camera.centerX/Y` + `markCameraPositioned()`. URL-hash camera still wins (hash parsing runs first). Compiler does NOT encode camera state into xgis source — top-level camera lives in the runtime, not the DSL.
zoom supported Same path as `center` — runtime-side via demo-runner.
bearing supported Same path as `center` — runtime-side via demo-runner.
pitch supported Same path as `center` — runtime-side via demo-runner.
sources supported sources.ts
layers supported layers.ts
sprite supported Importer extracts the URL from raw JSON and forwards to XGISMap.setSpriteUrl(). Runtime IconStage fetches `${url}.json` + `${url}.png` (DPR>=1.5 tries `@2x` first) and renders bitmap icons; SDF icons + icon-text-fit are Phase 2. Unknown icon names dropped silently at prepare-time; iter 526 added IconStage.getMissingIconNames() diagnostic for post-load misses.
glyphs supported Importer extracts the URL from raw JSON and forwards to XGISMap.setGlyphsUrl(). Runtime TextStage fetches MapLibre SDF PBFs and upgrades visually when available; Canvas2D fallback stays on for offline / missing-glyph cases. Not encoded in xgis source.
transition unsupported low Per-property fade-in dropped.
light unsupported low No fill-extrusion ambient lighting model.
fog unsupported low Mapbox v3 distance-fog gradient. Would need a post-process pass with depth-based mixing.
terrain unsupported medium Roadmap Batch 4 (raster-dem + hillshade).
projection partial low mercator only; URL `?proj=` provides limited overrides at runtime.
imports unsupported Mapbox v3 style-import not parsed.

Source types

`sources[id].type` values.

Property Status Impact Note Source
vector (.pmtiles) supported Routed to PMTilesBackend. sources.ts:38
vector (TileJSON) supported Runtime fetches manifest then attaches PMTiles backend. sources.ts:41
pmtiles supported Community-extension type ("type":"pmtiles") accepted as a sibling of the .pmtiles-URL detection path. sources.ts:94
tilejson (explicit) supported Third-party convention: `"type":"tilejson"` directly. Routed alongside the `vector` + URL-sniffing path. sources.ts:105
raster supported sources.ts:48
geojson (URL) supported sources.ts:73
geojson (inline) supported Captured via inlineGeoJSON collector → auto-pushed after run(). sources.ts:77
raster-dem partial medium Source registered, no hillshade renderer yet (Batch 4). sources.ts:57
image unsupported low Single-image source (e.g. user-supplied PNG draped onto a quad). Not in current loader; raster is the closest substitute.
video unsupported low Streaming video source. Not in current loader.

Layer types

`layer.type` values.

Property Status Impact Note Source
background supported Lifts to top-level `background { fill: # }` directive. mapbox-to-xgis.ts:82
fill supported
line supported
symbol (text) supported TextStage renders SDF glyphs from Canvas2D fonts. layers.ts:154
symbol (icon-only) unsupported high No text-field → skipped. Awaits Batch 2 (sprite atlas). layers.ts:159
fill-extrusion supported Extruded polygon with per-vertex z.
raster supported
circle supported Routes to the runtime PointRenderer (SDF disks). circle-radius/-color/-stroke-color/-stroke-width/-opacity all map onto the existing point utility surface, including interpolate-by-zoom + data-driven forms. layers.ts:514
heatmap unsupported medium Batch 3 (accumulation MRT + Gaussian blur). layers.ts:18
hillshade unsupported medium Batch 4 (raster-dem + lighting shader). layers.ts:19
sky unsupported low Atmospheric sky dome (sky-color / sky-atmosphere-* / sky-type). Layer-level skip added to SKIP_REASONS so the converter emits an explicit // SKIPPED comment with diagnostic note rather than falling through to the generic handler. layers.ts:SKIP_REASONS

Layer common fields

Shared across all `layer` shapes regardless of type.

Property Status Impact Note Source
id supported Sanitised into a valid xgis identifier. layers.ts:520
type supported Discriminator — see Layer types table above.
source supported layers.ts:521
source-layer supported Lowered to `sourceLayer: "..."` block prop. layers.ts:522
minzoom supported PR #81: enforced at every label submission via `inZoomRange`. layers.ts:523
maxzoom supported layers.ts:524
filter supported Legacy + expression form; routes through filter-eval. layers.ts:525
metadata unsupported low Informational — silently dropped.
ref n/a Deprecated layer-ref shorthand (Mapbox style spec v7).

Layout — fill / line

Property Status Impact Note Source
visibility supported `none` → `visible: false`. layers.ts:538
line-cap supported butt / round / square literals only. layers.ts:548
line-join supported miter / round / bevel literals only. layers.ts:552
line-miter-limit supported Constant only. layers.ts:556
line-round-limit unsupported low Limit beyond which round joins switch to bevel. X-GIS line-join logic uses a fixed threshold; per-layer override not threaded.
fill-sort-key unsupported low Per-feature fill draw-order. X-GIS uses layer-order; per-feature would need an additional sort pass.
line-sort-key unsupported low Per-feature line draw-order. Same gap as fill-sort-key.
circle-sort-key unsupported low Per-feature draw-order key for circle layers; current renderer ignores it.

Layout — symbol

Property Status Impact Note Source
symbol-placement supported point / line / line-center literals; `["step", ["zoom"], …]` form expands to multiple layers with intersected minzoom/maxzoom + segment-resolved placement (OFM Bright highway-shield-* coverage). Non-zoom step inputs fall back to default placement. layers.ts:447
symbol-spacing supported Defaults to 250 px when missing on line placement. layers.ts:471
symbol-avoid-edges unsupported low Skip labels whose bbox crosses tile boundaries. Useful for de-duping labels at tile seams; X-GIS today uses cross-tile collision instead.
symbol-sort-key partial medium Constant numeric value plumbed end-to-end (iter 399-405). Runtime collision pass sorts CollisionItems by sortKey ascending — lower wins. Expression form (`["get", "rank"]`) flattens to 0 with a warning. layers.ts:702
symbol-z-order unsupported low Per-feature draw-order override. X-GIS uses symbol-sort-key for ordering today; symbol-z-order would need a separate sort pass after collision.
text-field supported String / {token} / expression / number / boolean / null. Colon-bearing locale keys route via `get("name:xx")`. layers.ts:164
text-font supported Family extracted, weight + italic stripped into `label-font-weight-N` / `label-italic`. layers.ts:417
text-size supported Constant + interpolate-by-zoom + per-feature expression (sizeExpr). layers.ts:231
text-max-width supported Default 10 ems for non-line placement (Mapbox parity). layers.ts:385
text-line-height supported
text-letter-spacing supported Constant + interpolate-by-zoom.
text-justify supported auto / left / center / right literals.
text-anchor supported Full 9-way (center / top / bottom / left / right + 4 diagonals). layers.ts:295
text-variable-anchor supported Real layout property (and legacy array-in-text-anchor) lower to anchorCandidates; runtime collision picks first non-overlapping. layers.ts:370
text-variable-anchor-offset supported Per-anchor em offsets; runtime applies MapLibre baseline shift. layers.ts:435
text-radial-offset supported Constant em; runtime fromRadialOffset per candidate anchor (MapLibre-parity). layers.ts:435
text-offset supported Constant 2-tuple only. layers.ts:329
text-rotate supported Constant only.
text-padding supported Constant + interpolate-by-zoom. layers.ts:351
text-transform supported uppercase / lowercase / none literals.
text-allow-overlap supported
text-ignore-placement supported
text-overlap partial low MapLibre overlap-policy enum (never / always / cooperative). always → label-allow-overlap; never → default; cooperative approximated as always (priority-aware collision pending) + warning. Wins over legacy text-allow-overlap when both declared. layers.ts:418
text-optional unsupported low Icons not implemented — moot.
text-rotation-alignment supported Literal map / viewport / auto. Honoured at runtime. map.ts:2369
text-pitch-alignment partial medium Converter emits, runtime ignores — labels never project onto ground plane. Iter 10 surfaced an explicit warning when `map` is authored (the gap-revealing case) so authors of pitched-view styles see the diagnostic. `viewport` and `auto` match X-GIS' billboard-rendering default and stay silent. map.ts:2461
text-keep-upright supported Per-glyph flip for line labels. text-stage.ts:509
text-writing-mode unsupported medium CJK vertical text would need a per-glyph rotation pipeline.
text-max-angle unsupported low Maximum angle between consecutive glyphs on a line-placed label. X-GIS uses a fixed threshold; per-layer override would thread through label-placement.
icon-image supported high Constant + data-driven match/case via label-icon-image-[<expr>] bracket binding. Per-feature evaluation in TextStage.applyFeatureExprs dispatches IconStage.addIcon. Iter 490 + 491 shipped 2026-05-18. Iter 535 verified end-to-end across the OFM Bright highway-shield path (road_N / us-interstate_N / us-state_N): the iter 531 null-comparison fix unblocks the shield-layer filter, the diagnostic quartet (iter 526/532/533/534) confirmed dispatch → vertex buffer → GPU draw all complete. The atlas ships shields as WHITE-on-transparent backgrounds (zero SDF sprites) so colored shield appearance comes from the text-field number overlay — not sprite tinting. layers.ts:1007 + map.ts:applyFeatureExprs
icon-size supported Constant + zoom-interp (iter 523). Bracket-binding `label-icon-size-[interpolate(zoom, …)]` lowers to LabelShapes.iconSize PropertyShape; runtime resolveNumberShape at dispatchIcon time. Data-driven (case/match/get) still drops with a warning — no per-feature path. OFM bright road_oneway / road_oneway_opposite (15→0.5, 19→1) honoured. layers.ts:1075
icon-rotate supported Constant degrees. layers.ts:641
icon-anchor supported Literal 9-way enum. layers.ts:627
icon-offset supported [x, y] in CSS px; split into label-icon-offset-x / -y utilities. layers.ts:631
icon-allow-overlap partial medium No icon collision queue yet — every icon places (matches `true` semantics). OFM label_city/town/village/city_capital authoring `true` (4 layers per fixture) renders correctly. `false` would suppress overlapping icons; not implemented (would need icon-side collision bboxes). Iter 495 status review.
icon-overlap partial medium MapLibre overlap-policy enum. `always` matches X-GIS default (every icon places). `never`/`cooperative` need icon collision bboxes (deferred). Iter 495 status review.
icon-ignore-placement unsupported medium Same icon-collision gap as icon-allow-overlap. "true" would let other labels overlap this icon's footprint.
icon-optional partial low Default `false` (icon required for label placement) is X-GIS' current contract — labels with iconImage place when both fit. OFM label_city/town/etc. all author the default. `true` (label may place icon-less) needs icon-side collision arbitration; not implemented.
icon-rotation-alignment supported medium All three values (map / viewport / auto) honored. "viewport"/"auto" map to X-GIS axis-aligned icons; "map" adds the per-segment tangent to icon-rotate at dispatch time under symbol-placement=line (OFM road_oneway one-way arrows). Compiler iter 506 emits label-icon-rotation-alignment-map; runtime adds tangent in dispatchIcon. layers.ts:1056 + map.ts:dispatchIcon
icon-padding unsupported low Per-icon collision-bbox padding. X-GIS uses a fixed 2px default per spec; per-layer override needs to thread through label-collision.
icon-text-fit unsupported medium Shield/badge backgrounds depend on this.
icon-text-fit-padding unsupported low Padding when icon-text-fit fits glyph bbox; dependent on icon-text-fit.
icon-keep-upright unsupported low Flip line-placed icons so they always face up. Currently icons follow the symbol-placement=line tangent without flipping.
icon-pitch-alignment unsupported low viewport (default) / map / auto. X-GIS uses viewport-aligned icons unconditionally; map mode would project the icon quad onto the ground plane.

Paint — background

Property Status Impact Note Source
background-color partial low Constant + CSS form only — interpolate-by-zoom of background falls through (rare).
background-opacity partial low Constant numeric form folds into background-color hex alpha (iter 47, mirror of circle-stroke-opacity iter 4). Zoom-interp / data-driven still warn — would need a per-frame uniform on the background-fill emit path.
background-pattern unsupported low Needs sprite atlas + tiled fragment. Batch 2 dependency.

Paint — fill

Property Status Impact Note Source
fill-color supported Constant + interpolate-by-zoom + per-feature case/match expressions. paint.ts:91
fill-opacity supported paint.ts:133
fill-antialias partial low Default `true` is X-GIS' permanent contract — fragment shader smoothsteps every fill edge. OFM bright `building` / `road_area_pier` / `road_pier` author `true` explicitly = no-op match. OFM liberty `landcover_wood`/`grass`/`ice` set `false` for a pixel-art look; that opt-out (4 liberty layers) is not yet implemented and renders smooth instead of stepped. Iter 14 added a specific gap warning when `false` is authored explicitly so the gap surfaces rather than silently dropping.
fill-outline-color supported Lowers to `stroke-<color> stroke-1` on the same fill layer — the xgis polygon renderer paints fill + outline in the same pass. Constant + interpolate-by-zoom. paint.ts:153
fill-pattern supported high Stage 2 (true UV-tiled bitmap) landed iter-181/182/183 2026-05-20. Sprite atlas bound at @group(0) @binding(5) on every polygon pipeline + dedicated `sprite_samp` at binding(6). `fs_fill_pattern` fragment shader samples the atlas at world-anchored UV computed from `abs_merc / pattern_repeat_m`; pattern repeat in Mercator metres derived per-frame from sprite design CSS-px width × WORLD_MERC / (256 * 2^cameraZoom) so the bitmap stays anchored to the ground. Pattern parameters pack into reused uniform slots (fill_color = UV bbox, fill_translate = repeat metres) so the 192-byte Uniforms struct is unchanged. VTR routes fillPattern shows to `fillPipelinePatternGround` (+ Fallback) variant; ground polygons on the baseBindGroupLayout path only — variant + featureBindGroupLayout pattern shows fall through to the Stage 1 sprite-centre-pixel colour. Constant string form supported end-to-end. Documented trade-offs: pattern shows cannot also use solid fill-color or fill-translate; extrude-pattern walls still flat (Stage 2 ground-only). paint.ts iter-177/181/182/183
fill-translate partial low Constant vec2 + zoom-interp last-stop approx end-to-end. Runtime WGSL u.fill_translate_x/y adds CSS-px offset converted to NDC at vs_main (`clip.xy += u.fill_translate * clip.w`). OFM building-top pseudo-3D roof offset honoured. Full per-frame zoom-interp deferred. Iter 501 + 508 shipped 2026-05-18. paint.ts:addFillTranslate
fill-translate-anchor unsupported low viewport / map coordinate space for fill-translate; depends on fill-translate path.

Paint — line

Property Status Impact Note Source
line-color supported paint.ts:102
line-width supported Constant + interpolate-by-zoom (linear AND exponential base) + per-feature width. PR #104 added per-frame zoom-stops; PR #108 conformance test pins differential parity with MapLibre createExpression() at z=4..20 (incl. fractional zooms). paint.ts:113
line-opacity supported paint.ts:133
line-dasharray partial medium Constant numeric array only — interpolate-by-zoom dasharray not lowered. Iter 27 sharpened the non-constant warning to name the specific shape (zoom-interp needs PropertyShape<array>; data-driven needs per-feature dash plumbing). paint.ts:126
line-blur supported Edge feathering in CSS px. The line shader uses `aa_width_px` to widen both the geometry quad and the smoothstep range so the edge soft-fades over `1.5 + blur` px each side. Constant only — interpolate-by-zoom warns and drops. paint.ts:190
line-gap-width supported medium Constant + zoom-interp last-stop approx end-to-end via stroke-gap-N utility. Runtime double-draws each line at ±(gap+stroke)/2 via writeLayerSlot (iter 499). OFM road-casing layers honoured. Iter 498 + 499 + 513 shipped 2026-05-18. paint.ts:addLineGapWidth
line-offset supported Positive Mapbox values (right of travel) → `stroke-offset-right-N`; negative → `stroke-offset-left-N`. The xgis line renderer threads `strokeOffset` through to the vertex shader including offset-aware miter / join geometry. Constant only — interpolate-by-zoom warns and drops. paint.ts:175
line-translate unsupported low CSS-px viewport offset for lines. fill-translate is supported via u.fill_translate_x/y; line-translate would need a matching line-renderer uniform. Iter 32 added a specific gap warning naming the missing u.line_translate_x/y plumbing.
line-translate-anchor unsupported low viewport / map coordinate space for line-translate; dependent on line-translate.
line-pattern supported low Stage 2 landed iter-185 2026-05-20. line-renderer declares sprite_atlas at binding 5 + sprite_samp at binding 6 (shared TileBindGroupLayout with VTR so iter-181/182 atlas binding is already attached). New `fs_line_pattern` fragment + `pipelinePattern` alpha-blend pipeline. Pattern shows route via getDrawPipeline(translucent, patternActive=true). World-anchored UV (abs_merc / repeat_m) — Stage 2.1 along-line UV (arc length + transverse v) is a follow-up refinement. UV bbox packed into stroke_color uniform slot (20-23); repeat metres packed into layer.color.r / .a via writeLayerSlot override. Constant string form supported end-to-end. iter-165 probe: ZERO line-pattern uses in OFM bright/liberty target fixtures, so visual A/B unavailable against current set — Stage 2 is insurance for other styles (USA OSM / custom sprites). line-renderer.ts iter-178/185
line-gradient unsupported low Gradient along the line via ["line-progress"]. iter-166 probe: ZERO uses in OFM bright/liberty (also 0 lineMetrics declarations) — empirically confirms the low impact rating. Implementation cost (iter-158 scoping, the renderer change is NOT the hard part): (1) PREREQUISITE — geojson-vt currently IGNORES source.lineMetrics (geojsonvt/index.ts:14, sources.ts:406). line-progress is normalised over the ORIGINAL feature but geojson-vt clips lines per tile, so the clip stage must track each clipped segment's [progressStart,progressEnd] fraction of the original arc-length. This compiler-tiler change is the bulk of the work. (2) line-segment-build.ts interpolates per-vertex progress 0..1. (3) new per-vertex progress attribute + WGSL line fragment samples a gradient LUT the converter emits from the line-gradient interpolate stops. ~5 files; multi-day; not a surgical fix. PMTiles vector sources can't support it anyway (don't preserve original-line arc-length across tile boundaries) — feature is GeoJSON-source-with-lineMetrics-true only, niche. paint.ts:218 specific warning

Paint — symbol

Property Status Impact Note Source
text-color supported Constant + interpolate-by-zoom + per-feature colorExpr. layers.ts:199
text-opacity supported Constant folded into label-color alpha (applyAlphaMultiplier). Zoom-interp + data-driven emit `label-opacity-[…]` → LabelShapes.opacity PropertyShape; runtime resolveNumberShape multiplies into resolvedColor.a + resolvedHalo.color.a per frame. Iter 113. layers.ts:480
text-halo-color supported Constant + interpolate-by-zoom. layers.ts:269
text-halo-width supported Constant + interpolate-by-zoom; PR #76 fixed scaling into SDF units. layers.ts:259
text-halo-blur supported Constant only at conversion; IR exposes a PropertyShape so future zoom-interp / data-driven emit lands without IR changes. layers.ts:283
text-translate supported Pixel-space offset added on top of em-unit text-offset. layers.ts:340
text-translate-anchor unsupported low viewport (default) vs map coordinate space for text-translate. X-GIS applies text-translate in viewport space only; the `map` mode would need MVP-aware offset.
icon-color supported SDF sprite tint. iter 138 (Plan §4): IconRenderer carries a per-vertex tint + fwidth SDF fragment path; one batch mixes raster + SDF quads (per-vertex sdf flag, no pipeline split). Constant + zoom-interp + data-driven all route through LabelShapes.iconColor PropertyShape<RGBA> (same contract as text-color); runtime resolveColorShape at dispatchIcon → IconStage tint. Raster sprites ignore the tint per Mapbox spec. layers.ts icon-color emit / icon-renderer.ts fs sdf branch
icon-opacity supported Constant + zoom-interp + data-driven all route through LabelShapes.iconOpacity PropertyShape. Runtime resolveNumberShape at dispatchIcon → IconStage.addIcon per-vertex alpha. Iter 113. layers.ts:1260
icon-halo-color unsupported low SDF icon halo colour. iter-162 probe (playground/scripts/sprite-sdf-buffer-probe.ts) fetched the live OFM bright sprite: 264 entries, ZERO SDF. icon-halo applies ONLY to SDF sprites (Mapbox spec), so for the dominant OFM target styles this property is a NO-OP — implementing the composite produces zero visual change there. impact reclassified medium → low. iter-138 SDF icon foundation (fragment branch + per-vertex tint) STAYS and correctly serves any future style with SDF icons (USA OSM highway-shield-heavy styles, custom sprites). Composite shader work (second smoothstep at edge-haloWidth, mirror fs_text) is straightforward; the spritezero buffer constant remains UNRESOLVED (pin via the probe when a style with SDF icons becomes the target).
icon-halo-width unsupported low SDF icon halo width. Same iter-162 disposition as icon-halo-color: OFM bright has 0 SDF icons → no-op on the target style. impact reclassified medium → low.
icon-halo-blur unsupported low SDF icon halo feather. Same iter-162 disposition: OFM bright has 0 SDF icons → no-op on the target style.
icon-translate unsupported low CSS-px viewport offset for icons. Symmetric with line-translate / fill-translate; not threaded through IconStage. Iter 35 added a specific gap warning noting X-GIS shares text-translate offset for both icon and text today.
icon-translate-anchor unsupported low viewport / map coordinate space for icon-translate; dependent on icon pipeline (Batch 2).

Paint — circle

Property Status Impact Note Source
circle-radius supported Constant + interpolate-by-zoom + per-feature expression. CSS px (Mapbox radius = xgis size). layers.ts:537
circle-color supported Constant + interpolate-by-zoom + per-feature case/match.
circle-opacity supported Mapbox 0..1 → xgis 0..100 scaled. Constant + interpolate-by-zoom.
circle-stroke-color supported
circle-stroke-width supported CSS px; constant + interpolate-by-zoom.
circle-blur unsupported low Soft edge for circles. Point-renderer fragment uses smoothstep AA already; a per-feature blur attr + wider quad would extend the soft band.
circle-stroke-opacity partial low Constant numeric form folds into stroke-color hex alpha (iter 4, Plan §4 partial landing — same pattern later applied to background-opacity in iter 47). Zoom-interp / data-driven forms still warn + drop — need a dedicated paint shape for per-frame uniform multiplication. layers.ts:circle-stroke-color block
circle-translate unsupported low CSS-px viewport offset for circles. Symmetric with fill/line/icon translate. Iter 34 added a specific gap warning naming the missing point-renderer translate uniform.
circle-translate-anchor unsupported low viewport / map for circle-translate; dependent on circle-translate.
circle-pitch-scale unsupported low viewport (default — radius constant on screen) vs map (radius scales with zoom). X-GIS uses viewport-scale unconditionally.
circle-pitch-alignment unsupported low viewport (default) vs map. X-GIS uses viewport-aligned circles; map mode would project the disc onto the ground plane.

Paint — fill-extrusion

Property Status Impact Note Source
fill-extrusion-color supported
fill-extrusion-opacity supported
fill-extrusion-height supported Constant + interpolate-by-zoom + per-feature expression. paint.ts:154
fill-extrusion-base supported paint.ts:165
fill-extrusion-translate partial low iter-180 routed through addFillTranslate alongside fill-translate. The fill-extrusion vertex shaders (vs_main_quantized + vs_main_quantized_extruded) already apply u.fill_translate_x/y; the converter just stopped dropping the value. Constant vec2 + zoom-interp last-stop approximation supported. Full per-frame zoom-interp deferred (mirror of fill-translate). paint.ts:259 iter-180
fill-extrusion-translate-anchor unsupported low viewport / map space for fill-extrusion-translate; dependent on translate.
fill-extrusion-pattern supported low Stage 2 landed iter-186 2026-05-20. New `fillPipelinePatternExtruded` + Fallback variants (vs_main_quantized_extruded vertex + extrudedZBufferLayout for per-feature z + fs_fill_pattern fragment). VTR routes extruded pattern shows via setPatternExtrudedPipelines + an extrudedPatternActive gate symmetric with the iter-183 ground path. Same world-anchored UV math as fill-pattern + line-pattern (abs_merc / repeat_m). Documented Stage 2 trade-off: pattern-extrude shows lose the per-fragment wall_shade lighting — sprite colour replaces the shaded fill rgb directly. Stage 2.1 (dedicated fs_fill_pattern_extruded that multiplies the sample by wall_shade) is a follow-up refinement. Constant string form supported end-to-end. iter-165 probe: ZERO uses in OFM bright/liberty target fixtures — Stage 2 is insurance for other styles. paint.ts:270 iter-179/186
fill-extrusion-vertical-gradient partial low Default `true` is honoured end-to-end — fragment shader applies a vertical gradient ramp (0.6 base → 1.0 roof) plus a roof bonus matching MapLibre. Setting `false` to disable the gradient is the remaining gap (would need a per-show flag + WGSL branch). Iter 12 added spec-default suppression so authoring true (the spec default) no longer surfaces a spurious "ignored property" warning; only `false` (the real gap) warns. Promoted unsupported → partial in the capability-table expansion (iter 59) since the default path is real Phase 9 lighting, not stub.
fill-extrusion-ambient-occlusion-intensity unsupported low AO would need per-vertex normal + screen-space AO pass. Not in current renderer.
fill-extrusion-ambient-occlusion-radius unsupported low See fill-extrusion-ambient-occlusion-intensity.

Paint — raster

Property Status Impact Note Source
raster-opacity supported Constant + interpolate-by-zoom + data-driven (all PropertyShape kinds) routed through the global RasterRenderer opacity uniform. Single raster show per scene is supported; multi-raster styles fall back to the first declared show. paint.ts:38
raster-hue-rotate unsupported low Rotate raster hue in HSL. Would need a fragment HSL-rotate pass.
raster-brightness-min unsupported low Lower bound of raster brightness remap. Fragment-shader linear contrast adjust.
raster-brightness-max unsupported low Upper bound of raster brightness remap.
raster-saturation unsupported low HSL saturation multiplier on raster sample.
raster-contrast unsupported low Fragment-shader contrast scale.
raster-fade-duration unsupported low Crossfade between zoom levels. X-GIS swaps tiles atomically; no fade.
raster-resampling unsupported low linear (default) vs nearest. Sampler is fixed to linear; per-show override would need a separate sampler binding. Iter 17 added spec-default suppression + iter 18 generic SPEC_DEFAULT_NO_WARN helper so authoring `linear` (matches X-GIS) is silent; `nearest` warns explicitly.
resampling unsupported low MapLibre v3 alias for raster-resampling — same semantic.

Paint — heatmap

Heatmap layer renderer is not implemented; every property here is unsupported pending a roadmap entry.

Property Status Impact Note Source
heatmap-radius unsupported medium Heatmap layer renderer not implemented — radius (px) defines per-feature Gaussian footprint.
heatmap-weight unsupported medium Per-feature contribution multiplier; no renderer.
heatmap-intensity unsupported medium Overall density scale (per-zoom interpolated); no renderer.
heatmap-color unsupported medium Density → colour ramp (interpolate over `heatmap-density`); no renderer.
heatmap-opacity unsupported medium Layer-level opacity; no renderer.

Paint — hillshade

Hillshade layer renderer is not implemented; raster-dem source is recognised but produces no output.

Property Status Impact Note Source
hillshade-illumination-direction unsupported medium Hillshade renderer not implemented (raster-dem source registered but unused). Direction in degrees from N clockwise.
hillshade-illumination-altitude unsupported medium Light elevation angle (0–90°); no renderer.
hillshade-illumination-anchor unsupported low map / viewport — whether the sun follows bearing; no renderer.
hillshade-exaggeration unsupported medium Vertical-relief multiplier; no renderer.
hillshade-shadow-color unsupported medium Shadow side colour; no hillshade renderer.
hillshade-highlight-color unsupported medium Lit side colour; no hillshade renderer.
hillshade-accent-color unsupported low Per-feature accent tint; no hillshade renderer.
hillshade-method unsupported low basic / combined / igor / multidirectional — different DEM gradient algorithms.
resampling unsupported low bilinear / nearest sampling of the DEM raster; depends on hillshade renderer.

Expression operators

Mapbox Style Spec v1 expression form (the bracketed `["op", …]` syntax).

Property Status Impact Note Source
literal supported Scalar + array forms. Null-valued wrappers (`["literal", null]`) treated as "property omitted" by the paint-helper gate (isOmitted in paint.ts). expressions.ts:33
get supported Bare field for identifier-safe names; `get("name:xx")` for colon-bearing locale keys. expressions.ts:25
has supported expressions.ts:43
!has supported expressions.ts:52
coalesce supported Lowers to xgis `??` chain. expressions.ts:59
case supported expressions.ts:65
match supported Routes through `match() { … }` when input is FieldAccess; ternary fallback otherwise. expressions.ts:83
step supported expressions.ts:185
let / var supported Pure substitution at convert time. expressions.ts:199
all supported
any supported
! supported
== / != / < / <= / > / >= supported
in supported Both expression form and legacy form. Empty value list lowers to constant `false` per spec. expressions.ts:560
!in supported
+ / - / * / / / % supported
min / max supported
^ / abs / ceil / floor / round / sqrt supported
sin / cos / tan / asin / acos / atan supported
ln / log10 / log2 supported
pi / e / ln2 supported Zero-arg constants.
concat supported
length supported
upcase / downcase supported
at supported Array indexing.
to-number / number supported Converter passes through to a coalesce chain; xgis evaluator coerces in arithmetic context. Iter 539 added spec-compliant `to_number(v, fallback…)` builtin in the evaluator for hand-authored xgis source / tooling chains that bypass the converter. evaluator.ts:to_number
to-string / to-boolean / to-color supported Converter passes through to coalesce chains; iter 539 added spec-compliant `to_string` / `to_boolean` builtins in the evaluator (null → "", number → str, etc.); iter 541 added `to_color` (hex regex validation, X-GIS hex-only — converter pre-resolves CSS names like "red" via tokens/colors.ts:resolveColor). evaluator.ts:to_string + to_boolean + to_color
rgb / rgba partial low Constant channels only — hex-encoded at convert time. Per-channel v8 literal-wrap (`["literal", N]`) accepted. expressions.ts:507
hsl / hsla partial low Constant channels only — converted via CSS hsl()/hsla() and re-hexed at convert time. Per-channel v8 literal-wrap accepted. colors.ts:69
interpolate (linear) supported
interpolate (exponential) supported Mapbox `["exponential", N]` lowers to `interpolate_exp(zoom, N, …)`; runtime applies the Mapbox curve formula. base=1 collapses to the linear fast path. paint.ts:46
interpolate (cubic-bezier) partial low Numeric-valued zoom AND data-driven interpolates densify at compile time into a piecewise-linear approximation (6 samples per segment, CSS bezier-eased via Newton-Raphson). Runtime sees a longer linear stop list and visually approximates the bezier curve. Non-numeric values (colour stops) still warn and fold to pure linear. Iter 60-62 landings. paint.ts:cssBezierEase + expressions.ts:interpolate handler
interpolate-hcl supported LCh (polar Lab, hue shortest-path) colour interpolation: hex stops densify at compile time (iter 61-62 linear, iter 137 exponential — 6 samples / segment); non-hex (data-driven) stops now route to the runtime evaluator case interpolate_hcl (iter 164) which parses each stop's y at eval time, interpolates in LCh, and returns a hex. Full coverage modulo exponential×non-hex (rare combination — still warns and downgrades). paint.ts + expressions.ts + eval/evaluator.ts interpolate_hcl
interpolate-lab supported Lab (D50) colour interpolation: hex stops densify at compile time (iter 61-62 linear, iter 137 exponential — 6 samples / segment); non-hex (data-driven) stops now route to the runtime evaluator case interpolate_lab (iter 164) which parses each stop's y at eval time, interpolates in Lab, and returns a hex. Full coverage modulo exponential×non-hex (rare combination — still warns and downgrades). paint.ts + expressions.ts + eval/evaluator.ts interpolate_lab
geometry-type supported Routes via synthetic `$geometryType` prop injected at filter-eval time. expressions.ts:263
id supported Routes via synthetic `$featureId` prop injected from `feature.id` (GeoJSON RFC 7946 §3.2; MVT feature.id) at every filter-eval site. Same pattern as `geometry-type`. expressions.ts:278
properties unsupported low Returns whole feature properties object — X-GIS expressions access by field name (`.field` / `["get","field"]`); no object literal accessor.
feature-state n/a Mapbox v8 dynamic property setter — no xgis equivalent.
typeof supported Returns Mapbox-shaped strings ("string" / "number" / "boolean" / "object" / "null"). expressions.ts:237
format partial low Span texts concatenated via xgis concat(); per-span opts (font-scale / text-color / text-font / vertical-align) dropped — X-GIS labels render with one style per layer. Iter 25 added per-section partial-drop semantics: when one section fails to convert (e.g. uses an unsupported accessor), surviving sections still concat — only ALL-sections-fail returns null. Pre-fix any single failure bailed the whole format expression and dropped the label silently. expressions.ts:208
image unsupported high Sprite atlas (Batch 2).
number-format supported Lowers to positional `number_format(input, minFrac, maxFrac, locale, currency)` (xgis has no object-literal syntax). Routes through Intl.NumberFormat at runtime; null slots use spec defaults. expressions.ts:275
collator unsupported low Locale-aware comparator for ==/!=/in. X-GIS uses byte-exact string compare. Surface as warning when authored.
resolved-locale unsupported low Returns locale string from a collator. Depends on collator support.
is-supported-script unsupported low Returns true if all chars in a string are renderable. X-GIS assumes Unicode-renderable. No-op gate.
array partial low Type-assertion drops to value pass-through (X-GIS arrays carry no per-element type tag, so the spec's "abort if not array" semantic is lost; in paint/filter use a non-array would null-cascade anyway). expressions.ts:163
slice supported String or array; Mapbox `["slice", input, start[, end]]`. Routes to JS String/Array `.slice` semantics. expressions.ts:248
index-of supported Lowers to xgis `index_of(needle, haystack[, from])`. Returns -1 when not found. expressions.ts:257
zoom supported Lowers to bare `zoom` identifier. Works in `interpolate(zoom, …)` / `step(zoom, …)` AND anywhere else (filter compare, case condition, arithmetic). expressions.ts:471
pitch unsupported low Returns current camera pitch in expressions (e.g. for pitch-aware styling). X-GIS expression scope has zoom but no pitch identifier yet.
distance-from-center unsupported low Returns screen-space distance from viewport centre for the current feature. Would need per-feature distance evaluation in worker.
distance unsupported low Geometry-to-geometry geodesic distance. Surface as warning when authored; would need spatial index for performance.
within unsupported low Point-in-polygon test for filter context. Surface as warning when authored.
accumulated n/a Heatmap-only.
heatmap-density n/a Heatmap-only.
line-progress n/a line-gradient only.
sky-radial-progress n/a

Filters

Legacy + expression form. Most filter operators reuse the expression infrastructure.

Property Status Impact Note Source
== / != / < / <= / > / >= (legacy form) supported Field-as-second-arg shape recognised. expressions.ts:420
in / !in (legacy + expression form) supported
has / !has supported
all / any / ! supported
match (boolean form) supported Lowers to OR/AND chain when all values are boolean literals. expressions.ts:335
$type unsupported low Legacy filter — use the new `["geometry-type"]` accessor instead. expressions.ts:414
$id unsupported low Legacy filter — use the new `["id"]` expression-form accessor instead. Mirror of $type → geometry-type migration.

Keeping this page accurate

The table lives in compiler/src/convert/spec-coverage.ts. A vitest regression (spec-coverage-drift.test.ts) scans the converter source files at build time and fails when:

  • • A new case 'X':, layout['X'], or paint['X'] reference appears in the converter without a corresponding table entry, or
  • • An entry marked supported has no matching reference in the source (catches stale entries after the converter loses a feature).

Was this page helpful?

Tell us what's missing