Quick answer
Read site.progress and site.progressTotal. Calculate remaining work with Math.max(0, total - progress) and a display percentage only when the total is positive. Use FIND_MY_CONSTRUCTION_SITES for one visible room or Game.constructionSites for your current site collection, report at a controlled interval, and do not convert remaining work into a completion time without Builder throughput evidence.
Use progress and progressTotal
| Field | Meaning |
|---|---|
progress | Construction progress currently accumulated |
progressTotal | Total progress required to complete the Structure |
The object does not provide a built-in percent, remaining or estimatedTicks field.
Calculate remaining work and percentage
function summarizeConstructionProgress(site) {
const progress = Number.isFinite(site?.progress)
? site.progress
: 0;
const total = Number.isFinite(site?.progressTotal)
? site.progressTotal
: 0;
const remaining = Math.max(0, total - progress);
const percent = total > 0
? Math.min(
100,
Math.max(
0,
Math.floor((progress / total) * 100)
)
)
: 0;
return {
progress,
total,
remaining,
percent
};
}
The clamps protect a display layer from malformed or transitional input. They do not modify the official object.
Report one visible room
function getVisibleRoomSiteReport(roomName) {
const room = typeof roomName === 'string'
? Game.rooms[roomName]
: null;
if (!room) {
return {
roomVisible: false,
roomName,
sites: []
};
}
const sites = room.find(
FIND_MY_CONSTRUCTION_SITES
);
return {
roomVisible: true,
roomName: room.name,
sites: sites.map(site => ({
id: site.id,
structureType: site.structureType,
roomName: site.pos.roomName,
x: site.pos.x,
y: site.pos.y,
...summarizeConstructionProgress(site)
}))
};
}
FIND_MY_CONSTRUCTION_SITES deliberately excludes another player's visible sites. Choose a different query only when the monitoring goal is different.
Report all owned sites safely
function getAllOwnedConstructionSiteReport() {
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)
}));
}
Do not require site.room.name. The room property may be unavailable when the room is not visible, while site.pos.roomName remains suitable for identification.
Use deterministic sorting
function sortConstructionReport(items) {
return [...items].sort((left, right) =>
left.remaining - right.remaining
|| left.roomName.localeCompare(right.roomName)
|| left.structureType.localeCompare(
right.structureType
)
|| left.id.localeCompare(right.id)
);
}
Remaining-work-first is a reporting choice, not a Builder priority. Stable ties prevent the Console order from changing without a meaningful state change.
Do not expect same-tick progress changes
Player code reads the current tick state and submits commands. A Builder can receive OK from build(), but code later in the same loop should not assume site.progress already contains the resolved increase.
function snapshotSite(site) {
return {
gameTick: Game.time,
siteId: site.id,
progress: site.progress,
progressTotal: site.progressTotal
};
}
Recover the site by ID on a later tick, or inspect the coordinate if the object has disappeared.
Diagnose an empty site list
- The room may not be visible.
- There may be no owned sites in the room.
- The site may have completed and become a Structure.
- The site may have been removed.
- The code may be using a query that excludes the visible site.
function inspectFinishedOrMissingSite(snapshot) {
const site = Game.getObjectById(snapshot.siteId);
const room = Game.rooms[snapshot.roomName];
if (site) {
return {
state: 'site-visible',
progress: site.progress,
progressTotal: site.progressTotal
};
}
if (!room) {
return { state: 'room-not-visible' };
}
const structures = room.lookForAt(
LOOK_STRUCTURES,
snapshot.x,
snapshot.y
);
return structures.some(structure =>
structure.structureType === snapshot.structureType
)
? { state: 'completed-structure-observed' }
: { state: 'site-missing-or-removed' };
}
Do not turn remaining work into an unsupported ETA
Completion time depends on active Builder WORK parts, carried Energy, range, movement, roads, task interruptions, spawning, damage and competing sites. A current remaining value is useful state, but not enough to promise a tick count or wall-clock time.
Complete periodic report
function logConstructionSiteReport(
roomName,
interval = 50
) {
if (
!Number.isInteger(interval)
|| interval <= 0
|| Game.time % interval !== 0
) {
return;
}
const report = getVisibleRoomSiteReport(roomName);
if (!report.roomVisible) {
console.log(JSON.stringify({
type: 'construction-site-report',
roomName,
status: 'room-not-visible'
}));
return;
}
const sites = sortConstructionReport(
report.sites
);
console.log(JSON.stringify({
type: 'construction-site-report',
gameTick: Game.time,
roomName,
siteCount: sites.length,
sites
}));
}
module.exports.loop = function () {
logConstructionSiteReport('W1N1', 50);
};
The 50-tick interval is a logging policy. It can be changed without changing Construction Site mechanics.
Debugging checklist
- Choose room-level or all-site reporting intentionally.
- Read
progressandprogressTotal. - Clamp remaining work at zero.
- Avoid division by zero.
- Clamp display percentage from 0 to 100.
- Use stable sorting.
- Use
site.pos.roomNamewhen room visibility is uncertain. - Rate-limit Console output.
- Distinguish completion from removal.
- Do not promise an ETA without throughput evidence.
Scope and next steps
This guide does not select Builder targets, predict completion time, create sites, remove sites or schedule Energy delivery. Continue with the reviewed Structure destruction workflow for a completed misplaced Extension.
Frequently asked questions
Why can progress exceed total in an offline test?
The clamp makes the display function defensive. A normal live Construction Site should follow official state, but pure functions should still avoid negative remaining work or percentages above 100.
Why sort sites closest to completion first?
It makes the report easy to scan. It does not tell Builders which site to choose.
Can Game.constructionSites include a site in an invisible room?
Use the object collection and RoomPosition for identification, while treating the optional room object as unavailable unless present.