Skip to content

argos-visualizations.js #3

@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
    No fields configured for issues without a type.

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions