facett-map/src/gpu/line.wgsl
// Triangle-list line shader with SCREEN-SPACE width, miter/bevel joins, SDF anti-
// aliasing, and an optional dark casing pass — the "pretty roads" upgrade over the
// raw 1px `LineList` hairlines in draw.wgsl.
//
// Vertices are origin-local Mercator centreline points (the SAME high-zoom-safe bake
// as draw.wgsl): each polyline vertex is emitted twice (side = ±1) so a segment
// becomes a quad. The width offset is applied in SCREEN space (px), so a road keeps
// a constant on-screen width at any zoom (width-scaled, not world-scaled). The join
// bisector + miter factor (1/cos(θ/2), clamped for a bevel) are baked per vertex on
// the CPU so a corner is a clean miter, not two overlapping quads.
struct Uniforms {
zoom_x: f32,
zoom_y: f32,
screen_center_x: f32,
screen_center_y: f32,
ref_x: f32,
ref_y: f32,
viewport_w: f32,
viewport_h: f32,
feather_px: f32, // AA edge softness (px)
width_add: f32, // CEILING on the casing's extra half-width (px)
use_casing: f32, // 1.0 → paint `casing_col`; 0.0 → the per-vertex colour
// deck.gl's `widthScale` + `widthMinPixels`/`widthMaxPixels`, applied HERE (per
// frame) instead of being baked into the vertex buffer — so a road's on-screen
// width tracks zoom without re-uploading geometry, and matches the CPU painter's
// `facett_map::style::line_width_px` exactly (ONE formula, both lanes).
width_scale: f32,
width_min_px: f32,
width_max_px: f32,
// ── THE CASING IS A FRACTION OF ITS ROAD, NOT A CONSTANT ─────────────────
// `casing_ratio` is the extra half-width as a fraction of the road's OWN
// half-width; `casing_min_px` is the floor so a hairline still gets an edge.
// See `LINE_CASING_RATIO` in `gpu/mod.rs` for the measurement that forced it.
casing_ratio: f32,
casing_min_px: f32,
casing_col: vec4<f32>,
// The 2D ground frame (bearing rotation + cos(tilt) foreshorten) — the term this
// lane did not have, which is why a rotated camera fell back to the CPU painter
// entirely. Applied through the shared prelude's `project_ground_px` /
// `ground_apply`, so the two lanes and the CPU painter share ONE rotation.
rot: vec4<f32>,
foreshorten: f32,
_pad2: f32,
_pad3: f32,
_pad4: f32,
}
// **Two uniform slots, selected by `instance_index`** (GG audit row 21). A road chunk
// is drawn with `instance_count = 2`: instance 0 reads slot 0 (the casing block,
// `use_casing = 1`, wide, dark) and instance 1 reads slot 1 (the fill block). The two
// used to be two draws with two bind groups; the device rasterises a draw's instances
// in instance order, so the casing still lands under the fill exactly as before, for
// one command instead of two. A fill-outline chunk is drawn with one instance and both
// slots hold the outline block. Rust twin: `line.rs`'s `UNIFORM_PAIR_BYTES`.
@group(0) @binding(0) var<uniform> u2: array<Uniforms, 2>;
// Mercator → px scale for a LENGTH, as the inner-miter clamp reads it. Exactly `zoom`
// for the uniform camera every live caller builds; under a non-uniform fit it divides
// the allowance by the anisotropy ratio, which bounds the error in approximating the
// screen-space `sin(τ/2)` by the Mercator one — so the ceiling can only clamp HARDER
// than needed, never looser. Rust twin: `merc_to_px_scale`.
fn merc_to_px_len(zoom_x: f32, zoom_y: f32) -> f32 {
let lo = min(zoom_x, zoom_y);
let hi = max(max(zoom_x, zoom_y), 1e-20);
return lo * lo / hi;
}
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) col: vec4<f32>,
@location(1) side: f32, // signed across-width position in [-1, 1]
@location(2) half_px: f32, // this vertex's half-width (px), incl. width_add
// **The along-axis half of a ROUND END's offset** — lane GG #25. `0` on every quad
// vertex, so `length(vec2(side, cap_t))` degenerates to `abs(side)` and the road's
// body is unchanged; `0 → 1` across a cap/joint half-disc, where the pair together
// is the fragment's radial position in the disc. See `round_end_miter` in line.rs.
@location(3) cap_t: f32,
// **The fragment's ARC LENGTH along its way, in px** — HO-T3. `0` on every undashed
// vertex. Interpolates linearly across a segment quad, which IS the arc length there.
@location(4) arc_px: f32,
// **The dash period in px, or 0 for "no dash"** — HO-T3. Non-zero only on a dashed
// way in the FILL pass; the casing pass and every road carry 0 and take the exact
// path they took before this term existed.
@location(5) dash_period_px: f32,
}
// Rust twin: `paint_order::CASING_DEPTH_BIAS`.
const CASING_DEPTH_BIAS: f32 = 1.0e-6;
// ── THE DASH LAW (HO-T3). Rust twins: `style::DASH_PERIOD_RATIO` / `style::DASH_DUTY`,
// asserted equal by `tests/rail_dash_pixels.rs` from this text. A dashed core is lit for
// `DASH_DUTY` of every `DASH_PERIOD_RATIO · w_px` of ARC LENGTH along the way — the
// way's length, not the screen's x, so a diagonal rail carries as many dashes as a
// horizontal one of the same length and nothing crawls under a pan.
const DASH_PERIOD_RATIO: f32 = 4.0;
const DASH_DUTY: f32 = 0.5;
// **Coverage of the lit part of the dash cycle at arc position `arc_px`**, through the
// same `coverage_inside` ramp the road's edge uses, so a dash END is anti-aliased with the
// same feather as its SIDE. `d` is the signed px distance to the nearest lit/gap edge —
// positive inside the lit half, negative in the gap — so a fragment straddling the edge
// gets exactly the half-coverage a fragment straddling the road's edge gets.
fn dash_coverage(arc_px: f32, period_px: f32, feather_px: f32) -> f32 {
let lit = period_px * DASH_DUTY;
let ph = fract(arc_px / period_px) * period_px; // 0 ≤ ph < period
let d = select(-min(ph - lit, period_px - ph), min(ph, lit - ph), ph <= lit);
return coverage_inside(d, feather_px);
}
// The coverage below which a fragment is DISCARDED rather than drawn — lane GG #2.
// `0.5/255`: half a step of an 8-bit alpha channel, so a fragment under it rounds to
// nothing in the target and its only remaining effect would be the depth it writes.
// Rust twin: `MIN_COVERAGE_ALPHA` in this module's Rust file.
const MIN_COVERAGE_ALPHA: f32 = 0.00196078431;
// The sample count of the map's offscreen colour+depth pair — the ONE multisampled
// target in facett, and the reason this shader can hand its coverage to the rasteriser
// at all. Rust twin: `MAP_MSAA_SAMPLES` in `gpu/mod.rs`, which carries the ruling.
const MSAA_SAMPLES: u32 = 4u;
// **Geometric coverage → a sample mask** — lane GG #2, the real cure.
//
// `n = ceil(cov · 4)` of the four samples, as the low `n` bits.
//
// * **CEILING, not rounding, and the colour is corrected for it below.** A mask alone can
// only express coverage in quarters, and pushing a road's whole AA ramp through four
// levels is a real regression: MEASURED on oden / RTX 4090, 2026-09-05, with a plain
// `round` mask and the alpha left alone, the motorway's faintest edge pixel
// (x = 256, `[28, 28, 34]`) vanished into paper entirely and the residential's own
// mid-row colour moved `[46, 51, 62]` → `[42, 47, 57]`. `coverage_alpha` removes that
// error exactly rather than trading it, which is why the ceiling is the right rounding:
// `n` is only ever the number of samples the DEPTH is claimed on, and claiming at most
// one sample too many is the conservative direction.
// * **The LOW bits, not an arbitrary n.** A road's casing and its fill are concentric,
// drawn back to back at one depth bias apart, and the fill's coverage at a pixel is
// never greater than the casing's. Taking the low bits nests them: the fill's samples
// are always a SUBSET of the casing's, so the two never dither against each other. A
// pattern that varied per pass would sparkle along every road edge.
//
// A NOTE ON WHAT IS NOT HERE. A per-way ROTATION of the run was written and then removed,
// because the argument for it is good and the measurement for it is empty. The argument:
// if every way starts at bit 0, two overlapping ways claim maximally overlapping samples,
// so a lower-ranked way underneath only ever gets what the upper one's coverage ran out
// of, and what shows through is systematically its OUTERMOST ink. A per-way rotation is a
// bijection on the four bits, so it decorrelates that while leaving a single way's
// casing⊇fill nesting intact.
//
// MEASURED on oden / RTX 4090, 2026-09-05, with the rotation hashed from `paint_depth` and
// again with it forced to zero: the crossing frame was **bit-identical**, every pixel,
// both ways. It buys nothing that can be shown on any fixture in this repo, so it is not
// carried. If a future frame shows an edge sparkling between two ways, this paragraph is
// the first thing to try.
fn coverage_mask(cov: f32) -> u32 {
let n = u32(ceil(clamp(cov, 0.0, 1.0) * f32(MSAA_SAMPLES)));
return (1u << n) - 1u;
}
// **The alpha those `n` samples must blend at for the resolve to reproduce `cov` EXACTLY.**
//
// The mask puts the fragment on `n/4` of the pixel; blending there at `α` and averaging
// gives `(n/4)·α` of the ink. Setting `α = cov·4/n` makes that identically `cov`, for
// every coverage, with no quantisation error in the COLOUR at all — the four levels are
// spent on the depth claim, which is the only thing that needed them.
//
// `n = ceil(cov·4) ≥ cov·4`, so the result is always in `(0, 1]` and never has to be
// clamped. `cov = 0` cannot reach here: `fs_main` discards below `MIN_COVERAGE_ALPHA`.
fn coverage_alpha(cov: f32) -> f32 {
let c = clamp(cov, 0.0, 1.0) * f32(MSAA_SAMPLES);
return c / max(ceil(c), 1.0);
}
// **The two things `fs_main` used to fold into one alpha.**
//
// `col` carries the way's MATERIAL alpha and blends as it always did; `mask` carries the
// SUB-PIXEL coverage and gates colour and depth per sample. They were one number
// (`col.a · coverage_inside(...)`) and that is precisely why a half-covered fragment had
// to choose between compositing and owning the pixel's depth.
struct FsOut {
@location(0) col: vec4<f32>,
@builtin(sample_mask) mask: u32,
}
@vertex
fn vs_main(
@location(0) pos: vec2<f32>, // centreline, origin-local Mercator
@location(1) bisector: vec2<f32>, // mercator-space join bisector (unit)
// **`side` carries TWO things** (lane MP-a): its sign is the across-width side
// (+1 / -1) and its magnitude is `1 + depth`, the way's paint depth in 0..=1
// derived from its `PaintKey` rank. `LineVertex` is exactly 48 B and that size is
// load-bearing (vertex layout, chunk partition, bake format), while
// this field was one bit of information in a whole f32. Rust twin:
// `paint_order::pack_side_depth`.
@location(2) side_packed: f32,
@location(3) half_px: f32, // half width (px)
@location(4) miter: f32, // miter length factor (1/cos(θ/2)), clamped
@location(5) col: vec4<f32>,
@location(6) inner_lim: f32, // inner-miter ceiling, MERCATOR units
@builtin(instance_index) inst: u32,
) -> VsOut {
// Which pass this instance is: 0 casing, 1 fill (see `u2`). Clamped so a stray
// third instance could only ever redraw the fill, never read past the array.
let u = u2[min(inst, 1u)];
// Unpack the two halves `side_packed` carries. The RAW side drives the across-width
// offset exactly as before; the depth goes to Z so ordering survives being split
// across submissions — which is the whole point, since `PaintKey` cannot.
let side = select(1.0, -1.0, side_packed < 0.0);
let paint_depth = clamp(abs(side_packed) - 1.0, 0.0, 1.0);
// ── ROUND ENDS (lane GG #25) ─────────────────────────────────────────────
// `vertex_join` never emits a miter below 1.0 and MITER_LIMIT caps it at 4.0, so
// the sign is a free bit in a word that is already in the vertex — and `LineVertex`
// is 48 B, which is load-bearing, so a free bit is the only kind available. A
// NEGATIVE miter marks a cap/joint half-disc vertex and carries `-(1 + cap_t)`.
// Rust twin: `round_end_miter` / `decode_round_end`.
let is_round = miter < 0.0;
let cap_t = select(0.0, -miter - 1.0, is_round);
let miter_eff = select(miter, 1.0, is_round);
// ── THE DASH FLAG AND THE ARC LENGTH (HO-T3) ─────────────────────────────
// `half_px` is a half-WIDTH, strictly positive, so its sign was a free bit: negative
// marks a dashed way. On such a vertex `col.a` is not an alpha but the ARC LENGTH from
// the way's first point (origin-local Mercator) — a dashed way is opaque by
// definition, and the alpha is restored to 1.0 below. Same trick, same reason, as the
// two decodes above: `LineVertex` is 48 B and that is load-bearing. Rust twins:
// `pack_dash_half_px` / `decode_dash` / `pack_dash_col`.
let is_dashed = half_px < 0.0;
let half_abs = abs(half_px);
let arc_merc = select(0.0, col.a, is_dashed);
// Centreline → screen px through the shared prelude's ONE projection writer
// (subtract-before-scale, then the ground frame, then the pane centre) — the same
// call `draw.wgsl`'s `vs_main` makes.
let sc = project_ground_px(
pos,
vec2<f32>(u.ref_x, u.ref_y),
vec2<f32>(u.zoom_x, u.zoom_y),
u.rot,
u.foreshorten,
vec2<f32>(u.screen_center_x, u.screen_center_y),
);
let sx = sc.x;
let sy = sc.y;
// Bisector direction in SCREEN space (per-axis zoom), then ROTATED by the ground
// frame — the width offset has to turn with the map or a rotated road is drawn
// with its casing on the wrong side.
//
// Deliberately `ground_apply` with foreshorten = 1, NOT the full frame: this
// vector is normalised immediately below and then scaled by a width in PIXELS, so
// a road keeps a constant on-screen thickness under tilt. That is exactly what the
// CPU painter does — `egui::Shape::line` strokes a constant-width path along the
// already-foreshortened centreline — so the two lanes still resolve ONE width.
let sb0 = vec2<f32>(bisector.x * u.zoom_x, bisector.y * u.zoom_y);
let sb = ground_apply(sb0, u.rot, 1.0);
var sbn = vec2<f32>(0.0, 0.0);
if (length(sb) > 1e-12) {
sbn = normalize(sb);
}
// deck.gl `PathLayer`: resolve the stroke width for THIS frame's view, then clamp
// it into the pixel band, then halve. `half_px` carries the way's raw themed
// half-width (zoom-independent), so the clamp — not the bake — decides the pixels.
let w_px = resolve_line_width_px(half_abs * 2.0, u.width_scale, u.width_min_px, u.width_max_px);
// ── THE CASING SCALES WITH ITS ROAD ──────────────────────────────────────
// A FLAT `+ width_add` made the casing a constant 2 px of extra half-width on
// every way, so a 1 px residential street drew as a 5 px band of near-black
// navy with a 1 px light core — four fifths of it casing. MEASURED on the
// shipped korp-ui over Stockholm at zoom 12.7 (1254x724 canvas): the exact
// casing colour `#191C24` alone covered 25 512 px = 2.81 % of the pane before
// antialiasing, and 15.67 % of the pane was ink darker than grey 70. That is
// the "gryn" — it is the road casing, not the labels and not the buildings.
//
// A motorway keeps its ribbon because the ratio gives it MORE than the floor;
// a footpath gets a hairline edge instead of a black worm. `width_add` is now
// the CEILING (the old constant), so nothing widens.
var extra = 0.0;
if (u.use_casing > 0.5) {
extra = min(max(w_px * 0.5 * u.casing_ratio, u.casing_min_px), u.width_add);
}
let hw = w_px * 0.5 + extra;
// Rasterise WIDER than the road by one feather on each side, so the AA ramp —
// which `coverage_inside` centres on the true edge at `hw` — has fragments to
// land on all the way out to zero coverage. Without this the quad boundary
// truncates the ramp at 50% and the drawn width comes up `2·feather_px` short.
// `hw` (the TRUE half-width) is what travels to the fragment stage; the extra
// feather is geometry only.
let hw_ext = hw + max(u.feather_px, 0.0);
// ── THE INNER-MITER CLAMP (GFX_V2 §3.A) ──────────────────────────────────
// A miter join's CONCAVE corner does not stick out, it retreats: the bisector
// offset moves it `hw_ext · miter · sin(τ/2)` BACK along its segment, unbounded as
// τ → 180°. Once the two ends of one segment have retreated past each other, that
// segment's quad winds backwards and paints a bowtie — the self-intersection
// GFX_V2 §2 credits deck.gl with handling and we did not.
//
// `inner_lim` is the ceiling the CPU baked for THIS vertex — `0.5·L / sin(τ/2)` on
// the concave corner, `NO_INNER_LIMIT` on every other vertex — in MERCATOR units,
// because the ceiling is a property of the POLYLINE and must not move with stroke
// width, with zoom, or between the casing and fill passes. Converted to px here,
// per frame. Rust twin: `resolve_join_distance_px` + `merc_to_px_scale`.
let dist = min(hw_ext * miter_eff, inner_lim * merc_to_px_len(u.zoom_x, u.zoom_y));
// A quad vertex offsets purely along the bisector. A ROUND END offsets in TWO
// screen-space axes — `side` across `sbn`'s perpendicular, `cap_t` along `sbn` —
// which is what makes `length(vec2(side, cap_t))` the fragment's radial position and
// the silhouette an exact circle rather than a fan's polygon. The perpendicular is
// taken in SCREEN space (after the ground rotation, which is rigid) so the disc is
// round on the pane, which is where it is looked at.
var off = sbn * side * dist;
if (is_round) {
let spn = vec2<f32>(-sbn.y, sbn.x);
off = (spn * side + sbn * cap_t) * dist;
}
let ndc = px_to_ndc(vec2<f32>(sx + off.x, sy + off.y), vec2<f32>(u.viewport_w, u.viewport_h));
var out_col = col;
if (is_dashed) {
// The alpha slot carried the arc length; the material is opaque.
out_col.a = 1.0;
}
if (u.use_casing > 0.5) {
out_col = u.casing_col;
}
// ── THE DASH TERM (HO-T3), gated by the WAY and by the PASS ──────────────
// The arc length turns to px per frame with the same `merc_to_px_len` the inner-miter
// clamp uses, so the dash is a property of the way (pan-stable) that rescales with the
// camera like the width it is measured against. The period follows the RESOLVED width
// — the one formula GG #27 made both lanes share — so a rail's dashes are the same
// multiple of its stroke at every zoom. Only the FILL INSTANCE dashes — under GG #21
// the casing and the fill are instance 0 and instance 1 of ONE draw, so the gate is
// `inst`, not the slot's `use_casing`: instance 0 (the casing under it) stays whole
// and is what shows through the gaps, and the single-instance outline draw (which
// reads slot 0 with `use_casing = 0`) can never dash even in principle.
let arc_px = arc_merc * merc_to_px_len(u.zoom_x, u.zoom_y);
let dash_period_px = select(0.0, DASH_PERIOD_RATIO * w_px, is_dashed && inst == 1u);
// The casing rides one bias BEHIND its own fill, so the dark outline never covers
// the bright line it outlines. Rust twin: `paint_order::CASING_DEPTH_BIAS`.
let z = clamp(paint_depth + select(0.0, CASING_DEPTH_BIAS, u.use_casing > 0.5), 0.0, 1.0);
return VsOut(vec4<f32>(ndc, z, 1.0), out_col, side, hw, cap_t, arc_px, dash_period_px);
}
@fragment
fn fs_main(in: VsOut) -> FsOut {
// The fragment half reads only `feather_px`, which the casing and fill blocks
// share (and the outline holds in both slots), so slot 0 is the whole truth here.
let u = u2[0];
// Signed distance INWARD from the road's true edge, in px. `side` interpolates
// over the FEATHER-EXPANDED quad (half-extent `hw_ext`), so the perpendicular
// offset of this fragment is `|side| · hw_ext` and its inward distance from the
// true edge at `hw` is `hw − |side|·hw_ext`. Negative outside the road, positive
// inside, zero exactly on the boundary — which is where `coverage_inside` puts
// its 50% crossing, so the profile integrates to `2·hw = w_px` exactly.
let hw_ext = in.half_px + max(u.feather_px, 0.0);
// **`length(vec2(side, cap_t))`, not `abs(side)`** — lane GG #25. On a cap or joint
// half-disc the pair is the fragment's position in the disc's own screen frame, so
// the same `coverage_inside` ramp that squares off a butt end now rounds it, and the
// circle is exact rather than a fan's polygon.
//
// **The `cap_t == 0` arm is not an optimisation and it is not free either — it is
// what keeps a road body bit-identical.** `cap_t` is 0 at every quad vertex and
// interpolates to 0 across every quad, so `length` there is `sqrt(side·side)`:
// mathematically `abs(side)`, and NOT bit-for-bit, because the square rounds and the
// root rounds again. On a HARD (`feather = 0`) edge one ulp of `edge_px` is the whole
// verdict, and this shader is measured against a CPU model at exactly that hardness
// by `line_gpu::gpu_draws_exactly_the_clamped_join_geometry_the_cpu_model_predicts`.
//
// MEASURED on oden / RTX 4090, 2026-09-05: on the shipped (clamped) geometry that
// model↔frame diagonal is 0 mismatches out of 170 041 confidently classified pixels
// with the `select` in, with it out, and before this row existed — so the ulp is
// below the noise of the fixtures we have. The arm is kept because "the road's body
// does not move" is a property worth being able to state, not because a frame was
// seen to move.
let radial = select(abs(in.side), length(vec2<f32>(in.side, in.cap_t)), in.cap_t != 0.0);
let edge_px = in.half_px - radial * hw_ext;
// **The sub-pixel coverage, kept SEPARATE from the material alpha** — lane GG #2.
var cov = coverage_inside(edge_px, u.feather_px);
// ── THE DASH (HO-T3): in a gap the fill's coverage goes to zero and the fragment is
// discarded below, so the casing painted under it is what the pixel keeps. A road and
// the casing pass carry `dash_period_px = 0` and never enter this branch.
if (in.dash_period_px > 0.0) {
cov = cov * dash_coverage(in.arc_px, in.dash_period_px, u.feather_px);
}
let a = in.col.a * cov;
// ── A TRANSPARENT FRAGMENT MUST NOT CLAIM A PIXEL (lane GG #2) ────────────
// The quad is rasterised one feather WIDER than the road so the ramp has fragments
// to land on — and depth writing is on, so without this every road carried a ~1 px
// halo of INVISIBLE ink that reserved its pixel and depth-rejected any lower-ranked
// way drawn afterwards. The loser is not blended away, it is discarded, so what was
// left at that pixel was the bare pane background: a paper-coloured slit alongside
// every road, cutting whatever crossed it.
//
// MEASURED on oden / RTX 4090, 2026-09-05, BEFORE the sample mask below existed: this
// discard changed no pixel at all. Taken and painted were the same seven pixels with
// it in and with it neutralised, because a fragment under half an alpha step needs its
// centre within ~0.002 px of the quad's outer edge and essentially never rasterises.
// It was kept on principle, and the audit's claim that this halo was the mechanism
// behind GG #2 does not survive that measurement.
//
// **It is load-bearing now.** `coverage_mask` CEILS to whole samples, so a fragment at
// any coverage above zero claims at least one of the four — a quarter of the pixel's
// depth for ink nobody can see. This floor is what stops the ramp's outermost tail
// from doing that. `tests/gpu_lane_paint_order.rs`'s taken-vs-painted oracle is the
// one that would catch it going wrong, and it is no longer measuring a dead mechanism.
if (a < MIN_COVERAGE_ALPHA) {
discard;
}
// **Coverage to the RASTERISER, material alpha to the BLEND.**
//
// What used to be returned was `vec4(rgb, col.a · cov)`: one alpha carrying both,
// blended over the whole pixel, and — since MP-a — stamping the whole pixel's depth.
// A way drawn afterwards at a lower rank was then depth-REJECTED at that pixel, and a
// rejected fragment is not blended away, it never runs; what survived was this way
// over bare paper. MEASURED: across a motorway's entire 7 px footprint the frame
// with a residential underneath was bit-identical to the frame without it.
//
// With the coverage in the sample mask, this fragment owns exactly the samples it
// covers. The rest keep their clear depth, the road beneath fills them, and the 4×
// resolve averages the two — which is the composite the depth test was refusing to
// do. `col.a` alone goes to the blend, so a genuinely translucent way still blends
// rather than dithering.
return FsOut(vec4<f32>(in.col.rgb, in.col.a * coverage_alpha(cov)), coverage_mask(cov));
}