CONSTRUCTION · DESTRUCTION IDENTITY

Destroy One Extension Without Losing Object or Room Identity

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Construction

Recommended next steps

Separate Site.remove() from Structure.destroy()API boundaryCreate a replacement Site when neededFollow-up actionVerify that the original structure ID disappearedResult verification
Verification statusChinese source article: Reviewed in full · Official engine: Checked — destroy uses owned-room Controller authority, rejects hostile Creeps and hostile Power Creeps, queues a room destroyStructure intent and emits no Room event · Destructive scope: Restricted to one exact STRUCTURE_EXTENSION; original-ID disappearance and replacement-at-tile are reported separately

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official engine
Checked — destroy uses owned-room Controller authority, rejects hostile Creeps and hostile Power Creeps, queues a room destroyStructure intent and emits no Room event
Destructive scope
Restricted to one exact STRUCTURE_EXTENSION; original-ID disappearance and replacement-at-tile are reported separately
Static code review
Passed — target-bound confirmation, room Controller ownership, complete hostile checks, one operation queue and next-tick identity verification
JavaScript syntax
Passed
Screeps Console test
Pending
Live Extension removal, hostile Creep and Power Creep rejection, capacity impact and replacement verification
Pending
Genuine room or Console screenshots
Pending
Last verified
August 4, 2026

Separate accepted destruction from removal proof

Structure.destroy() returns OK after queuing a room-level destruction intent. The official engine checks that the room has your Controller and that no hostile regular Creep or hostile Power Creep is present. It does not create a Room event for the removal.

The next-tick verifier must answer two different questions: did the original Structure ID disappear, and what now occupies the reviewed tile? A replacement Extension at the same coordinate does not mean the original destruction failed.

Bind confirmation to the exact Extension

function buildDestroyConfirmation(request) {
  return [
    'DESTROY_STRUCTURE',
    request.requestId,
    request.structureId,
    request.roomName,
    request.x,
    request.y,
    request.expectedType
  ].join('_');
}
Memory.destroyRequests ??= {};

Memory.destroyRequests['remove-extension-7'] = {
  requestId: 'remove-extension-7',
  enabled: true,
  structureId: 'replace-with-extension-id',
  roomName: 'W1N1',
  x: 20,
  y: 20,
  expectedType: STRUCTURE_EXTENSION,
  requestedAt: Game.time,
  confirmation:
    'DESTROY_STRUCTURE_remove-extension-7_' +
    'replace-with-extension-id_W1N1_20_20_extension'
};

A static phrase such as DESTROY_EXTENSION proves only that someone typed a phrase. It does not prove which Extension was reviewed. Bind the exact ID, room, coordinate and allowed type.

Validate room authority and hostiles

function evaluateDestroyOperation(
  request,
  structure
) {
  if (!request || request.enabled !== true) {
    return { ready: false, status: 'request-disabled' };
  }

  if (
    typeof request.requestId !== 'string'
    || request.requestId.length === 0
    || typeof request.structureId !== 'string'
    || typeof request.roomName !== 'string'
    || !Number.isInteger(request.x)
    || !Number.isInteger(request.y)
    || request.x < 0
    || request.x > 49
    || request.y < 0
    || request.y > 49
    || request.expectedType !== STRUCTURE_EXTENSION
    || !Number.isInteger(request.requestedAt)
  ) {
    return { ready: false, status: 'request-invalid' };
  }

  if (request.confirmation !== buildDestroyConfirmation(request)) {
    return { ready: false, status: 'confirmation-mismatch' };
  }

  if (!structure) {
    return { ready: false, status: 'structure-unavailable' };
  }

  if (
    structure.id !== request.structureId
    || structure.structureType !== request.expectedType
    || structure.pos.roomName !== request.roomName
    || structure.pos.x !== request.x
    || structure.pos.y !== request.y
  ) {
    return { ready: false, status: 'structure-identity-mismatch' };
  }

  const room = structure.room;
  const controller = room?.controller || null;

  if (!room || !controller || controller.my !== true) {
    return { ready: false, status: 'room-controller-not-owned' };
  }

  const hostileCreepCount =
    room.find(FIND_HOSTILE_CREEPS).length;
  const hostilePowerCreepCount =
    room.find(FIND_HOSTILE_POWER_CREEPS).length;

  if (
    hostileCreepCount > 0
    || hostilePowerCreepCount > 0
  ) {
    return {
      ready: false,
      status: 'hostiles-present',
      hostileCreepCount,
      hostilePowerCreepCount
    };
  }

  return {
    ready: true,
    status: 'destroy-ready',
    room,
    hostileCreepCount,
    hostilePowerCreepCount
  };
}

The method's ownership boundary is the room Controller, not a generic assumption that every destroyable Structure must appear in Game.structures. This guide still restricts the operation to one reviewed Extension in one owned room.

Queue only one destructive operation

