CREEP LIFECYCLE · RENEWAL IDENTITY

How to Use StructureSpawn.renewCreep() in Screeps

Verification statusChinese source article: Reviewed in full · Official engine: Checked — renewCreep updates the exact Creep lifetime, charges the exact Spawn, removes every Boost, rejects any CLAIM body part and emits no Room event · Formula boundary: Checked — planned TTL floor(600 / body size) and Energy ceil(body cost / 2.5 / body size)

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official engine
Checked — renewCreep updates the exact Creep lifetime, charges the exact Spawn, removes every Boost, rejects any CLAIM body part and emits no Room event
Formula boundary
Checked — planned TTL floor(600 / body size) and Energy ceil(body cost / 2.5 / body size)
Static code review
Passed — one dispatcher, exact Spawn and Creep identity, accepted-call pending state, next-tick TTL signature and transfer-confound labels
JavaScript syntax
Passed
Screeps Console test
Pending
Live renewal, Boost removal, dual-Spawn contention and Energy-transfer verification
Pending
Genuine room or Console screenshots
Pending
Last verified
September 10, 2026

Quick answer: Use spawn.renewCreep(creep) when the owned Creep is adjacent to the owned Spawn, the Spawn is active and not already spawning, the Creep has no CLAIM body part, the Spawn has enough Energy, and the Creep lifetime is not already full. A successful renewal removes every Boost from the target.

TTL and Energy: One scheduled renewal adds floor(600 / bodySize) ticks and costs ceil(creepCost / 2.5 / bodySize) Energy. Preserve the actual return code: common documented results include OK, ERR_BUSY, ERR_NOT_ENOUGH_ENERGY, ERR_INVALID_TARGET, ERR_FULL, ERR_NOT_IN_RANGE, ERR_NOT_OWNER, and ERR_RCL_NOT_ENOUGH.

Verify afterward: OK means the renewal was scheduled successfully; it is not next-tick state proof. Keep the exact Spawn and Creep identities, then verify the expected TTL change after the tick is processed. The deeper sections below show how to keep Spawn contention, Energy transfers, and Boost removal visible instead of hiding them.

Start with the missing event

renewCreep() does not create a Room event in the official engine. A reliable diagnostic must therefore save the exact Spawn ID, Creep ID, pre-action TTL, expected renewal step, Spawn Energy and Boost signature only after the method returns OK. The next tick can then compare the same objects.

Do not treat a rising TTL as sufficient identity by itself. Two adjacent Spawns, duplicate schedulers or a stale renewal flag can submit competing actions unless both the Spawn and the Creep are reserved by one dispatcher.

Calculate one renewal step

function calculateRenewStep(creep) {
  if (!creep || !Array.isArray(creep.body)) {
    return null;
  }

  const bodySize = creep.body.length;
  if (bodySize <= 0) return null;

  const bodyCost = creep.body.reduce(
    (total, part) =>
      total + (BODYPART_COST[part.type] || 0),
    0
  );

  return {
    bodySize,
    bodyCost,
    addedTicks: Math.floor(600 / bodySize),
    energyCost: Math.ceil(
      bodyCost / 2.5 / bodySize
    )
  };
}

The formulas are planning inputs. The actual call may still be rejected because the Spawn is busy, the Creep is ineligible, the Creep is not adjacent, Energy is unavailable, or the maximum lifetime boundary would be exceeded.

Reject unsafe or ineligible renewals

function evaluateRenewal(spawn, creep, policy) {
  if (!spawn || !creep) {
    return { ready: false, status: 'object-missing' };
  }
  if (spawn.my !== true || creep.my !== true) {
    return { ready: false, status: 'ownership-invalid' };
  }
  if (
    spawn.isActive() !== true
    || spawn.spawning
    || creep.spawning === true
  ) {
    return { ready: false, status: 'spawn-or-creep-busy' };
  }

  const step = calculateRenewStep(creep);
  if (!step) {
    return { ready: false, status: 'renew-step-invalid' };
  }

  if (creep.body.some(part => part.type === CLAIM)) {
    return { ready: false, status: 'claim-part-present' };
  }

  const boosts = creep.body
    .map((part, index) => ({
      index,
      boost: part.boost || null
    }))
    .filter(item => item.boost);

  if (
    boosts.length > 0
    && policy.allowBoostRemoval !== true
  ) {
    return {
      ready: false,
      status: 'boost-removal-not-confirmed',
      boosts
    };
  }

  if (!spawn.pos.isNearTo(creep)) {
    return { ready: false, status: 'move-to-spawn' };
  }

  if (
    spawn.store.getUsedCapacity(RESOURCE_ENERGY)
    < step.energyCost
  ) {
    return {
      ready: false,
      status: 'spawn-energy-not-enough',
      required: step.energyCost
    };
  }

  if (
    creep.ticksToLive + step.addedTicks
    > CREEP_LIFE_TIME
  ) {
    return { ready: false, status: 'lifetime-ceiling' };
  }

  return { ready: true, status: 'renew-ready', step, boosts };
}

The engine rejects a Creep containing any CLAIM body part, not only an active one. Renewal also removes every Boost and recalculates body capacity, which can force resources out of the Creep. Approval for Boost removal must be explicit.

Reserve both the Spawn and Creep

function createRenewalDispatcher() {
  const usedSpawnIds = new Set();
  const usedCreepIds = new Set();

  return {
    reserve(spawn, creep) {
      if (usedSpawnIds.has(spawn.id)) {
        return { ready: false, status: 'spawn-already-used' };
      }
      if (usedCreepIds.has(creep.id)) {
        return { ready: false, status: 'creep-already-used' };
      }

      usedSpawnIds.add(spawn.id);
      usedCreepIds.add(creep.id);
      return { ready: true, status: 'renewal-reserved' };
    },
    release(spawn, creep) {
      usedSpawnIds.delete(spawn.id);
      usedCreepIds.delete(creep.id);
    }
  };
}

