Skip to content

argos-visualization.js #2

@natefrog808

Description

@natefrog808

/**

  • ArgOS Visualization Module
  • Provides a visualization layer for the reality-bending agent simulation
    */

import {
Position,
Environmental,
SensoryData,
Memory,
Goals,
Actions,
CognitiveState,
RealityFlux,
Communication,
Learning,
Social
} from './argos-framework.js';

export class ArgOSVisualizer {
/**

  • Create a new visualizer for ArgOS simulation
  • @param {string} canvasId - ID of the canvas element to render to
  • @param {object} world - BitECS world object
  • @param {number} pixelsPerUnit - Scale factor for rendering (pixels per world unit)
    */
    constructor(canvasId, world, pixelsPerUnit = 5) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext('2d');
    this.world = world;
    this.pixelsPerUnit = pixelsPerUnit;
    this.boundarySize = 100;
// Set canvas size based on boundary
this.canvas.width = this.boundarySize * this.pixelsPerUnit;
this.canvas.height = this.boundarySize * this.pixelsPerUnit;

// Track selected entity for detailed inspection
this.selectedEntity = null;

// Animation frame request id
this.animationFrameId = null;

// Set up event listeners
this.setupEventListeners();

// Colors for different entity types
this.colors = {
  agent: '#3498db',        // Blue
  resource: '#2ecc71',     // Green
  obstacle: '#7f8c8d',     // Gray
  hazard: '#e74c3c',       // Red
  selected: '#f39c12',     // Orange
  perception: 'rgba(52, 152, 219, 0.1)',  // Light blue for perception radius
  memory: 'rgba(155, 89, 182, 0.3)',      // Purple for memory
  communication: 'rgba(241, 196, 15, 0.3)', // Yellow for communication
  realityFlux: 'rgba(155, 89, 182, 0.7)'   // Vibrant purple for reality flux
};

}

/**

  • Set up mouse event listeners for entity selection
    */
    setupEventListeners() {
    this.canvas.addEventListener('click', (event) => {
    const rect = this.canvas.getBoundingClientRect();
    const x = (event.clientX - rect.left) / this.pixelsPerUnit;
    const y = (event.clientY - rect.top) / this.pixelsPerUnit;

    // Find closest entity
    let closestEntity = null;
    let closestDistance = Infinity;

    // Check all entities with positions
    for (let i = 0; i < this.world.entities.length; i++) {
    const entity = i;

    // Skip if doesn't have position
    if (!Position[entity]) continue;

    const entityX = Position.x[entity];
    const entityY = Position.y[entity];

    const dx = entityX - x;
    const dy = entityY - y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    // Check if this is closer than current closest
    if (distance < closestDistance && distance < 3) { // Within 3 units
    closestEntity = entity;
    closestDistance = distance;
    }
    }

    this.selectedEntity = closestEntity;
    });
    }

/**

  • Start rendering the simulation
    */
    start() {
    this.render();
    }

/**

  • Stop rendering the simulation
    */
    stop() {
    if (this.animationFrameId) {
    cancelAnimationFrame(this.animationFrameId);
    this.animationFrameId = null;
    }
    }

/**

  • Render a single frame of the simulation
    */
    render() {
    // Clear canvas
    this.ctx.fillStyle = '#ecf0f1'; // Light background
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Draw reality flux effects
this.drawRealityFluxEffects();

// Draw all entities
this.drawEnvironmentalEntities();
this.drawAgents();

// Draw UI elements
this.drawSelectedEntityDetails();
this.drawSimulationInfo();

// Request next frame
this.animationFrameId = requestAnimationFrame(() => this.render());

}

