
Fall of a Ridge
A ridge above a coast in plan, drawn with hachures and nothing else. The strokes run straight down the fall line, the way water would run off the ground, and stand in courses between breaks of height. They crowd where the ground is steeper and where it turns away from a light in the north-west, and flat ground gets none, so the crest of the ridge and the level upland behind it are bare paper. Spurs come down off the ridge toward the sea, each with a lit flank and a dark one. Offshore the sea is water-lined: lines laid parallel to the shore, each gap a little wider than the last, until the lining gives out. There are no streams, soundings or depth curves, and no border but the plate mark. The sheet is the relief alone, which is the one thing a hachured map draws that no other kind of drawing does. The seed decides the bearing of the coast, where the ridge stands, and how the spurs come down off it. Each seed is a different piece of ground.
Technique
`painting:flow-lines` layers reading tone and direction channels that the sketch publishes on the algorithm data bridge. The sketch itself makes no marks. The sketch builds a height field, then publishes its downhill direction and a tone taken from steepness and light, broken into courses at fixed intervals of height. Four hachure passes read that tone at rising thresholds, so steeper and darker ground takes more of them, and half are traced uphill along the same strokes, because a flow line runs only forward from where it starts and would otherwise thin the top edge of every course. The spurs are shoulders that fade out at the crest, not bumps added on top of it; added on top, every junction became a summit ringed by its own courses. The shoreline and the water-lining follow a smoothed distance from the land. This is the first drawing made for Relief of a Coast, kept as a sheet of its own when that work was re-engraved.
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.
4410
5150
1024
Layer stack
In paint order. Every mark in the image comes from these; there is no handwritten drawing code.
textures:paperSheetpainting:flow-linesWater-Lining — Parallel to the Shorepainting:flow-linesShorelinepainting:flow-linesHachures 1 — All Sloping Groundpainting:flow-linesHachures 2 — Steeper, Traced Uphillpainting:flow-linesHachures 3 — Steep or Turned from the Lightpainting:flow-linesHachures 4 — Steepest, Traced Uphillshapes:rectPlate Markfilter:grainPlate Tone
Source
The complete composition. Open it in GenArt to re-render, re-seed, or take it apart.
{
"genart": "1.2",
"id": "fall-of-a-ridge",
"title": "Fall of a Ridge",
"created": "2026-09-11T00:00:00Z",
"modified": "2026-09-11T17:05:40.872Z",
"renderer": {
"type": "canvas2d",
"version": "1.x"
},
"canvas": {
"width": 1400,
"height": 1000
},
"parameters": [],
"colors": [],
"dataChannels": [
{
"name": "hach1",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "hach2",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "hach3",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "hach4",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "coast",
"type": "vector",
"cols": 400,
"rows": 300
},
{
"name": "lining",
"type": "vector",
"cols": 400,
"rows": 300
}
],
"state": {
"seed": 4410,
"params": {},
"colorPalette": [
"#1d2327",
"#3b474f",
"#6c7a82",
"#b3bab8",
"#ebe8e0"
]
},
"algorithm": "// Fall of a Ridge — 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 stretch of coast drawn the way an engraved survey map draws ground: in\n// plan, from directly above, with no view and no horizon. Relief is shown by\n// hachures, short strokes that run straight down the fall line, laid in tiers\n// between breaks of height. A stroke's DIRECTION is the way water would run\n// off that slope, and its DENSITY is how steep the slope is and how far it is\n// turned from a light in the north-west. Flat ground carries no strokes at\n// all, so plateaus and valley floors are bare paper. The sea is water-lined:\n// lines laid parallel to the shore, each further out than the last and each\n// gap wider, until the lining gives out into open water.\n//\n// The same two rules as the rest of the series:\n// the direction of a hatch = the surface 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 var COLS = Math.round(200 * W / 700), ROWS = Math.round(150 * H / 500);\n // The field maps onto the inset plate, so this is the plate's aspect.\n var ASPECT = (W * 0.85) / (H * 0.81);\n var DX = ASPECT / (COLS - 1), DY = 1 / (ROWS - 1); // cell size, plate heights\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 + 104729);\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: an out-of-range sample becomes a NaN in a published channel\n // and takes the render down 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 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 // --- The lie of the land ------------------------------------------------\n // The coast runs ACROSS the plate and the land comes in from its edges. An\n // island set in the middle of the sheet is the map equivalent of a specimen\n // floating in the middle of the paper: the most generic thing it can do.\n var th = rand() * Math.PI * 2;\n var nx0 = Math.cos(th), ny0 = Math.sin(th); // from sea toward land\n var px0 = -ny0, py0 = nx0; // along the coast\n var cx = ASPECT / 2, cy = 0.5;\n // How much of the plate is land: about three fifths. At half, a whole side\n // of the plate was open water with nothing in it.\n var off = 0.06 + rand() * 0.12;\n var ridgeS = 0.36 + rand() * 0.12; // the main ridge, inland\n var ph = [];\n for (var i = 0; i < 8; i++) ph.push(rand() * Math.PI * 2);\n // Spurs run off the main ridge toward the sea. Where they reach it they make\n // headlands, and the valleys between them make the bays.\n var spurs = [];\n var nSp = 4 + Math.floor(rand() * 3);\n for (var k = 0; k < nSp; k++) {\n spurs.push({\n t: -0.95 + (k + 0.2 + rand() * 0.6) * (1.9 / nSp),\n w: 0.045 + rand() * 0.04,\n hgt: 0.14 + rand() * 0.12,\n bend: (rand() * 2 - 1) * 0.25,\n reach: -0.10 + rand() * 0.14, // how far into the sea it runs\n });\n }\n var n1 = lattice(7, 5), n2 = lattice(15, 11), n3 = lattice(33, 25);\n\n function height(x, y) {\n var dx = x - cx, dy = y - cy;\n var s = dx * nx0 + dy * ny0 + off; // + inland, - out to sea\n var t = dx * px0 + dy * py0; // along the coast\n var u = x / ASPECT, v = y;\n // Coastal plain rising to an upland that levels off behind the ridge.\n var h = 0.26 * Math.tanh(s * 3.4);\n // The main ridge, only slightly uneven along its length. 🔴 Every crest of\n // that unevenness is a summit, and a hachured summit is ringed by its own\n // courses, so a ridge that rose and fell by a third strung a row of\n // concentric ovals along the plate and it read as a fingerprint. Kept\n // low, the crest runs as one long ridge and the courses run along it.\n var rh = 0.30 + 0.045 * Math.sin(t * 3.1 + ph[0]) + 0.025 * Math.sin(t * 7.7 + ph[1]);\n var prof = Math.exp(-Math.pow((s - ridgeS) / 0.15, 2));\n h += rh * prof;\n for (var q = 0; q < spurs.length; q++) {\n var sp = spurs[q];\n // A spur bends as it runs down, so the valleys between do not line up\n // like the teeth of a comb.\n var tc = sp.t + sp.bend * (ridgeS - s);\n // Seaward of the crest only. Left at 1 inland, each spur came back on\n // the far side of the ridge as a ridge of its own and drew horseshoes of\n // courses along the plate edge.\n var along = smooth(sp.reach - 0.08, ridgeS, s) * smooth(ridgeS + 0.06, ridgeS, s);\n // 🔴 A spur is a shoulder coming DOWN off the ridge, so it fades out at\n // the crest (1 - prof). Added on top of the ridge instead, every junction\n // stood higher than the crest either side of it: a summit, ringed by its\n // own courses, and a row of them read as a fingerprint. Because the\n // ridge outweighs any spur, height now climbs all the way up each spur\n // to the crest and no junction can become a peak.\n h += sp.hgt * along * (1 - prof) * Math.exp(-Math.pow((t - tc) / sp.w, 2));\n }\n // 🔴 Kept faint. Every lump in the height field is a summit, and a summit\n // is drawn as a white spot with hachures radiating from it, so the finer\n // lattices at their first strength strewed the land with starbursts. The\n // relief is carried by the ridge and the spurs; these only roughen them.\n h += 0.026 * n1(u, v) + 0.012 * n2(u, v) + 0.004 * n3(u, v);\n return h;\n }\n\n var N = COLS * ROWS;\n var hmap = new Float32Array(N);\n for (var r = 0; r < ROWS; r++)\n for (var c = 0; c < COLS; c++)\n hmap[r * COLS + c] = height(c * DX, r * DY);\n\n function at(a, c, r) {\n if (c < 0) c = 0; else if (c > COLS - 1) c = COLS - 1;\n if (r < 0) r = 0; else if (r > ROWS - 1) r = ROWS - 1;\n return a[r * COLS + c];\n }\n\n // --- Hachures -------------------------------------------------------------\n var slope = new Float32Array(N), fall = new Float32Array(N), facing = new Float32Array(N);\n var LX = -Math.SQRT1_2, LY = -Math.SQRT1_2; // toward the light, north-west\n var samples = [];\n for (var r2 = 0; r2 < ROWS; r2++) {\n for (var c2 = 0; c2 < COLS; c2++) {\n var n = r2 * COLS + c2;\n var gx = (at(hmap, c2 + 1, r2) - at(hmap, c2 - 1, r2)) / (2 * DX);\n var gy = (at(hmap, c2, r2 + 1) - at(hmap, c2, r2 - 1)) / (2 * DY);\n var g = Math.sqrt(gx * gx + gy * gy);\n slope[n] = g;\n // Downhill: the way water runs off it.\n fall[n] = Math.atan2(-gy, -gx);\n facing[n] = g > 1e-6 ? (-gx * LX - gy * LY) / g : 0; // +1 faces the light\n if (hmap[n] > 0 && (r2 * 7 + c2) % 11 === 0) samples.push(g);\n }\n }\n samples.sort(function (a, b) { return a - b; });\n // Steepness is read against this coast's own steep ground, so every seed\n // spends the whole tonal range whatever its relief happens to be.\n var smax = samples.length ? samples[Math.floor(samples.length * 0.92)] : 1;\n\n var landTone = new Float32Array(N);\n // The courses. Without them the strokes piled up as hair: every spur a\n // feather, every summit an eye with lashes. With them over a LUMPY field\n // they drew a contour ring round every lump and the land read as a\n // fingerprint. Over smooth relief they run in long bands along the ridges\n // and spurs, which is what a survey sheet's hachures actually do. The\n // interval is set from this coast's own steep ground, so the courses on the\n // steepest slopes stand about 18px apart whatever the seed.\n var TIER = 0.022 * smax;\n for (var m = 0; m < N; m++) {\n if (hmap[m] <= 0) continue;\n var sn = Math.min(1.25, slope[m] / smax);\n var f = facing[m];\n // Steepness first, as the survey hachure is defined; then the oblique\n // light, which is what makes the ground read as form rather than as a\n // chart of gradients. A slope turned away from the light goes darker, one\n // turned toward it goes lighter, and flat ground stays bare either way.\n var T = 0.04 + 0.46 * Math.pow(sn, 0.85);\n T += 0.38 * sn * (f < 0 ? -f : 0);\n T -= 0.14 * sn * (f > 0 ? f : 0);\n // The break between courses is narrow. At a quarter of the interval every\n // course was a band of ink with bare paper either side, and the land read\n // as zebra stripe or wood grain with the light drowned under it. On a\n // survey sheet the courses are felt more than seen.\n var tier = hmap[m] / TIER - Math.floor(hmap[m] / TIER);\n if (tier < 0.12) T = 0;\n landTone[m] = T < 0 ? 0 : T > 1 ? 1 : T;\n }\n\n // --- The sea ------------------------------------------------------------\n // Distance from the shore, in cells, by a two-pass chamfer.\n var dist = new Float32Array(N);\n var BIG = 1e6;\n for (var a2 = 0; a2 < N; a2++) dist[a2] = hmap[a2] > 0 ? 0 : BIG;\n var D1 = 1, D2 = Math.SQRT2;\n for (var r3 = 0; r3 < ROWS; r3++) for (var c3 = 0; c3 < COLS; c3++) {\n var i3 = r3 * COLS + c3, best = dist[i3];\n if (c3 > 0) best = Math.min(best, dist[i3 - 1] + D1);\n if (r3 > 0) {\n best = Math.min(best, dist[i3 - COLS] + D1);\n if (c3 > 0) best = Math.min(best, dist[i3 - COLS - 1] + D2);\n if (c3 < COLS - 1) best = Math.min(best, dist[i3 - COLS + 1] + D2);\n }\n dist[i3] = best;\n }\n for (var r4 = ROWS - 1; r4 >= 0; r4--) for (var c4 = COLS - 1; c4 >= 0; c4--) {\n var i4 = r4 * COLS + c4, b4 = dist[i4];\n if (c4 < COLS - 1) b4 = Math.min(b4, dist[i4 + 1] + D1);\n if (r4 < ROWS - 1) {\n b4 = Math.min(b4, dist[i4 + COLS] + D1);\n if (c4 < COLS - 1) b4 = Math.min(b4, dist[i4 + COLS + 1] + D2);\n if (c4 > 0) b4 = Math.min(b4, dist[i4 + COLS - 1] + D2);\n }\n dist[i4] = b4;\n }\n // The shoreline direction comes from a smoothed copy, because the chamfer's\n // own gradient steps in eighths of a turn and the lining would follow it.\n var sm = new Float32Array(dist), tmp = new Float32Array(N);\n for (var pass = 0; pass < 4; pass++) {\n for (var r5 = 0; r5 < ROWS; r5++) for (var c5 = 0; c5 < COLS; c5++) {\n var acc = 0, cnt = 0;\n for (var oy = -2; oy <= 2; oy++) for (var ox = -2; ox <= 2; ox++) {\n var cc = c5 + ox, rr = r5 + oy;\n if (cc < 0 || rr < 0 || cc >= COLS || rr >= ROWS) continue;\n acc += Math.min(sm[rr * COLS + cc], 60); cnt++;\n }\n tmp[r5 * COLS + c5] = acc / cnt;\n }\n var sw = sm; sm = tmp; tmp = sw;\n }\n var shoreDir = new Float32Array(N), coast = new Float32Array(N), lining = new Float32Array(N);\n // Each line a little further out than the last and each gap a little wider,\n // which is how a water-lined chart makes the sea deepen away from the shore.\n var BANDS = [];\n for (var k2 = 1; k2 <= 16; k2++) BANDS.push(1.6 + 2.1 * Math.pow(k2, 1.32));\n for (var r6 = 0; r6 < ROWS; r6++) for (var c6 = 0; c6 < COLS; c6++) {\n var i6 = r6 * COLS + c6;\n var gx6 = (at(sm, c6 + 1, r6) - at(sm, c6 - 1, r6)) / 2;\n var gy6 = (at(sm, c6, r6 + 1) - at(sm, c6, r6 - 1)) / 2;\n shoreDir[i6] = Math.atan2(gy6, gx6) + Math.PI / 2;\n var d = dist[i6];\n if (d <= 0) continue;\n if (d <= 1.6) coast[i6] = 1;\n for (var b = 0; b < BANDS.length; b++) {\n if (Math.abs(d - BANDS[b]) < 0.62) { lining[i6] = 1; break; }\n }\n }\n\n // --- Publish ------------------------------------------------------------\n var gl = (typeof globalThis !== 'undefined') ? globalThis : window;\n gl.__genart_data = gl.__genart_data || {};\n gl.__genart_data.cols = COLS;\n gl.__genart_data.rows = ROWS;\n\n function pack(name, mag, ang, skew) {\n var f32 = new Float32Array(N * 3);\n for (var p = 0; p < N; p++) {\n var an = ang[p] + (skew || 0);\n var mg = mag[p];\n if (!isFinite(an)) an = 0;\n if (!isFinite(mg)) mg = 0;\n f32[p * 3] = Math.cos(an);\n f32[p * 3 + 1] = Math.sin(an);\n f32[p * 3 + 2] = mg;\n }\n gl.__genart_data[name] = f32;\n }\n var dith = [lattice(83, 63), lattice(79, 59), lattice(89, 67), lattice(73, 55)];\n function gate(tone, thr, fray) {\n var out = new Float32Array(N);\n for (var p = 0; p < N; p++) {\n if (tone[p] <= 0) continue;\n var uu = (p % COLS) / (COLS - 1), vv = Math.floor(p / COLS) / (ROWS - 1);\n // Small: at 0.06 the edges of flat ground came out moth-eaten.\n out[p] = tone[p] >= thr + 0.03 * fray(uu, vv) ? 1 : 0;\n }\n return out;\n }\n\n // Hachures run straight down the fall line and never cross, so the passes\n // barely diverge. Half are traced UPHILL (skew + PI: the same stroke, the\n // other way along it). The stepper traces forward only, so a pass traced\n // one way thins the upstream edge of every tier; two each way cancels it.\n // The first threshold is high enough that gentle ground stays bare paper:\n // on a hachured map, flat ground carrying strokes looks like a slope.\n pack('hach1', gate(landTone, 0.20, dith[0]), fall, 0);\n pack('hach2', gate(landTone, 0.28, dith[1]), fall, Math.PI + 0.05);\n pack('hach3', gate(landTone, 0.42, dith[2]), fall, -0.05);\n pack('hach4', gate(landTone, 0.56, dith[3]), fall, Math.PI);\n pack('coast', coast, shoreDir, 0);\n pack('lining', lining, shoreDir, 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": "#ebe8e0",
"seed": 4410
}
},
{
"id": "lining",
"type": "painting:flow-lines",
"name": "Water-Lining — Parallel to the Shore",
"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:lining",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4421,
"lineCount": 30000,
"seedDistribution": "grid-jittered",
"lineLength": 26,
"stepSize": 4,
"lineWeight": 0.92,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.55,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "coast",
"type": "painting:flow-lines",
"name": "Shoreline",
"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:coast",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4433,
"lineCount": 30000,
"seedDistribution": "grid-jittered",
"lineLength": 22,
"stepSize": 4,
"lineWeight": 1.6,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.7,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "hach-1",
"type": "painting:flow-lines",
"name": "Hachures 1 — All Sloping Ground",
"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:hach1",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4441,
"lineCount": 24000,
"seedDistribution": "grid-jittered",
"lineLength": 4,
"stepSize": 4,
"lineWeight": 1.12,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.52,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "hach-2",
"type": "painting:flow-lines",
"name": "Hachures 2 — Steeper, Traced Uphill",
"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:hach2",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4453,
"lineCount": 22000,
"seedDistribution": "grid-jittered",
"lineLength": 4,
"stepSize": 4,
"lineWeight": 1.16,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.54,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "hach-3",
"type": "painting:flow-lines",
"name": "Hachures 3 — Steep or Turned from the Light",
"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:hach3",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4469,
"lineCount": 21000,
"seedDistribution": "grid-jittered",
"lineLength": 4,
"stepSize": 4,
"lineWeight": 1.24,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.58,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"maskCenterY": -1,
"maskSpread": 0.25
}
},
{
"id": "hach-4",
"type": "painting:flow-lines",
"name": "Hachures 4 — Steepest, Traced Uphill",
"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:hach4",
"fieldCols": 400,
"fieldRows": 300,
"minMagnitude": 0.5,
"seed": 4477,
"lineCount": 19000,
"seedDistribution": "grid-jittered",
"lineLength": 4,
"stepSize": 4,
"lineWeight": 1.32,
"lineWeightVariation": 0.12,
"taper": "none",
"color": "#1d2327",
"colorVariation": 0.05,
"opacity": 0.62,
"paintMode": "multiply",
"depthScale": false,
"horizonY": 0,
"depthWeightRange": "[1, 1]",
"depthOpacityRange": "[1, 1]",
"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": "#6c7a82",
"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": 4410,
"monochrome": true
}
}
]
}