Reported from a real run: the ETA showed ~4-5 minutes, jumped to 2 hours for a few seconds, came back to 2-3 minutes, then oscillated — 2.10, then 1.99, then 2.22 — before settling. On a second run it swung from 5 hours to 2 minutes and then to --.
Two separate causes, both in Engine.snapshot() (moon_engine.py:586-591).
1. The 2 hours is a clamp, not an estimate
eta = min(files_remaining * avg_file / (mbs * 1_048_576), 7200)
7200 seconds is exactly 2 hours. Whenever the computation blows up — which it does early in a run, when mbs is momentarily tiny — the result is clamped and the GUI renders it as "2h", indistinguishable from a genuine estimate. The operator sees a confident, precise, wrong number.
A clamped value means "I don't know". It should be displayed as unknown (--), the same as the eta = 0.0 branch already is, not as its ceiling.
2. The estimate is built on a badly skewed average early on
avg_file = total_downloaded / dl_done
total_downloaded sums bytes from every file including the ones still in flight, while dl_done counts only the ones that finished. Early in a run — say 40 files downloading, 2 finished — this divides 40 files' worth of bytes by 2, so avg_file is wildly overestimated and the ETA with it. That is the "5 hours" reading.
3. The oscillation
mbs comes from a 3-second window:
recent = [(t, b) for t, b in snap if t > now - 3.0]
Three seconds is short enough that normal chunk-arrival jitter moves the ETA visibly every refresh. 2.10 → 1.99 → 2.22 is that noise, not the estimate genuinely changing.
What to do
- Return
None (rendered --) when the estimate is clamped or not yet meaningful, instead of the ceiling
- Compute
avg_file from completed files only, or from bytes_total / (dl_done + partial progress of in-flight files) — either is defensible, say which you chose
- Smooth the rate used for the ETA over a longer window than the 3 seconds used for the live speed display, or apply an exponential moving average. The displayed speed should stay responsive; the ETA does not need to be
Acceptance criteria
- No run ever displays exactly "2h" as a result of the clamp
- The ETA does not visibly jitter every refresh once a run is in steady state
- The live speed readout stays as responsive as it is today
tests/test_snapshot.py is the place for the regression tests, and #57 is adding contract tests there already — coordinate if both are in flight.
Reported from a real run: the ETA showed ~4-5 minutes, jumped to 2 hours for a few seconds, came back to 2-3 minutes, then oscillated — 2.10, then 1.99, then 2.22 — before settling. On a second run it swung from 5 hours to 2 minutes and then to
--.Two separate causes, both in
Engine.snapshot()(moon_engine.py:586-591).1. The 2 hours is a clamp, not an estimate
7200seconds is exactly 2 hours. Whenever the computation blows up — which it does early in a run, whenmbsis momentarily tiny — the result is clamped and the GUI renders it as "2h", indistinguishable from a genuine estimate. The operator sees a confident, precise, wrong number.A clamped value means "I don't know". It should be displayed as unknown (
--), the same as theeta = 0.0branch already is, not as its ceiling.2. The estimate is built on a badly skewed average early on
total_downloadedsums bytes from every file including the ones still in flight, whiledl_donecounts only the ones that finished. Early in a run — say 40 files downloading, 2 finished — this divides 40 files' worth of bytes by 2, soavg_fileis wildly overestimated and the ETA with it. That is the "5 hours" reading.3. The oscillation
mbscomes from a 3-second window:Three seconds is short enough that normal chunk-arrival jitter moves the ETA visibly every refresh. 2.10 → 1.99 → 2.22 is that noise, not the estimate genuinely changing.
What to do
None(rendered--) when the estimate is clamped or not yet meaningful, instead of the ceilingavg_filefrom completed files only, or frombytes_total / (dl_done + partial progress of in-flight files)— either is defensible, say which you choseAcceptance criteria
tests/test_snapshot.pyis the place for the regression tests, and #57 is adding contract tests there already — coordinate if both are in flight.