CONSTRUCTION · PROGRESS AND EVENT IDENTITY

Measure Construction Progress and Verify the Exact Builder Event

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

Have a Builder advance the SiteExecution pathVerify EVENT_BUILD on the next tickRuntime evidenceTrack the Site by stable IDObject identitySeparate completion from explicit removalState boundary
Verification statusExisting English route: Preserved · Official docs: Checked — ConstructionSite, Creep.build(), Room.getEventLog(), object identity, and tick timing · Static API review: Passed — progress reporting, exact Builder/Site event identity, completion, removal, and missed window

VERIFICATION

Evidence and test status

Existing English route
Preserved
Official docs
Checked — ConstructionSite, Creep.build(), Room.getEventLog(), object identity, and tick timing
Static API review
Passed — progress reporting, exact Builder/Site event identity, completion, removal, and missed window
Offline state review
Passed — progress clamps, preflight, accepted, active, completed, missing, ambiguous, and missed-window states
Human editorial pass
Passed
Screeps Console test
Pending
Live multi-tick verification
Pending
Genuine room or Console screenshots
Pending
Last verified
August 3, 2026

Use this guide when

Use the first half of this guide to report current Construction Site progress. Use the tracked-build workflow when you need to prove that one specific Builder's accepted build() intent produced the next tick's build event.

Choose the site-creation guide when the site does not exist yet. This page does not create, remove, or prioritize sites for an entire room.

Read the official progress fields

function summarizeConstructionProgress(site) {
  const progress = Number.isFinite(site?.progress)
    ? site.progress
    : 0;
  const total = Number.isFinite(site?.progressTotal)
    ? site.progressTotal
    : 0;

  return {
    progress,
    total,
    remaining: Math.max(0, total - progress),
    percent: total > 0
      ? Math.min(
          100,
          Math.max(
            0,
            Math.floor((progress / total) * 100)
          )
        )
      : 0
  };
}

remaining and percent are display calculations. They are not fields on the official object and they do not provide a completion time.

Build a stable current-state report

function getOwnedConstructionReport() {
  return Object.values(Game.constructionSites)
    .map(site => ({
      id: site.id,
      structureType: site.structureType,
      roomName: site.pos.roomName,
      roomVisible: Boolean(site.room),
      x: site.pos.x,
      y: site.pos.y,
      ...summarizeConstructionProgress(site)
    }))
    .sort((left, right) =>
      left.remaining - right.remaining
      || left.roomName.localeCompare(
        right.roomName
      )
      || left.structureType.localeCompare(
        right.structureType
      )
      || left.id.localeCompare(right.id)
    );
}

Use site.pos.roomName for identity. The optional room object may be unavailable when the room is not visible.

Preflight one tracked Builder action

The diagnostic below tracks one accepted build at a time. It is not a room-wide Builder scheduler.

