MARKET · ORDER CREATION IDENTITY

Create One Market Order Without Misattributing a Later Order

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

Use an immediate deal instead of maintaining an orderAlternative trade pathPrepare the Terminal side of resource movementLogistics prerequisiteBudget the Energy cost against room reservesResource budget
Verification statusChinese source article: Reviewed in full · Official engine: Checked at 80977824199a596d174d392fd0cf8c458c21fcbd — ordinary-resource creation requires an owned Terminal in the room, queues a global createOrder intent, rounds the 5% fee upward in milli-Credit storage, and creates an initially inactive order with its created tick · Technical correction: Removed the unsupported active-Terminal requirement; added immutable request confirmation, shared fee reservation, one attributable creation slot and a bounded next-tick ID-difference verifier

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official engine
Checked at 80977824199a596d174d392fd0cf8c458c21fcbd — ordinary-resource creation requires an owned Terminal in the room, queues a global createOrder intent, rounds the 5% fee upward in milli-Credit storage, and creates an initially inactive order with its created tick
Technical correction
Removed the unsupported active-Terminal requirement; added immutable request confirmation, shared fee reservation, one attributable creation slot and a bounded next-tick ID-difference verifier
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 fee, order-cap, simultaneous-create and next-tick order-ID test
Pending
Genuine Console or market screenshots
Pending
Last verified
August 5, 2026

Start with the creation boundary

Game.market.createOrder() returns a status code, not the new order ID. In the official engine, an accepted ordinary-resource call queues a global intent. Tick processing later charges the rounded fee and inserts an order whose initial active value is false, amount is zero, and created is the submission tick.

Therefore three facts must remain separate: the reviewed request, the accepted API call, and the exact order object observed later. A matching order found several ticks afterward is not enough; another module or Console command may have created it.

Freeze one reviewed request revision

function buildCreateOrderConfirmation(request) {
  return [
    'CREATE_MARKET_ORDER',
    request.requestId,
    request.revision,
    request.type,
    request.resourceType,
    request.roomName,
    request.price,
    request.totalAmount,
    request.creditReserve
  ].join('|');
}
Memory.marketCreateRequests ??= {};
Memory.marketCreateRequests['sell-U-001'] = {
  requestId: 'sell-U-001',
  revision: 1,
  enabled: true,
  type: ORDER_SELL,
  resourceType: RESOURCE_UTRIUM,
  roomName: 'W1N1',
  price: 1,
  totalAmount: 10000,
  creditReserve: 1000000,
  confirmation:
    'CREATE_MARKET_ORDER|sell-U-001|1|sell|U|' +
    'W1N1|1|10000|1000000'
};

The values are examples, not trading recommendations. Any edit to type, resource, room, price, amount, reserve or revision invalidates the old confirmation. Do not approve a request ID and then let another module mutate its payload.

For ordinary resources, verify Terminal ownership

function evaluateCreateOrderRequest(request) {
  if (!request || request.enabled !== true) {
    return { ready: false, status: 'request-disabled' };
  }

  if (
    typeof request.requestId !== 'string'
    || request.requestId.length === 0
    || !Number.isInteger(request.revision)
    || request.revision < 1
    || (request.type !== ORDER_BUY
      && request.type !== ORDER_SELL)
    || typeof request.resourceType !== 'string'
    || typeof request.roomName !== 'string'
    || !Number.isFinite(request.price)
    || request.price <= 0
    || !Number.isInteger(request.totalAmount)
    || request.totalAmount <= 0
    || !Number.isFinite(request.creditReserve)
    || request.creditReserve < 0
  ) {
    return { ready: false, status: 'request-invalid' };
  }

  if (
    request.confirmation
      !== buildCreateOrderConfirmation(request)
  ) {
    return {
      ready: false,
      status: 'confirmation-mismatch'
    };
  }

  const room = Game.rooms[request.roomName];
  const terminal = room?.terminal ?? null;

  if (!terminal || terminal.my !== true) {
    return {
      ready: false,
      status: 'owned-terminal-not-observed'
    };
  }

  return {
    ready: true,
    status: 'creation-ready',
    terminalId: terminal.id
  };
}

Order creation is not a Terminal action. The engine checks that an owned Terminal exists in the requested room; it does not require that Terminal to have zero cooldown or pass isActive(). Adding those requirements can incorrectly block a valid creation request.

Reserve the fee at engine storage precision

function calculateCreateOrderFeeCeiling(
  price,
  totalAmount
) {
  const raw = price * totalAmount * 0.05;
  return Math.ceil(raw * 1000) / 1000;
}

