VISION · GAME.ROOMS AND LIVE OBJECTS

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

Understand when a Room exists in Game.rooms, separate current-tick visibility from historical Memory, guard Controller and structure reads, and build a safe visibility-first inspection helper.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — Game.rooms contains rooms currently available through visibility · Tick boundary: Game objects are current-tick objects; historical Memory is not live room visibility

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — Game.rooms contains rooms currently available through visibility
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
Screeps Console test
Pending
Live multi-tick visibility test
Pending
Last verified
July 25, 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.

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