/**

  • Draw reality-bending visual effects
    */
    drawRealityFluxEffects() {
    // Draw a subtle grid pattern that distorts near reality flux events
    this.ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
    this.ctx.lineWidth = 1;
const gridSize = 10; // Grid spacing in world units

// Find entities with active reality flux
const activeFluxEntities = [];
for (let i = 0; i < this.world.entities.length; i++) {
  if (RealityFlux[i] && RealityFlux.effectType[i] !== 0) {
    activeFluxEntities.push({
      x: Position.x[i],
      y: Position.y[i],
      effect: RealityFlux.effectType[i],
      duration: RealityFlux.duration[i]
    });
  }
}

// Draw reality wave effect if active
if (this.world.realityWave && this.world.realityWave.active) {
  const wave = this.world.realityWave;
  
  // Draw wave front
  if (wave.direction === 'horizontal') {
    const waveX = wave.x * this.pixelsPerUnit;
    // Create a gradient for the wave
    const gradient = this.ctx.createLinearGradient(
      waveX - 15, 0, 
      waveX + 15, 0
    );
    gradient.addColorStop(0, 'rgba(155, 89, 182, 0)');
    gradient.addColorStop(0.5, 'rgba(155, 89, 182, 0.5)');
    gradient.addColorStop(1, 'rgba(155, 89, 182, 0)');
    
    this.ctx.fillStyle = gradient;
    this.ctx.fillRect(waveX - 15, 0, 30, this.canvas.height);
    
    // Add oscillating pattern to wave
    this.ctx.strokeStyle = 'rgba(155, 89, 182, 0.7)';
    this.ctx.beginPath();
    for (let y = 0; y < this.canvas.height; y += 10) {
      const offset = Math.sin(y * wave.frequency + this.world.time * 0.1) * wave.amplitude;
      if (y === 0) {
        this.ctx.moveTo(waveX + offset, y);
      } else {
        this.ctx.lineTo(waveX + offset, y);
      }
    }
    this.ctx.stroke();
  } else {
    const waveY = wave.y * this.pixelsPerUnit;
    const gradient = this.ctx.createLinearGradient(
      0, waveY - 15,
      0, waveY + 15
    );
    gradient.addColorStop(0, 'rgba(155, 89, 182, 0)');
    gradient.addColorStop(0.5, 'rgba(155, 89, 182, 0.5)');
    gradient.addColorStop(1, 'rgba(155, 89, 182, 0)');
    
    this.ctx.fillStyle = gradient;
    this.ctx.fillRect(0, waveY - 15, this.canvas.width, 30);
    
    // Add oscillating pattern to wave
    this.ctx.strokeStyle = 'rgba(155, 89, 182, 0.7)';
    this.ctx.beginPath();
    for (let x = 0; x < this.canvas.width; x += 10) {
      const offset = Math.sin(x * wave.frequency + this.world.time * 0.1) * wave.amplitude;
      if (x === 0) {
        this.ctx.moveTo(x, waveY + offset);
      } else {
        this.ctx.lineTo(x, waveY + offset);
      }
    }
    this.ctx.stroke();
  }
  
  // Draw wave particles
  if (wave.particles) {
    this.ctx.fillStyle = 'rgba(155, 89, 182, 0.7)';
    for (let i = 0; i < wave.particles.length; i++) {
      const particle = wave.particles[i];
      const size = particle.size * (0.5 + 0.5 * Math.sin(this.world.time * 0.2 + i * 0.5));
      this.ctx.beginPath();
      this.ctx.arc(
        particle.x * this.pixelsPerUnit, 
        particle.y * this.pixelsPerUnit, 
        size * this.pixelsPerUnit,
        0, 2 * Math.PI
      );
      this.ctx.fill();
    }
  }
}

// Draw vertical grid lines with distortion
for (let x = 0; x < this.boundarySize; x += gridSize) {
  this.ctx.beginPath();
  
  for (let y = 0; y < this.boundarySize; y += 1) {
    let distortedX = x;
    
    // Apply distortion from nearby flux entities
    for (const flux of activeFluxEntities) {
      const dx = x - flux.x;
      const dy = y - flux.y;
      const distance = Math.sqrt(dx*dx + dy*dy);
      
      if (distance < 20) { // Effect radius
        const strength = (1 - distance/20) * 3 * Math.sin(this.world.time/10);
        distortedX += strength * Math.sin(y/5);
      }
    }
    
    if (y === 0) {
      this.ctx.moveTo(distortedX * this.pixelsPerUnit, y * this.pixelsPerUnit);
    } else {
      this.ctx.lineTo(distortedX * this.pixelsPerUnit, y * this.pixelsPerUnit);
    }
  }
  
  this.ctx.stroke();
}

// Draw horizontal grid lines with distortion
for (let y = 0; y < this.boundarySize; y += gridSize) {
  this.ctx.beginPath();
  
  for (let x = 0; x < this.boundarySize; x += 1) {
    let distortedY = y;
    
    // Apply distortion from nearby flux entities
    for (const flux of activeFluxEntities) {
      const dx = x - flux.x;
      const dy = y - flux.y;
      const distance = Math.sqrt(dx*dx + dy*dy);
      
      if (distance < 20) { // Effect radius
        const strength = (1 - distance/20) * 3 * Math.sin(this.world.time/10);
        distortedY += strength * Math.sin(x/5);
      }
    }
    
    if (x === 0) {
      this.ctx.moveTo(x * this.pixelsPerUnit, distortedY * this.pixelsPerUnit);
    } else {
      this.ctx.lineTo(x * this.pixelsPerUnit, distortedY * this.pixelsPerUnit);
    }
  }
  
  this.ctx.stroke();
}

// Draw reality flux auras
for (const flux of activeFluxEntities) {
  const effectRadius = 15;
  const flickerIntensity = 0.7 + 0.3 * Math.sin(this.world.time/5);
  
  this.ctx.beginPath();
  this.ctx.arc(
    flux.x * this.pixelsPerUnit,
    flux.y * this.pixelsPerUnit,
    effectRadius * this.pixelsPerUnit * flickerIntensity,
    0, 2 * Math.PI
  );
  
  // Different colors for different effects
  let fluxColor;
  switch (flux.effect) {
    case 1: // Teleport
      fluxColor = 'rgba(155, 89, 182, ' + flickerIntensity/3 + ')';
      break;
    case 2: // Phase
      fluxColor = 'rgba(41, 128, 185, ' + flickerIntensity/3 + ')';
      break;
    case 3: // Transform
      fluxColor = 'rgba(230, 126, 34, ' + flickerIntensity/3 + ')';
      break;
  }
  
  this.ctx.fillStyle = fluxColor;
  this.ctx.fill();
}

}

