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

+78 -1
View File
@@ -4,11 +4,58 @@ import assert from 'node:assert/strict';
import {
resolveCockpitUtilityAnchor,
resolveCockpitUtilityLayout,
resolveCyberCockpitPanelLane,
} from './cockpitUtilityLayout.js';
// 1512x790, the height the strip used to collide with the briefing card at.
const desktop = { viewportHeight: 790, stripHeight: 107, collapsedHeight: 50 };
test('Cyber shared panel slot reserves both footer lanes above the decorative frame', () => {
for (const viewportHeight of [600, 673, 713, 986, 1376]) {
for (const signalHeight of [44, 76]) {
for (const contactHeight of [44, 84, 120]) {
const top = Math.max(164, Math.min(204, viewportHeight * 0.19));
const lane = resolveCyberCockpitPanelLane({ viewportHeight, top, signalHeight, contactHeight });
assert.ok(lane.panelHeight >= 120);
assert.ok(top + lane.panelHeight + 8 + contactHeight <= viewportHeight * 0.82 + .01);
assert.ok(lane.signalTop + signalHeight <= viewportHeight * 0.82 + .01);
assert.equal(lane.signalTop, top + lane.utilityHeight + 8);
}
}
}
});
test('Cyber keeps the Data Layers top while reserving briefing clearance', () => {
for (const viewportHeight of [673, 713, 986, 1376]) {
const leftTop = Math.max(164, Math.min(204, viewportHeight * 0.19));
const collapsed = resolveCockpitUtilityAnchor({
viewportHeight, stripHeight: 128, collapsedHeight: 60, recBottom: 0,
signalTop: leftTop + 128 + 28, minTopFloor: leftTop, minTopRatio: 0,
});
assert.equal(collapsed.top, leftTop);
assert.ok(collapsed.maxHeight >= 128);
const expanded = resolveCockpitUtilityAnchor({
viewportHeight, stripHeight: 700, collapsedHeight: 60, recBottom: 0,
signalTop: 255, minTopFloor: leftTop, minTopRatio: 0,
signalCollapsed: true, utilityExpanded: true, reserveViewportLane: true, signalHeight: 44,
});
assert.equal(expanded.top, leftTop);
assert.equal(expanded.top + expanded.maxHeight + 8 + 44 + 80, viewportHeight);
}
});
test('expanded utilities reserve the visible collapsed briefing header at every desktop height', () => {
for (const viewportHeight of [640, 720, 987, 1376]) {
for (const signalHeight of [48, 76, 100]) {
const { top, maxHeight } = resolveCockpitUtilityAnchor({
...desktop, viewportHeight, recBottom: 0, signalTop: 255,
signalCollapsed: true, utilityExpanded: true, signalHeight, reserveViewportLane: true,
});
assert.equal(top + maxHeight + 8, viewportHeight - 80 - signalHeight);
}
}
});
test('the strip hangs 12px under the REC readout when the briefing card leaves room', () => {
const { top } = resolveCockpitUtilityAnchor({ ...desktop, recBottom: 148.1, signalTop: 420 });
assert.equal(Number(top.toFixed(1)), 160.1);
@@ -54,6 +101,37 @@ test('a missing REC readout leaves the strip on the viewport ceiling', () => {
assert.equal(top, 96);
});
test('an expanded utility claims the vertical lane released by a collapsed briefing', () => {
const { top, maxHeight } = resolveCockpitUtilityAnchor({
...desktop,
recBottom: 148.1,
signalTop: 265.4,
signalCollapsed: true,
utilityExpanded: true,
reserveViewportLane: true,
});
assert.equal(Number(top.toFixed(1)), 160.1);
assert.equal(Number(maxHeight.toFixed(1)), 541.9);
});
test('non-relocating themes preserve the visible briefing boundary even when collapsed', () => {
const { top, maxHeight } = resolveCockpitUtilityAnchor({
...desktop, recBottom: 148.1, signalTop: 265.4,
signalCollapsed: true, utilityExpanded: true,
});
assert.equal(Number((top + maxHeight).toFixed(1)), 257.4);
});
test('a collapsed briefing still bounds the utility strip until a panel expands', () => {
const { maxHeight } = resolveCockpitUtilityAnchor({
...desktop,
recBottom: 148.1,
signalTop: 265.4,
signalCollapsed: true,
});
assert.equal(Number(maxHeight.toFixed(1)), 107);
});
test('keeps the collapsed sibling visible when both Cockpit utilities fit', () => {
assert.deepEqual(resolveCockpitUtilityLayout({
availableHeight: 320,
@@ -94,4 +172,3 @@ test('clamps malformed or short corridors to the minimum expanded height', () =>
expandedMaxHeight: 120,
});
});