FOUNDATION · TARGET RESTORATION

How to Restore a Screeps Target from Memory with Game.getObjectById()

Store an object ID and room name, recover the current object every tick, distinguish missing vision from a destroyed target, validate the restored type, and define an explicit invalidation policy.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — Game.getObjectById(), Game.rooms, Memory · Visibility rule: Only objects in currently visible rooms are accessible

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Game.getObjectById(), Game.rooms, Memory
Visibility rule
Only objects in currently visible rooms are accessible
JavaScript syntax
Passed
Offline branch review
Passed — invalid ID, no vision, missing object, type guard, reselection
Screeps Console test
Pending
Live remote-vision test
Pending
Last verified
July 25, 2026

Quick answer

Store a game object's stable id in Memory, then call Game.getObjectById(id) on each later tick to recover the current object. The method returns an object or null, not an action result code. For remote targets, also store roomName: null can mean that the room is not currently visible, so do not delete the ID until you distinguish missing vision from a visible-room target that has actually disappeared.

Save stable data, restore current objects

Memory
→ stable ID, room name, target kind, selection metadata

Game.getObjectById(id)
→ current object from the current tick and current vision

A useful saved record is:

creep.memory.target = {
  id: source.id,
  roomName: source.pos.roomName,
  kind: 'source',
  selectedAt: Game.time
};

roomName and kind are project-defined diagnostic fields. They are not required arguments to Game.getObjectById().

Why a full game object does not belong in Memory

This is incorrect:

creep.memory.source = source;

Memory stores JSON data. A serialized Source snapshot is not the live Source on the next tick, does not provide current methods or properties, and wastes limited Memory space. Store the ID instead:

creep.memory.sourceId = source.id;

Then restore the current object:

const source = Game.getObjectById(
  creep.memory.sourceId
);

What Game.getObjectById() returns

The API returns:

  • a current game object instance that is accessible in your present vision; or
  • null when the object cannot be found or accessed.

It does not return OK, ERR_NOT_IN_RANGE, or another action code. Those codes come later when the Creep calls a method such as harvest().

Check room vision before invalidating a remote target

function hasRoomVision(roomName) {
  return typeof roomName === 'string'
    && roomName.length > 0
    && Boolean(Game.rooms[roomName]);
}

Use this interpretation order:

No valid ID
→ select a target

Valid ID, target room not visible
→ keep the ID and wait for vision

Valid ID, room visible, object is still null
→ clear or replace the target

Without the stored room name, a remote task cannot reliably distinguish “temporarily invisible” from “gone while visible.” The official API states that only objects in rooms currently visible to you can be accessed by ID.

Validate the restored object type

Memory can be corrupted, migrated from an older schema, or reused by another task. A Source-specific branch should validate Source-like features:

function isSourceObject(target) {
  return Boolean(
    target
    && typeof target.id === 'string'
    && target.pos
    && Number.isFinite(target.energy)
    && Number.isFinite(target.energyCapacity)
  );
}

This is a runtime guard, not a complete JavaScript type system. A TypeScript project can add stronger external declarations, but live validation is still useful when reading persisted data.

Complete saved-Source example

State impact: this script stores a Source target record in Harvester1 Memory, updates diagnostic status fields, may clear an invalid record, and may move or harvest. It uses a dynamic-source policy when the selected Source is empty.

function isSourceObject(target) {
  return Boolean(
    target
    && typeof target.id === 'string'
    && target.pos
    && Number.isFinite(target.energy)
    && Number.isFinite(target.energyCapacity)
  );
}

function clearStoredTarget(creep, reason) {
  delete creep.memory.target;
  creep.memory.lastTargetStatus = reason;
  creep.memory.lastTargetChangedAt = Game.time;
}

function storeSourceTarget(creep, source) {
  creep.memory.target = {
    id: source.id,
    roomName: source.pos.roomName,
    kind: 'source',
    selectedAt: Game.time
  };
  creep.memory.lastTargetStatus =
    'source-selected';
  creep.memory.lastTargetChangedAt = Game.time;
}

function selectVisibleSource(creep) {
  const source = creep.pos.findClosestByPath(
    FIND_SOURCES_ACTIVE
  );

  if (!source) {
    return null;
  }

  storeSourceTarget(creep, source);
  return source;
}

