# How it works

A short, honest walk-through of the generator, so a stranger can see it is real
(code-driven, seeded, reproducible) and not hand-placed. Everything runs from a
single HTML file plus `css/style.css` and `js/*.js`. No build step, no server:
double-click `index.html`.

## 1. The projection formula (true 2:1 isometric)

World coordinates are `(x, y)` on the grid plane and `z` for height. To pick an
azimuth (0/90/180/270) we apply a rigid 90-degree rotation to the world first:

    rotateXY(x, y, az):  0 -> (x, y) | 90 -> (-y, x) | 180 -> (-x, -y) | 270 -> (y, -x)

Then the rotated point `(rx, ry)` is written to the screen with `js/lib.js`:

    sx = (rx - ry) * A
    sy = (rx + ry) * B - z * C

with `A = 30`, `B = 15`, `C = 22`. Because `A = 2*B`, the plan rhombus of one
unit is `2A` wide by `2B` tall = 60 x 30, i.e. **width : height = 2 : 1**, the
classic 2:1 isometric tile. `C` is the screen height of one world-height unit
(a storey).

A unit cube is drawn as three faces whose corners are all taken from the *same*
list of projected corners (`rotRect` -> `buildingFaces` in `js/render.js`):

* top face: the four eaves-height corners,
* `+ry` face: the two corners with the largest `ry`, extruded from ground to eaves,
* `+rx` face: the two corners with the largest `rx`, likewise.

Because every face edge is the same two projected points, the faces meet exactly
- no seams. The outer outline is the familiar hexagon.

## 2. The depth-sort rule (painter's algorithm, actually correct)

Draw order is back-to-front. Each building gets a key from its rotated centre:

    depthKey = rx + ry        (larger = nearer the camera, lower on screen)

sorted ascending (`order` in `buildScene`). A tall building that is *behind*
another therefore has a smaller key and is painted first, so the nearer one
always covers it. Rotating the camera never breaks this, because a rotation is a
**rigid rotation of the identical scene** - the same key rule applies in the
rotated space, so occlusion is preserved at all four azimuths. Buildings of the
same `rx + ry` are drawn in a stable order (by id).

Water, roads and the ground slab are painted before any building; cranes, the
market roof and the lighthouse after, in the same group.

## 3. The single light rule

One light direction shades every face from the same table (`faceLightness`):

    top  : 0.35 + 0.72 * daylight
    +ry  : 0.30 + 0.62 * daylight
    +rx  : 0.26 + 0.55 * daylight

`daylight` runs from 0.22 (deep night) to 1.0 (noon) from the time-of-day
control. Top is always brightest, one side mid, one side darkest, so the whole
scene reads as one lit scene and no face is a flat unlit grey (the darkest side
stays at ~0.33 of its base lightness even at night, and keeps its hue). Window
glass is dark by day and glows warm only after dusk.

## 4. The seeded pipeline (reproducible)

Everything derives from the seed string, which lives in the URL hash
(`#s=...`) so a view can be shared and reopened identically:

1. `hashSeed` (FNV-1a style) turns the seed string into a uint32.
2. `mulberry32` (`js/lib.js`) is a tiny hand-written PRNG - **no `Math.random()`
   anywhere**. `makeRng(seed)` wraps it with `int/range/pick/chance/float`.
3. `valueNoise(seed)` gives a smooth deterministic field (lattice hash per
   corner, smoothstep interpolated, 4 octaves of fractal sum).
4. `generateScene` (`js/gen.js`):
   * an elevation field = `valueNoise` + a coastline gradient puts water in the
     far corner (a harbour);
   * one vertical + one horizontal road split the district;
   * coast cells become beach or quay by a seeded noise arc;
   * a market hall, a crane yard and a breakwater + lighthouse are placed;
   * the rest becomes warehouses (near water) or terraced housing, merged into
     blocks along each row;
   * each building is composed from a parametric vocabulary - base, eaves
     height, roof (flat/pitched/hipped/ridged), ridge axis, hue, window
     counts, and seeded chimney/dormer/sign/door - all from the stream.
5. Window "lit" flags come from the stream too, so which windows glow at night
   never flickers.

Two runs with the same seed produce byte-identical geometry; a different seed
produces a different district. (Verified: same-seed signatures match, different
seeds differ.)

## 5. Geometry reuse and node count

* Big coplanar regions (water, roads, beach, quay, the grid overlay) are a
  **single `<path>`** made of many overlapping unit quads - overlaps are
  invisible, so a whole region is one node.
* The lighthouse is one `<symbol>` reused via a single `<use>`; cranes are drawn
  from a small shared vocabulary.
* Windows are two compound `<path>`s per building (lit and unlit panes), each
  holding many quad sub-paths, so hundreds of windows cost only a couple of
  nodes.
* Detail is level-of-detail gated: silhouettes only when far, + windows and
  doors mid-zoom, + chimneys/dormers/signs when close. The detail layers are
  rebuilt on zoom thresholds, so the node count tracks what you actually see.

Measured DOM nodes in the `#world` subtree:

| view            | `#world` nodes | whole `#scene` |
|-----------------|----------------|----------------|
| overview (far)  | 711            | 756            |
| deep zoom (max) | 1205           | 1250           |

Comfortably under the ~4000 budget, and it scales with what is on screen rather
than being fixed.

## 6. Interaction

Pan/zoom is a single `translate scale` transform on `#view` (no per-object
layout). Zoom is about the cursor; rotate re-sorts and re-centres. The minimap
is a top-down schematic of the generator cells; its viewport rectangle is the
current view un-rotated into grid space, and clicking jumps there. Reveal-grid
draws the raw 18x18 generator cells so you can check the layout is procedural.
