# SAFETY-93 - the maintenance charge end hands back smoothly **Status: implemented 2026-10-02, for review.** Tests: `test_safety93.py`. **The defect** (SAFETY-85 rig test, check 4; also on 9358781). At N=2, when A's maintenance charge ended, B went 0 -> -1600 W in one cycle and charged from the grid with the house at 6000 W. The HOLD end was already smooth (SAFETY-85). **Cause.** During the charge the site target holds A's charge (SAFETY-84's `charge` ceiling, -2000 W). At the end A gets its HOLD pin (0 W), but the loop starts its next step from last cycle's site target. The slew limit moves the site total by 400 W (-2000 -> -1600), and the only battery left to place it on is B. The same happens when the charge times out or the battery is switched Off (A free, its -2000 W shared out). With the charge suspended by the guard and A giving under the loop, B took A's share in one step (675 W). **The fix** (`main.py`, `_reseed_charge_end()`, N>=2 only). On the cycle a battery leaves CHARGE, the loop's start point (`self.target`, and the integrator with it) is what the other batteries stood at plus that battery's new command: its pin (HOLD or abort, 0 W), or, if it is free, its last discharge (a charge stops). The loop then moves from there one slew step a cycle. The ending battery's own step (-2000 -> 0 W) is maintenance's pin, as before; it lowers the grid import. The end is kept until the loop next runs, so a missed poll on that cycle (the input hold) does not lose it. Review: a charge also ends, for the site, when the battery is still in CHARGE but can no longer be placed: a stale link or a refusal (SAFETY-73) drops it, and its -2000 W landed on B the same way (B 0 -> -1600 W at a 1200 W house). So the end is noted after the unit claims are filed: a battery leaves the charge when it leaves CHARGE, is not live, or its own claims contradict. The guard suspending the charge (SAFETY-84) is no end. Why a start-point reseed, and not a per-battery slew in the allocator: the jump comes from one stale number at one edge, the kind of edge SAFETY-73 (`_unit_refusal_end`) and OPT-11 P3 (`_reseed_run_edge`) already reseed. The change stays at that edge; nothing else at N>=2 moves. **Before -> after** (`test_safety93.py`, 2 x 2500 W, 10 s lag, 2 s meter): | case | before | after | |---|---|---| | rig case: house 6000, A charges 2000 to the end | B 0 -> -1600, charges 4 cycles | B 0 -> 400 -> 800 ..., never charges | | house 1200 | B 0 -> -1600 (2800 W from the grid) | B steps of 400, never charges | | charge times out / A switched Off | B 0 -> -1600 | B 0 -> 400 | | guard suspended the charge, A gives 1825 | B step 675 W | B step 395 W | | missed poll on the end cycle | B 0 -> -1600 a cycle later | B 0 -> 400 | | A dropped mid-charge (stale link, refusal), house 1200 | B 0 -> -1600 | B 0 -> 400 | | N=1, both charge ends above | | byte-identical | No pinned digest moved: the 46 digests of `tools/*identity*.py` (N=1 and N=2) are byte-identical to f3e409c; none of their scenarios reaches a charge end. **Not changed.** The peak guard's floor still moves the site in one step when it engages (a house step, a quarter edge): the guard outranks the slew (owner order). A HOLD pin still drops the holding battery to 0 W at once and SAFETY-86 gives way to the others first. SAFETY-88 (charge flapping above the aim) is untouched.