test(qa): flights parity checklist on both engines, side by side

Task 10b:
- qa-w1-layers (flights): 3 focus (the selected object is the focus target,
  at the followed centre ≤ 3 px), 4 tracking (held ≤ 3 px for 5 s; wheeled
  in, ≥ 149 m), 5 the layer's queries (tracked info, contacts, nearby,
  detectable; compared by --compare), 6 share links (the followed object
  restored on the same and on the other engine). Clicks only on points the
  canvas takes (the Navara attribution card covered the old empty point).
  --compare reports the global pose's 2.3 px as the cameras' (Cesium
  projects its own nadir 2.27 px off centre at 15,000 km).
  Cesium 19/19, Navara 20/20, parity.
- qa-flights-engines (new): the same frozen synthetic fleet at the global,
  city, orbit and near (JFK, oblique) poses on both engines, UI hidden;
  side-by-side JPEGs (docs/migration/phase3/flights, models off and on),
  the same objects in the frame, screen points ≤ 2 px, glyph fill colours
  (brightest pixel at the glyph) within 3. 12/12 with models off and on.
- qa-labels: on an engine with flights but not military/satellites, the
  deterministic 12,000 / 5,200 field is all aircraft, so detection is
  gated on Navara too: 14/14 (Cesium 14/14 unchanged).
This commit is contained in:
German Khokhlov committed 2026-10-17 02:35:32 +04:00
1 parent b14bf7e013
commit 03a0ff3a01
2 files changed
+36 -3

No files matched your search

+1
View File
@@ -1118,6 +1118,7 @@
"src/app/layers/qaSources.js",
"src/app/layers/qaSources.test.mjs",
"scripts/qa-w1-layers.mjs",
"scripts/qa-flights-engines.mjs",
"src/bench/w1Feeds.js",
"scripts/qa-map-switch.mjs",
"scripts/qa-engine-notice.mjs"
+35 -3
View File
@@ -322,7 +322,38 @@ async function main() {
(ids) => ids.filter((id) => !window.__godsEyeView.layers.available(id)),
fieldLayers,
);
if (missingLayers.length) {
// An engine with flights but not the other field layers (Navara, W1 so
// far): the same 12,000 / 5,200 observations, all as aircraft, so the
// detection lane is measured on the layers the engine has.
let fieldCounts = FIELD_COUNTS;
let normalCounts = NORMAL_COUNTS;
if (missingLayers.length && !missingLayers.includes('flights')) {
const sum = (counts) =>
Object.values(counts).reduce((total, count) => total + count, 0);
fieldCounts = Object.fromEntries(
fieldLayers.map((id) => [
id,
missingLayers.includes(id)
? 0
: id === 'flights'
? sum(FIELD_COUNTS) - fieldLayers.filter((other) => other !== 'flights' && !missingLayers.includes(other)).reduce((total, other) => total + FIELD_COUNTS[other], 0)
: FIELD_COUNTS[id],
]),
);
normalCounts = Object.fromEntries(
fieldLayers.map((id) => [
id,
missingLayers.includes(id)
? 0
: id === 'flights'
? sum(NORMAL_COUNTS) - fieldLayers.filter((other) => other !== 'flights' && !missingLayers.includes(other)).reduce((total, other) => total + NORMAL_COUNTS[other], 0)
: NORMAL_COUNTS[id],
]),
);
console.log(
` [INFO] the field as ${JSON.stringify(fieldCounts)} (no ${missingLayers.join(', ')} on ${ENGINE})`,
);
} else if (missingLayers.length) {
console.log(` [SKIP] the deterministic field checks — no ${missingLayers.join(', ')} on ${ENGINE}`);
skippedChecks += 1;
record(
@@ -370,7 +401,8 @@ async function main() {
window.__LABEL_QA_NORMAL_COUNTS = normalCounts;
for (const layerId of layerIds) {
const entry = dataManager.layers.get(layerId);
entry.module.getDetectableObjects = () => window.__LABEL_QA_FIELD[layerId];
if (entry)
entry.module.getDetectableObjects = () => window.__LABEL_QA_FIELD[layerId];
}
// The global camera, orbiting west to east (the old rotateRight about
@@ -446,7 +478,7 @@ async function main() {
};
requestAnimationFrame(sample);
return Object.fromEntries(layerIds.map((layerId) => [layerId, field[layerId].length]));
}, { fieldCounts: FIELD_COUNTS, normalCounts: NORMAL_COUNTS });
}, { fieldCounts, normalCounts });
record(
'deterministic field contains exactly 12,000 observations',