VISION · GAME.ROOMS AND LIVE OBJECTS

Why Is Game.rooms[roomName] Undefined in Screeps?

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Debugging / Observability

Recommended next steps

Use an Observer for remote visibilityAcquire visionSeparate stored IDs from current live objectsObject recoveryConnect visibility to cross-room routingMovement branch
Verification statusChinese source article: Reviewed in full — the August 15 current-vision, saved-ID, lastSeenAt, and Observer timing boundaries are preserved · Tick boundary: Game objects are current-tick objects; historical Memory is not live room visibility · Object boundary: Controller, owner, structures, Sources and other Room data are read only after the Room guard

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full — the August 15 current-vision, saved-ID, lastSeenAt, and Observer timing boundaries are preserved
Tick boundary
Game objects are current-tick objects; historical Memory is not live room visibility
Object boundary
Controller, owner, structures, Sources and other Room data are read only after the Room guard
JavaScript syntax
Passed
Offline state review
Passed — visible, invisible, remembered, controllerless, owned and reserved room states
Official documentation
Checked August 18, 2026 — Game.rooms visibility, Game.getObjectById(), RoomPosition, Game.map, current-tick Game objects, and Observer next-tick semantics
Static code review
Passed — room-name and RoomPosition representation do not imply vision, live Room access is explicitly gated, unknown saved-object state is distinct from confirmed absence, and Observer attribution is not inferred from visibility alone
Evidence level
Official-documentation review, Chinese-source review, and static code review only; no real-shard execution is claimed
Console test pending
Not run in this editorial pass
Screeps Console test
Pending — no real-shard Console transcript was collected for this revision
Live multi-tick verification pending
No live Creep, structure, or Observer visibility transition, saved-object disappearance, or multi-tick attribution trace was collected for this revision
Last editorial review
August 18, 2026

Quick answer

Game.rooms[roomName] is undefined when that room is not available to your script in the current tick. Check the Room before reading its Controller, structures, Sources, hostiles, or terrain-dependent live objects. A value in Memory.rooms[roomName] is historical application data, not proof of current vision.

What Game.rooms actually contains

Game.rooms is a hash of live Room objects available during the current tick. The global Game object is rebuilt each tick, so a Room reference is not a permanent handle that should be kept in Memory.

const roomName = 'W2N2';
const room = Game.rooms[roomName];

if (!room) {
  console.log({
    type: 'room-not-visible',
    roomName,
    tick: Game.time
  });
  return;
}

console.log({
  type: 'room-visible',
  roomName: room.name,
  tick: Game.time
});

The guard is not optional defensive style. It is the boundary between “a live Room exists now” and “your code only knows a room name.”

What can make a room visible

The official API describes rooms as visible when you have a Creep or an owned structure in them. Other game mechanisms can also make a room available, including an Observer request completed for the current tick. The important application rule is simpler: use the presence of Game.rooms[roomName] as the current-tick fact.

StateWhat your code may conclude
Game.rooms[name] existsThe Room object is available now
The key is missingCurrent room data is unavailable
A Creep was there previouslyHistorical fact only
An Observer request returned OK this tickThe request was scheduled; read the target next tick
Memory contains room intelSaved data exists, with unknown freshness unless timestamped

Memory.rooms is not a live Room object

Memory.rooms may contain data after a room disappears from Game.rooms. That is useful for intel, route policy, source IDs, or your own configuration, but it must not be treated as a current Room snapshot.

function describeRoomAvailability(roomName) {
  const room = Game.rooms[roomName];
  const remembered = Memory.rooms?.[roomName];

  return {
    roomName,
    visibleNow: Boolean(room),
    hasRememberedData: Boolean(remembered),
    rememberedAt: Number.isInteger(remembered?.observedAt)
      ? remembered.observedAt
      : null
  };
}

Without a timestamp and an explicit data contract, an old entry cannot answer whether the Controller is still owned, whether hostiles are present, or whether a Structure still exists.

Read a room with an explicit guard

State impact: this example reads live state and logs a bounded summary. It does not write Memory or submit game actions.

function inspectVisibleRoom(roomName) {
  if (typeof roomName !== 'string' || roomName.length === 0) {
    return {
      status: 'room-name-invalid',
      roomName: null
    };
  }

  const room = Game.rooms[roomName];

  if (!room) {
    return {
      status: 'room-not-visible',
      roomName,
      hasRememberedData: Boolean(
        Memory.rooms?.[roomName]
      )
    };
  }

  const controller = room.controller;

  return {
    status: 'room-visible',
    roomName: room.name,
    controllerPresent: Boolean(controller),
    controllerOwner: controller?.owner?.username ?? null,
    controllerReservation:
      controller?.reservation?.username ?? null,
    controllerLevel: controller?.level ?? 0,
    sourceCount: room.find(FIND_SOURCES).length,
    hostileCount: room.find(FIND_HOSTILE_CREEPS).length
  };
}

module.exports.loop = function () {
  const result = inspectVisibleRoom('W2N2');

  if (Game.time % 50 === 0) {
    console.log(JSON.stringify({
      type: 'room-visibility-check',
      tick: Game.time,
      ...result
    }));
  }
};

The function returns a distinct status instead of replacing missing vision with an empty object. That prevents “unknown” from silently becoming “no hostiles,” “no Controller,” or “no structures.”

Complete visibility-first inspection helper

For reusable code, separate the live Room requirement from the business operation. Callers then choose whether to skip, request vision, use stale intel, or retry later.