function createDestructionDispatcher() {
  let operationReserved = false;
  const reservedStructureIds = new Set();

  return {
    reserve(structureId) {
      if (operationReserved) {
        return {
          ready: false,
          status: 'destruction-slot-already-used'
        };
      }
      if (reservedStructureIds.has(structureId)) {
        return {
          ready: false,
          status: 'structure-already-reserved'
        };
      }

      operationReserved = true;
      reservedStructureIds.add(structureId);
      return {
        ready: true,
        status: 'destruction-reserved'
      };
    },
    release(structureId) {
      operationReserved = false;
      reservedStructureIds.delete(structureId);
    }
  };
}

The engine can queue more than one room destruction intent in a tick. This guide intentionally permits one reviewed destructive operation per dispatcher run. Expand that policy only with a batch-level dependency review.

Record only an accepted destroy call

function submitDestroyOperation(
  dispatcher,
  requestId
) {
  const request =
    Memory.destroyRequests?.[requestId];

  if (!request || request.requestId !== requestId) {
    return { status: 'request-identity-mismatch' };
  }

  const structure =
    typeof request.structureId === 'string'
      ? Game.getObjectById(request.structureId)
      : null;

  const decision = evaluateDestroyOperation(
    request,
    structure
  );
  if (!decision.ready) {
    return decision;
  }

  const reservation =
    dispatcher.reserve(structure.id);
  if (!reservation.ready) {
    return reservation;
  }

  Memory.pendingStructureDestructions ??= {};
  if (
    Memory.pendingStructureDestructions[
      structure.id
    ]
  ) {
    dispatcher.release(structure.id);
    return { status: 'pending-operation-exists' };
  }

  request.enabled = false;
  request.lastAttemptAt = Game.time;

  const before = {
    structureType: structure.structureType,
    roomName: structure.pos.roomName,
    x: structure.pos.x,
    y: structure.pos.y,
    energyCapacityAvailable:
      decision.room.energyCapacityAvailable,
    hostileCreepCount:
      decision.hostileCreepCount,
    hostilePowerCreepCount:
      decision.hostilePowerCreepCount
  };

  const result = structure.destroy();
  request.lastResult = result;

  if (result !== OK) {
    dispatcher.release(structure.id);
    request.status =
      'destroy-rejected-review-required';
    return {
      status: request.status,
      result
    };
  }

  Memory.pendingStructureDestructions[
    structure.id
  ] = {
    submittedAt: Game.time,
    requestId: request.requestId,
    structureId: structure.id,
    roomName: structure.pos.roomName,
    x: structure.pos.x,
    y: structure.pos.y,
    expectedType: structure.structureType,
    before
  };

  request.status =
    'accepted-awaiting-verification';

  return {
    status: request.status,
    result,
    structureId: structure.id
  };
}

A pending record exists only after OK. A rejected request remains disabled for review instead of firing automatically after hostiles leave.

Distinguish removal from replacement

function verifyDestroyedStructure(pending) {
  if (!pending) {
    return { status: 'no-pending-operation' };
  }

  const expectedTick = pending.submittedAt + 1;
  if (Game.time < expectedTick) {
    return { status: 'waiting-for-next-tick' };
  }
  if (Game.time > expectedTick) {
    return { status: 'verification-window-missed' };
  }

  const original =
    Game.getObjectById(pending.structureId);

  if (original) {
    return {
      status: 'accepted-original-still-observed',
      structureId: original.id
    };
  }

  const room = Game.rooms[pending.roomName];
  if (!room) {
    return {
      status: 'original-gone-tile-evidence-unavailable'
    };
  }

  const structures = room.lookForAt(
    LOOK_STRUCTURES,
    pending.x,
    pending.y
  );
  const replacement = structures.find(structure =>
    structure.structureType
      === pending.expectedType
  ) || null;

  return replacement
    ? {
        status: 'original-destroyed-replacement-present',
        replacementId: replacement.id
      }
    : {
        status: 'original-destroyed-tile-empty'
      };
}

Both terminal states prove that the original ID is gone. The replacement state is operationally important, but it must not retroactively invalidate the original removal.

Preserve destructive-operation states

StatusMeaningResponse
room-controller-not-ownedThe room does not provide current destroy authorityStop without guessing from Structure properties
hostiles-presentA hostile regular Creep or Power Creep is visibleKeep the request unsubmitted
destruction-slot-already-usedAnother destructive operation was selected this tickDefer without submitting
accepted-original-still-observedThe saved ID still resolves next tickPreserve the discrepancy
original-destroyed-replacement-presentThe original ID is gone and the tile contains another ExtensionReview the replacement separately

Review capacity and layout impact

An Extension contributes to room Energy capacity and may be referenced by logistics, Rampart coverage, path matrices and blueprints. Record the pre-action capacity for impact review, but do not treat a later capacity change as exact destruction proof. This guide makes no refund claim and does not rebuild automatically.

Production integration boundary

Route all destructive requests through one final queue, verify accepted operations before opening another slot, and require a new target-bound confirmation after any blueprint or object change. Console execution, hostile Power Creep behavior, capacity impact, replacement timing, genuine screenshots and CPU measurements remain pending.

Official documentation

Review the official Screeps API for Structure.destroy(), room Controllers, hostile Creeps, hostile Power Creeps, object IDs, room look methods and return codes. The official engine implementation clarifies the room-authority and queued-intent boundaries.

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.