Skip to content

[feature] Starting dev Stratum/Worker#11

Closed
5133n wants to merge 26 commits intomasterfrom
stratum
Closed

[feature] Starting dev Stratum/Worker#11
5133n wants to merge 26 commits intomasterfrom
stratum

Conversation

@5133n
Copy link
Copy Markdown
Contributor

@5133n 5133n commented Sep 1, 2021

No description provided.

@5133n 5133n closed this Mar 24, 2022
@frstrtr frstrtr deleted the stratum branch March 6, 2026 10:10
frstrtr added a commit that referenced this pull request Apr 20, 2026
Closes the most visible limitation of ?new-explorer=1 (Phase B #9):
operators enabling the flag no longer see dashes across the stats
panel. Parity with the inline renderer's stat counters achieved
except for minor #defrag-blocks formatting (coin-name still
hardcoded "LTC / DOGE"; generic merged-chain labelling arrives with
CoinDescriptor consumption per M1 D5).

src/explorer/delta.ts

- WindowSnapshot<S> gains three optional metadata fields:
    chainLength, primaryBlocks, dogeBlocks
  All typed explicitly as `T | undefined` so
  exactOptionalPropertyTypes stays satisfied.

src/explorer/realtime.ts

- New type RealtimeStats: shares, chainLength, verified, mine,
  stale, dead, fee, v36native, v36signaling, primaryBlocks,
  dogeBlocks.
- RealtimeState.stats surfaced through getState(), lazy-computed
  by currentStats() and cached per lastAppliedTip so repeated
  polls on the same tip are O(1). Cache invalidated on rebuild +
  on applyDelta.
- extractMeta() helper pulls chain_length + blocks + doge_blocks
  from raw /sharechain/window and /sharechain/delta payloads (spec
  §5.1 + §5.3). Only-defined-fields return keeps
  exactOptionalPropertyTypes happy across the spread patterns.
- rebuildWindow + applyDelta populate the new meta fields from the
  raw response; applyDelta preserves previous meta fields across
  deltas that don't carry them.
- Classification thresholds use ctx.shareVersion so a Dash V16 host
  reports v36native / v36signaling counts against 16 — the field
  names are legacy from dashboard.html; semantics are coin-agnostic.

src/explorer/index.ts

- Re-exports RealtimeStats.

tests/unit/realtime-stats.test.ts (10 tests)

- Empty window → all-zero stats.
- Priority order: dead(2) / stale(1) / unverified / fee / verified.
- Mine counting against myAddress; empty myAddress always 0.
- v36native vs v36signaling branches including boundary V===36 +
  V===37 native counting.
- Dash V16 threshold demonstrates coin-agnostic behaviour.
- chain_length + blocks + doge_blocks passthrough from payload.
- Cache identity: same tip → same object reference (===).
- Cache invalidation: delta merge produces distinct stats object.

web-static/dashboard.html (flag-on bootstrap)

- Builds an element lookup once (#defrag-total, -chain-len,
  -verified, -mine, -stale, -dead, -fee, -v36native, -v36sig,
  -blocks).
- 250 ms setInterval calls writeStats() which short-circuits when
  lastAppliedTip hasn't changed — matches the orchestrator's
  per-tip caching so DOM writes are minimal.
- #defrag-blocks formatted as "N LTC / M DOGE" — identical to the
  inline path at :5909. Coin-aware label comes with CoinBridge.
- beforeunload clears the interval.

web-static/sharechain-explorer/dashboard-bundled.mjs

- Header stats line now surfaces chain length, v36/v36sig, mine,
  stale/dead, primary/doge blocks counts alongside the existing
  tip + connection state. Dropped fields that are zero to keep
  the line compact.

Status

- 176/176 tests pass (166 prior + 10 new).
- Typecheck clean.
- Bundles: shared-core 25.1 KB / 40, sharechain-explorer 43.5 KB
  / 120 — both under budget. (+1.5 KB from ~2 KB of new stats
  code.)
- dashboard.html default path (flag off) touches zero new code at
  runtime — the stat polling is gated inside the flag-on bootstrap.

Next: Phase B #11 particles + card overlays (largest remaining
visual piece), or Phase B #12 hover-zoom + per-share PPLNS treemap
(dashboard.html:5686-5800 port), or Qt refactor step 1.
frstrtr added a commit that referenced this pull request Apr 20, 2026
Closes the second-most-visible flag-on gap identified in Phase B #9:
hovering a share cell now pops the 240x240 squarified PPLNS panel
that the inline renderer showed via dashboard.html:5686-5782. Both
dashboard.html (?new-explorer=1) and dashboard-bundled.html get the
feature in this commit.

Three new primitives, all verbatim-ported from dashboard.html so
M2 pixel-diff holds.

src/explorer/pplns.ts (~45 LOC)

- interface PPLNSEntry = { addr, amt, pct }
- parsePPLNS(raw): handles both flat {addr: amt} and merged
  {addr: {amount, merged:[]}} payload shapes, filters zero/
  negative/NaN amounts, sorts desc by amt, normalises pct to the
  remaining total. Byte-identical behaviour to dashboard.html's
  _parsePPLNS() at :5631-5643.
- PPLNSPlugin: id 'explorer.pplns.parser', provides 'pplns.parser'.

src/explorer/hover-zoom.ts (~210 LOC)

- buildHoverZoomProgram(opts): PaintCommand[] — pure builder. Uses
  SharedCore's squarify + addrHue. Cell colours preserved verbatim
  (dashboard.html:5719-5725):
    hovered miner  → hsl(hue, 85%, 55%)   brightest
    my address     → hsl(hue, 60%, 42%)   mid
    everyone else  → hsl(hue, 35%, 30%)   muted
  White highlight ring on hovered miner (line 2.5). Label emission
  thresholds preserved: % when cell > 30x16, address when > 44x28
  with font-size scaling exactly matching dashboard.html:5746-5761.

- createHoverZoomPanel(opts): HoverZoomPanel — thin DOM adapter.
  position: fixed 240x240 canvas + label row + border/padding.
  show() clamps to viewport edges (flip left past a configurable
  tooltip-width offset, matches dashboard.html:5775-5781).
  destroy() removes from parent. Does not capture pointer events
  (pointer-events: none) so hover-through still works.

- HoverZoomPlugin: id 'explorer.hover-zoom.canvas', provides
  'renderer.hover-zoom', fills slot 'explorer.main.overlay'.

src/explorer/delta.ts + realtime.ts

- WindowSnapshot<S> gains:
    pplnsCurrent?: readonly PPLNSEntry[]
    pplnsByShare?: ReadonlyMap<string, readonly PPLNSEntry[]>
- RealtimeOrchestrator.extractMeta() parses pplns_current + pplns
  from /sharechain/window and /sharechain/delta payloads (spec
  §5.1, §5.3). Every parse uses parsePPLNS() so the same shape
  handling applies everywhere.
- applyDelta merges pplnsByShare additively across deltas; delta
  entries win over prior (mirrors dashboard.html:8079-8080).
  Growth bounded at 2 x windowSize per architecture doc §2 ("up
  to 10000 entries") / delta v1 §C.2 LRU.
- New method: orchestrator.getPPLNSForShare(shortHash). Implements
  the three-step fallback from dashboard.html:5647-5663:
    exact cache hit → walk backward (toward newer) to nearest
    cached → pplns_current.
- RealtimeController surfaces getPPLNSForShare passthrough for
  DOM consumers.

web-static/dashboard.html (flag-on path)

- After rt.start(), startHoverZoom() creates a HoverZoomPanel and
  attaches mousemove + mouseleave to #defrag-canvas.
- mousemove: computes layout from current containerWidth, hit-tests
  via cellAtPoint, looks up share via getState().window.shares[idx],
  fetches PPLNS via controller.getPPLNSForShare(share.h), calls
  panel.show() with cursor coords. Cell-index memoised to avoid
  re-rendering when hover stays inside the same cell.
- mouseleave: hides panel.

web-static/sharechain-explorer/dashboard-bundled.mjs

- Parallel wiring so the A/B surface also shows hover-zoom.
- disconnect() / connect() manage hoverPanel lifecycle.
- onMouseMove reuses controller.getPPLNSForShare. myAddress is
  read from the header input so A/B-testers can exercise the
  is-me colour.

tests/unit/pplns-hoverzoom.test.ts (15 tests)

- parsePPLNS: empty / flat / merged / zero-filter / missing-amount
- getPPLNSForShare: exact / walk-backward / fallback to
  pplns_current / additive-across-deltas
- buildHoverZoomProgram: empty → bg only; correct fill count;
  hovered miner gets 2 strokes including white highlight;
  large cell gets % + address labels; tiny cells skip labels.

Status

- 191/191 tests pass (176 prior + 15 new).
- Typecheck clean.
- Bundles: shared-core 25.1 KB / 40, sharechain-explorer 48.3 KB
  / 120 — both under budget. (+4.8 KB from the new modules.)
- dashboard.html default path (flag off) unchanged: bundle isn't
  loaded, no new DOM, no new event listeners.

Flag-on parity with inline now covers: canvas rendering + all
stat panel counters + hover-zoom PPLNS treemap. Remaining cosmetic
gaps from Phase B #9: RealTime toggle button (inert; bundled is
always live) and particles + dissolve/birth card overlays.

