Skip to content

Command receipts

Use receipts when a script must distinguish queue admission from later native submission, rejection, or cancellation. Ordinary commands return one boolean and do not require receipt cleanup. A receipt is transport evidence; it does not establish that the game carried out the requested action.

Tracked requests occupy a bounded pool of 256 records until their terminal receipts are released. A script that does not need this evidence should use the ordinary command; ignoring tracked receipts can eventually reject new tracked requests. Some tracked commands also validate whole groups more strictly than their ordinary counterparts. Read the command-specific rules below before switching an existing script.

Start with Commands for the usual command names and arguments.

Tracked commands

UnitsMoveTracked(units, position, append?), UnitsTargetObjectTracked(units, target, append?), SetUnitStanceSeekShelterTracked(units) and SetUnitStanceUngarrisonTracked(buildings, guardedOccupants) return accepted, receipt. Acceptance is queue admission. A rejected admission includes state="rejected" and reason without an ID. Accepted receipts use an opaque decimal string id; never convert it to a Lua number.

UnitsStopTracked(units) uses the same group stop order as UnitsStop and returns accepted, receipt. It exposes later actor/layout/allocation rejection, cancellation before dispatch, and native transport evidence. Submitted means the transport returned, not proof that each object stopped in the game.

SetUnitStanceFollowTracked(units, target) and SetUnitStanceGuardTracked(units, target) also return accepted, receipt. They reject invalid actor groups and require a live target both at admission and before submission. A disappeared/dead target is reported as invalid-target. They retain the same native follow/guard behavior as the boolean variants.

SetUnitStancePatrolTracked(units, position) and SetUnitStanceAttackMoveTracked(units, position) submit the existing immediate, single-waypoint orders with receipts. They require finite coordinates and valid actor groups. They do not append an order or create a waypoint sequence. Patrol and attack-move boolean variants also reject non-finite coordinates.

The following orders also support accepted, receipt results:

Call Argument contract
SetUnitCombatStanceTracked(units, stance) Integer UnitCombatStance value, 0..3.
SetUnitFormationTracked(units, formationId) Non-negative signed 32-bit integer ID.
SetUnitStanceAutoScoutTracked(units) Explicit nonempty actor group.
GatherInsideTracked(buildings[, state = -1]) State -1, 0, or 1.
DeleteUnitTracked(objects) Explicit nonempty actor group. Deletes live game objects.

These tracked calls reject invalid group members rather than filtering the group. They preserve the native commands and provide transport evidence, not proof of the requested effect. Formation ID range validation establishes representability, not whether the current game supports that formation. Use deletion only from an explicit script action/option; cancelling a receipt cannot restore deleted objects.

SetGatherPointTracked(buildings, target), RingTownBellTracked(townCenter[, active = true]), SendBackToWorkTracked(townCenter), and SendAllBackToWorkTracked(townCenter) return accepted, receipt. Rally targets accept an object or position, matching SetGatherPoint. Object targets are re-read before submission; disappearance or a changed definition produces rally-target-changed. Town-center commands retain actor/session validation. Submission does not prove the requested effect.

DropResourcesTracked(units) uses native chunks of up to 60 actors. All actors must be valid at admission and are revalidated before execution. If an earlier packet was submitted and a later packet fails, the receipt is indeterminate with reason partial-submission, and no further packets are attempted. It has no elapsedMs for a complete submission. Do not retry the entire group: some actors may already have received the order. submitted means all packet transports returned, not that every actor dropped its resources.

Production commands also return accepted, receipt:

Call Request
TrainUnitTracked(buildings, buildingType, unitType[, count = 1]) Queue unit production.
ResearchTechnologyTracked(buildings, technology) Queue research.
UnitsBuildStructureTracked(builders, buildingType, position[, options]) Place a foundation.

These use the same native orders as their boolean counterparts, but reject an invalid actor instead of filtering the group. Tracked type/technology IDs must be integers in 0..32767; train counts must be integers in 1..32767. Invalid values return a rejected receipt rather than wrapping the packet field. invalid-count, invalid-type, and invalid-technology identify those failures. Native allocation failure is packet-allocation-failed. A missing research predicate, failed query, or unavailable research layout produces research-query-or-layout-unavailable at execution.

Cancelling a queued receipt prevents this library from sending the request. It does not cancel an item already in the game's production queue. Submission does not prove affordability, production eligibility, legal placement, or game acceptance. Observe live queues, research state, or foundations separately.

