Nexus Evolution: Sovereignty Dashboard & Portal Tunnel Effect
Some checks failed
CI / validate (pull_request) Failing after 4s
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:
189
app.js
189
app.js
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user