birth: Pulsing Reaction Nebula

This commit is contained in:
motd_admin 2026-03-27 17:47:23 +00:00
parent 3b3dac4d1a
commit 90c7d52f1f

261
index.html Normal file
View file

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neurameba Organism</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
font-family: 'Courier New', monospace;
color: #fff;
display: flex;
flex-direction: column;
height: 100vh;
}
canvas {
display: block;
flex: 1;
}
#attribution {
text-align: right;
padding: 10px;
font-size: 10px;
opacity: 0.5;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="attribution">neurameba · motd.social</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Parameters derived from the abstract specifications
const params = {
motion: 0.463,
density: 0.496,
complexity: 0.313,
connectedness: 0.434,
lifespan: 0.678,
pulse: { avg: 0.72, min: 0.30, max: 1.40 },
tone: { anger: 0.00, sadness: 0.00, curiosity: 0.30, dryness: 0.80, playfulness: 0.00, tension: 0.00 },
topology: {
survivingNodes: 7,
branchCount: 7,
loops: 25,
maxDepth: 8,
thicknessRatio: 1.25,
fractalDimension: 0.500,
finalEnergy: 28.8
}
};
// Reaction-diffusion system with adaptive parameters
const W = canvas.width;
const H = canvas.height;
// Initialize grids
let A = new Array(W * H).fill(0);
let B = new Array(W * H).fill(0);
let A_prev = new Array(W * H).fill(0);
let B_prev = new Array(W * H).fill(0);
// Reaction-diffusion parameters
const Da = 1.0 + params.motion * 0.5;
const Db = 0.5 + params.connectedness * 0.3;
const feed = 0.0545 + params.complexity * 0.02;
const kill = 0.062 + params.connectedness * 0.01;
const timeStep = 0.05;
const scale = 10 * (1 - params.dryness);
// Initialize with sparse pattern based on density
function initPattern() {
const sparsity = 1 - params.density;
for (let i = 0; i < W * H; i++) {
const x = i % W;
const y = Math.floor(i / W);
// Create sparse initial pattern with some clustering
const distToCenter = Math.sqrt(Math.pow(x - W/2, 2) + Math.pow(y - H/2, 2));
const centerFactor = 1 - Math.min(1, distToCenter / Math.max(W, H) * 2);
A_prev[i] = 1;
B_prev[i] = 0;
// Add some variation based on complexity
if (Math.random() > sparsity * (0.7 + params.complexity * 0.3)) {
A_prev[i] *= 1 - Math.random() * 0.3;
}
}
}
// Reaction-diffusion step
function step() {
// Swap buffers
[A_prev, A] = [A, A_prev];
[B_prev, B] = [B, B_prev];
const width = W;
const height = H;
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const i = x + y * width;
// Diffusion
A[i] = A_prev[i] +
(Da * laplacian(A_prev, x, y, width, height) -
A_prev[i] * B_prev[i] * B_prev[i] +
feed * (1 - A_prev[i])) * timeStep;
B[i] = B_prev[i] +
(Db * laplacian(B_prev, x, y, width, height) +
A_prev[i] * B_prev[i] * B_prev[i] -
(kill + feed) * B_prev[i]) * timeStep;
// Apply pulse variation
const pulseFactor = params.pulse.avg + (Math.random() * (params.pulse.max - params.pulse.min) - (params.pulse.max - params.pulse.min)/2);
A[i] *= pulseFactor;
B[i] *= pulseFactor;
// Apply energy decay
A[i] *= 1 - (params.topology.finalEnergy / 100) * timeStep;
B[i] *= 1 - (params.topology.finalEnergy / 100) * timeStep;
// Clamp values
A[i] = Math.max(0, Math.min(1, A[i]));
B[i] = Math.max(0, Math.min(1, B[i]));
}
}
}
function laplacian(grid, x, y, width, height) {
let sum = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
sum += grid[nx + ny * width];
} else {
sum += grid[x + y * width]; // Boundary condition
}
}
}
return sum / 8 - grid[x + y * width];
}
// Drawing with adaptive complexity and connectedness
function draw() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, W, H);
const cellSize = Math.max(1, Math.floor(2 * (1 - params.density) + 1));
const gap = 2;
// Draw reaction-diffusion pattern with varying thickness
for (let y = 0; y < H; y += cellSize + gap) {
for (let x = 0; x < W; x += cellSize + gap) {
const i = x + y * W;
if (i >= A.length) continue;
const a = A[i];
const b = B[i];
const combined = Math.min(1, (a + b) * params.topology.thicknessRatio);
if (combined > 0.1) {
const hue = (params.tone.curiosity * 180) + (params.tone.dryness * 360 * (1 - a - b));
const saturation = 50 + params.tone.dryness * 30;
const value = 80 + Math.min(20, combined * 50);
const alpha = combined * (0.5 + params.pulse.avg * 0.5);
ctx.fillStyle = `hsla(${hue}, ${saturation}%, ${value}%, ${alpha})`;
// Vary shape complexity based on parameter
const complexityFactor = params.complexity;
const shapeType = Math.floor(complexityFactor * 3);
switch(shapeType) {
case 0:
// Simple circles
ctx.beginPath();
ctx.arc(x, y, cellSize * combined * 2, 0, Math.PI * 2);
ctx.fill();
break;
case 1:
// Squares with rotation
ctx.save();
ctx.translate(x, y);
ctx.rotate(combined * Math.PI * 2);
ctx.fillRect(-cellSize * combined, -cellSize * combined,
cellSize * 2 * combined, cellSize * 2 * combined);
ctx.restore();
break;
case 2:
// Complex polygons
ctx.beginPath();
const sides = 3 + Math.floor(combined * 7);
for (let s = 0; s < sides; s++) {
const angle = (s / sides) * Math.PI * 2 + combined * Math.PI;
const px = x + Math.cos(angle) * cellSize * combined * 1.5;
const py = y + Math.sin(angle) * cellSize * combined * 1.5;
if (s === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fill();
break;
}
}
}
}
// Add some loops/connectedness patterns
if (params.topology.loops > 10) {
ctx.strokeStyle = `rgba(255, 255, 255, ${params.motion * 0.3})`;
ctx.lineWidth = 1;
for (let i = 0; i < params.topology.loops * 0.5; i++) {
ctx.beginPath();
const centerX = W/2 + Math.sin(Date.now() * 0.001 * params.motion) * W/3 * Math.sin(i * 0.2);
const centerY = H/2 + Math.cos(Date.now() * 0.0007 * params.motion) * H/3 * Math.cos(i * 0.15);
const radius = 50 + i * 5;
const segments = 5 + Math.floor(params.topology.maxDepth * 0.5);
for (let s = 0; s <= segments; s++) {
const angle = (s / segments) * Math.PI * 2 + Date.now() * 0.002 * params.motion * (i % 2 === 0 ? 1 : -1);
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
if (s === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
}
}
}
// Animation loop
function animate() {
step();
draw();
requestAnimationFrame(animate);
}
initPattern();
animate();
</script>
</body>
</html>