Use this guide when
Use this guide when you need to measure a concrete code section or decide whether optional work fits the current tick. Do not use it to infer the execution environment from a CPU value: two zero readings do not prove that the code is running in Simulation.
global.cpuProbe ??= {
zeroSamples: 0
};
function recordCpuProbe() {
const used = Game.cpu.getUsed();
if (used === 0) {
global.cpuProbe.zeroSamples += 1;
return {
status: 'zero-sample-inconclusive',
used
};
}
return {
status: 'measurable-sample',
used
};
}
zero-sample-inconclusive means exactly that: the sample is not enough to classify the environment or prove that a workload was free.
Keep essential work ahead of optional work
function runTick() {
runDefense();
const reserveCpu = 5;
const minimumBucket = 2000; // Project example, not an official Screeps recommendation.
const remaining = Game.cpu.tickLimit
- Game.cpu.getUsed();
if (
remaining > reserveCpu
&& Game.cpu.bucket >= minimumBucket
) {
runOptionalWork();
}
}
The exact reserveCpu and minimumBucket = 2000 values are application policy. Keep them only if your own measurements justify them; the API does not define either value as a universal threshold.
Quick answer
Use Game.cpu.getUsed() as a cumulative current-tick meter. Sample immediately before and after the code you want to inspect, subtract the two values, and keep the surrounding limit, tickLimit, and bucket context. Do not use a zero getUsed() reading to guess which environment you are running in.
Keep the four CPU values separate
| Value | What it tells you | What it does not tell you |
|---|---|---|
Game.cpu.getUsed() | Cumulative CPU used so far in the current tick | The cost of one section unless you subtract two samples |
Game.cpu.limit | The normal CPU limit currently assigned to this shard | The exact amount every tick may consume before termination |
Game.cpu.tickLimit | The maximum CPU currently available for this tick | A performance target you should try to consume |
Game.cpu.bucket | Stored CPU credit used by the runtime's burst system | A direct measurement of one function's cost |
The bucket has an official upper bound of 10,000. That fact does not make a specific “run optional work below/above X” threshold official; any such threshold in your bot is policy.
Measure one section with getUsed()
function measureCpu(label, fn) {
const before = Game.cpu.getUsed();
const value = fn();
const after = Game.cpu.getUsed();
return {
label,
value,
before,
after,
used: Math.max(0, after - before)
};
}
Use it around a concrete unit of work:
const sample = measureCpu(
'find-hostiles',
() => room.find(FIND_HOSTILE_CREEPS)
);
if (Game.time % 50 === 0) {
console.log(JSON.stringify({
tick: Game.time,
label: sample.label,
used: sample.used,
limit: Game.cpu.limit,
tickLimit: Game.cpu.tickLimit,
bucket: Game.cpu.bucket
}));
}
The measurement itself has overhead, and very small sections can be noisy. Compare repeated samples of the same boundary before changing architecture.
Do not infer the environment from a zero reading
The official API states that Game.cpu.getUsed() always returns 0 in Simulation. That rule only goes one direction:
Simulation -> getUsed() is 0
getUsed() is 0 -/-> therefore Simulation
A tiny section outside Simulation can produce a zero-looking delta or values too small to support a useful conclusion. Know which environment you launched, then interpret the measurement. Do not write environment detection like this:
// Do not use this as an environment detector.
const inSimulation = Game.cpu.getUsed() === 0;
Sample at useful boundaries
Instrument decisions, not every expression. Useful boundaries include path search, room scans, market processing, spawn planning, cache rebuilds, and optional visual/debug work.
function readCpuContext(label) {
return {
label,
tick: Game.time,
used: Game.cpu.getUsed(),
limit: Game.cpu.limit,
tickLimit: Game.cpu.tickLimit,
bucket: Game.cpu.bucket
};
}
A start/end pair explains a section delta. A context snapshot explains the budget surrounding that sample. Neither proves that a different tick, room count, shard, or cache state will cost the same.
Treat bucket thresholds as your policy
The bucket exists so low-usage periods can support bursts. Your bot still needs a workload policy. For example:
const CPU_POLICY = {
optionalWorkBucketFloor: 7000
};
function canRunOptionalWork() {
return Game.cpu.bucket
>= CPU_POLICY.optionalWorkBucketFloor;
}
7000 is an example project threshold, not an official recommendation. A colony with different room count, market work, pathfinding, or visual output may need a different policy. Measure the trend before adopting a value.
For a full degraded-mode scheduler with hysteresis, use the CPU bucket degradation guide.
Turn a CPU delta into a comparable measurement
A section delta answers one narrow question: how much cumulative CPU increased between two samples in this tick. It does not explain why the work cost that amount, and one low value does not prove an optimization. Compare the same boundary under similar input size, room visibility, cache state, and feature flags before changing architecture.
function describeCpuSample(label, used, context = {}) {
return {
tick: Game.time,
label,
used,
bucket: Game.cpu.bucket,
limit: Game.cpu.limit,
tickLimit: Game.cpu.tickLimit,
roomsVisible: Object.keys(Game.rooms).length,
...context
};
}
Keep context fields small and chosen in advance. For a path search, useful context might be route length or whether a CostMatrix cache was warm. For a room scan, it might be the number of visible rooms or candidate objects. Do not add so much instrumentation that the profiler becomes the workload you are measuring.
Use section deltas to locate work. Use whole-tick CPU and bucket trend to decide whether moving or caching that work actually improved the colony's runtime budget.
Keep diagnostics bounded
If you persist samples, enforce a fixed window so diagnostics do not become an unbounded Memory cost:
function pushCpuSample(sample, limit = 20) {
Memory.cpuSamples ??= [];
Memory.cpuSamples.push(sample);
if (Memory.cpuSamples.length > limit) {
Memory.cpuSamples.splice(
0,
Memory.cpuSamples.length - limit
);
}
}
Persist only what you need for later comparison. For high-frequency temporary profiling, a disposable global cache may be better because it avoids turning every sample into durable state.
Production adaptation
Measure with a purpose: identify an expensive decision, compare it under similar conditions, then remove or reduce instrumentation after the problem is understood. Keep critical colony work separate from optional reports, visuals, market scans, or background planning. A bucket policy should degrade optional work before correctness-critical tasks.
Common measurement mistakes
- Reading
getUsed()once and calling that number “the function cost.” - Using a zero sample as proof of the environment.
- Comparing different workloads without recording room/tick context.
- Treating
tickLimitas a target instead of an upper runtime boundary. - Calling an arbitrary bucket threshold an official best practice.
- Persisting an unlimited CPU history in Memory.
- Claiming an optimization from static review without real before/after measurements.
Choose another guide when
If the problem is not measurement but deciding which optional systems to suspend as the bucket falls, use the CPU bucket degradation guide. If repeated parsing or lookup work should disappear from the hot path and can be rebuilt after a reset, use the global-cache guide. This page stays focused on measuring a concrete boundary and interpreting that evidence without turning a project threshold into an API rule.
What this guide does not prove
This revision has official-documentation review, syntax/build checks, and offline logic coverage. It does not contain a real shard CPU sample set, an observed bucket trend, or a benchmark showing that one implementation is faster than another. Those measurements remain environment work.
Frequently asked questions
What does getUsed() measure?
CPU consumed so far in the current tick. Use two samples to estimate a section delta.
Does zero mean Simulation?
No. Simulation always reports zero, but the inverse is not a documented environment test.
Are limit and tickLimit the same?
No. limit is the normal shard limit; tickLimit is the maximum currently available for that tick under bucket rules.
What bucket floor should I use?
Choose it from your own measured workload. A threshold in application code is project policy unless the official API explicitly defines it.