Commit Graph
3 Commits
Author SHA1 Message Date
gramps 28525c90aa feat(scoreboard): scroll speed is a property (20-400 px/s, persisted)
The marquee now has the dial it needs. `ScoreboardPresenter.PixelsPerSecond` is the single owner of
the range (20-400, default 120), the layer's properties expose it with a live px/s readout, and the
value is persisted as Scoreboard.ScrollSpeed because it is a READABILITY choice — re-dialling it
every launch would be a tax on the creator who needs the strip to be readable.

Advance() still takes an optional override so tests can drive exact distances.

Guard: the presenter test proves speed is real (same elapsed time, twice the speed, different
window) — not a value that is stored and ignored — and the panel test pins the control, its range
and the persistence key.
2026-10-30 15:19:28 -07:00
gramps 460b36204d feat(scoreboard): it ticks — the marquee is the design, not an option
Creator, plainly: "the entire point of a ticker is to tick… so, tick." The static strip was my
reading of an earlier exchange; it is gone.

How it ticks without becoming the compositor's most expensive layer:
- `ScoreboardRenderer.RenderRun` rasterises the run ONCE per value change at its natural width plus
  a gap.
- `ScoreboardPresenter` crops a 1920-wide window at a moving offset each tick — a row-wise byte
  copy, no text layout, no re-raster.
- `TickScoreboard` runs on the existing 30 Hz meter clock and only while the strip is shown, so a
  hidden or disabled scoreboard costs nothing.

The dev simulation uses the same path, so what is previewed is what ships.

Test updated to the real contract: no tick → identical pixels; a tick → different pixels; a value
change → re-rasterised run. (The old test asserted frame-instance identity, which a cropped window
can never satisfy.)
2026-10-30 15:17:55 -07:00
gramps 41b0cd6e98 feat(scoreboard): unit 5 — tile selection and the cached strip presenter
ScoreboardTiles decides which of the eight facts appear and shapes their text (money uses the
session's dominant currency and flags a second one rather than converting; watch hours carry
'as of {date}' because Analytics is a daily aggregate). Tiles are OPT-IN, so 'None' really means the
layer draws nothing, and 'Default' is the six session facts.

ScoreboardPresenter owns the raster cache: ScoreboardRecorder.Changed fires only on a visible change,
so the strip rebuilds only then — a per-tick raster would be the compositor's most expensive layer
for identical pixels.

Guard: ScoreboardPresenterTests asserts the cache identity (same instance when nothing moved, a new
frame when a value moved, reuse when a recipient-side gift event arrives) and that an empty selection
returns no frame at all.
2026-10-30 14:38:59 -07:00