CONSTRUCTION · ROAD SITE PLACEMENT

How to Create One Road Construction Site Safely

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

Apply Site creation to your first ExtensionPractical applicationTrack progress after placementNext stepRemove a misplaced Site safelyCorrectionUnderstand deletion after construction completesLifecycle
Verification statusExisting English route: Preserved — no URL, slug, Canonical, or Chinese mapping change · Official documentation: Checked August 18, 2026 — Room.createConstructionSite(), current return codes, global Construction Site limit behavior, LOOK_CONSTRUCTION_SITES, and Road placement on natural wall terrain · Static code review: Passed — one-time request identity, tile inspection, Road-on-wall exception, account-wide site limit context, submit-before-verify separation, and exact next-tick verification window remain explicit

VERIFICATION

Evidence and test status

Existing English route
Preserved — no URL, slug, Canonical, or Chinese mapping change
Official documentation
Checked August 18, 2026 — Room.createConstructionSite(), current return codes, global Construction Site limit behavior, LOOK_CONSTRUCTION_SITES, and Road placement on natural wall terrain
Static code review
Passed — one-time request identity, tile inspection, Road-on-wall exception, account-wide site limit context, submit-before-verify separation, and exact next-tick verification window remain explicit
Evidence level
Official-documentation review and static code review only; no live Road-site creation sequence is claimed
Console test pending
Not run in this editorial pass
Screeps Console test
Pending — no real-shard createConstructionSite() transcript was collected for this revision
Live multi-tick verification pending
No live accepted request, exact next-tick Site observation, missed-window case, ERR_FULL, or hostile claim/reservation failure was collected for this revision
Genuine in-game screenshot
Pending — no new first-party Construction Site screenshot was captured for this revision
Last editorial review
August 18, 2026

Quick answer

Store one reviewed request containing a room, X, Y and STRUCTURE_ROAD. Require a visible room, integer coordinates from 0 through 49, no existing Road, no existing Construction Site, and a current site count below MAX_CONSTRUCTION_SITES. Record wall terrain instead of rejecting it, set request.enabled = false, call room.createConstructionSite() once, save the return code, and inspect the coordinate again on a later tick.

A Construction Site is not a completed Road

Room.createConstructionSite() creates a planning object. It does not produce a completed Structure and does not spend Builder Energy by itself.

StageObjectAction
PlannedConstructionSiteCreate or remove the site
Under constructionConstructionSiteCreeps call build()
CompletedStructureRoadUse, repair or destroy the Structure

Do not reject natural wall terrain for Roads

function inspectRoadTerrain(room, x, y) {
  const terrain = room.getTerrain().get(x, y);

  return {
    terrain,
    onNaturalWall:
      terrain === TERRAIN_MASK_WALL
  };
}

Roads are allowed on natural wall terrain, with different construction cost behavior. That does not mean a Spawn, Extension or arbitrary Structure can use the same placement rule.

Create a one-time Road request

Memory.constructionSiteRequest = {
  enabled: true,
  roomName: 'W1N1',
  x: 20,
  y: 20,
  structureType: STRUCTURE_ROAD
};

The narrow structure type prevents a single-coordinate example from becoming an unreviewed generic blueprint engine.

Build a testable placement plan

function evaluateRoadSiteRequest(input) {
  const request = input.request;

  if (!request || request.enabled !== true) {
    return { ready: false, reason: 'disabled' };
  }

  if (
    typeof request.roomName !== 'string'
    || !Number.isInteger(request.x)
    || !Number.isInteger(request.y)
    || request.x < 0
    || request.x > 49
    || request.y < 0
    || request.y > 49
    || request.structureType !== STRUCTURE_ROAD
  ) {
    return { ready: false, reason: 'invalid-request' };
  }

  if (!input.roomVisible) {
    return { ready: false, reason: 'room-not-visible' };
  }

  if (input.hasRoad) {
    return { ready: false, reason: 'road-exists' };
  }

  if (input.hasSite) {
    return { ready: false, reason: 'site-exists' };
  }

  if (
    !Number.isInteger(input.siteCount)
    || input.siteCount >= MAX_CONSTRUCTION_SITES
  ) {
    return { ready: false, reason: 'site-limit' };
  }

  return { ready: true, reason: 'ready' };
}

This preflight catches obvious stale inputs. It cannot reproduce every Controller, edge, terrain-overlay and structure-count rule, so the API result remains authoritative.

Complete Road site handler

