This models Android's real Doze idle-maintenance resource governor. While the screen is off and the device sits still, background apps cannot run whenever they like — every wake-lock request is queued and only released during short maintenance windows that get progressively rarer:
window_gap(n) = min(base * 2^n, cap)
n = number of completed maintenance windows since Doze engaged
cap = 8 * base (this simulator's ceiling)
So the first gap after Doze engages is base seconds, then 2×base, 4×base, 8×base, capped — exactly the exponential back-off Android uses to push devices toward deeper sleep the longer they stay idle. Each app also independently generates wake-lock requests at a Poisson-like rate set by "wake demand": the average wait between two requests from one app is 60 / demand seconds.
With Doze on, a request that arrives outside a window is queued (amber) and only executes (teal) once the next window opens, so many apps' jobs get batched into one short burst of shared CPU/radio activity. With Doze off, every request executes the instant it arrives, so the device's radio/CPU keep waking independently — many more short wake periods overlapping in time.
drain(t) = P_idle + P_active * (# apps executing at t)
battery -= drain(t) * dt / 60 [%/min integrated over sim time]
Because overlapping independent wake-ups draw P_active far more often than batched ones, disabling Doze roughly doubles to triples the steady-state drain rate for the same workload — the same trade-off real Android exposes as `dumpsys deviceidle` maintenance windows and the wake-lock budget API.