Conversation
…type_stream->PackStream
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.