Use this guide when
Use this guide when a decision must survive later ticks or a global reset and you need to decide whether it belongs in persistent Memory or in disposable heap cache. The three places state can live for this decision are current-tick game state, persistent Memory, and rebuildable JavaScript heap data.
- Current-tick
Gamedata: read the objects and state that exist now. - Persistent
Memory: keep correctness-critical JSON-compatible decisions that must survive a runtime reset. - Module or
globalheap data: cache derived information only when losing it on a global reset is safe.
Save IDs and recover the current object with Game.getObjectById() when a target must be remembered. Live multi-tick verification remains pending for this editorial revision; the page does not present a fabricated reset trace.
Quick answer
Game, JavaScript heap data, and Memory have different lifetimes. Game describes the current tick. Module-scope and global values can be reused across ticks while the same runtime context stays alive, but a global reset can remove them. Memory is the JSON-backed store for state that must still be available after that reset.
Use this guide when: you need a Creep to remember a role, phase, home room, target ID, or another small decision across ticks. Choose another guide when: the value is only a rebuildable performance cache or you need large persistent payloads; those belong in the global-cache or RawMemory Segments guides.
Memory, Game, and heap data have different lifetimes
| Place | Lifetime to design for | Good use | Failure boundary |
|---|---|---|---|
Game | Current tick | Visible Creeps, Structures, Sources, rooms, and current runtime values | Rebuilt for the next tick |
Local variable inside loop | Current loop call | Temporary calculations | Created again on the next call |
Module scope / global | May survive while the runtime context is reused | Derived caches that are cheap enough to rebuild | Can disappear on a global reset |
Memory | Persistent JSON-backed state | Decisions that must survive later ticks and runtime resets | Must remain serializable and intentionally bounded |
This distinction prevents two opposite bugs. Putting every cache into Memory adds unnecessary persistent data and parse/serialization work. Treating heap data as durable state can break the colony after a global reset.
let sourceCache = null;
module.exports.loop = function () {
let temporaryCount = 0;
sourceCache ??= {};
temporaryCount += 1;
// Memory is the durable choice for a decision
// that must survive a global reset.
Memory.colony ??= {};
Memory.colony.mode ??= 'normal';
};
sourceCache may be reused on later ticks, but production code must be able to reconstruct it when the runtime context is recreated. Memory.colony.mode is appropriate only if that mode is genuinely persistent state.
What Memory actually preserves
The official Global Objects documentation separates Game from Memory: the game object is recreated for each tick, while JSON data written through Memory is serialized and made available on later ticks.
Memory.exampleValue = 1;
console.log(JSON.stringify({
tick: Game.time,
value: Memory.exampleValue
}));
That example proves only a write in the current execution. To verify persistence in your own account, inspect the same field on later ticks. This article has not recorded a live multi-tick transcript, so that verification remains pending rather than being presented as observed evidence.
How creep.memory relates to Memory.creeps
A Creep exposes its own persistent memory through creep.memory. For ordinary Creep state, this corresponds to the name-indexed entry under Memory.creeps.
const creep = Game.creeps.Worker1;
if (creep) {
creep.memory.role = 'harvester';
console.log(JSON.stringify({
fromCreep: creep.memory.role,
fromMemory: Memory.creeps?.[creep.name]?.role
}));
}
role is not an official Screeps class. It is your own string, and it has no effect until your code reads it and chooses behavior. Keep spelling and schema rules consistent across the modules that consume it.
Initialize Memory when spawning
StructureSpawn.spawnCreep() accepts an options object with a memory field. This is a clean place to initialize Creep state instead of relying on a later tick to guess the intended role.
const result = Game.spawns.Spawn1.spawnCreep(
[WORK, CARRY, MOVE],
'Worker1',
{
memory: {
role: 'harvester',
working: false,
homeRoom: 'E51S44'
}
}
);
console.log('spawnCreep() returned ' + result);
The Memory option does not bypass normal spawn requirements. The Spawn must be yours, the name and body must be valid, the room must have enough available Energy, and the return code must be checked. Use the first spawnCreep() lesson for that action boundary.
Persist a working-state decision
A boolean such as working is useful when the next action depends on a phase that should not reset every tick. Switch at resource boundaries, not on every loop call:
const usedEnergy = creep.store.getUsedCapacity(
RESOURCE_ENERGY
);
const freeEnergy = creep.store.getFreeCapacity(
RESOURCE_ENERGY
);
if (usedEnergy === 0) {
creep.memory.working = false;
} else if (freeEnergy === 0) {
creep.memory.working = true;
}
At partial Energy, the previous state is preserved. Writing the boolean does not make the Creep harvest, transfer, build, or upgrade automatically; your action logic must read it. The working-state guide covers that state machine in detail.
Choose data that belongs in Memory
| Candidate | Store in Memory? | Reason |
|---|---|---|
'harvester' | Usually yes | Small persistent role decision |
true / false | Usually yes | Small phase or policy state |
'E51S44' | Yes when needed later | Stable room identifier |
| Game object ID string | Yes when the target must be remembered | Lets later code recover the current visible object |
| Pathfinding result that can be rebuilt | Usually heap cache first | Derived data should tolerate reset and avoid unnecessary persistent writes |
| Unbounded event history | No | It grows indefinitely and turns diagnostics into persistent overhead |
Live Source, Creep, or Structure object | No | The object belongs to current game state, not durable JSON state |
If you store arrays or objects, give them a clear schema and a bound. Persistence is not a reason to retain data forever.
Store object IDs, not live objects
When a Creep needs to remember a Source, store both the stable ID and enough location context to distinguish “not visible” from “visible but unavailable” later:
const source = creep.pos.findClosestByRange(
FIND_SOURCES_ACTIVE
);
if (source) {
creep.memory.sourceId = source.id;
creep.memory.sourceRoom = source.pos.roomName;
}
Game.getObjectById() can access room objects only when their room is visible. A null result therefore does not, by itself, prove that a remembered remote object was destroyed or that its ID is invalid.
function resolveRememberedSource(creep) {
const sourceId = creep.memory.sourceId;
const sourceRoom = creep.memory.sourceRoom;
if (
typeof sourceId !== 'string'
|| typeof sourceRoom !== 'string'
) {
return { status: 'source-not-assigned', source: null };
}
if (!Game.rooms[sourceRoom]) {
return {
status: 'source-room-not-visible',
source: null
};
}
const source = Game.getObjectById(sourceId);
if (!source) {
return {
status: 'source-missing-in-visible-room',
source: null
};
}
return { status: 'source-visible', source };
}
Keep the assignment when the room is merely out of vision. If the room is visible and a target that your design expects to be persistent is genuinely missing, then clear or repair the assignment according to that target type. Transient targets such as dropped Resources need a different invalidation policy from persistent room objects such as Sources.
Handle Memory after a Creep dies
Game.creeps describes current Creeps. Name-indexed Creep Memory can outlive the Creep, so stale entries may remain after the name disappears from Game.creeps.
for (const name in Memory.creeps) {
if (!Game.creeps[name]) {
console.log(name + ' exists in Memory but not Game.creeps');
}
}
This diagnostic does not delete anything. Use the dead Creep Memory cleanup guide before adding deletion, especially when other name-indexed task structures depend on the same Creep.
Minimal reset-safe example
State impact: this loop initializes two small fields on Worker1. It does not move the Creep or submit a game action.
module.exports.loop = function () {
const creep = Game.creeps.Worker1;
if (!creep) {
console.log('Worker1 was not found.');
return;
}
if (typeof creep.memory.role !== 'string') {
creep.memory.role = 'harvester';
}
if (typeof creep.memory.working !== 'boolean') {
creep.memory.working = false;
}
console.log(JSON.stringify({
tick: Game.time,
name: creep.name,
role: creep.memory.role,
working: creep.memory.working
}));
};
To verify it yourself, observe the same Memory fields on later ticks. Then trigger or wait for a real global reset only if you already have a safe way to identify one; the persistent fields should not depend on heap-cache survival. No such live reset trace is claimed by this article.
Production adaptation notes
Keep correctness state small and explicit. A useful production split is:
- Memory: role, phase, durable task identity, home room, schema version, and other decisions whose loss would change behavior incorrectly.
- Heap cache: derived lookup tables, parsed configuration, path helpers, or indexes that can be rebuilt after a reset.
- RawMemory Segments: larger persistent string payloads that need their own activation and validation lifecycle.
Do not duplicate the same truth in several places unless you define which copy is authoritative. If a heap cache is derived from Memory, rebuild from Memory after reset rather than attempting to make both independently persistent.
Debugging checklist
- Check that the Creep exists before reading
creep.memory. - Use one spelling and schema for role or phase values.
- Do not expect a custom Memory field to perform an action by itself.
- Persist correctness-critical decisions; keep rebuildable derived data in heap cache.
- Assume module/global cache can vanish after a global reset.
- Store object IDs instead of live game objects.
- Handle
Game.getObjectById()returningnullwithout deleting a remote assignment until visibility makes that conclusion safe. - Bound arrays, histories, and diagnostic data.
- Do not infer a death cause merely because a name remains in
Memory.creeps.
Choose the next guide
Use Disposable Global Cache when the problem is repeated calculation that is safe to rebuild. Use RawMemory Segments when you need larger persistent string storage. Use Working State when you want a Creep to act on a persistent acquire/spend phase.
Frequently asked questions
Does a normal JavaScript variable survive every Screeps tick?
A local variable created inside module.exports.loop is recreated on the next call. Module-scope or global values may survive runtime-context reuse, but they can disappear on a global reset. Use Memory when correctness requires the value after that reset.
Is creep.memory different from Memory.creeps?
creep.memory exposes the Creep-specific persistent Memory associated with that Creep name. It is the convenient place for small per-Creep state.
Can I save a Source or Structure object directly?
Do not treat a live object as durable state. Save its id, resolve it with Game.getObjectById() later, and handle null.
When should I use global cache instead?
Use heap cache for derived data that is safe to lose and deterministic to rebuild. Use Memory when loss after a global reset would make behavior incorrect.