{
 "genart": "1.2",
 "id": "wind-in-the-barley",
 "title": "Wind in the Barley",
 "created": "2026-09-10T00:00:00Z",
 "modified": "2026-09-10T19:31:32.540Z",
 "renderer": {
  "type": "canvas2d",
  "version": "1.x"
 },
 "canvas": {
  "width": 1400,
  "height": 1000
 },
 "parameters": [],
 "colors": [],
 "dataChannels": [
  {
   "name": "crop1",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "crop2",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "crop3",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "crop4",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "ears",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "near",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "hedge",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "hedge-base",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "rut",
   "type": "vector",
   "cols": 400,
   "rows": 300
  }
 ],
 "state": {
  "seed": 2287,
  "params": {},
  "colorPalette": [
   "#2b1a12",
   "#5c3320",
   "#8a5a3c",
   "#c9a982",
   "#ece3d4"
  ]
 },
 "algorithm": "// Wind in the Barley — field generation only. This code makes NO marks.\n// It publishes a tone map and a stalk-direction map on the ADR 062 data\n// bridge; every line in the picture is drawn by a plugin layer that reads them.\n//\n// Two rules govern the plate, the same two that govern Quarry Face:\n//   the direction of a hatch = the lean of the stalks it describes\n//   the density of a hatch   = how far that crop is turned from the light\n//\n// The subject is the wind, which cannot be drawn. What can be drawn is what\n// the wind does to a crop: where a gust presses the barley over, the stalks\n// turn their sides up and the ears stop facing the viewer, and that patch goes\n// pale. Where the crop still stands, the eye looks down into it and it goes\n// dark. So the gusts are pale streaks running across a dark field, and their\n// shape is the shape of the air.\n//\n// Everything is computed on the ground plane and projected, so the gust bands\n// and the tramlines foreshorten and converge on one vanishing point instead of\n// being drawn as screen-space decoration.\nfunction sketch(ctx, state) {\n  var W = state.canvas.width, H = state.canvas.height;\n  var seed = state.seed || 0;\n\n  ctx.fillStyle = state.colorPalette[4];\n  ctx.fillRect(0, 0, W, H);\n\n  // The field grid scales with the canvas so a cell stays the same size in\n  // pixels. Holding it fixed softens every mask edge as the plate grows.\n  var COLS = Math.round(200 * W / 700), ROWS = Math.round(150 * H / 500);\n\n  function rngFrom(a) {\n    return function () {\n      a |= 0; a = a + 0x6D2B79F5 | 0;\n      var t = Math.imul(a ^ a >>> 15, 1 | a);\n      t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;\n      return ((t ^ t >>> 14) >>> 0) / 4294967296;\n    };\n  }\n  var rand = rngFrom(seed * 2654435761 + 12345);\n  var ph = [];\n  for (var i = 0; i < 24; i++) ph.push(rand() * Math.PI * 2);\n\n  /** A lattice of random values, bilinearly sampled. Used only to give the\n   *  crop its crookedness: a barley stalk is not a ruled line, and a field of\n   *  ruled lines is the one thing that would give the whole plate away. */\n  function lattice(nx, ny) {\n    var v = new Float32Array(nx * ny);\n    for (var k = 0; k < nx * ny; k++) v[k] = rand() * 2 - 1;\n    return function (u, t) {\n      // 🔴 Clamp. A sample outside [0,1] indexes past the lattice, and the\n      // undefined it returns becomes a NaN direction in a published channel.\n      // The flow-line stepper then walks to a NaN position, whose bounds check\n      // passes (every comparison with NaN is false), and dies indexing\n      // samples[NaN] — reported as \"Cannot read properties of undefined\n      // (reading 'dx')\" on a frame the CLI still exits 0 for.\n      if (u < 0) u = 0; else if (u > 1) u = 1;\n      if (t < 0) t = 0; else if (t > 1) t = 1;\n      var x = u * (nx - 1), y = t * (ny - 1);\n      var x0 = Math.floor(x), y0 = Math.floor(y);\n      var x1 = x0 + 1 > nx - 1 ? nx - 1 : x0 + 1, y1 = y0 + 1 > ny - 1 ? ny - 1 : y0 + 1;\n      var fx = x - x0, fy = y - y0;\n      fx = fx * fx * (3 - 2 * fx); fy = fy * fy * (3 - 2 * fy);\n      var a = v[y0 * nx + x0], b = v[y0 * nx + x1], c = v[y1 * nx + x0], d = v[y1 * nx + x1];\n      return (a + (b - a) * fx) * (1 - fy) + (c + (d - c) * fx) * fy;\n    };\n  }\n  // Every lattice is indexed in [0,1], so its features are a fixed fraction of\n  // the plate and scale with it. Resolutions are therefore literals, and the\n  // seeded picture is the same picture at any canvas size.\n  var clump = lattice(17, 13);   // stalks lean together in patches\n  var splay = lattice(61, 47);   // and the awns splay inside a patch\n  var edge  = lattice(53, 41);   // used to break binary mask boundaries\n  var nod   = lattice(97, 73);   // how each ear nods off its own stalk\n  // 🔴 The one that stops the field reading as combed fur. A smooth jitter\n  // coarser than the spacing between lines bends every neighbour by the same\n  // amount, so they stay parallel and the crop turns into a pelt. This one is\n  // finer than the line spacing, so adjacent stalks genuinely disagree.\n  var kink  = lattice(151, 113);\n  // One dither lattice per plate pass, at four different resolutions. A pass\n  // covers a hard-edged region and a hard edge shows as a contour drawn across\n  // the crop, so each threshold is jogged cell by cell and its boundary breaks\n  // into stipple. They have to be four DIFFERENT lattices: share one and the\n  // four boundaries jog together, land on top of each other and resolve back\n  // into the single hard contour the dither was there to destroy.\n  var dith  = [lattice(83, 63), lattice(79, 59), lattice(89, 67), lattice(73, 55)];\n\n  // --- The ground -------------------------------------------------------\n  var hv     = 0.150 + rand() * 0.048;   // the skyline\n  // Kept off centre. A vanishing point in the middle with a tramline pair\n  // straddling it turns the plate into a symmetrical funnel.\n  var vpU    = (rand() < 0.5 ? 0.15 + rand() * 0.18 : 0.67 + rand() * 0.18);\n  var XK     = 1.15;                     // ground units visible per unit depth\n  // 🔴 How far back the viewer stands, in the same units as a gust wavelength.\n  // At 1 the near edge of the plate is right at the viewer's feet, and because\n  // perspective makes the visible width proportional to the distance, the whole\n  // foreground then falls inside a third of one gust and can only be a single\n  // flat mass however the tone is graded. Standing back puts more of the plate\n  // at the distances where the wind is legible, which is why a landscape is\n  // almost never drawn from the ground at one's own feet.\n  var DNEAR  = 2.6;\n  var DMAX   = DNEAR * 24;\n  // A gust band is a line of constant phase on the ground. At windTh = PI/2 it\n  // runs directly away from the viewer and converges at the vanishing point;\n  // at 0 it lies flat across the view as a horizontal stripe, which reads as\n  // depth banding rather than as weather. So it stays near PI/2, tilted just\n  // far enough that the gusts cross the tramlines at a shallow angle.\n  var windTh = 1.15 + rand() * 0.34;\n  var wSin = Math.sin(windTh), wCos = Math.cos(windTh);\n  // Which way the crop is pushed on the sheet. One direction only: the wind\n  // does not change its mind inside a single plate.\n  var leanSign = rand() < 0.5 ? -1 : 1;\n\n  // The tramline: one pair of wheelings, laid by a machine, running to the\n  // vanishing point. It is the only straight thing in the picture and the only\n  // evidence of a person.\n  // In ground units, so they hold their real width however far back the\n  // viewer stands.\n  var tramX  = (rand() * 2 - 1) * 0.55 * DNEAR;\n  var gauge  = (0.26 + rand() * 0.09) * DNEAR;\n  var rutW   = (0.019 + rand() * 0.009) * DNEAR;\n\n  // The hedge that closes the field. Its top edge is the only drawing in the\n  // upper third, so it carries the whole horizontal, and it is not a strip of\n  // even height. It is bushed up in places, thin in\n  // others, and somewhere along it one thing has been let grow into a tree —\n  // which is the only vertical accent in the upper third and the thing that\n  // gives the horizon a scale.\n  var treeU = 0.13 + rand() * 0.74;\n  function hedgeTop(u) {\n    var body = 0.026\n             + 0.024 * (0.5 + 0.5 * Math.sin(u * 2.3 + ph[0]))\n             + 0.017 * (0.5 + 0.5 * Math.sin(u * 5.9 + ph[1]))\n             + 0.010 * (0.5 + 0.5 * Math.sin(u * 17.1 + ph[6]))\n             + 0.006 * (0.5 + 0.5 * Math.sin(u * 41.3 + ph[9]));\n    // One tree, not two, and tall and narrow rather than broad: a pair of\n    // broad ones sat on the horizon as a pair of hills. Built from three\n    // overlapping lobes, because a single bell curve is a cone and reads as a\n    // haystack.\n    var tree = 0.098 * Math.exp(-Math.pow((u - treeU) * 30.0, 2))\n             + 0.062 * Math.exp(-Math.pow((u - treeU + 0.021) * 41.0, 2))\n             + 0.052 * Math.exp(-Math.pow((u - treeU - 0.026) * 36.0, 2));\n    return hv - (body + tree);\n  }\n\n  var tone = new Float32Array(COLS * ROWS);\n  var dir = new Float32Array(COLS * ROWS);\n  var nearMask = new Float32Array(COLS * ROWS);\n  var hedgeTone = new Float32Array(COLS * ROWS);\n  var hedgeDir = new Float32Array(COLS * ROWS);\n  var hedgeBase = new Float32Array(COLS * ROWS);\n  var rutTone = new Float32Array(COLS * ROWS);\n  var rutDir = new Float32Array(COLS * ROWS);\n\n  for (var r2 = 0; r2 < ROWS; r2++) {\n    for (var c = 0; c < COLS; c++) {\n      var u = c / (COLS - 1), v = r2 / (ROWS - 1);\n      var idx = r2 * COLS + c;\n\n      var ht = hedgeTop(u);\n      if (v < ht) continue;                          // sky: bare paper\n\n      var HB = hv + 0.012;                           // the foot of the hedge\n      if (v < HB) {\n        // The hedge is solid at its base and open at the top, where the last\n        // growth stands separately against the sky. Filled to a hard edge it\n        // reads as a dark strip laid across the plate — a bar, not a hedge —\n        // so the crown thins out and only the body below is continuous. The\n        // break-up runs across three scales at once: one coarse lattice alone\n        // decides the whole width of the tree with a single value and fills it\n        // in as a solid mound, and the finer ones cut that mound into branches.\n        var f = (v - ht) / Math.max(0.004, HB - ht);\n        var solid = (f - 0.02) / 0.30; if (solid > 1) solid = 1; else if (solid < 0) solid = 0;\n        if (0.5 + 0.16 * clump(u, f) + 0.15 * splay(u, f) + 0.11 * nod(u, f) <= solid) {\n          // Foliage has no single direction, so its marks are thrown wide. That\n          // scatter is the difference between a hedge and a wall, and it is the\n          // only place in the plate where direction is not reporting the lean\n          // of something.\n          hedgeTone[idx] = 1;\n          hedgeDir[idx] = -Math.PI / 2 + 1.30 * kink(u, v) + 0.70 * splay(u, v) + 0.40 * clump(u, v);\n          // The base of a hedge, in its own shadow over the ditch, is the\n          // darkest thing on the horizon. It gets a second covering for the\n          // same reason the crop's troughs do: tone is built by returning.\n          if (f > 0.48) hedgeBase[idx] = 1;\n        }\n      }\n\n      // Project the cell onto the ground plane. 🔴 This runs UNDER the hedge as\n      // well as below it. Two binary masks that merely ABUT do not meet: the\n      // renderer interpolates the field between grid cells, so along the shared\n      // row both fall under the magnitude gate and neither draws, leaving a\n      // bright hairline across the full width of the plate. Masks that touch\n      // have to overlap.\n      var rec = (v - hv) / (1 - hv);                 // 1 at the near edge, 0 at the skyline\n      if (rec < DNEAR / DMAX) rec = DNEAR / DMAX;    // at and above the skyline, as far as it goes\n      var d = DNEAR / rec;                           // depth, DNEAR at the near edge\n      var gx = (u - vpU) * d * XK;\n\n      // The gust field, in ground coordinates so it foreshortens correctly.\n      // A gust does not travel as a ruled stripe. Its front snakes, and it\n      // breaks into patches along its own length, so the band is modulated by\n      // the coordinate that runs across it as well as the one along it.\n      var s = gx * wSin + d * wCos + 0.62 * Math.sin(d * 0.40 + ph[7]);\n      var sPerp = gx * wCos - d * wSin;\n      // Detail dissolves with distance. Without this the high-frequency terms\n      // oscillate faster than the field grid can carry them near the skyline\n      // and the far crop breaks into a moire that reads as corduroy.\n      var hf = 1 / (1 + d * 0.16 / DNEAR);\n      // Weighted so the broad sweep wins. Run the other way round, with the\n      // gusts louder than the sweep, the plate fills with evenly sized stripes\n      // and reads as corduroy: a texture, not weather. One large movement with\n      // smaller ones inside it is both what wind on a crop looks like and the\n      // only version of this that composes.\n      var g = Math.sin(s * 1.45 + ph[3]) * 0.95         // the one broad sweep\n            + Math.sin(s * 4.40 + ph[4]) * 0.52 * (0.35 + 0.65 * hf)   // the gusts inside it\n            + Math.sin(s * 9.80 + ph[5]) * 0.26 * hf;   // and their fraying\n      g /= 1.55;\n      g *= 0.74 + 0.26 * Math.sin(sPerp * 0.72 + ph[8]);\n      // You cannot make out one gust from another at the far side of a field,\n      // and trying to draw them there squeezes several cycles into the few\n      // grid rows left above the recession, which resolves as hard pale\n      // rectangles rather than as weather. So the figure fades out into an\n      // even grey before it reaches the hedge.\n      g *= 0.68 + 0.32 * hf;\n      // Pushed a little toward its limits so the plate has a settled range of\n      // value whatever the seed. Left raw, the sum of sines rarely approaches\n      // either end and the deepest pass finds almost nothing to cover: measured\n      // at three seeds, under half a percent of the plate reached the fourth\n      // threshold and the darks varied by a tenth between them. Pushed hard,\n      // though, the figure goes bimodal, its edges harden into ridge lines and\n      // the crop reads as dunes — so this is deliberately gentle.\n      g = Math.tanh(g * 1.55);\n\n      var pale = Math.pow(g > 0 ? g : 0, 1.30);        // crests: crop pressed over, turned from the light\n      var trough = Math.pow(g < 0 ? -g : 0, 1.25);     // troughs: crop standing, looked into\n\n      // Tone. The field itself is a middle value that only leans darker toward\n      // the viewer, because the eye looks down into near crop and the far crop\n      // closes up into one surface. The large swings belong to the wind: the\n      // deepest darks are standing crop in the troughs and the paper is left\n      // almost bare on the crests. Grading it the other way round, with the\n      // recession carrying the range, produced a plate that was a black field\n      // with weather in it somewhere.\n      // Depth is carried by the SIZE of the marks, which the layers' own depth\n      // scaling handles, so the tone map stays nearly flat from near to far and\n      // spends its range on the wind instead. Grading it steeply with distance\n      // made a plate that read as a dark bank rising out of a bright one.\n      var T = 0.30 + 0.05 * Math.sqrt(rec);\n      T -= 0.30 * pale;\n      T += 0.32 * trough;\n\n      // Lean. Strongest on the crest of a gust, never reversed.\n      var lean = leanSign * (0.06 + 0.52 * (0.5 + 0.5 * g));\n      var A = -Math.PI / 2 + lean + 0.15 * clump(u, v) + 0.13 * splay(u, v) + 0.23 * kink(u, v);\n\n      // The tramline. Bare soil in the wheelings, and the crop at their edges\n      // leans into the gap and stands against the light, so each rut is a pale\n      // line with a dark lip — which is exactly how you see one from the gate.\n      for (var t2 = -1; t2 <= 1; t2 += 2) {\n        var dr = Math.abs(gx - (tramX + t2 * gauge));\n        if (dr < rutW) {\n          T = 0;\n          // Wheelings and their ruts run to the vanishing point, so their few\n          // marks are the only ones in the crop that lie down in perspective.\n          rutTone[idx] = 1;\n          var du = u - vpU, dv = v - hv;\n          rutDir[idx] = Math.atan2(dv, du) + (du < 0 ? Math.PI : 0);\n        } else if (dr < rutW * 2.2) {\n          var f = 1 - (dr - rutW) / (rutW * 1.2);\n          T += 0.32 * f * f;\n        }\n      }\n\n      // 🔴 A floor, and it matters more than any other number here. Barley\n      // pressed flat by a gust is PALER, not absent: the stalks are still\n      // standing in it, they are just showing less of their own shadow. Let the\n      // crests fall through the first pass and they become bare paper, and a\n      // large ragged patch of bare paper in the middle of a field does not read\n      // as wind, it reads as a hole. So the first pass covers all of the crop\n      // and the whole wind figure is made by which of the LATER passes return\n      // to a place. Only the wheelings, which really are bare, sit below this.\n      if (rutTone[idx] === 0 && T < 0.19) T = 0.19;\n      if (T < 0) T = 0; else if (T > 1) T = 1;\n      tone[idx] = T;\n      dir[idx] = A;\n\n      // The near crop, drawn again with taller strokes so the foreground has\n      // its own scale. A straight horizontal cut would show as a line across\n      // the plate, so the boundary is broken by the same lattice that bends\n      // the stalks.\n      if (rec > 0.44 + 0.13 * edge(u, v) && rutTone[idx] === 0 && T >= 0.22) nearMask[idx] = 1;\n    }\n  }\n\n  // --- Publish one field per plate pass ---------------------------------\n  // An engraver builds value by returning to the plate, not by pressing\n  // harder. Each pass covers only what is at least as dark as its threshold,\n  // and sits a little off the last, so tone accumulates by crossing.\n  // Set against the measured tone distribution, not by eye. The first is high\n  // enough that the crests of the gusts fall below it and stay bare paper,\n  // which is the whole of how the wind reads; the last is low enough that the\n  // deepest crossing actually finds something to cover at every seed.\n  var THRESHOLDS = [0.14, 0.30, 0.42, 0.53];\n  // Wider than a quarry wall's, because barley genuinely splays: a bed of\n  // stalks all within a few degrees of each other combs into a pelt, and the\n  // crossing is what makes the dark read as ink rather than as shading.\n  var SKEW = [0.10, -0.24, 0.32, -0.55];\n  var g2 = (typeof globalThis !== 'undefined') ? globalThis : window;\n  g2.__genart_data = g2.__genart_data || {};\n  g2.__genart_data.cols = COLS;\n  g2.__genart_data.rows = ROWS;\n\n  function pack(name, mag, ang, skew) {\n    var f32 = new Float32Array(COLS * ROWS * 3);\n    for (var n = 0; n < COLS * ROWS; n++) {\n      var a = ang[n] + (skew || 0);\n      var m = mag[n];\n      // One non-finite cell anywhere in a channel takes the whole render down,\n      // so nothing unchecked is published. See the note in lattice().\n      if (!isFinite(a)) a = -Math.PI / 2;\n      if (!isFinite(m)) m = 0;\n      f32[n * 3] = Math.cos(a);\n      f32[n * 3 + 1] = Math.sin(a);\n      f32[n * 3 + 2] = m;\n    }\n    g2.__genart_data[name] = f32;\n  }\n\n  // The jog is wide — a fifth of the whole tonal range — and runs at two\n  // scales. This is the single thing that separates wind moving over a crop\n  // from ploughed ridges: a gust has no edge, it frays out over a stretch of\n  // ground several stalks deep, and a pass boundary cut at one tone contour\n  // instead reads as the lip of a dune however the value is graded.\n  for (var p = 0; p < THRESHOLDS.length; p++) {\n    var m = new Float32Array(COLS * ROWS);\n    for (var n2 = 0; n2 < COLS * ROWS; n2++) {\n      var uu = (n2 % COLS) / (COLS - 1), vv = Math.floor(n2 / COLS) / (ROWS - 1);\n      // The first pass is NOT jogged. It is the crop's own covering, it sits\n      // under the whole field, and dithering it punches pale holes right\n      // through the ground the other three are built on — which dapples the\n      // field like a leopard instead of fraying the edges between passes.\n      var th = p === 0 ? THRESHOLDS[0]\n             : THRESHOLDS[p] + 0.075 * dith[p](uu, vv) + 0.032 * clump(uu, vv);\n      m[n2] = tone[n2] >= th ? 1 : 0;\n    }\n    pack('crop' + (p + 1), m, dir, SKEW[p]);\n  }\n  // The ears are the one part of a barley plant that does not lie along the\n  // stalk: they nod off it at every angle, and their scatter is what stops a\n  // field reading as a pelt. Drawn very short and thrown wide of the lean.\n  var earsMask = new Float32Array(COLS * ROWS);\n  var earsDir = new Float32Array(COLS * ROWS);\n  for (var e = 0; e < COLS * ROWS; e++) {\n    earsMask[e] = tone[e] >= 0.26 ? 1 : 0;\n    earsDir[e] = dir[e] + 0.62 * nod((e % COLS) / (COLS - 1), Math.floor(e / COLS) / (ROWS - 1));\n  }\n  pack('ears', earsMask, earsDir, 0);\n  pack('near', nearMask, dir, 0);\n  pack('hedge', hedgeTone, hedgeDir, 0);\n  pack('hedge-base', hedgeBase, hedgeDir, -0.55);\n  pack('rut', rutTone, rutDir, 0);\n}\n",
 "layers": [
  {
   "id": "sheet",
   "type": "textures:paper",
   "name": "Sheet",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 0,
    "y": 0,
    "width": 1400,
    "height": 1000,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "preset": "rough",
    "roughness": -1,
    "color": "#ece3d4",
    "seed": 2287
   }
  },
  {
   "id": "hedge",
   "type": "painting:flow-lines",
   "name": "Hedge — Field Boundary",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:hedge",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2378,
    "lineCount": 34000,
    "seedDistribution": "grid-jittered",
    "lineLength": 7,
    "stepSize": 4,
    "lineWeight": 1.16,
    "lineWeightVariation": 0.34,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.07,
    "opacity": 0.36,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "hedge-base",
   "type": "painting:flow-lines",
   "name": "Hedge — Base and Ditch",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:hedge-base",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2390,
    "lineCount": 24000,
    "seedDistribution": "grid-jittered",
    "lineLength": 6,
    "stepSize": 4,
    "lineWeight": 1.16,
    "lineWeightVariation": 0.34,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.07,
    "opacity": 0.38,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "pass-1",
   "type": "painting:flow-lines",
   "name": "Field Pass 1 — Standing Crop",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:crop1",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2300,
    "lineCount": 30000,
    "seedDistribution": "grid-jittered",
    "lineLength": 14,
    "stepSize": 4,
    "lineWeight": 1.08,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.26,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "pass-2",
   "type": "painting:flow-lines",
   "name": "Field Pass 2 — Half Tone",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:crop2",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2313,
    "lineCount": 29000,
    "seedDistribution": "grid-jittered",
    "lineLength": 11,
    "stepSize": 4,
    "lineWeight": 1.1,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.31,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "pass-3",
   "type": "painting:flow-lines",
   "name": "Field Pass 3 — Shadow",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:crop3",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2326,
    "lineCount": 27000,
    "seedDistribution": "grid-jittered",
    "lineLength": 9,
    "stepSize": 4,
    "lineWeight": 1.2,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.41,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "pass-4",
   "type": "painting:flow-lines",
   "name": "Field Pass 4 — Cross, Deepest Only",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:crop4",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2339,
    "lineCount": 23000,
    "seedDistribution": "grid-jittered",
    "lineLength": 7,
    "stepSize": 4,
    "lineWeight": 1.28,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.47,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "ears",
   "type": "painting:flow-lines",
   "name": "Ears — Broken Texture",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:ears",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2498,
    "lineCount": 24000,
    "seedDistribution": "grid-jittered",
    "lineLength": 5,
    "stepSize": 4,
    "lineWeight": 1.12,
    "lineWeightVariation": 0.34,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.07,
    "opacity": 0.28,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.80, 1.08]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "wheelings",
   "type": "painting:flow-lines",
   "name": "Wheelings — Tramline",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:rut",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2344,
    "lineCount": 90000,
    "seedDistribution": "grid-jittered",
    "lineLength": 7,
    "stepSize": 4,
    "lineWeight": 1,
    "lineWeightVariation": 0.34,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.22,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.70, 1.10]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "near-crop",
   "type": "painting:flow-lines",
   "name": "Near Crop — Foreground Stalks",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "field": "algorithm:near",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 2424,
    "lineCount": 6800,
    "seedDistribution": "grid-jittered",
    "lineLength": 16,
    "stepSize": 4,
    "lineWeight": 1.48,
    "lineWeightVariation": 0.14,
    "taper": "tail",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.24,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.18,
    "depthWeightRange": "[0.96, 1.12]",
    "depthOpacityRange": "[0.94, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "plate-mark",
   "type": "shapes:rect",
   "name": "Plate Mark",
   "visible": true,
   "locked": false,
   "opacity": 0.55,
   "blendMode": "normal",
   "transform": {
    "x": 105,
    "y": 75,
    "width": 1190,
    "height": 810,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "fillColor": "#ffffff",
    "fillEnabled": false,
    "strokeColor": "#8a5a3c",
    "strokeWidth": 2,
    "strokeEnabled": true,
    "cornerRadius": 0
   }
  },
  {
   "id": "tone",
   "type": "filter:grain",
   "name": "Plate Tone",
   "visible": true,
   "locked": false,
   "opacity": 1,
   "blendMode": "normal",
   "transform": {
    "x": 0,
    "y": 0,
    "width": 1400,
    "height": 1000,
    "rotation": 0,
    "scaleX": 1,
    "scaleY": 1,
    "anchorX": 0,
    "anchorY": 0
   },
   "properties": {
    "intensity": 0.1,
    "size": 2,
    "seed": 2287,
    "monochrome": true
   }
  }
 ]
}