Skip to content

Add opt-in weighted-mixed-frames progress estimate#4

Closed
kdmukAI-bot wants to merge 1 commit into
odudex:mainfrom
kdmukAI-bot:feat/weighted-progress-estimate
Closed

Add opt-in weighted-mixed-frames progress estimate#4
kdmukAI-bot wants to merge 1 commit into
odudex:mainfrom
kdmukAI-bot:feat/weighted-progress-estimate

Conversation

@kdmukAI-bot

Copy link
Copy Markdown
Contributor

Adds an opt-in weighted progress estimate for animated (fountain-coded) UR
decoding, alongside the existing reference estimate. Ported from
SeedSigner/seedsigner#541, where it has been the progress metric shown during
SeedSigner's live animated-QR scans in production since 2024.

Why

The reference estimate is min(0.99, processed_parts_count / (expected_part_count * 1.75)) — a frame-count guesstimate. On long animated QRs
it reaches its 0.99 cap well before the decode completes and sits there,
reporting a misleading "almost done" for many frames. In real-world use this is
a common source of user frustration: the display shows 99% and looks about to
finish, but the scan keeps going for what can feel like a long time.

Method (opt-in)

The weighted estimate counts information actually recovered:

  • A fully decoded fragment scores 1.0.
  • A fragment present only inside mixed (XOR'd) frames gets partial credit: each
    mixed frame contributes 1/(fragments mixed) to every fragment it covers,
    summed across mixed frames.
  • Each fragment's partial score is capped at 0.75, so an undecoded fragment
    never counts as much as a decoded one (and the reported percentage can't
    decrease).
  • estimated_percent_complete gains an opt-in weight_mixed_frames flag; the
    default (False) returns the reference estimate byte-for-byte, so existing
    callers are unaffected.

Quantified comparison (on-device, ESP32-P4)

Realistic runs — the user misses the opening systematic frames and only ever
catches fountain frames, with a 30% misframe rate — across message sizes × 3
random seeds each. "Stuck" = the displayed integer percent does not change;
measured before completion:

seq_len metric avg frames frames stuck >=99% longest stall
40 reference 72 3.0 2.7
40 weighted 72 0.0 12.0
80 reference 176 33.7 32.7
80 weighted 176 0.0 25.3
120 reference 275 63.0 62.0
120 weighted 275 0.0 30.0
160 reference 357 73.7 72.7
160 weighted 357 0.0 34.0

The reference sits at 99% longer as the QR grows (up to ~74 frames on a
160-fragment QR); the weighted estimate never reports 99% before completion.
Both plateau near the end — the decode itself stalls waiting for a peeling
frame, so the weighted's longest stall is 12-34 frames — but it stalls at a
truthful ~85-90%, not a misleading 99%.

Trade-off

Because it scores information actually recovered rather than frames seen, the
weighted estimate rises faster early in a scan (partial credit for the mixed
frames already held in memory), then slows toward the end as the decode waits on
the last peeling frames. It can give the impression of a faster scan up front
followed by a slowdown — but that shape honestly reflects what is actually being
reassembled in memory at each stage.

The weighted values are identical whether computed by this C implementation or
the reference Python decoder; verified on ESP32-P4.

Adds a weighted progress estimate for animated (fountain-coded) UR decoding,
alongside the existing reference estimate. Ported from SeedSigner/seedsigner#541,
where it has been the progress metric shown during SeedSigner's live animated-QR
scans in production since 2024.

The reference estimate, min(0.99, processed_parts_count /
(expected_part_count * 1.75)), is a frame-count guesstimate that reaches its
0.99 cap well before a long animated QR completes and sits there, reporting a
misleading "almost done" for many frames -- a common real-world source of user
frustration.

The weighted estimate counts information actually recovered: a decoded fragment
scores 1.0; a fragment present only inside mixed (XOR'd) frames gets partial
credit (each mixed frame contributes 1/(fragments mixed) to every fragment it
covers, summed across mixed frames), capped at 0.75 per fragment so an undecoded
fragment never counts as much as a decoded one and the percentage can't decrease.

- fountain_decoder.c/.h: fountain_decoder_estimated_percent_complete_weighted().
- ur_decoder.c/.h: ur_decoder_estimated_percent_complete_weighted() wrapper.
- uUR.c: estimated_percent_complete gains an opt-in weight_mixed_frames flag
  (default False returns the reference estimate byte-for-byte; existing callers
  are unaffected).

Verified on ESP32-P4; weighted values match the reference Python decoder.

Co-Authored-By: kdmukai <934746+kdmukai@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@odudex

odudex commented Jul 6, 2026

Copy link
Copy Markdown
Owner

@kdmukai I merged your commits from both PRs, together with follow-ups closing some small gaps and formatting.
Please check if it still builds and behaves correctly on your end.
Thank you!

@odudex odudex closed this Jul 6, 2026
@kdmukAI-bot kdmukAI-bot deleted the feat/weighted-progress-estimate branch July 7, 2026 13:47
@kdmukai

kdmukai commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@odudex everything looks good. Confirmed via a P4 build and on-device tests.

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.

3 participants