Commit graph

665 commits

Author SHA1 Message Date
Admin
d6a933ea5c map: nature labels + building-age and population choropleths
- natura2000/wetlands areas label their Dutch source names (naam_n2k /
  naam) in green at their centroids
- Building age: BAG bouwjaar choropleth over the building fills (rust =
  pre-1800 through blue = 2010s, gray = unknown), z13-14
- Population: CBS vierkant cells (vk500/vk100) yellow-to-deep-blue by
  aantal_inwoners at 45% opacity; empty cells stay transparent
- two more checkboxes in the Layers panel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 12:28:37 +02:00
Admin
ea201e40ad widgets: set_visible on a never-drawn view escalates to full redraw
A visible:false view that has never drawn has an empty area, so its own
redraw was a no-op — set_visible(true) only took effect on the next
unrelated full repaint, the classic "appears after hot reload" bug (hit
by the map app's layers panel; same class as the elevation graph fix).
Also: overlay_chargers_probe ignored-test builds a real Amsterdam tile
with the chargers overlay (450 icons verified).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 12:25:23 +02:00
Admin
9ca167c8b0 map: geodata overlay layers render — chargers, transit, nature, districts
Phase 1 of the layers.md track:
- MapView grows overlay_mbtiles_paths (semicolon list) + set_overlay_paths
  (stale-keeping rebuild). The loader opens each overlay mbtiles, clamps
  to its metadata zoom range, and fetches ancestor tiles with a
  quadrant transform when the overlay's maxzoom is below the requested
  tile (fills/strokes ride the existing clippers; points bounds-filter)
- overlay MVT layers merge into the normal tile build, styled by layer
  name: chargers = blue bolt icons from z12, transit stops dots z13 +
  route shapes above the road net, natura2000/wetlands translucent
  green tint (fill alpha now flows through fill groups) + outline,
  gemeente/wijk/buurt purple dashed boundaries by admin level
- examples/map: Layers button + checkbox panel (EV chargers, Transit,
  Nature areas, Districts; raster + rain marked as next phase)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 12:12:27 +02:00
Admin
a2e1f77326 map: footbridge deck uses pedestrian surface color; tracks join gray paths
bridge_area_fill is the gray bridge-structure color — the deck between
the rims needs the pedestrian street surface (white light / dark slab
dark). Track dots drop carto brown for the light-gray path family.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:45:47 +02:00
Admin
8605804807 map: footbridge rim/deck in correct passes, theme-derived edge colors
- the renderer paints all casings before all centers; the bridge rim
  was a center-pass line and covered the deck (dark-filled bridges).
  Rim is now the casing, the light deck the center, and the faint dots
  ride a companion style one sort-rank above
- outline colors (bridge rim, platform edge, pedestrian-area edge) now
  derive from theme fills via contrast_edge() — dark fills get a
  lighter rim, light fills a darker one — so dark mode flips them
  automatically instead of baking light-theme grays

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:42:00 +02:00
Admin
47bad601f7 map: footbridge three-pass rendering — dark rim, white deck, dots on top
The dotted centerline continues across the span (it does on osm.org,
just faint); the rim is a companion stroke job keyed off the deck color
so the two-pass StrokeStyle stays untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:34:35 +02:00
Admin
236e4de5d1 map: footbridges draw as white deck with dark rim, dots stop on the span
Matches osm.org's little outlined bridge box: dark gray casing, near-
white solid deck, no dotted centerline across the water.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:33:15 +02:00
Admin
caeb26f4b4 map: pedestrian-area edge line, slimmer footbridge decks
- road-polygon fills (pedestrian squares, wide walking-path areas) get
  carto's thin gray constant-px edge from z15
- footbridge deck narrowed (2.4x the dot width, was 3.4x) — the rope
  bridge at Artis read twice as fat as osm.org

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:30:44 +02:00
Admin
1a0cda9b11 map: zoo animal labels from attraction nodes, gray paths, footbridge decks
- the animal enclosure WAYS turned out absent from the detail archive
  (converter mystery, ids fine, no filter — parked), but every animal
  also exists as an attraction=animal NODE: those now flow through the
  micro_pois point path as label-only features (culture brown, no icon).
  Verified live at Artis: Zwarte slingeraap, Roze pelikaan, Rode vari,
  Goudwanggibbon, Aziatische olifant all place like osm.org
- walking paths are light gray dotted everywhere (user preference over
  carto salmon); restricted-access stays gray too
- footway bridges draw a small white deck under the dots — the little
  outline box carto shows at water crossings
- diagnostic probes for the Artis tiles kept as ignored tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 10:25:11 +02:00
Admin
2f114d5f9d map: area labels can finally compete — zoo animal names place now
- root cause found by driving a real Artis tile through the full build
  in a test: admission, extraction and compaction were all fine (42/42
  enclosures labeled in the tile buffers) — the labels died at placement
  scoring. Point-anchored area labels have a ~zero-length path, and the
  score formula's path-length term let every street name outscore them
  in dense viewports. They now get a 420px length credit and green_area
  ranks 4 (above pois, below streets)
- pedestrian streets/paths render carto-style: near-white core with a
  gray casing (both palettes) instead of falling through to defaults
- trees 2x: canopy disc 5.4 with a 1.7 dark core, matching osm.org
  proportions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:53:32 +02:00
Admin
17ae14b723 map: OSM layer-tag collision fix, barriers, two-tone trees, zoo polish
- CRITICAL: OSM's own layer=-1/1 stacking tag collided with our "layer"
  source-layer key (or_insert kept the OSM value) — any layer-tagged
  feature was unrecognizable downstream: the Artis tourism=zoo way
  (layer=-1) never got its boundary, and detail admission missed such
  features entirely. Source-layer name now owns the key; OSM's value
  moves to "osm_layer"
- barrier lines from the detail archive: walls/retaining walls dark
  thin, fences lighter, hedges green (the Artis perimeter wall)
- trees are now carto's light canopy disc with a dark center dot
- attraction rings force-close (tile clipping opened them — Aziatische
  olifant label was dropped)
- restricted-access paths (private/no/customers) gray out instead of
  public salmon inside zoos and private grounds
- micro street furniture (trees, benches, bins, dots) collides at ~45%
  radius so a tree row no longer knocks out its benches

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:44:50 +02:00
Admin
f3087e1fec map: pattern fills anchored to the map, staggered stipple + tree circles
- pattern coords now come from the vertex stage in tile-local space
  scaled to view px (v_world was post-camera SCREEN space — the pattern
  sat still while the map panned under it)
- courtyard gardens: smaller dots on an odd/even staggered grid;
  playgrounds keep the hatch; woods/forests/cemeteries get staggered
  OPEN circles (tree rings) instead of sharing the garden dots

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:38:56 +02:00
Admin
89595fcb3c map: procedural pattern fills — garden/cemetery stipple, playground hatch
carto textures its fills; ours were flat. Fill groups now carry a
pattern shape (30 = dot stipple for leisure=garden and landuse=cemetery,
31 = diagonal hatch for playgrounds) and the map shader darkens the
fill color procedurally in screen-stable world space — no textures,
pans/rotates with the map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:34:50 +02:00
Admin
c5dccb78e5 map: Artis zoo pass — enclosure labels, sand fills, zoo boundary, cemetery green
- named zoo enclosures/attractions (attraction=animal etc.) from the
  detail archive label at their centroid in carto's culture brown; area
  labels no longer require a fill color, so a named enclosure on plain
  grass still gets its name
- natural=sand/beach/shingle fill carto's pale tan (theme rule both
  palettes); sand-floored enclosures rank above the zoo's grass
