[claude] Re-implement dual-brain panel (#481) (#499)
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
This commit was merged in pull request #499.
This commit is contained in:
208
app.js
208
app.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user