Commands and priority
Two independent source slots
Each player gets one AGENT plan slot and one MANUAL plan slot per Tick, and
they are merged per object:
Manual explicit action > Agent explicit action > WAIT
- The Agent plan holds automated actions. Leave an object out and it does
WAIT, unless Manual has something for it. - The Manual plan holds human overrides. Leave an object out and it falls back to the Agent.
- To stop an Agent action, Manual has to send an explicit
WAIT— omitting the object is not enough. - All of one player's Agent clients share a single Agent slot.
- All of one player's browser tabs share a single Manual slot.
A new plan replaces the old one
Each successful POST replaces whatever that source sent before. The server never patches or merges the two.
So if an Agent wants to change one Unit and leave the others doing what they were doing, it has to send those other actions again too.
Static and dynamic validation
Static validation runs before anything is persisted, and checks that:
- the body is one JSON object with no unknown fields;
- the Tick is positive;
- Unit UUID keys are lowercase and hyphenated;
- every acting Unit referenced belongs to the player;
- the action type is allowed for that Unit;
- required fields are present;
- unrelated action fields are absent.
A single problem rejects the whole request atomically, and your previous valid plan is left exactly as it was.
Dynamic conditions are a different matter — they can only be settled later, at resolution:
- the target moved;
- the destination filled up;
- the movement was contested;
- resources ran short;
- a lower UUID won the Beacon;
- the Ranger's line got blocked.
None of these invalidate the POST. They show up in the next state.events.
Ordering and rate limit
Valid requests for the same (player, tick, source) are serialized in the order
they enter the gate, and each stored plan replaces the one before it. There is no
client-supplied version number in the protocol.
Each source slot accepts at most 64 new submissions per Tick, counted after the
idempotency precheck — valid and statically invalid requests both count. Anything
beyond that gets 429 COMMAND_RATE_LIMITED, and your last valid plan stands
untouched.
Idempotency and receipts
Every command request carries an Idempotency-Key.
- Same key, same body: you get the original HTTP response back.
- Same key, different body:
IDEMPOTENCY_CONFLICT. - A replay does not broadcast a second
received.
Once the server has stored a new plan:
- HTTP returns a minimal
202 Acceptedwith receipt metadata. - Every live connection for that player gets the stored plan in
received.plan. - Reconnecting during the same OPEN Tick restores the latest receipt from each source.
Receipts are cleared at the next Tick. This is not a plan-history service.