ROOM ECONOMY · STORE CAPACITY

Screeps Store API: getUsedCapacity, getFreeCapacity, and null

Verification statusOfficial API: Checked — Store.getUsedCapacity(), getFreeCapacity(), getCapacity(), resource arguments and null return states · Public engine source: Checked — Store proxy properties, shared-capacity calculation and limited Store compatibility · Public engine tests: Checked — general, limited, inactive, read-only, Lab and specialized Store cases

VERIFICATION

Evidence and test status

Official API
Checked — Store.getUsedCapacity(), getFreeCapacity(), getCapacity(), resource arguments and null return states
Public engine source
Checked — Store proxy properties, shared-capacity calculation and limited Store compatibility
Public engine tests
Checked — general, limited, inactive, read-only, Lab and specialized Store cases
JavaScript syntax
Passed by the article simulation gate
Offline Store cases
Passed — null versus zero, shared capacity, specialized resources, safe amounts and later-delta states
Screeps Console test
Pending
Official-shard action test
Pending
Evidence level
Official documentation, public source review, repository integration, syntax checks and offline simulation only

Quick answer

Use getUsedCapacity(resource) for the amount already stored, getFreeCapacity(resource) for the amount that can still be accepted, and getCapacity(resource) for the maximum capacity that applies to that resource. The difficult part is not the method names. It is knowing whether the Store has one shared capacity, several resource-specific capacities, or no writable capacity at all.

function inspectStoreResource(
  object,
  resourceType
) {
  if (!object?.store) {
    return {
      status: 'store-missing'
    };
  }

  const used = object.store.getUsedCapacity(
    resourceType
  );
  const free = object.store.getFreeCapacity(
    resourceType
  );
  const capacity = object.store.getCapacity(
    resourceType
  );

  return {
    status:
      used === null
      && free === null
      && capacity === null
        ? 'resource-unsupported'
        : 'store-observed',
    resourceType,
    used,
    free,
    capacity
  };
}

Never collapse null into zero with value || 0. Zero usually means the query is valid but empty or full. Null means the requested capacity does not apply, the resource is unsupported by that limited Store, or the object has no writable capacity.

Separate general, limited, and read-only Stores

The official API describes two capacity models. General-purpose Stores use one shared capacity for many resources. Creep, Container, Storage, Terminal, and Factory are common examples. Limited Stores accept only the resources required by their object, such as Energy in a Spawn or Tower, Energy and Power in a Power Spawn, or Energy and one mineral channel in a Lab.

A third operational category is useful in code: read-only resource Stores such as Tombstones and Ruins. They expose stored amounts for withdrawal but do not expose writable capacity.

CategoryExamplesCapacity behavior
GeneralCreep, Container, Storage, Terminal, FactoryResources share one total capacity
LimitedSpawn, Extension, Tower, Lab, Power Spawn, NukerCapacity depends on a valid resource argument
Read-onlyTombstone, RuinUsed amount is readable; capacity and free space are null

Ask the three capacity questions correctly

function readCapacityTriplet(
  store,
  resourceType
) {
  return {
    used: store.getUsedCapacity(resourceType),
    free: store.getFreeCapacity(resourceType),
    capacity: store.getCapacity(resourceType)
  };
}

For a general Store, omitting the resource from getUsedCapacity() returns total used capacity. Omitting it from getCapacity() or getFreeCapacity() returns the shared total and shared remaining capacity. For a limited Store, an omitted resource often produces null because there is no single unrestricted capacity.

Do not assume used + free === capacity until all three values are finite numbers.

function capacityIsConsistent(snapshot) {
  return Number.isFinite(snapshot.used)
    && Number.isFinite(snapshot.free)
    && Number.isFinite(snapshot.capacity)
    && snapshot.used + snapshot.free
      === snapshot.capacity;
}

Treat zero and null as different states

function classifyFreeCapacity(value) {
  if (value === null) {
    return 'capacity-not-applicable';
  }

  if (value === 0) {
    return 'store-full';
  }

  if (Number.isFinite(value) && value > 0) {
    return 'space-available';
  }

  return 'unexpected-capacity-value';
}

An empty valid resource slot returns zero used capacity. A full valid Store returns zero free capacity. A Spawn queried for Power returns null because Power is not valid for that Store. A Tombstone queried for free Energy capacity also returns null because it cannot receive resources.

Understand shared capacity in general Stores

Suppose a Storage has total capacity 2,000 and currently contains 700 Energy and 200 Hydrogen. Its total used capacity is 900 and its shared free capacity is 1,100. Both of these calls return 1,100:

const freeForEnergy = storage.store
  .getFreeCapacity(RESOURCE_ENERGY);
const freeForHydrogen = storage.store
  .getFreeCapacity(RESOURCE_HYDROGEN);

The result does not mean Energy and Hydrogen each have a separate 1,100 slot. They compete for the same remaining space. Resource-specific getUsedCapacity() reports the amount of that resource; resource-specific getFreeCapacity() on a general Store reports the remaining shared capacity.

Pass a resource to limited Stores

function inspectEnergyOnlyStructure(
  structure
) {
  const used = structure.store.getUsedCapacity(
    RESOURCE_ENERGY
  );
  const free = structure.store.getFreeCapacity(
    RESOURCE_ENERGY
  );
  const capacity = structure.store.getCapacity(
    RESOURCE_ENERGY
  );

  if (
    used === null
    || free === null
    || capacity === null
  ) {
    return {
      status: 'energy-capacity-unavailable'
    };
  }

  return {
    status: free > 0
      ? 'needs-energy'
      : 'full',
    used,
    free,
    capacity
  };
}

