SPAWNING · DYNAMIC BODY DESIGN

How to Build a Screeps Creep Body from Available Energy

Distinguish current Energy from room capacity, repeat a role-specific body unit, enforce the 50-part limit, calculate cost and spawn time, and decide when to spawn a smaller emergency body.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — Room Energy fields, 50-part maximum, BODYPART_COST, CREEP_SPAWN_TIME · Policy boundary: Body builder chooses a valid body; spawn timing remains a separate decision

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Room Energy fields, 50-part maximum, BODYPART_COST, CREEP_SPAWN_TIME
Policy boundary
Body builder chooses a valid body; spawn timing remains a separate decision
JavaScript syntax
Passed
Offline calculation review
Passed — 0, 199, 200, 550, 3200 and high-Energy inputs
Screeps Console test
Pending
Live replacement-cycle test
Pending
Last verified
July 25, 2026

Quick answer

A dynamic body should not simply spend every available Energy unit. Start with a role-specific minimum unit, calculate its cost from BODYPART_COST, repeat it only while current room.energyAvailable and the 50-part limit permit, and apply a role-specific maximum. Keep the body-planning function separate from the decision to spawn now or wait for more Energy.

energyAvailable versus energyCapacityAvailable

PropertyMeaningTypical policy
room.energyAvailableEnergy currently loaded in all room Spawns and Extensions.Spawn the best affordable body now.
room.energyCapacityAvailableTotal capacity of those Spawns and Extensions.Wait until enough Energy is loaded for the desired body.

This example uses current energyAvailable, which is useful for emergency or degraded spawning. A normal replacement system may prefer to wait when current workers still have enough lifetime.

Define one role-specific body unit

const WORKER_UNIT = [WORK, CARRY, MOVE];

function getBodyCost(body) {
  return body.reduce((total, part) => {
    const cost = BODYPART_COST[part];

    if (!Number.isFinite(cost)) {
      throw new TypeError(
        'Unknown body part: ' + String(part)
      );
    }

    return total + cost;
  }, 0);
}

The unit costs 200 Energy using current official constants. The code derives that value instead of scattering the number across multiple branches.

Why the example stops at 16 units

16 units × 3 parts = 48 parts
17 units × 3 parts = 51 parts

A regular Creep body can contain at most 50 parts. Therefore this three-part pattern can repeat at most 16 times, even when the room can afford more.

Pure repeated-body builder

function buildRepeatedBody(input) {
  const {
    energyAvailable,
    unit,
    maximumParts = 50,
    maximumUnits = Infinity
  } = input;

  if (
    !Number.isFinite(energyAvailable)
    || energyAvailable < 0
    || !Array.isArray(unit)
    || unit.length === 0
    || !Number.isInteger(maximumParts)
    || maximumParts < 1
    || (
      maximumUnits !== Infinity
      && !Number.isFinite(maximumUnits)
    )
    || maximumUnits < 0
  ) {
    return {
      valid: false,
      reason: 'invalid-input',
      body: []
    };
  }

  const unitCost = getBodyCost(unit);
  const unitsByEnergy = Math.floor(
    energyAvailable / unitCost
  );
  const unitsByParts = Math.floor(
    maximumParts / unit.length
  );
  const units = Math.max(
    0,
    Math.min(
      unitsByEnergy,
      unitsByParts,
      Math.floor(maximumUnits)
    )
  );
  const body = [];

  for (let index = 0; index < units; index += 1) {
    body.push(...unit);
  }

  return {
    valid: true,
    reason: body.length > 0
      ? 'ready'
      : 'energy-below-minimum',
    body,
    units,
    unitCost,
    bodyCost: units * unitCost,
    spawnTime: body.length * CREEP_SPAWN_TIME
  };
}

The function uses only input values and official constants. That makes boundary tests independent of a live room.

Calculate spawn time

CREEP_SPAWN_TIME is the base number of ticks per body part. The plan reports:

const spawnTime = body.length * CREEP_SPAWN_TIME;

A 48-part Creep occupies a Spawn much longer than a three-part Creep. Larger bodies therefore require earlier replacement scheduling.

Body order changes damage behavior

These bodies have equal cost and part counts:

[WORK, CARRY, MOVE, WORK, CARRY, MOVE]

[WORK, WORK, CARRY, CARRY, MOVE, MOVE]

They do not lose abilities in the same order when damaged because earlier parts absorb damage first. Repeating a unit preserves a visible ratio but is not universally optimal. Haulers, fixed miners, upgraders, and combat Creeps need different ordering rules.

Complete dynamic spawn example

State impact: the planning functions do not mutate the game. The final call may start one Worker when no current Worker exists and the Spawn is idle.

const WORKER_UNIT = [WORK, CARRY, MOVE];

