MEMORY · DEAD-CREEP CLEANUP

Clean Dead Creep Memory Without Deleting Unrelated State

Verification statusChinese source article: Reviewed in full · Technical correction: Existence detection, owned-index cleanup, shared references, and death cause are separated · JavaScript syntax: Passed by the editorial batch gate

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Technical correction
Existence detection, owned-index cleanup, shared references, and death cause are separated
JavaScript syntax
Passed by the editorial batch gate
Live multi-tick verification
Pending
Live death, cleanup, and name-reuse test
Pending
Official documentation
Checked August 18, 2026 — Game.creeps, StructureSpawn.spawnCreep({memory}), immediate Memory.creeps[name] storage, StructureSpawn.spawning, and spawning.name
Deletion boundary
Delete only project-owned name-indexed state when the name is absent from Game.creeps and not protected by any current spawn.spawning.name
Static code review
Passed — live names survive, spawning names survive, stale names are collected first, and cleanup reason does not claim an unobserved death cause
Evidence level
Current official-documentation review plus static code review; no real-shard execution is claimed
Screeps Console test
Pending — no live Console transcript was collected for this revision
Live multi-tick verification pending
No live spawn-start, mid-spawn Memory, spawn-cancel, natural-death, recycle, suicide, replacement, or multi-spawn cleanup trace was collected for this revision
Last editorial review
August 18, 2026
Publication status
Ready after repository and production gates pass

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

QuestionWhat 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.

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.