Devcore filesystem gtest#5
Merged
5133n merged 1 commit intofrstrtr:origin/sharechain/async_threadfrom Apr 23, 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 24, 2026
Crash reproducibly hit on first SML sync timer fire (PID dies right after the [SML] sync request log line, before any mnlistdiff arrives). Apport core dump bt: Thread 1 SIGSEGV #0 0x...3f0 (??) ← jumped to garbage #1 initiate_async_wait::operator()<std::function<...>&>(...) #2 main::{lambda(...)#3}::operator()(...) ← timer lambda body #3 wait_handler::do_complete(...) #4 scheduler::run(error_code&) #5 main Bug: the persistent std::function `sml_sync_tick` was passed BY LVALUE to async_wait, captured `&sml_sync_tick` for self-reference. boost:: asio's perfect-forwarding into the internal handler queue moved-from the lvalue on first dispatch (universal-reference deduction + std::forward semantics), leaving the outer std::function empty. The copy that ran on first fire then re-armed by passing the now-empty outer function — second fire dereferenced the empty std::function and SIGSEGV'd into garbage. Fix: hold the persistent std::function on the heap via shared_ptr, and NEVER pass it directly to async_wait. Instead, schedule via a fresh wrapper lambda that captures the shared_ptr by value and invokes (*sml_sync_tick)(ec) when fired. Each schedule() call hands async_wait a brand-new lambda; boost::asio can move-from the temporary as much as it wants without affecting the persistent function. Verified: pattern is the canonical chained-timer idiom. The shared_ptr keeps the tick function + captures alive across reschedules; each fresh wrapper lambda holds its own copy of the shared_ptr (refcount++) so the timer chain is self-sustaining until io_context exits.
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.