DEFENSE · STAGED REPAIR AND EVENT IDENTITY

Repair Walls and Ramparts Without Hiding Duplicate Work

CURATED PATH

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

Defense / Combat

Recommended next steps

Separate Tower repair from long-term fortificationRepair policyChange Rampart access state when requiredAccess controlUnderstand Safe Mode when the defense line failsEmergency protection
Verification statusChinese source article: Reviewed in full · Official engine: Checked — Creep repair emits EVENT_REPAIR with Repairer objectId, targetId, amount and energySpent · Policy boundary: Stage values and structure priority are room strategy, not official safety thresholds

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official engine
Checked — Creep repair emits EVENT_REPAIR with Repairer objectId, targetId, amount and energySpent
Policy boundary
Stage values and structure priority are room strategy, not official safety thresholds
Static code review
Passed — one room coordinator, duplicate-Creep guard, target reservation and exact next-tick identity
JavaScript syntax
Passed
Screeps Console test
Pending
Live multi-tick verification
Pending
Genuine room or Console screenshots
Pending
Last verified
August 3, 2026

Separate the repair stage from target identity

A fortification stage answers how far the room is willing to repair. It does not answer which Repairer acted, which structure received the action, or whether several Repairers selected the same weak target. Treat those as separate contracts:

  • a reviewed room policy defines eligible structure types and current hits stages;
  • one coordinator assigns a target to each Repairer once per tick;
  • only an accepted repair() call creates a pending verification record;
  • the next tick matches the exact Repairer and structure in EVENT_REPAIR.

Do not use hitsMax as the operating target. Walls and Ramparts can absorb room Energy indefinitely, while spawning, defense response, upgrading and logistics have shorter deadlines.

Define a room policy

function readFortificationPolicy(roomName) {
  const value = Memory.fortificationPolicy?.[roomName];

  if (
    !value
    || value.enabled !== true
    || !Number.isFinite(value.wallStage)
    || !Number.isFinite(value.rampartStage)
    || value.wallStage <= 0
    || value.rampartStage <= 0
  ) {
    return { enabled: false };
  }

  return {
    enabled: true,
    stages: {
      [STRUCTURE_WALL]: value.wallStage,
      [STRUCTURE_RAMPART]: value.rampartStage
    }
  };
}

Separate Wall and Rampart stages when their jobs differ. A Rampart protecting a Spawn may deserve a different policy from an outer Wall. The values remain player strategy; no official API constant declares a safe hits target.

Reserve targets in one coordinator

function createRepairCoordinator(room, policy) {
  const assignedCreeps = new Set();
  const reservedTargets = new Set();

  function chooseTarget(creep) {
    const candidates = room.find(FIND_STRUCTURES, {
      filter: structure => {
        const stage = policy.stages[structure.structureType];
        return Number.isFinite(stage)
          && structure.hits < structure.hitsMax
          && structure.hits < stage
          && !reservedTargets.has(structure.id);
      }
    });

    return candidates.sort((left, right) => {
      const leftStage = policy.stages[left.structureType];
      const rightStage = policy.stages[right.structureType];
      const leftDeficit = (leftStage - left.hits) / leftStage;
      const rightDeficit = (rightStage - right.hits) / rightStage;

      return rightDeficit - leftDeficit
        || left.hits - right.hits
        || creep.pos.getRangeTo(left) - creep.pos.getRangeTo(right)
        || left.id.localeCompare(right.id);
    })[0] || null;
  }

  return {
    assign(creep) {
      if (!creep || assignedCreeps.has(creep.id)) {
        return { status: 'creep-already-assigned' };
      }

      assignedCreeps.add(creep.id);
      const target = chooseTarget(creep);
      if (!target) return { status: 'stage-complete' };

      reservedTargets.add(target.id);
      return { status: 'assigned', target };
    },
    release(targetId) {
      reservedTargets.delete(targetId);
    }
  };
}

