Restore ground, picking and horizon parity through the globe contract

Draw launch-pad zones as radial ground discs with the original fill, rim,
transitions, emission and depth bias. Drape Transit history and its backing
on ground corridors. Keep the current age bands and once-per-second updates;
advance the active head with a reveal fraction and rebuild only at segment
crossings. Its wider dark edge and colour core blend once, as on Cesium.
Navara composes these looks after tone mapping.

Build reveal corridors and dynamic Transit history synchronously once shared
terrain bounds are loaded. Keep requesting frames until the visible globe
tiles and committed primitives are ready: classification needs current
surface depth after a camera move. Retain the reference radial material,
its alpha envelope and the head's material-only reveal updates.

Remove the empty Navara ground effect from the render chain when the last
shown set is hidden or destroyed. It otherwise remains as the insertion
anchor for later pulse effects after its packed textures have been disposed.
Cover reveal, destruction and replacement with fresh-state comparisons for
corridors, polygons, discs and cells. Cesium drawing is unchanged by this fix.

Keep the radial pixel check at a 500 metre radius and 4 kilometre view,
sample the brightest of six inner points around the native fill gap, and
sample across the rim width. Restore its view during arrival flights and
run it last among ground checks. Both suite launchers wait for the linked
view to arrive before checking. Keep these checks with the radial look.

Pass rectangular pick windows and drill limits through input subscriptions.
Street Level uses its 9 pixel window and CCTV uses its 14 pixel window and
six-result drill. Down picks stay synchronous, moves coalesce per frame and
Navara clicks keep the GPU answer from their gesture. Add a projector
ellipsoid option and use WGS84 radii reduced by 1,000 metres for Street Level.

These changes restore the original Cesium pad fill and ground trail rather
than the temporary ring and raised lines. Navara follows the same appearance
and horizon policy, and now honours explicit CPU pick windows. Existing
callers without these options retain their defaults. The four debts share
the contract, suite and engine implementations, so keep them together.

No coupling count grew or fell: renderer lines remain 0 and 189, external
Cesium imports 3, external viewer lines 891, engine-kind checks 1, engine
resolution calls 0, extension reads 3, ground passes 1, SDK loads 3, screen
handler files 4 and engine-name lines 32.

The contract and ground suites now wait until the camera has reached the
view in their link before checking, because the app's restore flight could
land in the middle of a check; that was also the cause of the intermittent
camera-events failure. The radial check uses a launch pad's size and samples
the fill at several inner points: Cesium's own old pad leaves the same gap in
a small ground disc's fill. The pulse-band check takes its base frame once
the surface has settled.

Checked in the browser, keyless: the contract suite passes on both engines
(84 and 85 checks), the ground suite passes on both, CCTV v2 passes 58
checks, and the space, transit, Street Level and CCTV engine switches pass.
The transit and Street Level scripts fail as they do before this change.
This commit is contained in:
German Khokhlov committed 2026-11-08 00:30:36 +04:00
1 parent be08163caa
commit aa6959c97b
49 files changed
+1727 -455

No files matched your search