- tourism=zoo/theme_park perimeter draws a muted purple constant-px
  boundary line
- landuse=cemetery gets its proper green (was generic gray — Joodse
  begraafplaats Zeeburg)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:32:38 +02:00
Admin
d7704ea424 map: angle-gated overzoom smoothing, no dark casing on footpath bridges
- Chaikin corner-cutting now requires a GENTLE turn (< ~30 degrees) in
  addition to short adjacent segments: densely sampled canal-belt quay
  roads still carry sharp corners at bridge junctions between short
  segments, and cutting those curved the road through the corner
  buildings and off the bridges
- the "black dashed fragments" at every canal crossing were the dark
  bridge casing applied to thin footway/steps bridges (a sub-px 0x4a4a4a
  edge under 0.9px salmon dots); carto only cases real roads — thin
  uncased paths keep their plain dotted style on bridges
- tile.rs: keep an ignored bridge_probe diagnostic test (dumps line
  features near a point in a real tile with tags)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:28:07 +02:00
Admin
c7a739aacd map: adaptive Chaikin (fix roads curving through buildings), square force-close
- overzoom smoothing regression: uniform Chaikin also cut real 90-degree
  street corners, rounding roads through building blocks and detaching
  them from their bridges. Now only vertices with BOTH adjacent segments
  under ~10 screen px get cut (dense tile-quantized curves); sparse
  vertices are genuine corners and stay sharp
- pedestrian squares: area=yes semantically means polygon — close the
  ring unconditionally (tile clipping can leave LineString rings open)
- examples/map: headless UI smoke test (makepad_test) + local data
  symlink so the in-process hub run finds local/maps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 09:24:03 +02:00
Admin
0c47be94ef map: implicit ring closure for osm_lines areas, icon collision priority
- Hella Haasseplein root cause: pedestrian squares in osm_lines arrive
  as LineStrings, so `closed` was never set and the area admission
  skipped them — detect implicit closure (first==last) and mark the
  ring closed for the fill pass
- icon collision: doors and generic dots now yield to real symbols
  (the recycling icon was losing to the building entrance beside it);
  entrances gate at z18 like carto
- tools: tagprobe <pbf> <name> — dump every OSM element matching a
  name, for answering "how is this actually tagged" without overpass

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:44:12 +02:00
Admin
d52731c769 map: polygon-anchored POIs, pedestrian squares, top-to-bottom vertical labels
- polygon POIs (parking lots, garages, shops/offices mapped on their
  building) now icon at the ring centroid like carto — this is what
  makes the parking P's appear; underground garages skip the centroid
  (carto shows their entrance instead: amenity=parking_entrance now
  draws the P) and named parkings get a blue name label
- pedestrian squares (Hella Haasseplein): admitted from the detail
  archive into the street-area fill pipeline — including closed
  highway=pedestrian area=yes ways that live in osm_lines, since
  highway ways don't classify as polygons at conversion — and named
  squares/greens both label at their centroid
- vertical street labels read TOP-TO-BOTTOM (osm.org convention, user
  preference), never bottom-to-top; upside-down protection stays via
  the text-span chord

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:40:25 +02:00
Admin
e4054b72be map: station parity batch — platforms, POI icons, label robustness
- platform polygons (railway/public_transport=platform from the detail
  archive) as gray slabs with a constant-px edge, 2D and 3D, z15.5+
- new icons: traffic signals, parking P, EV charger, plus a generic
  colored dot for named POIs with no dedicated symbol (offices like
  TomTom get dot + name label from the detail layer); per-icon zoom
  gates (entrances z17.5, signals/chargers/dots z16.5, parking z15.5)
- rail: sidings/yards (service=*) fade lighter and thinner like carto's
  dead-end tracks; railway landuse gets carto's pale purple tint
- small green patches (verges, lawns, scrub) restored at street zoom
  from the detail archive — z14 base generalization drops them
- overzoom Chaikin smoothing on stroke geometry: ovals and tram loops
  stop reading as polygons at 8-16x magnification; roundabout/circular
  junctions get oneway arrows without an explicit oneway tag
