diff --git a/src/fountain_decoder.c b/src/fountain_decoder.c index f9c8b29..e63e9d3 100644 --- a/src/fountain_decoder.c +++ b/src/fountain_decoder.c @@ -1373,6 +1373,61 @@ fountain_decoder_estimated_percent_complete(fountain_decoder_t *decoder) { return progress > 0.99 ? 0.99 : progress; } +// Weighted-mixed-frames completion estimate. Ports SeedSigner's +// weight_mixed_frames=True method (helpers/ur2/fountain_decoder.py): count the +// fully decoded fragments, plus partial credit for fragments that are still +// only present inside mixed (XOR'd) frames. Each mixed frame contributes +// 1/(frames mixed) to every index it covers; each index's total contribution +// is capped at 0.75 so a not-yet-decoded fragment never counts as much as a +// decoded one (and so the reported percentage cannot decrease mid-decode). +// Backward-compatible addition — the reference estimate above is unchanged. +double fountain_decoder_estimated_percent_complete_weighted( + fountain_decoder_t *decoder) { + if (!decoder) + return 0.0; + if (fountain_decoder_is_complete(decoder)) + return 1.0; + if (!decoder->expected_part_indexes || + decoder->expected_part_indexes->count == 0) + return 0.0; + + size_t parts = decoder->expected_part_indexes->count; + + // Per-index partial scores from the mixed parts. Fragment indexes are in + // [0, seq_len) == [0, parts), so a scratch array keyed by index mirrors the + // Python dict `mixed_index_scoring`. Fall back to the reference estimate if + // the scratch allocation fails. + double *scoring = calloc(parts, sizeof(double)); + if (!scoring) + return fountain_decoder_estimated_percent_complete(decoder); + + mixed_parts_hash_t *hash = decoder->mixed_parts_hash; + if (hash) { + for (size_t b = 0; b < hash->capacity; b++) { + for (hash_entry_t *entry = hash->buckets[b]; entry; entry = entry->next) { + size_t cnt = entry->key.count; + if (cnt == 0) + continue; + double score = 1.0 / (double)cnt; + for (size_t k = 0; k < cnt; k++) { + size_t index = entry->key.indexes[k]; + if (index < parts) + scoring[index] += score; + } + } + } + } + + double mixed_score = 0.0; + for (size_t i = 0; i < parts; i++) { + mixed_score += scoring[i] < 0.75 ? scoring[i] : 0.75; + } + free(scoring); + + double num_complete = (double)decoder->received_part_indexes.count; + return (num_complete + mixed_score) / (double)parts; +} + uint8_t *fountain_decoder_result_message(fountain_decoder_t *decoder) { if (!decoder || !decoder->result) return NULL; diff --git a/src/fountain_decoder.h b/src/fountain_decoder.h index 4b0b876..7ee5030 100644 --- a/src/fountain_decoder.h +++ b/src/fountain_decoder.h @@ -77,6 +77,15 @@ size_t fountain_decoder_processed_parts_count(fountain_decoder_t *decoder); */ double fountain_decoder_estimated_percent_complete(fountain_decoder_t *decoder); +/** + * Get estimated completion percentage using the weighted-mixed-frames method + * (partial credit for fragments still only present inside mixed/XOR'd frames). + * @param decoder Pointer to fountain decoder + * @return Completion percentage (0.0 to 1.0) + */ +double fountain_decoder_estimated_percent_complete_weighted( + fountain_decoder_t *decoder); + /** * Get result message * @param decoder Pointer to fountain decoder diff --git a/src/ur_decoder.c b/src/ur_decoder.c index 55178f8..485695b 100644 --- a/src/ur_decoder.c +++ b/src/ur_decoder.c @@ -458,6 +458,13 @@ double ur_decoder_estimated_percent_complete(ur_decoder_t *decoder) { return fountain_decoder_estimated_percent_complete(decoder->fountain_decoder); } +double ur_decoder_estimated_percent_complete_weighted(ur_decoder_t *decoder) { + if (!decoder || !decoder->fountain_decoder) + return 0.0; + return fountain_decoder_estimated_percent_complete_weighted( + decoder->fountain_decoder); +} + ur_decoder_error_t ur_decoder_get_last_error(ur_decoder_t *decoder) { return decoder ? decoder->last_error : UR_DECODER_ERROR_NULL_POINTER; } diff --git a/src/ur_decoder.h b/src/ur_decoder.h index 6d4552c..c4ed058 100644 --- a/src/ur_decoder.h +++ b/src/ur_decoder.h @@ -97,6 +97,13 @@ size_t ur_decoder_processed_parts_count(ur_decoder_t *decoder); */ double ur_decoder_estimated_percent_complete(ur_decoder_t *decoder); +/** + * Get estimated completion percentage using the weighted-mixed-frames method. + * @param decoder Pointer to URDecoder instance + * @return Completion percentage (0.0 to 1.0) + */ +double ur_decoder_estimated_percent_complete_weighted(ur_decoder_t *decoder); + /** * Get last error * @param decoder Pointer to URDecoder instance diff --git a/uUR.c b/uUR.c index fa02b26..35790cf 100644 --- a/uUR.c +++ b/uUR.c @@ -295,18 +295,34 @@ static mp_obj_t ur_decoder_processed_parts_count_py(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(ur_decoder_processed_parts_count_obj, ur_decoder_processed_parts_count_py); -// estimated_percent_complete method -static mp_obj_t ur_decoder_estimated_percent_complete_py(mp_obj_t self_in) { - mp_obj_ur_decoder_t *self = MP_OBJ_TO_PTR(self_in); +// estimated_percent_complete(weight_mixed_frames=False) method. +// weight_mixed_frames is an opt-in flag: the default (False) returns the +// original reference estimate byte-for-byte, so existing callers are +// unaffected; True selects the weighted-mixed-frames method, which gives +// partial credit for fragments still only present inside mixed/XOR'd frames. +static mp_obj_t ur_decoder_estimated_percent_complete_py(size_t n_args, + const mp_obj_t *pos_args, + mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + {MP_QSTR_weight_mixed_frames, MP_ARG_BOOL, {.u_bool = false}}, + }; + mp_arg_val_t parsed[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, parsed); + mp_obj_ur_decoder_t *self = MP_OBJ_TO_PTR(pos_args[0]); if (!self->decoder) { return mp_obj_new_float(0.0); } - return mp_obj_new_float(ur_decoder_estimated_percent_complete(self->decoder)); + double pct = + parsed[0].u_bool + ? ur_decoder_estimated_percent_complete_weighted(self->decoder) + : ur_decoder_estimated_percent_complete(self->decoder); + return mp_obj_new_float(pct); } -static MP_DEFINE_CONST_FUN_OBJ_1(ur_decoder_estimated_percent_complete_obj, - ur_decoder_estimated_percent_complete_py); +static MP_DEFINE_CONST_FUN_OBJ_KW(ur_decoder_estimated_percent_complete_obj, 1, + ur_decoder_estimated_percent_complete_py); // URDecoder locals dict static const mp_rom_map_elem_t ur_decoder_locals_dict_table[] = {