/**

  • Draw all environmental entities (resources, obstacles, hazards)
    */
    drawEnvironmentalEntities() {
    for (let i = 0; i < this.world.entities.length; i++) {
    // Skip if not an environmental entity
    if (!Environmental[i]) continue;

    const x = Position.x[i] * this.pixelsPerUnit;
    const y = Position.y[i] * this.pixelsPerUnit;
    const type = Environmental.type[i];
    const isSelected = i === this.selectedEntity;

    // Determine color based on type
    let color;
    switch (type) {
    case 0: // Resource
    color = this.colors.resource;
    break;
    case 1: // Obstacle
    color = this.colors.obstacle;
    break;
    case 2: // Hazard
    color = this.colors.hazard;
    break;
    default:
    color = 'black';
    }

    // Draw different shapes based on type
    this.ctx.fillStyle = color;
    this.ctx.strokeStyle = isSelected ? this.colors.selected : color;
    this.ctx.lineWidth = isSelected ? 2 : 1;

    // Check for reality flux phasing effect
    const isPhasing = RealityFlux[i] && RealityFlux.effectType[i] === 2;
    if (isPhasing) {
    this.ctx.globalAlpha = 0.3 + 0.2 * Math.sin(this.world.time/5);
    }

    switch (type) {
    case 0: // Resource (diamond)
    const size = 1.5 * this.pixelsPerUnit;
    this.ctx.beginPath();
    this.ctx.moveTo(x, y - size);
    this.ctx.lineTo(x + size, y);
    this.ctx.lineTo(x, y + size);
    this.ctx.lineTo(x - size, y);
    this.ctx.closePath();
    this.ctx.fill();
    if (isSelected) this.ctx.stroke();
    break;

    case 1: // Obstacle (square)
    const obstacleSize = 2 * this.pixelsPerUnit;
    this.ctx.fillRect(x - obstacleSize/2, y - obstacleSize/2, obstacleSize, obstacleSize);
    if (isSelected) {
    this.ctx.strokeRect(x - obstacleSize/2, y - obstacleSize/2, obstacleSize, obstacleSize);
    }
    break;

    case 2: // Hazard (triangle)
    const radius = 2 * this.pixelsPerUnit;
    this.ctx.beginPath();
    this.ctx.moveTo(x, y - radius);
    this.ctx.lineTo(x + radius * 0.866, y + radius * 0.5);
    this.ctx.lineTo(x - radius * 0.866, y + radius * 0.5);
    this.ctx.closePath();
    this.ctx.fill();
    if (isSelected) this.ctx.stroke();
    break;
    }

    // Reset opacity if it was changed
    if (isPhasing) {
    this.ctx.globalAlpha = 1.0;
    }
    }
    }

