Skip to content

osm basemap: degrade sub-pixel shapes to dots, and stop compositing toward black - #128

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/q2-osm-map-reencoding-56p5e2
Aug 13, 2026
Merged

osm basemap: degrade sub-pixel shapes to dots, and stop compositing toward black#128
AdaWorldAPI merged 1 commit into
mainfrom
claude/q2-osm-map-reencoding-56p5e2

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Follow-up to #127. Two independent causes of the black core in the z12 overview, both reported from a live Berlin view where the outskirts rendered correctly — which is the clue that made it two problems rather than one.

1. Edges that cannot resolve

At z12 a Berlin building is a fraction of a pixel. Its outline was still stroked, and stroke colours are dark by design (other = #39424f), so in the dense core thousands landed on the same pixels and saturated them — while the fill, the colour that carries the meaning, never survived. Density made the map darker, which is backwards; the outskirts stayed legible only by being sparse.

dot_if_subpixel measures each simplified shape in the same 2^(z−24) world-pixel units encode_tile_bin projects with, and below 1.5 px collapses it to one centroid point carrying its class.

  • No wire change — a dot is npoints == 1, which both client parsers previously skipped at n<2.
  • A resolution rule, not a zoom rule — a no-op once a shape exceeds a pixel, so no zoom branch, and it disables itself as you zoom in. The under-budget city view is byte-identical.
  • Client, both paths: GL gains uDot[10] + uMode==4, drawn between the fill and line passes so road structure stays legible over the dot texture; Canvas2D gains a dots Path2D map in the same slot.
  • The dot takes the class fill colour, falling back to stroke for the fill-less line classes. Without that fallback road/rail/other would be transparent dots — recreating the exact disappearance being fixed.

2. Alpha over a dark ground

Every fill was dark and alpha'd (…80, …4d, …66), and #map's background — which is the land, since GL clears transparent and Canvas2D only clears — was #11151c. Each fill therefore composited toward black rather than replacing it: one polygon read as a faint tint, a stack of them converged on the ground colour.

Opaque fills also restore FILL_PASS's back-to-front ordering as the thing that decides what wins — what that ordering was written for, and what alpha was quietly defeating.

Palette is now opaque and OSM-Carto-shaped over #f2efe9 land: blue is water, green is vegetation, beige-grey is built.

Verification

  • cargo check -p cockpit-server — exit 0, no new warnings.
  • The embedded JS extracts and passes node --check (936 lines). It lives inside a Rust string literal, so nothing in the Rust build can see it — this gap is worth knowing about.
  • Both shaders compile and link in real headless WebGL2 (swiftshader), with uFill/uStroke/uDot/uMode/uView/uTrans/uPointPx and aPos/aCls all present.
    • The first run of that check reported a false PASS: it grepped the dumped DOM, which also contains the unexecuted 'PASS:' literal from the script source. The result is now read from the output div only. Recording it because a detector that can't distinguish success from failure is the failure mode worth naming.
  • Not verified: the live visual result. Dot density and the palette want a real browser look, and the 1.5 px threshold is a first estimate, not a measured one.

Draft pending that visual check.


Generated by Claude Code

…oward black

Two independent reasons the overview drew a black core, both reported from a
live z12 view of Berlin where the outskirts rendered correctly.

── 1. Edges that cannot resolve ──

At z12 a Berlin building is a fraction of a pixel. Its OUTLINE was still
stroked, and the stroke colours are dark by design (`other` is #39424f), so
in the dense core thousands of them landed on the same pixels and saturated
them, while the FILL — the colour that actually carries the meaning — never
survived at all. Density made the map darker, and the outskirts stayed
legible only because they were sparse.

`dot_if_subpixel` measures each simplified shape in the same 2^(z-24)
world-pixel units `encode_tile_bin` projects with, and below 1.5px collapses
it to ONE centroid point carrying its class. The wire is unchanged: a dot is
`npoints == 1`, which both client parsers previously skipped via `n<2`.

This is a RESOLUTION rule, not a zoom rule — a no-op once a shape exceeds a
pixel, so it needs no zoom branch and disables itself as you zoom in. The
under-budget city view is byte-identical.

Client, both paths: GL gains `uDot[10]` + `uMode==4` and draws the points
between the fill and line passes, so road structure stays legible over the
dot texture; Canvas2D gains a `dots` Path2D map drawn in the same slot. The
dot takes the class FILL colour, falling back to stroke for the fill-less
line classes — without that fallback road/rail/other would be transparent
dots, recreating the disappearance being fixed.

── 2. Alpha over a dark ground ──

Every fill was dark AND alpha'd (…80, …4d, …66), and `#map`'s background —
which IS the land, since GL clears transparent and Canvas2D only clears —
was #11151c. So each fill composited toward black instead of replacing it:
one polygon read as a faint tint and a stack of them converged on the
ground. Opaque fills also make FILL_PASS's back-to-front ordering the thing
that decides what wins, which is what that ordering was written for and
which alpha was quietly defeating.

Palette is now opaque and OSM-Carto-shaped over #f2efe9 land, because that
vocabulary is the point of a basemap: blue is water, green is vegetation,
beige-grey is built.

── Verified ──

- `cargo check -p cockpit-server` passes (exit 0, no new warnings).
- The embedded JS extracts and passes `node --check` (936 lines) — it lives
  in a Rust string literal, so nothing in the Rust build can see it.
- Both shaders COMPILE AND LINK in real headless WebGL2 (swiftshader), with
  uFill/uStroke/uDot/uMode/uView/uTrans/uPointPx and aPos/aCls all present.
  The first run of that check reported a false PASS by grepping the dumped
  DOM, which also contains the unexecuted 'PASS:' literal from the script
  source; the result is read from the output div only.
- NOT verified: the live visual result. The dot-density and palette choices
  want a real browser look, and the resolution threshold (1.5px) is a first
  estimate, not a measured one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NMeiLmtDKhomJNSo2ecbJw
@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7047aeb8-60e0-4b31-a96a-c1d554e4066f)

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review August 13, 2026 13:23
@AdaWorldAPI
AdaWorldAPI merged commit d0310cf into main Aug 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants