Start with the intent boundary
StructureRampart.setPublic() schedules one boolean state change. In the official engine, the call writes a setPublic intent keyed to the Rampart ID and returns OK. A second call for the same Rampart during the same JavaScript tick can replace the earlier value before the server processes either request.
That makes the method different from a simple configuration assignment. One module may submit true, another may submit false, and both callers may see OK while only the final intent remains. Use one dispatcher, one Rampart reservation and one next-tick verifier.
The method creates no Room event. Visual appearance, a Creep standing nearby or the original return value is not completion evidence.
Bind approval to one exact Rampart
function buildRampartConfirmation(request) {
return [
'SET_RAMPART_ACCESS',
request.requestId,
request.rampartId,
request.roomName,
request.x,
request.y,
request.public ? 'PUBLIC' : 'PRIVATE'
].join('_');
}
Memory.rampartAccessRequests ??= {};
Memory.rampartAccessRequests['gate-west-1'] = {
requestId: 'gate-west-1',
enabled: true,
rampartId: 'replace-with-rampart-id',
roomName: 'W1N1',
x: 20,
y: 20,
public: true,
requestedAt: Game.time,
confirmation:
'SET_RAMPART_ACCESS_gate-west-1_' +
'replace-with-rampart-id_W1N1_20_20_PUBLIC'
};
The confirmation includes the request ID, Rampart ID, room, coordinates and target state. Changing any of those fields invalidates the old approval. Require typeof request.public === 'boolean'; the engine coerces truthy and falsy values, but a production request should not silently turn the string "false" into public access.
Validate current identity and state
function evaluateRampartAccess(request, rampart) {
if (!request || request.enabled !== true) {
return { ready: false, status: 'request-disabled' };
}
if (
typeof request.requestId !== 'string'
|| request.requestId.length === 0
|| typeof request.rampartId !== '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
|| typeof request.public !== 'boolean'
|| !Number.isInteger(request.requestedAt)
) {
return { ready: false, status: 'request-invalid' };
}
if (request.confirmation !== buildRampartConfirmation(request)) {
return { ready: false, status: 'confirmation-mismatch' };
}
if (!rampart) {
return { ready: false, status: 'rampart-unavailable' };
}
if (
rampart.id !== request.rampartId
|| rampart.structureType !== STRUCTURE_RAMPART
|| rampart.pos.roomName !== request.roomName
|| rampart.pos.x !== request.x
|| rampart.pos.y !== request.y
) {
return { ready: false, status: 'rampart-identity-mismatch' };
}
if (rampart.my !== true) {
return { ready: false, status: 'rampart-not-owned' };
}
if (rampart.isPublic === request.public) {
return { ready: false, status: 'state-already-observed' };
}
return { ready: true, status: 'access-change-ready' };
}
A matching state is an observation, not evidence that this request caused it. Close or archive that request separately instead of manufacturing an accepted operation record.
Reserve the Rampart for the tick
function createRampartAccessDispatcher() {
const reservedRampartIds = new Set();
return {
reserve(rampartId) {
if (reservedRampartIds.has(rampartId)) {
return {
ready: false,
status: 'rampart-already-reserved'
};
}
reservedRampartIds.add(rampartId);
return {
ready: true,
status: 'rampart-reserved'
};
},
release(rampartId) {
reservedRampartIds.delete(rampartId);
}
};
}
Every module that can change Rampart access must use the same dispatcher. A local reservation inside one role file cannot stop another independent module from overwriting the intent later in the tick.
Record only an accepted change
function submitRampartAccess(
dispatcher,
requestId
) {
const request =
Memory.rampartAccessRequests?.[requestId];
if (!request || request.requestId !== requestId) {
return { status: 'request-identity-mismatch' };
}
const rampart = typeof request.rampartId === 'string'
? Game.getObjectById(request.rampartId)
: null;
const decision = evaluateRampartAccess(
request,
rampart
);
if (!decision.ready) {
return decision;
}
const reservation = dispatcher.reserve(rampart.id);
if (!reservation.ready) {
return reservation;
}
Memory.pendingRampartAccess ??= {};
if (Memory.pendingRampartAccess[rampart.id]) {
dispatcher.release(rampart.id);
return { status: 'pending-operation-exists' };
}
request.enabled = false;
request.lastAttemptAt = Game.time;
const before = {
isPublic: rampart.isPublic,
roomName: rampart.pos.roomName,
x: rampart.pos.x,
y: rampart.pos.y
};
const result = rampart.setPublic(request.public);
request.lastResult = result;
if (result !== OK) {
dispatcher.release(rampart.id);
request.status = 'access-change-rejected';
return {
status: request.status,
result
};
}
Memory.pendingRampartAccess[rampart.id] = {
submittedAt: Game.time,
requestId: request.requestId,
rampartId: rampart.id,
roomName: rampart.pos.roomName,
x: rampart.pos.x,
y: rampart.pos.y,
requestedPublic: request.public,
before
};
request.status = 'accepted-awaiting-verification';
return {
status: request.status,
result,
rampartId: rampart.id
};
}
The request is disabled before the mutation call, but the pending record is created only after OK. A rejection stays disabled for review; do not silently re-enable an access-control operation on the next tick.
Verify the exact object next tick
function verifyRampartAccess(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 rampart =
Game.getObjectById(pending.rampartId);
if (!rampart) {
const room = Game.rooms[pending.roomName];
const replacement = room
? room.lookForAt(
LOOK_STRUCTURES,
pending.x,
pending.y
).find(structure =>
structure.structureType
=== STRUCTURE_RAMPART
) || null
: null;
return replacement
? {
status: 'original-rampart-missing-replacement-present',
replacementId: replacement.id
}
: { status: 'original-rampart-unavailable' };
}
if (
rampart.structureType !== STRUCTURE_RAMPART
|| rampart.pos.roomName !== pending.roomName
|| rampart.pos.x !== pending.x
|| rampart.pos.y !== pending.y
|| rampart.my !== true
) {
return { status: 'rampart-identity-mismatch' };
}
return {
status: rampart.isPublic
=== pending.requestedPublic
? 'rampart-access-state-observed'
: 'accepted-state-not-observed',
rampartId: rampart.id,
beforePublic: pending.before.isPublic,
requestedPublic: pending.requestedPublic,
observedPublic: rampart.isPublic
};
}
Identity is checked before state. A newly built Rampart at the same tile does not prove the original operation. A late observation may show the desired boolean, but it cannot prove which same-tick caller produced it.
Preserve missing and overwritten states
| Status | Meaning | Response |
|---|---|---|
rampart-already-reserved | Another request owns this Rampart for the tick | Do not submit a second call |
pending-operation-exists | The previous accepted call has not been verified | Verify or archive it first |
accepted-state-not-observed | OK was returned but the target state is absent next tick | Investigate a later same-tick overwrite |
verification-window-missed | The exact next-tick sample was not taken | Keep the operation unverified |
original-rampart-missing-replacement-present | The saved ID vanished and another Rampart occupies the tile | Do not transfer evidence to the replacement |
Public is not an ally list
setPublic(true) allows other players' Creeps to pass. It does not accept usernames, alliances, body-part filters, schedules or intent rules. Those policies belong in a separate access dispatcher that decides whether to open or close the exact Rampart; the API still receives only one boolean.
Production integration boundary
Run one final Rampart-access dispatcher after all policy modules have proposed changes. Reject duplicate Rampart IDs, verify pending operations before accepting another request, and keep failed or missed samples visible. Console execution, live passage behavior, same-tick overwrite traces, genuine screenshots and CPU measurements remain pending.
Official documentation
Review the official Screeps API for StructureRampart.setPublic(), StructureRampart.isPublic, ownership, object IDs and return codes. The official engine implementation is also relevant when reasoning about same-tick intent replacement.