Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/validate-continuous-zoom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Validate Continuous Worldwide Zoom

on:
workflow_dispatch:
pull_request:
branches:
- main
paths:
- .github/workflows/validate-continuous-zoom.yml
- package-lock.json
- package.json
- style.json
- scripts/**
- src/**
- server.mjs

permissions:
contents: read

concurrency:
group: occumed-continuous-zoom-${{ github.ref }}
cancel-in-progress: true

env:
SURFACE_ASSET: occumed-world-surface.pmtiles

jobs:
continuous-motion:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm

- run: npm ci

- name: Build and validate the production map
run: npm run build

- name: Install Chromium
run: npx playwright install --with-deps chromium

- name: Build candidate worldwide physical surface
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential libsqlite3-dev zlib1g-dev
bash scripts/build-world-surface.sh \
"dist/virtual-assets/${SURFACE_ASSET}"

- name: Run continuous zoom and pan gate
run: |
set -euo pipefail
mkdir -p continuous-motion
OCCUMED_WORLD_SURFACE_URL="http://127.0.0.1:4173/virtual-assets/${SURFACE_ASSET}" \
node server.mjs > continuous-motion/server.log 2>&1 &
server_pid="$!"
cleanup() {
status="$?"
if [ "$status" -ne 0 ]; then
tail -300 continuous-motion/server.log || true
fi
kill "$server_pid" 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT

for attempt in {1..90}; do
if curl --fail --silent http://127.0.0.1:4173/healthz > /dev/null; then
break
fi
sleep 1
done
curl --fail http://127.0.0.1:4173/healthz

OCCUMED_PREVIEW_OUTPUT=continuous-motion/results \
node scripts/validate-continuous-zoom.mjs

- uses: actions/upload-artifact@v4
if: always()
with:
name: continuous-zoom-${{ github.sha }}
path: |
continuous-motion/results
continuous-motion/server.log
if-no-files-found: error
retention-days: 14
23 changes: 20 additions & 3 deletions scripts/check-pmtiles-integration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ if (styleBuilder.toLowerCase().includes('openfreemap')) {
if (helper.includes('Protocol') || helper.includes('setUrl(') || helper.includes('world-pmtiles-router')) {
fail('The browser helper still contains PMTiles shard routing.');
}
if (!helper.includes('fadeDuration: 0')) fail('The browser can crossfade stale basemap tiles.');
if (!helper.includes('cancelPendingTileRequestsWhileZooming: false')) {
fail('The browser can cancel still-loading parent tiles during zoom.');
}
if (!helper.includes('maxTileCacheZoomLevels: 8')) {
fail('The browser does not retain enough parent zoom levels for seamless motion.');
}
if (!helper.includes('refreshExpiredTiles: false')) {
fail('The browser can replace visible tiles through in-session expiry refreshes.');
}
if (!helper.includes('fadeDuration: 300')) {
fail('Normal symbol collision fading is not enabled during zoom.');
}
if (!helper.includes("source.tiles = source.tiles.map")) {
fail('Permanent vector tile templates are not resolved to the style origin.');
}
Expand All @@ -66,6 +77,12 @@ if (!gateway.includes('Promise.all')) fail('The gateway cannot resolve intersect
if (!gateway.includes('mergeVectorTiles')) fail('The gateway cannot merge MVT layers at shard boundaries.');
if (!gateway.includes('MemoryTileCache')) fail('Resolved worldwide tiles are not cached.');
if (!gateway.includes('overscaleVectorLayer')) fail('The land surface cannot remain continuous above its generalized zoom.');
if (!gateway.includes("includeLayers: ['land']")) {
fail('The gateway does not isolate the worldwide surface to a non-overlapping land mask.');
}
if (gateway.includes("includeLayers: ['land', 'landcover', 'depth']")) {
fail('The gateway still overlays generalized surface detail on regional geometry.');
}
if (!server.includes('OCCUMED_WORLD_SURFACE_URL')) {
fail('Read-only visual validation cannot serve its candidate physical surface.');
}
Expand All @@ -77,7 +94,7 @@ if (!manifestBuilder.includes('version: 2')) fail('The server-only routing manif
if (!manifestBuilder.includes('overviewAsset')) fail('The manifest does not declare the consolidated overview archive.');
if (!manifestBuilder.includes('surfaceAsset')) fail('The manifest does not declare the worldwide land surface.');
if (!manifestBuilder.includes("surfaceLayers: ['land', 'landcover', 'depth']")) {
fail('The manifest does not declare continuous vector land, landcover, and bathymetry.');
fail('The manifest does not document the physical surface archive schema.');
}
if (manifestBuilder.includes('switchZoom')) fail('The obsolete browser switch zoom remains in the manifest.');
if (manifestBuilder.includes('archiveProxyTemplate')) fail('The manifest still advertises regional archives to browsers.');
Expand Down Expand Up @@ -121,4 +138,4 @@ if (failures.length) {
process.exit(1);
}

console.log('PMTiles storage integration validated behind one permanent worldwide vector endpoint.');
console.log('PMTiles storage integration validated behind one permanent worldwide vector endpoint with parent-tile retention and non-overlapping surface geometry.');
35 changes: 25 additions & 10 deletions scripts/check-world-tile-gateway.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ const overscaled = inspectVectorTile(overscaleVectorLayer(surface, {
targetY: 32768
}));
expect(overscaled.land?.featureCount === 1, 'The worldwide land surface cannot overscale through max zoom.');
const physicalSurface = inspectVectorTile(
mergeVectorTiles([surface], { includeLayers: ['land', 'landcover', 'depth'] })
const physicalLandMask = inspectVectorTile(
mergeVectorTiles([surface], { includeLayers: ['land'] })
);
expect(physicalSurface.land?.featureCount === 1, 'The physical surface lost its land layer.');
expect(physicalSurface.landcover?.featureCount === 1, 'The physical surface lost generalized landcover.');
expect(physicalSurface.depth?.featureCount === 1, 'The physical surface lost its bathymetry layer.');
expect(physicalLandMask.land?.featureCount === 1, 'The physical surface lost its land layer.');
expect(!physicalLandMask.landcover, 'The physical surface still overlays generalized landcover on regional detail.');
expect(!physicalLandMask.depth, 'The physical surface still overlays generalized bathymetry on regional detail.');
const normalizedProperties = normalizeMvtProperties({
safeRank: 4,
unsafePositive: 2 ** 65,
Expand Down Expand Up @@ -179,7 +179,18 @@ expect(!JSON.stringify(runtime).includes('pmtiles://'), 'A storage archive is st
expect(!helper.includes('setUrl('), 'The browser helper can still replace the vector source URL.');
expect(!helper.includes("addProtocol('pmtiles'"), 'The browser still reads PMTiles storage shards directly.');
expect(!helper.includes('WorldPmtilesRouter'), 'The browser still installs the removed regional router.');
expect(helper.includes('fadeDuration: 0'), 'Tile fading can still expose stale cartography during zoom.');
expect(
helper.includes('cancelPendingTileRequestsWhileZooming: false'),
'MapLibre can still cancel parent tiles while child zoom tiles are loading.'
);
expect(
helper.includes('maxTileCacheZoomLevels: 8'),
'The browser does not retain enough parent zoom levels for continuous motion.'
);
expect(
helper.includes('fadeDuration: 300'),
'Normal symbol collision fading is not enabled during continuous zoom.'
);
expect(server.includes('/tiles\\/(\\d+)\\/(\\d+)\\/(\\d+)\\.pbf'), 'The server does not expose the permanent worldwide tile route.');
expect(server.includes("'world-virtual-manifest.json'"), 'The gateway does not use the isolated server-only manifest.');
expect(!server.includes('/world-tiles/'), 'The server still exposes regional storage URLs to the browser.');
Expand All @@ -194,13 +205,17 @@ expect(
'A missing overview enrichment can still reject the complete worldwide tile.'
);
expect(
gateway.includes("includeLayers: ['land', 'landcover', 'depth']"),
'The gateway does not expose land, landcover, and bathymetry as one continuous physical surface.'
gateway.includes("includeLayers: ['land']"),
'The gateway does not isolate the continuous physical surface to the land mask.'
);
expect(
!gateway.includes("includeLayers: ['land', 'landcover', 'depth']"),
'The gateway still overlays generalized landcover and bathymetry on regional detail.'
);
expect(manifestBuilder.includes('version: 2'), 'The server-only routing manifest is not version 2.');
expect(
manifestBuilder.includes("surfaceLayers: ['land', 'landcover', 'depth']"),
'The routing manifest does not declare the complete physical surface schema.'
'The routing manifest does not document the surface archive schema.'
);
expect(!manifestBuilder.includes('switchZoom'), 'The obsolete browser switch zoom remains in the manifest.');

Expand All @@ -217,4 +232,4 @@ if (failures.length) {
process.exit(1);
}

console.log('Virtual worldwide tileset validated: one permanent source, boundary merging, antimeridian routing, land, landcover, and bathymetry continuity, surface overscaling, caching, and no browser-visible shards.');
console.log('Virtual worldwide tileset validated: one permanent source, boundary merging, antimeridian routing, land-mask continuity, parent-tile retention, caching, and no browser-visible shards.');
Loading
Loading