Satellite ground and real building footprints
CI/CD / test (push) Successful in 18s
CI/CD / publish (push) Successful in 22s
CI/CD / deploy (push) Successful in 6s

- The ground is Esri World Imagery: ~1 m/pixel tiles around the city
  zone, the factory and 300 m either side of the drive between them,
  coarser tiles for the whole region. Tiles are hosted with the game
  (public/imagery, scripts/fetch-imagery.mjs), stream in near the camera,
  are cached by the service worker as visited, and replace the drawn
  roads and land areas
- ~141,500 Microsoft building footprints (ODbL) join OSM's ~4,000 in a
  compact delta-encoded file (public/data/buildings.bin, built by
  scripts/build-buildings.mjs). Heights are estimated from footprint
  area and land use, with industrial halls outside the ring road.
  Buildings are built in 500 m blocks as the camera approaches
- Caddy compresses the buildings file and caches map data for a week;
  the build targets ES2022 for top-level await
- Tests for decoding, factory clearance, machine reachability and tile
  placement

Imagery attribution: Esri, Maxar, Earthstar Geographics, and the GIS
User Community. Commercial use requires a suitable Esri licence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 09:30:02 +03:30
co-authored by Claude Opus 5.5
parent 5cea802b97
commit 9b289f76b0
1012 changed files with 529 additions and 41 deletions
+25
View File
@@ -0,0 +1,25 @@
// Reads public/data/buildings.bin (written by scripts/build-buildings.mjs) into the same
// [height, x0, z0, x1, z1, …] arrays the OSM buildings in mellat.json use.
export function decodeBuildings(buffer) {
const view = new DataView(buffer);
const magic = String.fromCharCode(...new Uint8Array(buffer, 0, 4));
if (magic !== 'RMB2') throw new Error(`unknown buildings file ${magic}`);
const count = view.getUint32(4, true), minX = view.getFloat32(8, true), minZ = view.getFloat32(12, true), step = view.getFloat32(16, true);
const buildings = new Array(count);
let o = 20;
for (let b = 0; b < count; b++) {
const n = view.getUint8(o), height = view.getUint8(o + 1) * .5;
o += 2;
const building = new Array(1 + n * 2);
building[0] = height;
let qx = 0, qz = 0;
for (let i = 0; i < n; i++, o += 4) {
qx = i ? qx + view.getInt16(o, true) : view.getUint16(o, true);
qz = i ? qz + view.getInt16(o + 2, true) : view.getUint16(o + 2, true);
building[1 + i * 2] = minX + qx * step;
building[2 + i * 2] = minZ + qz * step;
}
buildings[b] = building;
}
return buildings;
}