MARKET · DEAL SETTLEMENT IDENTITY

Execute One Market Deal Without Assuming the Requested Amount Settled

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

Create a standing order insteadAlternative trade pathAvoid same-tick Terminal intent contentionResource lockConnect transaction Energy cost to room reservesResource budget
Verification statusChinese source article: Reviewed in full · Official engine: Checked at 80977824199a596d174d392fd0cf8c458c21fcbd — deal queues up to ten global intents, Terminal sends are processed separately, and settlement may reduce amount for order balance, seller inventory, buyer capacity or buyer Credits · Technical correction: Added immutable operation confirmation, one shared Terminal reservation across deal and send, exact next-tick transaction-time matching, and explicit full-versus-partial settlement states

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official engine
Checked at 80977824199a596d174d392fd0cf8c458c21fcbd — deal queues up to ten global intents, Terminal sends are processed separately, and settlement may reduce amount for order balance, seller inventory, buyer capacity or buyer Credits
Technical correction
Added immutable operation confirmation, one shared Terminal reservation across deal and send, exact next-tick transaction-time matching, and explicit full-versus-partial settlement states
JavaScript syntax
Required by the dedicated editorial simulation on the current branch
Repository integration
Pending — TypeScript, ESLint, build and production smoke checks have not completed on this branch
Screeps Console test
Pending
Live order race, partial fill, Terminal collision and transaction-ID test
Pending
Genuine Console or market screenshots
Pending
Last verified
August 5, 2026

Start with scheduled versus settled

Game.market.deal() returning OK means the global deal intent was accepted. It does not guarantee that the requested amount will settle. During processing, the engine can reduce amount to the remaining order balance, seller inventory, buyer Terminal free capacity, or affordable Credit amount.

This guide buys an ordinary resource from one exact ORDER_SELL order. Selling into a buy order and account-bound resources need different transaction direction and resource checks.

Freeze one reviewed deal revision

function buildDealConfirmation(request) {
  return [
    'BUY_FROM_SELL_ORDER',
    request.requestId,
    request.revision,
    request.orderId,
    request.terminalId,
    request.resourceType,
    request.amount,
    request.maximumPrice,
    request.creditReserve,
    request.energyReserve
  ].join('|');
}

Require a matching confirmation immediately before submission. A change to amount, price ceiling, reserves, order ID or Terminal ID creates a new operation revision rather than silently reusing old approval.

Refresh the exact sell order immediately before reserving

function evaluateReviewedSellOrder(request) {
  if (!request || request.enabled !== true) {
    return { ready: false, status: 'request-disabled' };
  }
  if (
    typeof request.requestId !== 'string'
    || !Number.isInteger(request.revision)
    || request.revision < 1
    || typeof request.orderId !== 'string'
    || typeof request.terminalId !== 'string'
    || typeof request.resourceType !== 'string'
    || !Number.isInteger(request.amount)
    || request.amount <= 0
    || !Number.isFinite(request.maximumPrice)
    || request.maximumPrice <= 0
    || request.confirmation
      !== buildDealConfirmation(request)
  ) {
    return { ready: false, status: 'request-invalid' };
  }

  const order = Game.market.getOrderById(request.orderId);
  if (!order) {
    return { ready: false, status: 'order-unavailable' };
  }
  if (
    order.id !== request.orderId
    || order.type !== ORDER_SELL
    || order.resourceType !== request.resourceType
    || typeof order.roomName !== 'string'
  ) {
    return { ready: false, status: 'order-identity-mismatch' };
  }
  if (order.price > request.maximumPrice) {
    return { ready: false, status: 'price-above-limit' };
  }
  if (order.amount <= 0) {
    return { ready: false, status: 'order-amount-empty' };
  }

  return { ready: true, status: 'order-ready', order };
}

Do not require the current order.amount to equal or exceed the full requested amount if partial settlement is acceptable. Store the request ceiling and let later evidence report the actual amount. Projects that require all-or-nothing behavior should stop when order.amount < request.amount.

Plan current Credit and Energy reserves

function buildDealBudget(request, terminal, order) {
  const requestedAmount = Math.min(
    request.amount,
    order.amount
  );
  const energyEstimate =
    Game.market.calcTransactionCost(
      requestedAmount,
      terminal.room.name,
      order.roomName
    );
  const creditEstimate =
    requestedAmount * order.price;

  if (
    Game.market.credits - creditEstimate
      < request.creditReserve
  ) {
    return { ready: false, status: 'credit-reserve' };
  }
  if (
    terminal.store.getUsedCapacity(RESOURCE_ENERGY)
      - energyEstimate
      < request.energyReserve
  ) {
    return { ready: false, status: 'energy-reserve' };
  }

  return {
    ready: true,
    status: 'budget-ready',
    requestedAmount,
    energyEstimate,
    creditEstimate
  };
}

These are preflight ceilings. Another accepted operation in the same tick can consume the same Credits, Terminal Energy or free capacity unless every producer shares the coordinator below.

Reserve both the deal slot and exact Terminal

function createTerminalMarketDispatcher() {
  let dealSlots = 0;
  const terminalIds = new Set();
  const orderIds = new Set();

  return {
    reserveDeal(terminalId, orderId) {
      if (dealSlots >= 10) {
        return { ready: false, status: 'deal-limit' };
      }
      if (terminalIds.has(terminalId)) {
        return {
          ready: false,
          status: 'terminal-already-reserved'
        };
      }
      if (orderIds.has(orderId)) {
        return {
          ready: false,
          status: 'order-already-reserved'
        };
      }

      dealSlots += 1;
      terminalIds.add(terminalId);
      orderIds.add(orderId);
      return { ready: true, status: 'deal-reserved' };
    },
    releaseTerminal(terminalId) {
      terminalIds.delete(terminalId);
    }
  };
}

