Summary
The counterfactual counter-regulation term in ClosedLoopSimulator.swift was difference-based:
crSub = (counterreg(counter_BG) − counterreg(real_BG))·dt.
When a counterfactual ran high above a real low — precisely the less-aggressive / lows-reducing arm (e.g. low insulin-needs, or any hypo-prevention candidate) — it shed the full real-low defense rateA, driving the arm implausibly below the real substrate.
Why it matters (methodological)
Systematic downward bias against lows-reducing candidates: the sim manufactured lows for any arm running high over a real low. This inflated the low-insulin-needs reference baseline and ate candidate lift — undermining exactly the frontier comparisons we care about (lows-prevention). Compounded by compression lows: a fake real low → fake rateA → spurious crash.
Evidence
Per-step decomposition of a needs-0.6 arm (aliased dataset user2, 2026-05-24 03:53):
- patient ICE (NIE) flat +5 mg/dL/5min
- insulin term (m·candPhys) flat ~−8
- counter-reg-diff term: 0 → −8 → −16 → −24 → −28 — matches the total ΔBG exactly.
The arm crashed 117 → 30 mg/dL (below the real substrate at 39), with IOB negative — neither insulin nor physiology.
Root cause
realBGdelta only carries a real-low defense when the real trace is actually low; subtracting the full modeled counter-reg rate from a high-running arm is wrong. The counter-reg magnitude (up to 8 mg/dL·min⁻¹) differenced at full strength is also very strong.
History
Counter-reg was originally additive (1cba461: a positive defense when the sim goes low). Commit 4826d3c changed it to differencing to preserve identity / avoid double-counting (real counter-reg is already in the ICE/realBGdelta). That change introduced the shedding artifact.
Fix (876a08a)
Gate the whole term on belowC > 0 — apply only when the counterfactual itself is below onset. Preserves identity (0 when counter==real) and the extra-defense-when-counter-lower case; removes the high-side crash. Cost: a high counter keeps a small inherited real-low lift (minor).
Impact: needs-0.60 t<54 0.569 → 0.051; needs-1.0 also shifts (t<54 1.18→1.09).
Follow-ups
🤖 Generated with Claude Code
Summary
The counterfactual counter-regulation term in
ClosedLoopSimulator.swiftwas difference-based:crSub = (counterreg(counter_BG) − counterreg(real_BG))·dt.When a counterfactual ran high above a real low — precisely the less-aggressive / lows-reducing arm (e.g. low insulin-needs, or any hypo-prevention candidate) — it shed the full real-low defense
rateA, driving the arm implausibly below the real substrate.Why it matters (methodological)
Systematic downward bias against lows-reducing candidates: the sim manufactured lows for any arm running high over a real low. This inflated the low-insulin-needs reference baseline and ate candidate lift — undermining exactly the frontier comparisons we care about (lows-prevention). Compounded by compression lows: a fake real low → fake
rateA→ spurious crash.Evidence
Per-step decomposition of a needs-0.6 arm (aliased dataset
user2, 2026-05-24 03:53):The arm crashed 117 → 30 mg/dL (below the real substrate at 39), with IOB negative — neither insulin nor physiology.
Root cause
realBGdeltaonly carries a real-low defense when the real trace is actually low; subtracting the full modeled counter-reg rate from a high-running arm is wrong. The counter-reg magnitude (up to 8 mg/dL·min⁻¹) differenced at full strength is also very strong.History
Counter-reg was originally additive (
1cba461: a positive defense when the sim goes low). Commit4826d3cchanged it to differencing to preserve identity / avoid double-counting (real counter-reg is already in the ICE/realBGdelta). That change introduced the shedding artifact.Fix (
876a08a)Gate the whole term on
belowC > 0— apply only when the counterfactual itself is below onset. Preserves identity (0 when counter==real) and the extra-defense-when-counter-lower case; removes the high-side crash. Cost: a high counter keeps a small inherited real-low lift (minor).Impact: needs-0.60 t<54 0.569 → 0.051; needs-1.0 also shifts (t<54 1.18→1.09).
Follow-ups
m(fidelity sensitivity) inference has a 30-min trailing-window lag — stays elevated ~30 min after a steep drop even as ICE turns positive. Not a bug per se, but worth documenting.🤖 Generated with Claude Code