nn-core/game.js

374 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =========================================================
NUMEX AI — Neural Network Core Visualizer
Canlı sinir ağı 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);
})();