The Terminal reservation must be shared with direct terminal.send() producers. The engine stores send and deal through different intent channels, so a deal-only call counter cannot prevent the same Terminal from being scheduled twice.

Submit one frozen operation

function submitReviewedBuyDeal(request, dispatcher) {
  const reviewed = evaluateReviewedSellOrder(request);
  if (!reviewed.ready) return reviewed;

  const terminal = Game.getObjectById(request.terminalId);
  if (
    !terminal
    || terminal.structureType !== STRUCTURE_TERMINAL
    || terminal.my !== true
    || terminal.isActive() !== true
    || terminal.cooldown > 0
  ) {
    return { status: 'terminal-not-ready' };
  }

  const budget = buildDealBudget(
    request,
    terminal,
    reviewed.order
  );
  if (!budget.ready) return budget;

  const reservation = dispatcher.reserveDeal(
    terminal.id,
    reviewed.order.id
  );
  if (!reservation.ready) return reservation;

  const incomingIdsBefore =
    Game.market.incomingTransactions
      .map(transaction => transaction.transactionId)
      .filter(Boolean);

  request.enabled = false;
  const pending = {
    requestId: request.requestId,
    revision: request.revision,
    confirmation: request.confirmation,
    submittedAt: Game.time,
    orderId: reviewed.order.id,
    orderType: reviewed.order.type,
    orderRoom: reviewed.order.roomName,
    terminalId: terminal.id,
    targetRoom: terminal.room.name,
    resourceType: reviewed.order.resourceType,
    requestedAmount: budget.requestedAmount,
    price: reviewed.order.price,
    incomingIdsBefore
  };

  const result = Game.market.deal(
    pending.orderId,
    pending.requestedAmount,
    pending.targetRoom
  );

  request.lastResult = result;
  request.lastAttemptAt = Game.time;
  request.status = result === OK
    ? 'accepted-pending-settlement'
    : 'deal-rejected';

  if (result === OK) {
    Memory.pendingMarketDeals ??= {};
    Memory.pendingMarketDeals[request.requestId] = pending;
  } else {
    dispatcher.releaseTerminal(terminal.id);
  }

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

The local deal slot remains consumed after a real call attempt as a conservative anti-loop policy. A rejected request stays disabled and requires a newly reviewed revision.

Treat the requested amount as a ceiling

During tick processing, actual amount may be reduced by current order balance, seller inventory, receiving Terminal capacity or available Credits. A transaction with 0 < amount < requestedAmount is not necessarily evidence of a wrong order. It is a partial settlement that must be reported explicitly.

Do not infer actual amount from the Terminal Store delta alone. Creeps, Labs, Factories, sends and other market operations can change the same Store in the same tick.

Verify the exact new transaction on the next tick

function verifyBuyDealSettlement(pending) {
  if (Game.time < pending.submittedAt + 1) {
    return { status: 'waiting-for-next-tick' };
  }
  if (Game.time > pending.submittedAt + 1) {
    return { status: 'verification-window-missed' };
  }

  const before = new Set(pending.incomingIdsBefore);
  const candidates = Game.market.incomingTransactions
    .filter(transaction =>
      transaction.transactionId
      && !before.has(transaction.transactionId)
      && transaction.time === pending.submittedAt
      && transaction.order?.id === pending.orderId
      && transaction.order?.type === pending.orderType
      && Math.abs(
        transaction.order.price - pending.price
      ) < 0.0005
      && transaction.from === pending.orderRoom
      && transaction.to === pending.targetRoom
      && transaction.resourceType
        === pending.resourceType
      && transaction.amount > 0
      && transaction.amount
        <= pending.requestedAmount
    );

  if (candidates.length === 0) {
    return { status: 'accepted-no-transaction-observed' };
  }
  if (candidates.length > 1) {
    return {
      status: 'transaction-identity-ambiguous',
      candidateIds: candidates.map(
        transaction => transaction.transactionId
      )
    };
  }

  const transaction = candidates[0];
  return {
    status: transaction.amount
      === pending.requestedAmount
      ? 'full-deal-settlement-observed'
      : 'partial-deal-settlement-observed',
    transactionId: transaction.transactionId,
    requestedAmount: pending.requestedAmount,
    actualAmount: transaction.amount
  };
}

The exact transaction ID, processing tick, order identity, price, direction, resource and bounded amount form the settlement evidence. Credits and Store changes are supporting diagnostics, not unique identity.

Preserve no-record and ambiguity states

StatusMeaning
terminal-already-reservedA deal or send already owns this Terminal for the tick.
accepted-pending-settlementThe deal intent was accepted; actual settlement is unknown.
accepted-no-transaction-observedNo exact record appeared in the next snapshot.
transaction-identity-ambiguousSeveral exact new records matched.
partial-deal-settlement-observedOne exact record settled less than requested.
full-deal-settlement-observedOne exact record settled the complete request ceiling.

Production integration boundary

Instantiate one dispatcher per tick and pass it to all market and logistics modules. Reserve the Terminal before the irreversible call, retain one pending record per request revision, verify it exactly on the next tick, and never retry an accepted-but-unverified operation automatically.

Official documentation and engine source

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.