feat(hud): Cyber HUD theme with GPU contact sonar (#706)

Add the opt-in Cyber HUD layout with coordinated map and cockpit panel
styling, GPU-only contact sonar (rings, range, power, opacity, sector) with
native-rendering fallback, and set_cyber_sonar voice controls. Sonar visual
style inspired by kk376's tactical naval sonar (#214).

Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-09-23 20:31:56 -07:00
1 parent f913066cf2
commit 513dc87a0e
74 files changed
+7320 -90

No files matched your search

+30 -1
View File
@@ -257,6 +257,7 @@
"runtime": "node",
"exports": ["./server/providers/openai"],
"modules": [
"src/hudLayoutPolicy.js",
"server/providers/common/rate-limit.js",
"server/providers/common/request.js",
"server/providers/common/source-root.js",
@@ -327,6 +328,7 @@
"exports": ["./ui/layout"],
"modules": [
"src/ui/panelRails.js",
"src/ui/displayPanelScroll.js",
"src/ui/leftPanelRail.js",
"src/ui/rightPanelRail.js",
"src/ui/panelMeasurement.js",
@@ -462,6 +464,7 @@
"src/ui/cockpitContext.js",
"src/ui/cockpitController.js",
"src/ui/cockpitDisplayPortal.js",
"src/ui/displayPanelScroll.js",
"src/ui/cockpitInput.js",
"src/ui/cockpitInstruments.js",
"src/ui/cockpitLayout.js",
@@ -487,6 +490,10 @@
"ui-shell": {
"exports": ["./ui/shell"],
"modules": [
"src/hudLayouts.js",
"src/hudLayoutPolicy.js",
"src/cyberSonar.js",
"src/ui/radioPanelPlacement.js",
"src/app/stateChannel.js",
"src/bloom.js",
"src/cctvFocusPolicy.js",
@@ -517,6 +524,7 @@
"src/styles/surveillance.js",
"src/styles/thermal.js",
"src/ui/applicationShell.js",
"src/ui/cyberSonarControls.js",
"src/ui/locationNavigation.js",
"src/ui/cockpitCoordinator.js",
"src/ui/cameraOrientationControls.js",
@@ -535,6 +543,7 @@
"src/ui/cockpitContext.js",
"src/ui/cockpitController.js",
"src/ui/cockpitDisplayPortal.js",
"src/ui/displayPanelScroll.js",
"src/ui/cockpitInput.js",
"src/ui/cockpitInstruments.js",
"src/ui/cockpitLayout.js",
@@ -727,6 +736,7 @@
"civil-flights": {
"exports": ["./layers/flights"],
"modules": [
"src/cyberSonar.js",
"src/data/aircraftClass.js",
"src/data/aircraftIcons.js",
"src/data/aircraftMeta.js",
@@ -767,6 +777,7 @@
"military-flights": {
"exports": ["./layers/military"],
"modules": [
"src/cyberSonar.js",
"src/data/aircraftClass.js",
"src/data/aircraftIcons.js",
"src/data/aircraftMeta.js",
@@ -921,6 +932,7 @@
"launches-layer": {
"exports": ["./layers/launches"],
"modules": [
"src/cyberSonar.js",
"src/layers/launches/camera.js",
"src/layers/launches/controls.js",
"src/layers/launches/index.js",
@@ -967,6 +979,7 @@
"awareness-layer": {
"exports": ["./layers/awareness"],
"modules": [
"src/cyberSonar.js",
"src/cockpitMath.js",
"src/data/iconOrientation.js",
"src/data/militaryAwarenessEngine.js",
@@ -1247,6 +1260,12 @@
"./standalone/catalog"
],
"modules": [
"src/cyberSonarScene.js",
"src/cyberSonar.js",
"src/hudLayouts.js",
"src/hudLayoutPolicy.js",
"src/ui/radioPanelPlacement.js",
"src/cyberSonarGpu.js",
"src/scenes/nepalEvidencePack.js",
"src/data/bhoteKoshiEvent.js",
"src/ui/imagerySplit.js",
@@ -1685,6 +1704,7 @@
"src/styles/surveillance.js",
"src/styles/thermal.js",
"src/ui/applicationShell.js",
"src/ui/cyberSonarControls.js",
"src/ui/locationNavigation.js",
"src/ui/cockpitCoordinator.js",
"src/ui/cameraOrientationControls.js",
@@ -1703,6 +1723,7 @@
"src/ui/cockpitContext.js",
"src/ui/cockpitController.js",
"src/ui/cockpitDisplayPortal.js",
"src/ui/displayPanelScroll.js",
"src/ui/cockpitInput.js",
"src/ui/cockpitInstruments.js",
"src/ui/cockpitLayout.js",
@@ -1950,6 +1971,7 @@
"application-layer-construction": {
"exports": ["./application/layers"],
"modules": [
"src/cyberSonar.js",
"src/data/bhoteKoshiEvent.js",
"src/ui/imagerySplit.js",
"src/maps/imageryComparison.js",
@@ -2481,6 +2503,10 @@
"ui-visual-settings": {
"exports": ["./ui/visual-settings"],
"modules": [
"src/cyberSonar.js",
"src/hudLayouts.js",
"src/hudLayoutPolicy.js",
"src/ui/radioPanelPlacement.js",
"src/bloom.js",
"src/cockpitVisionPolicy.js",
"src/contactsDetectionPolicy.js",
@@ -2505,6 +2531,8 @@
"ui-panel-chrome": {
"exports": ["./ui/panel-chrome"],
"modules": [
"src/ui/displayPanelScroll.js",
"src/ui/radioPanelPlacement.js",
"src/panelStackLayout.js",
"src/ui/leftPanelRail.js",
"src/ui/panelChrome.js",
@@ -2595,7 +2623,7 @@
},
"voice-action-schemas": {
"exports": ["./voice/action-schemas"],
"modules": ["src/voice/actionSchemas.js"],
"modules": ["src/voice/actionSchemas.js", "src/hudLayoutPolicy.js"],
"external": []
},
"voice-session": {
@@ -2757,6 +2785,7 @@
"transit-standalone": {
"exports": ["./layers/transit/standalone"],
"modules": [
"src/cyberSonar.js",
"src/app/sourceSlot.js",
"src/celestialRing.js",
"src/data/cameraSensitivity.js",
+92
View File
@@ -1943,6 +1943,98 @@ try {
}),
);
// Cyber relocates its collapsed briefing header into the expanded utility
// lane. Exercise actual height changes without reload and measure both cards.
await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
manager._setHudVariant('cyber');
manager.hud.setMode('on');
manager._setCockpitDisclosure('display', false);
manager._setCockpitDisclosure('radio', true);
});
const cyberRadioOrder = await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
manager.cockpitView.syncSignalLayout();
const display = document
.getElementById('cockpit-display-toggle-btn')
.closest('.cockpit-utility-control');
const radio = document
.getElementById('cockpit-radio-toggle-btn')
.closest('.cockpit-utility-control');
const displayRect = display.getBoundingClientRect();
const radioRect = radio.getBoundingClientRect();
return {
display: displayRect.toJSON(),
radio: radioRect.toJSON(),
displayExpanded: display.classList.contains('is-expanded'),
radioExpanded: radio.classList.contains('is-expanded'),
gap: radioRect.top - displayRect.bottom,
};
});
check(
'Cyber expanding Radio preserves the authored Display-then-Radio order',
!cyberRadioOrder.displayExpanded
&& cyberRadioOrder.radioExpanded
&& cyberRadioOrder.display.height > 0
&& cyberRadioOrder.radio.height > 0
&& cyberRadioOrder.gap >= 7,
JSON.stringify(cyberRadioOrder),
);
await page.screenshot({ path: path.join(shotsDir, 'cyber-radio-order.png') });
await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
manager._setCockpitDisclosure('radio', false);
manager._setCockpitDisclosure('display', true);
});
const cyberStyleStates = await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
const states = [];
for (const style of ['normal', 'surveillance', 'thermal', 'retro']) {
document.querySelector(`.style-btn[data-style="${style}"]`).click();
states.push({
requestedStyle: style,
activeStyle: manager.activeStyle,
hud: manager.hud.getVariant(),
theme: document.documentElement.dataset.uiTheme,
selector: document.getElementById('hud-layout-select').value,
savedHud: manager.getVisualState().hud.variant,
});
}
return states;
});
check('Cockpit visual-style controls retain Cyber HUD, selector, shell and saved layout',
cyberStyleStates.every(state => state.activeStyle === state.requestedStyle
&& state.hud === 'cyber' && state.theme === 'cyber'
&& state.selector === 'cyber' && state.savedHud === 'cyber'),
JSON.stringify(cyberStyleStates));
for (const height of [987, 720, 640, 900]) {
await page.setViewport({ width: 1440, height, deviceScaleFactor: 1 });
await new Promise((resolve) => setTimeout(resolve, 400));
const geometry = await page.evaluate(() => {
const signal = document.getElementById('cockpit-signal-stream');
const utility = document.getElementById('cockpit-utility-controls');
const expanded = utility.querySelector('.cockpit-utility-control.is-expanded');
const s = signal.getBoundingClientRect();
const u = expanded.getBoundingClientRect();
const toggle = document.getElementById('cockpit-signal-toggle');
const t = toggle.getBoundingClientRect();
const hit = document.elementFromPoint(t.x + t.width / 2, t.y + t.height / 2);
return { height: innerHeight, theme: document.documentElement.dataset.uiTheme,
collapsed: signal.dataset.collapsed, signal: s.toJSON(), utility: u.toJSON(),
reachable: Boolean(hit && toggle.contains(hit)),
contained: u.bottom <= s.top - 7 && s.bottom < innerHeight,
};
});
check(`Cyber collapsed Live Signals remains visible and reachable after resize to ${height}px`,
geometry.theme === 'cyber' && geometry.collapsed === 'true'
&& geometry.signal.width > 0 && geometry.signal.height > 0
&& geometry.reachable && geometry.contained, JSON.stringify(geometry));
if (height === 640 || height === 900) {
await page.screenshot({ path: path.join(shotsDir, `cyber-resize-${height}.png`) });
}
}
await page.evaluate(() => window.__godsEyeView.styleManager._setHudVariant('operator'));
const signalTransition = await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
const cockpit = manager.cockpitView;