- labels: reading direction decided by the chord across the whole text
  span (mid-point tangent flipped labels 180 on zigzag segments), and
  small rotation deltas reuse the cached placement rigidly rotated
  about the pivot — no more per-frame wiggle during heading-up nav

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:30:35 +02:00
Admin
0ce4f3c669 map: entrance/information icons, rail stripe proportions, flat-city elevation
- entrance door icons (entrance=*, railway=subway_entrance) from the
  all-tag detail layer at door-level zoom (z17.5+); tourism=information
  (i) icon in both the shortbread poi and detail micro-poi paths
- rail sleeper retune vs OSM reference: thinner band (casing 2.4, core
  1.2) with longer airy stripes dash(8,8); sleeper look starts z15 so
  zoomed-out station corridors stay a calm thin line; trams only darken
  toward near-black at z16+
- elevation: median spike removal + ~220m smoothing + 2.5m climb
  hysteresis — SRTM rooftop noise no longer fakes 50m of ascent across
  pancake-flat Amsterdam (test asserts single-digit climb)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:17:24 +02:00
Admin
e66b64ddeb map: Europe searchdb, route elevation profile, rail/tram/water-label styling
- libs/map_nav searchdb: disk-backed all-of-Europe search index (120M docs,
  8GB, ~30ms cold queries via pread; external-sort builder, cell-sharded
  postings, rank heads, digit-token address filtering with street fallback);
  nav-build --searchdb two-pass pbf streaming; nav-probe dbsearch; app
  worker prefers local/maps/europe.searchdb over the places index
- examples/map elevation: dem.rs fetches AWS skadi SRTM tiles on demand
  (cached local/maps/dem/, offline after), median+220m smoothing and 2.5m
  climb hysteresis so radar rooftop noise doesn't fake ascent in flat
  cities; elev_graph.rs DrawVector profile panel above the route bar
- rail styling: heavy rail draws carto sleeper look (theme casing + white
  stripe dash) z14+; trams/metro stay a solid near-black line (shortbread
  tags trams rail=true — kind decides now); platform/station/dead railway
  values no longer draw as track linework
- water name labels: water_polygons_labels/water_lines_labels admitted
  through extraction, source ranks, and LABEL_CLASS_WATER colors; label
  layers excluded from stroke styling (double-drew as rivers)
- label fix: near-vertical reading-direction tie-break was inverted;
  vertical street names read bottom-to-top under any camera rotation
- cleanness: oneway arrows lighter+sparser, tree discs smaller

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 23:13:47 +02:00
Admin
9f067b5623 map: continent-wide place search with Google-style tiered ranking
score_search_hit: settlements are near-immune to distance ("brussels"
from Amsterdam means Bruxelles, not the closest Brusselsestraat),
POIs/streets keep strong local bias, and a number token signals
address intent. nav-build --places-only scans a pbf for settlement
nodes into a compact index (Europe: 1.31M places, 67MB, 60s); the app
merges it with the regional full index, deduping same-name near hits.

Route overlay decimation now measures against the last drawn point,
bounding the error at ~1.5px — pairwise skipping compounded and
visibly reshaped the route when zoomed out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 22:16:44 +02:00
Admin
cda49d8c9f map: readable search results, second-search fix, double-click routing
Search results render as two-line cards (name, then category · address
· distance) instead of one cramped line. Picking a result clears the
input — the next query no longer appends to the old text, which broke
every follow-up search. The nav worker restarts on demand if a script
hot-reload wiped the channel state.

Long-press never synthesizes from a held mouse button on desktop, so
double-click now triggers the same action (set position, then route
here); hints updated accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 22:09:57 +02:00
Admin
ff309a5f1b map: true 2D/3D toggle — tiles rebake when tilt crosses zero
Flat mode returns to the classic building style (base fills +
outlines) instead of a top-down view of the extruded roofs: crossing
between flat and tilted bumps the style epoch and sentinels every
resident tile's bucket, so the normal stale-bucket restyle path
rebuilds them while the previous geometry stays visible and
cross-fades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:57:32 +02:00
Admin
ed90441a5f map: tilt-mode hardware depth, camera drag gesture, z-fight fixes
Tilted rendering now resolves occlusion in the depth buffer: map
geometry lives in a negative depth domain (UI/labels/overlay drawn
later always win by call order), view-ground y dominates so walls and
roofs occlude anything behind them under any rotation, and a baked
sort-rank micro-depth in param5 (4 depth quanta per rank step) orders
overlapping layers at equal ground without quantization flicker.
Bridges bias their micro-depth like their sort rank, the dark bridge
edge sits one step under the road fill (exact ties noise-win — black
bridges), and nested greens get distinct sub-ranks (park < garden <
grass < pitch) so parks stop shimmering. Flat mode stays on the
classic call-order path, byte-identical.

Camera ergonomics: right-mouse-drag (or Option-drag) rotates the
camera horizontally and tilts vertically, emitting ViewportChanged on
release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:51:02 +02:00
Admin
adbdbeaa2d map: 2.5D mode — tilt camera and extruded shaded buildings
Axonometric tilt as camera uniforms: screen y compresses about the
view center and building vertices carry their height in meters in
param4, lifted toward screen-top by height * px_per_m * sin(tilt) —
tilt animates freely with zero tile rebuilds. All CPU projections
(gestures, zoom anchor, screen<->lonlat, tile-selection AABB, label
placement + cache, overlay) undo the tilt alongside the rotation.

In 3D mode building footprints come from the all-tag detail archive
(real height / building:levels tags, 8m default) replacing the base
building fills: per-edge flat-shaded wall quads (NW light) painted
north-first as the painter's occlusion approximation, then the lifted
roof. The example app gets a 3D toggle with an eased tilt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:25:21 +02:00
Admin
b634482efa map: admit osm_points through the MVT point filter
The detail archive's micro-POIs never reached the collector: the
decode-time point filter only admitted pois/addresses/road-label
layers. Bellamyplein now renders its trees, benches, statues, bins,
recycling and bicycle parking like the osm.org reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:10:18 +02:00
Admin
c00bd23050 map: heading-up rotation, micro-POI detail composition, per-frame sim
Heading-up camera: MapView rotation (bearing-up degrees) as uniforms in
the map shader — geometry and map-aligned glyphs (oneway arrows) rotate
about the view center, POI symbols stay upright billboards. Rotation-
aware tile selection AABB, pan/zoom-anchor/screen<->lonlat math, overlay
projection and puck heading, label placement (street labels follow the
rotated baseline, point labels stay horizontal) and the label pan-shift
cache. The nav app eases the camera onto the travel bearing with a
shortest-arc exponential and resets to north when navigation ends.

