[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 harnessPulseMesh;
|
||||||
let powerMeterBars = [];
|
let powerMeterBars = [];
|
||||||
let particles, dustParticles;
|
let particles, dustParticles;
|
||||||
|
let dualBrainGroup, dualBrainScanCtx, dualBrainScanTexture;
|
||||||
|
let cloudOrb, localOrb, cloudOrbLight, localOrbLight, dualBrainLight;
|
||||||
let debugOverlay;
|
let debugOverlay;
|
||||||
let glassEdgeMaterials = []; // Glass tile edge materials for animation
|
let glassEdgeMaterials = []; // Glass tile edge materials for animation
|
||||||
let voidLight = null; // Point light below glass floor
|
let voidLight = null; // Point light below glass floor
|
||||||
@@ -142,6 +144,7 @@ async function init() {
|
|||||||
createThoughtStream();
|
createThoughtStream();
|
||||||
createHarnessPulse();
|
createHarnessPulse();
|
||||||
createSessionPowerMeter();
|
createSessionPowerMeter();
|
||||||
|
createDualBrainPanel();
|
||||||
updateLoad(90);
|
updateLoad(90);
|
||||||
|
|
||||||
composer = new EffectComposer(renderer);
|
composer = new EffectComposer(renderer);
|
||||||
@@ -930,6 +933,183 @@ function createSessionPowerMeter() {
|
|||||||
scene.add(group);
|
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 ═══
|
// ═══ VISION SYSTEM ═══
|
||||||
function createVisionPoints(data) {
|
function createVisionPoints(data) {
|
||||||
data.forEach(config => {
|
data.forEach(config => {
|
||||||
@@ -1719,6 +1899,34 @@ function gameLoop() {
|
|||||||
// Animate Agents
|
// Animate Agents
|
||||||
updateAgents(elapsed, delta);
|
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
|
// Animate Power Meter
|
||||||
powerMeterBars.forEach((bar, i) => {
|
powerMeterBars.forEach((bar, i) => {
|
||||||
const level = (Math.sin(elapsed * 2 + i * 0.5) * 0.5 + 0.5);
|
const level = (Math.sin(elapsed * 2 + i * 0.5) * 0.5 + 0.5);
|
||||||
|
|||||||
Reference in New Issue
Block a user