CMake action for github#7
Merged
5133n merged 2 commits intofrstrtr:origin/sharechain/async_threadfrom Apr 28, 2021
Merged
Conversation
frstrtr
added a commit
that referenced
this pull request
Apr 20, 2026
Three-phase animation state machine (spec §6, dashboard.html:4866-
5400). This commit ships the core — timing math, stagger schedules,
position interpolation for dying/wave/born tracks, and the
controller (start/tick/queueNext/reset). Scale, colour-lerp,
particle dissolution and card overlays land in subsequent commits;
each is a clearly isolable addition that the pure-function structure
accommodates without redesign.
Phase timing (verbatim from dashboard.html:4977-4982):
phase1Dur = 3000 DYING
phase2Dur = fast ? 2000 : 4000 WAVE (spec §6 text has typo,
code is authoritative)
phase2Start = phase1Dur
phase3Start = phase2Start + phase2Dur * 0.7 (overlap)
phase3Dur = 3000 BORN
duration = phase3Start + phase3Dur
Stagger schedules (all preserved verbatim from dashboard.html):
DYING — last dying share first, 150 ms per share. Newer end of
the window dies visibly before older shares (mirrors the
natural tail-eviction order).
WAVE — tail-first per dashboard.html:5146-5151:
distFromTail = (N-1) - newIndex
fraction = distFromTail / (N-1)
shareStart = fraction * phase2Dur * 0.7
shareT = clamp01((p2elapsed - shareStart) / 600)
ease = 1 - (1-shareT)^3 (easeOut-cubic)
Tail starts first, head last; each share animates over a
600 ms window regardless of phase2Dur.
BORN — newest share first, 150 ms per share, full phase3Dur
window per share.
Input / output:
AnimationInput {
oldShares, newShares, addedHashes, evictedHashes,
oldLayout, newLayout, userContext, palette,
hashOf, fast?
}
AnimationPlan {
tEnd, phase1Start/Dur, phase2Start/Dur, phase3Start/Dur,
frameAt(t): FrameSpec
}
FrameSpec {
cells: CellFrame[], // per-share {x,y,size,color,alpha,track}
backgroundColor,
layout // post-merge layout
}
Controller:
createAnimationController() → {
isRunning(), start(plan, now), tick(now), queueNext(plan), reset()
}
- tick() returns the frame to paint, or null when idle.
- queueNext() during running: queued plan starts automatically on
next tick after current tEnd (§6 threshold rule #2, _animDeferred).
- queueNext() while idle: queued plan starts on the next tick.
- reset() drops current + queued.
Also exposed:
SKIP_ANIMATION_NEW_COUNT_THRESHOLD = 100 (§6 rule #1, callers
skip build/start when
newCount >= 100)
Helpers: clamp01, lerp, easeInOut, computePhaseTiming(fast?)
Plugin: explorer.animator.three-phase (provides 'animator.grid')
Tests (19): phase-timing constants for slow + fast; clamp/lerp/ease
sanity; empty input edge; fast vs slow tEnd; wave position
interpolation at t=0 and t=tEnd; wave stagger (tail moves before
head at early phase2 tick); dying staggered alpha decay; dying at
phase1 end alpha ~= 0; born spawns below grid and lands at (0,0);
born alpha fade-in over first 30%; controller idle tick; controller
start+tick+finish becomes idle; controller queueNext during running;
controller queueNext on idle starts next tick; controller reset
drops both.
Status
- 132/132 tests pass in 2.2s (113 prior + 19 new)
- Typecheck clean
- Bundles: shared-core 25.1 KB / 40, sharechain-explorer 33.9 KB /
120 — both under budget
- Pipeline green end-to-end via npm run verify
Next commits layered onto this core:
Phase B #5 scale effects (lift-slide-land wave scale, dying-rise
scale, born shrink from bornScale→1x). Requires no new
types — just richer CellFrame.size interpolation and
paint-program extension for centred-scale fillRect.
Phase B #6 colour lerp — dying lerps toward palette.dead across
its stagger window; born coalesces from unverified to
coin colour.
Phase B #7 particle effects (ash dissolution, birth coalescence)
and card overlays (miner addr + PPLNS % text during
hold frames). Largest increment — may split again.
Phase B #8 wire mergeDelta + animator + gridRenderer into a
RealTime plugin (SSE subscription + delta application
+ animation trigger). Unlocks the demo against a live
c2pool server.
frstrtr
added a commit
that referenced
this pull request
Apr 20, 2026
Adds scale effects, colour lerp, and alpha handling to the animator
so dying shares fade + lerp to red, born shares coalesce from the
unverified grey to the final coin colour, and wave shares lift-
slide-land with a 1.35x pop — all verbatim from dashboard.html's
paint body (lines 5155-5172). Alpha rides as the 4th channel of an
rgba(...) colour string so the existing PaintCommand set doesn't
need a global-alpha extension.
src/explorer/color-utils.ts (~95 LOC)
- parseHexColor(hex) #rrggbb / #rgb / #rrggbbaa → {r,g,b}.
Alpha in 8-digit hex is ignored; use
applyAlpha() to set it explicitly.
- lerpColor(from, to, t) sRGB-space midpoint, t clamped to
[0,1], `rgb(r,g,b)` output. Falls
back to `from` on parse failure.
- applyAlpha(color, a) Accepts hex / rgb() / rgba(), returns
`rgba(r,g,b,a)` with a clamped to
[0,1]. Idempotent (rgba→rgba swaps
just the alpha channel).
- lerpColorWithAlpha Composition convenience.
- ColorUtilsPlugin id 'explorer.color-utils', provides
capability 'color-utils', registered
via registerExplorerBaseline.
src/explorer/animator.ts (in-place enrichment)
- WAVE cells Lift-slide-land scale per
dashboard.html:5155-5167 verbatim:
ease < 0.2 → liftA = ease/0.2
0.2-0.8 → lift held, slide begins
≥ 0.8 → slide done, landA ramp
pop = liftA * (1 - landA)
scale = 1 + 0.35 * pop
Cell drawn centred on sliding centre
(x = centre - size/2).
- DYING cells colour lerps toward palette.dead over
first 60% of stagger; alpha decays
linearly 1→0; rise scale 1→1.10 over
first 30%. Cell centred.
- BORN cells colour coalesces from palette.unverified
to share's final getColor() over first
70% of stagger; alpha fades in over
first 30%; scale shrinks from
BORN_INITIAL_SCALE (3x, pending text-
width-derived bornScale with cards in
Phase B #7) to 1x over last 20%.
Cell centred.
- New tunables WAVE_PEAK_SCALE = 1.35 (matches 0.35
bump in dashboard:5164),
DYING_RISE_SCALE = 1.10,
BORN_INITIAL_SCALE = 3,
WAVE_PER_SHARE_MS = 600 (per-share
slice dur, already present).
src/explorer/grid-paint.ts
- buildAnimatedPaintProgram(frame, dpr)
New function: consumes an animator
FrameSpec, emits setTransform + bg +
per-cell fillCell using the CellFrame's
own `size` (so scale effects carry
through). Cells with alpha ≤ 0 are
skipped entirely.
Tests added
- color-utils.test.ts (14) parseHexColor hex6/hex3/hex8/rejects;
lerpColor endpoints/midpoint/clamp/
fallback; applyAlpha hex/rgb/rgba
paths + clamping; lerpColorWithAlpha
composition.
- animator-visuals.test.ts (8) wave scale hits 1.35 peak and returns
to 1 across its slice; scale never
exceeds peak; dying colour alpha
fades; dying rise scale in bounds;
born colour coalesces + scale shrinks
from 3x to 1x; buildAnimatedPaintProgram
skips alpha-0 cells, threads dpr.
- animator.test.ts (1 update) wave stagger test now compares cell
centres (x + size/2) instead of top-
lefts, since scaling shifts the top-
left inward.
Status
- 154/154 tests pass in 2.2 s (132 prior + 22 new)
- Typecheck clean under strict + exactOptionalPropertyTypes
- Bundles: shared-core 25.1 KB / 40, sharechain-explorer 36.0 KB /
120 — both under budget
- Pipeline green via npm run verify
Next: Phase B #7 particles + card overlays (largest remaining
piece), or Phase B #8 realtime plugin (SSE + delta + animator
wired), or Qt refactor step 1 (CMake deps + option flags).
frstrtr
added a commit
that referenced
this pull request
Apr 20, 2026
Wires Transport subscribeStream + fetchDelta + mergeDelta + Animator
+ renderer into a single live-updates runtime. Operational parity
with dashboard.html's RealTime-mode pipeline.
src/explorer/realtime.ts (~370 LOC)
- RealtimeOrchestrator — pure state machine, no DOM, no RAF.
constructor(RealtimeConfig)
start() fetches window, subscribes to stream
stop() unsubscribe + AbortController.abort()
refresh() forces full window rebuild
getState() { window, animating, hasQueued, started,
shareCount, lastAppliedTip, deltaInFlight }
currentFrame(now) FrameSpec | null (idle returns static frame)
buildStaticFrame() no-animation snapshot for initial paint +
post-animation idle frames + resize events
Contract (spec §5 + §6):
1. start() → fetchWindow → subscribeStream.
2. onTip({hash}) → dedup against lastAppliedTip + pendingTip.
3. Only one delta request in flight at a time; if a newer tip
arrives during a fetch, drain it right after the current one
settles (coalesce).
4. mergeDelta → if fork_switch, trigger full rebuild.
5. If added.length >= skipAnimationThreshold (default 100 per
spec §6 rule #1), skip animation and reset the animator.
6. Otherwise buildAnimationPlan + either start (idle animator)
or queueNext (running animator per rule #2, _animDeferred).
7. onReconnect → fetchTip and apply if changed (delta v1 §A.3
catch-up semantics).
8. All Transport calls thread AbortSignal from the orchestrator's
internal AbortController so stop() cancels in-flight work.
- createRealtime(RealtimeDOMOptions) → RealtimeController
DOM adapter: owns the canvas + requestAnimationFrame loop.
Sizes canvas for devicePixelRatio each frame; paints via
buildAnimatedPaintProgram + executePaintProgram. stop() cancels
the RAF, calls orchestrator.stop(), and destroys the renderer.
- RealtimePlugin — id 'explorer.realtime.default', provides
'realtime.orchestrator', fills slot 'explorer.data.realtime'.
Capabilities expose { RealtimeOrchestrator, createRealtime } for
plugin consumers. Registered via registerExplorerBaseline.
Type tightening
- ShareForClassify gains `h: string` (spec §5.1 — every share has
one; omitting it was an oversight). Now satisfies DeltaShare
directly.
- DeltaShare relaxed: dropped the `[key: string]: unknown` index
signature — only requires `{ h: string }`. Strict interfaces
(ShareForClassify) now satisfy it without casts.
- realtime.ts extracts `h` from the provided hashOf (default:
(s) => s.h) rather than dictating the shape.
Tests (tests/unit/realtime.test.ts — 12 tests)
- start: fetches window + sets tip; empty window valid
- tip triggers delta fetch and appends; delta.since = current.tip
- tip dedup on same hash
- tip coalescing: max 1 delta in flight under rapid-fire tips
- fork_switch triggers second fetchWindow
- skipAnimationThreshold: bulk updates skip animation path
- below threshold: animation runs and completes via currentFrame(t)
- stop unsubscribes + halts tip processing after stop
- reconnect: fetches tip, applies delta if changed
- fetchWindow error surfaces via onError as structured ExplorerError
- currentFrame static snapshot reflects live window state
Status
- 166/166 tests pass in ~2.3s (154 prior + 12 new)
- Typecheck clean
- Bundles: shared-core 25.1 KB / 40 (63%), sharechain-explorer 42.0
KB / 120 (35%) — both under budget; 78 KB headroom for
particles + cards + any future wiring.
Next: Phase B #8 particles + card overlays (largest remaining
visual piece; ~300-400 LOC), or Qt refactor step 1 (CMake deps,
~30 LOC, parallel track). After particles + cards, Phase B is
feature-complete and M2 pixel-diff work begins.
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.