fix: the density's ceiling comes from the fade, so the ramp is visible

The library's `full_scale` now defaults to the default fade's steady state and `gamma` to 1.0 (its
own commit); this side states the ceiling for the fade *it* configured, because that is what the
ceiling means: the ink the display settles at, so the newest acquisition is a fraction of full ink
and white is reached only where acquisitions agree.

- **acquisitions**: `1/(1 - factor)` -- the geometric sum, 2.0 at the panel's 0.5, and 1.0 at 0
  (only the newest sweep, at full ink).
- **time**: the library's default of two sweeps. A time fade has no fixed steady state -- it
  settles where the trigger's rate and the constant meet, which is the *point* of it -- so a fast
  trigger reads brighter than a slow one the way a phosphor's does.
- `gamma` is left at the library's 1.0: a cell is a count of sweeps, so half as many must read as
  half the ink. With the ceiling at one sweep and gamma at 0.45 (the library's first defaults) one
  sweep was already full white and every display looked the same -- which is exactly what the user
  reported seeing, and why the assertion for it is a framebuffer one in the library's case
  (`empty=21 full=83 quarter=54`) rather than anything `ink`/`peak` can say.

The probe's own figures are unchanged in kind (they are the grid's): `on` `peak=1.0000`,
`step-acquisitions` `peak=1.5000`, `step-time` `ratio=0.367879`, `off` all zeros -- re-read on this
build, with the frame cost `tick 0.53 -> 7.49 ms`, `draw 1.89 -> 16.78 ms` and `gtk warnings: 0` in
the same run. The trigger/pinning invariants carry the ceiling rule and why the eye found it.
This commit is contained in:
Martino Ferrari committed 2026-10-16 17:38:06 +02:00
1 parent 974d6aa2d9
commit 5e32199597
3 files changed
+24 -1

No files matched your search

+12
View File
@@ -3396,6 +3396,18 @@ void apply_density(App& app, DisplayMode display, bool live_display) {
: plot::DensityDecay::PerAcquisition;
cfg.time_constant_s = static_cast<float>(tc.density_tau_s);
cfg.per_acquisition = static_cast<float>(tc.density_factor);
// The brightness ceiling: the ink this fade settles at, so the newest acquisition is a
// *fraction* of full ink and the display reaches white only where the acquisitions agree. For a
// per-acquisition fade that is the geometric sum `1/(1-f)` exactly. A time fade has no fixed
// one -- it settles where the trigger's rate and the constant meet, which is the point of it --
// so it keeps the library's default of two sweeps, and a fast trigger then reads brighter than
// a slow one the way a phosphor's does. The gamma is left at the library's 1.0: the cell is a
// count of sweeps, so half as many sweeps must read as half the ink. (With the ceiling at one
// sweep -- the library's first default -- one sweep was already full white and the fade was
// invisible, which is exactly what a user reported seeing.)
cfg.full_scale = tc.density_decay == PersistDecay::Time
? 2.0f
: static_cast<float>(1.0 / std::max(1.0 - tc.density_factor, 0.05));
// The plot drops what it held when this turns the display off, so the step below only ever
// speaks about an image that is on screen.
p->set_density(cfg);