CONTROLLER · RESERVE OR CLAIM

How to Choose Between Reserving and Claiming a Controller

Separate renewable remote-room reservations from one-time room ownership, require an active CLAIM part and range 1, block owned Controllers and hostile reservations, require explicit claim confirmation and GCL capacity, save return codes, and stop a claim mission after OK.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — reserveController(), claimController(), Controller owner and reservation fields, CLAIM parts, range, GCL and return codes · Mission boundary: Mission purpose, claim confirmation and hostile-reservation policy are project controls, not official strategic recommendations

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — reserveController(), claimController(), Controller owner and reservation fields, CLAIM parts, range, GCL and return codes
Mission boundary
Mission purpose, claim confirmation and hostile-reservation policy are project controls, not official strategic recommendations
Execution boundary
OK schedules the Controller action; owner, reservation, GCL and mission completion require later observation
JavaScript syntax
Passed
Offline mission review
Passed — action, active CLAIM, Controller ownership, friendly or hostile reservation, GCL capacity, confirmation, range and completion states
Screeps Console test
Pending
Live reservation renewal, 5,000-tick cap, GCL claim, hostile reservation and next-tick state test
Pending
Last verified
July 26, 2026

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

DecisionreserveController()claimController()
PurposeTemporary remote-room control benefitsPermanent owned-room expansion
Controller ownerRemains neutralBecomes yours after success
GCL room capacityDoes not consume an owned-room slotRequires available claim capacity
LifecycleUsually renewed over timeOne-time ownership transition
Typical missionRemote harvesting or temporary accessBuild 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

CodeTypical meaningResponse
OKController action scheduledVerify reservation or owner later
ERR_NOT_OWNERCreep not yoursCurrent Creep object
ERR_BUSYCreep still spawningWait
ERR_INVALID_TARGETController state is invalidOwner and reservation
ERR_FULLNo claim capacityGCL and owned rooms
ERR_NOT_IN_RANGENot adjacentMove to range 1
ERR_NO_BODYPARTNo active CLAIM partCurrent body damage
ERR_GCL_NOT_ENOUGHClaim exceeds account capacityCurrent 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.

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.