function inspectRoadSiteTile(room, x, y) {
  const structures = room.lookForAt(
    LOOK_STRUCTURES,
    x,
    y
  );
  const sites = room.lookForAt(
    LOOK_CONSTRUCTION_SITES,
    x,
    y
  );
  const terrain = inspectRoadTerrain(room, x, y);

  return {
    hasRoad: structures.some(structure =>
      structure.structureType === STRUCTURE_ROAD
    ),
    hasSite: sites.length > 0,
    onNaturalWall: terrain.onNaturalWall
  };
}
function handleRoadSiteRequest() {
  const request = Memory.constructionSiteRequest;
  if (!request || request.enabled !== true) {
    return { status: 'disabled' };
  }

  const coordinatesValid =
    Number.isInteger(request.x)
    && Number.isInteger(request.y)
    && request.x >= 0
    && request.x <= 49
    && request.y >= 0
    && request.y <= 49;
  const room = typeof request.roomName === 'string'
    ? Game.rooms[request.roomName]
    : null;
  const tile = room && coordinatesValid
    ? inspectRoadSiteTile(room, request.x, request.y)
    : {
        hasRoad: false,
        hasSite: false,
        onNaturalWall: null
      };
  const siteCount = Object.keys(
    Game.constructionSites
  ).length;
  const plan = evaluateRoadSiteRequest({
    request,
    roomVisible: Boolean(room),
    hasRoad: tile.hasRoad,
    hasSite: tile.hasSite,
    siteCount
  });

  request.checkedAt = Game.time;
  request.status = plan.reason;

  if (!plan.ready) {
    request.enabled = false;
    return { status: plan.reason };
  }

  request.enabled = false;
  request.status = 'submitted';
  request.submittedAt = Game.time;
  request.before = {
    roomName: room.name,
    x: request.x,
    y: request.y,
    structureType: STRUCTURE_ROAD,
    onNaturalWall: tile.onNaturalWall,
    siteCount
  };

  const result = room.createConstructionSite(
    request.x,
    request.y,
    STRUCTURE_ROAD
  );

  request.result = result;
  request.resultAt = Game.time;
  request.status = result === OK
    ? 'accepted'
    : 'failed-review-required';

  return {
    status: request.status,
    result,
    before: request.before
  };
}
module.exports.loop = function () {
  const outcome = handleRoadSiteRequest();

  if (
    outcome.status === 'accepted'
    || outcome.status === 'failed-review-required'
  ) {
    console.log(JSON.stringify({
      type: 'road-site-request-result',
      ...outcome
    }));
  }
};

Disable stale and submitted requests

The handler disables preflight failures because a request that is invalid now should not silently execute when visibility, site count or tile occupancy changes later. It also disables immediately before the API call to prevent repetition after an exception.

request.enabled = false;
const result = room.createConstructionSite(
  request.x,
  request.y,
  STRUCTURE_ROAD
);

Respect the current site limit

const mySiteCount = Object.keys(
  Game.constructionSites
).length;

const capacityRemaining = Math.max(
  0,
  MAX_CONSTRUCTION_SITES - mySiteCount
);

The local count is diagnostic. Handle ERR_FULL even after preflight because current rules and simultaneous actions can still reject placement.

ERR_FULL is an account-wide Construction Site limit boundary. The planner's Object.keys(Game.constructionSites).length check is intentionally global; a low site count in only the target room cannot rule out the global limit.

Verify the next tick

function verifyRoadSite(request) {
  const room = Game.rooms[request.roomName];
  if (!room) {
    return { verified: false, reason: 'room-not-visible' };
  }

  const site = room.lookForAt(
    LOOK_CONSTRUCTION_SITES,
    request.x,
    request.y
  ).find(item =>
    item.structureType === STRUCTURE_ROAD
  ) || null;

  return site
    ? { verified: true, siteId: site.id }
    : { verified: false, reason: 'site-not-observed' };
}

Only a later matching object proves the site became observable. A Builder and Energy supply are still required for completion.

Bind verification to the exact next tick

The request record already stores submittedAt. Use that identity to prevent a much later observation from being presented as proof of what happened immediately after the accepted call. A Construction Site could be built or removed before a delayed check.

function verifySubmittedRoadSiteNextTick(request) {
  if (!Number.isInteger(request?.submittedAt)) {
    return {
      verified: false,
      reason: 'invalid-submission-record'
    };
  }

  if (Game.time === request.submittedAt) {
    return {
      verified: false,
      reason: 'waiting-for-next-tick'
    };
  }

  if (Game.time !== request.submittedAt + 1) {
    return {
      verified: false,
      reason: 'verification-window-missed'
    };
  }

  return verifyRoadSite(request);
}

verification-window-missed is an evidence boundary, not proof that site creation failed. Inspect current world state separately instead of backfilling a next-tick claim from a later observation.

Handle return codes

CodeTypical meaningReview
OKSite creation scheduledInspect the tile later
ERR_NOT_OWNERThe room is claimed or reserved by a hostile player.Inspect current Controller ownership and reservation before retrying.
ERR_INVALID_TARGETTile cannot accept the siteTerrain and existing objects
ERR_FULLConstruction Site limit reachedGame.constructionSites
ERR_INVALID_ARGSCoordinates or type invalid0–49 integers and Road constant
ERR_RCL_NOT_ENOUGHController or structure limitRCL and room structures

Debugging checklist

  • Require a one-time enabled request.
  • Allow only STRUCTURE_ROAD.
  • Validate 0–49 integer coordinates.
  • Require current room visibility.
  • Check existing structures and sites.
  • Record natural wall terrain without rejecting it.
  • Check MAX_CONSTRUCTION_SITES.
  • Disable stale requests.
  • Save the official return code.
  • Verify a matching site later.

Scope and next steps

This guide does not place a path, validate a full room blueprint, create arbitrary Structures, manage site batches, assign Builders or remove old layouts. Continue with Construction Site progress reporting.

Frequently asked questions

Why use MAX_CONSTRUCTION_SITES instead of only 100?

The named game constant makes the code's dependency explicit. The API return remains the final authority.

Why reject any existing site instead of only an existing Road site?

A different site on the same coordinate is a layout conflict that needs review rather than automatic replacement.

Can the request stay enabled until visibility returns?

This guide deliberately does not do that because the old coordinate may no longer reflect current player intent.

Official documentation

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.