Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt

Use this file to discover all available pages before exploring further.

This example demonstrates how to create procedurally generated animations that are fully deterministic and reproducible using Helios utility functions.

Overview

The procedural generation example shows:
  • Deterministic random number generation
  • Frame-based procedural animations
  • Color interpolation over time
  • Grid-based generative patterns

Complete implementation

composition.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Procedural Generation Example</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        background-color: black;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas></canvas>
    <script type="module" src="./src/main.ts"></script>
  </body>
</html>
main.ts
import { Helios, random, interpolateColors } from '@helios-project/core';

// Init Helios
const helios = new Helios({ fps: 30, duration: 10 });
helios.bindToDocumentTimeline();

const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;

// Grid Config
const COLS = 10;
const ROWS = 10;

// Resize handler
function resize() {
    if (!canvas) return;
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    helios.setSize(canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();

helios.subscribe((state) => {
  const frame = state.currentFrame;
  const { width, height } = canvas;

  // Background Color Cycle
  const bg = interpolateColors(
    frame,
    [0, 300],
    ['#1a1a2e', '#16213e'],
    { extrapolateRight: 'clamp' }
  );
  ctx.fillStyle = bg;
  ctx.fillRect(0, 0, width, height);

  const cellW = width / COLS;
  const cellH = height / ROWS;

  // Draw Grid
  for (let i = 0; i < COLS * ROWS; i++) {
     const col = i % COLS;
     const row = Math.floor(i / COLS);

     // Deterministic properties based on index
     const seed = i;
     const baseSize = random(seed) * (Math.min(cellW, cellH) * 0.5);
     const speed = random(seed + 1000) * 0.2;
     const offset = random(seed + 2000) * Math.PI * 2;
     const colorSeed = random(seed + 3000);

     // Animate scale using sine wave based on frame
     const currentScale = 0.5 + 0.5 * Math.sin(frame * speed + offset);

     const x = col * cellW + cellW/2;
     const y = row * cellH + cellH/2;

     // Color based on position or random
     ctx.fillStyle = colorSeed > 0.5 ? 'tomato' : 'teal';

     // Draw
     ctx.beginPath();
     ctx.arc(x, y, baseSize * currentScale, 0, Math.PI * 2);
     ctx.fill();
  }
});

// Expose helios for verification
(window as any).helios = helios;

Key patterns

Deterministic random generation

The random() function from Helios core provides deterministic pseudo-random numbers based on a seed:
import { random } from '@helios-project/core';

// Same seed always returns same value
const value1 = random(42);  // Always returns same number
const value2 = random(42);  // Identical to value1

// Different seeds return different values
const value3 = random(43);  // Different from value1
This ensures that procedural animations are reproducible and render identically every time.

Seed-based property generation

Generate unique but deterministic properties for each element:
for (let i = 0; i < COLS * ROWS; i++) {
    const seed = i;
    const baseSize = random(seed) * (Math.min(cellW, cellH) * 0.5);
    const speed = random(seed + 1000) * 0.2;
    const offset = random(seed + 2000) * Math.PI * 2;
    const colorSeed = random(seed + 3000);
}
By using different seed offsets (1000, 2000, 3000), each property gets independent randomness while remaining deterministic.

Color interpolation

Use interpolateColors() for smooth color transitions:
import { interpolateColors } from '@helios-project/core';

const bg = interpolateColors(
    frame,
    [0, 300],
    ['#1a1a2e', '#16213e'],
    { extrapolateRight: 'clamp' }
);
This interpolates between colors based on frame number with clamping at the end.

Frame-based animation

Combine random seeds with frame-based timing for dynamic animations:
const speed = random(seed + 1000) * 0.2;
const offset = random(seed + 2000) * Math.PI * 2;
const currentScale = 0.5 + 0.5 * Math.sin(frame * speed + offset);
Each element animates at its own deterministic speed and phase offset.

Performance tips

Cache random values

For large grids, cache random values instead of recalculating:
// Pre-generate random properties
const elements = Array.from({ length: COLS * ROWS }, (_, i) => ({
    seed: i,
    baseSize: random(i) * maxSize,
    speed: random(i + 1000) * 0.2,
    offset: random(i + 2000) * Math.PI * 2,
    color: random(i + 3000) > 0.5 ? 'tomato' : 'teal'
}));

// Use cached values in draw loop
helios.subscribe((state) => {
    elements.forEach(elem => {
        const scale = 0.5 + 0.5 * Math.sin(state.currentFrame * elem.speed + elem.offset);
        // Draw with elem.baseSize * scale
    });
});

Optimize grid rendering

Skip rendering elements outside the viewport:
for (let i = 0; i < COLS * ROWS; i++) {
    const x = col * cellW + cellW/2;
    const y = row * cellH + cellH/2;
    
    // Skip if outside viewport
    if (x < -margin || x > width + margin || 
        y < -margin || y > height + margin) {
        continue;
    }
    
    // Render element
}

Use integer calculations

Where possible, use integer math for better performance:
// Good - integer operations
const col = i % COLS;
const row = Math.floor(i / COLS);

// Avoid - floating point when unnecessary
const col = Math.floor(i / COLS) * COLS;

Batch canvas operations

Group similar drawing operations to reduce state changes:
// Draw all circles of one color
ctx.fillStyle = 'tomato';
elementsA.forEach(elem => {
    ctx.beginPath();
    ctx.arc(elem.x, elem.y, elem.radius, 0, Math.PI * 2);
    ctx.fill();
});

// Then draw all circles of another color
ctx.fillStyle = 'teal';
elementsB.forEach(elem => {
    ctx.beginPath();
    ctx.arc(elem.x, elem.y, elem.radius, 0, Math.PI * 2);
    ctx.fill();
});

Advanced techniques

Noise-based generation

Implement Perlin or simplex noise for organic patterns:
import { createNoise2D } from 'simplex-noise';
import { random } from '@helios-project/core';

// Create seeded noise function
const noise2D = createNoise2D(() => random(12345));

helios.subscribe((state) => {
    for (let x = 0; x < width; x += 10) {
        for (let y = 0; y < height; y += 10) {
            const noiseValue = noise2D(x * 0.01, y * 0.01 + state.currentTime * 0.1);
            const brightness = (noiseValue + 1) * 127.5;
            ctx.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`;
            ctx.fillRect(x, y, 10, 10);
        }
    }
});

Particle systems

Create deterministic particle systems:
const PARTICLE_COUNT = 1000;
const particles = Array.from({ length: PARTICLE_COUNT }, (_, i) => ({
    x: random(i) * width,
    y: random(i + 10000) * height,
    vx: (random(i + 20000) - 0.5) * 2,
    vy: (random(i + 30000) - 0.5) * 2,
    radius: random(i + 40000) * 5 + 2,
    color: `hsl(${random(i + 50000) * 360}, 70%, 60%)`
}));

helios.subscribe((state) => {
    const dt = 1 / state.fps;
    
    particles.forEach(p => {
        // Update position based on velocity and time
        p.x += p.vx * dt * state.currentFrame;
        p.y += p.vy * dt * state.currentFrame;
        
        // Wrap around screen
        p.x = ((p.x % width) + width) % width;
        p.y = ((p.y % height) + height) % height;
        
        // Draw
        ctx.fillStyle = p.color;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
        ctx.fill();
    });
});

L-System generation

Generate fractal patterns using L-Systems:
function generateLSystem(axiom: string, rules: Record<string, string>, iterations: number): string {
    let result = axiom;
    for (let i = 0; i < iterations; i++) {
        result = result.split('').map(char => rules[char] || char).join('');
    }
    return result;
}

const rules = { 'F': 'F+F-F-F+F' };
const pattern = generateLSystem('F', rules, 3);

// Render L-System with turtle graphics
function drawLSystem(instructions: string, frame: number) {
    let x = width / 2, y = height / 2;
    let angle = 0;
    const step = 10;
    const angleStep = Math.PI / 2;
    
    // Animate drawing based on frame
    const progress = Math.min(frame / 300, 1);
    const visibleLength = Math.floor(instructions.length * progress);
    
    for (let i = 0; i < visibleLength; i++) {
        const cmd = instructions[i];
        if (cmd === 'F') {
            const newX = x + Math.cos(angle) * step;
            const newY = y + Math.sin(angle) * step;
            ctx.beginPath();
            ctx.moveTo(x, y);
            ctx.lineTo(newX, newY);
            ctx.stroke();
            x = newX;
            y = newY;
        } else if (cmd === '+') {
            angle += angleStep;
        } else if (cmd === '-') {
            angle -= angleStep;
        }
    }
}

Multi-layer composition

Combine multiple procedural layers:
helios.subscribe((state) => {
    const frame = state.currentFrame;
    
    // Layer 1: Background gradient
    const gradient = ctx.createLinearGradient(0, 0, width, height);
    gradient.addColorStop(0, interpolateColors(frame, [0, 300], ['#000428', '#004e92']));
    gradient.addColorStop(1, interpolateColors(frame, [0, 300], ['#004e92', '#000428']));
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, width, height);
    
    // Layer 2: Animated grid
    drawProceduralGrid(frame);
    
    // Layer 3: Particle overlay
    drawParticles(frame);
    
    // Layer 4: Noise texture
    drawNoiseOverlay(frame, 0.1);
});

Build docs developers (and LLMs) love