Use two evidence layers
launchNuke() is irreversible and does not emit a Room event in the official engine. Preserve two evidence layers:
- local launcher evidence from the exact Nuker ID, its post-launch cooldown, and its Energy and Ghodium Stores;
- target evidence from an exact
Nukeobject at the reviewed room and coordinates when that room is visible.
Target vision is not required to submit the API call. It is required only for direct observation of the created Nuke object.
Bind approval to Nuker and target
function buildNukeConfirmation(request) {
return [
'LAUNCH_NUKE',
request.nukerId,
request.targetRoomName,
request.x,
request.y
].join('_');
}
function isValidTarget(request) {
return (
typeof request.targetRoomName === 'string'
&& /^[WE]\d+[NS]\d+$/.test(
request.targetRoomName
)
&& Number.isInteger(request.x)
&& request.x >= 0
&& request.x <= 49
&& Number.isInteger(request.y)
&& request.y >= 0
&& request.y <= 49
);
}
Including the Nuker ID prevents approval for one launcher from being reused by another structure. Any change to room name or coordinates invalidates the old confirmation.
Validate the exact launch request
function evaluateNukeLaunch(request) {
if (!request || request.enabled !== true) {
return { ready: false, status: 'request-disabled' };
}
if (!isValidTarget(request)) {
return { ready: false, status: 'invalid-target' };
}
if (
request.confirmation
!== buildNukeConfirmation(request)
) {
return {
ready: false,
status: 'confirmation-mismatch'
};
}
const nuker = Game.getObjectById(request.nukerId);
if (
!nuker
|| nuker.my !== true
|| nuker.structureType !== STRUCTURE_NUKER
) {
return { ready: false, status: 'nuker-invalid' };
}
if (nuker.isActive() !== true) {
return { ready: false, status: 'nuker-inactive' };
}
if (nuker.cooldown > 0) {
return { ready: false, status: 'nuker-cooldown' };
}
const distance = Game.map.getRoomLinearDistance(
nuker.room.name,
request.targetRoomName,
false
);
if (distance > NUKE_RANGE) {
return { ready: false, status: 'target-out-of-range' };
}
if (
nuker.store.getUsedCapacity(RESOURCE_ENERGY)
< NUKER_ENERGY_CAPACITY
) {
return { ready: false, status: 'energy-shortage' };
}
if (
nuker.store.getUsedCapacity(RESOURCE_GHODIUM)
< NUKER_GHODIUM_CAPACITY
) {
return { ready: false, status: 'ghodium-shortage' };
}
return { ready: true, status: 'launch-ready', nuker };
}
Current state can still change before intent processing. The method return code is the submission boundary. Protected or unavailable target-room rules must be handled through the documented return code rather than guessed from stale Memory.
Record only an accepted launch
function submitNukeLaunch(request) {
const decision = evaluateNukeLaunch(request);
if (!decision.ready) {
if (request) request.enabled = false;
return decision;
}
const nuker = decision.nuker;
const target = new RoomPosition(
request.x,
request.y,
request.targetRoomName
);
request.enabled = false;
request.lastAttemptAt = Game.time;
const result = nuker.launchNuke(target);
request.lastResult = result;
if (result !== OK) {
request.status = 'launch-rejected';
return {
status: request.status,
result
};
}
Memory.pendingNukeLaunches ??= {};
Memory.pendingNukeLaunches[nuker.id] = {
submittedAt: Game.time,
nukerId: nuker.id,
launchRoomName: nuker.room.name,
targetRoomName: request.targetRoomName,
x: request.x,
y: request.y,
expectedCooldown: NUKER_COOLDOWN,
expectedLandTime:
Game.time + NUKE_LAND_TIME,
before: {
energy: nuker.store.getUsedCapacity(
RESOURCE_ENERGY
),
ghodium: nuker.store.getUsedCapacity(
RESOURCE_GHODIUM
),
cooldown: nuker.cooldown
}
};
request.status = 'accepted-awaiting-verification';
return {
status: request.status,
result,
nukerId: nuker.id,
targetRoomName: request.targetRoomName,
x: request.x,
y: request.y
};
}
The request is disabled before the call so a thrown exception or later module cannot repeat an irreversible action. A rejected request stays disabled for human review. Only OK creates the pending operation record.
Verify the launcher signature
function verifyNukerState(pending) {
if (!pending) return { status: 'no-pending-launch' };
if (Game.time <= pending.submittedAt) {
return { status: 'wait-for-next-tick' };
}
const nuker = Game.getObjectById(pending.nukerId);
if (!nuker) return { status: 'nuker-not-visible' };
const energy =
nuker.store.getUsedCapacity(RESOURCE_ENERGY);
const ghodium =
nuker.store.getUsedCapacity(RESOURCE_GHODIUM);
const localSignature =
nuker.cooldown > 0
&& energy === 0
&& ghodium === 0;
return {
status: localSignature
? 'launcher-signature-observed'
: 'accepted-launch-signature-mismatch',
nukerId: nuker.id,
cooldown: nuker.cooldown,
energy,
ghodium,
expectedCooldown:
pending.expectedCooldown
};
}
The official engine sets an absolute cooldown time internally; the API exposes remaining cooldown. Check for a positive cooldown rather than requiring a copied value at every observation tick. The zero-resource signature is local proof for this Nuker, but it does not replace optional target evidence.
Match the target Nuke when visible
function verifyTargetNuke(pending) {
const room = Game.rooms[pending.targetRoomName];
if (!room) {
return { status: 'target-evidence-unavailable' };
}
const matches = room.find(FIND_NUKES).filter(nuke =>
nuke.pos.x === pending.x
&& nuke.pos.y === pending.y
&& nuke.launchRoomName
=== pending.launchRoomName
);
if (matches.length === 0) {
return { status: 'target-nuke-not-observed' };
}
if (matches.length > 1) {
return {
status: 'target-nuke-ambiguous',
count: matches.length
};
}
return {
status: 'target-nuke-observed',
nukeId: matches[0].id,
launchRoomName:
matches[0].launchRoomName,
timeToLand: matches[0].timeToLand,
expectedLandTime:
pending.expectedLandTime
};
}
Coordinates alone are not enough because several players can launch at the same tile. Match the saved launching room as well. timeToLand is current remaining time; expectedLandTime is the submitted absolute planning value and should be reported separately.
Keep incomplete evidence visible
| Status | Meaning | Response |
|---|---|---|
confirmation-mismatch | The approved launcher or target changed | Require a new review |
launch-rejected | The API did not accept the irreversible action | Keep the request disabled and retain the return code |
accepted-launch-signature-mismatch | The saved Nuker lacks the expected local state | Preserve the discrepancy |
target-evidence-unavailable | The target room is not visible | Report local evidence only |
target-nuke-not-observed | Vision exists but no exact Nuke matches | Do not substitute another Nuke at the tile |
target-nuke-ambiguous | More than one exact launcher-and-position match exists | Inspect the room objects directly |
Production integration boundary
Keep one irreversible-operations queue, disable each request before the API call, and archive accepted records without mutating their target fields. Do not automate target selection, diplomacy, damage prediction or launch timing from this guide. Console execution, protected-area behavior, live Nuke observation and genuine screenshots remain pending.
Official documentation
Review the official Screeps API for StructureNuker.launchNuke(), RoomPosition, FIND_NUKES, Nuke.launchRoomName, Nuke.timeToLand, NUKE_RANGE, capacities, cooldown, landing time and return codes before adapting the examples.