Debugging & Profiling

January 27, 2026 · View on GitHub

Learn how to debug and profile your Screeps bot for performance optimization.


Table of Contents


Debugging Tools

Console Commands

The framework includes a console command system:

import { ConsoleCommands } from '@ralphschuler/screeps-console';

// Register custom commands
ConsoleCommands.register('stats', () => {
  console.log(`CPU: ${Game.cpu.getUsed().toFixed(2)}`);
  console.log(`Bucket: ${Game.cpu.bucket}`);
  console.log(`Rooms: ${Object.keys(Game.rooms).length}`);
});

// Use in console:
// > stats()

Built-in Console Commands

// Get process statistics
kernel.getStatistics()

// Profile a function
profile.start('myFunction')
// ... code ...
profile.end('myFunction')

// Memory usage
Memory.stats

// Cache statistics
cache.getStats()

CPU Profiling

Manual Profiling

function profiledFunction() {
  const startCPU = Game.cpu.getUsed();
  
  // Your code
  expensiveOperation();
  
  const cpuUsed = Game.cpu.getUsed() - startCPU;
  console.log(`CPU: ${cpuUsed.toFixed(3)}`);
}

Profiling Decorators

import { profile } from '@ralphschuler/screeps-core';

class MyManager {
  @profile('MyManager.process')
  process() {
    // Automatically profiled
  }
}

Process-Level Profiling

import { kernel } from '@ralphschuler/screeps-kernel';

// Get all process statistics
const stats = kernel.getStatistics();

for (const [processId, stat] of Object.entries(stats)) {
  console.log(`${processId}: ${stat.cpu.toFixed(3)} CPU, ${stat.executions} runs`);
}

Hot Path Identification

// Track expensive operations
const hotPaths = new Map<string, number>();

function trackOperation(name: string, cpu: number) {
  const total = hotPaths.get(name) || 0;
  hotPaths.set(name, total + cpu);
}

// Every 100 ticks, log hot paths
if (Game.time % 100 === 0) {
  const sorted = Array.from(hotPaths.entries())
    .sort((a, b) => b[1] - a[1]);
  
  console.log('Top CPU consumers:');
  for (const [name, cpu] of sorted.slice(0, 10)) {
    console.log(`  ${name}: ${cpu.toFixed(2)} total CPU`);
  }
  
  hotPaths.clear();
}

Memory Inspection

Memory Size

// Calculate memory size
function getMemorySize(): number {
  return JSON.stringify(Memory).length;
}

console.log(`Memory size: ${(getMemorySize() / 1024).toFixed(2)} KB`);

Memory Breakdown

function analyzeMemory() {
  const sizes = {
    rooms: JSON.stringify(Memory.rooms).length,
    creeps: JSON.stringify(Memory.creeps).length,
    empire: JSON.stringify(Memory.empire).length,
    stats: JSON.stringify(Memory.stats).length
  };
  
  console.log('Memory breakdown:');
  for (const [key, size] of Object.entries(sizes)) {
    console.log(`  ${key}: ${(size / 1024).toFixed(2)} KB`);
  }
}

Memory Leaks

// Detect memory leaks (growing memory)
let lastMemorySize = 0;

if (Game.time % 100 === 0) {
  const currentSize = getMemorySize();
  const delta = currentSize - lastMemorySize;
  
  if (delta > 10000) {  // >10 KB growth
    console.log(`⚠️ Memory leak detected: +${(delta / 1024).toFixed(2)} KB`);
    analyzeMemory();
  }
  
  lastMemorySize = currentSize;
}

Visual Debugging

Room Visuals

import { RoomVisual } from '@ralphschuler/screeps-visuals';

// Visualize pathfinding
function visualizePath(creep: Creep, target: RoomPosition) {
  const path = creep.pos.findPathTo(target);
  
  creep.room.visual.poly(
    path.map(p => [p.x, p.y]),
    { stroke: '#ffffff', lineStyle: 'dashed' }
  );
}

// Visualize CPU usage per room
function visualizeRoomCPU(room: Room, cpu: number) {
  room.visual.text(
    `CPU: ${cpu.toFixed(2)}`,
    25, 1,
    { color: cpu > 0.15 ? '#ff0000' : '#00ff00' }
  );
}

Pheromone Visualization

