// ============================================================
//  CUSTOM SATIN — PROGRAMMABLE COLUMNS WITH `satin @fn`
//  `satin @reporter` replaces the built-in zigzag with a procedure
//  you write. It is queried once per stitch pair as the engine walks
//  the column spine and returns FIVE numbers:
//      [advance, leftw, rightw, leftlag, rightlag]   (all mm)
//  advance — how far to step the cursor forward (dynamic density, > 0)
//  leftw/rightw — half-widths of the two rails (asymmetry is free)
//  leftlag/rightlag — how far each rail endpoint sits AHEAD (+) or
//      BEHIND (−) the cursor along the spine. Independent lags let a
//      stitch rake steeply enough to cross its own line — woven satin —
//      while the cursor only ever moves forward.
//  Inputs are (t, s, i, u): cursor arc-length mm, normalized position
//  0..1, 0-based pair index, local spine heading. It sits where the
//  built-in generator sits, so pullcomp / underlay / density / DST all
//  still apply. `satin 4` is exactly `satin @[0.4, 2, 2, 0, 0]`.
// ============================================================

let len   = 46     // [24:1:60] column length, mm
let rake  = 0.9    // [0.2:0.05:2] crosshatch rake — bigger crosses harder
let amp   = 1.4    // [0:0.1:3] ripple-edge wobble amplitude, mm
let lean  = 1.3    // [0:0.1:3] asymmetric column extra width, mm

seed 3
fabric "canvas        // stable ground — dense crossings want it
maxdensity 5          // the crossings ARE the look; allow them knowingly

// ── 1. LEAF TAPER — end-relative width via `s` ──────────────────────
// sin(s*180) is 0 at both tips and 2.2 mm at the middle: a pointed leaf.
// A plain perpendicular bite (lags 0), shaped only by position.
def leaf(t, s, i, u) [
  let w = sin(s * 180) * 2.2
  return [0.45, w, w, 0, 0]
]

// ── 2. WOVEN CROSSHATCH — alternating rake via the pair index `i` ───
// even pairs lean "/", odd pairs lean "\" (the lags swap sign), so
// successive stitches cross. No state held — `i` alone flips the rake.
def crosshatch(t, s, i, u) [
  if mod(i, 2) == 0 [ return [0.4, 2, 2, -rake, rake] ]
  return [0.4, 2, 2, rake, -rake]
]

// ── 3. RIPPLE EDGE — width modulated along the column by `t` ────────
// real-mm `t` means the wobble's wavelength doesn't rescale with length.
def ripple(t, s, i, u) [
  let w = 1.8 + amp * sin(t * 36)
  return [0.4, w, w, 0, 0]
]

// ── 4. ASYMMETRIC RAMP — leftw ≠ rightw, ramped by `s` ──────────────
// the left rail fattens toward the top while the right stays slim: a
// comma / paisley taper that falls straight out of independent widths.
def ramp(t, s, i, u) [
  return [0.4, 0.6 + lean * s, 0.7, 0, 0]
]

// draw the four columns side by side, each bottom-to-top
let names = [@leaf, @crosshatch, @ripple, @ramp]
let x0    = -33

for c = 0 to 3 [
  color c
  up setxy(x0 + c * 22, -len / 2) seth 0 down   // start at the foot, aim up
  satin names[c]
  fd len
  satin 0                                        // flush this column
]