Micro-POIs: an optional all-tag detail archive (detail_mbtiles_path)
composes over the shortbread base at icon zooms — trees (canopy discs),
benches, waste baskets, recycling, bicycle parking, playgrounds and
artwork/memorial statues, matching osm.org's park furniture. Icons
only; base labels are never duplicated.

Simulated drive now ticks on NextFrame instead of a 20 Hz timer, so
the follow camera moves once per rendered frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 21:06:07 +02:00
Admin
9e7b3b8576 map: GPU re-expandable strokes — zoom-invariant widths on stale tiles
Stroke geometry now bakes the centerline anchor per vertex (svg
tessellator emits anchors; offsets ride in param1/2, width-growth
class in param3, shape_id+100 marks expand mode — the shared vertex
format is unchanged). The map vertex shader re-expands each stroke
with a per-class width correction (regular roads, thin paths/rails,
waterways, constant-px building outlines) computed from the tile's
styled bucket vs the live fractional view zoom.

Tiles rendered at a stale zoom bucket keep the exact widths a fresh
restyle would produce, through the whole gesture: no fat roads while
zooming in, no width snap when the rebuild lands, dash dots and
building outlines stay crisp. Fading outgoing generations carry their
own bucket so they correct too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 20:39:23 +02:00
Admin
f22f9b7c16 map: carto-style zoom gating for paths and waterway width curve
Per-rule min_zoom on road/waterway rules: footway/path/steps hidden
below z15, cycleway/track below z14, streams/ditches below z13 and
canal lines below z12 — carto hides these long before roads; at city
scale they read as dot confetti over every block.

Waterway centerlines shrink faster than roads below z14
(zoom_mult^1.6): linear scaling kept Amsterdam's canal grid at near
full width at z12 and the whole city read as water; the water
polygons carry the visual weight at those zooms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 20:23:41 +02:00
Admin
f099c05750 map: Europe tile conversion pipeline + offline GPS navigator
Conversion tooling (tools/map_tiles): curated Shortbread base pyramid
from a VersaTiles planet archive (bbox extract, brotli->gzip transcode
parallelized per 256x256 block), all-tag native OSM pbf detail
converter, pbf audit, and nav-build/nav-probe producing the routing
graph + search index artifacts. download_map.sh orchestrates pinned
downloads and conversions. mbtile_reader writer now skips SQLite's
lock-byte page at byte offset 1 GiB — allocating through it corrupted
every database over 1 GiB ("2nd reference to page 16385"); the 31 GB
Europe conversion passes integrity checks.

Navigation (libs/map_nav, new): region.search place/POI/street/address
index (prefix autocomplete, NL/EN category synonyms, proximity
ranking) and region.graph routing graph (CSR directed edges,
car/bike/foot speeds, oneway, turn restrictions, snap grid, A*),
maneuver generation and the NavSession map-matching state machine;
26 unit tests.

MapView interaction layer: MapViewAction, camera API + animated
fly_to, overlay.rs (route polyline with traveled dimming, markers,
position puck), per-widget mbtiles_path override, archive
minzoom/maxzoom honored for tile requests, zoom-level cross-fade over
the previous level's imagery, floating panels win hit-testing.
examples/map is the navigator app: worker-thread search, Drive/Bike/
Walk routing, simulated turn-by-turn with banner, follow camera.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 20:13:21 +02:00
Admin
302f3bc6c8 map: bridge casings, green area labels, icon collision
- bridge roads get a dark casing edge (carto bridge style); global
  casing pass keeps it under all road fills
- named parks/green areas emit centroid labels in carto green (z15+),
  new LABEL_CLASS_GREEN; health-class label color wired in the view
- icon-vs-icon collision at bake time: first symbol wins within an
  icon-sized neighborhood, so dense shopping streets show distinct
  symbols instead of a stacked carpet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:54:13 +02:00
Admin
7c4bb0cbdc map: oneway arrows
Zoom-constant arrow glyphs (shaft+head) spaced every ~130 screen px
along oneway highways, offsets pre-rotated into the travel direction
(oneway_reverse honored), emitted into the symbol buffer so they draw
above road centers. Same anchor+screen-offset encoding as POI symbols.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:50:17 +02:00
Admin
51aa6f0fbb map: typed MVT decode — no more JSON round trip
Local tiles decode the MVT protobuf straight into tile-local f32
features (MvtSink trait + MvtLocalCollector with scale-aware thinning);
the MVT -> generated-Overpass-JSON -> parse detour is gone (a ~1-3MB
binary tile inflated to ~10MB of JSON re-parsed on every restyle, most
of the ~1s per-tile rebuild). The shared feature builder now takes
tile-local ways/points; the Overpass network path projects into the
same structures. MvtTileJsonBuilder deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:46:34 +02:00
Admin
7305a52f17 map: cross-fade tile generations instead of popping
New tile geometry fades in over 250ms via a tile_fade uniform
(multiplies the premultiplied output); the replaced generation's
geometry stays drawable underneath until the fade completes, so bucket
restyles and fresh tiles blend instead of flickering in. A 16ms timer
keeps frames coming while any fade is active (the paint beat idles
without input).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:37:02 +02:00
Admin
65814d02dc map: labels track zoom via affine cached draw; no more vanishing
Cached label placements were redrawn shifted by the raw map-offset
delta; during a zoom the offsets change by world-scale amounts, flinging
the cache thousands of px off-screen (labels vanished just off max zoom
and stayed gone while idle because the re-place rate limit counted
frames). Now: cached glyphs draw through the exact affine screen
transform s*k + (off_new - off_old*k) with glyph size scaled, so labels
track the map mid-gesture; the re-place limit is wall-time based; and
the settle timer re-arms until a budget-truncated label pass completes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:34:57 +02:00
Admin
e234774096 map: time-based zoom settle + timer wake — restyle without wiggling
The settle window counted FRAMES, but the paint beat idles when input
stops: after a zoom gesture no frames occur, the frame delta never
reaches the threshold, and stale-bucket restyles only fired when the
user wiggled the map. Settle checks now use wall time and the zoom
gesture schedules a 150ms timeout so a wake is guaranteed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:26:02 +02:00
Admin
fbd2620d65 map: request watchdog, at-rest full label pass, bigger tile label cap
- in-flight tile requests time out after 600 frames and clear stuck
  Loading placeholders; a leaked key would otherwise choke the 12-slot
  dispatch cap (zoom-only restyles appeared to never regenerate)
