Nexus Evolution: Sovereignty Dashboard & Portal Tunnel Effect
Some checks failed
CI / validate (pull_request) Failing after 4s

- Added "SOVEREIGNTY" terminal panel visualizing agent competency ratings
- Implemented "Portal Tunnel" visual effect for immersive transitions
- Updated Batcave terminals with dynamic Gitea data fetching
- Integrated Ash Storm effect for Morrowind portal proximity
This commit is contained in:
Google Agent
2026-03-24 14:21:08 +00:00
parent 6f26f07bf4
commit 9410132609

189
app.js
View File

@@ -137,10 +137,13 @@ async function init() {
createHarnessPulse();
createSessionPowerMeter();
createWorkshopTerminal();
createAshStorm();
updateLoad(90);
loadSession();
connectHermes();
fetchGiteaData();
setInterval(fetchGiteaData, 30000); // Refresh every 30s
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
@@ -357,12 +360,13 @@ function createBatcaveTerminal() {
{ title: 'NEXUS COMMAND', color: NEXUS.colors.primary, rot: -0.4, x: -6, y: 3, lines: ['> STATUS: NOMINAL', '> UPTIME: 142.4h', '> HARNESS: STABLE', '> MODE: SOVEREIGN'] },
{ title: 'DEV QUEUE', color: NEXUS.colors.gold, rot: -0.2, x: -3, y: 3, lines: ['> ISSUE #4: CORE', '> ISSUE #5: PORTAL', '> ISSUE #6: TERMINAL', '> ISSUE #7: TIMMY'] },
{ title: 'METRICS', color: NEXUS.colors.secondary, rot: 0, x: 0, y: 3, lines: ['> CPU: 12% [||....]', '> MEM: 4.2GB', '> COMMITS: 842', '> ACTIVE LOOPS: 5'] },
{ title: 'THOUGHTS', color: NEXUS.colors.primary, rot: 0.2, x: 3, y: 3, lines: ['> ANALYZING WORLD...', '> SYNCING MEMORY...', '> WAITING FOR INPUT', '> SOUL ON BITCOIN'] },
{ title: 'AGENT STATUS', color: NEXUS.colors.gold, rot: 0.4, x: 6, y: 3, lines: ['> TIMMY: ● RUNNING', '> KIMI: ○ STANDBY', '> CLAUDE: ● ACTIVE', '> PERPLEXITY: ○'] },
{ title: 'SOVEREIGNTY', color: NEXUS.colors.gold, rot: 0.2, x: 3, y: 3, lines: ['REPLIT: GRADE: A', 'PERPLEXITY: GRADE: A-', 'HERMES: GRADE: B+', 'KIMI: GRADE: B', 'CLAUDE: GRADE: B+'] },
{ title: 'AGENT STATUS', color: NEXUS.colors.primary, rot: 0.4, x: 6, y: 3, lines: ['> TIMMY: ● RUNNING', '> KIMI: ○ STANDBY', '> CLAUDE: ● ACTIVE', '> PERPLEXITY: ○'] },
];
panelData.forEach(data => {
createTerminalPanel(terminalGroup, data.x, data.y, data.rot, data.title, data.color, data.lines);
const terminal = createTerminalPanel(terminalGroup, data.x, data.y, data.rot, data.title, data.color, data.lines);
batcaveTerminals.push(terminal);
});
scene.add(terminalGroup);
@@ -480,23 +484,32 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) {
textCanvas.width = 512;
textCanvas.height = 640;
const ctx = textCanvas.getContext('2d');
ctx.fillStyle = '#' + new THREE.Color(color).getHexString();
ctx.font = 'bold 32px "Orbitron", sans-serif';
ctx.fillText(title, 20, 45);
ctx.fillRect(20, 55, 472, 2);
ctx.font = '20px "JetBrains Mono", monospace';
ctx.fillStyle = '#a0b8d0';
lines.forEach((line, i) => {
let fillColor = '#a0b8d0';
if (line.includes('● RUNNING') || line.includes('● ACTIVE')) fillColor = '#4af0c0';
else if (line.includes('○ STANDBY')) fillColor = '#5a6a8a';
else if (line.includes('NOMINAL')) fillColor = '#4af0c0';
ctx.fillStyle = fillColor;
ctx.fillText(line, 20, 100 + i * 40);
});
const textTexture = new THREE.CanvasTexture(textCanvas);
textTexture.minFilter = THREE.LinearFilter;
function updatePanelText(newLines) {
ctx.clearRect(0, 0, 512, 640);
ctx.fillStyle = '#' + new THREE.Color(color).getHexString();
ctx.font = 'bold 32px "Orbitron", sans-serif';
ctx.fillText(title, 20, 45);
ctx.fillRect(20, 55, 472, 2);
ctx.font = '20px "JetBrains Mono", monospace';
ctx.fillStyle = '#a0b8d0';
const displayLines = newLines || lines;
displayLines.forEach((line, i) => {
let fillColor = '#a0b8d0';
if (line.includes('● RUNNING') || line.includes('● ACTIVE') || line.includes('ONLINE')) fillColor = '#4af0c0';
else if (line.includes('○ STANDBY') || line.includes('OFFLINE')) fillColor = '#5a6a8a';
else if (line.includes('NOMINAL')) fillColor = '#4af0c0';
ctx.fillStyle = fillColor;
ctx.fillText(line, 20, 100 + i * 40);
});
textTexture.needsUpdate = true;
}
updatePanelText();
const textMat = new THREE.MeshBasicMaterial({
map: textTexture,
transparent: true,
@@ -536,7 +549,70 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) {
group.add(scanMesh);
parent.add(group);
batcaveTerminals.push({ group, scanMat, borderMat });
return { group, scanMat, borderMat, updatePanelText, title };
}
// ═══ GITEA DATA INTEGRATION ═══
async function fetchGiteaData() {
try {
const [issuesRes, stateRes] = await Promise.all([
fetch('/api/gitea/repos/admin/timmy-tower/issues?state=all'),
fetch('/api/gitea/repos/admin/timmy-tower/contents/world_state.json')
]);
if (issuesRes.ok) {
const issues = await issuesRes.json();
updateDevQueue(issues);
updateAgentStatus(issues);
}
if (stateRes.ok) {
const content = await stateRes.json();
const worldState = JSON.parse(atob(content.content));
updateNexusCommand(worldState);
}
} catch (e) {
console.error('Failed to fetch Gitea data:', e);
}
}
function updateAgentStatus(issues) {
const terminal = batcaveTerminals.find(t => t.title === 'AGENT STATUS');
if (!terminal) return;
// Check for Morrowind issues
const morrowindIssues = issues.filter(i => i.title.toLowerCase().includes('morrowind') && i.state === 'open');
const perplexityStatus = morrowindIssues.length > 0 ? '● MORROWIND' : '○ STANDBY';
const lines = [
'> TIMMY: ● RUNNING',
'> KIMI: ○ STANDBY',
'> CLAUDE: ● ACTIVE',
`> PERPLEXITY: ${perplexityStatus}`
];
terminal.updatePanelText(lines);
}
function updateDevQueue(issues) {
const terminal = batcaveTerminals.find(t => t.title === 'DEV QUEUE');
if (!terminal) return;
const lines = issues.slice(0, 4).map(issue => `> #${issue.number}: ${issue.title.substring(0, 15)}...`);
while (lines.length < 4) lines.push('> [EMPTY SLOT]');
terminal.updatePanelText(lines);
}
function updateNexusCommand(state) {
const terminal = batcaveTerminals.find(t => t.title === 'NEXUS COMMAND');
if (!terminal) return;
const lines = [
`> STATUS: ${state.tower.status.toUpperCase()}`,
`> ENERGY: ${state.tower.energy}%`,
`> STABILITY: ${(state.matrix.stability * 100).toFixed(1)}%`,
`> AGENTS: ${state.matrix.active_agents.length}`
];
terminal.updatePanelText(lines);
}
// ═══ AGENT PRESENCE SYSTEM ═══
@@ -1470,6 +1546,8 @@ function gameLoop() {
harnessPulseMesh.material.opacity = Math.max(0, harnessPulseMesh.material.opacity - delta * 0.5);
}
updateAshStorm(delta, elapsed);
const mode = NAV_MODES[navModeIdx];
const chatActive = document.activeElement === document.getElementById('chat-input');
@@ -1635,6 +1713,9 @@ function gameLoop() {
composer.render();
updateAshStorm(delta, elapsed);
updatePortalTunnel(delta, elapsed);
if (workshopScanMat) workshopScanMat.uniforms.uTime.value = clock.getElapsedTime();
if (activePortal !== lastFocusedPortal) {
lastFocusedPortal = activePortal;
@@ -1732,4 +1813,72 @@ function triggerHarnessPulse() {
}
}
init();
// ═══ ASH STORM (MORROWIND) ═══
let ashStormParticles;
function createAshStorm() {
const count = 1000;
const geo = new THREE.BufferGeometry();
const pos = new Float32Array(count * 3);
const vel = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
pos[i * 3] = (Math.random() - 0.5) * 20;
pos[i * 3 + 1] = Math.random() * 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 20;
vel[i * 3] = -0.05 - Math.random() * 0.1;
vel[i * 3 + 1] = -0.02 - Math.random() * 0.05;
vel[i * 3 + 2] = (Math.random() - 0.5) * 0.05;
}
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
geo.setAttribute('velocity', new THREE.BufferAttribute(vel, 3));
const mat = new THREE.PointsMaterial({
color: 0x886644,
size: 0.05,
transparent: true,
opacity: 0,
depthWrite: false,
blending: THREE.AdditiveBlending
});
ashStormParticles = new THREE.Points(geo, mat);
ashStormParticles.position.set(15, 0, -10); // Center on Morrowind portal
scene.add(ashStormParticles);
}
function updateAshStorm(delta, elapsed) {
if (!ashStormParticles) return;
const morrowindPortalPos = new THREE.Vector3(15, 0, -10);
const dist = playerPos.distanceTo(morrowindPortalPos);
const intensity = Math.max(0, 1 - (dist / 12));
ashStormParticles.material.opacity = intensity * 0.4;
if (intensity > 0) {
const pos = ashStormParticles.geometry.attributes.position.array;
const vel = ashStormParticles.geometry.attributes.velocity.array;
for (let i = 0; i < pos.length / 3; i++) {
pos[i * 3] += vel[i * 3];
pos[i * 3 + 1] += vel[i * 3 + 1];
pos[i * 3 + 2] += vel[i * 3 + 2];
if (pos[i * 3 + 1] < 0 || Math.abs(pos[i * 3]) > 10 || Math.abs(pos[i * 3 + 2]) > 10) {
pos[i * 3] = (Math.random() - 0.5) * 20;
pos[i * 3 + 1] = 10;
pos[i * 3 + 2] = (Math.random() - 0.5) * 20;
}
}
ashStormParticles.geometry.attributes.position.needsUpdate = true;
}
}
init().then(() => {
createAshStorm();
createPortalTunnel();
fetchGiteaData();
setInterval(fetchGiteaData, 30000);
});