# Showcase: Procedural map

A fantasy map that draws itself: a 10×10 parchment grid charts in row by row, terrain clusters into
named forests, mountains, villages, and water bodies, and a single road is autotiled from a start
marker to a destination arrow with a dead-end spur or two. Every notable place gets a seeded name —
settlements, regions, and battle sites — lettered onto the parchment in ink. Then a red dashed
**adventure line** traces the road, retracting cleanly out of each dead-end it explores — losing a
battle there (a shield that crossfades to a cross), arriving at a campfire, and reseeding a fresh,
different map. It is the odd one out of the five: not a live simulation but a **seeded procedural
generator**, and the repo's **first raster pipeline** — the one showcase that decodes image assets
through Skia and the one that renders **text**.

<Aside type="note" title="Read Foundations first">
This guide builds on [Foundations](/guides/foundations) — the contract, the shared surface, the
palette, and the engine-liftable pure-module discipline. The map art is the CC0 Kenney Cartography
pack.
</Aside>

## What the engine provides here

A small footprint: `GameLoop` (`@flare-engine/core`) for the pause-correct clock, `SeededRNG`
(`@flare-engine/math`) for reproducible generation — driving both the geometry **and** the place-name
generator — and the shared post-FX surface, used here only for its WebGL context lifecycle +
context-loss resilience, **not** its bloom. The full CanvasKit build also decodes IM Fell English (the
antique 1600s "old map" serif) from a `.woff2` into a `Typeface`/`Font` for the labels. **Everything
that makes it a map is yours**: the road generator, the
autotiler, the terrain clustering, the seeded naming table, the adventure-line traversal, and the
raster tile pipeline.

## The first raster pipeline

Every other showcase is pure vector (`drawCircle`/`drawLine`). The Map decodes PNGs. The Kenney tiles
are fetched same-origin from `public/cartography/` and decoded **once** via
`kit.MakeImageFromEncoded` into a **module-scoped** `SkImage` cache:

```ts
const bytes = new Uint8Array(await (await fetch(url)).arrayBuffer());
const img = kit.MakeImageFromEncoded(bytes); // lazy — survives context loss, one decode per PNG
```

The cache is deliberately never per-instance: a governor re-create or dev restart reuses the
already-decoded textures (deleting one would pull a GPU texture out from under the next instance).
Because `create()` is synchronous but the fetch is async, the renderer paints the dark table + a
parchment bed until the cache warms, then reveals.

A single `drawTile` helper places any 128px sprite into a cell with **rotation and mirroring** — the
road/arrow tiles rotate (0/90/180/270°), every terrain sprite only mirrors:

```ts
sc.save();
sc.translate(cx, cy);
if (qt) sc.rotate(qt * 90, 0, 0);
if (mirror) sc.scale(-1, 1);
sc.drawImageRectOptions(img, src, dst, FilterMode.Linear, MipmapMode.None, paint);
sc.restore();
```

<Aside type="note" title="Opaque, not additive">
The parchment map is an **opaque** image, so — unlike the neon showcases — it would bloom out and
smear through the post-FX chain. The Map draws straight to the surface's **on-screen** canvas and
flushes: no composite, no bloom. Same GL lifecycle, different finish.
</Aside>

## Roads: carve, validate, autotile

`grid.ts` is the pure procgen core (bun-tested, zero-import but for the RNG). The road is carved by a
**biased self-avoiding walk** from a `pathEnd` start toward an `arrowHead` end. The endpoints are
placed on opposite edges **and** in opposite perpendicular thirds — a diagonal span — and a coverage
gate rejects any carve that touches fewer than **three of the four quadrants**, so the road crosses the
map instead of hugging one half. A simple-path rule (a step is only allowed if the new cell touches the
existing road at its parent only) keeps it from clumping into loops. Backtracking handles dead-ends;
1–2 short **spurs** are grafted onto interior cells as battle dead-ends. A flood-fill validator then
asserts the road is a single simple path (a tree: `edges === cells − 1`), rejecting degenerate maps.

The sprite for each road cell is chosen by **edge-mask autotiling** — a 4-bit neighbor mask
(N=1,E=2,S=4,W=8) indexes a frozen 16-entry table of `{sprite, rotation}`, so five base tiles cover
every junction:

```ts
// mask 5 (N|S) → straight r0 · 3 (N|E) → corner r0 · 7 (N|E|S) → split r0 · 15 → crossing · 1 (N) → end r0
const mask = (N ? 1 : 0) | (E ? 2 : 0) | (S ? 4 : 0) | (W ? 8 : 0);
const { name, qt } = MASK_TILE[mask];
```

