Quick answer
When StructureSpawn.spawnCreep() fails, save the return value instead of immediately repeating the call. Check five areas in order: the Spawn, the requested name, the body, currently available Spawn-and-Extension Energy, and the options object. Use dryRun: true for a non-mutating precheck, then save the real call's result separately because current conditions can still change later in the tick.
The five checks to run first
- The Spawn exists, belongs to you, is active, and is not already spawning.
- The requested name is a non-empty string of at most 100 characters and is not already present.
- The body is an array containing 1–50 official body-part constants.
- The room currently has enough Energy in its Spawns and Extensions.
memory,energyStructures, anddirectionsmatch the API contract.
This article is a debugging companion to the beginner spawnCreep() tutorial. It does not repeat the first-Creep walkthrough.
The basic spawnCreep() call
const result = Game.spawns.Spawn1.spawnCreep(
[WORK, CARRY, MOVE],
'Worker1',
{
memory: {
role: 'worker'
}
}
);
console.log('spawnCreep() returned ' + result);
OK means the spawning process was accepted. It does not mean the Creep is complete in the current tick. Read spawn.spawning and later ticks to observe progress.
Validate a 1–50 part body
The current official API accepts the eight regular body-part constants and requires 1–50 elements:
const BODY_PARTS = new Set([
WORK,
MOVE,
CARRY,
ATTACK,
RANGED_ATTACK,
HEAL,
TOUGH,
CLAIM
]);
function validateBody(body) {
if (!Array.isArray(body)) {
return {
valid: false,
reason: 'body-not-array'
};
}
if (body.length < 1 || body.length > 50) {
return {
valid: false,
reason: 'body-length-invalid'
};
}
for (const part of body) {
if (!BODY_PARTS.has(part)) {
return {
valid: false,
reason: 'unknown-body-part'
};
}
}
return {
valid: true,
reason: 'ready'
};
}
A body that is syntactically valid may still be unsuitable for its role. Use the body-parts guide for ability design.
Calculate body cost from official constants
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);
}
Compare the result with spawn.room.energyAvailable. That property is the current Energy in all Spawns and Extensions in the room. It does not include Storage, Terminal, Link, or Container inventory.
Validate the Creep name
function validateCreepName(name) {
if (typeof name !== 'string') {
return {
valid: false,
reason: 'name-not-string'
};
}
if (name.length < 1 || name.length > 100) {
return {
valid: false,
reason: 'name-length-invalid'
};
}
if (Game.creeps[name]) {
return {
valid: false,
reason: 'name-exists'
};
}
return {
valid: true,
reason: 'ready'
};
}
This precheck improves diagnostics but does not replace handling ERR_NAME_EXISTS. Another module can submit the same name later in the same tick.
What dryRun can and cannot prove
const dryRunResult = spawn.spawnCreep(
body,
name,
{
memory,
dryRun: true
}
);
A dry run checks the current request without starting the Spawn. It can reveal a busy Spawn, duplicate name, insufficient Energy, malformed body or name, ownership problems, and RCL restrictions. It is not a lock. Code that runs afterward can still consume the Spawn, use the name, or change the Energy state.
Complete safe submission helper
State impact: local validation and the dry run do not mutate the game. The final spawnCreep() call can reserve Energy and start spawning one Creep.
const BODY_PARTS = new Set([
WORK,
MOVE,
CARRY,
ATTACK,
RANGED_ATTACK,
HEAL,
TOUGH,
CLAIM
]);
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 validateSpawnRequest(input) {
const { spawn, body, name, memory } = input;
if (!spawn) {
return { valid: false, reason: 'spawn-missing' };
}
if (spawn.spawning) {
return { valid: false, reason: 'spawn-busy' };
}
if (
typeof name !== 'string'
|| name.length < 1
|| name.length > 100
) {
return { valid: false, reason: 'name-invalid' };
}
if (Game.creeps[name]) {
return { valid: false, reason: 'name-exists' };
}
if (
!Array.isArray(body)
|| body.length < 1
|| body.length > 50
|| body.some(part => !BODY_PARTS.has(part))
) {
return { valid: false, reason: 'body-invalid' };
}
if (
memory !== undefined
&& (
!memory
|| typeof memory !== 'object'
|| Array.isArray(memory)
)
) {
return { valid: false, reason: 'memory-invalid' };
}
const bodyCost = getBodyCost(body);
if (spawn.room.energyAvailable < bodyCost) {
return {
valid: false,
reason: 'energy-not-enough',
bodyCost,
energyAvailable: spawn.room.energyAvailable
};
}
return {
valid: true,
reason: 'ready',
bodyCost,
energyAvailable: spawn.room.energyAvailable
};
}
function submitSpawnRequest(input) {
const validation = validateSpawnRequest(input);
if (!validation.valid) {
return {
status: 'local-validation-failed',
...validation
};
}
const { spawn, body, name, memory, directions } = input;
const dryOptions = {
memory,
dryRun: true
};
if (Array.isArray(directions)) {
dryOptions.directions = directions;
}
const dryRunResult = spawn.spawnCreep(
body,
name,
dryOptions
);
if (dryRunResult !== OK) {
return {
status: 'dry-run-failed',
dryRunResult,
...validation
};
}
const actualOptions = { memory };
if (Array.isArray(directions)) {
actualOptions.directions = directions;
}
const result = spawn.spawnCreep(
body,
name,
actualOptions
);
return {
status: result === OK
? 'spawn-submitted'
: 'spawn-failed-after-dry-run',
dryRunResult,
result,
...validation
};
}
module.exports.loop = function () {
const spawn = Game.spawns.Spawn1;
const desiredName = 'Worker1';
if (Game.creeps[desiredName]) {
return;
}
const outcome = submitSpawnRequest({
spawn,
body: [WORK, CARRY, MOVE],
name: desiredName,
memory: {
role: 'worker',
memoryVersion: 1
},
directions: [TOP, RIGHT, BOTTOM, LEFT]
});
if (outcome.status !== 'spawn-submitted') {
console.log(JSON.stringify({
type: 'spawn-request-failed',
spawnName: spawn?.name ?? null,
creepName: desiredName,
...outcome
}));
}
};
Return-code table
| Code | Likely cause | Next check |
|---|---|---|
OK | The Spawn accepted the request. | Inspect spawn.spawning on later ticks. |
ERR_NOT_OWNER | The Spawn is not yours. | Check ownership and selected object. |
ERR_NAME_EXISTS | The name is already used. | Inspect Game.creeps and your naming strategy. |
ERR_BUSY | The Spawn is already spawning. | Coordinate all spawn callers. |
ERR_NOT_ENOUGH_ENERGY | Spawn and Extension Energy is insufficient. | Compare body cost with current energyAvailable. |
ERR_INVALID_ARGS | Body, name, directions, or options are malformed. | Validate each argument independently. |
ERR_RCL_NOT_ENOUGH | The Spawn is not active at the current RCL. | Check Controller ownership, level, and spawn.isActive(). |
When to use energyStructures
By default, the method draws from Spawns and Extensions in the room. An explicit list controls which eligible structures are used and in what order:
const energyStructures = [
spawn,
...spawn.room.find(FIND_MY_STRUCTURES, {
filter: structure =>
structure.structureType === STRUCTURE_EXTENSION
})
];
const result = spawn.spawnCreep(
body,
name,
{
memory,
energyStructures
}
);
Do not include Storage or Containers. The option accepts Spawns and Extensions used for the spawning process.
Debugging checklist
- Save the local validation, dry-run result, and final result separately.
- Confirm the Spawn exists, is yours, is active, and is idle.
- Validate body array length and every body-part constant.
- Compute cost from
BODYPART_COST. - Compare cost with current
room.energyAvailable. - Validate name type, length, and uniqueness.
- Use a plain object for initial Memory.
- Validate optional directions and energy structures.
- Coordinate all code that can call the same Spawn.
- Use the error-code reference for shared constants.
Scope and next steps
This guide does not design quotas, replacement timing, role priorities, multi-Spawn scheduling, boost plans, or automatic names for a large colony. Continue with building a body from current Energy.
Frequently asked questions
Does dryRun start spawning?
No. It only checks the current request.
Why can the real call fail after a successful dry run?
Another caller can use the Spawn, name, or Energy later in the tick.
Does energyAvailable include Storage?
No. It represents current Energy in the room's Spawns and Extensions.
Can spawnCreep() return ERR_NOT_IN_RANGE?
No. That code is not part of the documented return set for this method.