Partial: I learned the procedure, but the copy doesn't pass at 8×.
Ferrari never painted sagebrush. The closest things he did paint:
I copied only the first. The other two I read by eye, and that's where the tussock mass (page 4) and the rim flutes (page 9) come from.

His Living Worlds scenes are index maps, so you can ask which indices a passage uses and paint each one a loud colour. lw.py (borrowed from the willow student) loads them. I counted the indices in the tuft crop, found that three dominated, and painted them:
import lw, numpy as np
from PIL import Image
d = lw.load("V11AM"); idx = d["idx"]
r = idx[410:480, 230:330]
out = np.zeros(r.shape + (3,), np.uint8); out[:] = [60, 90, 160] # everything else: blue
out[r == 49] = [0, 0, 0]; out[r == 50] = [200, 60, 60]; out[r == 51] = [255, 230, 120]
Image.fromarray(np.repeat(np.repeat(out, 8, 0), 8, 1)).save("stage0/v11am_tuft_idx_8x.png")
(The first time I ran it, np.zeros(...) + [60, 90, 160] silently promoted the array to int64 and PIL refused it. Assign into a uint8 array instead.)

What that picture says, and what I measured:
The copy keeps his palette and a sketch, and generates every blade pixel. The sketch is two things: the crop with all grass pixels removed (each filled from its nearest non-grass neighbour), and tuft centres found as maxima of the blurred dark-grass density.
# s0_tufts.py
def sketch():
d = lw.load("V11AM")
idx = d["idx"][Y0:Y1, X0:X1].copy()
g = np.isin(idx, [DARK, MID, LIT])
_, (iy, ix) = ndi.distance_transform_edt(g, return_indices=True) # fill grass from neighbours
bg = idx[iy, ix]
dens = ndi.gaussian_filter((idx == DARK).astype(float), 6)
mx = (dens == ndi.maximum_filter(dens, 17)) & (dens > 0.18)
cy, cx = np.nonzero(mx)
return d, idx, bg, list(zip(cx, cy, dens[cy, cx]))
The blade is a curve whose bend gathers toward the tip, snapped to clean runs:
# grass.py
def tip_arc(x0, y0, ang0, length, bend, nseg=4, power=2.2):
"""Like sp.arc_points, but the bend gathers toward the tip (a grass blade is stiff at the
base and droops at the end)."""
pts = [(x0, y0)]
x, y = x0, y0
seg = length / nseg
for i in range(nseg):
t = (i + 0.5) / nseg
a = ang0 + bend * t ** power
x += np.sin(a) * seg
y -= np.cos(a) * seg
pts.append((x, y))
return pts
sp.polyline_clean turns those points into pixels with slopes snapped to 0, 1:4, 1:3, 1:2, 1:1, 2:1, 3:1, 4:1 or vertical, each step an equal run. That's the studio's clean-run rule for narrow forms. Then each blade pixel gets its role:
def blade_roles(pix, lit_side=1, tipdark=0.25, low_mid=0.35, low_dark=0.0):
"""Pixel roles for one blade: [(x,y,role)], role 0 dark, 1 mid, 2 lit."""
out = []
n = len(pix)
for i, (x, y) in enumerate(pix):
out.append((x, y, 0))
t = i / max(1, n - 1) # 0 base -> 1 tip
if t > 1 - tipdark or t < low_dark:
continue
j0, j1 = max(0, i - 2), min(n - 1, i + 2)
dx = pix[j1][0] - pix[j0][0]
dy = pix[j1][1] - pix[j0][1]
role = 2 if t > low_mid else 1
if abs(dy) >= abs(dx): # steep: lit on the sun side horizontally
out.append((x + lit_side, y, role))
else: # shallow: lit on top
out.append((x, y - 1, role))
return out
draw_blade_pairs lays the roles down in shuffled order. A light pixel skips another blade's dark pixel 70% of the time.

v1: too few blades, too short. The mesa is visible where he has a thicket.

v4a–c: more blades, fanned wider. Now it's a thatch of long comb teeth.

v5b, the one I stopped at. The light share matches (28% against his 28%). The coverage is 31% against his 25%.
Why it doesn't pass:
len_mu and len_sd to it instead of eyeballing.I stopped because the procedure was clear and my subject wasn't his tufts. What I carried forward: