nn-core/game.js

374 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2026-09-10 09:22:28 +00:00
/* =========================================================
NUMEX AI Neural Network Core Visualizer
Canlı sinir ı animasyonu + içinden akan NUMEX token akışı
Saf Canvas harici bağımlılık yok.
========================================================= */
(() => {
"use strict";
/* ---------- Konfigürasyon ---------- */
const CONFIG = {
// Katman mimarisi (nöron sayıları)
layers: [6, 14, 20, 16, 10, 7, 4, 2, 1],
tokenText: "NUMEX",
tokenSpawn: 1.7, // sn'de üretilecek token sayısı
activationSpeed: 0.9, // aktivasyon yayılım hızı
colorHot: [0, 226, 255], // camgöbeği — aktivasyon
colorCool: [88, 101, 242],// indigo — ağırlıklar
colorText: [255, 220, 90] // amber — NUMEX akışı
};
/* ---------- DOM Bağlantıları ---------- */
const netCanvas = document.getElementById("net");
const chartCanvas = document.getElementById("lossChart");
const ctx = netCanvas.getContext("2d");
const cctx = chartCanvas.getContext("2d");
const epochEl = document.getElementById("epoch");
const ppsEl = document.getElementById("pps");
const gaugeFill = document.getElementById("gaugeFill");
const gaugeLabel = document.getElementById("gaugeLabel");
/* ---------- Durum ---------- */
let W = 0, H = 0;
let DPR = Math.min(window.devicePixelRatio || 1, 2);
let nodePositions = []; // her katman için nöron pozisyonları
let tokenParticles = []; // NUMEX harfleri
let pulseEvents = []; // sinir darbeleri
let connectionCache = []; // ön hesaplanmış bağlantı puanları
let epoch = 0;
let lastSpawn = 0;
let trainProgress = 0;
let chartData = new Array(52).fill(1.0);
const LETTERS = CONFIG.tokenText.split("");
const FONT_BASIS = 16;
/* ---------- Sayı yardımcıları ---------- */
const rnd = (a, b) => a + Math.random() * (b - a);
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
/* ---------- Boyutlandırma ---------- */
function resize() {
W = window.innerWidth;
H = window.innerHeight;
DPR = Math.min(window.devicePixelRatio || 1, 2);
netCanvas.width = W * DPR;
netCanvas.height = H * DPR;
netCanvas.style.width = W + "px";
netCanvas.style.height = H + "px";
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
buildNodes();
}
/* ---------- Nöron yerleşimi ---------- */
function buildNodes() {
nodePositions = [];
const marginX = W * 0.12;
const usableW = W - marginX * 2;
const usableH = H * 0.72;
const topY = H * 0.18;
const gapX = usableW / (CONFIG.layers.length - 1);
CONFIG.layers.forEach((count, li) => {
const col = [];
const x = marginX + gapX * li;
// Katman yüksekliği nöron sayısıyla orantılı (görsel coşku)
const maxH = Math.max(...CONFIG.layers.slice(-3));
const span = Math.max(usableH * (count / maxH), 40);
for (let ni = 0; ni < count; ni++) {
const t = count === 1 ? 0.5 : ni / (count - 1);
const y = topY + span / 2 + (t - 0.5) * span * 1.2 + rnd(-3, 3);
// Her nöronun kendine has "canlılık" fazı
col.push({
x, y,
radius: count <= 2 ? 5 : count <= 6 ? 4 : 3.5,
seed: Math.random() * Math.PI * 2,
weight: rnd(-1, 1)
});
}
nodePositions.push(col);
});
// Bağlantı önbelleği
connectionCache = [];
for (let li = 0; li < nodePositions.length - 1; li++) {
const left = nodePositions[li];
const right = nodePositions[li + 1];
for (const a of left) {
for (const b of right) {
connectionCache.push({
ax: a.x, ay: a.y, bx: b.x, by: b.y,
l: li,
w: Math.random() * 2 - 1
});
}
}
}
}
/* ---------- NUMEX token parçacığı oluştur ---
Bir nöronun üstünden süzülüp komşu katmana akar. */
function spawnToken() {
const li = Math.floor(Math.random() * (nodePositions.length - 2));
const col = nodePositions[li];
if (!col || !col.length) return;
const n = col[Math.floor(Math.random() * col.length)];
const target = nodePositions[li + 1][Math.floor(Math.random() * nodePositions[li + 1].length)];
const letter = LETTERS[Math.floor(Math.random() * LETTERS.length)];
tokenParticles.push({
x: n.x, y: n.y,
tx: target.x, ty: target.y,
t: 0,
speed: rnd(1.2, 2.2), // akış hızı
letter,
size: rnd(FONT_BASIS * 0.7, FONT_BASIS * 1.1),
phase: Math.random() * Math.PI * 2,
life: 1
});
pushPulse(n.x, n.y, 0.7);
}
function pushPulse(x, y, r) {
pulseEvents.push({ x, y, r, a: 1, t: 0 });
}
/* ---------- Aktivasyon darbesi ---------- */
function spawnActivation() {
const li = Math.floor(Math.random() * nodePositions.length);
const col = nodePositions[li];
if (!col || !col.length) return;
const n = col[Math.floor(Math.random() * col.length)];
pushPulse(n.x, n.y, 1.6);
}
/* ---------- Ana döngü ---------- */
let lastTime = performance.now();
function loop(now) {
const dt = Math.min((now - lastTime) / 1000, 0.05);
lastTime = now;
// Epoch simülasyonu
epoch += dt * 24;
epochEl.textContent = String(Math.floor(epoch)).padStart(4, "0");
const pps = Math.floor(rnd(18, 44) * 1000);
ppsEl.textContent = (pps / 1000).toFixed(1) + "K";
trainProgress = Math.min(1, trainProgress + dt * 0.012);
gaugeFill.style.width = (trainProgress * 100).toFixed(1) + "%";
gaugeLabel.textContent =
trainProgress >= 1 ? "Training complete ✓" : "Optimizing weights " + (trainProgress * 100).toFixed(1) + "%";
// Token üretimi
lastSpawn += dt;
while (lastSpawn > 1 / CONFIG.tokenSpawn) {
lastSpawn -= 1 / CONFIG.tokenSpawn;
spawnToken();
spawnActivation();
}
// Böcekleyici — aşırı birikmeyi önle
if (tokenParticles.length > 340) tokenParticles.splice(0, tokenParticles.length - 340);
updateTokens(dt);
updatePulses(dt);
updateChart(dt);
draw();
requestAnimationFrame(loop);
}
/* ---------- Token güncelleme ---------- */
function updateTokens(dt) {
for (let i = tokenParticles.length - 1; i >= 0; i--) {
const p = tokenParticles[i];
p.t += dt * p.speed;
// Bezier kavsi veren ara nokta (hafif bombe)
const mx = (p.x + p.tx) / 2;
const my = Math.min(p.y, p.ty) - rnd(8, 22);
p.cx = mx;
p.cy = my;
if (p.t >= 1) {
pushPulse(p.tx, p.ty, 1.1);
tokenParticles.splice(i, 1);
}
}
}
function updatePulses(dt) {
for (let i = pulseEvents.length - 1; i >= 0; i--) {
const e = pulseEvents[i];
e.t += dt * 1.6;
e.r += dt * 24;
e.a = Math.max(0, 1 - e.t);
if (e.a <= 0) pulseEvents.splice(i, 1);
}
}
function updateChart(dt) {
// Kayıp eğrisi monoton azalan + gürültü
const cur = chartData[chartData.length - 1];
const next = Math.max(0.08, cur - dt * 0.06 + rnd(-0.03, 0.03));
chartData.push(clamp(next, 0.08, 1));
chartData.shift();
}
/* ---------- Çizim ---------- */
function lerpColor(c1, c2, t) {
return [
Math.round(c1[0] + (c2[0] - c1[0]) * t),
Math.round(c1[1] + (c2[1] - c1[1]) * t),
Math.round(c1[2] + (c2[2] - c1[2]) * t),
];
}
function rgba(c, a) {
return `rgba(${c[0]},${c[1]},${c[2]},${a})`;
}
function draw() {
// — Arka plan degrade
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, "#05060f");
bg.addColorStop(0.5, "#070a1a");
bg.addColorStop(1, "#04050c");
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
// — İnce grid
ctx.strokeStyle = "rgba(88,101,242,0.05)";
ctx.lineWidth = 1;
const gs = 54;
ctx.beginPath();
for (let x = 0; x < W; x += gs) { ctx.moveTo(x, 0); ctx.lineTo(x, H); }
for (let y = 0; y < H; y += gs) { ctx.moveTo(0, y); ctx.lineTo(W, y); }
ctx.stroke();
// — Bağlantılar (renk=ağırlık)
for (const c of connectionCache) {
const alpha = 0.05 + Math.abs(c.w) * 0.16;
const col = c.w > 0 ? CONFIG.colorHot : CONFIG.colorCool;
ctx.strokeStyle = rgba(col, alpha);
ctx.lineWidth = Math.abs(c.w) * 1.6;
ctx.beginPath();
ctx.moveTo(c.ax, c.ay);
ctx.lineTo(c.bx, c.by);
ctx.stroke();
}
// — Nöronlar (parlama)
nodePositions.forEach((col) => {
for (const n of col) {
const pulse = (Math.sin(now_t() * 1.2 + n.seed) + 1) / 2;
const base = n.weight > 0 ? CONFIG.colorHot : CONFIG.colorCool;
const hitC = lerpColor(base, [255,255,255], pulse * 0.4);
const g = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.radius * 4);
g.addColorStop(0, rgba(hitC, 0.35 + pulse * 0.3));
g.addColorStop(1, rgba(hitC, 0));
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(n.x, n.y, n.radius * 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = rgba(base, 0.5 + pulse * 0.5);
ctx.beginPath();
ctx.arc(n.x, n.y, n.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = rgba(hitC, 0.8);
ctx.lineWidth = 1;
ctx.stroke();
}
});
// — Token akışı (NUMEX) ve aktivasyon darbeleri
drawPulses();
drawTokens();
// — Alt grafik çizimi
drawChart();
}
let _t = 0;
function now_t() { return _t; } // loop içinde _t güncellenir
function drawPulses() {
for (const e of pulseEvents) {
const col = lerpColor(CONFIG.colorHot, [255,255,255], e.a);
ctx.strokeStyle = rgba(col, e.a * 0.8);
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(e.x, e.y, e.r, 0, Math.PI * 2);
ctx.stroke();
const g = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, e.r);
g.addColorStop(0, rgba(col, e.a * 0.4));
g.addColorStop(1, rgba(col, 0));
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(e.x, e.y, e.r, 0, Math.PI * 2);
ctx.fill();
}
}
function drawTokens() {
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (const p of tokenParticles) {
// Bezier ara nokta
const tt = p.t;
const bx = (1 - tt) * (1 - tt) * p.x + 2 * (1 - tt) * tt * p.cx + tt * tt * p.tx;
const by = (1 - tt) * (1 - tt) * p.y + 2 * (1 - tt) * tt * p.cy + tt * tt * p.ty;
const fade = Math.sin(tt * Math.PI); // başta-sonda silik, ortada parlak
// Harfin arkasına hafif glow
ctx.shadowBlur = 14;
ctx.shadowColor = rgba(CONFIG.colorText, 0.9);
ctx.fillStyle = rgba(CONFIG.colorText, 0.35 + fade * 0.65);
ctx.font = `700 ${p.size}px 'Segoe UI', system-ui, sans-serif`;
ctx.fillText(p.letter, bx, by);
ctx.shadowBlur = 0;
// İz bırakma (çizgi)
ctx.strokeStyle = rgba(CONFIG.colorText, fade * 0.18);
ctx.lineWidth = 1.4;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(bx, by);
ctx.stroke();
}
}
function drawChart() {
cctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
cctx.strokeStyle = "rgba(255,255,255,0.06)";
cctx.lineWidth = 1;
for (let i = 0; i <= 2; i++) {
const y = (chartCanvas.height / 2) * i;
cctx.beginPath();
cctx.moveTo(0, y);
cctx.lineTo(chartCanvas.width, y);
cctx.stroke();
}
cctx.beginPath();
chartData.forEach((v, i) => {
const x = (i / (chartData.length - 1)) * chartCanvas.width;
const y = (1 - v) * chartCanvas.height;
i === 0 ? cctx.moveTo(x, y) : cctx.lineTo(x, y);
});
cctx.strokeStyle = rgba(CONFIG.colorHot, 0.9);
cctx.lineWidth = 2;
cctx.stroke();
}
/* ---------- Başlangıç ---------- */
window.addEventListener("resize", resize);
resize();
// Gerçek zaman _t güncellemesi için loop'a entegre
const _origLoop = loop;
function finalLoop(now) {
_t = now / 1000;
_origLoop(now);
}
requestAnimationFrame(finalLoop);
})();