RESOURCES · FACTORY BATCH VERIFICATION

Produce One Commodity Without Confusing Hauling with Production

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Market / Industry

Recommended next steps

Move missing inputs or outputs across RoomsLogistics pathBudget Factory consumption against StorageResource budgetTrace raw mineral supply into productionUpstream resource
Verification statusChinese source article: Reviewed in full · Official docs: Checked — produce(), COMMODITIES, permanent Factory level, active PWR_OPERATE_FACTORY, cooldown and return codes · Level boundary: Permanent factory.level and the temporary active operation effect are evaluated separately

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — produce(), COMMODITIES, permanent Factory level, active PWR_OPERATE_FACTORY, cooldown and return codes
Level boundary
Permanent factory.level and the temporary active operation effect are evaluated separately
Attribution boundary
Exact component and output deltas require an enforced exclusive Factory Store window
Execution boundary
OK means production was scheduled; every component and the output require later verification
JavaScript syntax
Passed
Offline production review
Passed — recipe, components, capacity, permanent level, active effect, exact batch, no-delta and ambiguity states
Screeps Console test
Pending
Live level assignment, effect expiry, exclusive hauling and exact batch delta test
Pending
Live multi-tick verification
Pending
Last verified
August 1, 2026

Use this guide when

Use this workflow when one scheduler must submit one Factory batch and distinguish production from ordinary hauling. Output growth alone is not enough: a hauler can add the product, remove components, or change free capacity. Exact verification records every recipe component and requires an exclusive Store window.

Bind one request to one Factory and commodity

function validateFactoryRequest(request) {
  if (!request || request.enabled !== true) {
    return { valid: false, reason: 'disabled' };
  }

  if (
    typeof request.requestId !== 'string'
    || request.requestId.length === 0
    || typeof request.factoryId !== 'string'
    || typeof request.resourceType !== 'string'
  ) {
    return {
      valid: false,
      reason: 'request-invalid'
    };
  }

  return { valid: true, reason: 'valid' };
}

function getOwnedActiveFactory(id) {
  const factory = Game.getObjectById(id);

  return factory
    && factory.structureType ===
      STRUCTURE_FACTORY
    && factory.my === true
    && factory.isActive() === true
      ? factory
      : null;
}

The exact Factory ID prevents a room search from selecting another structure. The request ID identifies the approved batch across ticks.

Read the current recipe from COMMODITIES

function getCommodityRecipe(resourceType) {
  const recipe =
    typeof resourceType === 'string'
      ? COMMODITIES[resourceType]
      : null;

  if (
    !recipe
    || !Number.isInteger(recipe.amount)
    || recipe.amount <= 0
    || !recipe.components
  ) {
    return null;
  }

  return recipe;
}

function readComponentAmounts(factory, recipe) {
  return Object.fromEntries(
    Object.keys(recipe.components).map(
      resourceType => [
        resourceType,
        factory.store.getUsedCapacity(
          resourceType
        )
      ]
    )
  );
}

Use the runtime constant rather than a copied recipe table. The component map is also the identity of the batch that must be reconciled later.

Separate permanent Factory level from active operation

function getOperateFactoryEffect(factory) {
  return factory.effects?.find(effect =>
    effect.effect === PWR_OPERATE_FACTORY
    && effect.ticksRemaining > 0
  ) ?? null;
}

function checkFactoryLevel(factory, recipe) {
  if (!Number.isInteger(recipe.level)) {
    return {
      ready: true,
      reason: 'level-not-required'
    };
  }

  if (factory.level !== recipe.level) {
    return {
      ready: false,
      reason: 'permanent-level-mismatch'
    };
  }

  const effect = getOperateFactoryEffect(factory);
  return effect?.level === recipe.level
    ? {
        ready: true,
        reason: 'operated-at-required-level',
        effect
      }
    : {
        ready: false,
        reason: 'operate-effect-missing'
      };
}

factory.level is the permanent level assigned by the first successful PWR_OPERATE_FACTORY use and cannot later be changed. A leveled recipe also requires the matching active operation effect; a missing effect may produce ERR_BUSY. Level-less recipes do not need that effect.

Check cooldown, every component, and output capacity

function buildFactoryPlan(request) {
  const validation =
    validateFactoryRequest(request);

  if (!validation.valid) {
    return {
      ready: false,
      reason: validation.reason
    };
  }

  const factory = getOwnedActiveFactory(
    request.factoryId
  );
  const recipe = getCommodityRecipe(
    request.resourceType
  );

  if (!factory || !recipe) {
    return {
      ready: false,
      reason: factory
        ? 'recipe-invalid'
        : 'factory-unavailable'
    };
  }

  if (factory.cooldown > 0) {
    return {
      ready: false,
      reason: 'factory-cooling-down'
    };
  }

  const levelCheck =
    checkFactoryLevel(factory, recipe);
  if (!levelCheck.ready) return levelCheck;

  const componentsBefore =
    readComponentAmounts(factory, recipe);
  const missing = Object.entries(
    recipe.components
  ).filter(([resourceType, required]) =>
    componentsBefore[resourceType] < required
  );

  if (missing.length > 0) {
    return {
      ready: false,
      reason: 'components-insufficient',
      missing
    };
  }

  if (
    factory.store.getFreeCapacity(
      request.resourceType
    ) < recipe.amount
  ) {
    return {
      ready: false,
      reason: 'output-capacity-insufficient'
    };
  }

  return {
    ready: true,
    reason: 'ready',
    factory,
    recipe,
    componentsBefore,
    effect: levelCheck.effect ?? null
  };
}

