import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; import { WebGPURendererLayer } from './renderer/webgpu'; import { WebGL1Renderer } from './renderer/webgl1'; import { savePeer, getPeerCount } from './db/peer-history'; import gsap from 'gsap'; // ─── State & Shared Memory const sharedBuffer = new SharedArrayBuffer(4); const peerCounter = new Int32Array(sharedBuffer); let rendererLayer: any; let composer: EffectComposer; let worker: Worker; let wasmInstance: any; async function loadWasm() { const response = await fetch('/particles.wasm'); const buffer = await response.arrayBuffer(); const { instance } = await WebAssembly.instantiate(buffer, { env: { memory: new WebAssembly.Memory({ initial: 256 }), abort: () => console.log('Abort!'), } }); return instance.exports; } // ─── UI Elements const logEl = document.getElementById('log-entries') as HTMLDivElement; const badge = document.getElementById('renderer-badge') as HTMLSpanElement; const statPeers = document.getElementById('stat-peers') as HTMLSpanElement; const statStoredPeers = document.getElementById('stat-peers-stored') as HTMLSpanElement; const statUptime = document.getElementById('stat-uptime') as HTMLSpanElement; const infoApi = document.getElementById('info-api') as HTMLSpanElement; const infoParticles = document.getElementById('info-particles') as HTMLSpanElement; const infoFps = document.getElementById('info-fps') as HTMLSpanElement; const infoWorker = document.getElementById('info-worker') as HTMLSpanElement; const coreMat = new THREE.MeshBasicMaterial({ color: 0x58a6ff, wireframe: true, transparent: true, opacity: 0.8 }); // ─── Logger function log(msg: string, type: string = 'info') { const el = document.createElement('div'); el.className = `log-entry ${type}`; const d = new Date(); const ts = d.toTimeString().slice(0, 8); el.innerHTML = `[${ts}] ${msg}`; logEl.prepend(el); gsap.from(el, { opacity: 0, x: -20, duration: 0.6, ease: "power3.out", onStart: () => { if (type === 'error') gsap.to(el, { x: "+=2", repeat: 5, yoyo: true, duration: 0.05 }); } }); while (logEl.children.length > 8) logEl.removeChild(logEl.lastChild!); } // ─── Mouse Interaction const mouse = new THREE.Vector2(); window.addEventListener('mousemove', (e) => { mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; // Parallax HUD const tiltX = mouse.y * 5; const tiltY = -mouse.x * 5; gsap.to('.panel', { rotateX: tiltX, rotateY: tiltY, duration: 1, ease: "power2.out", stagger: 0.05 }); }); // ─── Initialization async function init() { log('INITIALIZING QUANTUM IPFS CORE...', 'info'); // 0. Load WASM Physics try { wasmInstance = await loadWasm(); log('WASM_LAYER: STATUS_OK (ZIG_PHYSICS_ACTIVE)', 'ok'); } catch (e) { log('WASM_LAYER: LOAD_FAILED. FALLING BACK TO JS_EMULATION.', 'error'); } // 1. Renderer Selection const canvas = document.getElementById('main-canvas') as HTMLCanvasElement; let rawRenderer: any; if (navigator.gpu) { try { rendererLayer = new WebGPURendererLayer(canvas); await rendererLayer.init(); rawRenderer = rendererLayer.renderer; badge.textContent = 'WebGPU ❆'; } catch (e) { rendererLayer = new WebGL1Renderer(canvas); rawRenderer = rendererLayer.renderer; badge.textContent = 'WebGL [LEGACY]'; badge.classList.add('legacy'); } } else { rendererLayer = new WebGL1Renderer(canvas); rawRenderer = rendererLayer.renderer; badge.textContent = 'WebGL [LEGACY]'; badge.classList.add('legacy'); } infoApi.textContent = rendererLayer.api; // 2. Scene & Post-processing setupScene(); // Bloom for WebGL (WebGPU post-processing is still experimental in r168) if (rendererLayer.api === 'WebGL') { composer = new EffectComposer(rawRenderer); composer.addPass(new RenderPass(scene, camera)); const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85 ); composer.addPass(bloomPass); composer.addPass(new OutputPass()); } // 3. Worker initWorker(); // 4. Entrance Animations gsap.set('.panel, #header, #log', { opacity: 0 }); const tl = gsap.timeline(); tl.to('#header', { y: 0, opacity: 1, duration: 1.5, ease: "expo.out" }) .to('.panel', { opacity: 1, x: 0, duration: 1, stagger: 0.2, ease: "expo.out" }, "-=1") .to('#log', { opacity: 1, y: 0, duration: 1, ease: "expo.out" }, "-=0.5"); // 5. Start Loop animate(); updateStoredCount(); } function initWorker() { worker = new Worker(new URL('./helia.worker.ts', import.meta.url), { type: 'module' }); worker.postMessage({ type: 'init', data: { sharedBuffer } }); worker.onmessage = async (e) => { const { type, msg, level, status, peer } = e.data; if (type === 'log') log(msg, level); if (type === 'worker_status') { infoWorker.textContent = status; infoWorker.className = 'stat-val live'; } if (type === 'peer_discovered') { const peerData = { ...peer, connectedAt: new Date().toISOString() }; await savePeer(peerData); updateStoredCount(); // Visual feedback in core gsap.to(coreMat, { opacity: 1, duration: 0.2, yoyo: true, repeat: 1 }); } }; } async function updateStoredCount() { const count = await getPeerCount(); if (statStoredPeers) statStoredPeers.textContent = count.toString(); } // ─── Scene Setup let scene: THREE.Scene, camera: THREE.PerspectiveCamera, controls: OrbitControls, nodeGeo: THREE.BufferGeometry; const NODE_COUNT = 800; // Even more particles for WASM demo function setupScene() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000); camera.position.set(0, 40, 180); const canvas = document.getElementById('main-canvas') as HTMLCanvasElement; controls = new OrbitControls(camera, canvas); controls.enableDamping = true; controls.autoRotate = true; controls.autoRotateSpeed = 0.3; // Starfield with depth const STAR_COUNT = 10000; const starGeo = new THREE.BufferGeometry(); const starPos = new Float32Array(STAR_COUNT * 3); for (let i = 0; i < STAR_COUNT * 3; i++) starPos[i] = (Math.random() - 0.5) * 3000; starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3)); const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.8, transparent: true, opacity: 0.3 })); scene.add(stars); // Node Particles nodeGeo = new THREE.BufferGeometry(); const nodePos = new Float32Array(NODE_COUNT * 3); const nodeCol = new Float32Array(NODE_COUNT * 3); if (wasmInstance) wasmInstance.init_system(BigInt(NODE_COUNT)); for (let i = 0; i < NODE_COUNT; i++) { const r = 50 + Math.random() * 80; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const x = r * Math.sin(phi) * Math.cos(theta); const y = r * Math.sin(phi) * Math.sin(theta); const z = r * Math.cos(phi); const speed = 0.02 + Math.random() * 0.08; nodePos[i*3] = x; nodePos[i*3+1] = y; nodePos[i*3+2] = z; // Gradient from Primary to White const mix = Math.random(); nodeCol[i*3] = mix * 0.4 + 0.3; nodeCol[i*3+1] = mix * 0.4 + 0.6; nodeCol[i*3+2] = 1.0; if (wasmInstance) wasmInstance.set_particle_data(BigInt(i), x, y, z, speed); } nodeGeo.setAttribute('position', new THREE.BufferAttribute(nodePos, 3)); nodeGeo.setAttribute('color', new THREE.BufferAttribute(nodeCol, 3)); const points = new THREE.Points(nodeGeo, new THREE.PointsMaterial({ size: 4, vertexColors: true, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false })); scene.add(points); // Holographic Core const core = new THREE.Mesh(new THREE.IcosahedronGeometry(8, 1), coreMat); scene.add(core); // Rings const ringGeo = new THREE.TorusGeometry(120, 0.2, 16, 100); const ringMat = new THREE.MeshBasicMaterial({ color: 0x58a6ff, transparent: true, opacity: 0.2 }); const ring1 = new THREE.Mesh(ringGeo, ringMat); ring1.rotation.x = Math.PI / 2; scene.add(ring1); infoParticles.textContent = NODE_COUNT.toString(); } // ─── Animation let frameCount = 0, lastFpsTime = performance.now(), startTime = performance.now(); function animate() { requestAnimationFrame(animate); const now = performance.now(); const delta = (now - lastFpsTime) / 1000; // WASM Physics Update (Quantum Turbulence + Core Attraction) if (wasmInstance) { wasmInstance.update_particles(120.0, delta * 60.0); // Normalizing to 60fps for calculations const ptr = wasmInstance.get_positions_ptr(); const memory = wasmInstance.memory as WebAssembly.Memory; const positions = new Float32Array(memory.buffer, ptr, NODE_COUNT * 3); nodeGeo.attributes.position.array.set(positions); nodeGeo.attributes.position.needsUpdate = true; // Use WASM-driven frame count for stats if requested // infoFps.textContent = wasmInstance.get_frame_count().toString(); } const currentPeers = Atomics.load(peerCounter, 0); statPeers.textContent = currentPeers.toString(); controls.update(); if (composer) { composer.render(); } else { rendererLayer.render(scene, camera); } // Stats frameCount++; if (now - lastFpsTime > 1000) { infoFps.textContent = Math.round(frameCount * 1000 / (now - lastFpsTime)).toString(); frameCount = 0; lastFpsTime = now; const e = Math.floor((now - startTime) / 1000); statUptime.textContent = `${String(Math.floor(e/3600)).padStart(2,'0')}:${String(Math.floor((e%3600)/60)).padStart(2,'0')}:${String(e%60).padStart(2,'0')}`; } } // ─── Actions (window as any).connectWallet = async function() { if (!(window as any).ethereum) return log('WALLET_ERR: NO_PROVIDER_FOUND', 'error'); try { const accounts = await (window as any).ethereum.request({ method: 'eth_requestAccounts' }); log(`WALLET_AUTH: ${accounts[0].slice(0, 12)}...`, 'ok'); const hue = parseInt(accounts[0].slice(2, 8), 16) % 360; gsap.to(coreMat.color, { duration: 1.5, r: new THREE.Color().setHSL(hue/360, 0.9, 0.6).r, g: new THREE.Color().setHSL(hue/360, 0.9, 0.6).g, b: new THREE.Color().setHSL(hue/360, 0.9, 0.6).b, }); const nodeid = document.getElementById('stat-nodeid'); if (nodeid) nodeid.textContent = accounts[0]; } catch (e) { log('WALLET_AUTH: USER_REJECTED_OR_FAILED', 'error'); } }; (window as any).startIPFSProbe = function() { log('INITIATING_NETWORK_PROBE: PROTOCOL_QUIC_WANT', 'info'); Atomics.add(peerCounter, 0, 1); // Visual Feedback: Bloom Pulse if (composer) { const bloom = composer.passes.find(p => p instanceof UnrealBloomPass) as UnrealBloomPass; if (bloom) { gsap.to(bloom, { strength: 4.0, duration: 0.1, yoyo: true, repeat: 1, ease: "power2.inOut", onComplete: () => { bloom.strength = 1.5; } }); } } // Flash Core gsap.to(coreMat, { opacity: 1, duration: 0.1, yoyo: true, repeat: 3 }); }; window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); rendererLayer.setSize(window.innerWidth, window.innerHeight); if (composer) composer.setSize(window.innerWidth, window.innerHeight); }); init();