# Temperature steady-state offset diagnosis

Status: root cause confirmed; no tuning/configuration mutation performed
Type: evidence
Updated: 2026-07-14
Next Action: correct soft-reset preservation and semantic validation, then recover the five approved I-limit values under attended heater-safe testing
Campaign Reason: Historical record from campaign 017-complete-temperature-control-profile-recovery-and-regulation; retained independently after campaign closure and requires no separate owner-queue execution.
Campaign: standalone
Parent: work/wp/completed/wp-semantic-configuration-validation-and-recovery.md

## Report and live result

The operator reported that the chamber consistently regulates below its 37 C
setpoint after the current candidate/recovery work. Read-only J-Link sampling on
probe 69664082 found:

- target: approximately 37.000 C;
- three samples: 36.597, 36.602, and 36.597 C;
- PID error: 0.407, 0.399, and 0.401 C;
- proportional term: 142.6, 139.6, and 140.5 ticks;
- integral term: 0 ticks in every sample;
- integral limit: 0 ticks;
- derivative term varied with short-term slope as expected;
- final heater requests in the samples: 148, 103, and 134 of 1000 ticks;
- door closed and post-door integral-freeze counter well beyond the configured
  300-second delay.

The live configuration has `MainTemp_K_P = 350`, `MainTemp_K_I = 0.75`, and
`MainTemp_K_D = 5000`, but all five `MainTemp_Ilim_30C` through
`MainTemp_Ilim_70C` values are zero. `calculateTemperatureControlPoints()`
therefore computes `main_Ilim = 0` and seeds/clamps `main_iTerm` to zero. The
controller is effectively proportional/derivative at steady state, so the
nonzero temperature error needed to supply holding heat is expected.

## What changed

`main..dev` comparison shows no PID-calculation change. The only current-branch
change in `Thread_Controller_entry.c` is submission of completed periodic log
records through the bounded SD queue. The integral-limit interpolation and PID
equations are identical.

The persistent configuration changed during the earlier recovery:

1. The pre-recovery configuration had a zero/incomplete temperature profile.
2. Service 524 calls `InitConfigToSoftFactoryDefaults()`, which first loads full
   defaults. Those defaults contain I limits 125, 250, 400, 525, and 675 ticks.
3. The same soft-reset function then restores all five I-limit fields from the
   pre-reset cache. Because those cached fields were zero, the defaults were
   overwritten with zeros and saved to both VEEPROM blocks.
4. The current semantic validator accepts each limit in the range 0--1000 and
   accepts a nondecreasing all-zero schedule, so it reports no finding and lets
   temperature control run with the integral term disabled.

The five preservation assignments originated in commit `eb55fbd7` (2025-08-08)
and exist on both `main` and `dev`; they were not added by the current SD/evaluator
branch. The observed regression is the combination of the previously zeroed
VEEPROM, service-524 recovery, and validation that treats the all-zero schedule
as operational.

## Corrective direction

- Do not preserve I-limit values during soft reset unless the complete schedule
  is explicitly valid and approved; otherwise retain factory defaults.
- Treat an all-zero main-temperature I-limit schedule as invalid when main
  temperature control is enabled.
- Add regression coverage for soft reset from a zeroed profile and verify the
  resulting values in both VEEPROM copies.
- Recover this controller only through an attended, heater-safe configuration
  procedure; do not force heater outputs or patch live RAM through J-Link.

No persistent configuration, tuning value, GPIO, or heater command was changed
during this diagnosis.
