Extract the native FIRMS renderer and own its settle timer

Keep fire normalization, LOD, anchors, cards and selection portable while the native renderer owns ground cells, sprites, input and scene disposal. Cancel superseded/removed settle timers and unregister the original sprite collection.

Preserve all ten native raster pairs exactly in dev and preview; record source/click/focus/lifecycle, legacy Metal and full regression evidence with strict comparator sensitivity checks.
This commit is contained in:
German Khokhlov committed 2026-10-27 14:40:08 +04:00
1 parent a3152ab719
commit a9b3125a99
25 files changed
+672 -123

No files matched your search

+1
View File
@@ -35,6 +35,7 @@ const navaraPackage = /^(?:(?:three|postprocessing)(?:\/|$)|@navaramap\/)/;
// Navara only in render/navara*.js. The list grows with each moved layer.
const RENDERED_LAYER_DIRS = [
'src/layers/earthquakes/',
'src/layers/firms/',
'src/layers/flights/',
'src/layers/military/',
'src/layers/localAdsb/',
+57
View File
@@ -0,0 +1,57 @@
// Real /api/firms envelope; fixed source receipt and acquisition clocks.
export const FIRMS_EPOCH_MS = Date.UTC(2026, 9, 16, 12);
export const FIRMS_VALID_COUNT = 371;
export function firmsFixture() {
const fires = [];
const regions = [
[37.77, -122.42],
[30.27, -97.74],
[61.2, -149.9],
];
for (const [region, [latitude, longitude]] of regions.entries()) {
for (let row = -5; row <= 5; row++) {
for (let column = -5; column <= 5; column++) {
const index = fires.length;
fires.push({
lat: latitude + row * 0.12,
lon: longitude + column * 0.12,
frp:
region === 0 && row === 0 && column === 0 ? 999 : 5 + (index % 120),
confidence: ['low', 'nominal', 'high', 85][index % 4],
brightness: 280 + (index % 90),
daynight: index % 2 ? 'N' : 'D',
acqDate: '2026-10-16',
acqTime: String(800 + (index % 60)),
instrument: index % 4 === 3 ? 'MODIS' : 'VIIRS',
satellite: index % 4 === 3 ? 'Terra' : 'NOAA-20',
});
}
}
}
for (const [lat, lon] of [
[10, 179.7],
[10, -179.7],
[89.5, 45],
[-89.5, -135],
[0.2, 179.8],
[-0.2, -179.8],
[70, 179.5],
[-70, -179.5],
]) {
fires.push({
lat,
lon,
frp: 30,
confidence: 'high',
brightness: 320,
daynight: 'D',
acqDate: '2026-10-16',
acqTime: '930',
instrument: 'VIIRS',
satellite: 'NOAA-20',
});
}
fires.push({ lat: 'invalid', lon: 0 }, { lat: 0, lon: 'invalid' });
return { fires, fetchedAt: FIRMS_EPOCH_MS - 120000, stale: false };
}
+8 -1
View File
@@ -1194,5 +1194,12 @@
"src/bench/w2GroundProbe.js",
"scripts/qa-w2-ground-mesh.mjs",
"scripts/qa-w2-ground-perf.mjs",
"scripts/qa-w2-ground-equivalence.mjs"
"scripts/qa-w2-ground-equivalence.mjs",
"src/layers/firms/render/cesium.js",
"src/layers/firms/render/cesium.test.mjs",
"src/layers/firms/rectangle.js",
"src/layers/firms/rectangle.test.mjs",
"src/layers/firms/viewport.test.mjs",
"scripts/qa-w2-firms.mjs",
"scripts/fixtures/w2/firms.mjs"
]
+9 -7
View File
@@ -1113,8 +1113,6 @@
"modules": [
"src/data/firmsAdapt.js",
"src/data/firmsLabels.js",
"src/data/iconOrientation.js",
"src/data/render/cesiumProjection.js",
"src/layers/firms/anchors.js",
"src/layers/firms/cards.js",
"src/layers/firms/index.js",
@@ -1145,10 +1143,10 @@
"src/geo/check.js",
"src/geo/debug.js",
"src/geo/developerError.js",
"src/globe/contract.js",
"src/globe/cesium/engineFromViewer.js"
"src/layers/firms/rectangle.js",
"src/layers/shared/color.js"
],
"external": ["cesium"]
"external": []
},
"satellites-layer": {
"exports": ["./layers/satellites"],
@@ -2698,7 +2696,9 @@
"src/layers/earthquakes/render/cesium.js",
"src/layers/earthquakes/render/navara.js",
"src/layers/earthquakes/render/navaraGeometry.js",
"src/layers/earthquakes/render/navaraGrid.js"
"src/layers/earthquakes/render/navaraGrid.js",
"src/layers/firms/rectangle.js",
"src/layers/firms/render/cesium.js"
],
"external": [
"hls.js",
@@ -3291,7 +3291,9 @@
"src/celestialRing/view.js",
"src/layers/shared/render/engineView.js",
"src/layers/satellites/render/cesium.js",
"src/layers/earthquakes/render/cesium.js"
"src/layers/earthquakes/render/cesium.js",
"src/layers/firms/rectangle.js",
"src/layers/firms/render/cesium.js"
],
"external": [
"hls.js",
+29 -1
View File
@@ -30,6 +30,7 @@
*/
import puppeteer from 'puppeteer';
import { redact } from './qa-browserEvidence.mjs';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -46,6 +47,11 @@ const getOpt = (name, dflt) => {
const APP_URL = getOpt('--url', 'http://localhost:4420');
const HEADFUL = argv.includes('--headful');
const FIXTURES = argv.includes('--fixtures');
const ANGLE = getOpt('--angle', 'swiftshader');
const OUT = getOpt('--out', null);
if (!['metal', 'swiftshader'].includes(ANGLE)) throw new Error('Unknown ANGLE backend');
const runtimeErrors = [];
const expectedSourceErrors = [];
const CHROME_EXECUTABLE_CANDIDATES = [
process.env.PUPPETEER_EXECUTABLE_PATH,
@@ -247,7 +253,7 @@ async function main() {
headless: HEADFUL ? false : 'new',
...(findChromeExecutable() ? { executablePath: findChromeExecutable() } : {}),
args: [
'--no-sandbox', '--disable-setuid-sandbox', '--use-gl=angle', '--use-angle=swiftshader',
'--no-sandbox', '--disable-setuid-sandbox', '--use-gl=angle', `--use-angle=${ANGLE}`,
'--disable-dev-shm-usage', '--disable-web-security',
'--disable-background-timer-throttling', '--disable-renderer-backgrounding',
'--window-size=1440,900',
@@ -261,6 +267,19 @@ async function main() {
await page.setRequestInterception(true);
let interceptMode = FIXTURES ? 'fixture' : 'live';
page.on('pageerror', error => runtimeErrors.push(redact(error.message)));
page.on('error', error => runtimeErrors.push(redact(error.message)));
page.on('console', message => {
if (message.type() !== 'error') return;
let expected = false;
try {
const url = new URL(message.location()?.url);
expected = interceptMode === 'keyless' && url.origin === new URL(APP_URL).origin
&& url.pathname === '/api/firms' && /^Failed to load resource:.*\b503\b/.test(message.text());
} catch { /* an unknown location is never waived */ }
if (expected) expectedSourceErrors.push('/api/firms: intentional keyless fixture503');
else runtimeErrors.push(redact(message.text()));
});
const stableStalePayload = stalePayload();
page.on('request', (req) => {
const url = req.url();
@@ -509,12 +528,21 @@ async function main() {
await browser.close();
}
record('RUNTIME: no unexpected browser errors', runtimeErrors.length === 0,
runtimeErrors.length ? JSON.stringify(runtimeErrors) : `expected source errors=${expectedSourceErrors.length}`);
if (runtimeErrors.length) exitCode = 1;
const pass = results.filter((r) => r.ok === true).length;
const fail = results.filter((r) => r.ok === false).length;
console.log('\n' + '─'.repeat(60));
console.log(` RESULT: ${pass} passed, ${fail} failed`);
console.log(` Shots : ${SHOTS_DIR}/firms-*.png`);
console.log('─'.repeat(60) + '\n');
if (OUT) {
const file = path.resolve(OUT);
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, JSON.stringify({ pass:exitCode === 0 && fail === 0, fixture:FIXTURES, angle:ANGLE,
results, browserErrors:runtimeErrors, expectedSourceErrors }, null, 2) + '\n');
}
process.exit(exitCode || (fail > 0 ? 1 : 0));
}