Files
the-nexus/modules/panels/sovereignty.js
Claude (Opus 4.6) 481a0790d2
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
[claude] Phase 3: Panel modules — Heatmap, Agent Board, Dual-Brain, LoRA, Sovereignty, Earth (#422) (#446)
Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
2026-03-24 18:22:34 +00:00

148 lines
4.2 KiB
JavaScript

// modules/panels/sovereignty.js — Sovereignty Meter holographic arc gauge
// Floating arc gauge above the platform showing the current sovereignty score.
// Reads from state.sovereignty (populated by data/loaders.js via sovereignty-status.json).
// The assessment is MANUAL — the panel always labels itself as such.
//
// Data category: REAL (manual assessment)
// Data source: state.sovereignty (sovereignty-status.json via data/loaders.js)
import * as THREE from 'three';
import { state } from '../core/state.js';
import { NEXUS } from '../core/theme.js';
import { subscribe } from '../core/ticker.js';
const FONT = NEXUS.theme.fontMono;
// Defaults shown before data loads
let _score = 85;
let _label = 'Mostly Sovereign';
let _assessmentType = 'MANUAL';
let _group, _arcMesh, _arcMat, _light, _spriteMat, _scene;
let _lastSovereignty = null;
function _scoreColor(score) {
if (score >= 80) return NEXUS.theme.sovereignHighHex;
if (score >= 40) return NEXUS.theme.sovereignMidHex;
return NEXUS.theme.sovereignLowHex;
}
function _scoreColorStr(score) {
if (score >= 80) return NEXUS.theme.sovereignHigh;
if (score >= 40) return NEXUS.theme.sovereignMid;
return NEXUS.theme.sovereignLow;
}
function _buildArcGeo(score) {
return new THREE.TorusGeometry(1.6, 0.1, 8, 64, (score / 100) * Math.PI * 2);
}
function _buildMeterTexture(score, label, assessmentType) {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 128;
const ctx = canvas.getContext('2d');
const col = _scoreColorStr(score);
ctx.clearRect(0, 0, 256, 128);
ctx.font = `bold 52px ${FONT}`;
ctx.fillStyle = col;
ctx.textAlign = 'center';
ctx.fillText(`${score}%`, 128, 50);
ctx.font = `16px ${FONT}`;
ctx.fillStyle = '#8899bb';
ctx.fillText(label.toUpperCase(), 128, 74);
ctx.font = `11px ${FONT}`;
ctx.fillStyle = '#445566';
ctx.fillText('SOVEREIGNTY', 128, 94);
ctx.font = `9px ${FONT}`;
ctx.fillStyle = '#334455';
ctx.fillText('MANUAL ASSESSMENT', 128, 112);
return new THREE.CanvasTexture(canvas);
}
function _applyScore(score, label, assessmentType) {
_score = score;
_label = label;
_assessmentType = assessmentType;
_arcMesh.geometry.dispose();
_arcMesh.geometry = _buildArcGeo(score);
const col = _scoreColor(score);
_arcMat.color.setHex(col);
_light.color.setHex(col);
if (_spriteMat.map) _spriteMat.map.dispose();
_spriteMat.map = _buildMeterTexture(score, label, assessmentType);
_spriteMat.needsUpdate = true;
}
/** @param {THREE.Scene} scene */
export function init(scene) {
_scene = scene;
_group = new THREE.Group();
_group.position.set(0, 3.8, 0);
// Background ring
const bgMat = new THREE.MeshBasicMaterial({ color: 0x0a1828, transparent: true, opacity: 0.5 });
_group.add(new THREE.Mesh(new THREE.TorusGeometry(1.6, 0.1, 8, 64), bgMat));
// Score arc
_arcMat = new THREE.MeshBasicMaterial({
color: _scoreColor(_score),
transparent: true,
opacity: 0.9,
});
_arcMesh = new THREE.Mesh(_buildArcGeo(_score), _arcMat);
_arcMesh.rotation.z = Math.PI / 2; // arc starts at 12 o'clock
_group.add(_arcMesh);
// Glow light
_light = new THREE.PointLight(_scoreColor(_score), 0.7, 6);
_group.add(_light);
// Sprite label
_spriteMat = new THREE.SpriteMaterial({
map: _buildMeterTexture(_score, _label, _assessmentType),
transparent: true,
depthWrite: false,
});
const sprite = new THREE.Sprite(_spriteMat);
sprite.scale.set(3.2, 1.6, 1);
_group.add(sprite);
scene.add(_group);
_group.traverse(obj => {
if (obj.isMesh || obj.isSprite) obj.userData.zoomLabel = 'Sovereignty Meter';
});
subscribe(update);
}
/**
* @param {number} _elapsed
* @param {number} _delta
*/
export function update(_elapsed, _delta) {
if (state.sovereignty && state.sovereignty !== _lastSovereignty) {
const { score, label, assessment_type } = state.sovereignty;
const s = Math.max(0, Math.min(100, typeof score === 'number' ? score : _score));
const l = typeof label === 'string' ? label : _label;
const t = typeof assessment_type === 'string' ? assessment_type : 'MANUAL';
_applyScore(s, l, t);
_lastSovereignty = state.sovereignty;
}
}
export function dispose() {
if (_group) _scene.remove(_group);
if (_spriteMat.map) _spriteMat.map.dispose();
}