import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' import { createCameraControl } from '../../../src/modules/cockpit/services/cameraControl.js' function fixture() { let now = 0 let frame = null const applied = [] const control = createCameraControl({ engine: { now: () => now, requestFrame: (callback) => { frame = callback }, applyView: (pivot, hpr) => applied.push({ pivot: { ...pivot }, hpr: { ...hpr } }), pickGroundAtCenter: () => null, viewport: () => ({ width: 1680, height: 900 }), fovY: () => Math.PI / 3, } }) const settle = () => { for (let n = 0; frame && n < 500; n += 1) { const callback = frame; frame = null; now += 16; callback(now) } assert.equal(control.getState().settled, true) } control.applyViewPreset({ lon: 110.4, lat: 28.44, height: 320 }, { heading: 0, pitch: -55, range: 150 }, { immediate: true }) return { control, settle, applied } } let failed = 0 function check(name, fn) { try { fn(); console.log(`PASS ${name}`) } catch (error) { failed += 1; console.error(`FAIL ${name}: ${error.message}`) } } check('pan then select device preserves sampled ground Z and stays above terrain', () => { const { control, settle, applied } = fixture() control.beginDrag({ button: 0, x: 500, y: 300 }) control.moveDrag({ x: 560, y: 320 }) control.endDrag(); settle() const before = control.getState().hpr const device = { lon: 110.4001, lat: 28.4401, height: 345 } control.setFocus(device); settle() const state = control.getState() assert.equal(state.pivot.height, device.height, 'device ground height must not be replaced by ellipsoid zero') assert.equal(state.focus.height, device.height) assert.deepEqual(state.hpr, before) const last = applied.at(-1) const cameraHeight = last.pivot.height + last.hpr.range * Math.sin(-last.hpr.pitch * Math.PI / 180) assert.ok(cameraHeight > device.height + 10, `camera ${cameraHeight} must clear ground ${device.height}`) assert.ok(applied.every((view) => view.pivot.height >= 320), 'flight must not interpolate down to zero') }) check('same XY device selection still updates newly sampled Z', () => { const { control, settle } = fixture() control.setFocus({ lon: 110.4, lat: 28.44, height: 360 }); settle() assert.equal(control.getState().pivot.height, 360) }) check('orbit drag pending selection preserves device ground height', () => { const { control, settle } = fixture() control.beginDrag({ button: 2, x: 500, y: 300 }) control.setFocus({ lon: 110.401, lat: 28.441, height: 350 }) control.endDrag(); settle() assert.equal(control.getState().pivot.height, 350) }) const service = readFileSync(new URL('../../../src/modules/cockpit/services/mapService.js', import.meta.url), 'utf8') const extract = (name, async = false) => service.match(new RegExp(`export ${async ? 'async ' : ''}function ${name}\\([^]*?\\n\\}`))[0].replace(/^export /, '') const { control, settle } = fixture() control.beginDrag({ button: 0, x: 500, y: 300 }) control.moveDrag({ x: 580, y: 350 }) control.endDrag(); settle() const pending = [] const record = { cameraControl: control, viewer: {}, engine: { sampleTerrainHeight: () => new Promise((resolve) => pending.push(resolve)), groundHeightAt: () => 345, } } const context = vm.createContext({ instance: record, pointLocateToken: 0, isRenderable: (object) => object.coordinate?.renderable === true, isStaleLocate: (mine, current) => mine !== current, TERRAIN_STATE: { NONE: 'none' }, terrainState: 'ready', }) for (const name of ['cancelPointLocate', 'setCameraFocus']) vm.runInContext(extract(name), context) vm.runInContext(extract('locateMonitoringPoint', true), context) context.deviceA = { longitude: 110.4001, latitude: 28.4401, coordinate: { renderable: true } } context.deviceB = { longitude: 110.4002, latitude: 28.4402, coordinate: { renderable: true } } const first = vm.runInContext("locateMonitoringPoint(deviceA, { tier: 'device', centerOnly: true })", context) pending.shift()(345) await first; settle() check('service terrain sampling survives real setCameraFocus and controller', () => { assert.equal(control.getState().pivot.height, 345) assert.ok(Math.abs(control.getState().pivot.lon - context.deviceA.longitude) < 1e-9) }) const stale = vm.runInContext("locateMonitoringPoint(deviceA, { tier: 'device', centerOnly: true })", context) const latest = vm.runInContext("locateMonitoringPoint(deviceB, { tier: 'device', centerOnly: true })", context) const resolveStale = pending.shift() const resolveLatest = pending.shift() resolveLatest(370); await latest resolveStale(100); await stale settle() check('late terrain response cannot move camera down to previously selected device', () => { assert.ok(Math.abs(control.getState().pivot.lon - context.deviceB.longitude) < 1e-9) assert.equal(control.getState().pivot.height, 370) }) console.log(`T45 device camera height: ${failed ? 'FAILED' : 'passed'}`) process.exitCode = failed ? 1 : 0