Preflight is a current-tick decision. The API return code remains authoritative if another owner changes the Factory before the call.

Reserve one exclusive Factory Store window

Between snapshot and verification, block component hauling, product hauling, and a second produce() call for this Factory. Without that scheduler contract, Store deltas cannot distinguish one production batch from unrelated logistics. Record exclusiveWindow: true only when the caller actually enforces it.

Snapshot the full recipe and submit once

function submitFactoryRequest(request) {
  const plan = buildFactoryPlan(request);

  request.lastCheckedAt = Game.time;
  request.lastStatus = plan.reason;

  if (!plan.ready) {
    return {
      status: 'not-submitted',
      reason: plan.reason,
      result: null
    };
  }

  request.enabled = false;
  request.status = 'submitted';
  request.submittedAt = Game.time;
  request.snapshot = {
    requestId: request.requestId,
    exclusiveWindow:
      request.exclusiveWindow === true,
    factoryId: plan.factory.id,
    resourceType: request.resourceType,
    outputAmount: plan.recipe.amount,
    outputBefore:
      plan.factory.store.getUsedCapacity(
        request.resourceType
      ),
    components: {
      ...plan.recipe.components
    },
    componentsBefore:
      plan.componentsBefore,
    recipeCooldown:
      plan.recipe.cooldown ?? null,
    cooldownBefore: plan.factory.cooldown,
    factoryLevel: plan.factory.level,
    operateEffect: plan.effect
      ? {
          level: plan.effect.level,
          ticksRemaining:
            plan.effect.ticksRemaining
        }
      : null
  };

  const result = plan.factory.produce(
    request.resourceType
  );

  request.result = result;
  request.resultAt = Game.time;
  request.status = result === OK
    ? 'accepted-pending-batch'
    : 'failed-review-required';

  return {
    status: request.status,
    result
  };
}

A non-OK call stays disabled. Review current components, capacity, cooldown, and Power state before authorizing another batch.

Verify one output increase and every component decrease

function verifyFactoryRequest(request) {
  const snapshot = request?.snapshot;

  if (!snapshot || request.result !== OK) {
    return {
      status: 'submission-not-accepted'
    };
  }

  if (snapshot.exclusiveWindow !== true) {
    return { status: 'not-exclusive' };
  }

  const factory = getOwnedActiveFactory(
    snapshot.factoryId
  );
  if (!factory) {
    return { status: 'factory-unavailable' };
  }

  const outputDelta =
    factory.store.getUsedCapacity(
      snapshot.resourceType
    ) - snapshot.outputBefore;
  const componentDeltas = Object.fromEntries(
    Object.keys(snapshot.components).map(
      resourceType => [
        resourceType,
        factory.store.getUsedCapacity(
          resourceType
        )
          - snapshot.componentsBefore[
              resourceType
            ]
      ]
    )
  );

  const componentsExact = Object.entries(
    snapshot.components
  ).every(([resourceType, required]) =>
    componentDeltas[resourceType] === -required
  );
  const exact =
    outputDelta === snapshot.outputAmount
    && componentsExact;

  if (exact) {
    return {
      status: 'verified-exact-batch',
      outputDelta,
      componentDeltas,
      cooldownNow: factory.cooldown
    };
  }

  const unchanged =
    outputDelta === 0
    && Object.values(componentDeltas)
      .every(delta => delta === 0);

  return {
    status: unchanged
      ? 'pending-no-store-delta'
      : 'state-changed-ambiguous',
    outputDelta,
    componentDeltas,
    cooldownNow: factory.cooldown
  };
}

Cooldown greater than zero is supporting evidence, not a unique batch identity. The exact component and output signature is stronger, and it still depends on the exclusive Store window.

Distinguish permanent level, active effect, and settlement

StatusMeaning
permanent-level-mismatchThe Factory was permanently assigned a different level.
operate-effect-missingThe correct permanent level exists, but the active operation effect is absent.
accepted-pending-batchproduce() returned OK; Store evidence is pending.
state-changed-ambiguousHauling or another batch may have altered the Store signature.
verified-exact-batchThe output and every component changed by exactly one recipe batch.

Preserve all documented return codes

Documented results include ERR_NOT_OWNER, ERR_BUSY, ERR_NOT_ENOUGH_RESOURCES, ERR_INVALID_TARGET, ERR_FULL, ERR_INVALID_ARGS, ERR_TIRED, and ERR_RCL_NOT_ENOUGH. ERR_BUSY specifically covers a Factory that is not currently operated when the recipe requires it.

Keep hauling, Power scheduling, and profitability separate

This page owns one reviewed production batch. Component delivery, output evacuation, Power Creep scheduling, commodity chains, market profitability, and multi-Factory allocation require separate policies. Use the market deal guide only after production identity is settled.

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.