- label placement budget only applies during gestures; at rest a full
  pass runs, otherwise the lowest-priority tail (house numbers) was
  permanently truncated at the same point every re-place
- per-tile label cap 32768: a city-sized overzoomed tile can exceed 16K
  labels and the priority sort dropped addresses first

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:23:40 +02:00
Admin
fb7f8e3248 map: dispatch tile rebuilds as parallel per-tile jobs
Single-batch dispatch meant one tile in flight at a time after a zoom
(requests only advance when a draw happens, draws only happen when a
tile lands) — restyles trickled sequentially. Now every missing/stale
tile dispatches its own worker job, up to 12 in flight, so a viewport
restyle overlaps across the pool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:18:25 +02:00
Admin
d473637aa8 map: 120Hz pan/zoom — rate-limited label placement, parallel restyles
Gap-distribution instrumentation on the 7680x2160@120 display showed the
renderer can hold 120fps (gap_avg 8.4ms in calm stretches) but dropped
frames wherever labels are dense: full label re-placement (5-22ms) ran
almost every other frame during pan (tile arrivals + pan distance
invalidated the cache) and EVERY frame during zoom (exact zoom equality
in the cache check).

- full re-places rate-limited to ~8/s; between them the cached placement
  draws shifted (~2ms), including during zoom gestures where labels stay
  screen-pinned briefly (pinch behavior a la Google Maps)
- each placement pass gets a hard 7ms budget; highest-scored labels
  place first, the tail defers to the next pass
- gap avg/max and >12ms counts added to the perf log
- tile restyle after zoom: one tile per worker job (10-tile batches ran
  sequentially in one closure, 2-3s per batch), settle delay 150->80ms

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:16:44 +02:00
Admin
2b6d5f161e map: shrink tile geometry — the frame-gap stutter was GPU-side
Instrumentation showed draw_walk at ~1ms and CPU tile insert at ~2-3ms
while inter-frame gaps hit 200-1000ms during interaction: the stall is
GPU buffer upload/pressure from oversized tiles (a bucket-17 z14 tile
holds ~6.8K outlined buildings -> tens of MB of 19-float vertices).

- scale-aware vertex thinning: drop points closer than ~0.35 screen px
  at the styled zoom (invisible detail dominated vertex volume)
- building outlines use miter joins (round-join bevel expansion on ~60K
  ring points per tile was the largest single buffer)
- tile GPU uploads throttled to 2 per frame via a pending queue
- resident tile cache capped at 48 (was 640) with faster idle eviction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:06:03 +02:00
Admin
364b4fd575 map: frame-time instrumentation + label scan/draw cost fixes
- perf log: draw_walk aggregates total/geometry/label ms, max frame,
  inter-frame gap and full-placement count per 240 frames into
  local/map_perf.log
- labels precompute name_key + tile-local bbox at tile build; the
  per-frame scan does no allocation and rejects offscreen labels with
  two point transforms before any path work (an overzoomed tile holds
  ~16K labels, mostly offscreen)
- batched glyph path no longer runs update_draw_vars per call (the open
  batch already did); halo underdraw 8 -> 4 diagonal offsets, halving
  glyph volume
- fill clip overlap shrunk to ~1 screen px: the wider double-drawn
  strip let the later tile's land paint over the earlier tile's
  buildings (pale band at tile edges at high zoom)
- point local mbtiles back at noord-holland-shortbread-1.0.mbtiles

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:57:59 +02:00
Admin
ad22ee7ba1 map: label performance pass — batch, pan-cache, shape-cache
- one glyph instance batch per frame (begin/end_glyph_batch on
  DrawRotatedText): every glyph previously opened and closed its own
  aligned-instance batch, x9 with the halo underdraw
- label placement cache: pan-only frames redraw the previous placement
  shifted by the pan delta (like the tile geometry) instead of
  re-scanning up to 16K labels and re-shaping/re-colliding ~1-2K; full
  re-placement only after 48px of pan, zoom change, or tile updates
- shaped-run cache keyed by (text, quantized font_scale) so periodic
  re-placements stop re-shaping every string every time

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:47:40 +02:00
Admin
2c3203c28d map: settle-deferred zoom restyles, full house-number coverage
- zoom-bucket restyles wait ~0.3s after the last zoom change so line
  widths scale geometrically (smooth) during the gesture and re-style
  once, instead of flickering tile-batch by tile-batch mid-zoom
- per-tile label cap 16384 and larger z16.5+ budgets: an overzoomed z14
  tile holds a whole city's addresses and lowest-priority house numbers
  were truncated before the viewport filter ever saw them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:37:56 +02:00
Admin
cba9e6b851 map: zoom-constant vector POI symbols + green-area ranking fixes
- openstreetmap-carto symbol SVGs (CC0) tessellated once at final screen
  size and baked into a 4th tile-cached vector buffer; vertices encode
  anchor + screen-px offset, the map vertex shader (shape id 20) adds the
  offset after the map transform so symbols keep constant pixel size at
  every zoom — pure GPU vector, no raster atlas
