# Grid ↔ sampler coordinate conventions

Established by review-01 §3. One convention for grid-attached layers, one
explicitly separate domain for the terrain heightmap.

## Grid space

- Grid coordinates are continuous; **integer coordinates are cell centers**.
- World mapping: `GridData.transform` (`OffsetScaleTransform2D`) maps grid
  `(x, y)` to world `(x·scale + offset, y·scale + offset)`; `computeScale`
  places cell centers at tile centers (offset = tileSize/2).

## GridDataLayer texels (the canonical mapping)

A `GridDataLayer` with `resolution = r` over a `w × h` grid has a `w·r × h·r`
sampler. The mapping is the **plain ratio**, defined in
`grid/coords/grid_to_texel.js` / `texel_to_grid.js`:

```
texel = grid · r        grid = texel / r
```

Integer texel coordinates are texel centers (this is what
`Sampler2D.sampleChannelBilinear` expects), so for `r = 1` the mapping is the
identity and texel t coincides with cell t. For `r > 1`, texel `t` is centered
at grid `t / r`; cell centers land exactly on every r-th texel.

Do **not** use corner-aligned mappings (`x / (w−1) · (sw−1)`) for layers — they
disagree with the ratio mapping by up to one cell at the far edge and divide by
zero on 1-wide grids.

## Out-of-bounds policy

- **Reads** (sampling filters): clamp — `Sampler2D` bilinear/bicubic clamp
  internally; sampling off-map returns the edge value.
- **Matches** (`CellMatcher` over a layer): out-of-bounds **fails the match**.
  A pattern probe that falls off the map matches nothing, it does not match
  against border cells.
- **Writes** (cell actions): out-of-bounds writes are **discarded** after an
  explicit bounds check (never index-arithmetic into a flat array — negative
  x wraps into the previous row).

## Terrain heightmap and splat — separate domains

- The terrain **heightmap** (`Terrain.samplerHeight`) is the terrain renderer's
  vertex-aligned domain. `ContinuousGridCellActionSetTerrainHeight` maps grid
  corners to sampler corners (`x/(w−1) · (sw−1)`) to match the terrain mesh —
  this is intentional and must stay consistent with the terrain renderer, not
  with grid layers.
- The terrain **splat map** is coverage data and uses the ratio mapping
  (`splat_texel = grid · splatRes/gridRes`), see `MarkerNodeActionPaintTerrain`.
- Resampling between two samplers of different resolutions (e.g.
  `ThemeEngine.tResample`) uses texel-center mapping:
  `source = (target + 0.5) · ratio − 0.5`.
