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

+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;