| name | running-tower |
| description | Launch and drive the Tower map app in a real browser — dev server, Playwright with working WebGL, and how to draw routes/zones via the right-click context menu. |
Running Tower
Tower is a Vite + React PWA rendering MapLibre + interleaved deck.gl. Verifying
rendering changes requires a real GPU-backed browser and drawing on the map.
Launch
npm run dev
The gateway (ws://localhost:9000) is usually absent in dev. The app works
without it; filter the repeating WebSocket connection ... failed console
errors out of script output.
Drive with Playwright
Import Playwright by absolute path (scripts outside the repo can't resolve it
otherwise):
import { chromium } from '<repo>/node_modules/playwright/index.mjs';
Headless does not work — Chromium falls back to SwiftShader and WebGL
context creation fails, so MapLibre throws and the map never mounts. Use:
const browser = await chromium.launch({ headless: false, args: ['--use-angle=metal'] });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 }, deviceScaleFactor: 2 });
Readiness: wait for [data-testid="map-container"], then ~4s for tiles.
Drawing features
There are no toolbar drawing buttons. Drawing starts from the map's
right-click context menu — selectDrawingMode() in e2e/utils.ts implements
this flow and is the reference:
- Right-click on the map → menu appears with items labeled from
DRAWING_MODES in src/renderer/constants.ts ("✈️ Air Route",
"🛤️ Ground Route", "⛔ No-Go Zone", …). Click one (page.getByText).
- Left-click each vertex (~350ms between clicks).
- Double-click the last vertex to finish.
Drawn features appear in the Mission Elements panel (📋 button), which opens
on the right and shifts layout — take screenshots after all drawing is done
and crop regions out of the full screenshot rather than pre-computing clip
rectangles.
window.__TOWER_STORE__ exposes the Zustand app store in every build (set at
the bottom of src/renderer/stores/appStore.ts). Use it to verify feature
state (getFeatureCount in e2e/utils.ts) or read getState() directly from
page.evaluate.
Inspecting rendering quality
To judge antialiasing/pixel-level output, crop a small region and upscale with
nearest-neighbor (PIL is available):
from PIL import Image
img = Image.open('full.png')
img.crop((x-60, y-60, x+60, y+60)).resize((600, 600), Image.NEAREST).save('zoom.png')