Next: Phase B #11 particles + cards (final cosmetic parity), or
Phase B #13 RealTime-toggle plumbing (small, wires the button to
rt.start/rt.stop), or Qt refactor step 1 (CMake deps).
frstrtr added a commit that referenced this pull request Apr 20, 2026
Builds the visual-regression infrastructure the Explorer spec §11
anchors against the (freshly-tagged) explorer-baseline-v0 on master
@ d95779a. Opens dashboard.html in both modes against a mock c2pool
server, screenshots #defrag-canvas in each, diffs with pixelmatch,
reports delta vs threshold. Current measured delta: 5.02%, passing
the 7% initial threshold with documented reduction path.

Also tags master:

  git tag -a explorer-baseline-v0 master  (annotated)

So every future commit on the explorer-module branch can pixel-diff
against a fixed anchor — spec §11 step 1.

Harness layout (tests/visual/)

- fixtures/generate.mjs      Seeded mulberry32 RNG (seed=0xC2FFEE),
                             deterministic 200-share chain across
                             the full V36-native / V35→V36 / V35-
                             legacy / stale / dead / fee / block mix.
                             Miner 'XMINEADDRESS' triggers mine-
                             colour branches. Emits window.json,
                             tip.json, stats.json, merged_payouts.json.

- mock-server.mjs            Node http server listening on 127.0.0.1
                             :18082. Serves the fixtures for the
                             endpoints dashboard.html hits. SSE
                             stream is keep-alive-only — no tip
                             pushes during capture window, so the
                             screenshots are static. Empty-object
                             replies for non-Explorer endpoints
                             (/peers, /uptime, /stratum_stats, …)
                             keep the rest of dashboard.html from
                             error-cascading.

- capture.mjs                puppeteer-core against the system
                             Chrome (/usr/bin/google-chrome by
                             default; CHROME_BIN overrides).
                             1280x900 viewport, dpr 1, font-hinting
                             off. Goes to dashboard.html twice:
                               inline  → no flag
                               bundled → ?new-explorer=1
                             waits for #defrag-canvas + a 3 s
                             render-settle pause, then screenshots
                             that element only. Stable sizing: same
                             fixture → same cols/rows → identically-
                             sized canvas on both paths.

- diff.mjs                   pngjs + pixelmatch. Writes out/diff.png
                             (red-highlighted delta), reports
                             {pixels, fraction, threshold}, exits
                             non-zero on exceed. Threshold: 0.07
                             default (7%), override via
                             THRESHOLD env or positional arg.

- run.sh                     Orchestrator: generate → start server
                             → capture → diff → kill server. EXIT
                             trap guarantees cleanup.

- README.md                  Design + measured-delta table + three
                             known divergence sources and how
                             future increments close them.

devDeps: puppeteer-core@^23, pixelmatch@^6, pngjs@^7. No browser
download — uses the system Chrome. Install footprint ~5 MB total.