/**

  • Draw all agents
    */
    drawAgents() {
    for (let i = 0; i < this.world.entities.length; i++) {
    // Skip if not an agent (has cognitive components)
    if (!SensoryData[i] || !Memory[i] || !Goals[i]) continue;

    const x = Position.x[i] * this.pixelsPerUnit;
    const y = Position.y[i] * this.pixelsPerUnit;
    const isSelected = i === this.selectedEntity;

    // Draw perception radius if this is the selected agent
    if (isSelected) {
    const perceptionRadius = SensoryData.radius[i] * this.pixelsPerUnit;
    this.ctx.beginPath();
    this.ctx.arc(x, y, perceptionRadius, 0, 2 * Math.PI);
    this.ctx.fillStyle = this.colors.perception;
    this.ctx.fill();
    }

    // Draw agent group indicator (small ring around agent)
    if (Social && Social[i]) {
    const groupId = Social.groupId[i];
    let groupColor;

    // Different colors for different groups
    switch(groupId % 3) {
    case 0: groupColor = 'rgba(231, 76, 60, 0.3)'; break; // Red
    case 1: groupColor = 'rgba(46, 204, 113, 0.3)'; break; // Green
    case 2: groupColor = 'rgba(52, 152, 219, 0.3)'; break; // Blue
    }

    this.ctx.beginPath();
    this.ctx.arc(x, y, 2.2 * this.pixelsPerUnit, 0, 2 * Math.PI);
    this.ctx.fillStyle = groupColor;
    this.ctx.fill();
    }

    // Draw agent body (circle)
    this.ctx.beginPath();
    this.ctx.arc(x, y, 1.5 * this.pixelsPerUnit, 0, 2 * Math.PI);
    this.ctx.fillStyle = this.colors.agent;
    this.ctx.fill();

    if (isSelected) {
    this.ctx.strokeStyle = this.colors.selected;
    this.ctx.lineWidth = 2;
    this.ctx.stroke();
    }

    // Draw direction indicator (where the agent is headed)
    if (Goals[i]) {
    const targetX = Goals.targetX[i] * this.pixelsPerUnit;
    const targetY = Goals.targetY[i] * this.pixelsPerUnit;

    // Only draw if target is not the current position
    const dx = targetX - x;
    const dy = targetY - y;
    if (dxdx + dydy > 1) {
    // Normalize direction vector
    const length = Math.sqrt(dxdx + dydy);
    const dirX = dx / length;
    const dirY = dy / length;

     // Draw line indicating direction
     this.ctx.beginPath();
     this.ctx.moveTo(x, y);
     this.ctx.lineTo(x + dirX * 2 * this.pixelsPerUnit, y + dirY * 2 * this.pixelsPerUnit);
     this.ctx.strokeStyle = isSelected ? this.colors.selected : 'rgba(52, 152, 219, 0.7)';
     this.ctx.lineWidth = 1;
     this.ctx.stroke();
    

    }
    }

    // Draw social connections (allies and rivals)
    if (Social && Social[i] && isSelected) {
    // Draw lines to allies
    for (let j = 0; j < 5; j++) {
    const allyId = Social.allies[i * 5 + j];
    if (allyId === 0) continue;

     // Skip if ally doesn't have position
     if (!Position[allyId]) continue;
     
     const allyX = Position.x[allyId] * this.pixelsPerUnit;
     const allyY = Position.y[allyId] * this.pixelsPerUnit;
     
     // Draw a green dashed line to ally
     this.ctx.beginPath();
     this.ctx.moveTo(x, y);
     this.ctx.lineTo(allyX, allyY);
     this.ctx.strokeStyle = 'rgba(46, 204, 113, 0.6)';
     this.ctx.lineWidth = 1;
     this.ctx.setLineDash([5, 3]);
     this.ctx.stroke();
     this.ctx.setLineDash([]);
    

    }

    // Draw lines to rivals
    for (let j = 0; j < 5; j++) {
    const rivalId = Social.rivals[i * 5 + j];
    if (rivalId === 0) continue;

     // Skip if rival doesn't have position
     if (!Position[rivalId]) continue;
     
     const rivalX = Position.x[rivalId] * this.pixelsPerUnit;
     const rivalY = Position.y[rivalId] * this.pixelsPerUnit;
     
     // Draw a red dashed line to rival
     this.ctx.beginPath();
     this.ctx.moveTo(x, y);
     this.ctx.lineTo(rivalX, rivalY);
     this.ctx.strokeStyle = 'rgba(231, 76, 60, 0.6)';
     this.ctx.lineWidth = 1;
     this.ctx.setLineDash([2, 3]);
     this.ctx.stroke();
     this.ctx.setLineDash([]);
    

    }
    }

    // Draw communication indicator
    if (Communication[i] && Communication.sending[i] === 1) {
    const commRadius = 3 * this.pixelsPerUnit;
    this.ctx.beginPath();
    this.ctx.arc(x, y, commRadius, 0, 2 * Math.PI);
    this.ctx.strokeStyle = this.colors.communication;
    this.ctx.lineWidth = 2;
    this.ctx.stroke();

    // Draw animated communication rings for effect
    const pulseSize = (1 + 0.3 * Math.sin(this.world.time * 0.2)) * commRadius;
    this.ctx.beginPath();
    this.ctx.arc(x, y, pulseSize, 0, 2 * Math.PI);
    this.ctx.strokeStyle = 'rgba(241, 196, 15, 0.2)';
    this.ctx.stroke();
    }

    // Show state labels for learning agents
    if (Learning && Learning[i]) {
    // Draw small indicator of learning state
    const stateColors = [
    'rgba(26, 188, 156, 0.7)', // Teal
    'rgba(241, 196, 15, 0.7)', // Yellow
    'rgba(231, 76, 60, 0.7)' // Red
    ];

    const state = Learning.lastState[i];
    const colorIndex = Math.min(2, Math.floor(state / 4));

    this.ctx.fillStyle = stateColors[colorIndex];
    this.ctx.beginPath();
    this.ctx.arc(x, y - 2.5 * this.pixelsPerUnit, 0.7 * this.pixelsPerUnit, 0, 2 * Math.PI);
    this.ctx.fill();
    }
    }
    }

/**

  • Draw details about the selected entity
    */
    drawSelectedEntityDetails() {
    if (this.selectedEntity === null) return;
const entity = this.selectedEntity;
const margin = 10;
const lineHeight = 20;
let yPos = margin;

// Set up text style
this.ctx.font = '14px Arial';
this.ctx.fillStyle = '#333';
this.ctx.textBaseline = 'top';

// Basic entity info
this.ctx.fillText(`Entity ID: ${entity}`, margin, yPos);
yPos += lineHeight;

// Position
if (Position[entity]) {
  const x = Position.x[entity].toFixed(2);
  const y = Position.y[entity].toFixed(2);
  this.ctx.fillText(`Position: (${x}, ${y})`, margin, yPos);
  yPos += lineHeight;
}

// Environmental entity info
if (Environmental[entity]) {
  const types = ['Resource', 'Obstacle', 'Hazard'];
  const type = types[Environmental.type[entity]] || 'Unknown';
  const value = Environmental.value[entity];
  
  this.ctx.fillText(`Type: ${type}`, margin, yPos);
  yPos += lineHeight;
  this.ctx.fillText(`Value: ${value}`, margin, yPos);
  yPos += lineHeight;
}

// Agent cognitive info
if (SensoryData[entity] && Memory[entity] && Goals[entity]) {
  this.ctx.fillText('Cognitive Agent:', margin, yPos);
  yPos += lineHeight;
  
  // Goal info
  const goalTypes = ['Explore', 'Collect', 'Avoid', 'Communicate'];
  const goalType = goalTypes[Goals.primaryType[entity]] || 'Unknown';
  this.ctx.fillText(`Current Goal: ${goalType} (Priority: ${Goals.priority[entity]})`, margin, yPos);
  yPos += lineHeight;
  
  // Memory info
  const memories = Memory.capacity[entity];
  this.ctx.fillText(`Memory Capacity: ${memories}`, margin, yPos);
  yPos += lineHeight;
  
  // Learning info
  if (Learning && Learning[entity]) {
    this.ctx.fillText('Learning:', margin, yPos);
    yPos += lineHeight;
    
    const learningRate = Learning.learningRate[entity].toFixed(2);
    const exploration = Learning.explorationRate[entity].toFixed(2);
    const rewardAccum = Learning.rewardAccumulator[entity].toFixed(1);
    
    this.ctx.fillText(`Learning Rate: ${learningRate}`, margin + 10, yPos);
    yPos += lineHeight;
    this.ctx.fillText(`Exploration Rate: ${exploration}`, margin + 10, yPos);
    yPos += lineHeight;
    this.ctx.fillText(`Total Reward: ${rewardAccum}`, margin + 10, yPos);
    yPos += lineHeight;
    
    // Show top actions by Q-value for current state
    const state = Learning.lastState[entity];
    this.ctx.fillText(`Current State: ${state}`, margin + 10, yPos);
    yPos += lineHeight;
    
    // Find best action for current state
    let bestAction = 0;
    let bestQValue = Number.NEGATIVE_INFINITY;
    
    for (let a = 0; a < 4; a++) {
      const qValue = Learning.qValues[entity * 40 + state * 4 + a];
      if (qValue > bestQValue) {
        bestQValue = qValue;
        bestAction = a;
      }
    }
    
    const actionNames = ['Explore', 'Collect', 'Avoid', 'Communicate'];
    this.ctx.fillText(`Best Action: ${actionNames[bestAction]} (Q: ${bestQValue.toFixed(1)})`, margin + 10, yPos);
    yPos += lineHeight;
  }
  
  // Social info
  if (Social && Social[entity]) {
    this.ctx.fillText('Social Dynamics:', margin, yPos);
    yPos += lineHeight;
    
    const trustLevel = Social.trustLevel[entity];
    const groupId = Social.groupId[entity];
    const cooperationCount = Social.cooperationCount[entity];
    
    const groupNames = ['Red Group', 'Green Group', 'Blue Group'];
    this.ctx.fillText(`Group: ${groupNames[groupId % 3]}`, margin + 10, yPos);
    yPos += lineHeight;
    this.ctx.fillText(`Trust Level: ${trustLevel}`, margin + 10, yPos);
    yPos += lineHeight;
    this.ctx.fillText(`Cooperation Count: ${cooperationCount}`, margin + 10, yPos);
    yPos += lineHeight;
    
    // Count allies and rivals
    let allyCount = 0;
    let rivalCount = 0;
    for (let j = 0; j < 5; j++) {
      if (Social.allies[entity * 5 + j] !== 0) allyCount++;
      if (Social.rivals[entity * 5 + j] !== 0) rivalCount++;
    }
    
    this.ctx.fillText(`Allies: ${allyCount}, Rivals: ${rivalCount}`, margin + 10, yPos);
    yPos += lineHeight;
  }
  
  // Emotional state
  if (CognitiveState[entity]) {
    let emotionalState = 'Neutral';
    const emotional = CognitiveState.emotionalState[entity];
    
    if (emotional < 30) emotionalState = 'Cautious';
    else if (emotional > 70) emotionalState = 'Bold';
    
    this.ctx.fillText(`Emotional State: ${emotionalState} (${emotional}/100)`, margin, yPos);
    yPos += lineHeight;
    
    const adaptability = CognitiveState.adaptability[entity];
    this.ctx.fillText(`Adaptability: ${adaptability}/100`, margin, yPos);
    yPos += lineHeight;
  }
}

// Reality flux info
if (RealityFlux[entity]) {
  const fluxEffects = ['None', 'Teleport', 'Phase', 'Transform'];
  const effectType = fluxEffects[RealityFlux.effectType[entity]] || 'Unknown';
  const duration = RealityFlux.duration[entity];
  const stability = RealityFlux.stability[entity];
  
  if (RealityFlux.effectType[entity] > 0) {
    this.ctx.fillStyle = '#e74c3c';  // Red for active effects
    this.ctx.fillText(`Reality Effect: ${effectType} (${duration} ticks)`, margin, yPos);
  } else {
    this.ctx.fillText(`Reality Stability: ${stability}%`, margin, yPos);
  }
  yPos += lineHeight;
}

}

/**

  • Draw general simulation information
    */
    drawSimulationInfo() {
    const margin = 10;
    const lineHeight = 20;
    let yPos = this.canvas.height - margin - lineHeight * 3;
// Set up text style
this.ctx.font = '14px Arial';
this.ctx.fillStyle = '#333';
this.ctx.textBaseline = 'top';

// Simulation time
this.ctx.fillText(`Simulation Time: ${this.world.time}`, margin, yPos);
yPos += lineHeight;

// Time until next reality shift
const timeUntilShift = 150 - (this.world.time % 150);
this.ctx.fillText(`Next Reality Shift: ${timeUntilShift} ticks`, margin, yPos);
yPos += lineHeight;

  // Number of entities
const entityCount = this.world.entities.length;
this.ctx.fillText(`Entities: ${entityCount}`, margin, yPos);

}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions