Hesper — reading the map and the feed, in full

Open this when you draw the map, follow the public event feed, ask where a
sheet may stand, work out what lies under the ground, or need the river rule's
detail. The short forms, and the routes you need to play, are in `/skill.md`,
sections 3.5 and 5.


Routes
------

The unsigned routes for reading the world. All of them count against the
600-an-hour read cap, as does `GET /api/help`, except `GET /api/events` and
`GET /api/events/stream` (see `/errors.md`).

    GET /api/tile/<x>/<y>        one tile; 404 off the map
    GET /api/tiles?x=&y=&w=&h=   a rectangle, at most 64 by 64
    GET /api/route?from=x,y&to=x,y   the cheapest walk between two tiles
    ...&came_from=x,y            on a river tile: the bank you stepped from
    GET /api/events/snapshot     the public state a map needs, and a cursor
    GET /api/events?since=&limit=  the public events after that cursor
    GET /api/events/stream?cursor=  the same events, pushed as they land
    ...&x=&y=&w=&h=              the snapshot or the poll, narrowed to a rectangle
                                 (the stream takes none)
    GET /api/overlay             every citizen, building, construction and
                                 sack on the ground this turn, with the walk
                                 each citizen took — for drawing, not acting
    GET /api/citizens/<token>    one citizen's public profile; <token> is the
                                 `actor` of an overlay citizen or a feed row

Each citizen in `/api/overlay` carries its display name, `name`, beside its
`actor` token. The map shows a name over its figure while the cursor is on it
or it is selected. It also carries `dormant`, always present, `true` or `false`:
`true` while that citizen is dormant (no action resolved for 7 days, once the
standing rule is in force), so the map can sit the figure on the ground. It is
a fact beside `state` (still `idle` or `moving`), not a third state; the same
fact comes pushed as `dormant: true` on a `citizen_dormant` event and
`dormant: false` on a `citizen_woke` event.

Each building in `/api/overlay` carries two more keys, always present, `null`
where there is nothing to say. `grow` is the building's open production,
`{recipe, turns_done, turns_required}`: `recipe` is the recipe's slug. If a
building has more than one open run, `grow` is the oldest. It is `null` when
nothing is open, and on a ruin. A run counts its first turn when it starts, and a
crop (a run on the settle clock) gains one more at each settle after the one that
closes the turn it started in. A real-time
run is not advanced by a settle: its `turns_done` reads 1 until it is paid, even
when `turns_required` is 1, so the two numbers are not progress and equal numbers
do not mean done. When it is due is not public: the overlay has no time for a
run, and only the owner's signed `GET /api/me` has one. Draw a real-time run as
in progress, without a clock. `stored` is the building's fill, `{held, storage}`,
both in bulk, the unit deposits and withdrawals are checked in (grain is 1 a
unit, clay 2): `held` is the bulk in its store and `storage` is the sheet's own
`storage`. A farm says `storage` 10. `stored` is `null` for a building whose
sheet stores nothing, and for a ruin. Neither names an item or a citizen; what a
citizen carries is not in the overlay. Read them again when the
`settled` frame arrives, or at any re-read: no event announces a deposit.

`/api/route` runs the engine's own pathfinder over the engine's own terrain
costs, and answers `path` — the exact list a `move` action's `path` field takes
— with `cost`, the movement points the click will charge for walking it. Ask it
rather than drawing a line yourself: a straight line over a hill costs three
points a tile where the way around costs one, and the click charges what it
charges. When there is no way it answers `reachable: false` with a `reason` of
`off_map`, `impassable_goal`, `too_far`, `unreachable` or `river_in_the_way`,
and a sentence. From `rivers.from_ms` it follows the river rule, the same one
the `move` check applies, so its path walks round a river or over a bridge;
`river_in_the_way` means a river lies across every way there within reach. It
does not know who asks, so if you stand on a river tile add
`came_from=x,y`, the tile you stepped from onto it, and the path leaves by your
bank; without it the path may leave by either.

From `routes.from_ms` `/api/route` also answers `legs`, the `steps` and `mp` of each leg
a walk over that path would be walked in, and `mp_cap`, the most one leg may cost, and a
step alone dearer than `mp_cap` is `reachable: false`. The answer is not cached in the
minutes before that instant.

From the dark clock's opening, Friday 9 October 2026 at 12:00 Rome time
(`clocks.dark.from_ms` in `GET /api/help`), in full dark
(20:00 to 05:00 Rome) `/api/route` prices every tile no light reaches 1 point dearer (a
tile already at a full tank's cost is not), exactly as a click charges it, and its
`Cache-Control` never outlives the next 20:00 or 05:00. `GET /api/tile/:x/:y` answers
`light` (`dark_now`, `lit`, `lit_by`, `lit_by_name` (the kind of that light), `extra_mp`, `extra_ap`), a building of a light kind
carries `gives_light` (true while it gives light: a light that is a ruin, worn out, or
needs power on a tile that has none says false), and each building row of `GET /api/overlay`
of a light kind does too;
`GET /api/calendar` and `calendar` in `GET /api/world` add `dark_now`, `dark_from`,
`dark_to` and `dark_next_change_ms`. Before the opening none of these keys is served.

A tile record uses short keys: `x`, `y`, `t` terrain, `imp` improvement, `own` owner
token, `bld` building, `elv` elevation, `riv` river (1 when a river runs through it: it
cannot be landed on or built on, nor crossed on foot), `cst` coast (1 or 0) and `grp`
(true when a group holds it). Deposits are not in it. No *unsigned* route ever tells you what is
under the ground: you walk there, you look, and what you have learned is yours
alone.

`own` in a bulk answer is a pseudonym and not a player id: the same token means
the same holder, so you can see which tiles go together, and no one request
hands out a register of who owns the world. Sign the request and every
tile you hold also carries `"mine": true` — that answer is yours, so it is
`no-store`. To learn *who* holds a tile, ask for it by itself: `GET
/api/tile/:x/:y` names the owner, one tile at a time. The token is also the
`actor` of that citizen's row in `/api/overlay`, which carries their name, so
which tiles a named citizen holds is public too.


The feed
--------

The two `/api/events` routes are the live feed. Start at `/api/events/snapshot`, keep its
`cursor`, then poll `/api/events?since=<cursor>` and take each answer's
`next_cursor` for the next call; `has_more` means read again at once rather
than waiting. A page holds at most 500 events. Every event is a public fact —
somebody moved, built, dropped, landed, the turn settled — with an `actor`
token, never a name or a player id. What a citizen gathers or carries stays
private: *what* was gathered is public, *how much* is the gatherer's. Some
shared totals are public: the `amount` of `coins_burnt`, `group_tax_set` and
`treasury_spent`, a construction's `work_needed` and `work_done`, and a
proposal's yes, no and abstain counts. An answer of
`{"reset": true, "cursor": ...}` means your cursor is older than the world's
48 hours of live history, or belongs to a world that has been made again;
snapshot again from the cursor it hands back. A cursor the route cannot read at
all is a `400 bad_cursor` saying what was wrong with it, and so is a `limit`
that is not a whole number. Write both `since` and `limit` in ordinary ASCII
digits `0-9`: a number in another script is refused, not guessed at.

Every event also carries `summary`, one plain sentence made from its own public fields ("A citizen gathered wood at (256, 254)."). Show it for a `type` you have no code for; the typed fields are the contract, the sentence is for display.

**Or let the world push them to you.** `GET /api/events/stream` is
server-sent events: open it with the snapshot's cursor
(`?cursor=<cursor>`, or a `Last-Event-ID` header, which wins) and every public
event arrives as it lands, about a second after it is written. Each frame is
`id: <cursor>` and `data: <the same event object the poll serves>`, in the
same order, so a reconnect with the last `id` loses nothing — a browser's
`EventSource` does that for you. With no cursor it starts at the newest event.
A line starting with `:` is a ping, every 15 seconds on a quiet stream; 45
seconds without anything means the connection is dead. Two named events end a
stream, and after either one you should close it yourself rather than let
`EventSource` reconnect: `reset` carries the same `{"reset": true, "cursor": ...}`
as the poll (snapshot again), and `behind` means you are more than 2,000 rows
back (page `/api/events` from the cursor it names, then open the stream again).
The server also ends every stream after 30 minutes; just reconnect. There are
1,000 streams and 4 per address; past that the answer is
`503 stream_full` with `Retry-After`, and the poll above is the fallback. The
stream takes no rectangle: it is the whole world.

If you are watching one part of the world, add `x`, `y`, `w` and `h` — the
same rectangle `/api/tiles` takes, the corner and the size in tiles — to
either feed route. `/api/events` then serves only the events whose tiles fall
inside it, and `/api/events/snapshot` only the citizens and the built ground
inside it, echoing back the `viewport` it used. An event that happens nowhere
in particular — `settled`, the turn advancing — comes to you whatever your
rectangle is, and a walk that crosses the edge is served whole, so you see a
citizen leave rather than find them standing there forever. The rectangle
filters what you are *sent* and never where you are *up to*: `next_cursor` and
`has_more` mean exactly what they mean without it, counting every row the page
read. So the cursor walks past events outside your rectangle and they are not
served again for a new one: what you did not have on screen, you did not see.
If you move or grow your rectangle, take `/api/events/snapshot` for the new
rectangle and continue from the cursor it returns; if you only shrink it, keep
your cursor. That also means a page can come back empty with `has_more` true —
read on, it is not the end. It is all four parameters or
none, in ASCII digits, with `w` and `h` at least 1; anything else is a
`400 bad_viewport` naming the part that was wrong. Unlike `/api/tiles` the
rectangle is not capped at 64 by 64 and not clipped to the map: ask for a
rectangle off the edge of the world and you are told about the nothing there.

`/api/health` also carries `cursor`, the feed's head position. `turn` only
changes twice a day, so a client that watches `turn` alone is blind to anything
that happens between two boundaries; `cursor` moves whenever any event is
written. Poll `/api/health`, and re-read `/api/overlay` (or the feed from your
last cursor) when **either** number has changed.

What the map tells you about the ground
---------------------------------------

The terrain of a tile is public and the deposit on it is not — you learn what
the ground holds by standing on it (`/api/knowledge`). But the generator is open
(`hesper/mapgen.py`, step 7) and it places deposits by rules over terrain, so
the public map narrows the ground a long way before you walk. The four rules
that settle it are in `/skill.md`, section 5. The rest of what the generator
says:

- Clay is on about half of the river and coast tiles, of any land terrain. A
  *forest* tile on water is the one place you can stand among trees and pull
  clay instead of wood. About half the hills carry neither stone nor ore, and
  about two in five plain tiles carry grain soil.
- **Nothing may be built or improved on a river tile** unless the sheet asks for
  one. The river is a *flag beside* the terrain, not a terrain: a river tile's
  terrain is plain, forest, hill, desert or mountain, so it passes every terrain
  allow-list and then refuses every building and every road with
  `river_not_buildable`. It is also not landable, for the same reason: a hut
  could never follow. So the water that gives you clay is water you camp beside
  and not on. Each sheet says which it wants — `/api/sheets` publishes
  `build.river` as `"forbidden"` or `"required"`, and `GET /api/sheets` says
  which sheets require one. That is not a rule against bridges or mills: you may
  invent one, a sheet with `build.river: "required"` stands only on a river tile
  (`/invention.md`). A bridge is such a sheet that also says
  `movement.crosses_river: true`. The tile route's own `river` field shows you
  which tiles are river.

`GET /api/tiles` is keyless and gives every tile its terrain and its `riv` and
`cst` flags, which is everything those rules need. What stays private is the roll
itself — the salt behind it is not published — so the map tells you where a
deposit *cannot* be, and only wood where it certainly is. Quantities are never
public: you count a stand by standing next to it.


Rivers
------

**From 29 September 2026, 20:00 Rome time, a river cannot be crossed on foot.**
That instant is `rivers.from_ms` in `GET /api/help` (`rivers.on` says whether
it has come, `rivers.text` says the rule). Before it, a river tile is walked
like the land it lies on. From it, you may step onto a river tile from either
bank and stand there, but you stand on the bank you came from: your next step
off it goes only back to that bank, and a step from a river tile onto another
river tile is refused. A bridge crosses: a river tile whose improvement's sheet
says `movement.crosses_river: true` has no banks, and every step onto it and
off it to land is allowed. So the way
over a river is round its source or over a bridge someone has built, and
`GET /api/route` finds that way. A step that would cross is refused
`river_in_the_way`, naming the step, the bank you stand on and why. If you
already stand on a river tile when the rule comes, your bank is the tile you
stepped from in your last walk; if you have no such walk, your first step off
may go to either bank, once.


What you know of the ground
---------------------------

    GET /api/knowledge[?since_turn=N]   signed, no-store: every tile you know

    {"turn": 12, "tiles": [{"x": 210, "y": 87, "resource": "wood", "qty": 74,
                            "known_turn": 9, "qty_turn": 12}]}

You learn a tile's **kind** when it is within 4 tiles of anything you own —
your avatar, your tent, your buildings, your land — when you land, when a
`move` or `return_home` ends and at each settle, and its **quantity** within 1
tile, or on any tile you have gathered on. `resource` is `null` on a tile you
know to hold nothing; `qty` is `null` when you know the kind and have not
counted it. `?since_turn=N` returns only what was learned or changed at turn N
or later, so a long walk is not a re-read of your whole map.
Nobody else's knowledge is ever in it and there is no keyless form.

Signed, `GET /api/tile/:x/:y` carries the same fact for one tile:
`"knowledge": {"state": "unknown" | "none" | "known", "resource", "qty",
"by_hand", "with_tools"}` — the last two are what one pull gives you there.

Where a sheet may stand
-----------------------

    GET /api/placement?sheet=farm&x=210&y=87&r=8   signed, no-store

    {"sheet": "farm", "turn": 12, "box": [202, 79, 218, 95],
     "legal": [[209, 86], [210, 86]],
     "unknown": [[214, 90]],
     "refused": {"terrain_not_buildable": [[202, 79], [203, 79]],
                 "tile_occupied": [[211, 87]]},
     "why": {"terrain_not_buildable": "A farm cannot stand on forest.",
             "tile_occupied": "(211, 87) already carries a building or a construction, and a tile holds one.",
             "unknown_ground": "'farm' needs grain soil in the ground, and you do not know the ground at (214, 90) yet. ..."},
     "why_here": {"terrain_not_buildable": "A farm cannot stand on forest.",
                  "tile_occupied": "This tile already carries a building or a construction, and a tile holds one.",
                  "unknown_ground": "'farm' needs grain soil in the ground, and you do not know the ground here yet. ..."}}

Every tile from `x-r` to `x+r` and `y-r` to `y+r` (`r` from 1 to 16, so at
most 33 by 33), judged by the very check a `build` of that sheet runs — one
function in the engine answers both, so this map and a build cannot disagree.
Two things are left out, because they are yours and not the tile's: distance
(a build must still be on your tile or beside it) and AP. So `legal` means a
build there would be accepted once you stand on or beside it with the AP.
Materials are not judged here either; `GET /api/sheets?x=&y=` counts them.

- `unknown`: the sheet needs a deposit (the farm's grain soil) and you have
  not learned that tile's ground. A build there is refused `unknown_ground`,
  and this says exactly that and no more: it never tells you what is under a
  tile you have not seen. Walk within 4 tiles of it and ask again.
- `refused`: every other tile, grouped under build's own refusal code —
  `terrain_not_buildable`, `river_not_buildable` or `river_required`,
  `resource_required` (you know the ground lacks the deposit),
  `tile_occupied`, `improvement_exists`, `power_line_exists`,
  `others_building` (another citizen's building or construction stands
  there), `road_under_building` (a road or path and a building never share
  a tile), `pole_on_building` (a pole, a sheet with "pole": true, and a
  building never share a tile), `permission_denied` (another citizen's
  land), `off_the_map`.
- `why`: for each code in `refused`, and for `unknown_ground` when `unknown`
  is not empty, the sentence build refuses with — word for word, for the
  first tile listed under that code (the sentences name a tile, and a terrain
  or a sheet, so one per code is an example, not a template).
- `why_here`: the same codes and the same sentences, said of "this tile"
  instead of naming one, so you can show them for any tile under the code:
  `(x, y)` at the start of a sentence becomes "This tile", " at (x, y)"
  becomes " here", and anywhere else it is "this tile". A sentence that names
  no tile is unchanged.
- `box` is the box actually judged, `[x0, y0, x1, y1]` inclusive, clipped to
  the map: near an edge it is smaller than you asked for, and that is how you
  can tell. A box with no tile of the map in it is `400 off_the_map`.
- Your queue counts: a build you have already queued holds its slot here as it
  does in the next build you send.

A problem with the sheet itself is one refusal for the whole request, with the
code and sentence a build would get: `unknown_sheet`, `not_buildable` (a
thing or a recipe, or a sheet whose `build` is null in `/api/sheets`: the
settler is granted at landing and is never built),
`sheet_unavailable` (awaiting a law, in research, or not until turn N).
`r` outside 1 to 16 is `400 bad_radius`; a missing or non-whole `sheet`, `x`,
`y` or `r` is `400 bad_request`; before you have landed it is `409
not_landed`. It writes nothing and counts against the shared 600 reads an hour.

**`gather` is judged on what you know.** A gather queued on a tile you have not
learned is refused `unknown_ground` ("You do not know this ground yet: come within 4
tiles of it, or stand on it"): the submission cannot tell you what is
there, because that would make a refusal into a survey. `no_deposit` means
*you* know there is nothing, or that you last saw it empty. Resolution is still
the world's: what actually comes out of the ground is decided when the row
runs, against the real tile, and the row's `record` says what you got.
A forest away from water shows its wood from anywhere — the generator places no
other deposit there (see "What the map tells you about the ground" above). The kind is settled; the quantity is not.
