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

+53
View File
@@ -1,4 +1,5 @@
import { readStylesheet } from '../testSupport/readStylesheet.mjs';
import { displayPanelScroller } from './displayPanelScroll.js';
import assert from 'node:assert/strict';
import test from 'node:test';
import {
@@ -7,6 +8,58 @@ import {
measurePanelNaturalHeight,
} from './panelRails.js';
test('Display uses an inner scroll body only in Cyber', () => {
const body = { scrollTop: 95 };
const panel = {
ownerDocument: { documentElement: { dataset: { uiTheme: 'cyber' } } },
querySelector: () => body,
};
assert.equal(displayPanelScroller(panel), body);
panel.ownerDocument.documentElement.dataset.uiTheme = 'tactical';
assert.equal(displayPanelScroller(panel), panel);
assert.equal(displayPanelScroller(null), null);
});
test('Cyber restores one expanded owner, keeps launchers, and restores other theme preferences', () => {
for (const mobile of [false, true]) {
const f = fixture('right', {
mobile,
hud: { visible: true, variant: 'cyber' },
});
f.expand(f.first, 250);
f.expand(f.second, 250);
f.options.preferredPanelId = f.second.id;
f.run();
assert.equal(f.first.classList.contains('cyber-accordion-collapsed'), true);
assert.equal(f.second.classList.contains('collapsed'), false);
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
f.run();
assert.equal(
f.first.classList.contains('collapsed'),
true,
'layout does not reopen a peer',
);
f.options.hud.variant = 'operator';
f.run();
assert.equal(f.first.classList.contains('collapsed'), false);
assert.equal(
f.first.classList.contains('cyber-accordion-collapsed'),
false,
);
assert.equal(f.second.classList.contains('collapsed'), false);
}
});
test('Cyber restored accordion prefers keyboard focus when no explicit owner exists', () => {
const f = fixture('right', { hud: { visible: true, variant: 'cyber' } });
f.expand(f.first, 250);
f.expand(f.second, 250);
f.options.documentRef.activeElement = f.second;
f.run();
assert.equal(f.first.classList.contains('collapsed'), true);
assert.equal(f.second.classList.contains('collapsed'), false);
});
function element(
id,
{ height = 42, top = 234, left = 20, width = 272, collapsed = false } = {},