function inspectTrackedBuildRequest(
  creep,
  site
) {
  if (
    !creep
    || creep.my !== true
    || creep.spawning === true
  ) {
    return { ready: false, reason: 'builder-unavailable' };
  }

  if (!site || site.my !== true) {
    return { ready: false, reason: 'site-unavailable' };
  }

  if (creep.room.name !== site.pos.roomName) {
    return { ready: false, reason: 'different-room' };
  }

  if (creep.getActiveBodyparts(WORK) <= 0) {
    return { ready: false, reason: 'no-active-work' };
  }

  if (
    creep.store.getUsedCapacity(
      RESOURCE_ENERGY
    ) <= 0
  ) {
    return { ready: false, reason: 'no-energy' };
  }

  if (!creep.pos.inRangeTo(site, 3)) {
    return { ready: false, reason: 'not-in-range' };
  }

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

Range, active parts, Energy, ownership, and current object identity are preconditions. They do not prove the intent settled.

Submit and save the exact identities

function submitTrackedBuild(
  creep,
  site
) {
  const room = creep?.room;
  const existing = room?.memory?.trackedBuild;

  if (existing) {
    return {
      status: 'tracked-build-already-pending',
      submittedAt: existing.submittedAt,
      builderId: existing.builderId,
      siteId: existing.siteId
    };
  }

  const check = inspectTrackedBuildRequest(
    creep,
    site
  );

  if (!check.ready) {
    if (
      check.reason === 'not-in-range'
      && creep
      && site
    ) {
      return {
        status: 'moving-to-site',
        moveResult: creep.moveTo(site, {
          range: 3,
          reusePath: 10
        })
      };
    }

    return {
      status: 'build-preflight-failed',
      reason: check.reason
    };
  }

  const progressBefore = site.progress;
  const result = creep.build(site);

  if (result !== OK) {
    return {
      status: 'build-rejected',
      builderId: creep.id,
      siteId: site.id,
      result
    };
  }

  room.memory.trackedBuild = {
    submittedAt: Game.time,
    builderId: creep.id,
    builderName: creep.name,
    siteId: site.id,
    roomName: site.pos.roomName,
    x: site.pos.x,
    y: site.pos.y,
    structureType: site.structureType,
    progressBefore,
    progressTotal: site.progressTotal
  };

  return {
    status: 'build-accepted',
    builderId: creep.id,
    siteId: site.id,
    submittedAt: Game.time,
    result
  };
}

OK means the intent was accepted in the current tick. The pending record is created only after that result.

Match the next tick's exact build event

function findCompletedStructure(
  room,
  pending
) {
  return room.lookForAt(
    LOOK_STRUCTURES,
    pending.x,
    pending.y
  ).find(structure =>
    structure.structureType
      === pending.structureType
  ) ?? null;
}

function verifyTrackedBuild(room) {
  const pending = room?.memory?.trackedBuild;

  if (!pending) {
    return { status: 'no-tracked-build' };
  }

  if (Game.time <= pending.submittedAt) {
    return { status: 'accepted-this-tick' };
  }

  if (Game.time !== pending.submittedAt + 1) {
    delete room.memory.trackedBuild;

    return {
      status: 'verification-window-missed',
      submittedAt: pending.submittedAt,
      checkedAt: Game.time
    };
  }

  const matches = room.getEventLog()
    .filter(event =>
      event.event === EVENT_BUILD
      && event.objectId === pending.builderId
      && event.data?.targetId === pending.siteId
    );

  const site = Game.getObjectById(
    pending.siteId
  );
  const structure = site
    ? null
    : findCompletedStructure(room, pending);

  if (matches.length === 0) {
    delete room.memory.trackedBuild;

    return {
      status: structure
        ? 'structure-observed-without-matching-event'
        : site
          ? 'site-active-without-matching-event'
          : 'site-missing-without-matching-event',
      progressNow: site?.progress ?? null,
      structureId: structure?.id ?? null
    };
  }

  if (matches.length > 1) {
    delete room.memory.trackedBuild;

    return {
      status: 'matching-build-event-ambiguous',
      matchCount: matches.length
    };
  }

  const event = matches[0];
  delete room.memory.trackedBuild;

  return {
    status: structure
      ? 'build-event-verified-site-completed'
      : site
        ? 'build-event-verified-site-active'
        : 'build-event-verified-site-missing',
    eventAmount: event.data?.amount ?? null,
    progressBefore: pending.progressBefore,
    progressNow: site?.progress ?? null,
    structureId: structure?.id ?? null
  };
}

The event binds one Builder ID to one Site ID. The Site's net progress is supporting context: other Builders can add progress in the same tick. If the Site disappeared, the exact coordinate and structure type separate observed completion from a missing or removed Site.

Verify before submitting the next sample

module.exports.loop = function () {
  const room = Game.rooms.W1N1;

  if (!room) {
    return;
  }

  const verification = verifyTrackedBuild(room);

  if (verification.status === 'accepted-this-tick') {
    return;
  }

  const builder = Game.creeps.Builder1;
  const site = room.find(
    FIND_MY_CONSTRUCTION_SITES
  )[0];

  if (!builder || !site) {
    return;
  }

  const submission = submitTrackedBuild(
    builder,
    site
  );

  if (submission.status === 'build-rejected') {
    console.log(JSON.stringify({
      type: 'tracked-build',
      roomName: room.name,
      ...submission
    }));
  }
};

A real production loop should choose the Site deterministically and should not use array position zero as its permanent priority rule. The abbreviated selection keeps this section focused on event identity.

Interpret disappearance carefully

ObservationWhat it proves
Matching EVENT_BUILD and Site still presentThe exact Builder contributed to that Site; current progress is net room state.
Matching event and completed structure at the same coordinateThe exact Builder contributed in the completion tick and the structure replacement is visible.
Structure observed but no matching eventCompletion is visible, but it cannot be attributed to this tracked Builder.
Site and structure both absentThe Site is missing or was removed; completion is not proven.
Room not visible during the next-tick windowThe exact event cannot be recovered later from this one-tick log window.

Do not invent an ETA from remaining work

Completion time depends on active WORK, carried Energy, range, movement, fatigue, traffic, task allocation, interruptions, Builder replacement, competing Sites, and the number of accepted build intents. A remaining-progress value is useful state, not a tick or wall-clock promise.

Return-code boundaries

CodeMeaning hereResponse
OKThe build intent was accepted.Save Builder and Site IDs and inspect the next tick.
ERR_NOT_IN_RANGEThe Builder is farther than range 3.Move, then retry on a later tick.
ERR_NOT_ENOUGH_RESOURCESThe Builder lacks Energy.Return to its Energy phase.
ERR_NO_BODYPARTNo active WORK part remains.Replace or reassign the Creep.
ERR_INVALID_TARGETThe target is not a valid owned Construction Site.Refresh the Site object and ownership.
ERR_BUSYThe Creep is spawning.Wait for the Creep to finish.

Production adaptation notes

Use the current-state report continuously, but sample exact Builder events only when diagnostics require attribution. Keep one pending sample per room or use unique request IDs for multiple samples. Report missed-window and ambiguous states, then release the pending slot so a later diagnostic sample can run; never rewrite those states as successful progress.

Verification status and evidence boundary

The progress calculations, event identity, completion/removal branches, and missed-window behavior were checked statically and offline. No real Builder, Site replacement, room-visibility loss, Console output, or live multi-tick event sequence was available. Those checks remain pending.

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.