Quick answer
Use reserveController() when the mission is temporary remote-room access and ongoing renewal. Use claimController() only for reviewed permanent expansion. Both paths require a currently valid neutral Controller, an owned non-spawning Creep with an active CLAIM part, and range 1. Block another player's reservation. For claims, require an explicit confirmation and available GCL room capacity. Keep reserve missions renewable, but disable a claim mission after OK and verify ownership later.
Reserve and claim solve different goals
| Decision | reserveController() | claimController() |
|---|---|---|
| Purpose | Temporary remote-room control benefits | Permanent owned-room expansion |
| Controller owner | Remains neutral | Becomes yours after success |
| GCL room capacity | Does not consume an owned-room slot | Requires available claim capacity |
| Lifecycle | Usually renewed over time | One-time ownership transition |
| Typical mission | Remote harvesting or temporary access | Build and operate a new owned room |
The API does not choose the strategic goal. Store the intended action explicitly instead of inferring it from a neutral Controller.
Check shared Controller mission requirements
function inspectControllerMissionObjects(creep) {
const controller = creep?.room?.controller || null;
return {
creepExists: Boolean(creep),
creepOwned: creep?.my === true,
creepSpawning: creep?.spawning === true,
activeClaimParts: creep
? creep.getActiveBodyparts(CLAIM)
: 0,
controllerFound: Boolean(controller),
controllerOwned: Boolean(controller?.owner),
reservationUsername:
controller?.reservation?.username ?? null,
reservationTicks:
controller?.reservation?.ticksToEnd ?? null,
range: controller && creep
? creep.pos.getRangeTo(controller)
: null
};
}
A CLAIM part present in the original body is not enough. Use getActiveBodyparts(CLAIM) because a destroyed part no longer provides the action.
Understand renewable reservation state
A successful reserve action adds reservation time according to active CLAIM capability, up to the official reservation limit. The current state is visible through the optional reservation object:
function readControllerReservation(controller) {
const reservation = controller?.reservation;
if (!reservation) {
return {
username: null,
ticksToEnd: 0
};
}
return {
username: reservation.username,
ticksToEnd: reservation.ticksToEnd
};
}
Do not copy ticksToEnd into Memory and decrement it as the source of truth. Re-read the Controller whenever the room is visible.
Calculate room claim capacity
function getClaimCapacity() {
const ownedRoomCount = Object.values(Game.rooms)
.filter(room => room.controller?.my === true)
.length;
const gclLevel = Game.gcl.level;
return {
ownedRoomCount,
gclLevel,
available: Number.isInteger(gclLevel)
? Math.max(0, gclLevel - ownedRoomCount)
: 0
};
}
Owned rooms provide normal visibility, so this is a practical account-level check. The method return code still decides whether the current claim is accepted.
Stop on another player's reservation
function hasHostileReservation(
controller,
creepUsername
) {
const username =
controller?.reservation?.username ?? null;
return Boolean(
username
&& username !== creepUsername
);
}
This guide does not automatically call attackController(), attack the player or wait indefinitely. It records the conflict and leaves the next decision to a separate mission policy.
Build a testable mission decision
function evaluateControllerMission(input) {
if (input.enabled !== true) {
return { ready: false, reason: 'disabled' };
}
if (!['reserve', 'claim'].includes(input.action)) {
return { ready: false, reason: 'invalid-action' };
}
if (!input.creepOwned || input.creepSpawning) {
return { ready: false, reason: 'creep-unavailable' };
}
if (
!Number.isInteger(input.activeClaimParts)
|| input.activeClaimParts <= 0
) {
return { ready: false, reason: 'no-active-claim-part' };
}
if (!input.controllerFound) {
return { ready: false, reason: 'controller-missing' };
}
if (input.controllerOwned) {
return { ready: false, reason: 'controller-owned' };
}
if (
input.reservationUsername
&& input.reservationUsername !== input.creepUsername
) {
return { ready: false, reason: 'hostile-reservation' };
}
if (input.action === 'claim') {
if (input.claimConfirmed !== true) {
return { ready: false, reason: 'claim-not-confirmed' };
}
if (
!Number.isInteger(input.ownedRoomCount)
|| !Number.isInteger(input.gclLevel)
|| input.ownedRoomCount >= input.gclLevel
) {
return { ready: false, reason: 'gcl-not-enough' };
}
}
if (!Number.isInteger(input.range) || input.range > 1) {
return { ready: false, reason: 'move-to-controller' };
}
return { ready: true, reason: 'ready' };
}
claimConfirmed is a project safeguard. It prevents changing a remote reservation task into permanent expansion through one mistyped action value.
Complete Controller mission example
Configure a renewable reservation:
Memory.controllerMissions ??= {};
Memory.controllerMissions.Claimer1 = {
enabled: true,
action: 'reserve',
claimConfirmed: false
};
Configure a reviewed permanent claim:
Memory.controllerMissions.Claimer1 = {
enabled: true,
action: 'claim',
claimConfirmed: true
};
function runControllerMission(creep) {
const mission = Memory.controllerMissions?.[
creep?.name
];
if (!creep || !mission || mission.enabled !== true) {
return { status: 'disabled-or-missing' };
}
const controller = creep.room.controller;
const objects = inspectControllerMissionObjects(
creep
);
const capacity = getClaimCapacity();
const decision = evaluateControllerMission({
enabled: mission.enabled,
action: mission.action,
claimConfirmed: mission.claimConfirmed,
creepOwned: objects.creepOwned,
creepSpawning: objects.creepSpawning,
activeClaimParts: objects.activeClaimParts,
controllerFound: objects.controllerFound,
controllerOwned: objects.controllerOwned,
reservationUsername:
objects.reservationUsername,
creepUsername: creep.owner.username,
ownedRoomCount: capacity.ownedRoomCount,
gclLevel: capacity.gclLevel,
range: objects.range
});
mission.lastCheckedAt = Game.time;
mission.lastStatus = decision.reason;
mission.lastObserved = {
roomName: creep.room.name,
activeClaimParts: objects.activeClaimParts,
controllerOwned: objects.controllerOwned,
reservationUsername:
objects.reservationUsername,
reservationTicks: objects.reservationTicks,
range: objects.range,
ownedRoomCount: capacity.ownedRoomCount,
gclLevel: capacity.gclLevel
};
if (decision.reason === 'move-to-controller') {
const moveResult = creep.moveTo(controller, {
range: 1,
reusePath: 10
});
mission.lastMoveResult = moveResult;
return {
status: 'moving-to-controller',
moveResult
};
}
if (!decision.ready) {
if (
mission.action === 'claim'
&& [
'controller-owned',
'hostile-reservation',
'claim-not-confirmed',
'gcl-not-enough'
].includes(decision.reason)
) {
mission.enabled = false;
}
return { status: decision.reason };
}
const before = {
gameTick: Game.time,
action: mission.action,
roomName: creep.room.name,
reservation: readControllerReservation(
controller
),
controllerOwner:
controller.owner?.username ?? null,
ownedRoomCount: capacity.ownedRoomCount,
gclLevel: capacity.gclLevel
};
const result = mission.action === 'reserve'
? creep.reserveController(controller)
: creep.claimController(controller);
mission.lastBefore = before;
mission.lastResult = result;
mission.lastResultAt = Game.time;
mission.lastStatus = result === OK
? 'action-scheduled'
: 'action-rejected';
if (result === OK && mission.action === 'claim') {
mission.enabled = false;
mission.completedAt = Game.time;
}
return {
status: mission.lastStatus,
action: mission.action,
result,
before
};
}
module.exports.loop = function () {
const creep = Game.creeps.Claimer1;
if (!creep) {
return;
}
const outcome = runControllerMission(creep);
if (
outcome.status === 'action-rejected'
|| outcome.status === 'action-scheduled'
|| Game.time % 100 === 0
) {
console.log(JSON.stringify({
type: 'controller-mission-status',
creepName: creep.name,
...outcome
}));
}
};
Keep reserve renewable and claim one-time
A reserve mission normally remains enabled after OK because reservation time continues to decrease. A claim mission disables immediately after OK so the next tick becomes verification rather than another ownership call.
if (result === OK && mission.action === 'claim') {
mission.enabled = false;
mission.completedAt = Game.time;
}
Verify the next tick
For a reserve mission, re-read controller.reservation.username and ticksToEnd. For a claim mission, verify controller.my, the owner username and the new owned-room count. Do not write predicted ownership into Memory.
Handle return codes
| Code | Typical meaning | Response |
|---|---|---|
OK | Controller action scheduled | Verify reservation or owner later |
ERR_NOT_OWNER | Creep not yours | Current Creep object |
ERR_BUSY | Creep still spawning | Wait |
ERR_INVALID_TARGET | Controller state is invalid | Owner and reservation |
ERR_FULL | No claim capacity | GCL and owned rooms |
ERR_NOT_IN_RANGE | Not adjacent | Move to range 1 |
ERR_NO_BODYPART | No active CLAIM part | Current body damage |
ERR_GCL_NOT_ENOUGH | Claim exceeds account capacity | Current GCL and owned rooms |
Method-specific return lists may differ. Keep the actual result rather than forcing every Controller method into one generic table.
Debugging checklist
- Store an explicit reserve or claim action.
- Require a visible Controller.
- Count active CLAIM parts.
- Check current owner.
- Check reservation username and time.
- Stop on another player's reservation.
- Require explicit claim confirmation.
- Calculate current GCL capacity.
- Move to range 1 only when needed.
- Save the official return code.
- Keep reserve renewable.
- Disable claim after OK and verify later.
Scope and next steps
This guide does not attack a hostile reservation, spawn a Claimer, route across rooms, score expansion locations, build a new Spawn, sign the Controller or coordinate multiple claims. Return to the English article library for movement and room-control foundations.
Frequently asked questions
Why not choose claim automatically when GCL is available?
Capacity does not prove strategic intent. Permanent expansion needs explicit confirmation, economy readiness and room selection outside this execution guide.
Can my own reservation block my claim?
The planner accepts a reservation owned by the current Creep's username. The official claim result still decides whether the current Controller state is valid.
Why disable some rejected claim states?
Ownership, hostile reservation, missing confirmation and insufficient GCL require review. Repeating them each tick does not improve the state and can hide the actual blocker.