ci / web (push) Successful in 1m2s
android / android (push) Canceled after 7m52s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 5m49s
ci / docs-site (push) Canceled after 55s
ci / bench (push) Canceled after 5s
ci / rust (push) Canceled after 5m32s
ci / rust-arm64 (push) Canceled after 2m39s
deb / build-publish (push) Canceled after 7s
deb / build-publish-host (push) Canceled after 3s
deb / build-publish-client-arm64 (push) Canceled after 1s
decky / build-publish (push) Canceled after 3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 2s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 3s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 2s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 3s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
windows-host / package (push) Successful in 19m26s
windows-host / winget-source (push) Skipped
Disconnecting the ENet peer stopped the freeze but the client then reported the session as error -1: a bare disconnect is indistinguishable from the host falling over, so Moonlight called it a connection failure. Which, from its side, was a fair reading — we never said otherwise. The protocol has a word for this. Send the TERMINATION control message with `NVST_DISCONN_SERVER_TERMINATED_CLOSED` first, then disconnect; the client maps that to a graceful termination (given it has seen a frame, true of any real session) and lands the player back in the app list. Constants verified against moonlight-common-c `ControlStream.c` rather than recalled, because every way of getting this wrong is silent — the client ignores an unparseable control message, which reads on glass as the same freeze: * the type comes from the table the CLIENT chose, so it is derived from the negotiated nonce scheme (plain 0x0100 / encrypted 0x0109) instead of guessed, * the reason is BIG-endian (the >=6-byte branch; the short branch is a little-endian u16, which is GFE's older shape). `disconnect_later` rather than `disconnect`, so the queued message actually reaches the wire instead of racing the teardown — which would land us back at -1. The session's GCM key is cached per tick because ending a session clears the launch state the key lives in, and this is precisely the message that has to be sealed after that.