The public formula is 5% of price multiplied by total amount. The engine stores Credits in thousandths and rounds the internal fee upward. Reserving the milli-Credit ceiling prevents a fractional estimate from leaving the account just below its required reserve.

Use one account-level creation coordinator

function createMarketCreationCoordinator() {
  let creationReserved = false;
  let creditsReserved = 0;

  return {
    reserve(input) {
      if (creationReserved) {
        return {
          ready: false,
          status: 'creation-slot-reserved'
        };
      }

      if (
        input.credits
          - creditsReserved
          - input.fee
          < input.creditReserve
      ) {
        return {
          ready: false,
          status: 'credit-reserve'
        };
      }

      creationReserved = true;
      creditsReserved += input.fee;
      return { ready: true, status: 'reserved' };
    }
  };
}

The API can accept more than one creation intent in a tick, but one-at-a-time is a deliberate attribution policy. Without it, every accepted call sees the same old order-ID set, and several new matching orders can appear together. The same coordinator must also account for other same-tick market fees in production.

Disable first, preserve old IDs, then call once

function submitCreateOrder(request, coordinator) {
  const decision = evaluateCreateOrderRequest(request);
  if (!decision.ready) return decision;

  const fee = calculateCreateOrderFeeCeiling(
    request.price,
    request.totalAmount
  );
  const reservation = coordinator.reserve({
    credits: Game.market.credits,
    fee,
    creditReserve: request.creditReserve
  });
  if (!reservation.ready) return reservation;

  const orderIdsBefore = Object.keys(Game.market.orders);

  request.enabled = false;
  request.lastAttemptAt = Game.time;

  const pending = {
    requestId: request.requestId,
    revision: request.revision,
    confirmation: request.confirmation,
    submittedAt: Game.time,
    type: request.type,
    resourceType: request.resourceType,
    roomName: request.roomName,
    price: request.price,
    totalAmount: request.totalAmount,
    fee,
    orderIdsBefore
  };

  const result = Game.market.createOrder({
    type: pending.type,
    resourceType: pending.resourceType,
    roomName: pending.roomName,
    price: pending.price,
    totalAmount: pending.totalAmount
  });

  request.lastResult = result;
  request.status = result === OK
    ? 'accepted-pending-order-id'
    : 'creation-rejected';

  if (result === OK) {
    Memory.pendingMarketCreations ??= {};
    Memory.pendingMarketCreations[request.requestId] =
      pending;
  }

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

Disabling before the call prevents automatic retry. A rejection requires a new reviewed revision. The pending record contains only frozen values; later edits to the request cannot change what the verifier expects.

Verify the ID difference in the next account snapshot

function verifyCreatedOrder(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.orderIdsBefore);
  const candidates = Object.values(Game.market.orders)
    .filter(order =>
      !before.has(order.id)
      && order.type === pending.type
      && order.resourceType === pending.resourceType
      && order.roomName === pending.roomName
      && Math.abs(order.price - pending.price)
        < 0.0005
      && order.totalAmount === pending.totalAmount
      && order.created === pending.submittedAt
    );

  if (candidates.length === 0) {
    return { status: 'accepted-order-not-observed' };
  }
  if (candidates.length > 1) {
    return {
      status: 'new-order-identity-ambiguous',
      candidateIds: candidates.map(order => order.id)
    };
  }

  const order = candidates[0];
  return {
    status: 'created-order-observed',
    orderId: order.id,
    active: order.active,
    amount: order.amount,
    remainingAmount: order.remainingAmount
  };
}

The created tick and old-ID difference exclude later lookalikes. The small price tolerance reflects the public milli-Credit representation; it is not permission to accept a materially different price.

Preserve ambiguous and missed evidence

StatusMeaning
creation-slot-reservedAnother attributable creation already owns this tick.
accepted-pending-order-idThe call returned OK; no new ID is proven yet.
accepted-order-not-observedThe next snapshot did not contain an exact new order.
new-order-identity-ambiguousSeveral exact new IDs appeared; do not select one arbitrarily.
verification-window-missedThe bounded attribution window was skipped.
created-order-observedOne exact new ID and created tick matched.

Creation is not activation or a completed trade

The inserted order starts inactive in the engine. Later resource, Credit and Terminal conditions determine activation and executable amount. Observe those fields separately. An order ID proves creation, not activation, matching, profit or a future fill.

Production integration boundary

Create the coordinator once per game tick and route every create-order producer through it. Include change-price and extend-order fees in the same Credit reservation ledger. Persist accepted operations by request ID and revision, verify them exactly once on the next tick, and archive ambiguous or missed evidence for human review.

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.