A target reservation is a local scheduling rule, not a game lock. It prevents this coordinator from sending every Repairer to the same lowest-hits structure. A more advanced policy may permit several Repairers on a breach, but that should be explicit rather than accidental.

Record only accepted repair calls

function submitFortificationRepair(coordinator, creep) {
  if (
    !creep
    || creep.my !== true
    || creep.spawning === true
    || creep.getActiveBodyparts(WORK) <= 0
    || creep.store.getUsedCapacity(RESOURCE_ENERGY) <= 0
  ) {
    return { status: 'repairer-unavailable' };
  }

  const assignment = coordinator.assign(creep);
  if (assignment.status !== 'assigned') return assignment;

  const target = assignment.target;
  if (!creep.pos.inRangeTo(target, 3)) {
    coordinator.release(target.id);
    return {
      status: 'moving',
      moveResult: creep.moveTo(target, { range: 3, reusePath: 5 }),
      targetId: target.id
    };
  }

  const result = creep.repair(target);
  if (result !== OK) {
    coordinator.release(target.id);
    return { status: 'repair-rejected', result, targetId: target.id };
  }

  Memory.pendingFortificationRepairs ??= {};
  Memory.pendingFortificationRepairs[creep.id] = {
    submittedAt: Game.time,
    roomName: creep.room.name,
    creepId: creep.id,
    creepName: creep.name,
    targetId: target.id,
    targetType: target.structureType,
    hitsBefore: target.hits,
    energyBefore: creep.store.getUsedCapacity(RESOURCE_ENERGY)
  };

  return { status: 'repair-accepted', result, targetId: target.id };
}

Movement is not a repair reservation across ticks. Release the local target when the Creep still needs to travel, then select again from current room state on a later tick. Save a pending record only after the real action returns OK.

Verify the exact repair event

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

  const room = Game.rooms[pending.roomName];
  if (!room) return { status: 'room-not-visible' };

  const matches = room.getEventLog().filter(event =>
    event.event === EVENT_REPAIR
    && event.objectId === pending.creepId
    && event.data?.targetId === pending.targetId
  );

  if (matches.length === 0) {
    return { status: 'accepted-repair-event-missing' };
  }
  if (matches.length > 1) {
    return { status: 'repair-event-ambiguous', count: matches.length };
  }

  const event = matches[0];
  const target = Game.getObjectById(pending.targetId);
  return {
    status: 'repair-event-observed',
    amount: event.data?.amount ?? null,
    energySpent: event.data?.energySpent ?? null,
    hitsBefore: pending.hitsBefore,
    hitsNow: target?.hits ?? null
  };
}

The room event log is a one-tick evidence window. Run verification before submitting the next action for that Repairer, then clear or archive the pending record after producing a terminal result.

Use event amount without overclaiming

The official engine records the Repairer as objectId, the repaired structure as targetId, the repair output as amount, and consumed Energy as energySpent. That identity is stronger than a net hits delta. The structure may also receive Tower repair, another Creep repair, or damage during the same processed tick, so hitsNow - hitsBefore is not necessarily equal to this one event amount.

Keep missing and ambiguous states visible

StatusMeaningNext action
stage-completeNo eligible structure is below its configured stageStop repairing; do not auto-raise the stage
movingNo repair call was acceptedInspect movement and retry from current state
repair-rejectedThe API rejected the current actionUse the saved return code
accepted-repair-event-missingAn accepted call lacks the expected next-tick eventKeep the discrepancy; do not replace it with a hits guess
repair-event-ambiguousMore than one exact match was foundInspect duplicate dispatch or corrupted pending state
repair-event-window-missedVerification ran too lateRecord the missed window and sample a later action

Production integration boundary

Call the coordinator once per owned room and route all fortification Repairers through it. Keep emergency Tower logic, hostile response, repairer Energy supply and stage promotion in separate policies. Console execution, live multi-tick results and genuine room screenshots remain pending.

Official documentation

Review the official Screeps API for Creep.repair(), Room.getEventLog(), EVENT_REPAIR, Walls, Ramparts, body boosts and action 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.