[claude] Re-implement dual-brain panel (#481) (#499)
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s

This commit was merged in pull request #499.
This commit is contained in:
2026-03-25 03:08:02 +00:00
parent 475df10944
commit d09b31825b

208
app.js
View File

@@ -39,6 +39,8 @@ let thoughtStreamMesh;
let harnessPulseMesh;
let powerMeterBars = [];
let particles, dustParticles;
let dualBrainGroup, dualBrainScanCtx, dualBrainScanTexture;
let cloudOrb, localOrb, cloudOrbLight, localOrbLight, dualBrainLight;
let debugOverlay;
let glassEdgeMaterials = []; // Glass tile edge materials for animation
let voidLight = null; // Point light below glass floor
@@ -142,6 +144,7 @@ async function init() {
createThoughtStream();
createHarnessPulse();
createSessionPowerMeter();
createDualBrainPanel();
updateLoad(90);
composer = new EffectComposer(renderer);
@@ -930,6 +933,183 @@ function createSessionPowerMeter() {
scene.add(group);
}
// ═══ DUAL-BRAIN PANEL ═══
function createDualBrainTexture() {
const W = 512, H = 512;
const canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0, 6, 20, 0.90)';
ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = '#4488ff';
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, W - 2, H - 2);
ctx.strokeStyle = '#223366';
ctx.lineWidth = 1;
ctx.strokeRect(5, 5, W - 10, H - 10);
ctx.font = 'bold 22px "Courier New", monospace';
ctx.fillStyle = '#88ccff';
ctx.textAlign = 'center';
ctx.fillText('\u25C8 DUAL-BRAIN STATUS', W / 2, 40);
ctx.strokeStyle = '#1a3a6a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(20, 52);
ctx.lineTo(W - 20, 52);
ctx.stroke();
ctx.font = '11px "Courier New", monospace';
ctx.fillStyle = '#556688';
ctx.textAlign = 'left';
ctx.fillText('BRAIN GAP SCORECARD', 20, 74);
const categories = [
{ name: 'Triage' },
{ name: 'Tool Use' },
{ name: 'Code Gen' },
{ name: 'Planning' },
{ name: 'Communication' },
{ name: 'Reasoning' },
];
const barX = 20;
const barW = W - 130;
const barH = 20;
let y = 90;
for (const cat of categories) {
ctx.font = '13px "Courier New", monospace';
ctx.fillStyle = '#445566';
ctx.textAlign = 'left';
ctx.fillText(cat.name, barX, y + 14);
ctx.font = 'bold 13px "Courier New", monospace';
ctx.fillStyle = '#334466';
ctx.textAlign = 'right';
ctx.fillText('\u2014', W - 20, y + 14);
y += 22;
ctx.fillStyle = 'rgba(255, 255, 255, 0.06)';
ctx.fillRect(barX, y, barW, barH);
y += barH + 12;
}
ctx.strokeStyle = '#1a3a6a';
ctx.beginPath();
ctx.moveTo(20, y + 4);
ctx.lineTo(W - 20, y + 4);
ctx.stroke();
y += 22;
ctx.font = 'bold 18px "Courier New", monospace';
ctx.fillStyle = '#334466';
ctx.textAlign = 'center';
ctx.fillText('AWAITING DEPLOYMENT', W / 2, y + 10);
ctx.font = '11px "Courier New", monospace';
ctx.fillStyle = '#223344';
ctx.fillText('Dual-brain system not yet connected', W / 2, y + 32);
y += 52;
ctx.beginPath();
ctx.arc(W / 2 - 60, y + 8, 6, 0, Math.PI * 2);
ctx.fillStyle = '#334466';
ctx.fill();
ctx.font = '11px "Courier New", monospace';
ctx.fillStyle = '#334466';
ctx.textAlign = 'left';
ctx.fillText('CLOUD', W / 2 - 48, y + 12);
ctx.beginPath();
ctx.arc(W / 2 + 30, y + 8, 6, 0, Math.PI * 2);
ctx.fillStyle = '#334466';
ctx.fill();
ctx.fillStyle = '#334466';
ctx.fillText('LOCAL', W / 2 + 42, y + 12);
return new THREE.CanvasTexture(canvas);
}
function createDualBrainPanel() {
dualBrainGroup = new THREE.Group();
dualBrainGroup.position.set(10, 3, -8);
dualBrainGroup.lookAt(0, 3, 0);
scene.add(dualBrainGroup);
// Main panel sprite
const panelTexture = createDualBrainTexture();
const panelMat = new THREE.SpriteMaterial({
map: panelTexture, transparent: true, opacity: 0.92, depthWrite: false,
});
const panelSprite = new THREE.Sprite(panelMat);
panelSprite.scale.set(5.0, 5.0, 1);
panelSprite.position.set(0, 0, 0);
panelSprite.userData = { baseY: 0, floatPhase: 0, floatSpeed: 0.22, zoomLabel: 'Dual-Brain Status' };
dualBrainGroup.add(panelSprite);
// Panel glow light
dualBrainLight = new THREE.PointLight(0x4488ff, 0.6, 10);
dualBrainLight.position.set(0, 0.5, 1);
dualBrainGroup.add(dualBrainLight);
// Cloud Brain Orb
const cloudOrbGeo = new THREE.SphereGeometry(0.35, 32, 32);
const cloudOrbMat = new THREE.MeshStandardMaterial({
color: 0x334466,
emissive: new THREE.Color(0x334466),
emissiveIntensity: 0.1, metalness: 0.3, roughness: 0.2,
transparent: true, opacity: 0.85,
});
cloudOrb = new THREE.Mesh(cloudOrbGeo, cloudOrbMat);
cloudOrb.position.set(-2.0, 3.0, 0);
cloudOrb.userData.zoomLabel = 'Cloud Brain';
dualBrainGroup.add(cloudOrb);
cloudOrbLight = new THREE.PointLight(0x334466, 0.15, 5);
cloudOrbLight.position.copy(cloudOrb.position);
dualBrainGroup.add(cloudOrbLight);
// Local Brain Orb
const localOrbGeo = new THREE.SphereGeometry(0.35, 32, 32);
const localOrbMat = new THREE.MeshStandardMaterial({
color: 0x334466,
emissive: new THREE.Color(0x334466),
emissiveIntensity: 0.1, metalness: 0.3, roughness: 0.2,
transparent: true, opacity: 0.85,
});
localOrb = new THREE.Mesh(localOrbGeo, localOrbMat);
localOrb.position.set(2.0, 3.0, 0);
localOrb.userData.zoomLabel = 'Local Brain';
dualBrainGroup.add(localOrb);
localOrbLight = new THREE.PointLight(0x334466, 0.15, 5);
localOrbLight.position.copy(localOrb.position);
dualBrainGroup.add(localOrbLight);
// Scan line overlay
const scanCanvas = document.createElement('canvas');
scanCanvas.width = 512;
scanCanvas.height = 512;
dualBrainScanCtx = scanCanvas.getContext('2d');
dualBrainScanTexture = new THREE.CanvasTexture(scanCanvas);
const scanMat = new THREE.SpriteMaterial({
map: dualBrainScanTexture, transparent: true, opacity: 0.18, depthWrite: false,
});
const scanSprite = new THREE.Sprite(scanMat);
scanSprite.scale.set(5.0, 5.0, 1);
scanSprite.position.set(0, 0, 0.01);
dualBrainGroup.add(scanSprite);
}
// ═══ VISION SYSTEM ═══
function createVisionPoints(data) {
data.forEach(config => {
@@ -1719,6 +1899,34 @@ function gameLoop() {
// Animate Agents
updateAgents(elapsed, delta);
// Animate Dual-Brain Panel
if (dualBrainGroup) {
dualBrainGroup.position.y = 3 + Math.sin(elapsed * 0.22) * 0.15;
if (cloudOrb) {
cloudOrb.position.y = 3 + Math.sin(elapsed * 1.3) * 0.15;
cloudOrb.rotation.y = elapsed * 0.4;
}
if (localOrb) {
localOrb.position.y = 3 + Math.sin(elapsed * 1.3 + Math.PI) * 0.15;
localOrb.rotation.y = -elapsed * 0.4;
}
if (dualBrainLight) {
dualBrainLight.intensity = 0.4 + Math.sin(elapsed * 1.5) * 0.2;
}
if (dualBrainScanCtx && dualBrainScanTexture) {
const W = 512, H = 512;
dualBrainScanCtx.clearRect(0, 0, W, H);
const scanY = ((elapsed * 80) % H);
const grad = dualBrainScanCtx.createLinearGradient(0, scanY - 20, 0, scanY + 20);
grad.addColorStop(0, 'rgba(68, 136, 255, 0)');
grad.addColorStop(0.5, 'rgba(68, 136, 255, 0.6)');
grad.addColorStop(1, 'rgba(68, 136, 255, 0)');
dualBrainScanCtx.fillStyle = grad;
dualBrainScanCtx.fillRect(0, scanY - 20, W, 40);
dualBrainScanTexture.needsUpdate = true;
}
}
// Animate Power Meter
powerMeterBars.forEach((bar, i) => {
const level = (Math.sin(elapsed * 2 + i * 0.5) * 0.5 + 0.5);