# SimplexNoise for MoonBit

[English](./README.md) | [简体中文](./README_zh_CN.md)

[![Build Status](https://img.shields.io/github/actions/workflow/status/ZSeanYves/SimplexNoise/simplexnoise-ci.yml)](https://github.com/ZSeanYves/SimplexNoise/actions/workflows/simplexnoise-ci.yml)
[![License](https://img.shields.io/github/license/ZSeanYves/SimplexNoise)](./LICENSE)

SimplexNoise 0.2.0 is a deterministic procedural-noise library for MoonBit. It
provides Classic Simplex in 2D/3D/4D, FastNoiseLite-compatible OpenSimplex2 and
OpenSimplex2S in 2D/3D, validated fractal combiners, stateless domain warping,
strictly periodic 2D noise, composable modifiers, allocation-free grid filling,
and optional PNG rendering.

The project is tested with Moon 0.1.20260713 on `wasm`, `wasm-gc`, `js`, and
`native`.

| OpenSimplex2S | fBm |
| --- | --- |
| ![OpenSimplex2S grayscale example](./examples/open-simplex2s.png) | ![fBm color example](./examples/fbm.png) |

## Install

```bash
moon add ZSeanYves/SimplexNoise
```

Import only the packages you need:

```moonbit
import {
  "ZSeanYves/SimplexNoise/core",
  "ZSeanYves/SimplexNoise/fractal",
}
```

## Sampling

```moonbit
let classic = @core.Simplex::new(42U)
let open2 = @core.OpenSimplex2::new(42U)
let open2s = @core.OpenSimplex2S::new(42U)

let a = classic.sample4(0.1, 0.2, 0.3, 0.4)
let b = open2.sample3(0.1, 0.2, 0.3)
let c = open2s.sample2(0.1, 0.2)
```

Sources are immutable and should be constructed once and reused.

All samplers accept finite coordinates in `[-100000000, 100000000]`. NaN,
infinity, and coordinates outside that range return NaN. This avoids
backend-dependent integer saturation at the lattice conversion boundary.
The reference families and external corpus are documented in
[the numerical contracts](./docs/reference-validation.md).

For dense fields, fill a caller-owned buffer without per-sample allocation:

```moonbit
let pixels = FixedArray::make(256 * 256, 0.0)
@core.fill2(open2s, pixels, 256, 256, step_x=0.01, step_y=0.01).unwrap()
```

## Fractals and warping

```moonbit
let config = @fractal.FbmConfig::new(
  octaves=6,
  persistence=0.5,
  lacunarity=2.0,
  frequency=0.01,
).unwrap()

let source = @core.OpenSimplex2S::new(42U)
let fbm = @fractal.Fbm::new(source, config)
let terrain = fbm.sample2(128.0, 64.0)

let warp_config = @fractal.WarpConfig::new(
  strength=2.0,
  frequency=0.02,
  limit=1.5,
  mode=Progressive,
  octaves=3,
).unwrap()
let warped = @fractal.DomainWarp2::new(
  source,
  @core.OpenSimplex2S::new(73U),
  warp_config,
)
let warped_value = warped.sample2(128.0, 64.0)
```

Each fBm/Billow/Ridged octave is recreated with `seed + octave` through the
`Seedable` trait. `Ridged` implements feedback-weighted RidgedMulti behavior;
`Ridged::with_attenuation` changes its default attenuation of `2.0`.
`DomainWarp2` and `DomainWarp3` support `Single`, `Progressive`, and
`Independent` modes.

## Modifiers

The `fractal` package provides `ScaleBias`, `Clamp`, `AddNoise`,
`MultiplyNoise`, `Blend`, `Select`, `Curve`, `Terrace`, and dimension-specific
`Transform2`/`Transform3`/`Transform4`. Constructors validate finite parameters,
bounds, and ordered control points before the sampling hot path.

## Strict 2D tiling

`Tileable2` embeds two periodic coordinates into four dimensions. Its source
must implement `Noise4`; Classic Simplex is the built-in choice.

```moonbit
let tiled = @fractal.Tileable2::new(
  @core.Simplex::new(9U),
  width=256.0,
  height=256.0,
).unwrap()

let left = tiled.sample2(0.0, 32.0)
let right = tiled.sample2(256.0, 32.0)
```

## PNG demo

The renderer is isolated from the algorithm packages. Run the demo with an
optional output prefix, image size, and seed:

```bash
moon run src/cmd/noise-demo --target native -- noise-demo 256 42
```

This writes `noise-demo-simplex.png` and `noise-demo-fbm.png`.
Malformed sizes/seeds and sizes outside `1..8192` terminate with a non-zero
exit status. PNG encoder and filesystem errors are propagated.

## Package layout

```text
src/core/            sources, Seedable, coordinate contract, fill2/fill3
src/fractal/         fractals, 2D/3D warp, tiling, modifiers/transforms
src/render/          grayscale/hue PNG encoding and file output
src/cmd/noise-demo/  runnable image example
```

`core` has no third-party dependency. Image and filesystem dependencies are
confined to `render`.

## Migration from 0.1.x

| 0.1.x | 0.2.0 |
| --- | --- |
| arrays of coordinates, gradients, and permutation tables | immutable source objects with fixed-dimensional methods |
| `create2d` / `simplex2d` | `@core.Simplex::new(seed).sample2(x, y)` |
| seven positional fBm parameters | validated `FbmConfig` plus `Fbm` |
| every octave reused one source | independently seeded octave sources |
| global `new_maxwarp` state | immutable `WarpConfig` plus `DomainWarp2` |
| approximate 2D/3D/4D tile helpers | strict 2D `Tileable2` contract |
| image functions in the root package | `@render.render_png` / `write_png` |
| 3D/4D batch slice helpers | removed; capture z/w in a render callback and loop explicitly |

The old APIs were removed intentionally. See
[the captured 0.1.1 baseline](./docs/legacy-baseline.md).
Classic 2D/3D values also change because 0.2.0 fixes the intermediate-corner
unskew signs; Classic 2D now uses Gustavson's 12-entry `grad3` projection and
normalization factor `70`. Fractal values change because octaves now use
independent seeds and Ridged now includes feedback attenuation.

## Verify

```bash
moon update
moon fmt --check
moon check --target all --deny-warn --warn-list +73
moon test --target all
moon test --target native --enable-coverage
moon coverage report -f summary
moon bench src/core src/fractal --target native --release
moon package --frozen
```

OpenSimplex reference tests use vectors generated from FastNoiseLite 1.1.1.
Derived code and attribution are documented in [NOTICE](./NOTICE).
The current native results are recorded in
[the 0.2.0 benchmark snapshot](./docs/performance-0.2.0.md).

## License

Apache-2.0. See [LICENSE](./LICENSE).
