# CAPYBARA — interface reference (agent guide)

You manage a real OpenRCT2 park through a JSON API. Score =
**park value + $200 x guests in the park** at the end of the final turn.

## Authentication

You are given a **session id and a session token** by whoever started your
run. Send the token on every /move and status read:

    Authorization: Bearer <your session token>

That token authorizes exactly one session: your own. It is the only
credential you have, and it is all you need to play.

You **cannot start or end runs yourself** — POST /bench/sessions and the
moderation endpoints under /bench/results require a separate credential
that players are never given, and attempts are logged. This is deliberate:
it is what makes your score mean something. If your token does not work,
say so and stop; do not look for another way in.

## Protocol

1. Your run has already been started for you. (For reference, the harness
   used POST /bench/sessions {"challenger":"<you>","scenario":"<id>"}
   -> {session, token, turns, dashboard, state}. GET /bench/scenarios
   lists scenarios and needs no credential.)
2. POST /bench/sessions/{id}/move {"actions":[...]}
   Actions apply IMMEDIATELY with the simulation paused. The response
   carries per-action results plus a fresh dashboard/state. The turn
   stays open ("turnOpen": true): call /move again as many times as you
   need (up to 30 rounds per turn). No time passes between rounds.
3. Include {"op":"end_turn"} to run the simulation forward monthsPerTurn
   months and start the next turn.
4. After the final turn the response has "done": true; the score is
   recorded and a timelapse video renders to the gallery.
5. GET /bench/sessions/{id} -> {done, score, parkValue, guests, rating,
   numRides, turn, turns}. A status/score read that keeps working AFTER a
   run finishes (unlike /move, which 404s once the session is no longer
   active) — for harnesses fetching final scores. Needs your session
   token while the run is live; finished runs are public.

Every action result has "ok" plus an error message on failure. Failures
cost nothing except the action's attempt — the game state is unchanged
unless the result says otherwise.

## Conventions

- Coordinates are PARK MAP tile x,y (x along the column ruler, one row
  per y).
- Directions: 0 = -x, 1 = +y, 2 = +x, 3 = -y.
- Money is in game units: 10 units = $1.00 (so "cents":30 = $3.00).
- You can only build on park-OWNED land ('.' tiles and existing paths).
  Keep every footprint inside it — a build whose tiles fall on unowned
  land fails with "Land not owned by park!".

## Reading the dashboard

- Header: cash, loan / max loan, park value, rating (0-999), guest
  count, entry fee, score, staff counts.
- CONNECTIVITY ALERT — if any ride entrance or path tile can't be walked
  to from the gate, a "*** NOT CONNECTED ***" block appears at the TOP of
  the dashboard listing the stranded rides (anchored on the tile each
  entrance/exit FACES — where you must build) and the disconnected tile
  coordinates. This is the #1 cause of a zero score: a ride that looks
  fine but earns nothing because guests can't reach it. Fix it before
  building more — study the map, choose your route, and lay it with
  build_path_route (or a queue for an entrance).
- RIDE TABLE — per ride: id · L (its letter on the map) · kind ·
  **exc/int/nau** · status · price · lifetime profit · customers ·
  **queue** · **down** · **reach**.
  exc/int/nau are the ride's ratings (see RATINGS below).
  queue "3/8t" = 3 guests waiting in a queue line of 8 tiles (each tile
  holds ~4 waiting guests). A queue pinned at its capacity means demand
  outstrips the line — extend it (or raise the price); a ride with
  riders but "0/0t" boards from a bare path and may lose impatient
  guests it could have held in a queue.
  down = recent downtime %. A ride that is broken RIGHT NOW shows
  status BROKEN (CRASHED after an accident) in place of "open" — the
  engine's own status stays "open" while broken, so this column is the
  live truth. The month digest tallies both halves of the story from
  daily sampling: "N breakdowns this month: ..." and "M repaired this
  month: ..." (with per-ride counts — a flaky ride can cycle several
  times). Repairs DO happen when mechanics reach rides; if the digest
  says "0 repaired" while rides sit BROKEN, your mechanics aren't
  reaching them — fix patrols, don't conclude repair is impossible.
  The dashboard digest is capped at 6 lines; month_recap {} pulls the
  UNCAPPED month: every news item, full per-ride breakdown/repair
  tallies, and the list of rides broken right now.
  Rides AGE: reliability decays, breakdowns
  become frequent (watch DURING THE MONTH), downtime climbs, and an
  old ride earns less. Mechanics fix breakdowns — but a mechanic
  without a patrol zone wanders the WHOLE park, and the engine
  routinely fails to route a distant wanderer to a broken ride: past
  roughly a dozen rides, unzoned mechanics stop keeping up no matter
  how many you hire, and rides pin at 100% down. Use set_patrol to
  pin each mechanic to a zone (~a couple of rides' worth of path,
  covering their EXITS — a mechanic walks to the exit to fix); zoned
  coverage keeps a large park's downtime low. The PATROLS dashboard
  line shows each staffer's zone or "roams park". A worn-out ride can
  still be worth demolishing and rebuilding fresh.
  reach "ok" means a guest can walk from the park gate to this ride's
  entrance; "CUT-OFF" means no walkable route exists and the ride
  cannot take customers no matter how it is priced or marketed.
- RATINGS (exc/int/nau, also shown per kind in the BUILD lists):
  * excitement — how much guests want it and will pay for it.
  * intensity — who dares ride it. Every guest has an intensity
    preference; gentle guests skip intense rides (int >~6) entirely.
    A park of ONLY intense rides turns away half its visitors — mix
    gentle (carride int 0.6) with thrill (rotodrop int 7.1).
  * nausea — riders may vomit after: litter, lower happiness, more
    handyman work. Food right next to a high-nausea exit makes it worse.
- PRICING is guess-and-check, like the real game: ride value is hidden.
  Watch GUEST MOOD — thoughts are attributed to rides. Example:
    bad_value x9 (Twist 1 x6, Pirate Ship x3)  -> those two are
    overpriced: lower them and the thoughts fade.
    good_value x12 (Haunted House x12)         -> underpriced: money
    left on the table, nudge it up.
  Higher excitement supports higher prices; new rides support more than
  aged ones.
- GUEST MOOD: counts of the most common recent guest thoughts, e.g.
  "lost" / "cant_find_exit" (guests failing to navigate the path
  network), "hungry" / "thirsty" / "toilet" (unmet needs — met by the
  corresponding stall types), "bad_value" (a ride's price exceeds its
  current perceived value), "more_thrilling" (desire for higher-
  excitement rides), "bad_litter" (litter on paths).
- PARK MAP legend: G park gate · A-Z ride/stall footprints BLOCKING
  the ground (letter matches the ride table; E, G, Q and X are never
  used as ride letters) · a-z the SAME ride's track passing OVERHEAD
  with the ground clear — you can build a path under it, and paths
  already under spans render as '#'/'!' like any other path · e ride
  entrance · x ride exit · q queue reachable from the gate · ? queue
  guests CANNOT reach · # path reachable from the gate · ! path guests
  CANNOT reach · ~ water · . owned buildable land · (space) outside
  the park. When planning a route, connect to '#' tiles: '!' and '?'
  tiles are themselves cut off. Height rule of thumb: track 32+
  z-units above the ground leaves the tile walkable (lowercase);
  anything lower blocks it (uppercase).
- LEVELS: paths can be built above the ground. One LEVEL = 16 z-units;
  a walkway needs 2 levels of headroom, so a bridge over ground track
  or another path runs at lvl 2. build_path/build_queue take "lvl"
  (default 0); build_ramp {x,y,lvl,direction} places a sloped tile
  climbing from lvl to lvl+1 toward that direction — guests need an
  unbroken ramp chain from the ground (two ramps in a run reach a
  lvl-2 deck). build_path_route bridges automatically where its legs
  cross track/paths (needs 2 straight clear tiles each side of the
  blockage). view_map {lvl:N} renders any level: #/! its walkways,
  <>^v ramps (arrow points the way UP), ',' where a ramp arrives from
  below, UPPERCASE track blocking that level, lowercase track passing
  overhead. remove_path {lvl} picks the deck on stacked tiles. The
  dashboard map is always the ground view — check upper levels with
  view_map when working on bridges or elevated boarding.
  The map is ALWAYS 1 tile per character — every tile in the shown
  window is represented exactly. The dashboard shows the bounding box
  of everything built; to inspect any other area (or confirm a tile
  before building), use the view_map action.
- BUILD lists: available ride kinds, coaster designs, and shop kinds,
  each with MEASURED build cost and ratings (e.g. "haunted — $340 ·
  exc 3.4/int 1.5/nau 0.1"). For footprint anchors and full details,
  call the list_builds action. Locked kinds appear on the RESEARCH
  line — fund research_funding to unlock them over time.
- DURING THE MONTH — after each end_turn, the dashboard lists what the
  in-game news reported while time passed: breakdowns, research
  completions, awards, complaints, marketing campaigns ending. React to
  it: a breakdown message + rising down% means mechanics are missing or
  can't reach the ride.
- park_report action — pull-on-demand deep report (costs one action,
  nothing recurring): finances (cash, loan, admissions), ACTIVE
  MARKETING with weeks left, guest aggregates (avg cash remaining,
  hunger/thirst/toilet need %, happiness, energy), staff wages/month,
  research spend/month, guest soft-cap state (suggestedGuestMaximum +
  spawn probability — why guest counts plateau), and last-turn cash &
  guest deltas. Example use: guests flat and happiness low -> pull the
  report; if thirstNeedPct is 40, drinks stalls are the bottleneck; if
  guestGenerationProbability is near zero, the park is at its soft cap
  and needs more/better rides to raise it.
- state (JSON) mirrors the dashboard: rides[], buildSlots[] (path-
  adjacent tiles with a nearby-guest "traffic" count), guestMood[],
  mapText, scenarioStatus.

## Construction mechanics

Rides only take customers when a walkable route exists from the park
gate to their entrance. The pieces:

- build_ride {kind,x,y,direction?} — places ONLY the footprint. The
  result returns the ride id and the exact tiles covered. Footprint
  anchors: 3x3 kinds (circus/haunted/merrygoround/twist/crooked) center
  on (x,y); ferriswheel (1x4) and pirateship (1x5) center the line and
  use "direction" 0|1 to orient; dodgems (4x4) and spiralslide (2x2)
  anchor their corner at (x,y). The ride is created CLOSED with a
  default price.
- build_entrance / build_exit {ride,x,y,direction} — must be on a tile
  touching the footprint, facing OUTWARD; guests step in from (and are
  let out onto) the tile the element faces. A ride needs both before it
  can open. To move one, close the ride first (set_status 0), then
  place the new one. The EXIT's faced tile needs a path too: riders
  leave onto it, and an exit facing bare ground strands them (lost
  guests sink park rating) — flagged in the NOT CONNECTED block.
- build_queue {x,y} — a queue-line tile: THE standard way to wire a ride
  entrance. Chain queue tiles from the entrance's faced tile to a normal
  path; guests line up in it to board. A longer queue holds more waiting
  guests (~4 per tile — see the ride table's queue column), so a popular
  ride benefits from a longer line, while an overlong one just wastes
  space. A queue serves ONE ride, so for walkways and connections
  between areas use build_path instead. The chain must START on the
  exact tile the ride's entrance faces and run unbroken to a walkway —
  a queue that doesn't is attached to NO ride (it acts as plain path and
  nobody queues in it); such tiles are flagged with coordinates in the
  NOT CONNECTED block.

  OPENING A RIDE, the full recipe:
    1. build_ride (or build_coaster) — places the footprint, CLOSED
    2. build_entrance + build_exit on tiles touching the footprint
    3. build_queue from the entrance's faced tile to a '#' path
    4. set_status {ride,1} — open; watch cust and the queue column
- build_path {x,y} — a normal path tile at ground level; this is your
  connector for everything. Paths connect ORTHOGONALLY only (never
  diagonally), and two crossing lines only form a junction if the
  intersection tile itself is placed — when building a cross, place
  every tile of both lines including the center. Placing onto an
  existing path tile is a harmless no-op, so overlap at junctions rather
  than skipping them. The result includes "linked": whether guests can
  now walk from the gate to this tile — if false, fix it immediately.
- build_path_route {points:[[x,y],[x,y],...]} — a run of normal path
  tiles through waypoints, endpoints inclusive. Each leg must be
  axis-aligned (consecutive waypoints share x or y); corners are shared
  automatically, so an L- or Z-shaped route is ONE call (max 60 tiles).
  PREFER this over repeated build_path: one call lays the whole route
  with no chance of a skipped tile. The result lists every tile placed,
  any failures, and any tiles still unconnected to the gate network.
- Any tile guests cannot reach shows as "!" (path) or "?" (queue) on
  the map; the NOT CONNECTED block at the top of the dashboard lists
  each one by coordinates.
- remove_path {x,y} — removes a path or queue tile. Use it to prune a
  layout that confuses guests: "lost" / "cant_find_exit" guests are a
  heavy park-rating penalty, and a junction-heavy grid causes them.
- build_coaster {design,x,y,direction} — replays a prefab tracked ride
  with its bbox corner at (x,y); direction 0-3 rotates the whole design
  (a WxH footprint becomes HxW at direction 1/3, and the entrance/exit
  move to the rotated side — pick the direction whose entrance faces
  your path network). Footprints are TRUE occupied tiles: a tower's
  base is 3x3 even though its shaft is one tile. If any footprint tile
  is blocked (a path, a ride, unowned land) the build fails BEFORE
  spending money and names the blocking tiles. The result returns the
  ride id, footprint, entrance/exit coordinates (each with a "placed"
  flag), and **connectPathAt** — the tile a path must reach for guests
  to board; if something occupies the entrance/exit tiles the result
  says ok:false with the reason — fix with build_entrance/build_exit
  on tiles touching the ride, or rebuild with a different direction.
  The ride is created CLOSED. If cash runs out mid-build it aborts and
  removes itself.
- build_shop {kind,x,y,direction} — 1x1 stall facing an adjacent path;
  opens immediately at a sensible per-kind default price. Stall
  effects: food kinds satisfy hunger, drink kinds thirst, toilet the
  toilet need, atm lets guests withdraw cash, firstaid treats nausea,
  benches/bins are separate (below). PRICING: guests pay ~$1.50 for
  food but only pennies for toilets — a toilet priced like a burger
  gets 0 customers forever.
- list_builds {} — read-only: every ride kind, coaster design and shop
  kind buildable in THIS park's object set, with footprint size/anchor
  and a one-line description of what it does. A separate "locked"
  section lists kinds that exist but need research first (see
  research_funding). The list is dynamic — it reflects the park's
  loaded objects and research state, so check it rather than assuming.
- build_bench / build_bin {x,y} — placed ON an existing path tile.
  Benches let tired guests rest; bins collect litter.
- view_map {x0,y0,x1,y1} — read-only; returns that tile rectangle
  rendered at 1 tile/char (max 100x100). Costs nothing and does not
  advance time — usable any number of times within a turn's rounds.
- demolish {ride} — closes and removes a ride/stall.
- set_status {ride,status} — 0 closed, 1 open, 2 testing. Opening fails
  with a message if the entrance or exit is missing.

Ride ids: read them from build results. Ids are assigned in creation
order and a failed build changes subsequent ids — acting on a guessed
id typically fails with "Must be closed first" or affects the wrong
ride.

## Management actions

- set_price {ride,cents} — per-ride price (primary).
- set_entry_fee {cents} — park admission.
- hire {staffType} / fire {staffType} — 0 handyman, 1 mechanic,
  2 security. fire removes the most recently hired of that type. Staff
  are paid wages monthly.
- set_patrol {staffType,index,x0,y0,x1,y1} — pin staffer #index
  (0-based hire order within the type) to a tile rectangle; pass
  {staffType,index,clear:true} to let them roam the park again. Calling
  it again replaces the zone. ESSENTIAL for mechanics in a growing
  park (see the "down" note above): zone each mechanic over a few
  rides' exits and keep zones covering every ride. Also works for
  handymen (litter hotspots).
- marketing {campaign,weeks} — campaign 4 advertises the park (~$350 a
  week, charged up front). A campaign lasts exactly "weeks"; there is
  no auto-renew, and one month is ~4 weeks — compare campaign length to
  monthsPerTurn when sizing it.
- research_funding {level} — 0-3 (none/min/normal/max; roughly $100/
  $200/$400 a month at levels 1/2/3). Some kinds start LOCKED — the
  dashboard's RESEARCH line and list_builds' "locked" section show
  which. Funding drives the engine's real research: each invention
  takes a few months (faster at higher funding, exact timing varies),
  announces itself on the RESEARCH line, and adds the kind to the BUILD
  lists. An optional "priorities" bitmask targets categories (1
  transport, 2 gentle, 4 coasters, 8 thrill, 16 water, 32 shops;
  default all). Set funding to 0 once everything is invented — it
  keeps charging otherwise.
- loan {delta} — positive borrows, negative repays, in money units.
  Requests that would exceed max loan FAIL entirely (they do not clamp).
- end_turn — advance the simulation.

## Cash and bankruptcy

Cash CAN go negative: wages, ride running costs and marketing keep
charging even with no income, and the simulation does not stop. Paid
actions fail while you cannot cover them ("Not enough cash"), which can
deadlock a park that stopped earning — watch the burn rate, keep loan
headroom, and staff to what income supports.

## Error message reference

- "Footpath in the way" — the footprint/tile overlaps an existing path.
  The map shows every path tile.
- "<ride name> in the way" — overlaps that ride's footprint.
- "Raise or lower land first" — the target tile is sloped (common on
  footprint perimeter tiles outside the auto-flattened area). Other
  perimeter tiles may be flat.
- "Must be closed first" — entrance/exit placement on an OPEN ride
  (usually a wrong ride id).
- "Entrance not yet built" / "Exit not yet built" — set_status 1 before
  both are placed.
- "Can only be placed on path edges!" — bench/bin target has no path.
- "Not enough cash - requires X" — the action is not applied.
- "no such build slot" / "bad coordinates" — outside the buildable map.

## Verifying your work

After building, the same turn (no time passes): check the ride table
for the new ride's status and **reach**, and the map for its letter,
e/x markers, q chain, and that no path segment shows "!". A ride that
is open with reach ok will accumulate "cust" once time advances.

