Quick answer
Store an explicit positive hitsLimit for each room. Select only Walls and Ramparts whose current hits are below both that limit and their current hitsMax. Order candidates by lowest hits, then range and stable ID. Require a non-spawning owned Creep with Energy and at least one active WORK part, call repair(), move to range 3 only on ERR_NOT_IN_RANGE, save every result, and stop when no target remains below the current stage.
Do not repair every fortification to hitsMax
const unsafeCandidates = room.find(FIND_STRUCTURES, {
filter: structure =>
structure.hits < structure.hitsMax
});
Walls and Ramparts can remain below their maximum for a very long time. A generic damaged-structure filter can make them absorb most repair Energy and crowd out upgrading, construction, ordinary maintenance and reserves.
Make the hits limit room-specific
Memory.defenseRepair ??= {};
Memory.defenseRepair.W1N1 = {
enabled: true,
hitsLimit: 100000
};
The number is an example, not an official defense standard. Different rooms need different stages based on RCL, Energy income, Storage reserves, threat frequency, Tower coverage, repairer count and the value protected by each Rampart.
function readDefenseRepairConfig(roomName) {
const config = Memory.defenseRepair?.[roomName];
if (
!config
|| config.enabled !== true
|| !Number.isFinite(config.hitsLimit)
|| config.hitsLimit <= 0
) {
return {
enabled: false,
hitsLimit: null
};
}
return {
enabled: true,
hitsLimit: config.hitsLimit
};
}
Select the weakest eligible target
function selectDefenseRepairTarget(
creep,
structures,
hitsLimit
) {
if (!Number.isFinite(hitsLimit) || hitsLimit <= 0) {
return null;
}
const candidates = structures.filter(structure =>
(
structure.structureType === STRUCTURE_WALL
|| structure.structureType === STRUCTURE_RAMPART
)
&& Number.isFinite(structure.hits)
&& Number.isFinite(structure.hitsMax)
&& structure.hits < structure.hitsMax
&& structure.hits < hitsLimit
);
return [...candidates].sort((left, right) => {
if (left.hits !== right.hits) {
return left.hits - right.hits;
}
const rangeDifference =
creep.pos.getRangeTo(left)
- creep.pos.getRangeTo(right);
if (rangeDifference !== 0) {
return rangeDifference;
}
return left.id.localeCompare(right.id);
})[0] || null;
}
Weakest-first evens out obvious thin points. It is not a complete fortification-value model: a Rampart over a Spawn and an outer Wall can have different strategic importance.
Require Energy and active WORK
function inspectRepairCreep(creep) {
if (!creep || creep.my !== true) {
return { ready: false, reason: 'creep-missing' };
}
if (creep.spawning === true) {
return { ready: false, reason: 'creep-spawning' };
}
const energy = creep.store.getUsedCapacity(
RESOURCE_ENERGY
);
const activeWork = creep.getActiveBodyparts(WORK);
if (energy <= 0) {
return { ready: false, reason: 'energy-empty' };
}
if (activeWork <= 0) {
return { ready: false, reason: 'no-active-work' };
}
return {
ready: true,
reason: 'ready',
energy,
activeWork
};
}
A WORK part in the original body is not enough after damage. Use current active parts.
Complete staged repair example
function runDefenseRepair(room, creep) {
if (!room || !creep || creep.room.name !== room.name) {
return { status: 'room-or-creep-unavailable' };
}
const config = readDefenseRepairConfig(room.name);
if (!config.enabled) {
return { status: 'repair-disabled' };
}
const creepState = inspectRepairCreep(creep);
if (!creepState.ready) {
return { status: creepState.reason };
}
const target = selectDefenseRepairTarget(
creep,
room.find(FIND_STRUCTURES),
config.hitsLimit
);
if (!target) {
return {
status: 'stage-complete',
hitsLimit: config.hitsLimit
};
}
const before = {
gameTick: Game.time,
targetId: target.id,
targetType: target.structureType,
targetHits: target.hits,
targetHitsMax: target.hitsMax,
hitsLimit: config.hitsLimit,
range: creep.pos.getRangeTo(target),
creepEnergy: creepState.energy,
activeWork: creepState.activeWork
};
const result = creep.repair(target);
if (result === ERR_NOT_IN_RANGE) {
const moveResult = creep.moveTo(target, {
reusePath: 5,
range: 3
});
return {
status: 'moving-to-target',
result,
moveResult,
before
};
}
return {
status: result === OK
? 'repair-scheduled'
: 'repair-rejected',
result,
before
};
}
module.exports.loop = function () {
const room = Game.rooms.W1N1;
const creep = Game.creeps.Repairer1;
const outcome = runDefenseRepair(room, creep);
if (
outcome.status === 'repair-rejected'
|| outcome.status === 'stage-complete'
|| Game.time % 100 === 0
) {
console.log(JSON.stringify({
type: 'fortification-repair-status',
roomName: room?.name || null,
creepName: creep?.name || null,
...outcome
}));
}
};
Move to repair range 3
const moveResult = creep.moveTo(target, {
range: 3,
reusePath: 5
});
Roads, traffic and obstacles can still make movement fail, so preserve the movement result instead of assuming the Creep arrived.
Stop at the current stage
The selector requires both structure.hits < hitsLimit and structure.hits < structure.hitsMax. If the configured limit exceeds a current maximum, fully repaired Structures still leave the candidate list.
function isBelowDefenseStage(structure, hitsLimit) {
return Number.isFinite(hitsLimit)
&& hitsLimit > 0
&& structure.hits < hitsLimit
&& structure.hits < structure.hitsMax;
}
Raise the stage separately
Do not increment the limit merely because this tick has no target. A separate policy can require all current targets to meet the stage, Storage Energy above a reserve, no active battle, non-urgent Controller work, and explicit configuration approval.
function mayReviewNextDefenseStage(input) {
return Boolean(
input.currentStageComplete
&& input.storageEnergy >= input.storageReserve
&& input.hostileCount === 0
&& input.playerApproved
);
}
This returns permission to review a change, not a new automatic limit.
Verify later state
function inspectRepairResult(before) {
const target = Game.getObjectById(before.targetId);
return target
? {
targetFound: true,
hitsBefore: before.targetHits,
hitsNow: target.hits,
hitsLimit: before.hitsLimit
}
: {
targetFound: false,
hitsBefore: before.targetHits,
hitsLimit: before.hitsLimit
};
}
Other Creeps, Towers, damage and boosts can affect the observed hit delta, so do not attribute the entire change to one action without more evidence.
Handle return codes
| Code | Typical cause | Response |
|---|---|---|
OK | Repair scheduled | Re-read hits later |
ERR_NOT_OWNER | Creep not yours | Current Creep ownership |
ERR_BUSY | Creep spawning | Wait |
ERR_NOT_ENOUGH_RESOURCES | No Energy | Supply chain and Store |
ERR_INVALID_TARGET | Target invalid or gone | Refresh current selection |
ERR_NOT_IN_RANGE | Beyond repair range | Move to range 3 |
ERR_NO_BODYPART | No active WORK | Body damage and replacement |
Debugging checklist
- Use a positive room-specific hits limit.
- Filter only Walls and Ramparts.
- Require hits below both stage and hitsMax.
- Sort by hits, range and stable ID.
- Require Creep Energy and active WORK.
- Save repair and movement return codes.
- Move to range 3.
- Stop when the stage is complete.
- Raise stages through a separate reviewed policy.
- Verify current hits later.
Scope and next steps
This guide does not value individual fortification positions, predict boosts, coordinate Towers, choose room-wide emergency priorities, supply the repairer, or automatically raise stages. Return to the English article library for defense and logistics topics.
Frequently asked questions
Why select lowest hits instead of lowest ratio?
For high-maximum fortifications, an absolute staged target is easier to budget and compare. A ratio can remain tiny for a very long time.
Can Tower repair use this selector?
The target policy can be adapted, but Tower repair has different range behavior, Energy ownership and action allocation. Do not copy the Creep return-code table unchanged.
Should Ramparts and Walls share one limit?
They can for a simple stage, but a mature defense may use separate targets based on protected Structures and wall placement.