v0.15.1: fix damage-jump detection (window=16, threshold=12%), fix dispatch covers_disc check, sweep resume from mapfile

- 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
This commit is contained in:
MattJackson
2026-04-30 13:15:03 -07:00
parent 67fe93c0b8
commit 7dd5001d45
+21 -11
View File
@@ -1238,17 +1238,22 @@ impl Disc {
let map = mapfile::Mapfile::load(&mf_path) let map = mapfile::Mapfile::load(&mf_path)
.map_err(|e| Error::IoError { source: e })?; .map_err(|e| Error::IoError { source: e })?;
let stats = map.stats(); 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; let bad_bytes = stats.bytes_pending + stats.bytes_unreadable;
tracing::info!( 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_good,
stats.bytes_nontried, stats.bytes_nontried,
stats.bytes_pending, stats.bytes_pending,
stats.bytes_unreadable, stats.bytes_unreadable,
); );
if bad_bytes == 0 { if covers_disc && bad_bytes == 0 {
return Ok(CopyResult { return Ok(CopyResult {
bytes_total: map.total_size(), bytes_total: disc_size,
bytes_good: stats.bytes_good, bytes_good: stats.bytes_good,
bytes_unreadable: stats.bytes_unreadable, bytes_unreadable: stats.bytes_unreadable,
bytes_pending: 0, bytes_pending: 0,
@@ -1257,15 +1262,19 @@ impl Disc {
halted: false, halted: false,
}); });
} }
if stats.bytes_nontried > 0 { if !covers_disc || stats.bytes_nontried > 0 {
tracing::info!("copy dispatch: → sweep (NonTried regions remain)"); tracing::info!(
return self.sweep_internal(reader, path, opts); "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); return self.patch_internal(reader, path, opts);
} }
} }
self.sweep_internal(reader, path, opts) self.sweep_internal(reader, path, opts, false)
} }
fn sweep_internal( fn sweep_internal(
@@ -1273,10 +1282,11 @@ impl Disc {
reader: &mut dyn SectorReader, reader: &mut dyn SectorReader,
path: &std::path::Path, path: &std::path::Path,
opts: &CopyOptions, opts: &CopyOptions,
resume: bool,
) -> Result<CopyResult> { ) -> Result<CopyResult> {
let sweep_opts = SweepOptions { let sweep_opts = SweepOptions {
decrypt: opts.decrypt, decrypt: opts.decrypt,
resume: false, resume,
batch_sectors: None, batch_sectors: None,
skip_on_error: opts.multipass, skip_on_error: opts.multipass,
progress: opts.progress, progress: opts.progress,
@@ -1376,8 +1386,8 @@ impl Disc {
let mut bridge_degradation_count: u32 = 0; let mut bridge_degradation_count: u32 = 0;
const BRIDGE_DEGRADATION_MAX: u32 = 5; const BRIDGE_DEGRADATION_MAX: u32 = 5;
const BRIDGE_DEGRADATION_COOLDOWN_SECS: u64 = 10; const BRIDGE_DEGRADATION_COOLDOWN_SECS: u64 = 10;
const DAMAGE_WINDOW: usize = 50; const DAMAGE_WINDOW: usize = 16;
const DAMAGE_THRESHOLD_PCT: usize = 25; const DAMAGE_THRESHOLD_PCT: usize = 12;
const JUMP_SECTORS_FACTOR: u64 = 256; const JUMP_SECTORS_FACTOR: u64 = 256;
let mut damage_window: Vec<bool> = Vec::with_capacity(DAMAGE_WINDOW); let mut damage_window: Vec<bool> = Vec::with_capacity(DAMAGE_WINDOW);
let mut jump_multiplier: u64 = 1; let mut jump_multiplier: u64 = 1;