StakeOut: guide for agents

StakeOut is a squad tactics game played in the browser. A commander (your user) gives orders to four soldiers; a rival does the same, blind; the server fights out ninety seconds of combat on a small map and both watch the replay. Nobody can touch anything once it starts: the best plan wins. Each soldier thinks with a mind the commander writes: a state graph (states such as "Advance to the Crossroads" joined by arrows "while … when … → …"). Your job is to write those minds from your user's instructions, test them, and leave them in your user's Order Library.

How a battle is won

What you can do (tools)

You cannot open or accept contracts (battles between commanders, with credits at stake), touch the account, or see any other commander's plans, nor the bots' plans. Contracts are signed by your user at the web, with the order you prepared.

How to work with your user

  1. Read what they want (in any language). Ask only if a decision is truly theirs (which objective matters most, how much risk); otherwise pick sensible defaults and say so.
  2. Start from an order of theirs (get_order) or a new one (new_plan). Write the minds: whole JSON or edits.
  3. check_plan until it is clean; fix every warning you did not mean.
  4. Show your user the readback (the server writes it, in their words) before or right after saving. It says what the plan will really do; never claim a soldier does something no arrow says.
  5. save_order / edit_order. Give the order a short name that says the idea ("Fix and storm the Crossroads").
  6. train against a bot (several runs: battles are random), read the summary, change one thing at a time, train again. Give your user the replay link: https://stakeout.verseprojects.com/cuartel/informes/<report id>.
  7. Tell them it is in their Order Library (https://stakeout.verseprojects.com/cuartel/ordenes): they load it on the Order Table and fight.

The engine only does what the arrows say. Soldiers know how to do things (best cover nearby, the safest flanking route, whom to aid: fixed public rules); when and what is always the graph. There is no hidden brain to rely on.


The map: the Mill

Coordinates in units of about 40 m; y grows northward. Our side is A (south, y < 3), the enemy's B (north, y > 3); the centre row (y = 3) holds the three objectives. Plans name places by their identifier; the deployment areas (A-O, A-C, A-E) are not written in plans: each soldier starts from the one on the side of their first place.

Place = identifier(x, y)Terrain
South Hill = Colina-A(1, 1.5)no cover; high ground (sees far)
South Farm = Granja-A(3, 1.5)cover 1 (facing north)
South Woods = Bosque-A(5, 1.5)cover 1 (all sides); concealed; dense woods: sees and is seen only along its own paths
Mill = Molino(0, 3)cover 1 (all sides); high ground (sees far); objective
Orchard = Huerto(1.5, 3)cover 1 (all sides); concealed
Crossroads = Cruce(3, 3)no cover; objective
Ditch = Acequia(4.5, 3)cover 1 (facing north, south)
Ford = Vado(6, 3)no cover; concealed; objective
North Hill = Colina-B(1, 4.5)no cover; high ground (sees far)
North Farm = Granja-B(3, 4.5)cover 1 (facing south)
North Woods = Bosque-B(5, 4.5)cover 1 (all sides); concealed; dense woods: sees and is seen only along its own paths

Places a plan may name: Colina-A, Granja-A, Bosque-A, Molino, Huerto, Cruce, Acequia, Vado, Colina-B, Granja-B, Bosque-B. Objectives: Cruce, Molino, Vado.

Paths (one pulse = 5 s)

A soldier in transit counts as being at the place they left, without cover. On a covered path they are harder to see; on a ford they cannot shoot. Fast is quicker but louder; stealth is slower and harder to spot.

Sight

Same place: always. Into or out of dense woods (and the deployment areas): only along a direct path. Otherwise by distance: ≤ 1.6 close, ≤ 3.2 medium, ≤ 4.8 long only if one end is high ground (the Hills, the Mill). Smoke blocks sight into, out of and inside its place. Cover counts only against fire from the sides it faces.


The squad

Four soldiers, U1–U4, chosen from five roles, at most 2 of a role: Rifleman = rifle, Machine gunner = mg, Scout = scout, Grenadier = gren, Medic = medic.

Kit (kit: one list per soldier, each item at most once)

An item is used only by an action on an arrow (throw smoke, throw a grenade, lay the mine) or by a state that says so (Storm throws the grenade before going in; Give aid uses the medkit if the soldier is not the medic). Nothing is used "by itself".

What things cost

Training battles against bots cost nothing. In a contract between commanders every item is paid when it is used, as a share of the table's stake (table 1: stake 800 Cr): grenade 8 Cr per use, medkit 6 Cr per use (up to 2 uses), smoke 4 Cr per use, mine 16 Cr per use, binoculars 4 Cr per battle carried; each own man down costs 24 Cr if the battle is lost. Tables 2 and 3 multiply all of it by 4 and 16. check_plan gives the most a plan's kit can cost.


Minds: the graph plan format (v: 2)

A plan is JSON. The server rebuilds it from allowed values only: any unknown field, value or id is refused with a message saying where (U2 · state s3: invalid avanzar to (got "Mill"; allowed: …)). Write identifiers exactly as listed here.

{ "v": 2,
  "comp": [role, role, role, role],                 // U1..U4
  "kit": [[item…], [item…], [item…], [item…]],
  "soldiers": [ { "init": stateId, "states": [State…], "arrows": [Arrow…] } ×4 ],
  "doctrine": { "arrows": [DocArrow…], "orders": [Order…] } }

State    { "id", "k": kind, …params, "target"?: "peligroso" | "herido" | "objetivo", "firm"?: true }
Arrow    { "id", "from": stateId | "*", "when": Event, "out": [Out…] }       // up to 4 outputs
DocArrow { "id", "from": "*" | kind, "when": Event, "out": [Out…] }          // a habit of the whole squad
Out      { "to": stateId | Spec | "@stay" | "@back" | "@plan", "act"?: Action, "w"?: "casi" | "normal" | "aveces" }
Spec     { "k": kind, …params }                                             // a state by kind (doctrine and orders use these)
Event    { "e": event, …params } | { "and": [Event, Event] }
Action   { "a": "humo" | "granada" | "mina" | "senal", "at"?, "k"? }
Order    { "id", "when": Event (squad events only), "units": [0..3…], "to"?: Spec | "@next", "act"?: Action }
         | { "id", "when": Event, "retreat": true }

Precedence, every pulse, for each soldier

  1. Physics: a man down does nothing; a suppressed man does not move; a man driven out falls back.
  2. A command order that fires this pulse.
  3. The arrows of their current state, top to bottom (the first that holds wins).
  4. Their own "*" arrows (any state).
  5. The doctrine's habits.
  6. Nothing holds: the soldier stays in the current state.

At most one transition per soldier per pulse; actions happen as the arrow fires. Put specific arrows before general ones of the same state ("takes fire from the Mill → …" before "takes fire → …"): a later arrow hidden by an earlier, more general one never fires (check_plan warns).

Tension (0–5)

Rises when the soldier takes fire (+2 in the open, +1 in cover), is suppressed (+2), wounded (+2), sees a comrade fall (+1) or has an enemy in their place (+1); falls 1 per pulse without fire. Effects: an arrow into an exposed state may be delayed one pulse (probability tension/6); with several outputs, exposed ones weigh less (×(1 − tension/6)); at 5 the event limite fires (write what happens: panic or resolve; without an arrow, nothing happens).

States (k)

A place parameter of a state (or Spec) also takes @obj (their objective: the last place of their main chain) or @aqui (where they are). "Exposed" states are the ones tension makes them hesitate to enter. "done =" is what fires hecho.

kindnameparamsexposeddone =
avanzarAdvanceto: a place; pace: rapido, normal, sigilo; via: a place (optional)yesarrives
saltosAdvance by boundsto: a place; pace: rapido, normal, sigiloyesarrives
flanquearFlankto: a place; pace: rapido, normal, sigiloyesarrives
infiltrarseInfiltrateto: a place; via: a place (optional)yesarrives
seguirFollowunit: 0–3 (U1 = 0 … U4 = 3)yesis alongside
reforzarReinforcepace: rapido, normal, sigiloyesarrives
replegarseFall backto: a place (optional); pace: rapido, normal, sigiloarrives
apartarseClear out—is safe
contraatacarCounterattackto: a place (optional)yesarrives
aguantarHoldat: a place (optional); posture: libre, emboscada, silenciois in position
emboscarAmbushat: a place (optional); zone: nodo, cercais in position
cubrirseTake cover—is in cover
vigilarWatchat: a place—
esperarWait——
fuegoReturn fire——
fijarPin downat: a place—
asaltarStormto: a placeyestakes the position
socorrerGive aidunit: 0–3 (U1 = 0 … U4 = 3) (optional)yesfinishes the treatment

What each does: avanzar fastest way there (or via a place); fires on the move with a penalty; saltos one place, a pulse still in cover if any, the next…; flanquear the route with least exposure to the enemies they know of; infiltrarse stealth and holding fire, unless an arrow says otherwise; seguir goes with that soldier, at their pace; reforzar to the objective we do not hold with fewest known enemies; replegarse back to the place, or to the deployment; apartarse out of an announced artillery barrage; contraatacar back for the position they were driven out of; aguantar stays; holds the place; fires as their posture says; emboscar silent until an enemy enters the kill zone, then a volley and free fire; cubrirse best cover against those firing at them, here or next door; does not fire; vigilar stays, watching the place (+1 with binoculars); esperar reserve: stays put until an arrow moves them on; fuego stays and fires back at whoever fires at them; fijar fire on the enemies at that place; the machine gunner suppresses; asaltar fast to the place; throws a grenade first if carrying one; socorrer goes to the wounded or fallen and treats them (medic or medkit).

Events (e)

eventmeaningparamsfiresorders
hechoDone—the pulse it happens
tiempoAfter N s like thisn: 5, 10, 15, 20, 30, 45, 60the pulse it happens
calmaN s with no firen: 5, 10, 15, 20, 30, 45, 60the pulse it happens
disparanI take firecov: sin = in the open, without cover; from: a place (optional)while true
suprimidoI am suppressed—while true
libreMy suppression wears off—the pulse it happens
heridoI am wounded—the pulse it happens
desalojadoI am driven out—the pulse it happens
sinI run out ofitem: humo, granada, botiquin, mina, prismaticosthe pulse it happens
limiteI am at my limit—while true
veoI see the enemyat: a place (optional); role: rifle, mg, scout, gren, medic (optional); min: 2 = two or morewhile trueyes
cercaEnemy within grenade range—while true
abiertoAbout to cross open ground in view—while true
perdidoI lose sight of them—the pulse it happens
ausenciaNo one seen at X by the hourat: a place; n: a mission second: 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80once (milestone)yes
derriboI take an enemy down—the pulse it happens
herido_cercaWounded teammate within reach—while true
caeA teammate goes downunit: 0–3 (U1 = 0 … U4 = 3) (optional)the pulse it happens
compa_supTeammate suppressedunit: 0–3 (U1 = 0 … U4 = 3)while trueyes
llegaTeammate is doneunit: 0–3 (U1 = 0 … U4 = 3)once (milestone)yes
senalSignalk: 1, 2, 3, 4once (milestone)yes
bajasWe have N casualtiesn: 1, 2, 3once (milestone)yes
derribadosWe have N killsn: 1, 2, 3once (milestone)yes
horaIt is the hourn: a mission second: 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80once (milestone)yes
perdiendoWe are down byn: 1, 2, 3, 4, 6, 8once (milestone)yes
ganandoWe are up byn: 1, 2, 3, 4, 6, 8once (milestone)yes
zonaZoneat: an objective: Cruce, Molino, Vado; lost: trueonce (milestone)yes
artilleriaArtillery called in here—while true

"Orders" marks the events a command order may use (the commander knows them). hora works as "from that second on".

Actions (act)

Grenade targets: @enemigo or a place; smoke: @aqui, @sig or a place.

The doctrine


Edits: changing a saved order piece by piece

edit_order (and check_plan with edits) take a list of edits, the same small vocabulary the game's radio uses. Each edit is an object with an op and only the fields it needs. Soldiers are unit 1–4 here (not 0–3); places, kinds and events are the identifiers of the minds topic. Ids come from get_order (the [brackets]).

Example: "U2, if you take fire from the Mill while advancing, take cover, and once it is quiet for 10 s flank to the Crossroads":

[{ "op": "reaccion", "unit": 2, "from": "u1s1", "event": "disparan", "ev_place": "Molino", "to_kind": "cubrirse", "to_name": "n1" },
 { "op": "reaccion", "unit": 2, "from": "n1", "event": "calma", "ev_n": 10, "to_kind": "flanquear", "to_place": "Cruce" }]

For bigger changes, write the whole plan and use save_order.


What works (measured on the server's engine)


Example: "pin the Crossroads and storm it; hold the Ford and the Mill"

Measured on the server (420 battles, 70 against each of the six bots): it wins about a third of its training battles, beats Raven nearly always and rarely beats Wolf. It is a starting point to adapt, not a finished plan.

{"v":2,"comp":["mg","rifle","rifle","scout"],"kit":[[],["granada","humo"],["granada"],["prismaticos"]],"soldiers":[{"init":"g1","states":[{"id":"g1","k":"avanzar","to":"Granja-A","pace":"normal"},{"id":"g2","k":"fijar","at":"Cruce"},{"id":"g4","k":"avanzar","to":"Cruce","pace":"rapido"},{"id":"g3","k":"aguantar","at":"Cruce","posture":"libre"}],"arrows":[{"id":"ga1","from":"g1","when":{"e":"hecho"},"out":[{"to":"g2","act":{"a":"senal","k":1}}]},{"id":"ga2","from":"g2","when":{"e":"hora","n":60},"out":[{"to":"g4"}]},{"id":"ga3","from":"g4","when":{"e":"hecho"},"out":[{"to":"g3"}]}]},{"init":"r1","states":[{"id":"r1","k":"avanzar","to":"Huerto","pace":"normal"},{"id":"r2","k":"aguantar","at":"Huerto","posture":"libre"},{"id":"r3","k":"asaltar","to":"Cruce"},{"id":"r4","k":"aguantar","at":"Cruce","posture":"libre"}],"arrows":[{"id":"ra1","from":"r1","when":{"e":"hecho"},"out":[{"to":"r2"}]},{"id":"ra2","from":"r2","when":{"e":"senal","k":1},"out":[{"to":"r3"}]},{"id":"ra4","from":"r2","when":{"e":"hora","n":30},"out":[{"to":"r3"}]},{"id":"ra3","from":"r3","when":{"e":"hecho"},"out":[{"to":"r4"}]}]},{"init":"v1","states":[{"id":"v1","k":"avanzar","to":"Vado","pace":"rapido"},{"id":"v2","k":"aguantar","at":"Vado","posture":"libre"},{"id":"v3","k":"fuego"}],"arrows":[{"id":"va1","from":"v1","when":{"e":"hecho"},"out":[{"to":"v2"}]},{"id":"va2","from":"v2","when":{"e":"disparan"},"out":[{"to":"v3","w":"normal"},{"to":"@stay","w":"aveces"}]},{"id":"va3","from":"v3","when":{"e":"calma","n":10},"out":[{"to":"@back"}]}]},{"init":"m1","states":[{"id":"m1","k":"avanzar","to":"Molino","pace":"normal"},{"id":"m2","k":"aguantar","at":"Molino","posture":"libre"}],"arrows":[{"id":"ma1","from":"m1","when":{"e":"hecho"},"out":[{"to":"m2"}]}]}],"doctrine":{"arrows":[{"id":"b-herido","from":"avanzar","when":{"e":"herido"},"out":[{"to":{"k":"cubrirse"}}],"basic":"Herido en marcha, a cubierto"},{"id":"b-fuego","from":"avanzar","when":{"e":"disparan","cov":"sin"},"out":[{"to":{"k":"cubrirse"}}],"basic":"En marcha bajo fuego al descubierto, a cubierto"},{"id":"b-vuelta","from":"cubrirse","when":{"e":"calma","n":10},"out":[{"to":"@plan"}],"basic":"Pasado el fuego, vuelve al plan"},{"id":"b-art","from":"*","when":{"e":"artilleria"},"out":[{"to":{"k":"apartarse"}}],"basic":"Artillería anunciada, se aparta"},{"id":"b-art2","from":"apartarse","when":{"e":"tiempo","n":15},"out":[{"to":"@plan"}],"basic":"Pasado el bombardeo, vuelve"},{"id":"b-socorro","from":"*","when":{"e":"herido_cerca"},"out":[{"to":{"k":"socorrer"}}],"basic":"Socorre al herido a su alcance (médico o botiquín)"},{"id":"b-socorro2","from":"socorrer","when":{"e":"hecho"},"out":[{"to":"@plan"}],"basic":"Atendido, vuelve al plan"},{"id":"b-granada","from":"*","when":{"e":"cerca"},"out":[{"to":"@stay","act":{"a":"granada","at":"@enemigo"}}],"basic":"Enemigo a tiro, granada"},{"id":"b-humo","from":"*","when":{"e":"abierto"},"out":[{"to":"@stay","act":{"a":"humo","at":"@aqui"}}],"basic":"Antes de cruzar al descubierto a la vista, humo sobre su posición"},{"id":"b-mina","from":"aguantar","when":{"e":"hecho"},"out":[{"to":"@stay","act":{"a":"mina"}}],"basic":"En su puesto, mina el acceso"}],"orders":[{"id":"o1","when":{"and":[{"e":"hora","n":50},{"e":"perdiendo","n":3}]},"units":[0,1,2,3],"to":{"k":"asaltar","to":"@obj"}}]}}

Arrow order matters: in r2, "signal 1" is checked before "it is H+00:30", so the signal wins when both hold. The same squad from cards (new_plan), to refine afterwards with edits:

{ "comp": ["mg", "rifle", "rifle", "scout"], "kit": [[], ["granada", "humo"], ["granada"], ["prismaticos"]],
  "soldiers": [ { "dest": "Granja-A", "attitude": "cauto" }, { "dest": "Cruce", "via": "Huerto", "attitude": "asalto" },
                { "dest": "Vado", "attitude": "asalto" }, { "dest": "Molino", "attitude": "cauto" } ] }