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
| Event | Representative data |
|---|---|
EVENT_ATTACK | targetId, damage, attackType |
EVENT_BUILD | targetId, amount, coordinates, structure type |
EVENT_HEAL | targetId, amount, healType |
EVENT_REPAIR | targetId, amount, energySpent |
EVENT_UPGRADE_CONTROLLER | amount, energySpent |
EVENT_EXIT | destination 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.databefore 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.