From 65d41b971cb7b3404dcf95fa07d9a1dd8a6bc96f Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 15:05:36 +0400 Subject: [PATCH] Check traffic vehicles one by one against a fixed dark ground after the globe round trip Live Sentinel-2 imagery is bright at this altitude, so a fixed crop of white two-pixel vehicles changed anywhere from 14 to 300 pixels. The scenario now serves a uniform dark ground tile, waits for the layer to settle with vehicles in view, projects each vehicle to the window and requires 90% of those above the drawn terrain to change the picture where the layer places them; in simulation nothing else may change. Vehicles below the terrain and the population left after the round trip are reported, not asserted. --- scripts/qa-traffic-engine-switch.mjs | 190 ++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 18 deletions(-) diff --git a/scripts/qa-traffic-engine-switch.mjs b/scripts/qa-traffic-engine-switch.mjs index 75107417a..39c1ad701 100644 --- a/scripts/qa-traffic-engine-switch.mjs +++ b/scripts/qa-traffic-engine-switch.mjs @@ -31,9 +31,29 @@ try { const keyless = process.argv.includes('--simulation'); const reads = new Map(); + // A uniform dark Sentinel-2 tile: live imagery is bright and blurry at this + // altitude, so white 2 px vehicles on it gave anything from 14 to 300 + // changed pixels. A fixed dark ground makes every drawn vehicle visible. + const ground = await sharp({ + create: { + width: 256, + height: 256, + channels: 3, + background: { r: 28, g: 32, b: 30 }, + }, + }) + .jpeg() + .toBuffer(); await p.setRequestInterception(true); p.on('request', async (req) => { const u = new URL(req.url()); + if (u.hostname === 'tiles.maps.eox.at') + return req.respond({ + status: 200, + contentType: 'image/jpeg', + headers: { 'access-control-allow-origin': '*' }, + body: ground, + }); if ( u.hostname === 'tiles.openfreemap.org' || u.pathname.startsWith('/api/tomtom/') @@ -100,6 +120,7 @@ try { const first = await p.evaluate( () => window.qaTraffic.getStats().motion.rebuilds, ); + const population = await p.evaluate(() => window.qaTraffic.getStats().count); for (const kind of ['navara', 'cesium', 'navara']) { if ((await p.evaluate(() => window.__godsEyeView.engine.kind)) !== kind) { await p.select('#globe-engine', kind); @@ -143,39 +164,172 @@ try { } if (keyless) assert.ok(![...reads.keys()].some((u) => u.includes('/tomtom/flow/'))); - const pixels = async () => + // Compare the same view with the layer shown and hidden, vehicle by + // vehicle. Only a central window free of panels and status toasts counts. + const region = { left: 310, top: 160, right: 810, bottom: 660 }; + // Each vehicle in the window with its window position and its height above + // the terrain Navara draws there. A vehicle below that terrain is hidden by + // the depth test, so it cannot be expected in the picture. + const vehiclesInView = () => + p.evaluate((r) => { + const { engine } = window.__godsEyeView; + const a = 6378137, + f = 1 / 298.257223563, + e2 = f * (2 - f); + const rows = []; + for (const o of window.qaTraffic.getDetectableObjects()) { + const w = engine.projector.worldToWindow(o.position); + if (!w || w.x < r.left || w.y < r.top || w.x >= r.right) continue; + if (w.y >= r.bottom) continue; + const { x, y, z } = o.position; + const longitude = Math.atan2(y, x), + ring = Math.hypot(x, y); + let latitude = Math.atan2(z, ring * (1 - e2)), + height = 0; + for (let i = 0; i < 6; i++) { + const n = a / Math.sqrt(1 - e2 * Math.sin(latitude) ** 2); + height = ring / Math.cos(latitude) - n; + latitude = Math.atan2(z, ring * (1 - (e2 * n) / (n + height))); + } + const ground = engine.surface.terrainHeight({ + longitude, + latitude, + height: 0, + }); + rows.push({ + id: o.id, + x: w.x, + y: w.y, + clearance: Number.isFinite(ground) ? height - ground : null, + }); + } + return rows; + }, region); + const settled = async () => { + const deadline = Date.now() + 30000; + for (;;) { + const loading = await p.evaluate( + () => window.qaTraffic.getStats().loading, + ); + const rows = await vehiclesInView(); + if (!loading && rows.length >= 12) return rows; + if (Date.now() > deadline) + throw Error( + 'traffic did not settle with 12 vehicles in view: ' + + JSON.stringify({ loading, inView: rows.length }), + ); + await new Promise((resolve) => setTimeout(resolve, 500)); + } + }; + const raw = async () => sharp(await p.screenshot()) - .extract({ left: 360, top: 255, width: 330, height: 260 }) .removeAlpha() .raw() - .toBuffer(); + .toBuffer({ resolveWithObject: true }); // Source counts can be ready before the incoming renderer has drawn terrain. // Let the camera and the newly attached road heights settle before comparison. await new Promise((resolve) => setTimeout(resolve, 2200)); - const shown = await pixels(); + await settled(); + const settledCount = await p.evaluate( + () => window.qaTraffic.getStats().count, + ); + const before = await vehiclesInView(); + const shown = await raw(); + const after = new Map((await vehiclesInView()).map((v) => [v.id, v])); await p.evaluate(() => window.__godsEyeView.dataManager.setEnabled('traffic', false, { origin: 'user', }), ); await new Promise((resolve) => setTimeout(resolve, 600)); - const hidden = await pixels(); - let visible = 0; - for (let i = 0; i < shown.length; i += 3) { - if ( - shown[i] + - shown[i + 1] + - shown[i + 2] - - (hidden[i] + hidden[i + 1] + hidden[i + 2]) > - 110 - ) - visible++; + const hidden = await raw(); + const { width, height } = shown.info; + const scale = width / 1024; + const changed = (x, y) => { + const i = (y * width + x) * 3; + let d = 0; + for (let c = 0; c < 3; c++) + d += Math.abs(shown.data[i + c] - hidden.data[i + c]); + return d > 90; + }; + const near = new Set(); + const visible = { drawn: 0, missing: [] }; + let buried = 0, + unclear = 0; + for (const v of before) { + const later = after.get(v.id); + if (!later) continue; + // The screenshot falls between the two projections. + const cx = (v.x + later.x) / 2, + cy = (v.y + later.y) / 2; + let hit = false; + for (let dy = -8; dy <= 8; dy++) + for (let dx = -8; dx <= 8; dx++) { + const x = Math.round((cx + dx) * scale), + y = Math.round((cy + dy) * scale); + if (x < 0 || y < 0 || x >= width || y >= height) continue; + near.add(y * width + x); + if (Math.abs(dx) <= 4 && Math.abs(dy) <= 4 && changed(x, y)) hit = true; + } + if (v.clearance == null || Math.abs(v.clearance) < 1) unclear++; + else if (v.clearance < 0) buried++; + else if (hit) visible.drawn++; + else visible.missing.push(v); } - console.log('changed traffic pixels', visible); - assert.ok(visible > 15, 'changing the layer changes the rendered streets'); + let stray = 0, + area = 0; + for (let y = Math.round(region.top * scale); y < region.bottom * scale; y++) + for ( + let x = Math.round(region.left * scale); + x < region.right * scale; + x++ + ) { + area++; + if (!near.has(y * width + x) && changed(x, y)) stray++; + } + const aboveGround = visible.drawn + visible.missing.length; + console.log( + 'vehicles in view', + JSON.stringify({ + aboveGround, + drawn: visible.drawn, + belowTerrain: buried, + withinOneMetre: unclear, + strayChangedPixels: stray, + area, + }), + ); + if (visible.missing.length) + console.log('not drawn', JSON.stringify(visible.missing.slice(0, 10))); + if (buried) + console.log( + 'vehicles below the drawn terrain, hidden by the depth test:', + buried, + ); + // The checks after each switch run before the incoming globe republishes + // the roads. Report what the population settles to; it is not asserted yet. + console.log( + 'population before the switches', + population, + 'settled after the round trip', + settledCount, + ); + if (settledCount < 0.8 * population) + console.log( + 'known defect: the traffic population shrinks after returning to Navara', + ); + assert.ok(aboveGround >= 12, 'at least 12 vehicles above ground in view'); + assert.ok( + visible.drawn >= 0.9 * aboveGround, + 'at least 90% of vehicles above the ground are drawn where the layer places them', + ); + // Live mode adds congestion lines along roads; only the simulation must + // change nothing but the vehicles. + if (keyless) + assert.ok(stray < 0.002 * area, 'hiding the layer changes only vehicles'); assert.deepEqual(errors, []); console.log( - 'PASS retained traffic population and flow, real geography and stable identities across both globes', + 'PASS kept the traffic module, parameters, flow and vehicle identities across both globes; vehicles in view are drawn where the layer places them', ); } finally { await b.close();