CONSTRUCTION · ROAD SITE PLACEMENT

How to Create One Road Construction Site Safely

Use a one-time Memory request, validate a visible room and 0–49 coordinates, allow Road placement on natural wall terrain, reject an existing Road or Construction Site, respect MAX_CONSTRUCTION_SITES, disable before createConstructionSite(), and verify later.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — Room.createConstructionSite(), Game.constructionSites, Road terrain behavior, coordinates and return codes · Scope boundary: The request permits only STRUCTURE_ROAD; it is not a generic blueprint or arbitrary-structure placement API

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Room.createConstructionSite(), Game.constructionSites, Road terrain behavior, coordinates and return codes
Scope boundary
The request permits only STRUCTURE_ROAD; it is not a generic blueprint or arbitrary-structure placement API
Execution boundary
OK schedules site creation; the Construction Site object must be observed on a later tick
JavaScript syntax
Passed
Offline placement review
Passed — request, coordinates, visibility, existing Road, existing site, global site limit, natural wall and ready states
Screeps Console test
Pending
Live Road placement, wall terrain, RCL, special-tile and site-limit test
Pending
Last verified
July 26, 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.

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.

Handle return codes

CodeTypical meaningReview
OKSite creation scheduledInspect the tile later
ERR_NOT_OWNERController state disallows placementOwner and reservation
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 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.