CONSTRUCTION · FIRST EXTENSION DEBUGGING

Build Your First Screeps Extension Without Missing a Blocked Site

CURATED PATH

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

Construction

Recommended next steps

Automate building and repair with a BuilderNext lessonCreate a Construction Site safelyAPI deep diveTrack build progress and completionResult verificationDebug Room Energy after Extensions come onlineSystem follow-up
Verification statusChinese source article: Reviewed in full · Official documentation: Checked August 14, 2026 — StructureExtension, RCL structure limits, ConstructionSite, Creep.build(), Store and range behavior · Live multi-tick verification: Pending

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official documentation
Checked August 14, 2026 — StructureExtension, RCL structure limits, ConstructionSite, Creep.build(), Store and range behavior
Live multi-tick verification
Pending
Live boundary test
Pending — no live occupied Extension-site, Safe Mode blocker, progress-completion, or empty-finished-Extension trace was collected
Evidence level
Official documentation, official engine source, repository review and offline reasoning only
Official engine source
Checked current screeps/engine master 80977824199a596d174d392fd0cf8c458c21fcbd: Source harvest submission has no Store-capacity ERR_FULL preflight; the processor can drop overflow beyond Creep Store capacity
Source harvest capacity policy
Project policy — for the unboosted beginner bodies in these lessons, switch out of Source acquisition before another full active-WORK harvest batch would exceed remaining Store capacity
Offline state logic
Passed — the Extension occupancy diagnostics remain intact, Builder hysteresis now prevents the unboosted next Source batch from intentionally overflowing, oversized Source batches stop, and build() progress evidence remains separate
Screeps Console test
Pending — no live near-full Source harvest trace is claimed
Live Source-overflow test
Pending — no live near-full multi-WORK Builder overflow or occupied-site comparison is claimed
Last editorial review
August 17, 2026
Publication status
Published

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

PropertyRCL 2 valueEngineering consequence
Allowed Extensions5Start with one site while learning the build lifecycle.
Energy capacity50 eachA completed Extension must be filled by delivery logic.
Construction progress required3000Completion takes repeated accepted build intents and Energy.
build() range3Do 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

ResultMeaning for this lessonNext check
OKThe build intent was accepted.Verify later progress.
ERR_NOT_OWNERThe acting Creep is not yours.Resolve the correct Builder.
ERR_BUSYThe Creep is still spawning.Wait.
ERR_NO_BODYPARTNo active WORK remains.Inspect body damage.
ERR_NOT_ENOUGH_RESOURCESThe Creep has no carried Energy.Return to acquisition.
ERR_NOT_IN_RANGEThe site is farther than range 3.Move toward range 3.
ERR_INVALID_TARGETThe 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.

Official documentation and engine 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.