@@ -60,6 +60,9 @@ const MATRIX_FONT_SIZE = 14;
const MATRIX _COL _COUNT = Math . floor ( window . innerWidth / MATRIX _FONT _SIZE ) ;
const matrixDrops = new Array ( MATRIX _COL _COUNT ) . fill ( 1 ) ;
// Commit hashes for matrix rain — populated by heatmap fetch, used to inject real data into the rain
let _matrixCommitHashes = [ ] ;
function drawMatrixRain ( ) {
// Fade previous frame with semi-transparent black overlay (creates the trail)
matrixCtx . fillStyle = 'rgba(0, 0, 8, 0.05)' ;
@@ -67,8 +70,27 @@ function drawMatrixRain() {
matrixCtx . font = ` ${ MATRIX _FONT _SIZE } px monospace ` ;
// Tether rain density to commit activity — density range [0.1, 1.0]
const activity = typeof totalActivity === 'function' ? totalActivity ( ) : 0 ;
const density = 0.1 + activity * 0.9 ; // minimum 10% density
const activeColCount = Math . max ( 1 , Math . floor ( matrixDrops . length * density ) ) ;
for ( let i = 0 ; i < matrixDrops . length ; i ++ ) {
const char = MATRIX _CHARS [ Math . floor ( Math . random ( ) * MATRIX _CHARS . length ) ] ;
// Only render columns up to density-scaled count (skip inactive ones)
if ( i >= activeColCount ) {
// Inactive columns still fade but don't spawn new characters
if ( matrixDrops [ i ] * MATRIX _FONT _SIZE > matrixCanvas . height ) continue ;
}
// Occasionally inject a real commit hash (first 7 chars) instead of katakana
let char ;
if ( _matrixCommitHashes . length > 0 && Math . random ( ) < 0.02 ) {
const hash = _matrixCommitHashes [ Math . floor ( Math . random ( ) * _matrixCommitHashes . length ) ] ;
char = hash [ Math . floor ( Math . random ( ) * hash . length ) ] ;
} else {
char = MATRIX _CHARS [ Math . floor ( Math . random ( ) * MATRIX _CHARS . length ) ] ;
}
const x = i * MATRIX _FONT _SIZE ;
const y = matrixDrops [ i ] * MATRIX _FONT _SIZE ;
@@ -76,8 +98,9 @@ function drawMatrixRain() {
matrixCtx . fillStyle = '#aaffaa' ;
matrixCtx . fillText ( char , x , y ) ;
// Reset drop to top with some randomness
if ( y > matrixCanvas . height && Math . random ( ) > 0.97 5 ) {
// Reset drop to top — speed influenced by activity
const resetThreshold = 0.975 - activity * 0.01 5 ; // more activity = faster reset = denser rain
if ( y > matrixCanvas . height && Math . random ( ) > resetThreshold ) {
matrixDrops [ i ] = 0 ;
}
matrixDrops [ i ] ++ ;
@@ -166,6 +189,12 @@ const starMaterial = new THREE.PointsMaterial({
const stars = new THREE . Points ( starGeo , starMaterial ) ;
scene . add ( stars ) ;
// Star pulse state — tethered to Bitcoin block events
let _starPulseIntensity = 0 ; // 0 = normal, 1 = peak brightness
const STAR _BASE _OPACITY = 0.3 ;
const STAR _PEAK _OPACITY = 1.0 ;
const STAR _PULSE _DECAY = 0.012 ; // decay per frame (~3 seconds to fade)
// === CONSTELLATION LINES ===
// Connect nearby stars with faint lines, limited to avoid clutter
/**
@@ -798,6 +827,9 @@ async function updateHeatmap() {
if ( res . ok ) commits = await res . json ( ) ;
} catch { /* silently use zero-activity baseline */ }
// Feed commit hashes to matrix rain for data-tethered aesthetic
_matrixCommitHashes = commits . slice ( 0 , 20 ) . map ( c => ( c . sha || '' ) . slice ( 0 , 7 ) ) . filter ( h => h . length > 0 ) ;
const now = Date . now ( ) ;
const rawWeights = Object . fromEntries ( HEATMAP _ZONES . map ( z => [ z . name , 0 ] ) ) ;
@@ -1222,8 +1254,10 @@ let energyBeamPulse = 0;
function animateEnergyBeam ( ) {
energyBeamPulse += 0.02 ;
const pulseEffect = Math . sin ( energyBeamPulse ) * 0.3 + 0.7 ;
energyBeamMaterial . opacity = 0.3 + pulseEffect * 0.4 ;
// Tether beam intensity to active agent count: 0=faint, 1=0.4, 2=0.7, 3+=1.0
const agentIntensity = _activeAgentCount === 0 ? 0.1 : Math . min ( 0.1 + _activeAgentCount * 0.3 , 1.0 ) ;
const pulseEffect = Math . sin ( energyBeamPulse ) * 0.15 * agentIntensity ;
energyBeamMaterial . opacity = agentIntensity * 0.6 + pulseEffect ;
}
// === RESIZE HANDLER ===
@@ -1272,7 +1306,7 @@ sovereigntyGroup.add(scoreArcMesh);
const meterLight = new THREE . PointLight ( sovereigntyHexColor ( sovereigntyScore ) , 0.7 , 6 ) ;
sovereigntyGroup . add ( meterLight ) ;
function buildMeterTexture ( score , label ) {
function buildMeterTexture ( score , label , assessmentType ) {
const canvas = document . createElement ( 'canvas' ) ;
canvas . width = 256 ;
canvas . height = 128 ;
@@ -1282,18 +1316,22 @@ function buildMeterTexture(score, label) {
ctx . font = 'bold 52px "Courier New", monospace' ;
ctx . fillStyle = hexStr ;
ctx . textAlign = 'center' ;
ctx . fillText ( ` ${ score } % ` , 128 , 58 ) ;
ctx . fillText ( ` ${ score } % ` , 128 , 50 ) ;
ctx . font = '16px "Courier New", monospace' ;
ctx . fillStyle = '#8899bb' ;
ctx . fillText ( label . toUpperCase ( ) , 128 , 82 ) ;
ctx . fillText ( label . toUpperCase ( ) , 128 , 74 ) ;
ctx . font = '11px "Courier New", monospace' ;
ctx . fillStyle = '#445566' ;
ctx . fillText ( 'SOVEREIGNTY' , 128 , 10 4) ;
ctx . fillText ( 'SOVEREIGNTY' , 128 , 9 4) ;
// "MANUAL ASSESSMENT" label — honest about data source
ctx . font = '9px "Courier New", monospace' ;
ctx . fillStyle = '#334455' ;
ctx . fillText ( assessmentType === 'MANUAL' ? 'MANUAL ASSESSMENT' : 'MANUAL ASSESSMENT' , 128 , 112 ) ;
return new THREE . CanvasTexture ( canvas ) ;
}
const meterSpriteMat = new THREE . SpriteMaterial ( {
map : buildMeterTexture ( sovereigntyScore , sovereigntyLabel ) ,
map : buildMeterTexture ( sovereigntyScore , sovereigntyLabel , 'MANUAL' ),
transparent : true ,
depthWrite : false ,
} ) ;
@@ -1321,7 +1359,8 @@ async function loadSovereigntyStatus() {
scoreArcMat . color . setHex ( col ) ;
meterLight . color . setHex ( col ) ;
if ( meterSpriteMat . map ) meterSpriteMat . map . dispose ( ) ;
meterSpriteMat . map = buildMeterTexture ( score , label ) ;
const assessmentType = data . assessment _type || 'MANUAL' ;
meterSpriteMat . map = buildMeterTexture ( score , label , assessmentType ) ;
meterSpriteMat . needsUpdate = true ;
} catch {
// defaults already set above
@@ -1331,7 +1370,8 @@ async function loadSovereigntyStatus() {
loadSovereigntyStatus ( ) ;
// === ENERGY BEAM FOR BATCAVE TERMINAL ===
// Vertical energy beam from Batcave terminal area to the sky with animated opacity and pulse effec t.
// Vertical energy beam from Batcave terminal area — intensity tethered to active agent coun t.
let _activeAgentCount = 0 ; // updated by agent status fetch
const ENERGY _BEAM _RADIUS = 0.2 ;
const ENERGY _BEAM _HEIGHT = 50 ;
const ENERGY _BEAM _Y = 0 ;
@@ -1355,15 +1395,15 @@ scene.add(energyBeam);
// === RUNE RING ===
// 12 Elder Futhark rune sprites in a slow-orbiting ring around the center platform .
// Rune sprites tethered to portal data — count matches portals, colors from portals.json .
cons t RUNE _COUNT = 12 ;
le t RUNE _COUNT = 12 ; // default, updated when portals load
const RUNE _RING _RADIUS = 7.0 ;
const RUNE _RING _Y = 1.5 ; // base height above platform
const RUNE _ORBIT _SPEED = 0.08 ; // radians per second
const ELDER _FUTHARK = [ 'ᚠ' , 'ᚢ' , 'ᚦ' , 'ᚨ' , 'ᚱ' , 'ᚲ ' , 'ᚷ ' , 'ᚹ' , 'ᚺ' , 'ᚾ' , 'ᛁ ' , 'ᛃ' ] ;
const RUNE _GLOW _COLORS = [ '#00ffcc' , '#ff44ff' ] ; // alternating cyan / magenta
const RUNE _GLOW _COLORS = [ '#00ffcc' , '#ff44ff' ] ; // f allback, overridden by portal colors
/**
* Creates a canvas texture for a single glowing rune glyph.
@@ -1406,35 +1446,56 @@ runeOrbitRingMesh.position.y = RUNE_RING_Y;
scene . add ( runeOrbitRingMesh ) ;
/**
* @type {Array<{sprite: THREE.Sprite, baseAngle: number, floatPhase: number}>}
* @type {Array<{sprite: THREE.Sprite, baseAngle: number, floatPhase: number, portalOnline: boolean }>}
*/
const runeSprites = [ ] ;
for ( let i = 0 ; i < RUNE _COUNT ; i ++ ) {
const glyph = ELDER _FUTHARK [ i % ELDER _FUTHARK . length ] ;
const color = RUNE _GLOW _COLORS [ i % RUNE _GLOW _COLORS . length ] ;
const texture = createRuneTexture ( glyph , color ) ;
/**
* Rebuilds rune ring from portal data — count matches portals, colors from portals.json.
* Falls back to default 12 runes if portals not yet loaded.
*/
function rebuildRuneRing ( ) {
// Remove existing rune sprites
for ( const rune of runeSprites ) {
scene . remove ( rune . sprite ) ;
if ( rune . sprite . material . map ) rune . sprite . material . map . dispose ( ) ;
rune . sprite . material . dispose ( ) ;
}
runeSprites . length = 0 ;
const runeM at = new THREE . SpriteMaterial ( {
map : texture ,
transparent : true ,
opacity : 0.85 ,
depthWrite : false ,
blending : THREE . AdditiveBlending ,
} ) ;
const sprite = new THREE . Sprite ( runeMat ) ;
sprite . scale . set ( 1.3 , 1.3 , 1 ) ;
const portalD ata = portals . length > 0 ? portals : null ;
const count = portalData ? portalData . length : RUNE _COUNT ;
const baseAngle = ( i / RUNE _COUNT ) * Math . PI * 2 ;
sprite . position . set (
Math . cos ( baseAngle ) * RUNE _RING _RADIUS ,
RUNE _RING _Y ,
Math . sin ( baseAngle ) * RUNE _RING _RADIUS
) ;
scene . add ( s prite) ;
runeSprites . push ( { sprite , baseAngle , floatPhase : ( i / RUNE _COUNT ) * Math . PI * 2 } ) ;
for ( let i = 0 ; i < count ; i ++ ) {
const glyph = ELDER _FUTHARK [ i % ELDER _FUTHARK . length ] ;
const color = portalData ? portalData [ i ] . color : RUNE _GLOW _COLORS [ i % RUNE _GLOW _COLORS . length ] ;
const isOnline = portalData ? portalData [ i ] . status === 'online' : true ;
const texture = createRuneTexture ( glyph , color ) ;
const runeMat = new THREE . S priteMaterial ( {
map : texture ,
transparent : true ,
opacity : isOnline ? 1.0 : 0.15 , // bright if online, dim if offline
depthWrite : false ,
blending : THREE . AdditiveBlending ,
} ) ;
const sprite = new THREE . Sprite ( runeMat ) ;
sprite . scale . set ( 1.3 , 1.3 , 1 ) ;
const baseAngle = ( i / count ) * Math . PI * 2 ;
sprite . position . set (
Math . cos ( baseAngle ) * RUNE _RING _RADIUS ,
RUNE _RING _Y ,
Math . sin ( baseAngle ) * RUNE _RING _RADIUS
) ;
scene . add ( sprite ) ;
runeSprites . push ( { sprite , baseAngle , floatPhase : ( i / count ) * Math . PI * 2 , portalOnline : isOnline } ) ;
}
}
// Initial build with default count (will be rebuilt when portals load)
rebuildRuneRing ( ) ;
// === HOLOGRAPHIC EARTH ===
// A procedural holographic planet Earth slowly rotating above the Nexus.
@@ -2116,14 +2177,14 @@ function createDualBrainTexture() {
ctx . textAlign = 'left' ;
ctx . fillText ( 'BRAIN GAP SCORECARD' , 20 , 74 ) ;
// Categories
// Categories — honest offline state (no scores, empty bars)
const categories = [
{ name : 'Triage' , score : 0.87 , status : 'GRADUATED' , color : '#00ff88' } ,
{ name : 'Tool Use' , score : 0.78 , status : 'PROBATION' , color : '#ffcc00' } ,
{ name : 'Code Gen' , score : 0.62 , status : 'SHADOW' , color : '#4488ff' } ,
{ name : 'Planning' , score : 0.71 , status : 'SHADOW' , color : '#4488ff' } ,
{ name : 'Communication' , score : 0.83 , status : 'PROBATION' , color : '#ffcc00' } ,
{ name : 'Reasoning' , score : 0.55 , status : 'CLOUD ONLY' , color : '#ff4444' } ,
{ name : 'Triage' } ,
{ name : 'Tool Use' } ,
{ name : 'Code Gen' } ,
{ name : 'Planning' } ,
{ name : 'Communication' } ,
{ name : 'Reasoning' } ,
] ;
const barX = 20 ;
@@ -2134,34 +2195,22 @@ function createDualBrainTexture() {
for ( const cat of categories ) {
// Category label
ctx . font = '13px "Courier New", monospace' ;
ctx . fillStyle = '#ccd6f 6' ;
ctx . fillStyle = '#44556 6' ;
ctx . textAlign = 'left' ;
ctx . fillText ( cat . name , barX , y + 14 ) ;
// Score value
// Score value — dash (no data)
ctx . font = 'bold 13px "Courier New", monospace' ;
ctx . fillStyle = cat . color ;
ctx . fillStyle = '#334466' ;
ctx . textAlign = 'right' ;
ctx . fillText ( cat . score . toFixed ( 2 ) , W - 20 , y + 14 ) ;
ctx . fillText ( '\u2014' , W - 20 , y + 14 ) ;
y += 22 ;
// Bar background
// Bar background only — no fill (zero-width)
ctx . fillStyle = 'rgba(255, 255, 255, 0.06)' ;
ctx . fillRect ( barX , y , barW , barH ) ;
// Bar fill
ctx . fillStyle = cat . color ;
ctx . globalAlpha = 0.7 ;
ctx . fillRect ( barX , y , barW * cat . score , barH ) ;
ctx . globalAlpha = 1.0 ;
// Status label on bar
ctx . font = '10px "Courier New", monospace' ;
ctx . fillStyle = '#000000' ;
ctx . textAlign = 'left' ;
ctx . fillText ( cat . status , barX + 6 , y + 14 ) ;
y += barH + 12 ;
}
@@ -2174,35 +2223,32 @@ function createDualBrainTexture() {
y += 22 ;
// Overall scor e
ctx . font = '12 px "Courier New", monospace' ;
ctx . fillStyle = '#556688 ' ;
ctx . textAlign = 'left' ;
ctx . fillText ( 'OVERALL CONVERGENCE' , 20 , y ) ;
ctx . font = 'bold 36px "Courier New", monospace' ;
ctx . fillStyle = '#88ccff' ;
// Status text — honest offlin e
ctx . font = 'bold 18 px "Courier New", monospace' ;
ctx . fillStyle = '#334466 ' ;
ctx . textAlign = 'center' ;
ctx . fillText ( '0.73 ' , W / 2 , y + 44 ) ;
ctx . fillText ( 'AWAITING DEPLOYMENT ' , W / 2 , y + 10 ) ;
// Brain indicators at bottom
y + = 60 ;
// Cloud brain indicator
ctx . font = '11px "Courier New", monospace' ;
ctx . fillStyle = '#223344' ;
ctx . fillText ( 'Dual-brain system not yet connected' , W / 2 , y + 32 ) ;
// Brain indicators at bottom — dim (offline)
y += 52 ;
ctx . beginPath ( ) ;
ctx . arc ( W / 2 - 60 , y + 8 , 6 , 0 , Math . PI * 2 ) ;
ctx . fillStyle = '#00ddff ' ;
ctx . fillStyle = '#334466 ' ;
ctx . fill ( ) ;
ctx . font = '11px "Courier New", monospace' ;
ctx . fillStyle = '#00ddff ' ;
ctx . fillStyle = '#334466 ' ;
ctx . textAlign = 'left' ;
ctx . fillText ( 'CLOUD' , W / 2 - 48 , y + 12 ) ;
// Local brain indicator
ctx . beginPath ( ) ;
ctx . arc ( W / 2 + 30 , y + 8 , 6 , 0 , Math . PI * 2 ) ;
ctx . fillStyle = '#ffaa22 ' ;
ctx . fillStyle = '#334466 ' ;
ctx . fill ( ) ;
ctx . fillStyle = '#ffaa22 ' ;
ctx . fillStyle = '#334466 ' ;
ctx . fillText ( 'LOCAL' , W / 2 + 42 , y + 12 ) ;
return new THREE . CanvasTexture ( canvas ) ;
@@ -2233,13 +2279,13 @@ dualBrainLight.position.set(0, 0.5, 1);
dualBrainGroup . add ( dualBrainLight ) ;
// --- Brain Orbs ---
// Cloud brain orb (cyan) — positioned left of pa nel
const CLOUD _ORB _COLOR = 0x00ddff ;
// Cloud brain orb — dim grey (dual-brain offli ne)
const CLOUD _ORB _COLOR = 0x334466 ;
const cloudOrbGeo = new THREE . SphereGeometry ( 0.35 , 32 , 32 ) ;
const cloudOrbMat = new THREE . MeshStandardMaterial ( {
color : CLOUD _ORB _COLOR ,
emissive : new THREE . Color ( CLOUD _ORB _COLOR ) ,
emissiveIntensity : 1.5 ,
emissiveIntensity : 0.1 ,
metalness : 0.3 ,
roughness : 0.2 ,
transparent : true ,
@@ -2250,17 +2296,17 @@ cloudOrb.position.set(-2.0, 3.0, 0);
cloudOrb . userData . zoomLabel = 'Cloud Brain' ;
dualBrainGroup . add ( cloudOrb ) ;
const cloudOrbLight = new THREE . PointLight ( CLOUD _ORB _COLOR , 0.8 , 5 ) ;
const cloudOrbLight = new THREE . PointLight ( CLOUD _ORB _COLOR , 0.15 , 5 ) ;
cloudOrbLight . position . copy ( cloudOrb . position ) ;
dualBrainGroup . add ( cloudOrbLight ) ;
// Local brain orb (amber) — positioned right of pa nel
const LOCAL _ORB _COLOR = 0xffaa22 ;
// Local brain orb — dim grey (dual-brain offli ne)
const LOCAL _ORB _COLOR = 0x334466 ;
const localOrbGeo = new THREE . SphereGeometry ( 0.35 , 32 , 32 ) ;
const localOrbMat = new THREE . MeshStandardMaterial ( {
color : LOCAL _ORB _COLOR ,
emissive : new THREE . Color ( LOCAL _ORB _COLOR ) ,
emissiveIntensity : 1.5 ,
emissiveIntensity : 0.1 ,
metalness : 0.3 ,
roughness : 0.2 ,
transparent : true ,
@@ -2271,13 +2317,13 @@ localOrb.position.set(2.0, 3.0, 0);
localOrb . userData . zoomLabel = 'Local Brain' ;
dualBrainGroup . add ( localOrb ) ;
const localOrbLight = new THREE . PointLight ( LOCAL _ORB _COLOR , 0.8 , 5 ) ;
const localOrbLight = new THREE . PointLight ( LOCAL _ORB _COLOR , 0.15 , 5 ) ;
localOrbLight . position . copy ( localOrb . position ) ;
dualBrainGroup . add ( localOrbLight ) ;
// --- Brain Pulse Particle Stream ---
// Particles flow from cloud orb → local orb along a curved arc
const BRAIN _PARTICLE _COUNT = 12 0;
// Particles OFF — dual-brain system not deployed. Will flow when system comes online.
const BRAIN _PARTICLE _COUNT = 0 ;
const brainParticlePositions = new Float32Array ( BRAIN _PARTICLE _COUNT * 3 ) ;
const brainParticlePhases = new Float32Array ( BRAIN _PARTICLE _COUNT ) ; // 0..1 progress along arc
const brainParticleSpeeds = new Float32Array ( BRAIN _PARTICLE _COUNT ) ;
@@ -2361,6 +2407,12 @@ function animate() {
stars . rotation . x = ( targetRotX + elapsed * 0.01 ) * rotationScale ;
stars . rotation . y = ( targetRotY + elapsed * 0.015 ) * rotationScale ;
// Star brightness pulse — tethered to Bitcoin block events
if ( _starPulseIntensity > 0 ) {
_starPulseIntensity = Math . max ( 0 , _starPulseIntensity - STAR _PULSE _DECAY ) ;
}
starMaterial . opacity = STAR _BASE _OPACITY + ( STAR _PEAK _OPACITY - STAR _BASE _OPACITY ) * _starPulseIntensity ;
constellationLines . rotation . x = stars . rotation . x ;
constellationLines . rotation . y = stars . rotation . y ;
@@ -2533,17 +2585,28 @@ function animate() {
burst . geo . attributes . position . needsUpdate = true ;
}
// Animate rune ring — orbit and vertical float
// Animate rune ring — orbit and vertical float, brightness tethered to portal status
for ( const rune of runeSprites ) {
const angle = rune . baseAngle + elapsed * RUNE _ORBIT _SPEED ;
rune . sprite . position . x = Math . cos ( angle ) * RUNE _RING _RADIUS ;
rune . sprite . position . z = Math . sin ( angle ) * RUNE _RING _RADIUS ;
rune . sprite . position . y = RUNE _RING _Y + Math . sin ( elapsed * 0.7 + rune . floatPhase ) * 0.4 ;
rune . sprite . material . opacity = 0.65 + Math . sin ( elapsed * 1.2 + rune . floatPhase ) * 0.2 ;
// Online portal = bright, offline = dim
const baseOpacity = rune . portalOnline ? 0.85 : 0.12 ;
const pulseRange = rune . portalOnline ? 0.15 : 0.03 ;
rune . sprite . material . opacity = baseOpacity + Math . sin ( elapsed * 1.2 + rune . floatPhase ) * pulseRange ;
}
// Animate holographic Earth — slow axial rotation, gentle float, glow pulse
earthMesh . rotation . y = elapsed * EARTH _ROTATION _SPEED ;
// Animate holographic Earth — rotation speed tethered to totalActivity()
// Idle system = very slow (0.005), active system = faster (0.05)
const earthActivity = totalActivity ( ) ;
const targetEarthSpeed = 0.005 + earthActivity * 0.045 ;
// Smooth interpolation — don't jump
const _eSmooth = 0.02 ;
const currentEarthSpeed = earthMesh . userData . _currentSpeed || EARTH _ROTATION _SPEED ;
const smoothedEarthSpeed = currentEarthSpeed + ( targetEarthSpeed - currentEarthSpeed ) * _eSmooth ;
earthMesh . userData . _currentSpeed = smoothedEarthSpeed ;
earthMesh . rotation . y += smoothedEarthSpeed ;
earthSurfaceMat . uniforms . uTime . value = elapsed ;
earthGlowLight . intensity = 0.30 + Math . sin ( elapsed * 0.7 ) * 0.12 ;
earthGroup . position . y = EARTH _Y + Math . sin ( elapsed * 0.22 ) * 0.6 ;
@@ -2603,13 +2666,13 @@ function animate() {
Math . sin ( elapsed * dualBrainSprite . userData . floatSpeed + dualBrainSprite . userData . floatPhase ) * 0.22 ;
dualBrainScanSprite . position . y = dualBrainSprite . position . y ;
// Orb glow pulse
const cloudPulse = 1.2 + Math . sin ( elapsed * 1.8 ) * 0.4 ;
const localPulse = 1.2 + Math . sin ( elapsed * 1.8 + Math . PI ) * 0.4 ;
// Orb glow — dim idle pulse (dual-brain offline)
const cloudPulse = 0.08 + Math . sin ( elapsed * 0.6 ) * 0.03 ;
const localPulse = 0.08 + Math . sin ( elapsed * 0.6 + Math . PI ) * 0.03 ;
cloudOrbMat . emissiveIntensity = cloudPulse ;
localOrbMat . emissiveIntensity = localPulse ;
cloudOrbLight . intensity = 0.5 + Math . sin ( elapsed * 1.8 ) * 0.3 ;
localOrbLight . intensity = 0.5 + Math . sin ( elapsed * 1.8 + Math . PI ) * 0.3 ;
cloudOrbLight . intensity = 0.1 + Math . sin ( elapsed * 0.6 ) * 0.05 ;
localOrbLight . intensity = 0.1 + Math . sin ( elapsed * 0.6 + Math . PI ) * 0.05 ;
// Orb hover
cloudOrb . position . y = 3.0 + Math . sin ( elapsed * 0.9 ) * 0.15 ;
@@ -2617,33 +2680,27 @@ function animate() {
cloudOrbLight . position . y = cloudOrb . position . y ;
localOrbLight . position . y = localOrb . position . y ;
// Brain pulse particles — flow along a curved arc from cloud → local orb
{
// Brain pulse particles — OFF (dual-brain system not deployed)
// Will be re-enabled with flow rate proportional to convergence delta when system deploys
if ( BRAIN _PARTICLE _COUNT > 0 ) {
const pos = brainParticleGeo . attributes . position . array ;
const startX = cloudOrb . position . x ;
const endX = localOrb . position . x ;
const arcHeight = 1.2 ; // peak height of arc above orbs
const simRate = 0.73 ; // simulated learning rate tied to overall score
const arcHeight = 1.2 ;
const simRate = 0.73 ;
for ( let i = 0 ; i < BRAIN _PARTICLE _COUNT ; i ++ ) {
brainParticlePhases [ i ] += brainParticleSpeeds [ i ] * simRate * 0.016 ;
if ( brainParticlePhases [ i ] > 1.0 ) brainParticlePhases [ i ] -= 1.0 ;
const t = brainParticlePhases [ i ] ;
// Lerp X between orbs
pos [ i * 3 ] = startX + ( endX - startX ) * t ;
// Arc Y: parabolic curve peaking at midpoint
const midY = ( cloudOrb . position . y + localOrb . position . y ) / 2 + arcHeight ;
pos [ i * 3 + 1 ] = cloudOrb . position . y + ( midY - cloudOrb . position . y ) * 4 * t * ( 1 - t )
+ ( localOrb . position . y - cloudOrb . position . y ) * t ;
// Slight Z wobble for volume
pos [ i * 3 + 2 ] = Math . sin ( t * Math . PI * 4 + elapsed * 2 + i ) * 0.12 ;
}
brainParticleGeo . attributes . position . needsUpdate = true ;
// Colour lerp from cyan → amber based on progress (approximated via hue shift)
const pulseIntensity = 0.6 + Math . sin ( elapsed * 2.0 ) * 0.2 ;
brainParticleMat . opacity = pulseIntensity ;
brainParticleMat . opacity = 0.6 + Math . sin ( elapsed * 2.0 ) * 0.2 ;
}
// Scanning line effect — thin horizontal line sweeps down the panel
@@ -3633,6 +3690,7 @@ function createPortals() {
portals . forEach ( portal => {
const isOnline = portal . status === 'online' ;
const portalMat = new THREE . MeshBasicMaterial ( {
@@ -3642,8 +3700,8 @@ function createPortals() {
transparent : true ,
opacity : 0.7 ,
// Offline portals are dimmed
opacity : isOnline ? 0.7 : 0.15 ,
blending : THREE . AdditiveBlending ,
@@ -3705,8 +3763,14 @@ async function loadPortals() {
portals = await res . json ( ) ;
console . log ( 'Loaded portals:' , portals ) ;
createPortals ( ) ;
// Rebuild rune ring to match portal count/colors/status
rebuildRuneRing ( ) ;
// Rebuild gravity zones to align with portal positions
rebuildGravityZones ( ) ;
// If audio is already running, attach positional hums to the portals now
startPortalHums ( ) ;
// Run portal health checks
runPortalHealthChecks ( ) ;
} catch ( error ) {
console . error ( 'Failed to load portals:' , error ) ;
}
@@ -4267,18 +4331,99 @@ loadSoulMd().then(lines => { oathLines = lines; });
// === AGENT STATUS BOARD ===
const AGENT _STATUS _STUB = {
agents : [
{ name : 'claude' , status : 'working' , issue : 'Live agent s tatus board (#199)' , prs _today : 3 , local : true } ,
{ name : 'gemini' , status : 'idle' , issue : null , prs _today : 1 , local : false } ,
{ name : 'kimi' , status : 'working' , issue : 'Portal system YAML registry (#5)' , prs _today : 2 , local : false } ,
{ name : 'groq' , status : 'idle' , issue : null , prs _today : 0 , local : false } ,
{ name : 'grok' , status : 'dead' , issue : null , prs _today : 0 , local : false } ,
{ name : 'ollama' , status : 'idle' , issue : null , prs _today : 0 , local : true } ,
]
} ;
// Agent status cache — refreshed from Gitea API every 5 minutes
let _ agentStatusCache = null ;
let _ agentS tatusCacheTime = 0 ;
const AGENT _STATUS _CACHE _MS = 5 * 60 * 1000 ;
const AGENT _STATUS _COLORS = { working : '#00ff88' , idle : '#4488ff' , dead : '#ff4444' } ;
const GITEA _BASE = 'http://143.198.27.163:3000/api/v1' ;
const GITEA _TOKEN = '81a88f46684e398abe081f5786a11ae9532aae2d' ;
const GITEA _REPOS = [ 'Timmy_Foundation/the-nexus' , 'Timmy_Foundation/hermes-agent' ] ;
const AGENT _NAMES = [ 'Claude' , 'Kimi' , 'Perplexity' , 'Groq' , 'Grok' , 'Ollama' ] ;
/**
* Fetches real agent status from Gitea API — commits + open PRs for each agent.
* Results are cached for 5 minutes.
* @returns {Promise<{agents: Array}>}
*/
async function fetchAgentStatusFromGitea ( ) {
const now = Date . now ( ) ;
if ( _agentStatusCache && ( now - _agentStatusCacheTime < AGENT _STATUS _CACHE _MS ) ) {
return _agentStatusCache ;
}
const DAY _MS = 86400000 ;
const HOUR _MS = 3600000 ;
const agents = [ ] ;
// Fetch commits from all repos in parallel
const allRepoCommits = await Promise . all ( GITEA _REPOS . map ( async ( repo ) => {
try {
const res = await fetch ( ` ${ GITEA _BASE } /repos/ ${ repo } /commits?sha=main&limit=30&token= ${ GITEA _TOKEN } ` ) ;
if ( ! res . ok ) return [ ] ;
return await res . json ( ) ;
} catch { return [ ] ; }
} ) ) ;
// Fetch open PRs from the-nexus
let openPRs = [ ] ;
try {
const prRes = await fetch ( ` ${ GITEA _BASE } /repos/Timmy_Foundation/the-nexus/pulls?state=open&limit=50&token= ${ GITEA _TOKEN } ` ) ;
if ( prRes . ok ) openPRs = await prRes . json ( ) ;
} catch { /* ignore */ }
for ( const agentName of AGENT _NAMES ) {
const nameLower = agentName . toLowerCase ( ) ;
const allCommits = [ ] ;
for ( const repoCommits of allRepoCommits ) {
if ( ! Array . isArray ( repoCommits ) ) continue ;
const matching = repoCommits . filter ( c =>
( c . commit ? . author ? . name || '' ) . toLowerCase ( ) . includes ( nameLower )
) ;
allCommits . push ( ... matching ) ;
}
// Determine status based on most recent commit
let status = 'dormant' ;
let lastSeen = null ;
let currentWork = null ;
if ( allCommits . length > 0 ) {
allCommits . sort ( ( a , b ) =>
new Date ( b . commit . author . date ) - new Date ( a . commit . author . date )
) ;
const latest = allCommits [ 0 ] ;
const commitTime = new Date ( latest . commit . author . date ) . getTime ( ) ;
lastSeen = latest . commit . author . date ;
currentWork = latest . commit . message . split ( '\n' ) [ 0 ] ;
if ( now - commitTime < HOUR _MS ) status = 'working' ;
else if ( now - commitTime < DAY _MS ) status = 'idle' ;
else status = 'dormant' ;
}
// Count open PRs for this agent
const agentPRs = openPRs . filter ( pr =>
( pr . user ? . login || '' ) . toLowerCase ( ) . includes ( nameLower ) ||
( pr . head ? . label || '' ) . toLowerCase ( ) . includes ( nameLower )
) ;
agents . push ( {
name : agentName . toLowerCase ( ) ,
status ,
issue : currentWork ,
prs _today : agentPRs . length ,
local : nameLower === 'ollama' ,
} ) ;
}
_agentStatusCache = { agents } ;
_agentStatusCacheTime = now ;
return _agentStatusCache ;
}
const AGENT _STATUS _COLORS = { working : '#00ff88' , idle : '#4488ff' , dormant : '#334466' , dead : '#ff4444' , unreachable : '#ff4444' } ;
/**
* Builds a canvas texture for a single agent holo-panel.
@@ -4432,48 +4577,44 @@ function rebuildAgentPanels(statusData) {
}
/**
* Fetches live agent status, falling back to the stub when the endpoint is unavailable .
* @returns {Promise<typeof AGENT_STATUS_STUB>}
* Fetches live agent status from the Gitea API .
* Shows "UNREACHABLE" if the API call fails entirely.
* @returns {Promise<{agents: Array}>}
*/
async function fetchAgentStatus ( ) {
try {
const res = await fetch ( '/api/status.json' ) ;
if ( ! res . ok ) throw new Error ( 'status ' + res . status ) ;
return await res . json ( ) ;
return await fetchAgentStatusFromGitea ( ) ;
} catch {
return AGENT _STATUS _STUB ;
return { agents : AGENT _NAMES . map ( n => ( {
name : n . toLowerCase ( ) , status : 'unreachable' , issue : null , prs _today : 0 , local : false ,
} ) ) } ;
}
}
async function refreshAgentBoard ( ) {
const data = await fetchAgentStatus ( ) ;
rebuildAgentPanels ( data ) ;
// Update active agent count for energy beam tethering
_activeAgentCount = data . agents . filter ( a => a . status === 'working' ) . length ;
}
// Initial render, then poll every 30 s
// Initial render, then poll every 5 min (matching API cache interval)
refreshAgentBoard ( ) ;
setInterval ( refreshAgentBoard , 30000 ) ;
setInterval ( refreshAgentBoard , AGENT _STATUS _CACHE _MS ) ;
// === LORA ADAPTER STATUS PANEL ===
// Holographic panel showing which LoRA fine-tuning adapters are currently active .
// Reads from lora-status.json, falls back to stub d ata when unavailable .
// Holographic panel showing LoRA fine-tuning adapter status .
// Shows honest empty st ate when no adapters are deployed .
const LORA _STATUS _STUB = {
adapters : [
{ name : 'timmy-voice-v3' , base : 'mistral-7b' , active : true , strength : 0.85 } ,
{ name : 'nexus-style-v2' , base : 'llama-3-8b' , active : true , strength : 0.70 } ,
{ name : 'sovereign-tone-v1' , base : 'phi-3-mini' , active : false , strength : 0.50 } ,
{ name : 'btc-domain-v1' , base : 'mistral-7b' , active : true , strength : 0.60 } ,
] ,
updated : '' ,
} ;
// No LoRA stub — honest empty state when no adapters are deployed
const LORA _ACTIVE _COLOR = '#00ff88' ; // green — adapter is loaded
const LORA _INACTIVE _COLOR = '#334466' ; // dim blue — adapter is off
/**
* Builds a canvas texture for the LoRA status panel.
* @param {typeof LORA_STATUS_STUB} data
* Shows honest empty state when no adapters are deployed.
* @param {{ adapters: Array }|null} data
* @returns {THREE.CanvasTexture}
*/
function createLoRAPanelTexture ( data ) {
@@ -4510,14 +4651,6 @@ function createLoRAPanelTexture(data) {
ctx . fillStyle = '#664488' ;
ctx . fillText ( 'LoRA ADAPTERS' , 14 , 38 ) ;
// Active count badge (top-right)
const activeCount = data . adapters . filter ( a => a . active ) . length ;
ctx . font = 'bold 13px "Courier New", monospace' ;
ctx . fillStyle = LORA _ACTIVE _COLOR ;
ctx . textAlign = 'right' ;
ctx . fillText ( ` ${ activeCount } / ${ data . adapters . length } ACTIVE ` , W - 14 , 26 ) ;
ctx . textAlign = 'left' ;
// Separator
ctx . strokeStyle = '#2a1a44' ;
ctx . lineWidth = 1 ;
@@ -4526,31 +4659,43 @@ function createLoRAPanelTexture(data) {
ctx . lineTo ( W - 14 , 46 ) ;
ctx . stroke ( ) ;
// Adapter rows
// Honest empty state — no adapters deployed
if ( ! data || ! data . adapters || data . adapters . length === 0 ) {
ctx . font = 'bold 18px "Courier New", monospace' ;
ctx . fillStyle = '#334466' ;
ctx . textAlign = 'center' ;
ctx . fillText ( 'NO ADAPTERS DEPLOYED' , W / 2 , H / 2 + 10 ) ;
ctx . font = '11px "Courier New", monospace' ;
ctx . fillStyle = '#223344' ;
ctx . fillText ( 'Adapters will appear here when trained' , W / 2 , H / 2 + 36 ) ;
ctx . textAlign = 'left' ;
return new THREE . CanvasTexture ( canvas ) ;
}
// If adapters exist in the future, render them
const activeCount = data . adapters . filter ( a => a . active ) . length ;
ctx . font = 'bold 13px "Courier New", monospace' ;
ctx . fillStyle = LORA _ACTIVE _COLOR ;
ctx . textAlign = 'right' ;
ctx . fillText ( ` ${ activeCount } / ${ data . adapters . length } ACTIVE ` , W - 14 , 26 ) ;
ctx . textAlign = 'left' ;
const ROW _H = 44 ;
data . adapters . forEach ( ( adapter , i ) => {
const rowY = 50 + i * ROW _H ;
const col = adapter . active ? LORA _ACTIVE _COLOR : LORA _INACTIVE _COLOR ;
// Status dot
ctx . beginPath ( ) ;
ctx . arc ( 22 , rowY + 12 , 6 , 0 , Math . PI * 2 ) ;
ctx . fillStyle = col ;
ctx . fill ( ) ;
// Adapter name
ctx . font = 'bold 13px "Courier New", monospace' ;
ctx . fillStyle = adapter . active ? '#ddeeff' : '#445566' ;
ctx . fillText ( adapter . name , 36 , rowY + 16 ) ;
// Base model (right-aligned)
ctx . font = '10px "Courier New", monospace' ;
ctx . fillStyle = '#556688' ;
ctx . textAlign = 'right' ;
ctx . fillText ( adapter . base , W - 14 , rowY + 16 ) ;
ctx . textAlign = 'left' ;
// Strength bar
if ( adapter . active ) {
const BAR _X = 36 , BAR _W = W - 80 , BAR _Y = rowY + 22 , BAR _H = 5 ;
ctx . fillStyle = '#0a1428' ;
@@ -4559,14 +4704,7 @@ function createLoRAPanelTexture(data) {
ctx . globalAlpha = 0.7 ;
ctx . fillRect ( BAR _X , BAR _Y , BAR _W * adapter . strength , BAR _H ) ;
ctx . globalAlpha = 1.0 ;
ctx . font = '9px "Courier New", monospace' ;
ctx . fillStyle = col ;
ctx . textAlign = 'right' ;
ctx . fillText ( ` ${ Math . round ( adapter . strength * 100 ) } % ` , W - 14 , rowY + 28 ) ;
ctx . textAlign = 'left' ;
}
// Row divider (except after last)
if ( i < data . adapters . length - 1 ) {
ctx . strokeStyle = '#1a0a2a' ;
ctx . lineWidth = 1 ;
@@ -4589,7 +4727,7 @@ let loraPanelSprite = null;
/**
* (Re)builds the LoRA panel sprite from fresh data.
* @param {typeof LORA_STATUS_STUB } data
* @param {{ adapters: Array }|null } data
*/
function rebuildLoRAPanel ( data ) {
if ( loraPanelSprite ) {
@@ -4618,23 +4756,61 @@ function rebuildLoRAPanel(data) {
}
/**
* Fetches live LoRA adapter status, falling back to stub when unavailable .
* Renders the LoRA panel with honest empty state — no adapters deployed .
*/
async function loadLoRAStatus ( ) {
try {
const res = await fetch ( './lora-status.json' ) ;
if ( ! res . ok ) throw new Error ( 'not found' ) ;
const data = await res . json ( ) ;
if ( ! Array . isArray ( data . adapters ) ) throw new Error ( 'invalid' ) ;
rebuildLoRAPanel ( data ) ;
} catch {
rebuildLoRAPanel ( LORA _STATUS _STUB ) ;
}
function loadLoRAStatus ( ) {
rebuildLoRAPanel ( { adapters : [ ] } ) ;
}
loadLoRAStatus ( ) ;
// Refresh every 60 s so live updates propagate
setInterval ( loadLoRAStatus , 60000 ) ;
// === PORTAL HEALTH CHECKS ===
// Probes portal destination URLs to verify they're actually reachable.
// Uses portals.json status as the baseline — since all are currently "offline", this is honest.
// Health check runs every 5 minutes to detect if a portal comes online.
const PORTAL _HEALTH _CHECK _MS = 5 * 60 * 1000 ;
/**
* Runs a health check against each portal's destination URL.
* Updates portal status and refreshes visuals (runes, gravity zones).
*/
async function runPortalHealthChecks ( ) {
if ( portals . length === 0 ) return ;
for ( const portal of portals ) {
if ( ! portal . destination ? . url ) {
portal . status = 'offline' ;
continue ;
}
try {
await fetch ( portal . destination . url , {
mode : 'no-cors' ,
signal : AbortSignal . timeout ( 5000 ) ,
} ) ;
// Any response at all means the server is up
portal . status = 'online' ;
} catch {
portal . status = 'offline' ;
}
}
// Refresh rune ring and gravity zones with updated portal statuses
rebuildRuneRing ( ) ;
rebuildGravityZones ( ) ;
// Update portal mesh visuals — dim offline portals
for ( const child of portalGroup . children ) {
const portalId = child . name . replace ( 'portal-' , '' ) ;
const portalData = portals . find ( p => p . id === portalId ) ;
if ( portalData ) {
const isOnline = portalData . status === 'online' ;
child . material . opacity = isOnline ? 0.7 : 0.15 ;
}
}
}
// Schedule recurring health checks
setInterval ( runPortalHealthChecks , PORTAL _HEALTH _CHECK _MS ) ;
// === WEATHER SYSTEM — Lempster NH ===
// Fetches real current weather from Open-Meteo (no API key required).
@@ -4771,15 +4947,20 @@ function updateWeatherHUD(wx) {
*/
async function fetchWeather ( ) {
try {
const url = ` https://api.open-meteo.com/v1/forecast?latitude= ${ WEATHER _LAT } &longitude= ${ WEATHER _LON } ¤t=temperature_2m,weather_code,wind_speed_10m&temperature_unit=fahrenheit&wind_speed_unit=mph&forecast_days=1 ` ;
const url = ` https://api.open-meteo.com/v1/forecast?latitude= ${ WEATHER _LAT } &longitude= ${ WEATHER _LON } ¤t=temperature_2m,weather_code,wind_speed_10m,cloud_cover &temperature_unit=fahrenheit&wind_speed_unit=mph&forecast_days=1 ` ;
const res = await fetch ( url ) ;
if ( ! res . ok ) throw new Error ( 'weather fetch failed' ) ;
const data = await res . json ( ) ;
const cur = data . current ;
const code = cur . weather _code ;
const { condition , icon } = weatherCodeToLabel ( code ) ;
weatherState = { code , temp : cur . temperature _2m , wind : cur . wind _speed _10m , condition , icon } ;
const cloudcover = typeof cur . cloud _cover === 'number' ? cur . cloud _cover : 50 ;
weatherState = { code , temp : cur . temperature _2m , wind : cur . wind _speed _10m , condition , icon , cloudcover } ;
applyWeatherToScene ( weatherState ) ;
// Tether cloud layer density to real weather cloudcover
const cloudOpacity = 0.05 + ( cloudcover / 100 ) * 0.55 ; // range [0.05, 0.60]
cloudMaterial . uniforms . uDensity . value = 0.3 + ( cloudcover / 100 ) * 0.7 ; // range [0.3, 1.0]
cloudMaterial . opacity = cloudOpacity ;
updateWeatherHUD ( weatherState ) ;
} catch {
// Silently use defaults — no weather data available
@@ -4793,12 +4974,13 @@ setInterval(fetchWeather, WEATHER_REFRESH_MS);
// === GRAVITY ANOMALY ZONES ===
// Areas where particles defy gravity and float upward.
// Each zone has a glowing floor ring and a rising pa rticle stream .
// Tethered to portal positions and status — active po rtals have stronger anomalies .
const GRAVITY _ANOMALY _FLOOR = 0.2 ; // Y where particles respawn (ground level)
const GRAVITY _ANOMALY _CEIL = 16.0 ; // Y where particles wrap back to floor
const GRAVITY _ZONES = [
// Default zones — replaced when portals load
let GRAVITY _ZONES = [
{ x : - 8 , z : - 6 , radius : 3.5 , color : 0x00ffcc , particleCount : 180 } ,
{ x : 10 , z : 4 , radius : 3.0 , color : 0xaa44ff , particleCount : 160 } ,
{ x : - 3 , z : 9 , radius : 2.5 , color : 0xff8844 , particleCount : 140 } ,
@@ -4867,6 +5049,51 @@ const gravityZoneObjects = GRAVITY_ZONES.map((zone) => {
return { zone , ring , ringMat , disc , discMat , points , geo , driftPhases , velocities } ;
} ) ;
/**
* Rebuilds gravity anomaly zones to align with portal positions.
* Active/online portals get stronger anomaly; offline portals get weaker effect.
*/
function rebuildGravityZones ( ) {
if ( portals . length === 0 ) return ;
// Update existing zone positions/intensities to match portal data
for ( let i = 0 ; i < Math . min ( portals . length , gravityZoneObjects . length ) ; i ++ ) {
const portal = portals [ i ] ;
const gz = gravityZoneObjects [ i ] ;
const isOnline = portal . status === 'online' ;
const portalColor = new THREE . Color ( portal . color ) ;
// Reposition ring and disc to portal position
gz . ring . position . set ( portal . position . x , GRAVITY _ANOMALY _FLOOR + 0.05 , portal . position . z ) ;
gz . disc . position . set ( portal . position . x , GRAVITY _ANOMALY _FLOOR + 0.04 , portal . position . z ) ;
// Update zone reference for particle respawn
gz . zone . x = portal . position . x ;
gz . zone . z = portal . position . z ;
gz . zone . color = portalColor . getHex ( ) ;
// Update colors
gz . ringMat . color . copy ( portalColor ) ;
gz . discMat . color . copy ( portalColor ) ;
gz . points . material . color . copy ( portalColor ) ;
// Offline portals: reduced opacity/intensity
gz . ringMat . opacity = isOnline ? 0.4 : 0.08 ;
gz . discMat . opacity = isOnline ? 0.04 : 0.01 ;
gz . points . material . opacity = isOnline ? 0.7 : 0.15 ;
// Reposition particles around portal
const pos = gz . geo . attributes . position . array ;
for ( let j = 0 ; j < gz . zone . particleCount ; j ++ ) {
const angle = Math . random ( ) * Math . PI * 2 ;
const r = Math . sqrt ( Math . random ( ) ) * gz . zone . radius ;
pos [ j * 3 ] = gz . zone . x + Math . cos ( angle ) * r ;
pos [ j * 3 + 2 ] = gz . zone . z + Math . sin ( angle ) * r ;
}
gz . geo . attributes . position . needsUpdate = true ;
}
}
// === TIMMY SPEECH BUBBLE ===
// When Timmy sends a chat message, a glowing floating text sprite appears near
// his avatar position above the platform. Fades in quickly, holds for 5 s total,
@@ -5149,6 +5376,8 @@ async function fetchBlockHeight() {
// Force reflow so animation restarts
void blockHeightDisplay . offsetWidth ;
blockHeightDisplay . classList . add ( 'fresh' ) ;
// Pulse stars — chain heartbeat
_starPulseIntensity = 1.0 ;
}
lastKnownBlockHeight = height ;