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:
2026-04-30 13:15:03 -07:00
parent a1b8011192
commit c993437e16
+21 -11
View File
@@ -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<CopyResult> {
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<bool> = Vec::with_capacity(DAMAGE_WINDOW);
let mut jump_multiplier: u64 = 1;