function getStoredSource(creep) {
  const stored = creep.memory.target;

  if (!stored || typeof stored !== 'object') {
    return selectVisibleSource(creep);
  }

  if (
    typeof stored.id !== 'string'
    || stored.id.length === 0
  ) {
    clearStoredTarget(creep, 'invalid-id');
    return selectVisibleSource(creep);
  }

  if (
    typeof stored.roomName === 'string'
    && stored.roomName.length > 0
    && !Game.rooms[stored.roomName]
  ) {
    creep.memory.lastTargetStatus =
      'waiting-room-vision';
    return null;
  }

  const target = Game.getObjectById(stored.id);

  if (!isSourceObject(target)) {
    clearStoredTarget(creep, 'source-not-found');
    return selectVisibleSource(creep);
  }

  creep.memory.lastTargetStatus =
    'source-restored';
  return target;
}

function runHarvester(creep) {
  const source = getStoredSource(creep);

  if (!source) {
    return {
      status: creep.memory.lastTargetStatus
        || 'source-unavailable'
    };
  }

  const harvestResult = creep.harvest(source);

  if (harvestResult === ERR_NOT_IN_RANGE) {
    const moveResult = creep.moveTo(source, {
      range: 1,
      reusePath: 10
    });

    return {
      status: 'moving-to-source',
      harvestResult,
      moveResult
    };
  }

  if (
    harvestResult === ERR_NOT_ENOUGH_RESOURCES
  ) {
    clearStoredTarget(creep, 'source-empty');
  }

  return {
    status: harvestResult === OK
      ? 'harvest-submitted'
      : 'harvest-failed',
    harvestResult
  };
}

module.exports.loop = function () {
  const creep = Game.creeps.Harvester1;

  if (!creep || creep.spawning === true) {
    return;
  }

  const outcome = runHarvester(creep);

  if (outcome.status === 'harvest-failed') {
    console.log(JSON.stringify({
      type: 'stored-source-action-failed',
      creepName: creep.name,
      storedTarget: creep.memory.target ?? null,
      ...outcome
    }));
  }
};

For a Creep in its own room, room vision normally exists because the Creep is present. The explicit visibility branch keeps the same pattern usable for remote target records.

An empty Source is not necessarily an invalid Source

A Source can temporarily have no Energy and later regenerate. The correct policy depends on assignment:

  • Fixed miner: keep the ID and wait at the assigned Source.
  • Dynamic harvester: clear the ID and choose another active Source.
  • Managed multi-Creep system: let the room scheduler decide rather than allowing every Creep to compete independently.

The complete example clears the ID after ERR_NOT_ENOUGH_RESOURCES. That is an explicit dynamic-selection policy, not an official requirement.

Define target invalidation rules

A saved target should not live forever merely because its ID is a string. Clear or replace it when:

  • the stored ID is invalid;
  • the room is visible and the object cannot be restored;
  • the object fails the expected type guard;
  • the target no longer satisfies the job's business rule;
  • the route repeatedly fails;
  • the task version or room assignment changes;
  • a scheduler reassigns the Creep.

Keep the ID when the only known problem is missing remote vision.

Use the right identifier for each object type

ObjectCommon durable identifier
Source, Structure, ConstructionSiteid
CreepUsually name; current objects also have an ID.
Flagname
RoomroomName
RoomPositionroomName, x, and y

Do not force every kind of target into an ID-only schema when another stable identifier better matches the API.

Debugging checklist

  • Never save the complete game object in Memory.
  • Validate that the stored ID is a non-empty string.
  • Store the target room for remote or long-lived tasks.
  • Check room vision before deleting a remote ID after null.
  • Validate the restored object type or expected features.
  • Separate object restoration from action return codes.
  • Define whether an empty Source is fixed or replaceable.
  • Clear targets after repeated path or business-rule failure.
  • Record why and when the target changed.
  • Use the Memory guide for JSON-safe storage rules.

Scope and next steps

This guide does not implement Observer vision, Scout scheduling, room intel databases, cross-shard target data, TypeScript object unions, multi-Creep reservations, or path caching. Continue with cleaning dead Creep Memory.

Frequently asked questions

Does Game.getObjectById() return an error code?

No. It returns an object or null.

Does null prove that the target was destroyed?

No. A remote room without current vision can also make the object inaccessible by ID.

Should I store only the ID?

Local tasks may need only the ID. Remote tasks benefit from room name, target kind, and selection metadata.

Should I delete an empty Source ID?

Only when that matches the assignment policy. Fixed miners often keep it; dynamic harvesters may replace it.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.