function visualizePheromones(room: Room) {
  const pheromones = room.memory.swarm?.pheromones;
  if (!pheromones) return;
  
  const y = 2;
  const labels = [
    ['expand', pheromones.expand],
    ['harvest', pheromones.harvest],
    ['build', pheromones.build],
    ['defense', pheromones.defense]
  ];
  
  for (let i = 0; i < labels.length; i++) {
    const [label, value] = labels[i];
    const color = value > 0.7 ? '#ff0000' : value > 0.4 ? '#ffaa00' : '#00ff00';
    room.visual.text(
      `${label}: ${value.toFixed(2)}`,
      1, y + i,
      { color, align: 'left' }
    );
  }
}

Common Issues

Issue 1: High CPU Usage

Symptoms: CPU consistently > 20, bucket draining

Diagnosis:

// Profile all major systems
const systems = ['spawning', 'economy', 'defense', 'roles'];
for (const system of systems) {
  const start = Game.cpu.getUsed();
  runSystem(system);
  const cpu = Game.cpu.getUsed() - start;
  console.log(`${system}: ${cpu.toFixed(3)} CPU`);
}

Common Causes:

  • Uncached room.find() calls
  • Pathfinding every tick
  • Too many creeps
  • Inefficient role logic

Solutions: See Performance Guide

Issue 2: Memory Leaks

Symptoms: Memory size growing, parsing time increasing

Diagnosis:

// Track memory growth
analyzeMemory();  // Run every 100 ticks

Common Causes:

  • Dead creep memory not cleaned
  • Event logs growing unbounded
  • Cached data not expiring

Solutions:

// Clean dead creep memory
for (const name in Memory.creeps) {
  if (!Game.creeps[name]) {
    delete Memory.creeps[name];
  }
}

// Limit event logs
if (room.memory.swarm.eventLog.length > 20) {
  room.memory.swarm.eventLog = room.memory.swarm.eventLog.slice(-20);
}

Issue 3: Cache Inefficiency

Symptoms: Low cache hit rate, high CPU despite caching

Diagnosis:

const stats = cache.getStats();
const hitRate = stats.hits / (stats.hits + stats.misses);
console.log(`Cache hit rate: ${(hitRate * 100).toFixed(1)}%`);

Common Causes:

  • TTL too short (cache expires before reuse)
  • Cache keys not unique
  • LRU eviction too aggressive

Solutions:

  • Increase TTL for static data
  • Use namespaces to avoid key conflicts
  • Increase cache size limit

Performance Analysis

CPU Budget Analysis

function analyzeCPUBudgets() {
  const processes = kernel.getStatistics();
  const totalBudget = Object.values(processes)
    .reduce((sum, p) => sum + p.cpuBudget, 0);
  
  console.log(`Total CPU budget: ${totalBudget.toFixed(2)}`);
  console.log(`Actual CPU: ${Game.cpu.getUsed().toFixed(2)}`);
  
  if (Game.cpu.getUsed() > totalBudget * 1.2) {
    console.log('⚠️ Processes exceeding budgets!');
  }
}

Room-Level Profiling

function profileRooms() {
  const roomCPU = new Map<string, number>();
  
  for (const room of Object.values(Game.rooms)) {
    if (!room.controller?.my) continue;
    
    const start = Game.cpu.getUsed();
    processRoom(room);
    const cpu = Game.cpu.getUsed() - start;
    
    roomCPU.set(room.name, cpu);
  }
  
  // Find expensive rooms
  const sorted = Array.from(roomCPU.entries())
    .sort((a, b) => b[1] - a[1]);
  
  console.log('Most expensive rooms:');
  for (const [name, cpu] of sorted.slice(0, 5)) {
    console.log(`  ${name}: ${cpu.toFixed(3)} CPU`);
  }
}

Creep Performance

function profileCreeps() {
  const roleCPU = new Map<string, { total: number, count: number }>();
  
  for (const creep of Object.values(Game.creeps)) {
    const start = Game.cpu.getUsed();
    runCreepRole(creep);
    const cpu = Game.cpu.getUsed() - start;
    
    const role = creep.memory.role;
    const stats = roleCPU.get(role) || { total: 0, count: 0 };
    stats.total += cpu;
    stats.count += 1;
    roleCPU.set(role, stats);
  }
  
  console.log('Average CPU per role:');
  for (const [role, stats] of roleCPU) {
    const avg = stats.total / stats.count;
    console.log(`  ${role}: ${avg.toFixed(4)} CPU (${stats.count} creeps)`);
  }
}


Last Updated: 2026-01-27
Framework Version: 0.1.0