# How a Hesper citizen finds a path

The figures for the journal entry *A lake is a hole in the map* (2026-09-09),
and the code that draws them.

    python research/hesper/pathfinding/figures.py

Writes three PNGs into `site/public/images/`:

| file | what it shows |
|---|---|
| `hesper-path-live-2026-09-09.png` | the live world at turn 5: (257, 253) → (256, 252), the L-shape the play page built (5 movement points) against the cheapest path (3) |
| `hesper-path-lake-2026-09-09.png` | a lake between start and goal, and the path the search proves round it |
| `hesper-path-frontier-2026-09-09.png` | the same search with every settled tile numbered in settle order |

## Where the numbers come from

- **Terrain for figure 1 is the live world**, fetched from the public
  `GET /api/tiles?x=254&y=250&w=7&h=6` on hesper.untilnextsession.com. No key,
  no database read. The turn number printed on the figure is the one the API
  reported at fetch time.
- **Costs** are read from `hesper/vocab/v1.json`, the same file the engine
  reads — plain 1, forest 2, hill 3, road 1, water `null` (impassable) — rather
  than typed into this script.
- **Colours** are `hesper/assets/palette.json` through
  `hesper.laws.terrain.render.TERRAIN_COLOURS`, so a figure here and the map on
  the site are the same world.
- **Every path drawn is the engine's**: `hesper.laws.movement.pathfind
  .find_path`, the function `return_home` calls.

## The one thing computed twice, and the guard on it

`find_path` returns a path and nothing else — it does not report the order in
which tiles were settled, and that order is the whole subject of figure 3. So
the script runs its own Dijkstra with the same cost table and the same four-way
adjacency to get the settle order, and then **asserts that its own path costs
what the engine's path costs**. If the illustration ever drifts from the thing
it illustrates, the script fails rather than drawing a lie.

## The live case, reproduced

From the fetch on 2026-09-09 (turn 5):

```
        254     255     256     257
  250   hill    hill    hill    plain
  251   plain   plain   plain   plain
  252   plain   forest  forest  plain
  253   plain   plain   hill    plain
```

Start (257, 253) plain, goal (256, 252) forest.

- x first, then y: (256, 253) hill **3** + (256, 252) forest **2** = **5**
- y first, then x: (257, 252) plain **1** + (256, 252) forest **2** = **3**

Both are two steps. One costs 67 % more. That is H59.
