Use this guide when
Use this guide when a Creep moves more slowly than expected and you want to distinguish a body-design problem from current fatigue or terrain. The calculator below is deliberately limited to unboosted ordinary Creeps moving under their own power. Pulling and MOVE/CARRY boosts change the calculation and are outside this formula.
The four movement rules
- Road, plain, and swamp movement generate fatigue at rates 1, 2, and 10 per movement-weight part.
- Each active unboosted
MOVEpart removes 2 fatigue per tick. - Empty
CARRYcapacity does not add movement weight; carried resources make active CARRY capacity contribute weight. - Ordinary non-
MOVE, non-CARRYbody parts are movement weight even when those parts have been destroyed.
The last rule is easy to miss. “Active body parts” matters for MOVE effectiveness and usable CARRY capacity, but it is not a general rule that destroyed WORK, ATTACK, HEAL, CLAIM, RANGED_ATTACK, or TOUGH parts stop contributing movement weight.
Destroyed ordinary parts still count as weight
The Screeps engine movement implementation calculates ordinary weight by counting body entries whose type is neither MOVE nor CARRY. It does not apply a hits > 0 filter to those ordinary weight parts. In contrast, movement ability depends on an active MOVE part.
function countOrdinaryWeightParts(creep) {
return creep.body.filter(
part => part.type !== MOVE && part.type !== CARRY
).length;
}
That means this tempting implementation is wrong:
// Wrong for movement weight.
const ordinaryWeight = creep.body.filter(
part =>
part.hits > 0
&& part.type !== MOVE
&& part.type !== CARRY
).length;
If a damaged Creep loses active MOVE parts while its ordinary body entries still contribute weight, its movement interval can become much worse than a calculator based only on surviving ordinary parts predicts.
Count loaded CARRY separately
For an unboosted Creep, each active CARRY part provides 50 capacity. Empty CARRY does not weigh the Creep down. Use total resources in the Store, not only Energy:
function countLoadedCarryParts(creep) {
const used = creep.store.getUsedCapacity() ?? 0;
const activeCarry = creep.getActiveBodyparts(CARRY);
if (used <= 0 || activeCarry <= 0) {
return 0;
}
return Math.min(
activeCarry,
Math.ceil(used / CARRY_CAPACITY)
);
}
| Active unboosted CARRY | Store used | Loaded CARRY weight |
|---|---|---|
| 3 | 0 | 0 |
| 3 | 1–50 | 1 |
| 3 | 51–100 | 2 |
| 3 | 101–150 | 3 |
This helper is intentionally unboosted. CARRY boosts change capacity per active CARRY part, so a simple 50-capacity segmentation is no longer sufficient.
Calculate the steady-state interval
weight = ordinary non-MOVE/non-CARRY body entries
+ loaded active CARRY weight
fatigue generated by a step = weight × terrain rate
MOVE recovery per tick = active MOVE × 2
steady-state interval estimate = max(
1,
ceil(generated / recovery)
)
This is a body-and-terrain estimate for repeated movement. It does not erase creep.fatigue that already exists at the moment you inspect the Creep.
Road, plain, and swamp examples
A fully loaded [WORK, CARRY, MOVE] Creep has two weight parts and one active MOVE part:
| Terrain entered | Fatigue generated | Recovery/tick | Estimated interval |
|---|---|---|---|
| Road | 2 × 1 = 2 | 2 | 1 tick/step |
| Plain | 2 × 2 = 4 | 2 | 2 ticks/step |
| Swamp | 2 × 10 = 20 | 2 | 10 ticks/step |
The relevant terrain is the tile being entered. Do not sample only the Creep's current tile and assume it proves the next step cost.
Corrected unboosted calculator
State impact: this diagnostic reads body, Store, active MOVE/CARRY counts, and current fatigue. It does not move the Creep or write Memory.
const TERRAIN_FATIGUE = {
road: 1,
plain: 2,
swamp: 10
};
function countLoadedCarryParts(creep) {
const used = creep.store.getUsedCapacity() ?? 0;
const activeCarry = creep.getActiveBodyparts(CARRY);
if (used <= 0 || activeCarry <= 0) return 0;
return Math.min(
activeCarry,
Math.ceil(used / CARRY_CAPACITY)
);
}
function countMovementWeight(creep) {
const ordinaryWeight = creep.body.filter(
part => part.type !== MOVE && part.type !== CARRY
).length;
return ordinaryWeight + countLoadedCarryParts(creep);
}
function estimateCreepMovement(creep, terrain) {
if (!creep || creep.spawning === true) {
return { status: 'creep-unavailable' };
}
const terrainRate = TERRAIN_FATIGUE[terrain];
if (!Number.isInteger(terrainRate)) {
return { status: 'terrain-invalid' };
}
const activeMoveParts = creep.getActiveBodyparts(MOVE);
const weightParts = countMovementWeight(creep);
if (activeMoveParts <= 0) {
return {
status: 'no-active-move-part',
activeMoveParts,
weightParts,
currentFatigue: creep.fatigue
};
}
const fatigueGenerated = weightParts * terrainRate;
const fatigueRemovedPerTick = activeMoveParts * 2;
return {
status: 'estimated-unboosted',
activeMoveParts,
weightParts,
currentFatigue: creep.fatigue,
fatigueGenerated,
fatigueRemovedPerTick,
ticksPerStep: Math.max(
1,
Math.ceil(fatigueGenerated / fatigueRemovedPerTick)
)
};
}
The important correction is in countMovementWeight(): ordinary body entries are not filtered by part.hits.
Separate body design from current fatigue
A good body ratio does not guarantee that a Creep can move this tick. Read creep.fatigue separately. Positive fatigue blocks normal self-movement until enough MOVE recovery removes it.
console.log(JSON.stringify({
tick: Game.time,
fatigue: creep.fatigue,
activeMove: creep.getActiveBodyparts(MOVE),
movement: estimateCreepMovement(creep, 'plain')
}));
If the Creep is stationary, also check the actual movement return code. ERR_TIRED is a current-fatigue signal; ERR_NO_BODYPART is a no-active-MOVE signal. A static ratio estimate is not a substitute for those API results.
Use ratios as design constraints
For an unboosted, fully weighted Creep on plains, one active MOVE can recover the fatigue generated by roughly one weight part each tick. On roads, one MOVE can support more weight at one-tile-per-tick speed; on swamps, the same body needs far more MOVE recovery. Treat those as design math, not universal role templates.
Haulers deserve special attention because outbound empty CARRY may weigh nothing while the return trip is loaded. A body that is fast while empty can be deliberately slower when carrying resources.
Boosts, pulling, and live proof
This calculator excludes MOVE boosts, CARRY capacity boosts, pulling, Power Creeps, and traffic. It also does not claim a live damaged-Creep trace. The damaged-body correction is grounded in the current Screeps engine movement implementation and static code review; live multi-tick verification remains pending.