docs: refresh Forecast vs. actual duration for the real-temperature anchoring fixes

Documents the recent SudTask.send_forecast()/recv() changes (anchored at
get_theta_ist_set(), re-anchored on every fresh Start) and the two
spurious-divergence fixes: client/brewpi_gui.py's forecast_history t=0
seeding, and the hardcoded last_theta_ist=20 bug in both TempController
variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
This commit is contained in:
2026-06-22 22:44:19 +02:00
co-authored by Claude Sonnet 4.6
parent d981c36194
commit df91ec61f3
+41 -5
View File
@@ -308,11 +308,20 @@ server uses - plant params (`M`/`C`/`L`/`Td`) aren't a constructor
argument at all, since they come from the doc being forecast itself
(`Sud.derive_plant_params()`, same as the real run), not from any generic
default. A multi-hour brew simulates in well under a second since it's
pure CPU-bound iteration, no real time/IO involved. This
is deliberately *not* recomputed as a run progresses: a forecast that keeps
re-anchoring itself to match whatever's actually happening would no longer
be a meaningful baseline to compare reality against. It's computed once,
up front, and left alone.
pure CPU-bound iteration, no real time/IO involved.
It's anchored to the real current temperature
(`TempControllerBase.get_theta_ist_set()` - the raw sensor reading, valid
even before this controller's first `process()` tick, unlike
`get_theta_ist()`), not a cold start at ambient - `SudTask.send_forecast()`
computes it once on Load (a preview of what's coming), and again on every
fresh `Start` (not a Pause→resume, which keeps whatever forecast a run
already established rather than discarding it mid-run), since the real
temperature can have drifted in the meantime
(time passing, manual heating via the Manual tab in between). Once a run
is actually progressing, though, it's deliberately *not* continuously
re-anchored to match whatever's happening: a forecast that keeps chasing
reality would no longer be a meaningful baseline to compare it against.
`SudTask._send_forecast()` thins the curve to at most `MAX_FORECAST_POINTS`
(1000) before broadcasting it - a fine enough `dt` over a multi-hour brew
@@ -366,6 +375,33 @@ speedup runs measurably below it (confirmed: ~80x instead of a configured
100x), which on its own used to produce a divergence large enough to look
like a control problem.
Two other sources of spurious (non-control-related) divergence, both
fixed:
- `client/brewpi_gui.py`'s actual-trace history used to only get its first
sample at `on_plot_timer()`'s next 1-second (real time) tick - under a
real `sim_warp_factor`, simulated time has already moved measurably past
`t=0` by then, leaving a small visible gap between the forecast's exact
`t=0` anchor and where the actual line first appears. `on_sud_changed()`
now seeds `forecast_history` with the real `(elapsed, plot_temp_ist)`
point the moment the first `Elapsed` push of a *fresh* run arrives
(gated on elapsed still being small, `FRESH_RUN_ELAPSED_THRESHOLD_S`, so
reconnecting mid-brew doesn't get a bogus point plotted at `x=0`).
- Both `TempController` variants used to hardcode `last_theta_ist = 20` at
construction, used to compute the very first `process()` tick's
`heatrate_ist` - a wildly bogus rate spike whenever the real starting
temperature wasn't actually 20 (invisible until a forecast actually got
anchored to something other than 20, per the fix above), which then took
several exponentially-smoothed ticks to decay out of the rate-PID loop,
visibly distorting the trajectory during exactly that window. It now
starts `None` and treats the first tick's rate as `0` (no real history
yet) instead of computing it against a hardcoded, usually-wrong
baseline - `SudForecastEstimator` is the case that actually surfaced
this: it builds a fresh `TempController` on every call, so its first
tick always hit this fresh, unlike the real controller, which only ever
goes through it once near server startup and has long since
self-corrected by the time any forecast comparison matters.
## Logging & analysis
`tasks/sud_log.py`'s `SudLogTask` records every Sud run's measured data -