feat: v0.3.0 — flat 2D camera: remove zoom-driven tilt + terrain; max zoom ~100 km screen width

This commit is contained in:
2026-09-17 00:33:10 +00:00
parent 11619d11b2
commit b7796e5d78
7 changed files with 15 additions and 80 deletions
+3 -27
View File
@@ -3,16 +3,14 @@
import { useEffect, useRef, useState } from 'react';
import * as maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { pitchForZoom } from '@/lib/camera';
const EUROPE_CENTER: [number, number] = [10, 52];
const INITIAL_ZOOM = 4;
// Useful gameplay range: continent overview → city detail (10–100 m/px). The
// zoom→pitch camera transition completes at z12; street-level zoom has no value.
// Flat 2D view only. Max zoom ≈ ~100 km across a typical desktop viewport width at
// European latitudes (~47 m/px at z11, 52°N); street-level zoom has no gameplay value.
export const MIN_ZOOM = 3;
export const MAX_ZOOM = 14;
export const MAX_ZOOM = 11;
const OPENFREEMAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
const TERRAIN_TILES = ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'];
function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -25,10 +23,6 @@ export default function Map() {
useEffect(() => {
if (!containerRef.current) return;
const terrainEnabled =
new URLSearchParams(window.location.search).get('terrain') !== '0' &&
process.env.NEXT_PUBLIC_TERRAIN !== '0';
// Bundlers (Turbopack) break MapLibre's derived worker URL, so point it at a
// route that serves the worker script (and its relative imports) from node_modules.
maplibregl.setWorkerUrl('/api/mlworker/maplibre-gl-worker.mjs');
@@ -38,20 +32,12 @@ export default function Map() {
style: OPENFREEMAP_STYLE,
center: EUROPE_CENTER,
zoom: INITIAL_ZOOM,
pitch: 0,
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
});
// Dev/e2e hook so tests can project coordinates to screen space.
(window as unknown as Record<string, unknown>).__flightMap = map;
map.on('move', () => {
const target = pitchForZoom(map.getZoom());
if (Math.abs(map.getPitch() - target) > 0.05) {
map.setPitch(target);
}
});
// Airports are GeoJSON, so they don't depend on basemap tiles: initialize as soon
// as the style is applied ('styledata') rather than waiting for 'load', which also
// requires all tile sources to finish.
@@ -60,16 +46,6 @@ export default function Map() {
if (initialized) return;
initialized = true;
try {
if (terrainEnabled) {
map.addSource('dem', {
type: 'raster-dem',
tiles: TERRAIN_TILES,
encoding: 'terrarium',
tileSize: 256,
});
map.setTerrain({ source: 'dem', exaggeration: 1.4 });
}
const res = await fetch('/api/airports');
if (!res.ok) return;
const data = (await res.json()) as { features: unknown[] };