Rotation is free, so the tiling is a **pure function of the boolean road grid** — automatically
deterministic. The end cell is overridden to `arrowHead`, rotated to point off the map edge.

## Terrain that clusters

Decorations are placed so they read as *regions*, not confetti. `grid.ts` seeds a few biome origins,
flood-grows each into a contiguous zone with a falloff, then fills by biome: forests self-attract,
villages hug the road (via a distance-to-road field), mountains stay contiguous. **Water** comes in two
forms. A **sea** grows as a solid cluster and renders as the Kenney wave texture recolored to ink via a
`SrcIn` blend — monochrome hatch lines on the parchment, no color, the way antique maps drew open
water. Separately, **1–2 lakes** are placed as discrete features: the 2:1 lake sprite drawn across two
cells at its natural aspect (no squish) and mirrored left/right. Road cells and the start/end clearance
are excluded, and ~half the board is left empty — so the map breathes. The largest few regions (the sea
always), plus both settlements and every battle site, are then **named**: a seeded syllable table
composes "Ashford", "Blackwood", "Frost Peaks", "The Cursed Keep", "Sea of Mor", drawn in the antique
IM Fell serif with a cream halo so they stay legible over terrain.

## The adventure line

At generation time a **DFS over the road tree** bakes an explicit ordered route: it detours into each
dead-end spur (recording a battle stop), backtracks, and descends the trunk to the end last. Because
consecutive route cells are adjacent, arc-length is just the step count, so the traveler's position is
a simple `d(t)` advanced by the pause-correct clock.

The line itself is drawn *not* from the raw Euler walk — that would overdraw itself on the way back out
of a spur — but from the **simple tree-path START→traveler**. A BFS parent pointer (rooted at START)
lets the renderer walk from whichever route endpoint is nearer to START, then stroke a final partial
segment out to the exact traveler point. As the traveler retreats from a dead-end, that "nearer"
endpoint slides back down the trunk, so the spur line **retracts behind it** — no self-overlap, and the
campfire view is the clean trunk with every explored dead-end erased. And the line **follows the road
art**: the tiles draw their centerline through each cell's center, so the line passes through every
cell center — arcing through a `pathCorner` tile (a quadratic forced through the center, a ~0.35·tile
sweep) and running straight everywhere else (including the straight arms of split/crossing junctions).
The curve model was calibrated against the actual Kenney pixels — sampled, it lands on a drawn road
edge **0%** of the time, staying dead-centre in the corridor instead of cutting corners. It is stroked with an animated
`PathEffect.MakeDash` — marching ants that keep crawling during the battle dwells. At each dead-end the
shield **crossfades** to a cross (the battle is always lost); at the end a campfire fades in and holds
three seconds before a hash-stepped seed generates the next, different map.

## Recap

<Steps>

1. The first raster pipeline: `MakeImageFromEncoded` + a module-scoped `SkImage` cache + a
   rotate/mirror `drawTile`, plus an IM Fell `.woff2`→`Font` for labels — all drawn opaque to the
   on-screen surface (no bloom).
2. A seeded self-avoiding-walk road with a flood-fill validator — one simple path + dead-end spurs.
3. Edge-mask autotiling — five sprites, a 16-entry table, a pure function of the road grid.
4. Clustered terrain (forests / mountains / villages / an ink-hatched sea / 1–2 mirrored lake sprites)
   with seeded place names, leaving the board breathing.
5. A retracting DFS adventure line (simple tree-path START→traveler) with a dashed reveal,
   crossfading dead-end battles, and a hash-stepped reseed.

</Steps>

No live simulation — a seeded generator and an asset pipeline instead. The road carver + autotiler +
the seeded naming table are the seed of a planned **`@flare-engine/procgen`** (its tile side already
ships as **`@flare-engine/tilemap`**, a generic Tiled/LDtk culled renderer). And the decode/cache +
`drawTile` + the `Font` label pass are exactly what the now-shipping **`@flare-engine/render-web`**
Skia backend provides — its `createPostFxSurface` / `createWebSurface` / `loadCanvasKit` are the
published form of the CanvasKit surface this hero still runs its own, more context-loss-resilient
precursor of.

<Aside type="note">
See it live in the [hero](/) (pick "Map") and press the backtick key to open the dev panel and reseed
or retune the journey. Next: [Logo reveal](/guides/logo) — the light showcase. Back to the
[Foundations](/guides/foundations) overview, the other showcases, or the
[package catalog](/reference/packages).
</Aside>
