testing
The passing test that dimmed the wrong thing
A fix for per-feature fill opacity came with a fail-before/pass-after test: 0.25 and 0.75 baked to alpha 64 and 191, two values where there had been one. It was green and I reverted the change — because the alpha it proved feeds the outline, not the fill.
By the X-GIS team 4 min read
The change looked finished. A polygon layer with a data-driven fill-opacity —
0.25 for one feature, 0.75 for another — used to collapse every feature to
the same alpha; the fix folded the opacity expression into the per-feature color
at bake time, and a unit test proved it:
FAIL-BEFORE: expected 1 to be >= 2 // one distinct alpha — the collapsePASS-AFTER: ✓ bakes >= 2 distinct per-feature alphas // 0.25→64, 0.75→191Fail-before, pass-after, a real number moving from a set of size one to a set of size two. By every ritual we use to certify a fix, this was done. I reverted all of it.
The wrong first move
The tempting read is the one the test hands you: the bug was “per-feature opacity
collapses,” the test asserts “per-feature opacity no longer collapses,” it fails
before and passes after, so ship it. That reasoning is airtight about the code
and silent about the world. It proves the function extractFeatureColors now
returns two different alphas. It says nothing about whether those alphas are the
ones the user is looking at.
I nearly kept it for a worse reason still: “even if it’s incomplete, it’s a tested primitive the real fix will reuse.” A green test makes incomplete work feel like a down payment instead of a liability.
What actually happened
Two facts, each a one-line grep, dismantled it.
First, the wiring was inert. The fold reads a fillOpacityExprs map that the
worker is supposed to receive — but nothing populates it:
$ grep -rn 'fillOpacityExprs' --include=*.ts .$ # nothing outside the reverted change — no producerThe sibling it was modeled on, strokeColorExprs, is built in
show-source-maps.ts and threaded through source-manager.ts into the worker.
fillOpacityExprs had the consumer and no producer, so in production the
expression never arrived and every alpha stayed 255. The unit test passed only
because it called the baker directly with an expression the running app never
delivers.
Second — and this is the part the test actively hid — the alpha it proved is the
wrong alpha. extractFeatureColors is called with strokeColorExprs; its output
colors the stroke / line segments. The polygon fill interior is a different
GPU path entirely, drawn from a vertex format that carries no color at all:
export const POLYGON_FILL_FORMAT = buildFormat([ { name: 'q_xy', ... }, { name: 'q_z', ... }, { name: 'feature_id', location: 2, ... }, // ← the fill is keyed by id { name: 'abs_lon', ... }, { name: 'abs_lat', ... }, { name: 'true_lat', ... },])The interior resolves its color in the shader by feature_id. So folding
opacity into the CPU segment-color bake dims the polygon’s outline and leaves
its fill fully opaque — the exact surface the issue is about. The test measured
a real value on the wrong channel, and a green bar on the wrong channel is
indistinguishable, on the dashboard, from a fix.
The fix was to not have one
There is no small correct version of this change. Per-feature fill opacity has to
be folded into the fill-color resolution that runs in the GPU compute variant,
keyed by feature_id — a compiler-IR-and-shader change — plus the ~nine files of
producer plumbing the inert map was missing. That is a different, larger piece of
work than a bake-time alpha multiply, and pretending a green unit test had
delivered it would have closed the issue on a fill that never dims. Reverting and
writing down why the CPU layer can’t reach the fill was the honest deliverable.
What generalizes
A passing test certifies that the code does what the test asserts. It does not certify that the assertion is about the bug. Those come apart whenever the test can reach the code more directly than the running system can — here the test handed the baker an expression the app never plumbs — or when the assertion lands one pipeline stage away from the defect, on a value that is real but not the one on screen. Before you trust a fail-before/pass-after, check the two joints the green bar can’t see: does production actually feed this path the input the test feeds it, and is the thing being asserted the thing the user sees? A fix at the wrong layer is green, and green is exactly how it hides.
Read next
verification
5 min read
Verified only where the bug showed: a hot-path fix that shipped a zoom-in regression
A WebGL2 flicker fix (#1139) was render-parity-verified on exactly one scenario — the zoom-OUT hold-tiles case that reproduced the original flicker, retained-content 0.19 → 0.79 matching WebGPU — and never on zoom-in or per-frame cost. It shipped an unmemoized per-frame classifyTile run twice per tile plus synchronous uploads, and stuttered with progressively-black fills on a Seoul zoom-in. Reverted 36 minutes later (#1140). A hot-path fix must be verified in its worst regime, not the one that showed the original bug.
rendering
7 min read
Draped at the wrong tile: a deferred write turned a shared uniform pool into a data race
A draped landcover polygon rendered hundreds of kilometres offshore — but only on WebGPU. Every vector slice of one source shares one Material uniform pool, and each slice resets the pool cursor to 0, so a later slice's tile i overwrote an earlier slice's tile-i buffer. WebGPU's queue.writeBuffer defers to submit (last-writer-wins), so every draw read the final bytes; WebGL2's immediate bufferSubData never showed it — and the differential test was structurally blind, because both frames aliased identically.
rendering
7 min read
The hemisphere that was never selected: a fast-path bbox that collapsed to a point
Half the globe rendered as background again — same symptom as the drape-suppression bug, opposite root cause. This time the trans-antimeridian tiles were never selected: the overzoom fast-path unprojects the viewport corners to a lon/lat box, but on a small globe disc the corners miss the sphere, the box collapses to the sub-camera point, and it emits the single tile column under the camera — which a contiguous longitude range can never wrap across the dateline. Told apart from the draw bug by seam location and persistence.