Quick answer
At RCL 2, place an owned Extension Construction Site, give a Builder Energy, and call build(site) from range 3. But if an Extension site visibly exists and build() returns ERR_INVALID_TARGET, do more than “find it again next tick.” The current official engine also rejects an obstacle-structure site when a blocking object or Creep occupies the target tile.
That distinction matters because the site can still be a real ConstructionSite while the current build request is invalid.
RCL 2 Extension facts
| Property | RCL 2 value | Engineering consequence |
|---|---|---|
| Allowed Extensions | 5 | Start with one site while learning the build lifecycle. |
| Energy capacity | 50 each | A completed Extension must be filled by delivery logic. |
| Construction progress required | 3000 | Completion takes repeated accepted build intents and Energy. |
build() range | 3 | Do not force the Builder to range 1 just to build. |
The Construction Site and the finished StructureExtension are different objects. When construction finishes, code that saved the site object or ID must expect that site to disappear.
Why an occupied Extension site can return ERR_INVALID_TARGET
The current Creep.build() submission code validates the Construction Site and range, then checks the target tile when the planned structure type is listed as an obstacle.
An Extension is an obstacle structure. If another blocking obstacle object is on that tile, the call returns ERR_INVALID_TARGET. Blocking Creeps can also cause the same result. Under owned-room Safe Mode, the current engine narrows that Creep blocker check to your own Creeps; outside that case it checks Creeps on the tile generally.
This is a build-time boundary, not merely a site-placement boundary. A generic message such as “target is stale” loses useful evidence.
Read the site and its tile before guessing
State impact: read-only.
const creep = Game.creeps['Builder1'];
if (!creep) {
console.log('Builder1 was not found.');
} else {
const site = creep.room.find(FIND_MY_CONSTRUCTION_SITES, {
filter: value =>
value.structureType === STRUCTURE_EXTENSION
})[0];
if (!site) {
console.log('No owned Extension site in this room.');
} else {
console.log(JSON.stringify({
tick: Game.time,
siteId: site.id,
progress: site.progress,
progressTotal: site.progressTotal,
range: creep.pos.getRangeTo(site),
carriedEnergy:
creep.store.getUsedCapacity(RESOURCE_ENERGY),
activeWork: creep.getActiveBodyparts(WORK),
creepsOnTile: site.pos
.lookFor(LOOK_CREEPS)
.map(value => value.name),
structuresOnTile: site.pos
.lookFor(LOOK_STRUCTURES)
.map(value => value.structureType)
}));
}
}
The diagnostic deliberately reports occupants instead of pretending every structure on the tile is a blocker. The engine's obstacle rules determine whether an occupant invalidates the build request.
A safer one-Extension Builder loop
const CREEP_NAME = 'Builder1';
function moveIfNeeded(creep, target, range) {
return creep.moveTo(target, {
range,
reusePath: 5
});
}
function runBuilder(creep) {
const activeWork = creep.getActiveBodyparts(WORK);
const usedEnergy =
creep.store.getUsedCapacity(RESOURCE_ENERGY) ?? 0;
const freeEnergy =
creep.store.getFreeCapacity(RESOURCE_ENERGY) ?? 0;
const energyCapacity =
creep.store.getCapacity(RESOURCE_ENERGY) ?? 0;
const harvestBatch = activeWork * HARVEST_POWER;
if (usedEnergy === 0) {
creep.memory.building = false;
} else if (
freeEnergy === 0
|| (
!creep.memory.building
&& freeEnergy > 0
&& freeEnergy < harvestBatch
)
) {
creep.memory.building = true;
}
if (
!creep.memory.building
&& usedEnergy === 0
&& harvestBatch > energyCapacity
) {
return { status: 'harvest-batch-exceeds-store' };
}
if (creep.memory.building) {
const site = creep.room.find(
FIND_MY_CONSTRUCTION_SITES,
{
filter: value =>
value.structureType === STRUCTURE_EXTENSION
}
)[0];
if (!site) {
return { status: 'no-extension-site' };
}
const actionResult = creep.build(site);
let moveResult = null;
if (actionResult === ERR_NOT_IN_RANGE) {
moveResult = moveIfNeeded(creep, site, 3);
}
return {
status: actionResult === OK
? 'build-accepted'
: 'build-not-accepted',
siteId: site.id,
progressBefore: site.progress,
actionResult,
moveResult
};
}
const source =
creep.pos.findClosestByPath(FIND_SOURCES_ACTIVE);
if (!source) {
return { status: 'no-active-source' };
}
const actionResult = creep.harvest(source);
let moveResult = null;
if (actionResult === ERR_NOT_IN_RANGE) {
moveResult = moveIfNeeded(creep, source, 1);
}
return {
status: actionResult === OK
? 'harvest-accepted'
: 'harvest-not-accepted',
sourceId: source.id,
actionResult,
moveResult
};
}
module.exports.loop = function () {
const creep = Game.creeps[CREEP_NAME];
if (!creep || creep.spawning) {
return;
}
const result = runBuilder(creep);
if (
result.actionResult !== undefined
&& result.actionResult !== OK
&& result.actionResult !== ERR_NOT_IN_RANGE
) {
console.log(JSON.stringify({
tick: Game.time,
creep: creep.name,
...result
}));
}
};
The empty/full switch remains hysteresis: ordinary partial Energy keeps the previous phase. This tutorial adds one conservative project policy for its unboosted mobile Builder: while acquiring from a Source, switch to building when some Energy is already carried and the remaining capacity is smaller than active WORK × HARVEST_POWER. Current Source harvest() has no Store-capacity ERR_FULL preflight, and the checked processor can drop overflow. An unboosted body whose entire Energy capacity is smaller than one full Source batch is rejected by the example instead of silently wasting the excess.
Current Creep.build() return boundary
| Result | Meaning for this lesson | Next check |
|---|---|---|
OK | The build intent was accepted. | Verify later progress. |
ERR_NOT_OWNER | The acting Creep is not yours. | Resolve the correct Builder. |
ERR_BUSY | The Creep is still spawning. | Wait. |
ERR_NO_BODYPART | No active WORK remains. | Inspect body damage. |
ERR_NOT_ENOUGH_RESOURCES | The Creep has no carried Energy. | Return to acquisition. |
ERR_NOT_IN_RANGE | The site is farther than range 3. | Move toward range 3. |
ERR_INVALID_TARGET | The site is invalid, stale, or fails the current obstacle-site occupancy boundary. | Re-resolve the site and inspect the target tile. |
Diagnose ERR_INVALID_TARGET without destroying the evidence
When the result is ERR_INVALID_TARGET, capture these facts before choosing another task:
- the exact site ID and
structureType; - whether that ID still resolves as a Construction Site;
- the Builder's range and active
WORK; - Creeps and structures currently on the site tile;
- whether the room currently has owned Safe Mode, because that changes the engine's Creep blocker subset;
- whether the site finished or disappeared between observations.
Do not immediately fall through to repair or Controller upgrading in the same branch. A hard build error is useful diagnostic state, not a reason to hide the selected task.
Verify progress and completion on later ticks
Record site.progress before an accepted build intent and inspect the site again on a later tick. If the site no longer exists, check the target tile for the finished STRUCTURE_EXTENSION.
OK alone does not prove the exact progress delta. Other builders can contribute, the site can complete, and the current object can disappear after intent processing.
A finished Extension starts as a structure that still needs Energy delivery. Construction completion and filling the Extension are separate workflows.
Valid placement does not eliminate every later build failure
Placing a site successfully proves that the placement request passed its own rules at that time. It does not guarantee that every future build() call will remain valid. Room occupancy can change between ticks.
This is why the article separates site creation, current target validity, build intent acceptance, and later construction progress.
Scope and known limits
- This is one Builder and one Extension target, not a layout planner.
- It does not assign multiple Builders or reserve site work.
- It does not claim a live occupied-site trace; that boundary remains Pending.
- It uses active Source selection and a conservative unboosted next-batch Store guard for a mobile beginner Builder; a fixed miner or boosted harvesting design can use another explicit policy.
Next lesson
After the Extension is complete, continue with the Builder priority guide. For exact long-running construction evidence, use the ConstructionSite progress guide.