Spawn, Extension, and Tower code should explicitly pass RESOURCE_ENERGY. A request for an unsupported resource should remain null rather than being silently treated as an empty slot. That distinction catches bad resource routing before transfer() returns an error.

Handle Lab and specialized capacities

A Lab has an Energy capacity and a separate mineral or compound capacity. Power Spawn and Nuker also have multiple allowed resources with separate limits. A total capacity call cannot express those independent channels reliably.

function inspectLabStore(
  lab,
  mineralType
) {
  return {
    energy: inspectStoreResource(
      lab,
      RESOURCE_ENERGY
    ),
    mineral: inspectStoreResource(
      lab,
      mineralType
    )
  };
}

Use the exact intended mineral type. A Lab that already contains one compound may not be a valid destination for a different compound even when a generic mineral-capacity calculation appears nonzero. Capacity is one precondition; object-specific action rules and return codes still apply.

Recognize Tombstone and Ruin Stores

function canReceiveResource(
  object,
  resourceType
) {
  if (!object?.store) {
    return false;
  }

  const free = object.store.getFreeCapacity(
    resourceType
  );

  return Number.isFinite(free)
    && free > 0;
}

A Tombstone or Ruin can report getUsedCapacity(resource) and total used capacity, but its capacity and free-capacity queries are null. The existence of object.store therefore does not prove that the object is a legal transfer destination.

Do not infer validity from Object.keys

The public engine Store proxy returns zero when a known resource property is absent, but enumeration normally exposes only nonzero resources. This means Object.keys(store) is useful for listing currently present resources, not for testing every supported resource.

function listStoredResources(store) {
  return Object.keys(store)
    .filter(resourceType =>
      store.getUsedCapacity(resourceType) > 0
    )
    .map(resourceType => ({
      resourceType,
      amount: store.getUsedCapacity(resourceType)
    }));
}

Use a known resource constant and a Store method when you need to determine its amount or capacity behavior.

Calculate a safe withdraw amount

function calculateWithdrawAmount(
  creep,
  source,
  resourceType,
  requestedAmount = Infinity
) {
  if (!creep?.store || !source?.store) {
    return {
      status: 'store-missing',
      amount: 0
    };
  }

  const available = source.store
    .getUsedCapacity(resourceType);
  const free = creep.store
    .getFreeCapacity(resourceType);

  if (available === null || free === null) {
    return {
      status: 'resource-unsupported',
      amount: 0
    };
  }

  const amount = Math.min(
    available,
    free,
    Number.isFinite(requestedAmount)
      ? Math.max(0, requestedAmount)
      : Infinity
  );

  return {
    status: amount > 0
      ? 'amount-ready'
      : 'nothing-to-withdraw',
    amount,
    available,
    free
  };
}

The amount is bounded by source stock, Creep free capacity, and any caller limit. It is still a current-tick calculation, not proof that the later action will complete.

Calculate a safe transfer amount

function calculateTransferAmount(
  creep,
  target,
  resourceType,
  requestedAmount = Infinity
) {
  if (!creep?.store || !target?.store) {
    return {
      status: 'store-missing',
      amount: 0
    };
  }

  const carried = creep.store
    .getUsedCapacity(resourceType);
  const free = target.store
    .getFreeCapacity(resourceType);

  if (carried === null || free === null) {
    return {
      status: 'resource-unsupported',
      amount: 0
    };
  }

  const amount = Math.min(
    carried,
    free,
    Number.isFinite(requestedAmount)
      ? Math.max(0, requestedAmount)
      : Infinity
  );

  return {
    status: amount > 0
      ? 'amount-ready'
      : 'nothing-to-transfer',
    amount,
    carried,
    free
  };
}

Also validate range, ownership, active structure state, object-specific resource rules, and the actual transfer() return code.

Verify Store changes on a later tick

function createStoreActionEvidence(
  creep,
  target,
  resourceType,
  amount,
  result
) {
  return {
    creepName: creep.name,
    targetId: target.id ?? null,
    resourceType,
    amount,
    submittedAt: Game.time,
    result,
    before: {
      creepUsed: creep.store.getUsedCapacity(
        resourceType
      ),
      targetUsed: target.store.getUsedCapacity(
        resourceType
      )
    }
  };
}

An OK action result means the command was accepted. It does not mutate the script-visible Store immediately. Resolve the same identities on a later tick and compare bounded numeric deltas before reporting the transfer or withdrawal as observed.

Debugging checklist

  1. Confirm that the object exists and has a Store.
  2. Classify it as general, limited, or read-only.
  3. Pass a resource argument to limited Stores.
  4. Record used, free, and capacity separately.
  5. Distinguish zero from null explicitly.
  6. Remember that general resources share one remaining capacity.
  7. Inspect Lab, Power Spawn, and Nuker resources independently.
  8. Use Tombstone and Ruin only as withdrawal sources.
  9. Bound action amounts by source stock and destination space.
  10. Preserve the action result and verify Store deltas later.

Evidence and production boundary

This revision checks the official Store API, the public engine Store implementation, and the engine Store test suite. Repository tests syntax-check every example and cover general shared capacity, limited resource compatibility, null versus zero, Lab resource channels, read-only Stores, amount calculation, and later-delta classification.

Screeps Console execution, official-shard action settlement, concurrent hauler contention, Power effects, structure activation changes, and live CPU cost remain pending. The guide does not claim that a capacity snapshot guarantees an action result.

Official and source references: Store API, Creeps and resources, public Store implementation, and public Store tests.

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.