function getBodyCost(body) {
  return body.reduce((total, part) => {
    const cost = BODYPART_COST[part];

    if (!Number.isFinite(cost)) {
      throw new TypeError(
        'Unknown body part: ' + String(part)
      );
    }

    return total + cost;
  }, 0);
}

function buildRepeatedBody(input) {
  const {
    energyAvailable,
    unit,
    maximumParts = 50,
    maximumUnits = Infinity
  } = input;

  if (
    !Number.isFinite(energyAvailable)
    || energyAvailable < 0
    || !Array.isArray(unit)
    || unit.length === 0
  ) {
    return {
      valid: false,
      reason: 'invalid-input',
      body: []
    };
  }

  const unitCost = getBodyCost(unit);
  const units = Math.max(
    0,
    Math.min(
      Math.floor(energyAvailable / unitCost),
      Math.floor(maximumParts / unit.length),
      Math.floor(maximumUnits)
    )
  );
  const body = [];

  for (let index = 0; index < units; index += 1) {
    body.push(...unit);
  }

  return {
    valid: true,
    reason: body.length > 0
      ? 'ready'
      : 'energy-below-minimum',
    body,
    units,
    unitCost,
    bodyCost: units * unitCost,
    spawnTime: body.length * CREEP_SPAWN_TIME
  };
}

function createUniqueName(spawn, role) {
  return [role, spawn.name, Game.time].join('-');
}

module.exports.loop = function () {
  const spawn = Game.spawns.Spawn1;

  if (!spawn || spawn.spawning) {
    return;
  }

  const existingWorkers = Object.values(Game.creeps)
    .filter(creep =>
      creep.memory?.role === 'worker'
    );

  if (existingWorkers.length > 0) {
    return;
  }

  const plan = buildRepeatedBody({
    energyAvailable: spawn.room.energyAvailable,
    unit: WORKER_UNIT,
    maximumParts: 50,
    maximumUnits: 5
  });

  if (!plan.valid || plan.body.length === 0) {
    return;
  }

  const name = createUniqueName(spawn, 'Worker');
  const memory = {
    role: 'worker',
    bodyUnits: plan.units,
    memoryVersion: 1
  };
  const dryRunResult = spawn.spawnCreep(
    plan.body,
    name,
    {
      memory,
      dryRun: true
    }
  );

  if (dryRunResult !== OK) {
    console.log(JSON.stringify({
      type: 'dynamic-body-dry-run-failed',
      spawnName: spawn.name,
      name,
      dryRunResult,
      plan
    }));
    return;
  }

  const result = spawn.spawnCreep(
    plan.body,
    name,
    { memory }
  );

  if (result !== OK) {
    console.log(JSON.stringify({
      type: 'dynamic-body-spawn-failed',
      spawnName: spawn.name,
      name,
      result,
      plan
    }));
  }
};

Set a role-specific maximum

A room that can afford 48 parts does not necessarily need one 48-part general Worker. Use a project value such as:

maximumUnits: 5

Reasons include spawn time, movement speed, parallelism, the need to reserve Energy for other roles, and task throughput. This maximum is a project policy, not an official Screeps value.

When to spawn now or wait

  • No harvesters remain: spawn the minimum viable recovery body now.
  • A Builder quota is slightly low: waiting for a larger body may be reasonable.
  • Defense is urgent: use a current-Energy fallback plan.
  • Existing workers have long lifetime: wait for the desired capacity.

The body builder answers “what can I afford?” It does not answer “should I spawn this tick?”

Return-code checks

Handle ERR_BUSY, ERR_NAME_EXISTS, ERR_NOT_ENOUGH_ENERGY, ERR_INVALID_ARGS, ERR_NOT_OWNER, and ERR_RCL_NOT_ENOUGH. spawnCreep() does not return ERR_NOT_IN_RANGE. Use the focused return-code guide for full diagnostics.

Debugging checklist

  • Choose between current Energy and waiting for capacity deliberately.
  • Define a minimum body that can perform the role.
  • Calculate cost from BODYPART_COST.
  • Enforce the 50-part limit.
  • Set a role-specific maximum.
  • Report body cost, unit count, and spawn time.
  • Consider body ordering under damage.
  • Keep body planning separate from spawn timing.
  • Use dryRun before the real request.
  • Save the final return code.

Scope and next steps

This article does not optimize specialized miners, haulers, combat bodies, boosts, road movement, lifetime replacement lead time, or multi-Spawn queues. Continue with emergency recovery when no harvester remains.

Frequently asked questions

What is energyAvailable versus energyCapacityAvailable?

The first is current loaded Energy; the second is total room spawning capacity.

Why only 16 three-part units?

Sixteen use 48 parts. Seventeen would use 51 and exceed the limit.

Should the body spend every Energy unit?

No. Valid role capability and configured limits matter more than eliminating every remainder.

Does this function decide when to spawn?

No. It produces a plan; room policy decides whether to use it now.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.