ludic/examples/library/noise.ludic
Orkuncakilkaya a4f1494a04
All checks were successful
docs / build-and-deploy (push) Successful in 2s
feat(stdlib): add Noise.* — deterministic fixed-point procedural noise (#3)
A Noise.* namespace for procedural generation, implemented entirely in Q16.16
fixed point over an integer permutation hash so a seed reproduces the exact same
field on every platform and run (native/headless/wasm) — the determinism edge
over float noise that drifts across CPUs.

  - value2 / perlin2 / simplex2  — value, gradient, and simplex noise -> [-1,1]
  - fbm2(x,y,seed,octaves)       — fractal Brownian motion (octaves of simplex)
  - cellular2 / cellular2_id     — Worley F1 distance + nearest-cell id
  - unit(n)                      — remap [-1,1] -> [0,1]

Covers issue phases 1–2 fully plus cellular from phase 3; domain warp, ridged/
billow, and sample1/sample3 remain as follow-ups. Pure integer IR, C-free;
cellular/fbm reuse the math prelude's fx_sqrt.

- examples/library/noise.ludic: asserts the invariants a fixed-point generator
  must hold (Perlin == 0 at lattice points, every sampler within [-1,1],
  reproducibility, seed sensitivity, non-negative cellular distance). Wired into
  `x test` (now 52 passed).
- docs: a new Noise section + per-symbol pages; inventory and coverage pass.
- seed regenerated; `x bootstrap-cfree` fixpoint holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 21:50:50 +03:00

43 lines
2 KiB
Text

# noise.ludic — Noise.* determinism and structural invariants. Fixed-point noise
# can't be checked against a float reference bit-for-bit (that difference is the
# whole point), so we assert the properties that must hold: Perlin is exactly 0
# at integer lattice points, every sampler stays within [-1,1] (±65536 fixed),
# the same (x,y,seed) always reproduces, the seed changes the field, and cellular
# distance is non-negative. Running it prints: 1 2 3 4 5 6 7 8 9 10 11
program Noise {
entry {
let seed = 1337
# gradient noise is exactly 0 at integer lattice points (zero offset vectors)
if Noise.perlin2(fixed(0), fixed(0), seed) == 0 { print(1) }
if Noise.perlin2(fixed(3), fixed(5), seed) == 0 { print(2) }
# determinism: identical inputs -> identical output
let a = Noise.simplex2(20000, 100, seed)
let b = Noise.simplex2(20000, 100, seed)
if a == b { print(3) }
# range: every sampler stays within [-1, 1] (comparisons are in the fixed domain)
let lo = fixed(0) - fixed(1)
let hi = fixed(1)
let p = Noise.perlin2(12345, 54321, seed)
if p >= lo and p <= hi { print(4) }
let s = Noise.simplex2(12345, 54321, seed)
if s >= lo and s <= hi { print(5) }
let v = Noise.value2(12345, 54321, seed)
if v >= lo and v <= hi { print(6) }
let f = Noise.fbm2(12345, 54321, seed, 5)
if f >= lo and f <= hi { print(7) }
# unit() remaps [-1,1] -> [0,1]: endpoints and midpoint (all in the fixed domain)
let half = fixed(1) / fixed(2)
if Noise.unit(fixed(1)) == fixed(1) and Noise.unit(fixed(0) - fixed(1)) == fixed(0) and Noise.unit(fixed(0)) == half { print(8) }
# the seed selects the world: different seeds -> different fields
if Noise.value2(12345, 54321, 1) != Noise.value2(12345, 54321, 2) { print(9) }
# cellular F1 distance is non-negative; the cell id is deterministic
if Noise.cellular2(12345, 54321, seed) >= 0 { print(10) }
if Noise.cellular2_id(12345, 54321, seed) == Noise.cellular2_id(12345, 54321, seed) { print(11) }
}
}