Use this guide when
Use this page when names remain under Memory.creeps after the corresponding Creeps disappear from Game.creeps. The safe operation is narrow: identify names that are absent now, delete their Creep-owned Memory, then clean only the additional name-indexed structures whose schemas you explicitly own.
Separate existence, ownership, and cause
| Question | What the cleanup can know |
|---|---|
| Does the Creep exist now? | Object.hasOwn(Game.creeps, name) |
| Which data belongs to that name? | Only documented project namespaces |
| Why did it disappear? | Not proven by the missing name alone |
Absence can confirm that the current Creep is gone. It does not distinguish natural expiry, combat, suicide, recycling, or another cause.
Collect confirmed missing names before mutating Memory
function collectDeadCreepNames(
gameCreeps,
memoryCreeps
) {
if (
!gameCreeps
|| typeof gameCreeps !== 'object'
|| !memoryCreeps
|| typeof memoryCreeps !== 'object'
|| Array.isArray(memoryCreeps)
) {
return [];
}
return Object.keys(memoryCreeps)
.filter(name => !Object.hasOwn(gameCreeps, name))
.sort();
}
Starting from Memory.creeps is essential because dead names are already absent from Game.creeps. Collecting the list first also gives later cleanup steps one stable input.
Clean only explicitly owned name indexes
function deleteNameKey(record, name) {
if (
!record
|| typeof record !== 'object'
|| Array.isArray(record)
) {
return false;
}
return delete record[name];
}
function cleanOwnedCreepIndexes(name) {
const results = {
creepMemory: deleteNameKey(
Memory.creeps,
name
),
task: deleteNameKey(
Memory.creepTasks,
name
),
assignment: deleteNameKey(
Memory.creepAssignments,
name
)
};
return results;
}
Do not recursively search all Memory for matching text. A Creep name may appear in a historical event, room note, shared queue, combat report, or unrelated user-defined key. Each additional namespace needs its own ownership and retention rule.
Return a bounded cleanup report
function cleanDeadCreepMemory(limit = 20) {
Memory.creeps ??= {};
const names = collectDeadCreepNames(
Game.creeps,
Memory.creeps
);
const removed = [];
for (const name of names) {
removed.push({
name,
indexes: cleanOwnedCreepIndexes(name)
});
}
return {
status: names.length > 0
? 'stale-names-removed'
: 'nothing-to-remove',
count: names.length,
names: names.slice(0, limit),
truncated: names.length > limit,
removed
};
}
The full removed array is useful to in-process callers. Keep Console output bounded so a mass loss does not flood logs.
Run cleanup before counts, assignments, and spawning
module.exports.loop = function () {
const cleanup = cleanDeadCreepMemory(20);
if (cleanup.count > 0) {
console.log(JSON.stringify({
type: 'dead-creep-memory-cleanup',
tick: Game.time,
count: cleanup.count,
names: cleanup.names,
truncated: cleanup.truncated
}));
}
runRoleCounts();
runReplacementPlanning();
runSpawnManager();
};
Cleaning first prevents stale names from influencing role quotas or replacement decisions. The cleanup should not return early from the game loop and skip essential room work.
Shared queues and ID records need separate invalidation
A task queue may store a Creep name inside an array of records rather than as an object key. Removing those records requires a queue-specific rule: is the record disposable, should it become unassigned, or should it remain as history? Likewise, object IDs and remote-room plans are not dead merely because one Creep name disappeared. Use the target-restoration guide for object-ID validity.
Do not delete from ticksToLive or invent a death reason
A Creep with ticksToLive === 1 still exists in the current tick. Delete only after its name is absent from the current Game.creeps object. If the project needs a cause, record the relevant event before the Creep disappears; the cleanup pass cannot reconstruct it reliably afterward.
Make name reuse start from a clean namespace
Run cleanup before attempting to reuse a deterministic name. Pass fresh Memory in the new spawnCreep() request, and use an assignment generation or request ID when other systems must distinguish successive Creeps with the same name. Use the Spawn diagnostic for the final request.
Verify deletion and replacement as separate events
Record the stale name list and the exact namespaces changed on the cleanup tick. On later ticks, verify that role counts no longer include the old Creep and that a replacement receives new Memory and assignments. This revision contains static analysis and offline cleanup checks only; a live death-and-replacement cycle remains pending.
Choose another guide when
Use the Memory guide for persistent-state design. Use this page only for confirmed missing Creep names and the project-owned indexes attached to those names.
Do not delete Memory for a Creep that is still spawning
The simple rule “delete every Memory.creeps[name] entry missing from Game.creeps” needs one production safety boundary. spawnCreep() can store the new Creep's Memory immediately, while the Spawn separately exposes the in-progress name through spawn.spawning.name. Protect names that are currently being produced before classifying an absent Game.creeps[name] entry as stale.
This is a conservative race-avoidance rule derived from the official API surfaces. It does not claim that every server state exposes spawning Creeps identically through every collection; it simply avoids deleting Memory that the spawning API has already associated with an active production request.
function getSpawningCreepNames() {
const names = new Set();
for (const spawn of Object.values(Game.spawns)) {
const spawningName = spawn.spawning?.name;
if (
typeof spawningName === 'string'
&& spawningName.length > 0
) {
names.add(spawningName);
}
}
return names;
}
function collectStaleCreepMemoryNames() {
const spawningNames = getSpawningCreepNames();
const staleNames = [];
for (const name of Object.keys(Memory.creeps || {})) {
if (Game.creeps[name]) {
continue;
}
if (spawningNames.has(name)) {
continue;
}
staleNames.push(name);
}
return staleNames;
}
A name is now eligible for cleanup only when it is absent from both the current Creep collection and the current set of Spawn production names. If a spawn is cancelled or fails before a Creep becomes live, the production protection disappears and the leftover Memory can be collected by a later cleanup pass.
Record cleanup reason without inventing a death cause
Absence is enough to classify a name-indexed Memory entry as stale after the spawning guard. It is not enough to prove why the Creep disappeared. The Creep may have expired, died in combat, been recycled, suicided, or never completed a cancelled production request.
function cleanDeadCreepMemory() {
const staleNames = collectStaleCreepMemoryNames();
const removed = [];
for (const name of staleNames) {
delete Memory.creeps[name];
removed.push({
name,
reason: 'absent-from-live-and-spawning-name-sets',
cleanedAt: Game.time
});
}
return removed;
}
The reason describes the evidence used by the cleanup algorithm, not the biological or combat cause of death. If the project needs a death cause, collect separate evidence such as event logs or your own lifecycle records before the object disappears.