SAFETY-04: stop the clamps standing in for the mechanisms under test

AC 3 - "integration freezes while saturated" - had no non-vacuous test.
Deleting the integrator freeze outright failed 0 of 55 checks: the two checks
that name it used a fixture at max_w 2000 with the integrator bound following
it, so a wound value was truncated back to exactly 2000 and the assertion
passed on the clamp instead. Fixture lifted to max_w 5000, clear of every rail.
Deleting the freeze now fails 2.

Then swept the whole function for the same pattern, one mechanism at a time:
delete it, count which checks notice. It found a second instance - the OUTPUT
clamp. `clamped to max_w` and `clamped to -max_w` were both satisfied by the
integrator bound truncating first, so removing the output clamp failed only the
reason-string check. Those two fixtures now set integrator_max_w above max_w so
the mechanism they name is the binding one; the output clamp goes from 1 failure
to 3.

Every mechanism in compute() is now caught by a check that names it: freeze 2,
integrator clamp 6, bound-follows-max_w 4, output clamp 3, slew 4, output freeze
2, deadband 5, quantise 2, detector 10, duration 2, counter reset 6, grid bias 3,
None-seeding 6. No mechanism at zero.

Method note: the audit disables bytecode caching. Rewriting control.py inside
one second leaves a stale app/__pycache__ entry and silently under-reports -
it under-reported one mutation as 2 failures where the true figure is 6.

test_control.py stays at 55 checks, all passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Du77usMj8XNKNFZGmUiWDa
This commit is contained in:
glenn schrooyen
2026-08-24 22:59:29 +02:00
co-authored by Claude Opus 5
parent 53d301b920
commit 389d9ecd6d
+14 -5
View File
@@ -41,15 +41,19 @@ check("proportional step (gain 0.6)", d.target_w == 300)
d = compute(prev_w=0, grid_w=-500, actual_w=0, tuning=T) d = compute(prev_w=0, grid_w=-500, actual_w=0, tuning=T)
check("export drives charging", d.target_w == -300) check("export drives charging", d.target_w == -300)
# Clamp # Clamp.
d = compute(prev_w=1900, grid_w=1000, actual_w=1900, tuning=Tuning(max_w=2000, slew_w=5000)) # ⚠️ integrator_max_w is lifted clear of max_w so that the OUTPUT clamp is the
# mechanism under test. Left at the default the integrator bound truncates
# first, these two assertions pass on that alone, and deleting the output clamp
# fails nothing - the same masking that hid the integrator freeze.
TCLAMP = Tuning(max_w=2000, slew_w=5000, integrator_max_w=5000)
d = compute(prev_w=1900, grid_w=1000, actual_w=1900, tuning=TCLAMP)
check("clamped to max_w", d.target_w == 2000) check("clamped to max_w", d.target_w == 2000)
# Slew: from 0 with a huge error, no more than slew_w in one cycle. # Slew: from 0 with a huge error, no more than slew_w in one cycle.
d = compute(prev_w=0, grid_w=5000, actual_w=0, tuning=Tuning(max_w=5000, slew_w=1000)) d = compute(prev_w=0, grid_w=5000, actual_w=0, tuning=Tuning(max_w=5000, slew_w=1000))
check("slew limits one cycle", d.target_w == 1000) check("slew limits one cycle", d.target_w == 1000)
d = compute(prev_w=-1900, grid_w=-1000, actual_w=-1900, d = compute(prev_w=-1900, grid_w=-1000, actual_w=-1900, tuning=TCLAMP)
tuning=Tuning(max_w=2000, slew_w=5000))
check("clamped to -max_w", d.target_w == -2000) check("clamped to -max_w", d.target_w == -2000)
d = compute(prev_w=0, grid_w=-5000, actual_w=0, tuning=Tuning(max_w=5000, slew_w=1000)) d = compute(prev_w=0, grid_w=-5000, actual_w=0, tuning=Tuning(max_w=5000, slew_w=1000))
check("slew limits one cycle, charging", d.target_w == -1000) check("slew limits one cycle, charging", d.target_w == -1000)
@@ -132,7 +136,12 @@ check("integrator bound binds independently of the output clamp",
d.i_w == 1000 and d.target_w == 1000) d.i_w == 1000 and d.target_w == 1000)
# Freeze = may not wind further in the direction it is already pushing. # Freeze = may not wind further in the direction it is already pushing.
TF = Tuning(saturation_w=500, saturation_cycles=3) # ⚠️ max_w is raised WELL above the fixtures on purpose. At the default 2000
# the integrator bound truncates a wound value back to exactly 2000 and
# satisfies these assertions on its own, so deleting the freeze outright
# failed nothing - the clamp was standing in for the mechanism under test.
# Any fixture here must sit clear of every rail, or it tests the rail.
TF = Tuning(saturation_w=500, saturation_cycles=3, max_w=5000)
f1 = compute(prev_w=2000, grid_w=800, actual_w=0, tuning=TF, sat_count=3, i_w=2000.0) f1 = compute(prev_w=2000, grid_w=800, actual_w=0, tuning=TF, sat_count=3, i_w=2000.0)
check("frozen: integration does not wind further", f1.i_w == 2000.0 and f1.frozen) check("frozen: integration does not wind further", f1.i_w == 2000.0 and f1.frozen)
f2 = compute(prev_w=2000, grid_w=-800, actual_w=0, tuning=TF, sat_count=3, i_w=2000.0) f2 = compute(prev_w=2000, grid_w=-800, actual_w=0, tuning=TF, sat_count=3, i_w=2000.0)