{
 "genart": "1.2",
 "id": "standing-timber",
 "title": "Standing Timber",
 "created": "2026-09-10T00:00:00Z",
 "modified": "2026-09-10T22:01:51.122Z",
 "renderer": {
  "type": "canvas2d",
  "version": "1.x"
 },
 "canvas": {
  "width": 1400,
  "height": 1000
 },
 "parameters": [],
 "colors": [],
 "dataChannels": [
  {
   "name": "air1",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "air2",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "air3",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "air4",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "trunk1",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "trunk2",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "trunk3",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "trunk4",
   "type": "vector",
   "cols": 400,
   "rows": 300
  },
  {
   "name": "shadow",
   "type": "vector",
   "cols": 400,
   "rows": 300
  }
 ],
 "state": {
  "seed": 7788,
  "params": {},
  "colorPalette": [
   "#2b1a12",
   "#5c3320",
   "#8a5a3c",
   "#c9a982",
   "#ece3d4"
  ]
 },
 "algorithm": "// Standing Timber — field generation only. This code makes NO marks.\n// It publishes tone and direction maps on the ADR 062 data bridge; every line\n// in the picture is drawn by a plugin layer that reads them.\n//\n// A wood against a low sun. The sun is behind the trees, so\n// every trunk is a dark column with a thin lit edge on the side facing the\n// light, and every trunk throws its shadow straight toward the viewer. Those\n// shadows lie on the ground plane, so in the picture they are not parallel:\n// they radiate from the one point on the horizon under the sun, which is also\n// where the glow in the air is brightest. One light source sets the value of\n// everything, and its position is the whole composition.\n//\n// The same two rules as the rest of the series:\n//   the direction of a hatch = the surface or grain it describes\n//   the density of a hatch   = how far that surface is turned from the light\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  // The plate is inset in the sheet (see the builder), and the field maps\n  // onto the plate, so this is the plate's aspect, not the canvas's.\n  var ASPECT = (W * 0.85) / (H * 0.81);\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 + 7919);\n\n  /** A lattice of random values, bilinearly sampled over [0,1]. */\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] returns undefined, becomes a NaN\n      // direction in a published channel, and takes the whole render down on\n      // a frame the CLI still exits 0 for. See wind-algorithm.js.\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  function smooth(e0, e1, x) {\n    var t = (x - e0) / (e1 - e0);\n    if (t < 0) t = 0; else if (t > 1) t = 1;\n    return t * t * (3 - 2 * t);\n  }\n\n  var mist  = lattice(11, 9);     // the air is not evenly thick\n  var kink  = lattice(151, 113);  // finer than the line spacing, so neighbours disagree\n  var edge  = lattice(53, 41);    // breaks mask boundaries\n  // Bark runs along the trunk, so its lattice is fine across and coarse along.\n  var bark  = lattice(211, 23);\n  // One dither lattice per pass, each different, so the pass boundaries fray\n  // independently instead of jogging together into one contour.\n  var dith  = [lattice(83, 63), lattice(79, 59), lattice(89, 67), lattice(73, 55)];\n\n  // --- The ground and the light -----------------------------------------\n  var hv  = 0.57 + rand() * 0.08;   // the horizon, below the middle: a wood is tall\n  var vpU = 0.5;                    // the viewer looks straight in\n  var XK  = 1.4;                    // ground units visible across the near edge\n  var DNEAR = 1.0;\n  // The sun sits off centre, never in the middle and never at an edge. Its\n  // point on the horizon is where every ground shadow converges.\n  var sunU = rand() < 0.5 ? 0.26 + rand() * 0.16 : 0.58 + rand() * 0.16;\n  var sunV = hv - 0.06 - rand() * 0.07;\n  var sx = (sunU - vpU) * XK;       // the sun's direction on the ground, per unit depth\n\n  /** Screen v of a ground point at depth d. */\n  function groundV(d) { return hv + (1 - hv) * DNEAR / d; }\n\n  // --- The trees --------------------------------------------------------\n  // Four ranks, nearest first. Each tree is placed across the view, then set\n  // on the ground at its depth, so its base, its width and its shadow all come\n  // out of one projection instead of being set by hand.\n  //\n  // 🔴 Fewer than looks right on paper. The first version stood 42 trees in\n  // four evenly stratified ranks and read as a colonnade: a stage set of\n  // pillars, not a wood. The back ranks are now thrown at random, so they\n  // clump and leave gaps the way self-sown trees do, and they lean.\n  var RANKS = [\n    { n: 3,  d0: 0.80, d1: 1.45, w0: 0.080, w1: 0.160, lean: 0.025, strat: true },\n    { n: 5,  d0: 1.80, d1: 3.20, w0: 0.070, w1: 0.120, lean: 0.045, strat: true },\n    { n: 8,  d0: 3.60, d1: 6.50, w0: 0.060, w1: 0.110, lean: 0.040, strat: false },\n    { n: 13, d0: 7.00, d1: 15.0, w0: 0.060, w1: 0.100, lean: 0.035, strat: false },\n  ];\n  var trees = [];\n  for (var ri = 0; ri < RANKS.length; ri++) {\n    var R = RANKS[ri];\n    var tries = 0;\n    var slot = 1.14 / R.n;\n    for (var s = 0; s < R.n && tries < 400; tries++) {\n      var su = R.strat ? -0.07 + slot * (s + 0.15 + rand() * 0.7) : -0.07 + rand() * 1.14;\n      var d = R.d0 + rand() * (R.d1 - R.d0);\n      // The nearest rank carries the composition, so it is placed with care:\n      // never over the sun, where a dark column would put out the light the\n      // whole plate is organised around, and never evenly spaced.\n      if (ri === 0) {\n        if (Math.abs(su - sunU) < 0.13) continue;\n        var clash = false;\n        for (var q = 0; q < trees.length; q++) if (Math.abs(trees[q].u - su) < 0.2) clash = true;\n        if (clash) continue;\n      }\n      var wt = R.w0 + rand() * (R.w1 - R.w0);\n      trees.push({\n        u: su, d: d, rank: ri,\n        gx: (su - vpU) * d * XK,\n        wt: wt,\n        hw: wt / (d * XK) / 2,           // half width on screen, in u\n        vb: groundV(d),                  // where it stands\n        lean: (rand() * 2 - 1) * R.lean, // du per unit of height\n        sway: rand() * Math.PI * 2,\n      });\n      s++;\n    }\n  }\n  trees.sort(function (a, b) { return a.d - b.d; });\n\n  // Fray lattices for the ruled air, fine ACROSS the lines and coarse ALONG\n  // them, so a pass boundary lands at a different point on every line and the\n  // ruling ends raggedly, the way an engraver lets a sky thin out. The square\n  // dither lattices cut it into stair-stepped blocks: pixel art, round a sun.\n  // ⚠️ Allocated after the trees on purpose. Every lattice spends hundreds of\n  // rand() calls, so one allocated above would re-cut every seed's wood.\n  // 🔴 Fine along the lines too, at about a line's length. At 7 cells across,\n  // a whole row took one jog, every line in it stopped at the same height, and\n  // the top of the glow came out as a straight edge: a window, not a sun.\n  var streak = [lattice(23, 211), lattice(19, 199), lattice(27, 223), lattice(21, 191)];\n\n  /** Screen centre and half width of a tree at height v, or null below its foot. */\n  function trunkAt(t, v) {\n    if (v > t.vb) return null;\n    var up = t.vb - v;\n    // A trunk tapers only slightly over the height of a plate, but it\n    // flares at the root, where it grips the ground. Without the flare a trunk\n    // stands on the ground like a post pushed into it.\n    var flare = 1 + 0.55 * Math.exp(-up / (5 * t.hw * ASPECT));\n    var hw = t.hw * (1 - 0.14 * up) * flare;\n    var cu = t.u + t.lean * up + 0.004 * Math.sin(up * 6.0 + t.sway) * (t.rank === 0 ? 1 : 0.4);\n    return { cu: cu, hw: hw };\n  }\n\n  /** The air's own value: bright round the sun, closing in toward the canopy. */\n  function airTone(u, v) {\n    var dx = (u - sunU) * ASPECT, dy = (v - sunV) * 1.25;\n    var rr = Math.sqrt(dx * dx + dy * dy);\n    // 🔴 Kept small, and its edge is frayed by the pass dither below. Broad\n    // and cut at one threshold, the glow came out as a hard-edged white disc\n    // hung in the trees — a moon, not a low sun in mist.\n    // Graded slowly outward rather than steeply: pulled in tight to kill the\n    // disc, it left the whole plate at one murky middle value — night in a\n    // wood, not a winter morning.\n    var T = 0.02 + 0.40 * Math.pow(smooth(0.0, 1.1, rr), 0.9);\n    // The canopy closes overhead, so the top of the plate is darker than the\n    // glow's falloff alone would make it.\n    T += 0.12 * smooth(0.35, 0.0, v);\n    T += 0.04 * mist(u, v);\n    return T;\n  }\n\n  var HAZE = 4.2;   // the depth over which a trunk loses most of its contrast\n\n  var N = COLS * ROWS;\n  var airTone2 = new Float32Array(N);   // air above the horizon AND ground below it\n  var airDir = new Float32Array(N);\n  var trunkTone = new Float32Array(N);\n  var trunkAlong = new Float32Array(N);\n  var trunkAcross = new Float32Array(N);\n  var shadowMask = new Float32Array(N);\n  var rayDir = new Float32Array(N);\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      var air = airTone(u, v);\n\n      // 🔴 Air and ground are ONE tone map, ruled level by one family of\n      // passes. Kept as two, they were gated and marked differently, so equal\n      // tones drew as unequal densities: the floor of the wood ended at the\n      // horizon in an edge like a table top, and every trunk stepped in value\n      // there, because the air ran under it above the horizon and nothing ran\n      // under it below. Far ground now goes to exactly the air's value, so the\n      // horizon dissolves into the mist wherever the light says it should.\n      var base = air, dg = 0, gxg = 0;\n      if (v >= hv) {\n        var rec = (v - hv) / (1 - hv);\n        if (rec < DNEAR / 60) rec = DNEAR / 60;\n        dg = DNEAR / rec;\n        gxg = (u - vpU) * dg * XK;\n        var hg = Math.exp(-(dg - 0.8) / HAZE);\n        base = air + (0.17 - air) * hg;           // ground raked by low light: one pass\n      }\n      var levelDir = 0.05 * kink(u, v) + 0.03 * mist(u, v);\n\n      // Nearest tree covering this cell, if any.\n      var hit = null, geo = null;\n      for (var ti = 0; ti < trees.length; ti++) {\n        var g = trunkAt(trees[ti], v);\n        if (g && Math.abs(u - g.cu) < g.hw) { hit = trees[ti]; geo = g; break; }\n      }\n\n      if (hit) {\n        // 🔴 The air runs UNDER the trunks. Cut out round each one, it gave\n        // every trunk a bright outline on both sides (two masks that abut do\n        // not meet) and cut the air between trunks into hatched panels. So a\n        // trunk only adds the tone it has IN EXCESS of what lies behind it,\n        // which is also the right model of haze: a far trunk is the air plus a\n        // little, and near the sun the little goes to nothing.\n        var x = (u - geo.cu) / geo.hw;                 // -1..1 across the trunk\n        var toSun = sunU > geo.cu ? 1 : -1;\n        var xs = x * toSun;                            // 1 at the edge facing the sun\n        var h = Math.exp(-(hit.d - 0.8) / HAZE);\n        var Tint = 0.80 + 0.06 * bark(u, v);\n        Tint -= 0.10 * smooth(-0.55, -1.0, xs);        // sky fill on the shadow side\n        var ex = (Tint - base) * h;\n        // The lit edge: a hair of bare paper on the side facing the sun, and\n        // only where there is bright air behind the trunk to light it. Wider,\n        // or everywhere, it was a white stripe down every tree.\n        // Its jitter is kept small: at twice this the edge read as torn paper.\n        var rim = xs > 0.86 + 0.03 * edge(u, v) && air < 0.20 && h > 0.25;\n        trunkTone[idx] = rim ? 0 : (ex < 0 ? 0 : ex > 1 ? 1 : ex);\n        trunkAlong[idx] = -Math.PI / 2 + Math.atan(hit.lean * ASPECT) + 0.04 * kink(u, v);\n        // Across the trunk the marks bow round it, the way a line drawn round\n        // a column bends at its sides. Above eye level the rings are seen from\n        // underneath, so they bow up at the middle.\n        trunkAcross[idx] = Math.atan(0.45 * x * (v < hv ? 1 : -1)) + 0.03 * kink(u, v);\n        airTone2[idx] = rim ? 0 : base;\n        airDir[idx] = levelDir;\n        continue;\n      }\n\n      if (v < hv) {\n        airTone2[idx] = air;\n        airDir[idx] = levelDir;\n        continue;\n      }\n\n      // --- Ground, and the shadows cast across it ---\n      var shade = 0;\n      for (var tj = 0; tj < trees.length; tj++) {\n        var tr = trees[tj];\n        if (tr.d <= dg) continue;                      // shadows fall toward the viewer only\n        var tt = tr.d - dg;\n        var xc = tr.gx - tt * sx;\n        var half = tr.wt * 0.5 * (1 + 0.05 * tt);      // the penumbra spreads with distance\n        var e = 1 - smooth(0.75, 1.1, Math.abs(gxg - xc) / half);\n        if (e > shade) shade = e;\n      }\n      var hs = Math.exp(-(dg - 0.8) / (HAZE * 1.4));\n      var Tg = base + 0.40 * shade * hs;\n      airTone2[idx] = Tg < 0 ? 0 : Tg > 1 ? 1 : Tg;\n      airDir[idx] = levelDir;                          // ground is ruled level, like the air\n      // The shadows run to the sun's point on the horizon, so their marks do.\n      var du = u - sunU, dv = (v - hv) / ASPECT;\n      rayDir[idx] = Math.atan2(dv, du) + (du < 0 ? Math.PI : 0);\n      if (shade * hs > 0.35 + 0.08 * edge(u, v)) shadowMask[idx] = 1;\n    }\n  }\n\n  // --- Publish ----------------------------------------------------------\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(N * 3);\n    for (var n = 0; n < N; n++) {\n      var a = ang[n] + (skew || 0);\n      var m = mag[n];\n      if (!isFinite(a)) a = 0;\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  /** Gate a tone map at a threshold, frayed by a lattice (or not at all, when null). */\n  function gate(tone, th, fray) {\n    var m = new Float32Array(N);\n    for (var n = 0; n < N; n++) {\n      if (tone[n] <= 0) continue;\n      var uu = (n % COLS) / (COLS - 1), vv = Math.floor(n / COLS) / (ROWS - 1);\n      var t = fray ? th + 0.07 * fray(uu, vv) : th;\n      m[n] = tone[n] >= t ? 1 : 0;\n    }\n    return m;\n  }\n\n  // Air and ground. Unlike the barley, the first pass IS frayed: the glow is\n  // meant to be bare paper, and its edge is the one boundary in the plate that\n  // must not show.\n  // ⚠️ The first two passes do NOT share a threshold, against the squall's\n  // rule that crossing must start at the bottom of the stack. Shared, they put\n  // two crossed passes over nearly the whole plate and it went to one murky\n  // middle value. Most of the air here is a single ruling, as an engraved sky\n  // is; the kink lattice is what keeps it from beating into corrugation.\n  // 🔴 Passes 2 and 4 are traced LEFTWARD (skew + PI: the same orientation,\n  // the other way along it). The flow-line stepper traces forward only from\n  // each seed point and stops at the bounds, so the upstream edge of every\n  // pass is thin for one line's length: lines seeded there only cover what\n  // lies downstream. With all four passes running right, the left edge of the\n  // plate measured 0.76 luminance against 0.30 a little further in, and the\n  // ragged line starts along that ramp read as a scribbled border. Two passes\n  // each way puts two full-density passes ending at each edge.\n  pack('air1', gate(airTone2, 0.10, streak[0]), airDir, 0.06);\n  pack('air2', gate(airTone2, 0.22, streak[1]), airDir, -0.14 + Math.PI);\n  pack('air3', gate(airTone2, 0.34, streak[2]), airDir, 0.30);\n  pack('air4', gate(airTone2, 0.48, streak[3]), airDir, -0.40 + Math.PI);\n\n  // Trunks, gated on their tone in EXCESS of what lies behind them: grain\n  // first, then the crossing that turns them round. Four passes at four\n  // unrelated angles read as scribble; these stay close to the grain and only\n  // the third goes round the bole.\n  pack('trunk1', gate(trunkTone, 0.06, null), trunkAlong, 0);\n  pack('trunk2', gate(trunkTone, 0.26, dith[2]), trunkAlong, 0.12);\n  pack('trunk3', gate(trunkTone, 0.40, dith[3]), trunkAcross, 0);\n  pack('trunk4', gate(trunkTone, 0.52, dith[0]), trunkAlong, -0.16);\n\n  pack('shadow', shadowMask, rayDir, 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": 7788
   }
  },
  {
   "id": "air-1",
   "type": "painting:flow-lines",
   "name": "Air and Ground Pass 1 — Ruled",
   "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:air1",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7799,
    "lineCount": 26000,
    "seedDistribution": "grid-jittered",
    "lineLength": 18,
    "stepSize": 4,
    "lineWeight": 1.04,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.24,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "air-2",
   "type": "painting:flow-lines",
   "name": "Air and Ground Pass 2 — Crossed",
   "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:air2",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7811,
    "lineCount": 22000,
    "seedDistribution": "grid-jittered",
    "lineLength": 16,
    "stepSize": 4,
    "lineWeight": 1.04,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.22,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "air-3",
   "type": "painting:flow-lines",
   "name": "Air and Ground Pass 3 — Canopy and Shade",
   "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:air3",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7825,
    "lineCount": 24000,
    "seedDistribution": "grid-jittered",
    "lineLength": 12,
    "stepSize": 4,
    "lineWeight": 1.12,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.32,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "air-4",
   "type": "painting:flow-lines",
   "name": "Air and Ground Pass 4 — 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:air4",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7829,
    "lineCount": 22000,
    "seedDistribution": "grid-jittered",
    "lineLength": 9,
    "stepSize": 4,
    "lineWeight": 1.2,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.4,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "shadows",
   "type": "painting:flow-lines",
   "name": "Cast Shadows — Toward the Viewer",
   "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:shadow",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7855,
    "lineCount": 34000,
    "seedDistribution": "grid-jittered",
    "lineLength": 14,
    "stepSize": 4,
    "lineWeight": 1.16,
    "lineWeightVariation": 0.14,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.36,
    "paintMode": "multiply",
    "depthScale": true,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "trunk-1",
   "type": "painting:flow-lines",
   "name": "Trunk Pass 1 — Grain",
   "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:trunk1",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7859,
    "lineCount": 32000,
    "seedDistribution": "grid-jittered",
    "lineLength": 20,
    "stepSize": 4,
    "lineWeight": 1.08,
    "lineWeightVariation": 0.1,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.27,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "trunk-2",
   "type": "painting:flow-lines",
   "name": "Trunk 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:trunk2",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7871,
    "lineCount": 30000,
    "seedDistribution": "grid-jittered",
    "lineLength": 18,
    "stepSize": 4,
    "lineWeight": 1.12,
    "lineWeightVariation": 0.1,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.3,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "trunk-3",
   "type": "painting:flow-lines",
   "name": "Trunk Pass 3 — Round the Bole",
   "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:trunk3",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7885,
    "lineCount": 30000,
    "seedDistribution": "grid-jittered",
    "lineLength": 9,
    "stepSize": 4,
    "lineWeight": 1.16,
    "lineWeightVariation": 0.1,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.34,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 1.00]",
    "maskCenterY": -1,
    "maskSpread": 0.25
   }
  },
  {
   "id": "trunk-4",
   "type": "painting:flow-lines",
   "name": "Trunk 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:trunk4",
    "fieldCols": 400,
    "fieldRows": 300,
    "minMagnitude": 0.5,
    "seed": 7889,
    "lineCount": 26000,
    "seedDistribution": "grid-jittered",
    "lineLength": 16,
    "stepSize": 4,
    "lineWeight": 1.24,
    "lineWeightVariation": 0.1,
    "taper": "none",
    "color": "#2b1a12",
    "colorVariation": 0.05,
    "opacity": 0.38,
    "paintMode": "multiply",
    "depthScale": false,
    "horizonY": 0.61,
    "depthWeightRange": "[0.70, 1.08]",
    "depthOpacityRange": "[0.90, 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": 7788,
    "monochrome": true
   }
  }
 ]
}