feat(web): bring the app's 3D to the map: view button, bars, pillars, buildings, terrain and rotation - #596
Open
khagele wants to merge 7 commits into
Open
feat(web): bring the app's 3D to the map: view button, bars, pillars, buildings, terrain and rotation#596khagele wants to merge 7 commits into
khagele wants to merge 7 commits into
Conversation
…D parity The website drew its map with Leaflet on Carto raster tiles while the app had moved to MapLibre on OpenFreeMap's vector styles (efiten#147): two engines, two basemaps, two treatments of the same colour tokens, and no road to the 3D views the app has. map.js now builds its map from mapcore.js, a port of the app's huntmap.js shape (the same styles, the bare fallback, the sky, overlays re-added on every style load), with the collections the layers read built in mapmodel.js, which the unit suite covers. Points, hex cells, the CoreScope layers, Locate's cloud and dots, the node positions and the ticker's ring are GeoJSON sources swapped whole; markers and popups are MapLibre's. Zoom keeps Leaflet's numbers in shared links and in the server's hex binning, converted at the edge, so no link and no cell changes. The e2e fixtures answer the hosted style inline so load fires offline, and the specs read sources through page hooks where they counted Leaflet's SVG paths. Closes efiten#465 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…exaggeration setting Terrain was pulled in efiten#247 because the DEM source kept the map loading and froze weak GPUs. efiten#394 decided it comes back on the AWS terrarium tiles, capped at z10, exaggeration 7 by default because the Low Countries show no relief otherwise. This adds the source and a hillshade layer under the signal overlays, a sixth FAB that turns terrain on and off, and the exaggeration steps in Settings with the slope caveat on the control. The mesh waits: hillshade follows the button, setTerrain needs the button, DEM tiles for the view and a 3D view, so a slow host leaves the map flat rather than stalled. Leaving 3D drops the mesh before the tilt back, and entering 3D sets it once the tilt has settled, since easeTo({pitch}) is a no-op under a mesh. Closes efiten#396 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Kasper (2026-09-06): the button is the opt-in, not the opt-out. The loader answers on only for a stored '1'; the hint, the decision log and the release note say so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e light The flat cell is the tier colour at the tier's opacity over the basemap, a 19% tint for faint; the bar drew the token opaque at 0.85 for every tier, so a faint bar stood as solid purple on a pale patch, worse the weaker the signal. On top, MapLibre's default light darkened every face by 7 to 17%. The bar now takes the cell's tint pre-mixed over the theme background, opaque, so shared walls keep depth-testing, and the style light sits at 0.15 on every style load. Measured pixel for pixel in both themes: the bar is within a few levels of its cell at hot and at faint. The theme joins the hex cache key so a switch rebuilds the colours. Closes efiten#412 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Kasper (2026-09-06): 3D takes the terrain and the exaggeration along at once, the same rule the map follows in efiten#595. The sixth FAB goes, with its loader, its CSS and its place in the onboarding ring; huntmap's plan reads mode3D as the switch and the map only takes the exaggeration. Settings keeps the exaggeration. Decision log and release note follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… buildings, terrain and rotation Closes efiten#595. The map could pitch since efiten#465 and nothing reached it. A view button under the zoom and the compass eases to the app's pitch; hex cells stand as bars in their own tint (efiten#412), receptions as octagon pillars collapsed where they coincide (efiten#402), buildings rise from the hosted style's source. 3D carries the terrain on the site (Kasper, 2026-09-06): hillshade and the mesh come with the view at the Settings exaggeration. Rotation and tilt on; MapLibre's compass in the house colours. ?view=3d, ?pitch=, ?bearing= and ?exag= travel in the URL. signal.js, maplayers.js, pointmarker.js and terrain.js are the app's files whole in web/, pinned byte for byte. --ch-building added to both themes: undefined it was '', which MapLibre rejects and then skips the layer silently. Two e2e races fixed: popover's on-screen read polls, onboarding:233 reads one snapshot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
khagele
force-pushed
the
feat/595-web-3d
branch
from
September 6, 2026 09:16
1be4dcb to
ea64df4
Compare
A guest deep-linking ?nodepos=1 got the "needs a verified member account" key from the role gate, which also unchecks the box; the refresh the gate then asks for redrew from the box and took the key back 250 ms later. The ask is now held below member until the role can see the layer. The efiten#376 test arms an observer and forces a draw, so a flash no longer passes it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #595
Stacked on #592 (the map on MapLibre). It also carries the app-side commits it builds on, cherry-picked: #586 (terrain,
app/src/terrain.js, three commits) and #588 (a bar's tint,pillarTint). Those show in this diff until they merge; the web half is what this PR adds.The problem
Since #465 the map runs on MapLibre and could pitch, but nothing on the site reached it: rotation and tilt were off, there was no
hex-3d,points-3dorbuildings-3dlayer, no terrain and no control. A visitor who knows the app's 3D view could not get the same read of a cell's signal on the site, and the site is meant to be the superset of the app.My changes in this PR
The app's 3D, whole (decision log
docs/2026-09-06-map-in-3d.md):mapcore.js addButton, a MapLibre control group of one). Pressed, the camera eases to the app's pitch of 60; the hex cells stand as bars in the tint of their own cell (app: 3D bars don't render the tier colour their own flat cell does #412), the receptions as octagon pillars collapsed where they coincide (app: coincident 3D pillars z-fight against each other when receptions share a position #402), the buildings rise from the hosted style's own vector source. The layer mode stays the segmented control in the filter panel; which layers a view shows ismaplayers.js, so Both in 3D draws pillars over flat cells, as the app does.terrainPlanis read the same way on both surfaces.currentColor, since the library's needle is a dark image on our dark surface.?view=3dis the layer state and on its own implies the fixed pitch;?pitch=and?bearing=are the camera and win when present. A flat north-up view keeps them out.?exag=travels when off the default and persists like the theme.signal.js,maplayers.js,pointmarker.js,terrain.jsare the app's files inweb/, pinned byte for byte inparity.test.js. The web copy ofsignal.jsused to be a hand-kept half.--ch-buildingadded to both themes with the app's values, pinned per theme: undefined it was'', which MapLibre rejects and then skips the layer without a word.Three e2e tests fixed on the way, all pre-existing and all only visible under a full parallel run. Two were races in the tests:
popover.spec.jsread a panel's box once right after a resize (now polled, every edge still has to end up inside), andonboarding.spec.js:233counted the callouts and then waited on the inline panel from a later placement pass (now one atomic snapshot). The third was a bug in the map, in its own commit: a guest deep-linking?nodepos=1got the "needs a verified member account" key from the role gate, which also unchecks the box, and the refresh the gate then asks for redrew from the box and took the key back 250 ms later. The key now answers the ask, held past the uncheck until the role can see the layer; the test arms a MutationObserver and forces a draw, so it fails every time without the fix (3 of 3, checked) instead of 2 in 6.Verification
--fail-on-flaky-testsview3d.spec.jsBrowser, Playwright Chromium at 1100x700 against real OpenFreeMap tiles and the real DEM (Liège, relief and a city): 2D Both draws 40 points and 31 cells; the button eases to pitch 60, the mesh arrives once the DEM tiles are in (
ready: true, mesh: true), 40 pillars over flat cells, buildings up, hillshade on; Hex draws 31 bars; three Shift+Arrow turns givebearing=-45in the URL with the compass turned; Settings shows the exaggeration row with the five steps.Decisions worth a look
🤖 Generated with Claude Code