genart.dev
Download GenArt
Squall Line

Squall Line

Engraved · 2026 · 1400×1000 · Canvas 2D

A line squall crossing open ground. The shelf cloud has already come over, so it fills the sky to the top of the sheet and its base runs across the plate as a hard edge — the one hard edge in the series, and hard because a shelf cloud really has one. Rain falls from it in separate falls rather than as a single sheet, and the gaps between them are how you read the distance into it. At one end the cloud, the rain and the ground are a single continuous dark. At the other the rain has not arrived, and the strip of bare paper between the cloud base and the horizon is the only light in the picture. Everything is graded on the distance from that strip. The cloud is lit from underneath by it, so it pales toward the light and deepens away from it; the rain thins toward it; the ground lifts under it and darkens where a fall is standing on it. Nothing in the plate varies for its own sake. The hatch is the direction of the air. It runs with the front where it leaves the shelf and flattens into the body of the cloud above, and it falls with the rain, leaning hard where the rain leaves the cloud and standing up as it nears the ground, because the wind is stronger aloft. That curve is the whole reason a squall reads as moving rather than as hanging.

Technique

value is built the way an engraver builds it, by returning to the plate rather than by pressing harder. The sky is five `plugin-painting` flow-line passes covering progressively smaller areas as the tone threshold rises. The first two share a threshold and cross each other, so the ground tone of the sky is already two passes laid at an angle: marks that all lie within a few degrees of one orientation beat against each other, and over a whole sky that beat resolves into a regular ripple that reads as corrugated iron. The crossing has to start at the bottom of the stack. Further layers draw the leading edge, two crossed passes of rain, and the ground. The ground is `plugin-textures` rough paper, the impression is a `plugin-shapes` rule, and `plugin-filters` grain carries the plate tone. The sketch itself draws nothing: it computes the shelf, the falls, a tone map and an air-direction map from the seed and publishes them on the ADR 062 data bridge, and every line in the picture is made by a layer reading those channels. Changing the seed moves the front, turns it end for end, and puts the light on the other side.

Seeds

The same system at three seeds. The composition itself re-cuts: the geometry is derived from the seed, so masses, edges and placement all move, while the palette and the drawing language stay put.

  1. Squall Line, seed 44774477
  2. Squall Line, seed 812812
  3. Squall Line, seed 12651265

Layer stack

In paint order. Every mark in the image comes from these; there is no handwritten drawing code.

  1. textures:paperSheet
  2. painting:flow-linesSky Pass 1 — Shelf
  3. painting:flow-linesSky Pass 2 — Crossing the Shelf
  4. painting:flow-linesSky Pass 3 — Half Tone
  5. painting:flow-linesSky Pass 4 — Shadow
  6. painting:flow-linesSky Pass 5 — Cross, Deepest Only
  7. painting:flow-linesLeading Edge
  8. painting:flow-linesRain — Curtain
  9. painting:flow-linesRain — Squall, Crossing
  10. painting:flow-linesGround — Level Marks
  11. painting:flow-linesGround — Under the Weather
  12. shapes:rectPlate Mark
  13. filter:grainPlate Tone

Source

The complete composition. Open it in GenArt to re-render, re-seed, or take it apart.

