OBSERVABILITY · PREVIOUS-TICK EVENT LOGS

How to Read Room.getEventLog() Safely in Screeps

Read previous-tick room events, distinguish parsed arrays from raw JSON strings, validate event-specific data, preserve IDs when objects disappear, filter attacks on owned targets, and store bounded aggregates instead of unlimited history.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — previous-tick timing, parsed and raw modes, event fields and attack types · Timing boundary: The log describes the previous tick; current action return codes remain separate

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — previous-tick timing, parsed and raw modes, event fields and attack types
Timing boundary
The log describes the previous tick; current action return codes remain separate
Object boundary
Missing current objects do not invalidate their historical event IDs
JavaScript syntax
Passed
Offline event review
Passed — attack filtering, malformed data, vanished targets, ownership snapshots, raw parsing and bounded aggregation
Screeps Console test
Pending
Live room event, combat and multi-tick history test
Pending
Last verified
July 25, 2026

Quick answer

room.getEventLog() returns events that happened in that room during the previous tick. The default mode returns a parsed array. Passing a truthy raw argument returns a JSON string. Validate event.event before reading event-specific data, preserve IDs even when current objects are missing, and store bounded summaries when you need history.

The event log belongs to the previous tick

tick 99
combat, build, repair, harvest, transfer, and other outcomes occur

tick 100 begins
object state reflects those outcomes
room.getEventLog() returns tick 99 events
commands submitted during tick 100 are not those returned events

This code does not prove that the current attack created an event:

const actionResult = creep.attack(target);
const previousTickEvents = creep.room.getEventLog();

console.log({
  actionResult,
  eventTick: Game.time - 1,
  eventCount: previousTickEvents.length
});

The immediate return code and the previous-tick event array answer different questions.

Parsed mode and raw mode

const parsedEvents = room.getEventLog();
const rawJson = room.getEventLog(true);

console.log({
  parsedIsArray: Array.isArray(parsedEvents),
  rawIsString: typeof rawJson === 'string'
});

The official API notes that the first parsed access has JSON parsing cost and later parsed calls in the same tick reuse a cached result. Raw mode is useful only when your code deliberately controls parsing or forwards the string.

function parseRawEventLog(raw) {
  if (typeof raw !== 'string') {
    return {
      status: 'raw-not-string',
      events: []
    };
  }

  try {
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed)
      ? { status: 'parsed', events: parsed }
      : { status: 'not-array', events: [] };
  } catch (error) {
    return {
      status: 'invalid-json',
      events: [],
      error: error instanceof Error
        ? error.message
        : String(error)
    };
  }
}

Different events have different data

EventRepresentative data
EVENT_ATTACKtargetId, damage, attackType
EVENT_BUILDtargetId, amount, coordinates, structure type
EVENT_HEALtargetId, amount, healType
EVENT_REPAIRtargetId, amount, energySpent
EVENT_UPGRADE_CONTROLLERamount, energySpent
EVENT_EXITdestination room and coordinates

Do not assume every event has a targetId. Normalize each supported event type separately.

Normalize attack events defensively

function getAttackTypeName(value) {
  switch (value) {
    case EVENT_ATTACK_TYPE_MELEE:
      return 'melee';
    case EVENT_ATTACK_TYPE_RANGED:
      return 'ranged-or-tower';
    case EVENT_ATTACK_TYPE_RANGED_MASS:
      return 'ranged-mass';
    case EVENT_ATTACK_TYPE_DISMANTLE:
      return 'dismantle';
    case EVENT_ATTACK_TYPE_HIT_BACK:
      return 'hit-back';
    case EVENT_ATTACK_TYPE_NUKE:
      return 'nuke';
    default:
      return 'unknown';
  }
}

function normalizeAttackEvent(event) {
  if (!event || event.event !== EVENT_ATTACK) {
    return null;
  }

  const data = event.data
    && typeof event.data === 'object'
    ? event.data
    : {};

  if (typeof data.targetId !== 'string') {
    return null;
  }

  return {
    attackerId: typeof event.objectId === 'string'
      ? event.objectId
      : null,
    targetId: data.targetId,
    damage: Number.isFinite(data.damage)
      ? data.damage
      : 0,
    attackType: data.attackType ?? null,
    attackTypeName:
      getAttackTypeName(data.attackType)
  };
}

An unknown attack type is safer than incorrectly labeling a future or unsupported constant.

Identify attacks on owned targets

function classifyCurrentTarget(targetId) {
  const target = Game.getObjectById(targetId);

  if (!target) {
    return {
      status: 'target-unavailable',
      ownedNow: null,
      target: null
    };
  }

  return {
    status: target.my === true
      ? 'target-owned-now'
      : 'target-not-owned-now',
    ownedNow: target.my === true,
    target
  };
}

This checks current ownership and availability. It cannot prove ownership at the previous tick when the object no longer exists.

Preserve events when objects disappear

A valid historical event can reference a target or actor that is now dead, destroyed, outside vision, or otherwise unavailable. Keep the IDs and event fields in the incident record.

function enrichAttack(attack) {
  const target = Game.getObjectById(attack.targetId);
  const attacker = attack.attackerId
    ? Game.getObjectById(attack.attackerId)
    : null;

  return {
    ...attack,
    targetAvailableNow: Boolean(target),
    targetOwnedNow: target?.my ?? null,
    attackerAvailableNow: Boolean(attacker)
  };
}

Do not discard the event merely because enrichment fails.

Complete previous-tick attack reader

State impact: this reader reads the current visible Room and previous-tick events. It writes a bounded incident list and aggregate counters.

function getOwnedTargetIds(room) {
  return new Set([
    ...room.find(FIND_MY_CREEPS),
    ...room.find(FIND_MY_STRUCTURES)
  ].map(object => object.id));
}

function readPreviousTickAttacks(room) {
  const knownOwnedNow = getOwnedTargetIds(room);
  const previousOwned = new Set(
    Array.isArray(Memory.ownedObjectIds?.[room.name])
      ? Memory.ownedObjectIds[room.name]
      : []
  );
  const attacks = [];

  for (const event of room.getEventLog()) {
    const attack = normalizeAttackEvent(event);
    if (!attack) {
      continue;
    }

    const ownedBySnapshot =
      previousOwned.has(attack.targetId);
    const ownedNow = knownOwnedNow.has(attack.targetId);

    if (!ownedBySnapshot && !ownedNow) {
      continue;
    }

    attacks.push({
      tick: Game.time - 1,
      roomName: room.name,
      ownedBySnapshot,
      ownedNow,
      ...enrichAttack(attack)
    });
  }

  Memory.eventIncidents ??= {};
  const history = Array.isArray(
    Memory.eventIncidents[room.name]
  )
    ? Memory.eventIncidents[room.name]
    : [];
  Memory.eventIncidents[room.name] = [
    ...history,
    ...attacks
  ].slice(-100);

  Memory.eventStats ??= {};
  const stats = Memory.eventStats[room.name] || {
    attacksOnMine: 0,
    lastAttackTick: null
  };
  stats.attacksOnMine += attacks.length;
  if (attacks.length > 0) {
    stats.lastAttackTick = Game.time - 1;
  }
  Memory.eventStats[room.name] = stats;

  Memory.ownedObjectIds ??= {};
  Memory.ownedObjectIds[room.name] = [
    ...knownOwnedNow
  ];

  return attacks;
}

The previous ownership snapshot improves destroyed-target classification. It is still an application snapshot, so missed ticks or lost vision reduce confidence.

Track ownership before destruction

EVENT_OBJECT_DESTROYED can report that an object was destroyed, but current Game.getObjectById() cannot recover its old my property. Preserve a bounded previous-tick ID set or another ownership snapshot before the next event-read cycle.

function snapshotOwnedObjects(room) {
  Memory.ownedObjectIds ??= {};
  Memory.ownedObjectIds[room.name] = [
    ...room.find(FIND_MY_CREEPS),
    ...room.find(FIND_MY_STRUCTURES)
  ].map(object => object.id);
}

Store bounded aggregates

function summarizeEvents(events) {
  const counts = {};

  for (const event of events) {
    const key = String(event?.event);
    counts[key] = (counts[key] || 0) + 1;
  }

  return counts;
}

Aggregate counts, last-seen ticks, and a short incident list are usually cheaper than storing every raw event permanently. Define retention, reset, and missing-room policies.

Event logs do not replace action results

const result = tower.attack(target);

Memory.actionResults ??= [];
Memory.actionResults.push({
  tick: Game.time,
  actorId: tower.id,
  targetId: target.id,
  action: 'tower-attack',
  result
});
Memory.actionResults =
  Memory.actionResults.slice(-100);

The action result tells whether the command was accepted now. The next tick event log describes what happened in the prior tick. Record both when causality matters.

Debugging checklist

  • Require current room visibility before reading the log.
  • Label records with Game.time - 1.
  • Keep raw strings separate from parsed arrays.
  • Normalize each event type independently.
  • Validate event.data before reading fields.
  • Keep actor and target IDs when objects disappear.
  • Use a previous ownership snapshot for destroyed targets.
  • Record current action return codes separately.
  • Bound incident history.
  • Aggregate long-term statistics.

Scope and next steps

This guide does not implement complete schemas for every event type, durable Segment archives, cross-room incident correlation, combat attribution confidence, or live server validation. Continue with RoomVisual debugging to display current targets and state without confusing visuals with outcomes.

Frequently asked questions

Is the event log current-tick telemetry?

No. It describes the previous tick.

Is raw mode faster?

It avoids the built-in parsed object when you do not need it, but custom parsing still costs CPU and should be measured.

Does a missing target make the event invalid?

No. Preserve its historical ID and mark current availability separately.

Can getEventLog provide long-term history?

No. Your code must store bounded incidents or aggregates.

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.