DEFENSE · TOWER REPAIR EVENTS

Repair One Structure Without Confusing Decay or Other Workers

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 ordinary repair from staged fortificationRepair boundaryPreserve Energy for immediate defenseResource priorityConnect the Tower reserve to Storage budgetsResource budgetCalculate range falloff, repair output, and required ticksRepair estimate
Verification statusChinese source article: Reviewed in full · Official docs: Checked — repair(), Tower falloff constants, TOWER_ENERGY_COST, active Tower Power effects, Room.getEventLog(), EVENT_REPAIR and return codes · Policy boundary: Action order, Energy reserve, ratio threshold and fortification exclusion are project policies

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — repair(), Tower falloff constants, TOWER_ENERGY_COST, active Tower Power effects, Room.getEventLog(), EVENT_REPAIR and return codes
Policy boundary
Action order, Energy reserve, ratio threshold and fortification exclusion are project policies
Identity boundary
Every submitted intent stores the exact Tower ID and structure ID
Execution boundary
OK accepts a repair intent; a matching prior-tick repair event and recorded Energy cost are required for verification
JavaScript syntax
Passed
Offline event review
Passed — defense gates, reserve plus cost, target ratios, fortification exclusion, falloff estimate, over-repair allocation, exact event matching and missed windows
Screeps Console test
Pending
Live decay, incoming damage, Creep repair, Power effect, over-repair, reserve and event test
Pending
Live multi-tick verification
Pending
Last verified
August 1, 2026

Use this guide when

Use this workflow when idle Towers repair ordinary structures and you need to distinguish Tower repair from Creep repair, another Tower, damage, or natural decay. The exact proof is a prior-tick EVENT_REPAIR entry for the saved Tower and structure IDs. Net structure hits are secondary state only.

Require defense and healing gates first

function chooseTowerMode(input) {
  if (input.attackTarget) return 'attack';
  if (input.healTarget) return 'heal';
  if (input.repairTarget) return 'repair';
  return 'idle';
}

function getRepairableTowers(room, reserve) {
  return room.find(FIND_MY_STRUCTURES, {
    filter: structure =>
      structure.structureType === STRUCTURE_TOWER
      && structure.isActive() === true
      && structure.store.getUsedCapacity(
        RESOURCE_ENERGY
      ) >= reserve + TOWER_ENERGY_COST
  }).sort((left, right) =>
    left.id.localeCompare(right.id)
  );
}

Attack-before-heal-before-repair and the Energy reserve are project policies. The important engineering boundary is that one dispatcher chooses one final action for each Tower.

Select an ordinary damaged structure

function getStructureHitRatio(structure) {
  if (
    !structure
    || !Number.isFinite(structure.hits)
    || !Number.isFinite(structure.hitsMax)
    || structure.hitsMax <= 0
  ) {
    return null;
  }

  return structure.hits / structure.hitsMax;
}

function selectTowerRepairTarget(
  towers,
  structures,
  ratioLimit
) {
  return structures.filter(structure => {
    const ratio = getStructureHitRatio(structure);

    return Number.isFinite(ratio)
      && structure.hits < structure.hitsMax
      && ratio < ratioLimit
      && structure.structureType !== STRUCTURE_WALL
      && structure.structureType !== STRUCTURE_RAMPART;
  }).sort((left, right) =>
    getStructureHitRatio(left)
    - getStructureHitRatio(right)
    || Math.min(...towers.map(tower =>
      tower.pos.getRangeTo(left)
    ))
    - Math.min(...towers.map(tower =>
      tower.pos.getRangeTo(right)
    ))
    || left.id.localeCompare(right.id)
  )[0] || null;
}

Walls and Ramparts remain outside this ratio policy because fortification targets usually use absolute hit bands and separate budgets.

Estimate range-adjusted repair

function getTowerRangeAmount(basePower, range) {
  const clamped = Math.max(
    TOWER_OPTIMAL_RANGE,
    Math.min(TOWER_FALLOFF_RANGE, range)
  );
  const fraction = (
    clamped - TOWER_OPTIMAL_RANGE
  ) / (
    TOWER_FALLOFF_RANGE
    - TOWER_OPTIMAL_RANGE
  );

  return Math.floor(
    basePower * (1 - TOWER_FALLOFF * fraction)
  );
}

function getTowerEffectModifier(tower) {
  return (tower.effects || []).reduce(
    (modifier, effect) => {
      const relevant =
        effect.ticksRemaining > 0
        && (
          effect.effect === PWR_OPERATE_TOWER
          || effect.effect === PWR_DISRUPT_TOWER
        );
      const values = relevant
        ? POWER_INFO[effect.effect]?.effect
        : null;
      const factor = Array.isArray(values)
        ? values[effect.level - 1]
        : null;

      return Number.isFinite(factor)
        ? modifier * factor
        : modifier;
    },
    1
  );
}

function estimateTowerRepair(tower, target) {
  const range = tower.pos.getRangeTo(target);
  const rangeAmount = getTowerRangeAmount(
    TOWER_POWER_REPAIR,
    range
  );

  return {
    range,
    estimatedRepair: Math.floor(
      rangeAmount * getTowerEffectModifier(tower)
    )
  };
}

The estimate limits obvious over-repair. The actual event amount and energySpent are stronger settlement evidence than the estimate or net hits.

Stop allocating when estimated damage is covered