A Spawn can receive several apparently valid method calls during one JavaScript tick because intents are processed later. Route spawning and renewal through the same higher-level scheduler, or this local reservation cannot protect against another module.

Record only an accepted renewal

function submitRenewal(
  dispatcher,
  spawn,
  creep,
  policy
) {
  const decision = evaluateRenewal(
    spawn,
    creep,
    policy
  );
  if (!decision.ready) return decision;

  const reservation = dispatcher.reserve(spawn, creep);
  if (!reservation.ready) return reservation;

  Memory.pendingRenewals ??= {};
  if (Memory.pendingRenewals[creep.id]) {
    dispatcher.release(spawn, creep);
    return { status: 'pending-renewal-exists' };
  }

  const before = {
    ticksToLive: creep.ticksToLive,
    spawnEnergy:
      spawn.store.getUsedCapacity(RESOURCE_ENERGY),
    boosts: decision.boosts
  };

  const result = spawn.renewCreep(creep);
  if (result !== OK) {
    dispatcher.release(spawn, creep);
    return {
      status: 'renewal-rejected',
      result
    };
  }

  Memory.pendingRenewals[creep.id] = {
    submittedAt: Game.time,
    spawnId: spawn.id,
    spawnRoomName: spawn.room.name,
    creepId: creep.id,
    creepName: creep.name,
    expectedAddedTicks:
      decision.step.addedTicks,
    expectedEnergyCost:
      decision.step.energyCost,
    before
  };

  creep.memory.renewing = true;

  return {
    status: 'renewal-accepted',
    result,
    spawnId: spawn.id,
    creepId: creep.id
  };
}

A pending record is created only after OK. Movement, failed preflight and rejected actions are not renewal evidence. The persistent renewing flag describes mission state; it does not prove a particular call succeeded.

Verify the next-tick local signature

function verifyRenewal(pending) {
  if (!pending) return { status: 'no-pending-renewal' };
  if (Game.time <= pending.submittedAt) {
    return { status: 'wait-for-next-tick' };
  }
  if (Game.time > pending.submittedAt + 1) {
    return { status: 'renewal-window-missed' };
  }

  const spawn = Game.getObjectById(pending.spawnId);
  const creep = Game.getObjectById(pending.creepId);

  if (!spawn) return { status: 'spawn-not-visible' };
  if (!creep) return { status: 'creep-not-observed' };

  const expectedTtl =
    pending.before.ticksToLive
    - 1
    + pending.expectedAddedTicks;

  const ttlMatches =
    creep.ticksToLive === expectedTtl;

  const boostsRemaining = creep.body
    .map((part, index) => ({
      index,
      boost: part.boost || null
    }))
    .filter(item => item.boost);

  const room = Game.rooms[pending.spawnRoomName];
  const energyTransfers = room
    ? room.getEventLog().filter(event =>
        event.event === EVENT_TRANSFER
        && event.data?.resourceType === RESOURCE_ENERGY
        && (
          event.objectId === pending.spawnId
          || event.data?.targetId === pending.spawnId
        )
      )
    : [];

  const observedEnergy =
    spawn.store.getUsedCapacity(RESOURCE_ENERGY);
  const observedSpent =
    pending.before.spawnEnergy - observedEnergy;

  if (!ttlMatches) {
    return {
      status: 'renewal-ttl-signature-mismatch',
      expectedTtl,
      observedTtl: creep.ticksToLive,
      observedSpent,
      energyTransferCount: energyTransfers.length
    };
  }

  if (
    pending.before.boosts.length > 0
    && boostsRemaining.length > 0
  ) {
    return {
      status: 'renewal-boost-removal-mismatch',
      boostsRemaining
    };
  }

  return {
    status: energyTransfers.length > 0
      ? 'renewal-observed-energy-confounded'
      : 'renewal-local-signature-observed',
    expectedTtl,
    observedTtl: creep.ticksToLive,
    expectedEnergyCost:
      pending.expectedEnergyCost,
    observedSpent,
    boostsRemaining,
    energyTransferCount: energyTransfers.length
  };
}

Under the one-dispatch contract, the next-tick TTL is the previous TTL minus normal decay plus the planned renewal step. Spawn Energy is secondary evidence because transfers can change the same Store. The verifier labels those events instead of forcing the net Energy delta to equal the renewal cost.

Keep renewal state until verified

function finishRenewalSample(creep, outcome, targetTtl) {
  if (!creep || !outcome) return;

  if (
    outcome.status === 'renewal-local-signature-observed'
    || outcome.status === 'renewal-observed-energy-confounded'
  ) {
    if (creep.ticksToLive >= targetTtl) {
      creep.memory.renewing = false;
    }
  }
}

Clear the mission flag only after a verified sample reaches the reviewed target TTL. A missing or mismatched sample should remain visible and should not trigger unlimited retries through another Spawn.

Production integration boundary

Use one empire or room scheduler for both spawnCreep() and renewCreep(). Verify pending renewals before planning new ones, reserve Creep identity across all adjacent Spawns, and budget renewal against replacement throughput. Console execution, live Boost-loss behavior, exact CPU cost and genuine screenshots remain pending.

Official documentation

Review the official Screeps API for StructureSpawn.renewCreep(), Creep.ticksToLive, body-part costs, CREEP_LIFE_TIME, Spawn Energy, Boost removal, EVENT_TRANSFER and return codes before adapting the examples.

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.