Start with the set-intent boundary
StructureTerminal.send() writes one send intent for the exact Terminal ID. The official runtime uses a set operation, so another send() on the same Terminal later in the same JavaScript tick replaces the earlier payload. Both callers can see OK, but only the final send intent remains.
Market deals use a separate global intent channel and may reserve the same Terminal unless your code coordinates them. The engine processes direct sends before terminal market deals. Use one shared Terminal dispatcher rather than independent logistics and market schedulers.
Freeze one transfer revision
function buildTerminalSendConfirmation(request) {
return [
'TERMINAL_SEND',
request.requestId,
request.revision,
request.terminalId,
request.resourceType,
request.amount,
request.destination,
request.description ?? '',
request.energyReserve
].join('|');
}
Validate the request ID, positive revision, exact Terminal ID, resource, integer amount at least TERMINAL_MIN_SEND, different destination room, description length and reserve. Any payload edit requires a new confirmation.
Normalize the description that appears in the transaction ledger
function normalizeSendDescription(value) {
if (value == null || value === '') {
return {
apiDescription: undefined,
ledgerDescription: ''
};
}
const text = String(value);
if (text.length > 100) return null;
return {
apiDescription: text,
ledgerDescription: text.replace(/</g, '<')
};
}
function readLedgerDescription(transaction) {
return transaction.description ?? '';
}
Tick processing omits an empty description and escapes each literal less-than sign in a non-empty description. Comparing the raw request string directly with transaction.description incorrectly rejects both cases. The ledger normalization belongs in the frozen pending record.
Plan the source resource and Energy ceiling
function buildTerminalSendBudget(
request,
terminal
) {
const estimatedCost =
Game.market.calcTransactionCost(
request.amount,
terminal.room.name,
request.destination
);
const energy = terminal.store.getUsedCapacity(
RESOURCE_ENERGY
);
const resource = terminal.store.getUsedCapacity(
request.resourceType
);
const requiredEnergy =
request.resourceType === RESOURCE_ENERGY
? request.amount + estimatedCost
: estimatedCost;
if (
request.resourceType !== RESOURCE_ENERGY
&& resource < request.amount
) {
return { ready: false, status: 'resource-insufficient' };
}
if (
energy - requiredEnergy
< request.energyReserve
) {
return { ready: false, status: 'energy-reserve' };
}
return {
ready: true,
status: 'budget-ready',
estimatedCost,
requiredEnergy,
energyBefore: energy,
resourceBefore: resource
};
}
PWR_OPERATE_TERMINAL can reduce actual transfer cost and cooldown. The ordinary calculation is a conservative preflight estimate, not exact post-processing evidence.
Reserve the exact Terminal across send and deal
function createTerminalOperationDispatcher() {
const terminalIds = new Set();
return {
reserve(terminalId, operationId) {
if (terminalIds.has(terminalId)) {
return {
ready: false,
status: 'terminal-already-reserved'
};
}
terminalIds.add(terminalId);
return {
ready: true,
status: 'terminal-reserved',
operationId
};
},
release(terminalId) {
terminalIds.delete(terminalId);
}
};
}
Every direct send and every ordinary-resource market deal must use the same dispatcher instance. A reservation hidden inside only the send module cannot stop a deal module from using the same Terminal later in the tick.
Submit one frozen transfer
function submitTerminalTransfer(request, dispatcher) {
const description = normalizeSendDescription(
request?.description
);
if (!description) {
return { status: 'description-invalid' };
}
if (
!request
|| request.enabled !== true
|| typeof request.requestId !== 'string'
|| request.requestId.length === 0
|| !Number.isInteger(request.revision)
|| request.revision < 1
|| typeof request.terminalId !== 'string'
|| request.terminalId.length === 0
|| typeof request.resourceType !== 'string'
|| request.resourceType.length === 0
|| !Number.isInteger(request.amount)
|| request.amount < TERMINAL_MIN_SEND
|| typeof request.destination !== 'string'
|| request.destination.length === 0
|| !Number.isFinite(request.energyReserve)
|| request.energyReserve < 0
|| request.confirmation
!== buildTerminalSendConfirmation(request)
) {
return { status: 'request-invalid' };
}
const terminal = Game.getObjectById(request.terminalId);
if (
!terminal
|| terminal.structureType !== STRUCTURE_TERMINAL
|| terminal.my !== true
|| terminal.isActive() !== true
|| terminal.cooldown > 0
|| terminal.room.name === request.destination
) {
return { status: 'terminal-not-ready' };
}
const budget = buildTerminalSendBudget(request, terminal);
if (!budget.ready) return budget;
const reservation = dispatcher.reserve(
terminal.id,
request.requestId + ':' + request.revision
);
if (!reservation.ready) return reservation;
const outgoingIdsBefore =
Game.market.outgoingTransactions
.map(transaction => transaction.transactionId)
.filter(Boolean);
request.enabled = false;
const pending = {
requestId: request.requestId,
revision: request.revision,
confirmation: request.confirmation,
submittedAt: Game.time,
terminalId: terminal.id,
sourceRoom: terminal.room.name,
destination: request.destination,
resourceType: request.resourceType,
requestedAmount: request.amount,
apiDescription: description.apiDescription,
ledgerDescription: description.ledgerDescription,
estimatedCost: budget.estimatedCost,
outgoingIdsBefore
};
const result = terminal.send(
pending.resourceType,
pending.requestedAmount,
pending.destination,
pending.apiDescription
);
request.lastResult = result;
request.lastAttemptAt = Game.time;
request.status = result === OK
? 'accepted-pending-outgoing-transaction'
: 'send-rejected';
if (result === OK) {
Memory.pendingTerminalSends ??= {};
Memory.pendingTerminalSends[request.requestId] = pending;
} else {
dispatcher.release(terminal.id);
}
return { status: request.status, result };
}
Disabling before the call prevents automatic repetition. The pending record freezes both the API description and the normalized ledger form.
Treat destination capacity as an execution-time limit
The runtime cannot prove that a destination Terminal exists or has enough free capacity. During processing, the engine can reduce the sent amount to the receiving Terminal's free space. Therefore an exact new transaction with a smaller positive amount is a partial transfer, not automatically a mismatch.
If no valid destination Terminal exists, the accepted intent can produce no outgoing transaction. The next-tick verifier must report that absence rather than remain pending forever.
Verify the exact outgoing record on the next tick
function verifyTerminalTransfer(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.outgoingIdsBefore);
const candidates = Game.market.outgoingTransactions
.filter(transaction =>
transaction.transactionId
&& !before.has(transaction.transactionId)
&& transaction.time === pending.submittedAt
&& transaction.from === pending.sourceRoom
&& transaction.to === pending.destination
&& transaction.resourceType
=== pending.resourceType
&& transaction.amount > 0
&& transaction.amount
<= pending.requestedAmount
&& readLedgerDescription(transaction)
=== pending.ledgerDescription
&& !transaction.order
);
if (candidates.length === 0) {
return {
status: 'accepted-no-outgoing-transaction-observed'
};
}
if (candidates.length > 1) {
return {
status: 'outgoing-transaction-ambiguous',
candidateIds: candidates.map(
transaction => transaction.transactionId
)
};
}
const transaction = candidates[0];
return {
status: transaction.amount
=== pending.requestedAmount
? 'full-transfer-observed'
: 'partial-transfer-observed',
transactionId: transaction.transactionId,
requestedAmount: pending.requestedAmount,
actualAmount: transaction.amount
};
}
Requiring !transaction.order distinguishes a direct send from a market transaction that happens to share the same rooms, resource and amount.
Preserve overwritten and absent states
| Status | Meaning |
|---|---|
terminal-already-reserved | Another send or deal owns this Terminal this tick. |
accepted-pending-outgoing-transaction | The send intent was accepted but not processed yet. |
accepted-no-outgoing-transaction-observed | No exact direct-send record appeared next tick. |
outgoing-transaction-ambiguous | Several exact new records matched. |
partial-transfer-observed | One exact record sent a destination-limited amount. |
full-transfer-observed | One exact record sent the complete request. |
Sender evidence is not receiver workflow completion
An outgoing transaction proves server-side transfer settlement from the sender account. It does not prove that destination haulers moved the resource, Labs or a Factory consumed it, or another player's automation acknowledged receipt. Those are separate systems and may require destination-side evidence.
Production integration boundary
Use one dispatcher per tick for direct sends and terminal-backed deals. Save one immutable pending operation per request revision, verify it exactly on the next tick, and never allow a later request to overwrite an unresolved operation record for the same request ID.