MOVEMENT · FATIGUE AND BODY DESIGN

How to Calculate Screeps Creep Movement Speed

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Movement

Recommended next steps

Return to MOVE and body-part basicsPrerequisite conceptFull no-progress movement diagnosisSymptom debuggingCoordinate two Creeps with pull()Advanced movement
Verification statusOfficial documentation: Checked August 12, 2026 — Movement, Creep.fatigue, Creep.getActiveBodyparts(), MOVE, CARRY, terrain movement costs, and Store capacity · Engine source review: Checked August 12, 2026 — screeps/engine movement implementation counts all non-MOVE/non-CARRY body entries as movement weight; loaded CARRY weight is calculated from active CARRY capacity · Static code review: Passed — unboosted body-weight calculator corrected so destroyed ordinary non-MOVE/non-CARRY parts still count as movement weight while active MOVE recovery and active loaded CARRY capacity remain separate

VERIFICATION

Evidence and test status

Official documentation
Checked August 12, 2026 — Movement, Creep.fatigue, Creep.getActiveBodyparts(), MOVE, CARRY, terrain movement costs, and Store capacity
Engine source review
Checked August 12, 2026 — screeps/engine movement implementation counts all non-MOVE/non-CARRY body entries as movement weight; loaded CARRY weight is calculated from active CARRY capacity
Static code review
Passed — unboosted body-weight calculator corrected so destroyed ordinary non-MOVE/non-CARRY parts still count as movement weight while active MOVE recovery and active loaded CARRY capacity remain separate
Chinese source article
Existing bilingual mapping retained; this English-only pass did not use the Chinese page as live-game evidence
Console test pending
Not run in this editorial pass
Screeps Console test
Pending — no real-shard Console transcript was collected for this revision
Live multi-tick verification pending
No real-shard damaged-Creep or multi-terrain movement trace was collected for this revision
Last verified
August 12, 2026

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 MOVE part removes 2 fatigue per tick.
  • Empty CARRY capacity does not add movement weight; carried resources make active CARRY capacity contribute weight.
  • Ordinary non-MOVE, non-CARRY body 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 CARRYStore usedLoaded CARRY weight
300
31–501
351–1002
3101–1503

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 enteredFatigue generatedRecovery/tickEstimated interval
Road2 × 1 = 221 tick/step
Plain2 × 2 = 422 ticks/step
Swamp2 × 10 = 20210 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.

Official documentation and source

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.