npm run visual               Runs the whole pipeline. Exits non-zero
                             on threshold exceed.

.gitignore                   tests/visual/out/ ignored — screenshots
                             regenerate on every run. Fixture JSON
                             and scripts are tracked.

Status

- npm run visual             PASSES with 5.02% delta vs 7% threshold
- Fixtures:                  200 shares, 12 miners, 2 blocks
- Tests unchanged:           192/192 still green
- Bundle sizes:              shared-core 25.1 KB, sharechain-explorer
                             48.3 KB — unchanged
- explorer-baseline-v0 tag   Annotated, points at master @ d95779a,
                             local-only (not pushed)

Next tightening passes in priority order:

  Phase B #11 particles + cards  Expected to flatten row-boundary
                                 AA differences (source #2 in README)
                                 → 2-3% delta.
  Cell border pass in grid-paint Match inline's fill-then-stroke
                                 order (source #1) → 1% delta.
  Final parity audit             Pin threshold at 0.1%, bake into CI.
frstrtr added a commit that referenced this pull request Apr 20, 2026
Animator now emits particles and card overlays matching dashboard.html's
reference animation (4866-5400), with phase-faithful dying/born timings.

Dying (per share, after its stagger):
  dt < 0.30 RISE     - scale 1 to dyingScale, colour lerps to dead
  dt < 0.55 HOLD     - full-size card with miner addr + PPLNS%
  dt < 1.00 DISSOLVE - shrinking core + 20 ash particles

Born (per share, after its stagger):
  bt < 0.35 COALESCE - 20 particles gather into growing core
  bt < 0.65 HOLD     - full-size card with miner addr + PPLNS%
  bt < 1.00 LAND     - shrink bornScale to 1x, fly to grid slot

Particle positions and velocities are seeded deterministically
(mulberry32, seeded via AnimationInput.rngSeed) so frameAt(t) stays
pure — same inputs + same seed = identical particles.

FrameSpec gains `particles: ParticleFrame[]` and `cards:
CardOverlayFrame[]`. buildAnimatedPaintProgram renders the three
layers in z-order: cells -> particles -> cards. Card composition
(shadow, glow, fill, inner highlight, addr + pct text with drop
shadows) matches dashboard.html:5089-5112 / 5301-5327 exactly.

AnimationInput gains:
  dyingScale / bornScale - card-size multipliers (default 5)
  pplnsOf(share)         - returns fraction in [0,1] for card text;
                           when absent, card shows '--' instead
  minerOf(share)         - overrides the default share.m lookup
  rngSeed                - particle RNG seed (default 0)

Bundle size: 52.9 KB / 120 KB cap (was 48.3 KB; +4.6 KB for ~500
LOC of particle + card logic).

Tests: 196 total, 196 pass (was 192/192). Six animator tests
updated for the new phase semantics (alpha is now phase-gated
rather than linear-decayed); seven new tests cover card overlays,
particle determinism, and the DISSOLVE/COALESCE windows.

The static pixel-diff harness still measures 5.02% - particles
and cards only appear during animation, which the steady-state
screenshot doesn't capture. The border-pass increment (README
divergence source #1) is what moves the static delta needle.
frstrtr added a commit that referenced this pull request Apr 20, 2026
Phase B #11 shipped the animator card overlays with pplnsOf as an
optional input; when absent the card text falls back to "--".

This follow-up plumbs a real pplnsOf through the realtime
orchestrator so dying/born cards render their actual weight
fraction (e.g., "3.142%") during animation.

Implementation matches dashboard.html exactly:

  diff(s)    = bitsToDifficulty(s.b)   (dashboard.html:5875-5884)
  totalDiff  = sum of diff(s) over (oldShares ∪ newShares)
  pplnsOf(s) = diff(s) / totalDiff

Porting bitsToDifficulty verbatim keeps cached difficulty
computations byte-identical across the flag-on boundary. The `b`
field is read from the share object via the unknown escape hatch
so ShareForClassify stays a tight spec-derived contract (short
hash + flags only).

Bundle: 53.3 KB / 120 KB cap (+0.4 KB vs ed47391).
Tests: 196/196 still passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant