OSM-3D — exhaustive feature + gap ledger
nornir:class=reasoning
The single hero-feature ledger for facett's 3D OSM map (facett-map3d, facett-map3d-flight, facett-map, facett-osm). Mined from the git history + the .nornir map/OSM docs, this is the exhaustive "what ships / what is planned / what is missing" list — the backlog every OSM-3D round is dispatched against. Newest structural facts first; keep this file the source of truth for the gap set.
Legend: DONE shipped + proven (test/testmatrix cell) · PARTIAL exists but incomplete · GAP not built · DEFERRED intentionally parked (off-limits shader edit, heavy dep, needs korp contract).
A. Rendering / geometry (the extruded city)
| Feature | State | Where / note |
| Building footprint → 3D prism extrusion | DONE | extrude.rs extrude_ways; height-by-OSM-tag proof building_extrusion_proof.rs |
| Height scales by real metric OSM tag | DONE | building_height + building_extrusion_proof (monotone A/B) |
| The real OSM height TAG ZOO parses (feet, comma decimals, semicolon lists, ranges, junk) | DONE (2026-08-31) | facett-map/src/height.rs; mesh gate no_building_spikes.rs; METRES_PER_LEVEL 3.0 → measured 3.2; agreement corpus vs osm_katana::shared::height. .nornir/building-height-design.md |
| Line ways → ground ribbons | DONE | roads/paths as ribbons, road_ribbon |
| Road hierarchy (width/casing/fill/glow by class) | DONE | road_hierarchy_proof.rs |
| Rivers / water polygons → reflective water | DONE | Surface::Water → WaterMesh, Gerstner + Fresnel + foam; flightsim_water.rs |
| Rail / tram lines as distinct ribbons | GAP | no rail-specific styling; folds into generic line ways today |
Bridges / tunnels (layer/layer=* z-offset) |
GAP | layer tag ignored — no over/under crossing separation |
| Landuse / parks / greens as ground layers | DONE | depth-ordered ground layers (GroundLayer, layer_lift) |
| Terrain plane under the city | DONE | terrain_plane |
| Real DEM / procedural relief heightfield | DONE | terrain.rs Dem/ProceduralTerrain; $FACETT_HGT SRTM .hgt |
| Roof shapes (gabled/hipped/pyramidal) | DONE | extrude_prism_styled gabled/hipped from roof:shape (default gabled small houses); watertight — overhanging gabled roofs fall back to the hipped fan so no see-through gap under the eave at street level (irregular_roof_stays_within_the_wall_footprint_no_overhang) |
Building colour by material/building:colour |
DONE | resolve_prism_style reads building:colour/roof:colour/roof:material, tasteful class defaults otherwise; pitched_roofs/coloured_buildings oracles |
| Both demo surfaces use the extruded/roofed/coloured path (no flat twin) | DONE | sigtuna_demo + facett-demo osm3d_view both from_geometry_relief_roofed().with_roof_accent() (L5, was flat from_geometry_relief in the tab) |
Instanced GPU prisms + draw_indirect |
PARTIAL | GPU depth path exists; full instanced-indirect fast-lane planned |
B. Materials / lighting / post (the "expensive look")
| Feature | State | Where / note |
| Filament PBR BRDF + physical exposure + ACES | DONE | map3d.wgsl; commit 483e296 |
| IBL split-sum (SH-L2 diffuse + prefiltered spec) | DONE | commit 8c55c56 |
| Sun + sky analytic atmosphere / skybox | DONE | sky.rs SunSky, sky.wgsl; sky_proof.rs |
| Cascaded shadow maps + PCF | DONE | shadow.wgsl; flightsim_shadows.rs |
| PCSS soft shadows | DONE | with_soft_shadows; shadow_softness.rs |
| SSAO / GTAO | DONE | ssao.wgsl; ssao_proof.rs |
| Bloom (HDR highlight glow) | DONE | bloom.wgsl; bloom_proof.rs |
| Planar reflection pass (+ wasm sky-gradient fallback) | DONE | with_planar_reflection |
| Aerial / height fog | DONE | map3d.wgsl aerial fog |
| Vignette / film grain / colour grade | DONE | grade pass (graphics roadmap P0) |
| Time-of-day sun-angle sweep (dawn→noon→dusk) | DONE | facett-map3d-flight/src/daycycle.rs DayCycle: real solar position (rises east → season-dependent noon → sets west), golden-hour reddening derived from the reused atmos transmittance LUT, twilight/night dimming; demo facett-demo/src/demo_photoreal.rs; bench heavy_daycycle; daycycle-{design,guide,for-idiots}.md |
| Weather (rain/snow/cloud/overcast) | PARTIAL | overcast/cloud variant DONE (daycycle::Weather cloudiness — greys+desaturates sky, dims sun, lifts ambient, monotone); precipitation particles + cloud-deck geometry still GAP |
| SSR (screen-space reflections) | DEFERRED | needs gpu.rs depth-sample seam (P2) |
| FFT ocean spectrum | DEFERRED | would replace water.wgsl (off-limits) (P3) |
| Parallax-occlusion mapping | DEFERRED | mesh-shader edit (off-limits) (P3) |
C. Camera / navigation
| Feature | State | Where / note |
| Orbit / pan / dolly-to-cursor | DONE | camera.rs OrbitCamera; damped + inertial |
| View presets (top/iso/oblique/front) + reset | DONE | ViewPreset; keyboard [1..4] R |
| Idle auto-rotate (spins when untouched) | DONE | MapFacet idle auto-rotate; idle-auto-rotate.md |
| Ground-floor clamp (never dive below terrain) | DONE | clamp_elev; robot_ui.rs |
| Garmin-nav bearing + tilt vocabulary | DONE | bearing_deg/tilt_deg; camera_nav_proof.rs |
| Cinematic flythrough autopilot (watch mode) | DONE | flythrough.rs; d6530ee |
| Sigtuna car-nav tour (air-circle → descend → drive the streets) | DONE | tour.rs SigtunaTour; scale-aware metric street chase (with_scale); Map3D::with_tour; real-data demo bin sigtuna_demo over the cropped embedded Sigtuna core; sigtuna_tour_proof.rs + tour_robot.rs + demo sigtuna_tour.rs; sigtuna-car-nav-tour.md |
| Minimap inset (top-down overview + heading cone) | DONE (this round) | minimap.rs MinimapView; minimap_proof.rs + minimap_robot.rs |
| Turn-by-turn route following (chase a route) | DONE (this round) | route.rs Route + apply_route_camera; SetRouteFollow/RouteProgress; camera target+bearing ride the polyline; route_proof.rs + route_robot.rs |
| 6DOF flight camera (planetary, floating origin) | DONE (this round) | facett-map3d-flight/src/facet.rs FlightFacet — the engine finally has a host-facing pane. It owns no renderer: it drives facett_map3d::Map3D as the shared render core through the existing Garmin set_bearing_deg/set_tilt_deg vocabulary. tests/facet_robot.rs + probe_and_emit matrix cell. Also fixed the up-axis the whole flight layer had wrong — see flight-facet-and-world-frame-design.md §3: camera_fly::forward put the vertical in the Mercator NORTHING slot, so climbing moved the aircraft north and turning changed its altitude. Every rig test was basis-relative, so all of them passed on it. |
| Seize manual control from the autopilot (grab the stick) | DONE | Map3D::take_manual_control; any manual gesture disarms the tour (no jump), T (Msg::SetTour) re-arms; manual_control.rs robot proof |
| On-street drive faces FORWARD, between the houses | DONE | tour.rs drive eye azimuth = travel bearing + 180° (eye behind, looks down the road); flanking_houses() houses_left/houses_right oracle; osm3d_street_tour.rs (street_m=30, both_sides=290). FLIGHT viewer. |
| First-person / walk / street-level mode (pedestrian eye height) | DONE (this round) | FlightFacet Locomotion::Walk: eye pinned at WALK_EYE_HEIGHT_M = 1.70 above host-supplied ground, horizontal-only travel (looking up and walking forward does NOT take off), WALK_SPEED_MPS = 1.40 cap, 70° neck clamp — each a separate RED-when-broken assertion. FLIGHT viewer, as the partition requires. Remaining: no collision (a walker passes through walls — wants the picking/footprint-identity refactor first). |
| Smooth "fly-to" a named place / bbox | PARTIAL | framing + presets exist; no animated goto-target easing API |
D. Overlays / HUD / interaction
| Feature | State | Where / note |
| Street-name labels (car-nav, LOD-gated, billboarded) | DONE | street_label_anchors; 5aa8482 |
| MSDF crisp glowing labels at any zoom | DONE | msdf.rs/msdf.wgsl (graphics roadmap P1) |
| HUD (angle / distance / visible-tri / hints) | DONE | view() bottom-left HUD |
| Minimap focus marker + heading cone | DONE (this round) | paint_minimap; state_json.minimap* |
| POI billboards (shops/amenities as icons) | GAP | only street-name labels; no amenity/POI point billboards. NB the 2D NAVIGATOR's pin overlay now clusters (facett-geomap/src/osm2d/cluster.rs); when POI billboards land in 3D they should consume that same screen-space binning rather than twin it. |
| Picking / click-to-inspect entity | GAP | mesh is an identity-less triangle soup; no ray-pick → feature id/tags |
| Route overlay polyline (from a collector) | DONE (this round) | extrude::project_route+route_overlay_into bake a bright ribbon+waypoint markers onto the streets; drawn in the minimap HUD (paint_minimap); state_json.route_*; route-overlay-nav.md |
| North / compass rose indicator | PARTIAL | minimap heading cone shows heading; no dedicated north rose |
| Distance / scale bar | GAP | metres_per_design_unit exists; not surfaced as an on-screen scale bar |
| Selection highlight / hover outline | GAP | depends on picking (above) |
E. Data / streaming / LOD / source
| Feature | State | Where / note |
| Altitude → LOD tier ladder (Ground/Low/Mid/High) | DONE | lod.rs; hysteresis; < 300 m → reflective water |
| Per-tier feature/way budget decimation | DONE | LodTier::feature_budget |
| Douglas–Peucker line simplification per tier | DONE | graphics roadmap |
| Tile streaming (plan resident/in-flight/evict) | DONE | facett-map3d-flight plan_tiles/TileSource |
| On-disk GeoParquet per-tile source | DONE | DirTileSource (facett_osm::read_ways) |
Floating-origin f64→f32 rebase (planetary) |
DONE | origin.rs FloatingOrigin |
| Region picker (Sweden poly + osm-katana clip) | DONE | region_picker; osm-region-picker.md |
| korp connector as the real layer source | PARTIAL | korp.rs load_layers(ways) seam; awaits korp's contract (intentionally thin) |
| Live overpass / tile-server fetch | GAP | reads local .osm.bz2/parquet only; no network fetch |
F. Cross-cutting / proof surface
| Item | State | Note |
| CPU painter path (headless, no-GPU) | DONE | every feature must survive here (self-test) |
| CPU painter software z-buffer + ACES tonemap | DONE | paint_cpu rasterizes through raster_zbuffer (a true 1/z per-pixel depth test carrying each vertex's view-space vz) instead of a painter's-order centroid sort — the depth-tearing fix where a small near face overlapped a large far one (buildings/terrain/ground-cover now resolve by TRUE depth on llvmpipe/wasm). Shaded colour runs through aces_tonemap/tonemap_shaded (Narkowicz ACES) to match the wgpu curve. Centroid depth_monotone stays the device-free conformance oracle. |
Swappable RenderBackend seam |
DONE | backend.rs; CPU / wgpu+vello / null |
| Robot-UI scenarios | PARTIAL | orbit-confinement + minimap + route covered; POI/pick pending their features |
| testmatrix cells per feature | PARTIAL | grows with each feature; minimap + route cells added |
.nornir design + guide + SVG per feature |
PARTIAL | this ledger + minimap-nav.md + route-overlay-nav.md current |
| Ground-plane mesh faces render (roads/route ribbons) | DONE (2026-07-15) | the CPU painter's screen-space back-face cull (shade_mesh_faces STEP 4) dropped flat +Y ground-plane ribbons because their screen-winding sign flips with the viewing hemisphere. Fix: keep any strongly up-facing face (nv.y > GROUND_FACE_UP_NY, i.e. roads · route ribbon · terrain cover · roof caps) regardless of screen winding — the map camera is always above the ground, so an up-facing surface is always front. Roads + the 3-D route ribbon now pixel-draw from above. Proven headless by ground_roads_proof.rs (a road adds visible faces from oblique and overhead) + route_robot.rs (the ribbon now changes the main 3-D view, not only the minimap). Goldens (golden_248/render_proof) unaffected beyond a deterministic 0.008 % sub-budget delta — those scenes carry no roads. |
TWO distinct viewers (Rickard's partition — do NOT merge their feature sets)
- FLIGHT viewer (
sigtuna_demo+ facett-demoOSM 3Dtab) — "real flight feeling". Owns ALL street-level / walk-around work: air→descend→on-street drive between the houses (DONE), seize manual control (DONE), watertight roof shell (DONE), extruded coloured houses (DONE), perf (DONE,a3c79c8). Remaining: first-person pedestrian walk (row C:69). - NAVIGATOR viewer (Garmin turn-by-turn) — "garmin-alike or better". Owns NAV QUALITY, NOT pedestrian mode. Priorities: visible roads (§F ground-plane ribbons — matters MOST here), scale bar + north/compass rose, turn-by-turn + next-turn/ETA HUD, POI billboards, minimap. Do NOT add first-person walk here.
- Shared render-core (extrusion, roof shell, ground-plane roads) benefits both — fix once. Render-core landed this round: roofed/coloured houses + watertight roofs. Still open + shared: §F ground-plane ribbon render (invisible roads).
Round landed 2026-07-22 (this round)
- FLIGHT gets a
Facet(C:69) + pedestrianWalk(C:72) — the last two open FLIGHT-owned camera rows..nornir/flight-facet-and-world-frame-design.md. - The world-frame bug:
WorldPosis{easting, northing, altitude}butcamera_fly::forward,cinematic.rsand both demo binaries put the vertical in slot 1. Climbing moved you NORTH. One frame, one swap at the render boundary;rig_climbs_in_altitude_not_latitudeis the world-frame oracle that pins it. - The smear caps are viewport-tight.
no_spokeshalf_h*8 → half_h*2+1,matrix_gridscale*8 → scale*2+1,golden_248scale*8 → scale*2+1,frustum_scissorhalf_h*6 → half_h*2+1, facett-demoosm3d_spokehalf_h*8 → half_h*2+1. Proven RED-when-broken at the smear-bait pose: the pre-fix pipeline projects 1097 px against the new 601 px cap and under the old 2880 px one — the loophole demonstrated by the suite, not described in a doc. near_plane_probe.rshad NO assertions (eprintln only). It now asserts the pose renders geometry, that every face is viewport-bounded, that zero vertices escape the rect after the live clip, and carries its own fail-on-bug control.clip_pipeline'scompositematrix row carriedpng_path.exists()— green whenever a file landed on disk. It now carrieslit_px+ the margin-leak fraction, the quantities the test body actually asserts.- NAVIGATOR: pin clustering + the first real bench. See
.nornir/osm-2d-navigator-design.md. The bench foundPointGrid's lattice was laid over the whole world, so a regional dataset collapsed into ~90 cells and a city-block cull cost 5.77 ms at 1M points; it is now 355 ns.
Round landed 2026-07-22 (late) — the three things Rickard saw on the running demo
-
OSM 2D: the corner points were a blue SEA when zoomed out (
44fc490). The point layer is the OSM node cloud, i.e. mostly BUILDING CORNERS, and it painted every surviving node at any zoom in the theme'spointhue — an unrelated blue on the cartographic-day paper. At the Sigtuna fit that was ~21500 dots of radius 3 px over an 800×600 pane: 600000 px² of ink on a 480000 px² pane, more than a full screen of dots. Fixed as an LOD expressed in screen INK (POINT_INK_FRAC_MAX = 0.06), not a metres-per-pixel cutoff — ink is data-aware, so it culls a dense cloud at the ~1–2 m/px knee (exactly building zoom) and never touches a sparse 43-point annotation layer, which a m/px rule would have blanked (the same bug one layer up). Colour is nowMapPalette::building_stroke— the footprint outline's own ink, hoisted out ofrecolor_wayso corner and wall are the same value by construction. Debug mode (default OFF): a "debug pts" checkbox in the pane's own header, theVkey, orMsg::SetDebugPoints; bypasses the cull and paints a 3 Hz magenta/lime flash. Oracle:tests/osm_point_lod.rs—drawn_points == 0at a whole-dataset camera over a 40000-node cloud (the pre-fix painter drew five figures), the layer returns within 8 doublings, a 43-point layer survives 6 zoom-outs, the colour differs fromtheme.pointon both cartographic palettes. Bench:points/lod_decision2 ns,points/cull_then_lod_wide/1000000749280 ns. -
OSM 3D: 77 of 182 houses were TALLER THAN THEY ARE LONG (
a11fc38). Reported as a regression; the bisect says it is not one —git diff 1a092bb..HEADover facett-map3d / facett-map / facett-osm /osm_tabs.rsis EMPTY, and the world-frame swap landed in facett-map3d-flight, which this tab does not use. It is a pre-existing defect that became VISIBLE when the tab cropped to the town core (a3c79c8). Cause: only 69 of the embedded extract's 10373 buildings carry a height tag (0.7%); the rest fell back to a CONSTANT 6 m that knows nothing about the footprint it lifts. Because the roof rise scales with the footprint while the wall did not, the roof shrank to a lid on a tall box — "walls enlarged" and "roofs lean on the ground" are one mechanism. Fix: cap an untagged default atMAX_DEFAULT_WALL_OVER_FOOTPRINT = 0.9of the footprint's short side (tagged heights are real data and are never capped).taller_than_long77 → 0; worst wall/long 1.77 → 0.90; median roof/wall 0.36 → 0.41.HeightStatsnow carries the ratios andMap3Dpublishes them asstate_json.skyline. RED-when-broken proven: with the cap at INFINITY the town test fails "60 of 200 buildings are TALLER THAN THEY ARE LONG (worst ratio 2.80)". -
graph3d: the sphere was hollow because the DATA was a shell (
fe5de76). See.nornir/graph3d-dataset-design.md.
Highest-value OPEN gaps (dispatch order)
-
§F ground-plane ribbon render — INVISIBLE ROADS— DONE 2026-07-15. The CPU painter back-face-culled+Yground-plane road/route ribbons (their screen-winding sign flips with the viewing hemisphere). Fixed inshade_mesh_facesSTEP 4: keep any up-facing face (nv.y > GROUND_FACE_UP_NY) regardless of screen winding. Roads + the 3-D route ribbon now draw from above (headless oracleground_roads_proof.rs; route_robot asserts the ribbon in the main view). Goldens unchanged (their scenes carry no roads). -
Picking / click-to-inspect (D) — thread a per-footprint feature id through
extrudeso a screen ray resolves to an OSM entity + its tags. Unlocks selection highlight, hover, and the inspect panel. Largest unlock; needs a mesh identity refactor (id buffer parallel to triangles). -
Route overlay polyline (D/E)— DONE this round.route.rs+extrude::{project_route, route_overlay_into}(reusesribbon_rounded) + a minimap route line. Remaining: wire it intokorp::load_layersas a realRoutelayer + fix ground-plane rendering so the 3D ribbon shows in the painter (see §F GAP). -
Turn-by-turn route following (C)— DONE this round.apply_route_cameraSetRouteFollow/RouteProgress; camera chases the route, bearing on the tangent. Remaining: ETA / next-turn HUD text.
-
POI billboards (D) — amenity/shop nodes as LOD-gated icon billboards (reuse the street-label billboard machinery).
-
Time-of-day lever + weather (B) — animate
SunSkythrough a day cycle; add a cloud-deck / overcast sky variant + a cheap precipitation particle pass. -
Rail/tram + bridge/tunnel layering (A) — class-specific ribbons +
layertag z-offset so crossings read correctly.
(Rounds landed so far: #C-minimap, then #2 route overlay + #3 turn-by-turn follow. Next: #1 picking/click-to-inspect, and the §F ground-plane-render fix that lets the 3D route/road ribbons show in the CPU painter.)