function withVisibleRoom(roomName, reader) {
  if (
    typeof roomName !== 'string'
    || typeof reader !== 'function'
  ) {
    return {
      ok: false,
      status: 'arguments-invalid',
      value: null
    };
  }

  const room = Game.rooms[roomName];

  if (!room) {
    return {
      ok: false,
      status: 'room-not-visible',
      value: null
    };
  }

  try {
    return {
      ok: true,
      status: 'read-complete',
      value: reader(room)
    };
  } catch (error) {
    return {
      ok: false,
      status: 'reader-failed',
      value: null,
      message: error instanceof Error
        ? error.message
        : String(error)
    };
  }
}

const result = withVisibleRoom(
  'W2N2',
  room => ({
    roomName: room.name,
    structures: room.find(FIND_STRUCTURES).length
  })
);

The try/catch does not replace correct property checks. It only isolates an application reader so one inspection failure does not crash unrelated role logic.

Handle Controller states separately

A visible room can still have no Controller. Highways and some special rooms are examples where room.controller may be absent. A Controller can also be neutral, reserved, owned by you, or owned by another player.

ConditionMeaning
!roomNo current Room object
room && !room.controllerVisible room without a Controller
controller.myYour owned Controller
controller.ownerAn owned Controller
controller.reservationA reservation exists

Do not combine these states into one truthy test when ownership, reservation, or room type changes behavior.

Object IDs also depend on visibility

An ID stored in Memory remains a string, but resolving it with Game.getObjectById() can return null when the object is unavailable. Keep the ID, distinguish missing vision from confirmed deletion when possible, and resolve it again after vision returns.

function resolveRememberedTarget(roomName, targetId) {
  const visible = Boolean(Game.rooms[roomName]);
  const target = targetId
    ? Game.getObjectById(targetId)
    : null;

  if (!visible) {
    return {
      status: 'room-not-visible',
      target: null
    };
  }

  return target
    ? { status: 'target-visible', target }
    : { status: 'target-missing-in-visible-room', target: null };
}

When the room is visible and the expected object is still missing, your code has stronger evidence that the target was destroyed, expired, moved out of scope, or the stored ID is wrong.

Separate room names, positions, map data, and live Rooms

A valid room name is not the same thing as a live Room. Code can store a room-name string, construct a RoomPosition, or ask Game.map for map-level information without proving that Game.rooms[roomName] exists in this tick. Gate current Room methods and current Room objects on the live Game.rooms entry.

ValueWhat it provesWhat it does not prove
Room-name stringYour code has an identifier.Current vision.
RoomPositionA room coordinate can be represented.A live Room or current objects at that coordinate.
Game.map resultMap-level information for that map operation.That room.find() or room.controller is readable now.
Game.rooms[roomName]A live Room object is available this tick.Why vision exists or how long it will remain.
function describeRoomHandle(roomName) {
  if (typeof roomName !== 'string') {
    return { status: 'room-name-invalid' };
  }

  const position = new RoomPosition(
    25,
    25,
    roomName
  );
  const room = Game.rooms[roomName] || null;

  return {
    status: room
      ? 'live-room-available'
      : 'live-room-unavailable',
    roomName,
    representedPosition: {
      x: position.x,
      y: position.y,
      roomName: position.roomName
    },
    liveRoom: Boolean(room)
  };
}

Do not infer why vision exists

If Game.rooms[roomName] exists, you can safely say the room is available to the current script. Do not automatically say an Observer caused that vision: your Creeps, structures, or another supported visibility source may already expose the room. When attribution matters, keep the Observer ID, requested room, and request tick as separate evidence.

Apply the same evidence discipline to saved object IDs. If Game.getObjectById(id) returns null while the expected room is not visible, classify the object state as unknown rather than deleted. Once the expected room is visible, a missing ID is stronger evidence that the saved target is stale or absent; mobile-object assumptions still need their own identity check.

Use the Observer guide for the request/next-tick vision lifecycle and the saved-target guide for ID restoration.

Prove visibility across multiple ticks

A single log line proves only one tick. To debug intermittent visibility, record a compact transition history rather than writing entire Room objects.

function recordVisibility(roomName) {
  Memory.visibilityHistory ??= {};
  const history = Memory.visibilityHistory[roomName]
    ?? [];

  history.push({
    tick: Game.time,
    visible: Boolean(Game.rooms[roomName])
  });

  Memory.visibilityHistory[roomName] =
    history.slice(-20);
}

State impact: this writes a bounded Memory history. Twenty entries is an example limit, not an official recommendation.

Debugging checklist

  • Verify the exact room-name string.
  • Check Game.rooms[roomName] before every Room property chain.
  • Keep missing vision distinct from an empty room.
  • Do not use Memory.rooms as current visibility.
  • Timestamp saved intel.
  • Check room.controller before owner or reservation fields.
  • Resolve stored object IDs only after considering room visibility.
  • Log state transitions across ticks for intermittent cases.
  • Use an Observer or another vision source when current data is required.

Scope and next steps

This guide does not implement Observer scheduling, room-intel expiration policy, portals, shard travel, map status, or hostile-risk scoring. Continue with StructureObserver.observeRoom() timing and request state.

Frequently asked questions

Is an undefined Room a JavaScript error?

No. It is a normal state when the room is not available in the current tick. The error occurs only when code dereferences it without a guard.

Can old Memory fill in the live Room object?

No. Memory can provide your saved summary, not current game objects.

Should invisible mean safe?

No. Treat it as unknown.

What should request fresh visibility?

A Creep, owned vision-providing object, Observer workflow, or another applicable game mechanism must make the room available.

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.