function allocateTowerRepair(towers, target) {
  let remaining = Math.max(
    0,
    target.hitsMax - target.hits
  );
  const allocations = [];

  for (const tower of [...towers].sort(
    (left, right) =>
      left.pos.getRangeTo(target)
      - right.pos.getRangeTo(target)
      || left.id.localeCompare(right.id)
  )) {
    if (remaining <= 0) break;

    const estimate = estimateTowerRepair(
      tower,
      target
    );
    allocations.push({
      tower,
      target,
      ...estimate
    });
    remaining -= estimate.estimatedRepair;
  }

  return {
    allocations,
    estimatedRemaining: Math.max(0, remaining)
  };
}

Concurrent damage or repairs can invalidate the estimate during resolution. Rebuild the plan from current objects on every approved repair tick.

Snapshot and submit one repair operation

function submitTowerRepair(request) {
  const room = Game.rooms[request?.roomName];
  if (!request || request.enabled !== true || !room) {
    return {
      status: room
        ? 'disabled-or-invalid'
        : 'room-not-visible'
    };
  }

  const reserve = Number.isFinite(
    request.energyReserve
  )
    ? Math.max(0, request.energyReserve)
    : 500;
  const ratioLimit = Number.isFinite(
    request.ratioLimit
  )
    ? Math.max(0, Math.min(1, request.ratioLimit))
    : 0.8;
  const towers = getRepairableTowers(
    room,
    reserve
  );
  const target = selectTowerRepairTarget(
    towers,
    room.find(FIND_STRUCTURES),
    ratioLimit
  );

  if (!target || towers.length === 0) {
    return {
      status: towers.length === 0
        ? 'repair-energy-protected'
        : 'no-repair-target'
    };
  }

  const plan = allocateTowerRepair(
    towers,
    target
  );
  request.enabled = false;
  request.submittedAt = Game.time;
  request.target = {
    id: target.id,
    structureType: target.structureType,
    hitsBefore: target.hits,
    hitsMax: target.hitsMax
  };
  request.intents = plan.allocations.map(item => ({
    towerId: item.tower.id,
    targetId: target.id,
    range: item.range,
    estimatedRepair: item.estimatedRepair,
    energyBefore:
      item.tower.store.getUsedCapacity(
        RESOURCE_ENERGY
      ),
    result: item.tower.repair(target)
  }));
  request.status = request.intents.some(
    intent => intent.result === OK
  )
    ? 'accepted-pending-events'
    : 'failed-review-required';

  return {
    status: request.status,
    target: request.target,
    intents: request.intents
  };
}

The code assumes the dispatcher has already confirmed there is no attack or healing priority. The request is disabled before the first API call.

Match Tower ID, target ID, amount, and Energy

function verifyTowerRepair(request) {
  if (
    !request
    || request.status !== 'accepted-pending-events'
  ) {
    return {
      status: 'submission-not-accepted'
    };
  }

  if (Game.time <= request.submittedAt) {
    return { status: 'pending-next-tick' };
  }
  if (Game.time !== request.submittedAt + 1) {
    return { status: 'event-window-missed' };
  }

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

  const accepted = request.intents.filter(
    intent => intent.result === OK
  );
  const events = room.getEventLog().filter(
    event => event.event === EVENT_REPAIR
  );
  const matches = accepted.map(intent => {
    const event = events.find(item =>
      item.objectId === intent.towerId
      && item.data?.targetId === intent.targetId
    );
    const energyExact = event
      ? event.data?.energySpent
        === TOWER_ENERGY_COST
      : false;

    return {
      towerId: intent.towerId,
      targetId: intent.targetId,
      found: Boolean(event),
      amount: event?.data?.amount ?? null,
      energySpent:
        event?.data?.energySpent ?? null,
      energyExact
    };
  });
  const exactCount = matches.filter(
    item => item.found && item.energyExact
  ).length;

  return {
    status: exactCount === accepted.length
      ? 'verified-tower-repair'
      : exactCount === 0
        ? 'tower-events-missing'
        : 'partial-event-match',
    matches
  };
}

An EVENT_REPAIR entry names both the actor and target and exposes the resolved amount and Energy spent. That is more specific than observing a structure's final hit count.

Use net hits only as supporting context

After event verification, recover the structure by ID and record its current hits when visible. A Road can decay, an enemy can damage the target, and Creeps or other Towers can repair it. Those effects explain why net hits may not equal the sum of Tower repair events.

Separate budget, acceptance, and event evidence

StatusMeaning
repair-energy-protectedNo Tower could spend the action cost while preserving the configured reserve.
accepted-pending-eventsAt least one repair call returned OK; event evidence is pending.
event-window-missedThe previous-tick event log was not checked at the required tick.
partial-event-matchOnly some accepted Tower IDs produced an exact repair event.
verified-tower-repairEvery accepted Tower matched the structure and the recorded Energy cost.

Preserve documented return codes

Documented results are OK, ERR_NOT_OWNER, ERR_NOT_ENOUGH_ENERGY, ERR_INVALID_TARGET, and ERR_RCL_NOT_ENOUGH. The Tower can repair throughout its room; range changes the amount rather than producing ERR_NOT_IN_RANGE.

Keep fortifications and economy policy separate

This page owns ordinary-structure Tower repair and event verification. Wall and Rampart bands, road replacement economics, hauler scheduling, emergency defense reserves, and multi-room maintenance need separate policies.

Official documentation

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.