Quick answer
Game.cpu.getUsed() is cumulative within the current tick. Measure a section by recording a start value, running the section, and subtracting the start from the end. Treat Game.cpu.limit as the normal per-tick allowance, Game.cpu.tickLimit as the current hard ceiling, and Game.cpu.bucket as stored CPU capacity. The Simulation reports 0 for getUsed(), so it cannot prove real CPU cost.
Understand limit, tickLimit, bucket, and getUsed
| Field | Meaning | Do not confuse it with |
|---|---|---|
Game.cpu.getUsed() | Cumulative CPU consumed so far this tick | A section delta or a cross-tick average |
Game.cpu.limit | Normal CPU allowance associated with your account | The maximum CPU available on every tick |
Game.cpu.tickLimit | Current hard CPU ceiling for the tick | A target that should always be consumed |
Game.cpu.bucket | Stored CPU reserve, capped at 10,000 in the official environment | Free CPU with no future cost |
When the bucket is full, the official environment can allow a tickLimit as high as 500. These values describe the runtime budget. They do not identify which function is expensive.
Measure one code section
function measureCpu(label, fn) {
if (typeof fn !== 'function') {
return {
label,
ok: false,
cpu: null,
value: null,
error: 'function-required'
};
}
const start = Game.cpu.getUsed();
try {
const value = fn();
const end = Game.cpu.getUsed();
return {
label,
ok: true,
cpu: Math.max(0, end - start),
value,
error: null
};
} catch (error) {
const end = Game.cpu.getUsed();
return {
label,
ok: false,
cpu: Math.max(0, end - start),
value: null,
error: error instanceof Error
? error.message
: String(error)
};
}
}
The wrapper measures the function body plus wrapper overhead. Use the same harness before and after a change so the comparison remains consistent.
Why the Simulation cannot validate CPU cost
The official API states that Game.cpu.getUsed() always returns 0 in the Simulation. A zero delta there does not prove that pathfinding, sorting, JSON parsing, or room scans are free.
function canMeasureCpuHere() {
const first = Game.cpu.getUsed();
const second = Game.cpu.getUsed();
return {
first,
second,
measurementAvailable:
Number.isFinite(first)
&& Number.isFinite(second)
&& (first !== 0 || second !== 0)
};
}
A real server tick can also produce a very small or zero-looking delta for a tiny section. The environment warning should be explicit rather than inferred from one call.
Collect bounded samples
State impact: this helper writes a bounded numeric history to Memory. It does not change game objects.
function recordCpuSample(name, value, limit = 100) {
if (
typeof name !== 'string'
|| !Number.isFinite(value)
|| !Number.isInteger(limit)
|| limit <= 0
) {
return false;
}
Memory.cpuSamples ??= {};
const samples = Array.isArray(
Memory.cpuSamples[name]
)
? Memory.cpuSamples[name]
: [];
samples.push({
tick: Game.time,
value
});
Memory.cpuSamples[name] = samples.slice(-limit);
return true;
}
Memory writes and serialization have their own cost. For frequent profiling, sample only selected ticks, use a short history, or aggregate statistics instead of storing every raw event.
Use averages and percentiles
function summarizeNumbers(values) {
const clean = values
.filter(Number.isFinite)
.sort((a, b) => a - b);
if (clean.length === 0) {
return null;
}
const percentile = ratio => {
const index = Math.min(
clean.length - 1,
Math.max(
0,
Math.ceil(clean.length * ratio) - 1
)
);
return clean[index];
};
const total = clean.reduce(
(sum, value) => sum + value,
0
);
return {
count: clean.length,
average: total / clean.length,
median: percentile(0.5),
p95: percentile(0.95),
maximum: clean[clean.length - 1]
};
}
An average hides rare expensive ticks. A p95 or maximum can reveal hostile scans, route rebuilds, cache misses, market refreshes, or global-reset reconstruction.
Use bucket bands as policy
function classifyCpuBucket(bucket) {
if (!Number.isFinite(bucket)) {
return 'unknown';
}
if (bucket < 1000) {
return 'critical';
}
if (bucket < 4000) {
return 'conserve';
}
if (bucket > 9000) {
return 'surplus';
}
return 'normal';
}
The thresholds are example policy, not official recommendations. Choose them from your shard behavior and workload. Bucket bands decide whether optional work should run; they do not replace section-level measurements.
Complete CPU budget controller
State impact: this controller reads CPU fields and writes a compact runtime summary. It may skip optional callbacks, but it does not skip the essential callback.
function classifyCpuBucket(bucket) {
if (!Number.isFinite(bucket)) {
return 'unknown';
}
if (bucket < 1000) return 'critical';
if (bucket < 4000) return 'conserve';
if (bucket > 9000) return 'surplus';
return 'normal';
}
function runCpuBudgetedTick({
essential,
optional,
reserveCpu = 2
}) {
if (typeof essential !== 'function') {
throw new TypeError('essential callback required');
}
const startedAt = Game.cpu.getUsed();
const essentialResult = essential();
const afterEssential = Game.cpu.getUsed();
const bucketBand = classifyCpuBucket(
Game.cpu.bucket
);
const remainingToHardLimit = Math.max(
0,
Game.cpu.tickLimit - afterEssential
);
const optionalAllowed =
typeof optional === 'function'
&& bucketBand !== 'critical'
&& remainingToHardLimit > reserveCpu;
let optionalResult = null;
let optionalCpu = 0;
if (optionalAllowed) {
const optionalStart = Game.cpu.getUsed();
optionalResult = optional();
optionalCpu = Math.max(
0,
Game.cpu.getUsed() - optionalStart
);
}
const finishedAt = Game.cpu.getUsed();
const summary = {
tick: Game.time,
bucket: Game.cpu.bucket,
bucketBand,
limit: Game.cpu.limit,
tickLimit: Game.cpu.tickLimit,
essentialCpu: Math.max(
0,
afterEssential - startedAt
),
optionalAllowed,
optionalCpu,
totalCpu: Math.max(0, finishedAt - startedAt)
};
Memory.cpuRuntime = summary;
return {
summary,
essentialResult,
optionalResult
};
}
A production controller should define which operations are essential before enabling a low-bucket mode. Defense, spawn recovery, source logistics, Controller safety, and required Memory cleanup usually need stronger guarantees than analytics or broad scans.
Separate essential and optional work
| Class | Examples | Low-bucket behavior |
|---|---|---|
| Essential | Defense, spawn recovery, core harvesting, Controller protection | Run with bounded algorithms |
| Deferred maintenance | Cache rebuild, route refresh, market scan | Run less often or in slices |
| Optional diagnostics | Detailed logs, visuals, analytics | Disable first |
The categories depend on the colony. A market deal can become essential during an energy emergency, while an ordinary market scan remains optional.
Stop before optional work exhausts the tick
function hasCpuHeadroom(reserveCpu = 2) {
if (!Number.isFinite(reserveCpu) || reserveCpu < 0) {
return false;
}
return Game.cpu.getUsed()
< Game.cpu.tickLimit - reserveCpu;
}
function runUntilBudget(items, worker, reserveCpu = 2) {
let completed = 0;
for (const item of items) {
if (!hasCpuHeadroom(reserveCpu)) {
break;
}
worker(item);
completed += 1;
}
return completed;
}
This guard reduces the chance that optional loops consume the hard ceiling. It does not make an individual worker safe; each iteration still needs bounded complexity and error isolation.
Reduce measurement noise
- Compare the same room count and similar input size.
- Separate global-reset ticks from warm-cache ticks.
- Measure cache hits and misses independently.
- Record pathfinding operations and result shape alongside CPU.
- Limit logging during the measured section.
- Use several samples and report p95 or maximum.
- Measure the caller when optimization changes work distribution.
Moving work to another tick or hiding it behind a cache can lower one function's delta while leaving total colony CPU unchanged. Track both section and whole-tick measurements.
Debugging checklist
- Confirm the environment reports meaningful
getUsed()values. - Subtract start from end; do not treat the cumulative end value as section cost.
- Keep sample histories bounded.
- Record representative input size.
- Separate global-reset and warm-cache samples.
- Check average, p95, and maximum.
- Use bucket only as a scheduling signal.
- Keep essential work outside optional low-bucket gates.
- Reserve CPU before running optional loops.
- Verify the complete tick, not only one optimized function.
Scope and next steps
This guide does not provide shard-specific benchmark numbers, a production profiler library, heap statistics, account CPU allocation advice, pixel generation policy, or a live bucket recovery experiment. Continue with global cache design to understand warm and reset CPU behavior.
Frequently asked questions
Is getUsed() a timer in milliseconds?
Treat it as Screeps CPU usage reported by the runtime, not wall-clock duration.
Can I optimize in the Simulation?
You can test logic, but the Simulation's zero getUsed() result cannot validate real CPU cost.
Should I always spend CPU when the bucket is high?
No. A high bucket permits optional work, but the work should still have value and a bounded budget.
What should be disabled first?
Diagnostics, visuals, broad refreshes, and non-urgent analysis are safer candidates than survival logic.