Part of the Wrangle reference. Everything on this page goes in the node's Code field, and runs once per element of the current Run Over mode.
Run over: Texel
Run Mode = Texel - Domain/Run Per is ignored.
What it is
Set Run Mode to Texel and the code runs once per texel of a durable output image this node owns, instead of once per vertex or triangle. The subject Mesh parameter becomes optional: undeclared or unwired, the code still runs (against whatever texture parameters it names); wired, it passes through unchanged and doubles as the "Mesh" donor slot.
Cd = texture("Base", uv.x, uv.y) * 0.5
Cd - vec3 rgb, read/write
The texel's own colour. Seeded from the first wired Texture parameter (Parameters-list order), sampled at this texel (matching size) or bilinear at the texel centre (mismatched size); black with alpha 1 when none is wired.
Cd = vec3(1, 1, 1) - Cd
Cd.a - scalar, read/write
The texel's own alpha.
Cd.a = 1
uv - scalar components, read only
uv.x/uv.y are this texel's own centre, normalised to [0,1] - the same coordinates you would hand texture()/texturea() to read this exact texel back. Assigning to uv here is a compile error: a texel's position is fixed by which one it is, the same reason a triangle's P cannot be assigned.
Cd = texture("Ramp", uv.x, 0.5)
elemnum / numelem - scalar, read
This texel's flat index / texel count, exactly like every other domain.
Cd.a = elemnum / numelem
texelx() / texely()
This texel's own coordinates, as unwrapped integers (0 .. width-1 / 0 .. height-1) - the same position uv.x/uv.y give normalised. Legal only in this domain.
vector c = texel("Albedo", texelx() + 1, texely())
Not available
Everything that describes a mesh element is a positioned compile error here: P, N, mask, @attributes, wmap(...) writes, area, ptnum, neighbourcount()/neighbour()/pointpos()/pointnormal()/isboundary() (the self-mesh, no-pin forms). A texel is not a vertex or a triangle and has none of those.
P, N, mask, @name, wmap(...) writes, area, ptnum - all errors
Stays legal
Donor-mesh functions (npoints/pointpos/pointattr/neardist/nearpoint/surfattr/sdist/raydist with a pin argument) and every texture function: their pins are independent of the run-over. Parameters (ch()/chf()/chv()/chi()/chb() and bare reads) too. Detail constants (bboxmin() and friends) also stay legal but need the subject Mesh parameter declared and wired - with none, calling one is a compile error naming it.
float t = pointattr("Mesh", "thickness", 0)
Output
The result is OutputTextureWidth x OutputTextureHeight texels (0 = auto: the first wired Texture parameter's own size, else 1024), published on this node's primary Texture output if one exists (see primary on the Parameters page) - a plain texture you can wire anywhere a texture pin is accepted. Switching Run Mode to Texel does not create that output; Create pins from code (or the pin editor) adds one when the expression writes Cd. A step-budget abort or a compile failure discards the whole run, exactly like the other two domains: nothing new publishes, and the persisted output from the last successful run is left untouched.
Run over: Corner
Run Mode = Corner - Domain/Run Per is ignored.
What it is
Set Run Mode to Corner and the code runs once per corner (face-vertex/wedge) instead of once per vertex or triangle: cornerid = 3*tid + c, c in 0..2, for every live triangle's three corners, ascending. A mesh whose MaxTriangleID() exceeds MAX_int32/3 refuses the run with a node error rather than overflow the id.
N = normalize(N)
Why a corner exists
An overlay element (N, uv, Cd) has exactly one parent vertex, and a triangle references three distinct vertices, so one triangle references a given overlay element at most once - a corner is that (triangle, element) pair. Reading/writing "this corner's N" therefore always means one exact overlay element, never a vertex's several split elements averaged together the way Vertex mode's N does.
Split, never merged
Writing N/uv/Cd per corner can make two corners that used to share an overlay element disagree - a hard edge or a UV seam being authored by the expression, not just read. When that happens the shared element splits: one corner keeps the original element, the other(s) get a freshly split one as a fresh split element, grouped by exact bitwise equality (see the next row for why exact). Corners already written to the same value, or left unwritten and therefore still equal to the old value, are never split from each other - only genuine disagreement splits. The reverse never happens: this run-over never calls MergeElement, even when two corners' new values happen to end up equal. A seam that was deliberately split (an artist's hard edge, a UV chart boundary) must stay split - silently welding it because today's write happened to agree would destroy information the mesh cannot get back, and unpredictably so, since it would depend on what the expression happened to compute this run.
if (materialid() == 2) { N = vec3(0,0,1) } // splits at the material boundary
Exact equality, not epsilon
Grouping corners that share a value uses bitwise equality, not a tolerance. An epsilon comparison is not transitive (a within eps of b, b within eps of c, but a not within eps of c is possible), so which corners end up grouped together would depend on the order they happen to be compared in - not deterministic. Two corners you intend to be "close enough" to share an element should be made exactly equal on purpose (round() or a shared computation, e.g. read one corner's N and assign it to the other) rather than relying on two similar expressions to land on the same float bits by chance.
N = round(N * 1000) / 1000 // quantise on purpose so nearby corners actually match
Determinism
Corner ids are visited in a fixed order (ascending cornerid, i.e. ascending triangle id then 0/1/2), and every step of the apply pass that decides which elements split - the touched-element set and the split grouping - runs in that same ascending order, single threaded. Running the same program on the same mesh twice always produces the same element count and the same values in the same order; only the parallel per-corner candidate computation (TryToFloat, blend, clamp - no element-count-affecting decision) runs across workers.
wmap reduction
Each named WeightMap output write (wmap("Name") = ...) is promoted to the per-vertex mean over corners that actually wrote it (a serial, ascending-corner-id sum/count per vertex, for float-sum determinism) - a vertex none of whose corners wrote it keeps 0, exactly like Vertex mode's own "never written" default. A single corner conditionally writing wmap("Weights") = 1 on a vertex with three corners gives that vertex 1.0, not 1/3 - the mean is over writers, not over every corner.
if (cornerindex == 0) { wmap("Weights") = 1 }
Per-vertex normal buffer
If the mesh has both the split normal overlay and the legacy per-vertex normal buffer, Corner mode writes only the overlay - the per-vertex buffer is left exactly as it was, with a run-summary Note that it was not updated (never silently discarded). Rebuild it downstream (Recompute Normals, or any node that reads the overlay) if you need it in sync.
Restrict
Mask tests the corner's owning vertex (every corner of a vertex shares its fate, so a vertex is never partly included). MaterialID / PolyGroup test the owning triangle. Same Restrict dropdown as Mesh Domain.
Restrict = Material ID, Restrict Value = 2
Not available
Everything that needs exactly one current vertex to write is a positioned compile error here: P write (use Run Per = Vertex), setboneweight/clearinfluences (skin writes - several corners can share a vertex, so which one "wins" has no answer), setpointpos (Detail only). @attribute writes are refused outright too (promotion policy deferred - see the Attributes section).
P = ..., setboneweight(...), @name = ... - all errors in Corner
Stays legal
Everything else a Vertex-mode program could do, reading through PtNum/PrimNum instead of a bare vertex/triangle id: isboundary (both forms), materialid()/polygroup() (owning triangle), wmap("Name") (owning vertex), geodist/relax/smooth_implicit (owning vertex), relbbox()/displace() (owning vertex/normal), the point-topology family (neighbourcount/neighbour/neighbourweight/pointpos/pointnormal) and prim-topology family (neighbourprimcount/neighbourprim), foreach neighbours/neighbourprims/points()/nearpoints, and skin reads (boneweight/weight/weighti/numinfluences/influencebone/influenceweight - the owning vertex's working copy). Donor-mesh/texture/landmark/ramp/transform/delta functions and Parameters, exactly like every other domain.
wmap("Weights") = weight("root") + wmap("Falloff")