Skip to content

Devcore filesystem gtest#5

Merged
5133n merged 1 commit intofrstrtr:origin/sharechain/async_threadfrom
Kaieida:filesystem
Apr 23, 2021
Merged

Devcore filesystem gtest#5
5133n merged 1 commit intofrstrtr:origin/sharechain/async_threadfrom
Kaieida:filesystem

Conversation

@Kaieida
Copy link
Copy Markdown
Contributor

@Kaieida Kaieida commented Apr 23, 2021

No description provided.

@5133n 5133n merged commit 858602c into frstrtr:origin/sharechain/async_thread Apr 23, 2021
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.
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.

2 participants