- POI symbols colored by class (orange food, purple shops, brown culture,
  red health incl. pharmacies), labels shifted below the symbol
- symbols gated to z17+ and hidden immediately on zoom-out even while
  stale higher-bucket tiles are still displayed
- green areas (leisure/grass/forest/gardens) rank above generic landuse:
  they share the shortbread land layer with huge residential polygons and
  lost to protobuf feature order (Bellamyplein rendered gray)
- cache-only fallback when the local mbtiles source is missing (e.g.
  while a larger extract downloads)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:34:57 +02:00
Admin
d266571ab6 map: tile-seam overpaint fixes, label halos/colors/stability, z19
- per-end line caps (Tessellator::stroke_ends): butt at tile-clip cuts,
  round at true ends — road cap discs no longer stamp over neighbor
  tiles' tram tracks and roads at seams
- fill polygons clipped to their own tile square (Sutherland-Hodgman);
  a tile's MVT buffer fragments no longer overpaint the neighbor;
  building outlines skip segments running along the tile cut
- stroke clip padding 3px (under the generator buffer) so boundary
  cuts are detectable
- white label halos (8-offset underdraw, theme label_halo color)
- carto POI label colors (orange food, purple shops, brown culture,
  muted house numbers) from shortbread poi attributes
- label placement hysteresis across frames (no flicker while panning)
- street_polygons fill ranked below sites/parks (Bellamyplein-class
  plazas no longer cover the park inside them)
- max zoom 19 with width stops for z18/19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:53:36 +02:00
Admin
691021129b map: rebuild MapView to openstreetmap-carto quality
- semantic fill paint order (land/sites/water/buildings/street areas);
  raw MVT layer order was painting land over 6,880 buildings per tile
- tile-local f64 coordinates + per-tile offsets; fixes f32 web-mercator
  quantization (0.25px vertex, 2px shader ULP at z17)
- casing/center stroke buffers split; fills -> all casings -> all centers
  across tiles (carto roads-casing/roads-fill order)
- per-view-zoom-bucket restyling with carto width stops; stale buckets
  stay drawable during rebuild; screen-space AA/tolerance
- carto palette, building outlines z15+, bridge decks, tram/rail from
  streets layer above road centers, thin-path slow width growth
- house numbers (addresses) and shop names (pois) as point labels z16+
- MVT-correct absolute ring winding for multipolygon classification
- visible_tile_keys divides viewport by overzoom (was 64x over-request)
- text: prepare_single_line_run scales glyph size+raster with font_scale;
  draw_path_glyphs pins ambient font_scale (letter-spaced labels bug)
