Quick answer
Do not call activateSafeMode() directly from a broad hostile check. Create a one-time request containing the exact room and confirmation phrase, require a visible owned Controller, no active Safe Mode, at least one available activation, no cooldown and no upgrade block, save the before state, set request.enabled = false, call the API once, store the return code, and verify the Controller on a later tick.
Treat Safe Mode as a reviewed defense action
Safe Mode is an important protection mechanism, but it does not select Tower targets, repair defenses, remove hostiles or fix room layout. It also consumes a limited activation. The trigger must therefore be separated from simple visibility of a non-owned Creep.
function shouldRequestSafeMode(input) {
return Boolean(
input.criticalStructureThreatened
&& input.defenseResponseUnavailable
&& input.playerApproved
);
}
These inputs are project policy, not official API facts. This article begins only after the decision to request activation has already been reviewed.
Read Controller state without changing it
function inspectSafeModeState(roomName) {
const room = typeof roomName === 'string'
? Game.rooms[roomName]
: null;
const controller = room?.controller || null;
if (!controller) {
return {
roomVisible: Boolean(room),
controllerFound: false
};
}
return {
roomVisible: true,
controllerFound: true,
roomName: room.name,
owned: controller.my === true,
safeMode: controller.safeMode ?? null,
safeModeAvailable:
controller.safeModeAvailable ?? null,
safeModeCooldown:
controller.safeModeCooldown ?? null,
upgradeBlocked:
controller.upgradeBlocked ?? null,
ticksToDowngrade:
controller.ticksToDowngrade ?? null
};
}
This is a read-only Console-safe inspection. It reduces obvious mistakes but cannot replace the method's official return code.
Create an explicit one-time request
Memory.safeModeRequest = {
enabled: true,
roomName: 'W1N1',
confirmation: 'ACTIVATE_SAFE_MODE'
};
The Memory field and phrase are project controls. The phrase prevents an accidental boolean toggle from becoming a real activation, but it does not prove the room strategy is correct.
Build a testable activation plan
function evaluateSafeModeActivation(input) {
if (input.enabled !== true) {
return { ready: false, reason: 'disabled' };
}
if (input.confirmation !== 'ACTIVATE_SAFE_MODE') {
return { ready: false, reason: 'confirmation-missing' };
}
if (!input.roomVisible || !input.controllerFound) {
return { ready: false, reason: 'controller-missing' };
}
if (!input.owned) {
return { ready: false, reason: 'not-owner' };
}
if (Number.isFinite(input.safeMode) && input.safeMode > 0) {
return { ready: false, reason: 'already-active' };
}
if (
!Number.isInteger(input.safeModeAvailable)
|| input.safeModeAvailable <= 0
) {
return { ready: false, reason: 'no-activation' };
}
if (
Number.isFinite(input.safeModeCooldown)
&& input.safeModeCooldown > 0
) {
return { ready: false, reason: 'activation-cooldown' };
}
if (
Number.isFinite(input.upgradeBlocked)
&& input.upgradeBlocked > 0
) {
return { ready: false, reason: 'upgrade-blocked' };
}
return { ready: true, reason: 'ready' };
}
Other official restrictions can still reject the operation. In particular, same-shard room state is finalized by ERR_BUSY.
Complete Safe Mode request handler
function handleSafeModeRequest() {
const request = Memory.safeModeRequest;
if (!request || request.enabled !== true) {
return { status: 'disabled' };
}
const state = inspectSafeModeState(request.roomName);
const plan = evaluateSafeModeActivation({
enabled: request.enabled,
confirmation: request.confirmation,
...state
});
request.checkedAt = Game.time;
request.before = state;
request.status = plan.reason;
if (!plan.ready) {
request.enabled = false;
return { status: plan.reason, state };
}
const room = Game.rooms[request.roomName];
const controller = room.controller;
request.enabled = false;
request.status = 'submitted';
request.submittedAt = Game.time;
const result = controller.activateSafeMode();
request.result = result;
request.resultAt = Game.time;
request.status = result === OK
? 'accepted'
: 'failed-review-required';
return {
status: request.status,
roomName: room.name,
result,
before: state
};
}
module.exports.loop = function () {
const outcome = handleSafeModeRequest();
if (
outcome.status === 'failed-review-required'
|| outcome.status === 'accepted'
) {
console.log(JSON.stringify({
type: 'safe-mode-request-result',
...outcome
}));
}
};
Disable the request before the API call
request.enabled = false;
const result = controller.activateSafeMode();
If the API rejects the call or later code throws, the next tick will not automatically consume an activation after conditions change. Preserve the request object as evidence and require a new reviewed enable action.
Understand the same-shard busy state
function getVisibleActiveSafeModeRooms() {
return Object.values(Game.rooms)
.filter(room =>
room.controller?.my === true
&& Number.isFinite(room.controller.safeMode)
&& room.controller.safeMode > 0
)
.map(room => room.name)
.sort();
}
This diagnostic lists currently visible owned rooms. The API return code remains authoritative for the shard-wide activation restriction.
Verify the next tick
Re-read controller.safeMode and controller.safeModeAvailable, and compare them with the saved before snapshot. Do not replace the observed fields with predicted values in Memory.
Handle return codes
| Code | Meaning here | Review |
|---|---|---|
OK | Activation scheduled | Controller state later |
ERR_NOT_OWNER | Controller not yours | Room and ownership |
ERR_BUSY | Another same-shard room is active | Owned room Safe Mode state |
ERR_NOT_ENOUGH_RESOURCES | No activation available | safeModeAvailable |
ERR_TIRED | Cooldown, block or Controller restriction | Current Controller fields and docs |
Debugging checklist
- Separate threat classification from activation.
- Require exact room and confirmation.
- Inspect the visible owned Controller.
- Check current Safe Mode.
- Check available activations.
- Check cooldown and upgrade block.
- Save the before snapshot.
- Disable before calling.
- Store the official return code.
- Verify Controller state later.
Scope and next steps
This guide does not decide threat severity, run Towers, repair Ramparts, spawn defenders, generate Safe Mode charges or coordinate rooms across shards. Continue with Controller downgrade recovery.
Frequently asked questions
Why keep a failed request instead of deleting it?
It preserves the room, tick, before state and return code for review before a human explicitly retries.
Can a precheck guarantee activation?
No. It removes obvious invalid states; the current official return code is the final same-tick result.
Is the confirmation phrase secure authorization?
No. It is only an operational guard inside your Memory workflow.