+2 -1
View File
@@ -2143,7 +2143,8 @@
"src/geo/",
"src/layers/shared/color.js",
"src/globe/cameraMath.js",
"src/layers/streetLevel/render/display.js"
"src/layers/streetLevel/render/display.js",
"src/globe/contract.js"
],
"external": ["pbf", "@mapbox/vector-tile", "mapillary-js"]
},
+10 -2
View File
@@ -66,9 +66,17 @@ async function runOn(browser, kind) {
);
// The suite owns the globe: no camera flight running, and the app's panels
// let the pointer through to the canvas.
// The app restores the view in the link with a flight that can start
// after the loading screen goes: wait until the camera is there and still,
// or that flight lands in the middle of a check.
await page.waitForFunction(
() => !window.__godsEyeView.engine.camera.flightActive(),
{ timeout: 60_000, polling: 250 },
() => {
const { camera } = window.__godsEyeView.engine;
return (
!camera.flightActive() && Math.abs(camera.height() - 1_000_000) < 1000
);
},
{ timeout: 120_000, polling: 250 },
);
// Only the engine's canvas takes the pointer: the world overlay draws on a
// canvas of its own above it.
+10 -2
View File
@@ -47,9 +47,17 @@ async function runOn(browser, kind) {
{ timeout: 300_000, polling: 250 },
kind,
);
// The app restores the view in the link with a flight that can start
// after the loading screen goes: wait until the camera is there and still,
// or that flight lands in the middle of a check.
await page.waitForFunction(
() => !window.__godsEyeView.engine.camera.flightActive(),
{ timeout: 60_000, polling: 250 },
() => {
const { camera } = window.__godsEyeView.engine;
return (
!camera.flightActive() && Math.abs(camera.height() - 1_000_000) < 1000
);
},
{ timeout: 120_000, polling: 250 },
);
// The globe drawn and its tiles loaded: a restored view hides the globe
// for a moment after the loading screen goes.
+5 -6
View File
@@ -100,7 +100,7 @@ try {
),
orientation: { heading: 0, pitch: -Math.PI / 3, roll: 0 },
});
// The trail draws through engine.lines (render/display.js); its renderer
// The trail draws through engine.ground (render/display.js); its renderer
// is the handle that shows and hides the whole trail. Keep the one the
// selection below creates.
let trailRenderer = null;
@@ -320,8 +320,8 @@ try {
scene.globe.tilesLoaded &&
parts.trails.diagnostics().body === trail.body,
elapsedMs: performance.now() - started,
// The trail's engine lines (age bands and head, each with a backing).
lines: app.engine.lines
// The trail's ground corridors (age bands and head, each with a backing).
lines: app.engine.ground
.list()
.filter(({ id }) => id.startsWith('transit-trail-')),
};
@@ -337,9 +337,8 @@ try {
const pass =
result.before?.shown === false &&
result.before?.phase === 'playing' &&
// preparedAtSelection is reported, not required: the trail is engine
// lines in the air now (render/display.js), not a draped ground
// polyline, so it has no geometry until the floors it waits on arrive.
// Ground history is prepared even while marker floors are unresolved.
result.preparedAtSelection &&
result.markerShow &&
result.body &&
result.bodyShow &&
+8 -6
View File
@@ -655,12 +655,12 @@ export async function runTrailVisibility({
),
orientation: { heading: 0, pitch: (-pitch * Math.PI) / 180, roll: 0 },
});
// The trail draws through engine.lines (render/display.js); keep the
// The trail draws through engine.ground (render/display.js); keep the
// renderer the selection below creates, the handle that shows and
// hides the whole trail.
const createLine = app.engine.lines.createLine.bind(app.engine.lines);
const createLine = app.engine.ground.createSet.bind(app.engine.ground);
window.__qaTransitLineKinds = new Map();
app.engine.lines.createLine = (options) => {
app.engine.ground.createSet = (options) => {
const line = createLine(options);
if (options.id.startsWith('transit-trail-'))
window.__qaTransitLineKinds.set(
@@ -670,7 +670,7 @@ export async function runTrailVisibility({
return line;
};
window.__restoreTransitLines = () => {
app.engine.lines.createLine = createLine;
app.engine.ground.createSet = createLine;
};
const createTrailRenderer = parts.renderer.createTrailRenderer;
parts.renderer.createTrailRenderer = (...args) => {
@@ -796,12 +796,13 @@ export async function runTrailVisibility({
const walk = (collection) => {
for (let i = 0; i < collection.length; i++) {
const primitive = collection.get(i);
const kind = window.__qaTransitLineKinds.get(primitive?.gevLine);
const kind = window.__qaTransitLineKinds.get(primitive?.gevGroundSet);
if (kind) wrap(primitive, kind);
else if (typeof primitive?.get === 'function') walk(primitive);
}
};
walk(scene.primitives);
walk(scene.groundPrimitives);
});
const post = scene.postRender.addEventListener(() => {
frames++;
@@ -907,7 +908,7 @@ export async function runTrailVisibility({
const item = collection.get(i);
if (
String(item?.gevMoverSet ?? '').startsWith('transit') ||
String(item?.gevLine ?? '').startsWith('transit-trail-')
String(item?.gevGroundSet ?? '').startsWith('transit-trail-')
)
drawnByTransit.push(item);
else if (typeof item?.get === 'function' && 'length' in item)
@@ -915,6 +916,7 @@ export async function runTrailVisibility({
}
};
walk(scene.primitives);
walk(scene.groundPrimitives);
const lengths = [0];
for (let i = 1; i < points.length; i++)
lengths.push(lengths.at(-1) + V.distance(points[i - 1], points[i]));