feat: grade a sparse window's polyline by slope, so phosphor does not switch off on zoom
Intensity grading was gated on the column reduction, which is gated on a density whose only job is killing the moire beat. Dwell has nothing to do with that, so narrowing a window past the threshold turned the whole phosphor model off and a discontinuity went from a third of a flat top's ink to all of it in one pixel of zoom. stage_trace_slopes() grades each segment by its own slope instead, with a measure that agrees with the dense path at the boundary, and reports itself as FrameStats::slope_traces -- disjoint from column_traces, so a host asserting graded_traces == column_traces reads the same as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
cc908efa36
commit
37e414bcc5
8 files changed
+442
-13
No files matched your search
@@ -251,7 +251,18 @@ Five things carry it, and each was arrived at by being wrong first:
|
||||
geometry; only `TextInstance` carries `a_colour`), so a per-segment alpha is inexpressible and
|
||||
the tiers are what make the gradient drawable at all. `intensity_floor` keeps the faintest tier
|
||||
visible — a fast riser is one sub-bucket deep everywhere and must not be dimmed out of
|
||||
existence — and `intensity_gamma` lifts the faint end. A connector between two disjoint columns
|
||||
existence — and `intensity_gamma` lifts the faint end. The floor only holds if the riser's rows
|
||||
**reach** a tier: the "lit at all" cutoff that discards a column's empty rows is
|
||||
`min(0.01 × peak, 0.5 × w_min)` over the smallest deposit any of that column's own segments
|
||||
made, and not the first term alone. A column holding a flat level *and* a riser has its peak set
|
||||
by the level — one unit in one row — while the riser spreads a unit over 300, which is three
|
||||
orders of magnitude under `0.01 × peak`, so the riser was discarded before the floor could apply
|
||||
to it. Measured on a 2098-sample square wave at 105 samples a column: **2 916 of the ungraded
|
||||
path's 35 884 riser rows**, 106 of 116 riser columns gutted entirely, with a 311-row dark run in
|
||||
a 312-row axis; the ten survivors were the phase that put a riser in a column of its own, which
|
||||
is why it read as a riser *flickering* under a pan rather than as a missing edge. With the second
|
||||
term it is 35 572 rows and 115 of 116 columns, for 48 extra segments
|
||||
(`a_graded_column_keeps_a_riser_it_shares_with_a_flat_level`). A connector between two disjoint columns
|
||||
is emitted at the **faintest** tier: it is inferred geometry, not a place the signal was
|
||||
measured to dwell.
|
||||
- **Grading may not undo the reduction.** `level_for()` answers 0 — raw samples — as soon as the
|
||||
@@ -276,6 +287,46 @@ flat path is on the pyramid is what exercises the floor (`raw=0 lod=1 … seg=63
|
||||
visible=200001`); the test's first window sits at 105 samples a pixel, where both paths are raw
|
||||
and the floor is never reached.
|
||||
|
||||
## A sparse polyline is graded by slope
|
||||
|
||||
The column reduction stops at `dense_threshold_spp` (1.5 samples a device pixel), below which the
|
||||
trace is a polyline. Grading used to stop there with it, because it was staged *inside* the
|
||||
reduction — and that coupling was a defect, not a design. The reduction's gate is a density, and
|
||||
its only purpose is removing the moiré beat between the bucket pitch and the pixel pitch. Dwell
|
||||
has nothing to do with that beat, so zooming past the threshold turned the phosphor model off in
|
||||
one step: the same square-wave edge measured **71 of 191** at 3.4 samples a pixel and **191 of
|
||||
191** at 1.0, a 2.7× jump across one pixel of zoom, which reads as a discontinuity *lighting up*
|
||||
the moment you look at it closely. The thing a reader most needs told apart from a real signal
|
||||
excursion is a transition, and it was being drawn at full ink exactly when it filled the screen.
|
||||
|
||||
`Renderer::stage_trace_slopes()` grades the polyline on the same principle, differing only where
|
||||
it must:
|
||||
|
||||
- **The measure is the segment's own slope**, `1 / (1 + |Δy_screen|)`, which is dwell per row for
|
||||
a single segment. The `1 +` is not a guard against a division: a flat segment — span 0, the
|
||||
commonest case in a sparse window — must be the *brightest* tier, and this makes the measure
|
||||
dimensionless and **absolute**. There is no per-column peak to normalise against down here, and
|
||||
that is an improvement: a sparse frame's tiers mean the same thing from frame to frame, so
|
||||
panning does not reshade the trace.
|
||||
- **No gather mode is excluded.** The reduction is `Envelope`-only, because turning a bucket's
|
||||
min/max into a band answers a different question from the one a `Nearest` or `Mean` host asked.
|
||||
A segment's slope is a property of the polyline itself, so `grade` is not reused as the gate —
|
||||
only `intensity_grading` and `intensity_steps > 1` are.
|
||||
- **The geometry is bit-for-bit the ungraded polyline's**, staged exactly as `stage_xy()` stages
|
||||
it, with the same `snap = false`; only the batch a segment lands in differs. A segment with one
|
||||
non-finite end is dropped rather than clamped, which is the conclusion `add_polyline_xy()`
|
||||
reaches too.
|
||||
- **`FrameStats::slope_traces` counts it, and it is disjoint from `column_traces`.** A window is
|
||||
either dense enough to reduce onto the pixel grid or it is drawn as a polyline, never both — so
|
||||
a host asserting `graded_traces == column_traces` keeps that reading unchanged.
|
||||
|
||||
`a_sparse_trace_is_graded_by_slope_like_a_dense_one` is the guard: one window either side of the
|
||||
threshold over the same transition at the same place. The level reads **191 either way** (a flat
|
||||
segment dwells in its row, so it is the top tier and full ink) and the riser reads **71 either
|
||||
way**, against the 191 the sparse frame gave before. Across the whole sweep from 101 down to 0.1
|
||||
samples a pixel the riser holds 71 and the level holds 191, so there is no step at the threshold
|
||||
left to see.
|
||||
|
||||
## One sample or a statistic of many
|
||||
|
||||
Every number the library reports is one the samples actually hold. What differs between
|
||||
|
||||
Reference in new issue
Block a user