The loops that fought themselves
Two reports landed together: the hands-off autopilot porpoised, bobbing the nose up and down; and the black-out you used to get in a hard turn had quietly gone missing. One was a control loop fighting its own delay; the other was a casualty of a change nobody connected to it. Plus a bit of screen real estate back — chat and the kill log fold into one stream.
A controller with delay in it wants to oscillate
The autopilot's pitch channel is a simple thing: measure how far the nose is off level, push the elevator proportionally to close the gap, and add a dab of the opposite for how fast the nose is already moving so it doesn't overshoot. On a fast desktop it settled onto the horizon and stayed there. On a phone it porpoised.
The difference is delay. The command doesn't reach the airframe the instant it's computed — controls are resampled at 30 Hz, prediction and reconciliation add a frame or two, and on a phone running at a lower frame rate the physics integrates several ticks off a single autopilot solve. Feed a proportional loop its own output a beat late and, if the gain is hot enough, every correction arrives just in time to make the next error worse. That's a pilot-induced oscillation, and it's exactly what a porpoise is.
I pinned it down away from the game, driving the real flight model in a little Node harness with a variable delay bolted into the control path. The old gains were rock-steady at zero delay and at one frame and at two — and then at three frames, about 50 ms, they snapped into a sustained ±9° oscillation that never damped. That 50 ms is nothing on a desktop and completely ordinary on a phone. The autopilot wasn't badly written; it was tuned for a latency the mobile flight it was built for doesn't actually run at.
The fix is the boring, correct one: soften the proportional gain and lean harder on the rate damping, trading a hair of snappiness for a loop that stays flat no matter how much lag you hand it. Retuned, the same harness holds the horizon out past 130 ms of delay across the whole fleet — a phone would have to be crawling before it wobbled.
The black-out that got optimised away
The missing black-out was a different kind of bug — nothing was broken, everything was just calibrated for a world that had moved on. The grey-out veil started creeping in at 4.5 g and only reached a full tunnel at 8. Those numbers were set back when a full yank on the stick could pull whatever the wing would give. Since then the instructor limiter landed, and a committed hard turn now sits around 5–6 g — right in the dead zone where the old veil was still basically transparent. The physiology was firing; you just couldn't see it.
Re-anchoring the band to the plane's actual envelope — grey-out from 3.5 g, closing to a near-total black-out by 6.5 — puts the tunnel back where a warbird without a G-suit belongs. Ride a hard turn now and the edges darken and draw in; hang on too long and you're flying half-blind through a pinhole. It's a soft ceiling you feel in your eyes before the G-meter's even gone amber.
One stream for talk and kills
Last, a bit of screen real estate back. Team chat lived in its own stack bottom-left while the kill log ran top-right — two feeds, two corners, twice the clutter. They're one stream now, folded into the kill log's spot. Chat still reads apart from the combat log: comms carry a soft cyan rule down the side and a 💬 tag, kills and world events stay plain. Open the composer and the recent thread resurfaces so you can see what you're replying to. One corner, both conversations.
A control loop is only as stable as the delay you forgot to test it against. Tune for the hardware people actually play on, not the one you're debugging on.