From 7dd5001d45ceb9796d293ad8b0590fb2406f5f86 Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:15:03 -0700 Subject: [PATCH] v0.15.1: fix damage-jump detection (window=16, threshold=12%), fix dispatch covers_disc check, sweep resume from mapfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DAMAGE_WINDOW 50→16, DAMAGE_THRESHOLD_PCT 25→12: triggers on 2nd scattered failure - Previous 50/25% was too diluted by good reads between sparse failures - copy() dispatch checks mapfile total_size == disc capacity_bytes (covers_disc) - NonTried regions → sweep with resume=true (preserves mapfile) - Only NonTrimmed/NonScraped/Unreadable → patch - sweep_internal takes resume param: true when dispatching from existing mapfile - Verified: Pass 1 from 30-100% completes in ~20 min with correct jumps through 3 damage zones --- src/disc/mod.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/disc/mod.rs b/src/disc/mod.rs index 3b19420..429fe16 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -1238,17 +1238,22 @@ impl Disc { let map = mapfile::Mapfile::load(&mf_path) .map_err(|e| Error::IoError { source: e })?; let stats = map.stats(); + let disc_size = self.capacity_bytes; + let covers_disc = map.total_size() == disc_size; let bad_bytes = stats.bytes_pending + stats.bytes_unreadable; tracing::info!( - "copy dispatch: {} good, {} nontried, {} pending, {} unreadable", + "copy dispatch: disc={} map={} covers={} good={} nontried={} pending={} unreadable={}", + disc_size, + map.total_size(), + covers_disc, stats.bytes_good, stats.bytes_nontried, stats.bytes_pending, stats.bytes_unreadable, ); - if bad_bytes == 0 { + if covers_disc && bad_bytes == 0 { return Ok(CopyResult { - bytes_total: map.total_size(), + bytes_total: disc_size, bytes_good: stats.bytes_good, bytes_unreadable: stats.bytes_unreadable, bytes_pending: 0, @@ -1257,15 +1262,19 @@ impl Disc { halted: false, }); } - if stats.bytes_nontried > 0 { - tracing::info!("copy dispatch: → sweep (NonTried regions remain)"); - return self.sweep_internal(reader, path, opts); + if !covers_disc || stats.bytes_nontried > 0 { + tracing::info!( + "copy dispatch: → sweep (covers_disc={}, nontried={})", + covers_disc, + stats.bytes_nontried, + ); + return self.sweep_internal(reader, path, opts, true); } - tracing::info!("copy dispatch: → patch (only NonTrimmed/NonScraped/Unreadable remain)"); + tracing::info!("copy dispatch: → patch"); return self.patch_internal(reader, path, opts); } } - self.sweep_internal(reader, path, opts) + self.sweep_internal(reader, path, opts, false) } fn sweep_internal( @@ -1273,10 +1282,11 @@ impl Disc { reader: &mut dyn SectorReader, path: &std::path::Path, opts: &CopyOptions, + resume: bool, ) -> Result { let sweep_opts = SweepOptions { decrypt: opts.decrypt, - resume: false, + resume, batch_sectors: None, skip_on_error: opts.multipass, progress: opts.progress, @@ -1376,8 +1386,8 @@ impl Disc { let mut bridge_degradation_count: u32 = 0; const BRIDGE_DEGRADATION_MAX: u32 = 5; const BRIDGE_DEGRADATION_COOLDOWN_SECS: u64 = 10; - const DAMAGE_WINDOW: usize = 50; - const DAMAGE_THRESHOLD_PCT: usize = 25; + const DAMAGE_WINDOW: usize = 16; + const DAMAGE_THRESHOLD_PCT: usize = 12; const JUMP_SECTORS_FACTOR: u64 = 256; let mut damage_window: Vec = Vec::with_capacity(DAMAGE_WINDOW); let mut jump_multiplier: u64 = 1;