squall-line.genart
{
  "genart": "1.2",
  "id": "squall-line",
  "title": "Squall Line",
  "created": "2026-09-10T00:00:00Z",
  "modified": "2026-09-10T19:52:10.496Z",
  "renderer": {
    "type": "canvas2d",
    "version": "1.x"
  },
  "canvas": {
    "width": 1400,
    "height": 1000
  },
  "parameters": [],
  "colors": [],
  "dataChannels": [
    {
      "name": "cloud1",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "cloud2",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "cloud3",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "cloud4",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "cloud5",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "edge",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "rain1",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "rain2",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "ground1",
      "type": "vector",
      "cols": 400,
      "rows": 300
    },
    {
      "name": "ground2",
      "type": "vector",
      "cols": 400,
      "rows": 300
    }
  ],
  "state": {
    "seed": 4477,
    "params": {},
    "colorPalette": [
      "#2b1a12",
      "#5c3320",
      "#8a5a3c",
      "#c9a982",
      "#ece3d4"
    ]
  },
  "algorithm": "// Squall Line — field generation only. This code makes NO marks.\n// It publishes a tone map and an air-direction map on the ADR 062 data bridge;\n// every line in the picture is drawn by a plugin layer that reads them.\n//\n// The same two rules as the other two plates in the series:\n//   the direction of a hatch = the direction of the air it describes\n//   the density of a hatch   = how far that surface is turned from the light\n//\n// A line squall crossing open ground. The shelf cloud has already come over,\n// so it fills the sky to the top of the sheet and its base runs across the\n// plate as a hard edge — the one hard edge in the series, and hard because a\n// shelf cloud really has one. Rain falls from it in separate shafts. Ahead of\n// the front the rain has not arrived, and the only light in the picture is the\n// strip of bare paper between the cloud base and the horizon on that side.\n// Everything else is graded away from that strip: the cloud is lit from below\n// by it, so the cloud pales toward it and deepens away from it.\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  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, indexed in [0,1] so its\n   *  features are a fixed fraction of the plate and scale with it. Inputs are\n   *  clamped: a sample past the edge returns undefined, which becomes a NaN\n   *  direction, and a NaN direction walks a flow line to a NaN position and\n   *  kills the whole render with an undefined field sample. */\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      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  var mass  = lattice(13, 11);    // the slow turning of the cloud body\n  var sweep = lattice(7, 5);      // and the one broad swing of its hatch\n  var curl  = lattice(37, 29);    // ragged detail on the underside\n  var kink  = lattice(151, 113);  // finer than the spacing between lines\n  var body  = lattice(29, 23);    // the second scale of the cloud's own mass\n  // 🔴 Wide and very short on purpose. Rain has to break up ALONG its fall, not\n  // into blobs: a square-ish lattice cut the curtains into patches and they\n  // read as hanging cloth. Four pixels across and forty down gives streaks.\n  var veil  = lattice(104, 5);    // breaks the rain into separate falls\n  var dith  = [lattice(83, 63), lattice(79, 59), lattice(89, 67), lattice(73, 55)];\n\n  // --- The picture ------------------------------------------------------\n  var hyMid  = 0.822 + rand() * 0.045;          // the horizon, low: this is a sky\n  /** Open ground is not a ruled line. It lifts and falls, and a straight one\n   *  across the foot of the plate reads as a table edge under the weather. */\n  function horizon(u) {\n    return hyMid\n         + 0.011 * Math.sin(u * 2.6 + ph[3])\n         + 0.006 * Math.sin(u * 6.1 + ph[4])\n         + 0.007 * curl(u, 0.92);\n  }\n  // Which side the front came from, and therefore which side the light is on.\n  var side   = rand() < 0.5 ? -1 : 1;\n  var gapU   = side < 0 ? 0.06 + rand() * 0.16 : 0.78 + rand() * 0.16;\n  var cbMid  = 0.44 + rand() * 0.07;            // the cloud base at mid-frame\n  var cbTilt = (0.17 + rand() * 0.15) * -side;  // it lifts toward the light\n  // The nose of the shelf, where the base hangs lowest. A leading edge drawn\n  // as a straight rule cuts the plate in half and reads as a horizon, not as\n  // weather; the arc is what says this thing is coming toward you.\n  var noseU  = 0.22 + rand() * 0.50;\n  var noseA  = 0.075 + rand() * 0.070;\n  var nose2U = 0.10 + rand() * 0.80;\n\n  /** The underside of the shelf. Hard, and tilted so it lifts toward the gap. */\n  function cloudBase(u) {\n    return cbMid + cbTilt * (u - 0.5)\n         + noseA * Math.exp(-Math.pow((u - noseU) * 2.7, 2))\n         + noseA * 0.45 * Math.exp(-Math.pow((u - nose2U) * 6.5, 2))\n         + 0.017 * Math.sin(u * 3.7 + ph[0])\n         + 0.009 * Math.sin(u * 8.9 + ph[1])\n         + 0.014 * curl(u, 0.5);\n  }\n\n  // The rain. Separate shafts, because a squall line does not fall as one\n  // sheet — it falls in falls, and the gaps between them are how you read the\n  // distance into it. Each has a place, a width, and a reach: some get to the\n  // ground and some hang short of it as virga.\n  var SHAFTS = 4 + (rand() < 0.55 ? 1 : 0);\n  var shafts = [];\n  var far = side < 0 ? 1 : 0;                   // the end furthest from the light\n  // The first one is the wall: wide, heavy, and reaching the ground, so that\n  // at the far end the cloud, the rain and the ground are one continuous dark\n  // and the plate reads as a diagonal instead of as three stacked bands. It is\n  // the difference between weather standing over a landscape and weather\n  // arriving across it.\n  shafts.push({\n    u: far + (gapU - far) * (0.02 + 0.10 * rand()),\n    w: 0.11 + rand() * 0.07,\n    reach: 1.45 + rand() * 0.45,\n    wt: 1.0,\n  });\n  for (var q = 1; q < SHAFTS; q++) {\n    var t = (q + 0.22 + rand() * 0.56) / SHAFTS;\n    shafts.push({\n      // Strung out over the first three fifths of the way to the light and\n      // no further. Run right up to it and the falls close the gap off, and\n      // the one passage of bare paper in the plate — the whole of its light —\n      // becomes a few holes between curtains.\n      u: far + (gapU - far) * (0.12 + 0.58 * t),\n      w: 0.042 + rand() * 0.062,\n      reach: 0.88 + rand() * 0.58,                // >1 means it reaches the ground\n      wt: 0.42 + rand() * 0.44,\n    });\n  }\n\n  var cloudTone = new Float32Array(COLS * ROWS);\n  var cloudDir  = new Float32Array(COLS * ROWS);\n  var edgeTone  = new Float32Array(COLS * ROWS);\n  var rainTone  = new Float32Array(COLS * ROWS);\n  var rainDir   = new Float32Array(COLS * ROWS);\n  var groundTone = new Float32Array(COLS * ROWS);\n  var groundDir  = 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      var cb = cloudBase(u);\n      var hy = horizon(u);\n      // How far this column is from the light, 0 at the gap and 1 at the far\n      // end. Every value in the plate is graded on this one number.\n      var away = Math.abs(u - gapU) / Math.max(0.12, Math.max(gapU, 1 - gapU));\n      if (away > 1) away = 1;\n\n      if (v < cb) {\n        // ---- The cloud ------------------------------------------------\n        // Its rolls run parallel to the base, so their direction is the slope\n        // of the base itself. That slope is the only thing setting direction\n        // up here, which is what keeps a sky from turning into wallpaper.\n        var below = (cb - v) / Math.max(0.05, cb);   // 0 at the base, 1 at the top of the sheet\n\n        // Lit from below by the strip of clear sky, so the cloud is palest\n        // just above the gap and deepens away from it and up into the body.\n        // Darkest at the BASE and lifting into the body above it. Graded the\n        // other way the sky is one even weight from the top of the sheet down\n        // and there is nothing for the eye to go to; a storm cloud is heaviest\n        // where its water is, which is where it meets the air underneath.\n        var T = 0.15 + 0.36 * away - 0.11 * below;\n        // The body of it, at the scale of a whole mass rather than of a mark.\n        T += 0.26 * mass(u, v) + 0.13 * body(u, v);\n        // The rolls under the shelf: bands parallel to the base, and only\n        // near it. Run up through the whole sky they read as corduroy.\n        T += 0.085 * Math.sin(below * 11 + ph[2] + u * 1.4) * Math.exp(-below * 4.2);\n        T += 0.075 * curl(u, v);\n        // Where a shaft hangs, the cloud above it is the heaviest part of the\n        // sky — that is where the water is.\n        for (var q2 = 0; q2 < shafts.length; q2++) {\n          var dd = (u - shafts[q2].u) / shafts[q2].w;\n          T += 0.20 * shafts[q2].wt * Math.exp(-dd * dd) * Math.exp(-below * 2.2);\n        }\n        // 🔴 A floor, for the same reason the barley has one. Where there is\n        // cloud there is cloud: a thin place in it is PALER, not a hole. Let\n        // the tone fall through the first threshold and the sky comes out\n        // torn, with hard-edged patches of bare paper punched through a dark\n        // mass. The first pass covers all of the cloud and every lighter and\n        // darker place in the sky is made by which of the LATER passes return.\n        if (T < 0.235) T = 0.235;\n        cloudTone[idx] = T;\n        cloudDir[idx] = 0;                          // set from the form, in the pass below\n\n        // The leading edge. A shelf cloud has a genuinely hard underside and\n        // it is the one hard edge in the series; drawn soft, the whole sky\n        // reads as fog instead of as a front.\n        if (below < 0.055) edgeTone[idx] = 1;\n      } else if (v < hy + 0.055) {\n        // ---- Under the cloud -------------------------------------------\n        // Rain, or the bare paper that is the only light in the plate.\n        var drop = (v - cb) / Math.max(0.02, hy - cb);   // 0 at the base, 1 at the ground\n        // 🔴 The rain runs ON past the horizon and fades out inside the ground\n        // rather than stopping at it. Two masks that merely abut leave a bright\n        // hairline where both interpolate under the magnitude gate — the same\n        // fault the hedge had in Wind in the Barley — and beyond that, rain\n        // standing on ground hides the ground: a fall that stops dead on the\n        // horizon line is a curtain hung in front of the landscape.\n        var past = v > hy ? 1 - (v - hy) / 0.055 : 1;\n        var R = 0;\n        for (var q3 = 0; q3 < shafts.length; q3++) {\n          var sh = shafts[q3];\n          var d2 = (u - sh.u) / sh.w;\n          var across = Math.exp(-d2 * d2);\n          // A fall thins out as it descends, and stops where it stops.\n          var down = drop < sh.reach ? 1 - Math.pow(drop / sh.reach, 2.1) : 0;\n          R += 1.25 * sh.wt * across * down * past;\n        }\n        R *= 0.74 + 0.34 * veil(u, v);               // broken into separate falls\n        R *= 0.42 + 0.58 * away;                     // and thinning toward the light\n        rainTone[idx] = R > 1 ? 1 : R;\n        // Falling, sheared: the wind is stronger aloft, so the rain leans hard\n        // where it leaves the cloud and stands up as it nears the ground. That\n        // curve is the whole reason a squall reads as moving.\n        rainDir[idx] = Math.PI / 2 - side * (0.50 * (1 - drop) + 0.06) + 0.12 * kink(u, v);\n      }\n      if (v >= hy) {\n        // ---- The ground -------------------------------------------------\n        // A strip, level, and dark under the weather but lifting where the\n        // light reaches it.\n        var gz = (v - hy) / Math.max(0.02, 1 - hy);\n        // Wet ground under the weather and dry ground under the light, and\n        // the near edge lifting as it comes forward out of the squall's\n        // shadow. A single flat value here reads as a plinth under the sky.\n        // Ground under a fall is wet ground, and dark: the rain has to land\n        // somewhere, and a squall that stops dead at the horizon is a curtain\n        // hung in front of a landscape rather than weather standing in it.\n        var wet = 0;\n        for (var q4 = 0; q4 < shafts.length; q4++) {\n          var d3 = (u - shafts[q4].u) / (shafts[q4].w * 1.5);\n          wet += shafts[q4].wt * Math.exp(-d3 * d3) * (shafts[q4].reach > 1 ? 1 : 0.35);\n        }\n        if (wet > 1) wet = 1;\n        groundTone[idx] = 0.24 + 0.26 * away + 0.16 * gz + 0.22 * wet\n                        + 0.11 * curl(u, v) + 0.09 * mass(u, v);\n        groundDir[idx] = 0.04 + 0.20 * (u - 0.5) + 0.10 * kink(u, v);\n      }\n    }\n  }\n\n  // --- The direction of the air ----------------------------------------\n  // 🔴 Direction in the cloud is a SYSTEM, not a map of the tone. Taking it\n  // perpendicular to the gradient of the tone map — hatching along the\n  // contours, which is a real way to shade a solid form — turns a lumpy sky\n  // into a topographic survey: contours of a lumpy field are closed loops, so\n  // every lump in the cloud got its own ring of concentric ovals and the plate\n  // read as a brain. Closed cells are the failure mode to stay away from here.\n  //\n  // What carries the form is the VALUE. The direction stays broadly one thing:\n  // the air runs along the front, so the hatch runs with the slope of the\n  // shelf's own base, swinging slowly and by a little. Wide swings make waves\n  // and the sky comes out as corduroy; this is deliberately narrow.\n  for (var ry = 0; ry < ROWS; ry++) {\n    for (var cx = 0; cx < COLS; cx++) {\n      var i2 = ry * COLS + cx;\n      if (cloudTone[i2] === 0) continue;\n      var uu0 = cx / (COLS - 1), vv0 = ry / (ROWS - 1);\n      // 🔴 Measured over a WIDE span. The base carries deliberate fine wobble\n      // so its edge is ragged, and differentiating that over a hundredth of\n      // the width returns the slope of the wobble rather than the slope of the\n      // front — swinging through most of a radian at the wobble's own spatial\n      // frequency. Since this angle is then applied to the whole column of\n      // cloud standing above it, the sky came out ruled with vertical folds\n      // about twenty pixels apart and read as hanging curtain. A wide\n      // difference averages the wobble out and returns the front.\n      var fall = Math.atan((cloudBase(Math.min(1, uu0 + 0.06)) - cloudBase(Math.max(0, uu0 - 0.06))) / 0.12);\n      // The rolls lie along the front where they leave it and flatten out\n      // into the body of the cloud above.\n      var above = 1 - cloudTone[i2] * 0;          // (kept explicit: see below)\n      void above;\n      cloudDir[i2] = fall * (1 - 0.45 * (vv0 < 0.5 ? 1 - vv0 * 2 : 0))\n      // 🔴 The broad swing is kept SMALL and the fine one carries the\n      // variation. A flow line steps along the field, so wherever the\n      // direction converges the lines bunch and lay down a dark seam: a coarse\n      // lattice at any real amplitude ruled the sky with vertical creases\n      // about its own cell width apart and the cloud read as hanging curtain.\n      // Fine variation averages out over a mark instead of steering it.\n                   + 0.12 * sweep(uu0, vv0)\n                   + 0.16 * curl(uu0, vv0)\n                   + 0.26 * kink(uu0, vv0);\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(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      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  // Tone is built by returning to the plate, as an engraver builds it. Each\n  // pass covers only what is at least as dark as its threshold and sits a\n  // little off the last, so the sky deepens by crossing. The first pass is not\n  // dithered — it is the cloud's own covering and holes punched through it\n  // show as daylight — and the ones above it are, so a pass frays out instead\n  // of ending on a contour.\n  var CLOUD_TH = [0.20, 0.20, 0.38, 0.53, 0.68];\n  // 🔴 Wide, and much wider than the other two plates in the series. Marks\n  // that all lie within a few degrees of one orientation beat against each\n  // other, and over a whole sky that beat resolves into a regular ripple: the\n  // cloud came out as corrugated iron.\n  //\n  // The first TWO share a threshold. Widening the later passes alone does not\n  // help, because the base pass covers the whole sky on its own and most of\n  // the plate is therefore carrying one orientation and nothing else — the\n  // crossing has to start at the bottom of the stack, not above it. So the\n  // ground tone of the sky is itself two passes laid across each other, and\n  // every value above it is built by returning at another angle again.\n  var CLOUD_SKEW = [0.12, -0.62, 0.70, -0.28, 1.15];\n  for (var p = 0; p < CLOUD_TH.length; p++) {\n    var m1 = 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      var th = p < 2 ? CLOUD_TH[p]\n             : CLOUD_TH[p] + 0.070 * dith[p % 4](uu, vv) + 0.030 * curl(uu, vv);\n      m1[n2] = cloudTone[n2] >= th ? 1 : 0;\n    }\n    pack('cloud' + (p + 1), m1, cloudDir, CLOUD_SKEW[p]);\n  }\n  pack('edge', edgeTone, cloudDir, 0);\n\n  // Two rain passes, gated apart and crossed. A single pass of long parallel\n  // strokes is what rain wants to be and is exactly what aliases into dashes\n  // when the plate is resampled for the page, so the fall is built from short\n  // crossed marks instead.\n  var RAIN_TH = [0.12, 0.34];\n  var RAIN_SKEW = [0.05, -0.21];\n  for (var p2 = 0; p2 < RAIN_TH.length; p2++) {\n    var m2 = new Float32Array(COLS * ROWS);\n    for (var n3 = 0; n3 < COLS * ROWS; n3++) {\n      var uu2 = (n3 % COLS) / (COLS - 1), vv2 = Math.floor(n3 / COLS) / (ROWS - 1);\n      m2[n3] = rainTone[n3] >= RAIN_TH[p2] + 0.090 * dith[p2](uu2, vv2) ? 1 : 0;\n    }\n    pack('rain' + (p2 + 1), m2, rainDir, RAIN_SKEW[p2]);\n  }\n\n  var GROUND_TH = [0.30, 0.56];\n  for (var p3 = 0; p3 < GROUND_TH.length; p3++) {\n    var m3 = new Float32Array(COLS * ROWS);\n    for (var n4 = 0; n4 < COLS * ROWS; n4++) m3[n4] = groundTone[n4] >= GROUND_TH[p3] ? 1 : 0;\n    pack('ground' + (p3 + 1), m3, groundDir, p3 === 0 ? 0.03 : -0.28);\n  }\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": 4477
      }
    },
    {
      "id": "cloud-1",
      "type": "painting:flow-lines",
      "name": "Sky Pass 1 — Shelf",
      "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:cloud1",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4490,
        "lineCount": 26000,
        "seedDistribution": "grid-jittered",
        "lineLength": 10,
        "stepSize": 4,
        "lineWeight": 1.16,
        "lineWeightVariation": 0.16,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.24,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "cloud-2",
      "type": "painting:flow-lines",
      "name": "Sky Pass 2 — Crossing the Shelf",
      "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:cloud2",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4503,
        "lineCount": 24000,
        "seedDistribution": "grid-jittered",
        "lineLength": 9,
        "stepSize": 4,
        "lineWeight": 1.16,
        "lineWeightVariation": 0.16,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.24,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "cloud-3",
      "type": "painting:flow-lines",
      "name": "Sky Pass 3 — 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:cloud3",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4516,
        "lineCount": 22000,
        "seedDistribution": "grid-jittered",
        "lineLength": 8,
        "stepSize": 4,
        "lineWeight": 1.2,
        "lineWeightVariation": 0.16,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.29,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "cloud-4",
      "type": "painting:flow-lines",
      "name": "Sky Pass 4 — 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:cloud4",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4529,
        "lineCount": 21000,
        "seedDistribution": "grid-jittered",
        "lineLength": 7,
        "stepSize": 4,
        "lineWeight": 1.32,
        "lineWeightVariation": 0.16,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.39,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "cloud-5",
      "type": "painting:flow-lines",
      "name": "Sky Pass 5 — 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:cloud5",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4542,
        "lineCount": 18000,
        "seedDistribution": "grid-jittered",
        "lineLength": 6,
        "stepSize": 4,
        "lineWeight": 1.4,
        "lineWeightVariation": 0.16,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.47,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "edge",
      "type": "painting:flow-lines",
      "name": "Leading Edge",
      "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:edge",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4548,
        "lineCount": 34000,
        "seedDistribution": "grid-jittered",
        "lineLength": 6,
        "stepSize": 4,
        "lineWeight": 1.36,
        "lineWeightVariation": 0.3,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.44,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "rain-1",
      "type": "painting:flow-lines",
      "name": "Rain — Curtain",
      "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:rain1",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4566,
        "lineCount": 19000,
        "seedDistribution": "grid-jittered",
        "lineLength": 34,
        "stepSize": 4,
        "lineWeight": 1.04,
        "lineWeightVariation": 0.16,
        "taper": "tail",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.27,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "rain-2",
      "type": "painting:flow-lines",
      "name": "Rain — Squall, Crossing",
      "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:rain2",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4574,
        "lineCount": 15000,
        "seedDistribution": "grid-jittered",
        "lineLength": 24,
        "stepSize": 4,
        "lineWeight": 1.12,
        "lineWeightVariation": 0.16,
        "taper": "tail",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.34,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "ground-1",
      "type": "painting:flow-lines",
      "name": "Ground — Level Marks",
      "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:ground1",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4590,
        "lineCount": 9000,
        "seedDistribution": "grid-jittered",
        "lineLength": 9,
        "stepSize": 4,
        "lineWeight": 1.16,
        "lineWeightVariation": 0.28,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.3,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "maskCenterY": -1,
        "maskSpread": 0.25
      }
    },
    {
      "id": "ground-2",
      "type": "painting:flow-lines",
      "name": "Ground — Under the Weather",
      "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:ground2",
        "fieldCols": 400,
        "fieldRows": 300,
        "minMagnitude": 0.5,
        "seed": 4604,
        "lineCount": 7000,
        "seedDistribution": "grid-jittered",
        "lineLength": 7,
        "stepSize": 4,
        "lineWeight": 1.28,
        "lineWeightVariation": 0.28,
        "taper": "none",
        "color": "#2b1a12",
        "colorVariation": 0.05,
        "opacity": 0.4,
        "paintMode": "multiply",
        "depthScale": false,
        "horizonY": 0.5,
        "depthWeightRange": "[1.0, 1.0]",
        "depthOpacityRange": "[1.0, 1.0]",
        "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": 4477,
        "monochrome": true
      }
    }
  ]
}