FOUNDATION · WORKING STATE

Use Store Boundaries as Hysteresis, Not a Tick Toggle

Verification statusOfficial documentation: Checked August 12, 2026 — Memory, Creep.memory, Store.getUsedCapacity(), getFreeCapacity(), getCapacity(), and game-loop timing · Static code review: Passed — empty/full hysteresis, partial-state preservation, first-run policy, invalid Store handling, change-only Memory writes, and action/state boundaries reviewed · Chinese source article: Existing bilingual mapping retained; this English-only pass did not use the Chinese page as live-game evidence

VERIFICATION

Evidence and test status

Official documentation
Checked August 12, 2026 — Memory, Creep.memory, Store.getUsedCapacity(), getFreeCapacity(), getCapacity(), and game-loop timing
Static code review
Passed — empty/full hysteresis, partial-state preservation, first-run policy, invalid Store handling, change-only Memory writes, and action/state boundaries reviewed
Chinese source article
Existing bilingual mapping retained; this English-only pass did not use the Chinese page as live-game evidence
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 multi-tick worker trace was collected showing Store boundaries and phase transitions in a real room
Last verified
August 12, 2026

Use this guide when

Use this pattern when one Creep alternates between acquiring Energy and spending Energy, and you want the phase to persist across ticks without reversing after every small resource change. The working field is your own Memory contract; Screeps does not provide a built-in “working state.”

If the role is a fixed upgrader that refills from a nearby Link or Container while continuing to work, a full-to-empty cycle may be the wrong policy. This page is about a two-phase worker, not every possible role.

Define the phase contract

working = false
  acquire Energy

working = true
  spend Energy on the assigned task

Keep that meaning stable. Do not let one module interpret working: true as “has any Energy” while another interprets it as “currently executing a work action.” The field is a decision state, not proof that an action succeeded.

Use empty and full as hysteresis

Two boundaries create a stable cycle:

  • When carried Energy reaches 0, switch to acquisition.
  • When free Energy capacity reaches 0, switch to working.
  • At partial Energy, keep the previous initialized phase.
0 / 50   -> acquire
50 / 50  -> work
30 / 50  -> keep previous phase

This is hysteresis: the transition into each phase happens at a different boundary. It avoids the task-flapping produced by creep.memory.working = !creep.memory.working or by switching to work as soon as usedEnergy > 0.

Make the decision pure

function decideWorkingState(input) {
  const {
    usedEnergy,
    freeEnergy,
    totalCapacity,
    previousWorking
  } = input;

  if (
    !Number.isFinite(usedEnergy)
    || !Number.isFinite(freeEnergy)
    || !Number.isFinite(totalCapacity)
    || usedEnergy < 0
    || freeEnergy < 0
    || totalCapacity <= 0
  ) {
    return {
      valid: false,
      changed: false,
      working: previousWorking === true,
      reason: 'invalid-store-values'
    };
  }

  if (usedEnergy === 0) {
    return {
      valid: true,
      changed: previousWorking !== false,
      working: false,
      reason: 'energy-empty'
    };
  }

  if (freeEnergy === 0) {
    return {
      valid: true,
      changed: previousWorking !== true,
      working: true,
      reason: 'energy-full'
    };
  }

  if (typeof previousWorking === 'boolean') {
    return {
      valid: true,
      changed: false,
      working: previousWorking,
      reason: 'partial-keep-previous'
    };
  }

  return {
    valid: true,
    changed: true,
    working: false,
    reason: 'partial-initialized'
  };
}

The function has no Screeps side effects. You can test empty, full, partial, invalid, and first-run inputs without a live room.

Handle uninitialized partial Energy explicitly

A Creep can enter this code with partial Energy while creep.memory.working is undefined—for example after a schema change or after another system initialized the Creep differently. The API does not define what your custom boolean should become.

This guide chooses acquisition mode for an uninitialized partial Creep:

partial Energy + no previous boolean
-> working = false
-> reason = partial-initialized

That is a project policy, not an official Screeps rule. An emergency repair role could choose the opposite. The quality requirement is that the first-run rule is explicit and deterministic.

Write Memory only when the phase changes

Do not teach a persistent-state machine by rewriting the same boolean and “last checked tick” on every tick. Calculate first, then write the durable phase only when needed.

function updateWorkingState(creep) {
  const decision = decideWorkingState({
    usedEnergy:
      creep.store.getUsedCapacity(RESOURCE_ENERGY),
    freeEnergy:
      creep.store.getFreeCapacity(RESOURCE_ENERGY),
    totalCapacity:
      creep.store.getCapacity(RESOURCE_ENERGY),
    previousWorking: creep.memory.working
  });

  if (!decision.valid) {
    return decision;
  }

  if (decision.changed) {
    creep.memory.working = decision.working;
  }

  return decision;
}

decision.changed is useful for debugging and for limiting persistent writes. If you need transition history, keep it bounded and only append on real transitions rather than on every loop call.

Keep state transition and action result separate

The state decision chooses which branch should run. It does not prove that the branch's game action succeeded.

const decision = updateWorkingState(creep);

if (!decision.valid) {
  console.log(JSON.stringify({
    type: 'working-state-invalid',
    creepName: creep.name,
    reason: decision.reason
  }));
  return;
}

if (decision.working) {
  const result = creep.upgradeController(controller);
  // Handle result separately.
} else {
  const result = creep.harvest(source);
  // Handle result separately.
}

Submitting harvest() does not mean Store is already fuller in the current JavaScript execution, and submitting upgradeController() does not mean Store is already empty. Read the next tick's Store state and let the resource boundary drive the later transition.

Likewise, if the action returns ERR_NOT_IN_RANGE, movement is a separate operation. Keep the action result and movement result in different variables rather than changing the working phase because a movement call returned OK.

Do not force this cycle onto every role

  • Fixed upgrader: may withdraw locally and continue upgrading without waiting to fill completely.
  • Dedicated hauler: may use source/destination assignment rather than a generic “working” boolean.
  • Emergency repairer: may deliberately begin spending partial Energy before reaching full capacity.
  • Miner: often has a fixed position and a different resource-flow contract.

The empty/full pattern is a reusable state machine, not a universal role architecture.

Minimal diagnostic loop

State impact: this example may update only creep.memory.working when the phase changes. It does not submit a movement or work action.

module.exports.loop = function () {
  const creep = Game.creeps.Worker1;
  if (!creep || creep.spawning === true) return;

  const decision = updateWorkingState(creep);

  if (!decision.valid || decision.changed) {
    console.log(JSON.stringify({
      tick: Game.time,
      creepName: creep.name,
      working: decision.working,
      changed: decision.changed,
      reason: decision.reason,
      usedEnergy:
        creep.store.getUsedCapacity(RESOURCE_ENERGY),
      freeEnergy:
        creep.store.getFreeCapacity(RESOURCE_ENERGY)
    }));
  }
};

This produces a smaller, more meaningful log: invalid state and actual phase transitions, not one line every tick.

What static review does not prove

The boundary cases and Memory-write policy are reviewable offline. This revision does not contain a live multi-tick transcript of a Creep filling, switching, draining, and switching back. Console and live verification remain pending.

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.