# Creative edge continuations for terrain

The original edge-law table and world mathematics are Lars Rocha's work.
This experiment keeps that table intact and invents a useful continuation; it
does not claim to predict further Rule 30 measurements. Original mode remains
the default. The music lab's vendored libraries are deliberately not updated.

## Try it locally

Open `examples/atlas.html` and keep seed **k2v-1 k2em**.

1. Pick a place or click the globe. In **Edge sequence terrain lab**, choose
   **Bursty continuation**. The map regenerates but your selected tile/view stay.
2. Read the sequence strip: level 48 begins at **114**, then the invented tail
   follows. Gray and gold lines compare original and active terrain across the
   same east–west slice. The blue horizontal line marks water height 0.37.
3. Press **Compare Original here**, then **Try bursty here**, to switch back and
   forth. A coastline, biome, or settlement can change at the same address.
4. Click **Land here** and walk on the active terrain. Return to orbit to try
   Smooth or Rhythmic. Changing a recipe while walking exits to the globe.
5. Set blend to **0%** to recover the original height field exactly. At 100%,
   the new weighted terrain replaces the baseline height field.

The comparison means the same **coordinates**, not identical geography across
different recipes. Names remain address-based. An original cottage can disappear
if its tile becomes water or unsuitable high ground in the active world.

## Reusable sequence API

```python
import nedgelaw as ned

sequence = ned.EdgeSequence("k2v-1 k2em", mode="bursty")
sequence.value(48)                 # 114, the original table entry
sequence.value(49)                 # 117, a creative continuation for this seed
sequence.rows(49, 12)
sequence.edge(5 * 2**90 - 1)        # same result as sequence.value(90)
ned.extended_edge(2**90 - 1, mode="bursty", seed="k2v-1 k2em")
```

```js
const ned = require("./js");
const sequence = new ned.EdgeSequence("k2v-1 k2em", "bursty");
console.table(sequence.rows(49, 12));
sequence.edge(5n * (1n << 90n) - 1n);
ned.extendedEdge((1n << 90n) - 1n, "bursty", "k2v-1 k2em");
```

For each mode, levels **0–48** are copied from the original table without changes.
Each later level adds a positive integer, so the sequence remains increasing.
The generation lookup still selects by `v2(g + 1)`, retaining identical results
for generations with the same level. Python accepts arbitrary-size nonnegative
integers; JavaScript accepts safe integer Numbers or BigInts. Levels stop at
4096 to bound work/memory; `rows` returns at most 256 records at once.

- **Smooth:** repeat increments `2, 2, 3, 2, 3`. The continuation itself is seed
  independent; the terrain's spatial noise still depends on the world seed.
- **Rhythmic:** a small-step motif plus infrequent binary-level bonuses. The
  seed selects one of 16 phases; it does not guarantee a unique tail per seed.
- **Bursty:** an indexed hash selects from the 48 observed positive increments
  between original table entries. This reuses the empirical step distribution,
  not its exact order. Occasional larger steps can dominate particular scales.

Values are cached in an `EdgeSequence` instance. Its first lookup at a distant
level computes the missing increments, so that cost is linear in the number of
missing levels, not in the generation number. Later lookups reuse the cache.
These rules are versioned conceptually as `edge-extension-v1`.

## How the tail affects ordinary terrain

A small map's coordinate valuations never reach level 49. Simply extending the
lookup would therefore do nothing visible. The explicit bridge in this experiment
is to treat **increments at levels 49–54 as six spatial octave weights**:

```text
weight(octave) = 0.5**octave * (0.55 + increment(49 + octave) / 3)
frequency(octave) = 0.008 * 2**octave
signal = 2 * indexed_value_noise(x * frequency, y * frequency) - 1
ridge = 1 - 2 * abs(signal)
mixture = weighted_mean(0.7 * signal + 0.3 * ridge)
sculpted = clamp(0.52 + 1.35 * mixture, 0, 1)
height = clamp(original + strength * (sculpted - original), 0, 1)
```

The noise uses the existing indexed helpers and world seed. There is no new
random state or simulation history. This is a designed use of the sequence,
not a derivation of terrain heights from the original automaton. Modes can differ
subtly or strongly depending on the chosen seed's first six synthetic steps.

```python
world = ned.World("k2v-1 k2em", edge_mode="bursty", edge_strength=0.65)
ground = ned.Landscape("k2v-1 k2em", edge_mode="bursty", edge_strength=0.65)
assert world.height(196, 58) == ground.height_at(196 * 16, 58 * 16)
```

```js
const recipe = {edgeMode: "bursty", edgeStrength: 0.65};
const world = new ned.World("k2v-1 k2em", recipe);
const ground = new ned.Landscape("k2v-1 k2em", recipe);
```

Use the same seed, mode, strength, and implementation version to reproduce a
world. Tree/village eligibility reads the active height field, not the old one.
The block surface, props, and collision data consequently agree with the globe.
The atlas remains a finite rectangular patch wrapped onto a sphere; this does
not solve its seams/poles, create caves, or add erosion/river integration.

## First useful measurements

Full 512×256 `k2v-1 k2em` tile window, strength 0.65, normalized height 0–1:

| Recipe | Land fraction | Mean absolute change from original | Mean east-neighbor height difference |
| --- | ---: | ---: | ---: |
| Original | 78.90% | 0 | 0.06921 |
| Smooth | 69.48% | 0.18708 | 0.02453 |
| Rhythmic | 71.52% | 0.18970 | 0.02449 |
| Bursty | 68.79% | 0.19559 | 0.02454 |

None of the three blended experiments hit height 0 or 1 in this sample. The
gentler east-neighbor differences suggest useful broader landforms for walking,
but this is one sample—not a guarantee of navigability or better-looking worlds.
The smoothing mostly comes from blending the original field with the broader
spatial construction; differences among modes come from the continuation weights.

## Checks and next decisions

Automated checks cover original-table/default-output preservation, monotonic
extensions, large integer addressing, Python/JS parity, active biome/prop
consistency, comparison profiles, and landscape anchors/chunk boundaries.
Browser visual QA has not been performed; the next useful step is your A/B walk.

If this feels useful, later options include exposing the six weights directly,
using the tail to set terrace spacing, or adding a second continuation stream
for vegetation. Keep these optional rather than changing Original silently.


## Ranges & rivers (`edge_mode="ranges"`)

Not an edge-table continuation: a terrain law assembled from the RangeBlends
primitives already in the library, evaluated per tile.

| Stage | Rule | Locality |
|---|---|---|
| Bend | two `fbm2` fields move the tile up to ±22 tiles before anything else looks at it | the tile reads its own warp |
| Spines | `buildRidge` in 2-D: peaks at `k·2^v − 1`, `v = 2…8`, amplitude `CUM[v]/CUM[8]`, half-width `WIDTH`, `squareRamp` slide, `fbm1` wander | three candidate ridges per scale |
| Presence | an `fbm1` signal gates each ridge with `smooth` tapers (floor 0.28) | same tile, same scale |
| Spurs | the same family transposed, `v = 3…6`, floor 0.42, ×0.62 | three candidates per scale |
| Deepening | `best + RELIEF·(best² − best)` | pointwise |
| Basins | a slow bowl field lowers plains by up to 0.17, weighted `(1 − ridge)·roll` | one `fbm2` |
| Knee | above 0.8 heights ease toward 1 (`1 − e^{−t}`), never clamp | pointwise |
| Rivers | trunk per 64 tiles, tributaries on rows with `v2(j+1) ≥ 1`, distance in channel widths carves valley then water | three trunks, three rows |
| Polar taper | ridge amplitude fades above 72° latitude | pointwise |

Ridge and river identities repeat every 512 tiles and the continent field is
read on a cylinder with a 32-column stitch, so the atlas window has no seam in
this recipe. The original world keeps its seam; `edge_strength=0` reproduces it
exactly. Stream names carry a `-v2` suffix: changing the law means a new suffix,
never a silent change to an old one.


## RangeBlends range (`edge_mode="range"`)

range.html's third panel, the blend, read across the whole atlas. The
original atlas world added `run_at(x) / 43` as its ridge: the bare ruler comb,
one tile wide at every divisible column, so all the shape came from the noise
and the ladder only added needles. This recipe reads `buildRidge`'s profile
instead, and the numbers are range.html's own:

| Stage | Rule |
|---|---|
| Skeleton | a smoothstep bump at every column `k·2^v − 1`, height `CUM[v]/CUM[vlim]`, half-width `WIDTH·2^(v−1)`, the max over `v = 1…vlim` (`vlim = log2(width)`) |
| Massif | every peak slides by `squareRamp(k, v) · massif · 2^v`; the slider is range.html's, default 0.45 |
| Deepening | `h + RELIEF·(h² − h)` |
| Weather | `0.15·(f − 0.5) + 0.13·(d − 0.5)·(0.35 + ridge)`, `f` six octaves at 0.018, `d` four at 0.24, now two-dimensional |
| Bend | one `fbm2` field carries the tile up to ±22 columns before it reads the profile, so ranges arc |
| Continents | the original world's land field: `0.14 + 0.36·roll + 0.70·ridge·(0.25 + 0.75·roll)`, so a range stands on land, makes islands at the coast, and sinks to a reef at sea |
| Knee | above 0.8 heights ease toward 1, never clamp |
| Polar taper | ridge amplitude fades above 80% of the way to a pole |

Peak identities repeat every `width` columns and the peak at `−1` is the peak at
`width − 1`, so the profile closes around the globe; the noise is read on the
same cylinder as the ranges recipe. `ridge_at(g, vlim, massif, wrap=0)` is
`buildRidge` at one column and is compared with `~/pytools/rangeblends.ridge`
peak for peak, interior columns only: the window sweeps to its right edge
before applying the slide, so a peak just outside can slide back in and be
missed by the window alone. On one row of `k2v-1 k2em` the biggest step
between neighbouring tiles falls from 0.48 to 0.07, and ridges above 0.6 run
tens of tiles wide instead of a dozen.