For example, keep the receipt ID when you submit production once:

local accepted, receipt = TrainUnitTracked({ producer }, building_type, unit_type, 1)
if not accepted then
    LogWarn("Training request rejected", receipt.reason)
else
    pending_train_id = receipt.id
end

Here producer is a freshly queried owned building and the type IDs identify the producer and requested unit. In a later update, read CommandReceipt(pending_train_id). Keep waiting while queued or executing; nil means missing evidence. On a terminal state, record the result and release its metadata with ReleaseCommandReceipt. A submitted or indeterminate order must be reconciled against live game observations before another production request; neither status is permission to retry automatically. Native receipt release is metadata cleanup, not cancellation of production.

  • CommandReceipt(id) returns current evidence, or nil for unknown identity.
  • CancelCommandReceipt(id) cancels only queued work and returns actual current evidence. Execution may win the race; inspect the returned state.
  • ReleaseCommandReceipt(id) releases terminal native metadata, returning false for queued/executing or unknown records. Keep a cleanup owner until it succeeds.

States are queued, executing, submitted, rejected, cancelled and indeterminate. Submitted means the void native transport returned, not that the game accepted the order. elapsedMs measures time since that return using a native monotonic clock. sessionEnded separately indicates loss of the originating session; it does not rewrite the historical transport outcome. Missing receipt evidence is unknown, never proof of rejection or completion.

For recognized command functions, GetApiCapability(callable).supportsObservation reports whether receipt observation is implemented: currently true for SetDiplomacyTracked and UnitsTargetObjectTracked, false for other recognized commands (including boolean SetDiplomacy). The field is absent when the callable is not classified as a command. True does not guarantee that the native observation query is currently available; inspect the receipt's observation state. Aliases of the same native function report the same support.

SetDiplomacyTracked attaches observation = {kind="diplomacy", state=...} after its execution callback runs. Each receipt read queries the original source and target relationship using the native XS query. States are matched (raw relationship equals the requested state), not-matched, unavailable, not-submitted, and session-ended. Object-target observations are described below; other commands currently omit this field. Queued receipts can also omit it. Only submitted commands in their original active session read live state; a session change during the read discards it. A match is current state evidence, not proof of command causality, a transition, or completion. It may already have been true before submission and can change on a later read. Observation does not change transport state or trigger retries.

UnitsTargetObjectTracked issues the same default object action as UnitsTargetObject. It rejects invalid actors as a whole group and re-resolves the target before submission. A disappeared target produces invalid-target. Dead resource targets remain valid for native gathering behavior. Submission does not establish which contextual action the game selected or its completion.

After execution, its receipt also carries observation.kind="object-target". matched means every original actor's native current target ID equals the requested target ID. not-matched includes actors with no current target. Missing/dead actors, changed ownership, missing target, unresolved query or read fault produce unavailable. Reads are sequential live queries, not an atomic group snapshot. For appended orders, this observes the current target, not the queued future order. It cannot establish queue insertion, the contextual action, causality or action completion. A later human order can produce the same match. The observation never retries a command or changes its transport state.

The queue retains at most 256 tracked records, including unreleased terminals; it rejects capacity exhaustion instead of evicting active/unconsumed evidence. Scout retreat, villager anchor-return movement, reactive seek-shelter and shelter release use receipts in the managed runner. Release owns the actual container and all known occupants across shelter requests, supplying those occupants as native guards. Whole-group positive departure is required for effect completion. Tracked commands retain uncertain command ownership until positive live evidence or authoritative session loss permits reconciliation. Other managed command domains remain boolean-only.

Tracked admission rejects an invalid member or an oversized packet instead of filtering/truncating it. After deduplication, seek-shelter supports at most 16,382 actors and ungarrison supports at most 16,378 buildings. Ungarrison still affects the entire building. Optional guardedOccupants adds exact participant checks: each supplied guard must share the building group's known owner, be alive with finite positive health, and be inside one of the supplied buildings at admission. Before sending, guards are checked again for ownership, liveness, observed human orders, and the same containing building. A failed guard rejects the whole queued command. Omitted or empty guards retain the building-only contract; unlisted occupants are not protected by this argument. The command does not choose a shelter destination or prove capacity, reachability, game acceptance or occupant safety.