- script derive: cast numeric field defaults instead of .into() fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:34:44 +02:00
Athan
8278f31400 feat : add advance vide player example (#1138)
Co-authored-by: Athan Xiao <Athan.Xiao@one.nz>
2026-07-23 12:16:19 +02:00
Kevin Boos
087c80e236 Avoid redrawing for simple text-based widgets (#1141)
redrawing is significantly more expensive than text comparisons,
and doing this outside of each widget is difficult and less efficient.

this helped avoid a bunch of redraw cycles in robrix,
so it's probably worth doing for all widgets, especially since
setting text or another basic property now does an auto-redraw
(it didn't used to be like that)
2026-07-23 12:13:44 +02:00
Kevin Boos
d1efac1892 New unified kinetic scrolling. Vastly improve draw shaprness on low-res screens. Fix rendering and text perf issues on CPU+GPU (#1127)
* Fix rendering, gradient, sampling, etc issues on older GPUs

* Fix bug in `box_y` sdf function, which caused gradients to be split
  into two bands incorrectly. Mostly a problem on lower-res screens.
* Use per-texture filtering instead of GL sampler objects on Linux,
  especially for Mesa drivers that ignore min filter samplers.

This should help prevent blocky/pixellated things like emoji/avatars

* Improve rendering sharpness on low-DPI screens (icons, emoji, images)

On 1.0-DPI screens, emoji/SVG-icons/avatars were minified without
adequate sampling and SVG AA was sub-pixel, producing blocky/aliased
output. This reworks each path and adds optional full-window SSAA.

SVG icons (device-aware AA + round caps):
- draw_svg.rs/draw_vector.rs/render.rs: size the fill & stroke AA
  fringe and the curve-flatten tolerance in DEVICE pixels (≈constant
  regardless of icon size), so edges resolve via the analytic
  d/fwidth coverage and curves stay smooth at any scale; re-tessellate
  on scale change.
- triangulate.rs: thread the flatten tolerance through path fill/stroke.
- tessellate.rs: emit round caps as a solid disc (u=0.5) instead of a
  radial fade that collapsed to a square at small sizes.
- widgets/icon.rs: don't clip the Icon to its Fit bounds, so round
  caps that extend past the box render fully instead of being sheared.

Emoji:
- glyph_raster_image.rs/rasterizer.rs: rasterize color emoji near the
  on-screen size with an alpha-weighted box downscale (geometric-mean
  scale factor) instead of the font's native PNG strike.

Images / avatars (mipmaps):
- image_cache.rs/texture.rs/draw_list.rs/lib.rs: optionally emit a CPU
  mipmap chain (VecMipBGRAu8_32) for non-animated images so minified
  avatars/thumbnails sample cleanly. Env-gated MAKEPAD_IMAGE_MIPMAPS;
  default on for GL on Linux.
- metal.rs: real per-level mip upload. d3d11.rs/vulkan.rs/web_gl.rs:
  safe single-level fallback (no crash; real mips TODO).

Full-window supersampling (optional):
- window.rs: render the whole UI into an offscreen target at
  MAKEPAD_SUPERSAMPLE× device resolution and downscale-resolve into the
  window. Default 2×, env-tunable, 1× disables. Modeled on the existing
  GaussStack render-to-texture path.

* don't unconditionally enable supersampling SSAA of 2x by default

it's too expensive and too slow for most older devices

* cleanup, reduce comment verbosity

* improve SVG anti aliasing

* Windows: fix laggy/juddery scroll performance

- Pace the render loop to the display refresh using a DXGI frame-latency
  waitable object and present with vsync, replacing the free-spinning,
  uncapped Poll loop that caused uneven scroll cadence.
- Coalesce consecutive WM_MOUSEMOVE messages and paint once per loop pass
  to stop the judder when moving the mouse during fling deceleration.
- Cache get_dpi_factor() and the WM_NCHITTEST WindowDragQuery result to
  avoid per-mouse-move GetDeviceCaps syscalls and widget-tree hit-tests.
- Throttle XInput/DirectInput polling of empty/disconnected controller
  slots, which was stalling the UI thread.
- Rework the momentum fling to a native exponential model with a
  frame-interval EMA, and stop the tail auto-scroll from fighting an
  active fling/drag.
- D3D11: update the glyph atlas and image textures in place via
  UpdateSubresource instead of recreating them on every change, and
  spread D3D11 shader-object creation across frames.
- Slug atlas: only force a full re-layout on a width change; append rows
  on height growth.

* Windows: correctness fixes from review (off the scroll hot path)

- d3d11: close the DXGI frame-latency waitable HANDLE in Drop (it was
  leaked once per main-window lifecycle and the field comment was wrong);
  keep popup swap chains at frame-latency 1; track the waitable-swapchain
  flag for ResizeBuffers instead of inferring it from the handle; present
  without the vsync interval during a live resize.
- windows.rs: poll game input on the idle signal tick so a gamepad button
  can be serviced while the app is otherwise idle.
- win32_window / window: invalidate the WM_NCHITTEST / WindowDragQuery
  caches on window move and on a caption relayout, with a generation
  counter guarding against a reentrant invalidation being clobbered.
- windows_game_input: detect a controller already plugged in at launch via
  a one-shot full scan on the first poll, probe slot 0 (Player 1) first,
  and offset the DirectInput enumeration so it never stacks with the
  XInput probe.
- comment/doc corrections.

* Image cache: accept any Arc<D: AsRef<[u8]> + ?Sized> for async image data

The load_image_from_data_async family required Arc<Vec<u8>>, forcing callers
that already hold the bytes as Arc<[u8]> (e.g. a content-addressed media cache)
to copy the whole buffer via .to_vec() just to satisfy the type. Generalize the
data parameter to Arc<D> where D: AsRef<[u8]> + ?Sized, so those callers can pass
their existing Arc by refcount-clone with no byte copy. The decode path only ever
borrowed the bytes (&[u8]), so this is purely a signature relaxation; existing
Arc<Vec<u8>> callers are unaffected (D = Vec<u8>).

* Linux: GL glyph-atlas in-place texture update + X11/Wayland mouse-move coalescing

* Scroll: unified fling model + native trackpad momentum deceleration

Share one kinetic-scroll model between PortalList and ScrollBar (and thus
ScrollXView/ScrollYView/ScrollXYView) via a new widgets/src/scroll_motion.rs:

- Touch-drag flicks use an iOS-style exponential self-decay, frame-rate
  independent via a per-frame integrator with dt smoothing.
- Trackpad scrolling applies the OS momentum directly while fast (responsive,
  full native speed), then hands off to a gentler self-decaying tail once it
  slows past a threshold, so the deceleration is longer and smoother than the
  OS's short, choppy tail. Handoff is seeded at the current speed for a
  continuous transition; the seed is clamped against degenerate event timing.
- Add ScrollPhase to scroll events, mapped from NSEventPhase/momentumPhase on
  macOS and wl_pointer AxisStop on Wayland; None elsewhere (wheels/X11/Windows
  behave as before). MAKEPAD_RAW_TRACKPAD_MOMENTUM=1 bypasses the smoothed tail.
- A press catches an in-progress fling (stops the scroll, consumes the press so
  it doesn't also activate a child), matching iOS/Android/macOS.

* Shader codegen: prefix Metal/WGSL locals to avoid reserved-word collisions

The Metal/WGSL backends emitted user-declared shader locals verbatim, so a
local named after a reserved type keyword (e.g. `half`) produced invalid
shader source and failed to compile at runtime. Prefix them with `l_` like the
HLSL/GLSL backends already do.

* TextFlow: don't panic on unbalanced HTML close tags

end_code/end_quote unwrapped the area stack, so a stray `</pre>` or
`</blockquote>` in untrusted content (e.g. a chat message) panicked. Return
early instead, and drop a vestigial per-list-item area-stack push that leaked
an entry and could hand a stray close tag the wrong block's area.

* Scroll: expose fling decel, handoff threshold, & tail-decel as `#[live]` fields

This allows app devs to override the scroll feel per-widget in the DSL,
or globally by overriding the base widget's defaults — verified that a DSL
override takes effect.

* Windows: fix frame pacing, paste crash, and wheel input backlog

- Wait on the frame-latency waitable right before each window's vsync
  present instead of on every Paint, so input no longer stalls behind
  waits that have no matching present. Drain leftover credits after a
  live resize.
- Pasting when the clipboard has no text no longer panics.
- The poll loop now handles up to 32 messages (2 ms) per frame and
  merges consecutive mouse-wheel messages, so fast wheels can't build a
  backlog that keeps scrolling after the gesture ends. Sleep 1 ms when a
  frame presents nothing so animation polling doesn't spin a core.

* Image: don't build unused mip chains; fix stale images in recycled widgets

- Only build the CPU mip chain when a backend actually uploads it
  (Metal, behind its env var), and build it on the decode thread instead
  of the UI thread. Linux GL still gets its mipmaps via glGenerateMipmap
  and now retains less CPU memory per image.
- Recycled Image widgets no longer show the previous item's image or
  apply an old decode result. Placeholder textures set via set_texture
  (like blurhashes) stay visible while the real image decodes, and a
  failed load clears the widget instead of leaving old content up.

* Widgets: avoid needless caption redraws; cheaper PortalList height tracking

- Label::set_text does nothing when the text hasn't changed, and the
  window caption title is only synced when it actually changes, so mouse
  moves and animation ticks no longer redraw the whole window every
  event. The caption centering padding requests its own redraw now.
- PortalList records item heights only when new or changed, and only
  re-applies the default height after it drifts by half a pixel, so big
  lists don't walk every unmeasured item on every scroll frame.

* Text: cache layouts of long texts; stop cloning glyph outlines every frame

- The layout cache now accepts texts of any length (long messages and
  code blocks used to re-layout on every scroll frame). It is a real LRU
  with a byte budget on top of the entry cap, and texts drawn in the
  current frame are never evicted, so one heavy frame can't thrash the
  cache into a permanent miss cycle. The shaper cache is LRU now too.
- Glyph outlines are shared via Rc, so drawing a cached glyph no longer
  copies its command list, and outline complexity is computed once when
  the outline is built instead of every frame.

* Html: fix stale links/spans in recycled widgets and <details> renumbering

- set_text only rebuilds when the content actually changed, so a
  recycled link can't open the previous message's URL, and re-setting
  identical content keeps the user's <details> open/closed state.
- Custom widgets and <details> are keyed by their node index instead of
  a visit-order counter, so toggling a collapsed <details> can't
  renumber the widgets after it and rebind them to the wrong nodes.
  item_with_scope also recreates its widget when the template changes.

* Linux: fixed-distance wheel scrolling; Wayland frame-callback pacing

- Wheel scrolling moves a fixed 60 px per detent on X11 and Wayland
  instead of a timing-based guess that flipped between 12 px and 240 px
  depending on how events batched. Wayland reads real detent counts via
  AxisValue120 (wl_seat v9, with AxisDiscrete as the older fallback) and
  maps keymaps MAP_PRIVATE as v7+ requires. Touchpads are unchanged.
- Wayland frames are paced with wl_surface frame callbacks and swap
  interval 0, so redrawing a hidden or minimized window can no longer
  hang the whole app inside eglSwapBuffers (compositors withhold frame
  callbacks for hidden windows). Windows with a callback in flight skip
  presenting and stay dirty; X11 keeps vsync exactly as before.

* Text: bigger layout cache budget, reclaimed at the end of each frame

A maximal ~60 KB message lays out to roughly 4 MB of glyphs, so the 4 MB
budget couldn't hold even one alongside a normal screen. Raise it to
16 MB, and run eviction at the end of every frame so memory over the
budget is freed one frame after its content leaves the screen, instead
of lingering until some later layout happens to insert a new entry.

* Fix oversized uniform slices: the array lengths were in bytes, not f32 elements

* Wayland: flush buffered mouse motion before scroll events, and drop motion for closed windows

* GL: fall back to non-mipmapped filtering when glGenerateMipmap fails on strict GLES3 drivers

* Text: bucket emoji raster scales so zooming reuses atlas slots instead of re-decoding every step

* Image: add has_content() and record texture provenance on cache-hit loads too

* Scroll: native trackpad momentum, Chrome-model bounce; presses catch motion, never click children

* Dock: redraw the newly selected tab immediately when the active tab is closed

* Html: standard link colors with pressed precedence; skip re-parsing unchanged text; color setters

* Image: don't redraw on cache-hit loads of the already-bound image (per-draw reloaders looped forever)

* Scroll: log macOS momentum-end phase bits to check Cancelled (touch-cut) vs Ended (natural fade)

* Scroll: momentum state machine; flicks survive pagination; edge sentinels & once-per-frame actions

* Scroll: remove the MAKEPAD_SCROLL_DEBUG diagnostics

* Scroll: time-based fling velocity window; pointer fan-out guard; parked flings survive pagination

* PortalList: optional reached-start/end margins (Some(0) default); repositioning re-announces the edges

* Linux/Wayland: honor UI zoom across window resizes; scale caption bar with zoom

Keep the wayland-side window geom in native units so the zoomed dpi isn't
read back as "native" on the next Configure — UI zoom no longer resets or
flickers on maximize/tile. Also let the caption bar height scale with the
zoom (pin to native only on macOS, where the buttons are OS traffic lights).

* Windows: D3D11 fixes for drawlist mgmt

trying to help with the `new_batch` bugs

* draw_list.rs — `set_zbias` now returns whether it changed
* d3d11.rs — uploads draw_call_uniforms on the given condition:
  `uniforms_dirty || zbias_changed || buffer.is_none()`
  and the zbias advance is hoisted above the early-continues

* Scroll: hard flicks carry farther and boost on re-flick; iOS-style touch rubber band with per-edge bounce gating; Android fling spline behind a flag

* Splitter: redraw both pane subtrees on drag; cached views keep fresh fixed sizes in the dirty check

* iOS: re-deliver window-geometry changes dropped by re-entrant UIKit callbacks; Init only ever from the first draw

* final cleanup fixes. Ensure all examples, experiments, `studio` all work

* Image: skip re-parsing an SVG that is already shown, keyed on the caller's shared bytes

* CachedView: fix upside-down offscreen texture on GL/GL-ES

GL/GL-ES store offscreen FBOs bottom-up, unlike Metal/D3D/WGSL.
The DSL->script-shader migration switched the CachedView composite to plain
.sample() (non-flipping sample2d on GL), so cached views rendered
vertically mirrored on GL/GL-ES; Metal was fine on macOS.

Add a `sample_rt` script sampler (emits the V-flipping sample2d_rt on
GLSL, plain no-flip sample elsewhere) and use it in the CachedView and
CachedRoundedView composites.

* Fix `AdaptiveView::redraw()` to actually do something
2026-07-21 09:59:12 +02:00