Two things, separable — the CI fix is its own commit (fd4f032d) if you want it merged ahead of the notes.
1. ci: retry bun install — fixes the red docs-site job
docs-site died on error: Fail extracting tarball for "@rolldown/binding-linux-x64-gnu". The package is fine; the message points at the wrong thing. Measured before changing anything:
The tarball's sha512 matches docs-site/bun.lock exactly, and it is an ordinary 3-entry npm tgz — same gzip framing, same modes, no pax headers — as the 1.2.0 one that installs fine. Only the payload size differs (20.6 MB vs 19.0 MB of .node).
bun 1.3.13 and 1.3.14 both extract that exact tarball from disk in under 80 ms, so it is neither the bun bump the floating oven/bun:1 tag brought in nor a format bun stopped accepting.
In the same run (19630) the web job installed the same registry over the same network and passed, 25 s ahead of docs-site.
Run 19632, seven minutes later, installed the identical lockfile and passed.
So it is a transient truncation. bun install streams download-and-extract, so a tarball cut off mid-stream fails at the extract step and names the package it was reading — which is why "bump rolldown / pin bun / refresh the lockfile" would each have appeared to work by changing which bytes were in flight, while fixing nothing.
scripts/ci/retry.sh already exists for this and its header already diagnosed the cause ("the runner box executes many jobs in parallel and its network drops packets under that load … Wrap every single-shot network command in CI with this instead"). bun install was the one class still unwrapped. Now wrapped at all nine Linux sites: ci.yml (web + docs-site), arch, deb, rpm, web-screenshots, sdk-publish, plugin-kit-publish (both). 3 attempts, not 5 — a genuinely stale lockfile fails deterministically under --frozen-lockfile, and 10s+20s is enough to ride out a burst without making that honest failure wait 90 s. The two windows-host.yml installs are left alone (pwsh, different box).
Verified: all seven workflows parse; the helper resolves from web/, docs-site/ and sdk/ (the three working-directory shapes); the wrapper recovers a command that fails once then succeeds; and the wrapped command installs all 1138 docs-site packages locally.
2. The release notes, re-measured on current main
v0.31.0 was never tagged, so this is still 0.31.0 — Cargo.toml does not move. 173 commits since v0.30.0 (115 non-merge).
The number is now forced: C ABI 24 → 25 (punktfunk_set_log_callback, addition only), so include/punktfunk_core.h is no longer byte-identical to v0.30.0 — which the first cut claimed. Also corrected: openapi stamp 0.29.0 → 0.31.0 (stamp only), and the SDK / plugin-kit rows now record cuts that happened (sdk-v0.1.5, plugin-kit-v0.4.3).
Rewritten, not appended to — both described work superseded before it ever shipped, so a reader must not see a within-cycle correction as a second bug:
the Gaming Mode takeover (it idles the autologin now and leaves the display manager running; "Switch to Desktop" used to hang until reboot), and
the Android ASurfaceControl colour/system-bar fixes, folded into the new-presenter story.
Folded in: the Windows per-user installer (the Steam overlay/Big Picture fix, and the release's one real user action — MSIX users re-pair once), the guided Linux installer (preview), send-logs completing on Apple and Android, KWin 6.6 repair, Deck Wake-on-LAN, portable Playnite covers, cancel-connect, NixOS builds failing outright, the docs overhaul, and the Virtual displays tab fix.
Gates: fmt clean, cargo test -p punktfunk-core --lib273 passed, Play notes 456/500 and unique, both openapi copies identical.
⚠ The C ABI harness still could not run here (no libopus on this machine) and this time the header did change, so ABI 25's C compile is owed to CI — named in Verification status.
⚠ Owed outside this repo: platforms.json changed (the Windows download), so punktfunk-website needs bun run sync-platforms committed.
After merge: git tag -a v0.31.0 … && git push origin v0.31.0, wait for green, dispatch announce.
Two things, separable — the CI fix is its own commit (`fd4f032d`) if you want it merged ahead of the notes.
## 1. `ci: retry bun install` — fixes the red docs-site job
docs-site died on `error: Fail extracting tarball for "@rolldown/binding-linux-x64-gnu"`. **The package is fine; the message points at the wrong thing.** Measured before changing anything:
- The tarball's sha512 **matches `docs-site/bun.lock` exactly**, and it is an ordinary 3-entry npm tgz — same gzip framing, same modes, no pax headers — as the 1.2.0 one that installs fine. Only the payload size differs (20.6 MB vs 19.0 MB of `.node`).
- **bun 1.3.13 and 1.3.14 both extract that exact tarball from disk in under 80 ms**, so it is neither the bun bump the floating `oven/bun:1` tag brought in nor a format bun stopped accepting.
- In the **same run (19630)** the `web` job installed the same registry over the same network and passed, 25 s ahead of docs-site.
- **Run 19632, seven minutes later, installed the identical lockfile and passed.**
So it is a transient truncation. `bun install` streams download-and-extract, so a tarball cut off mid-stream fails at the extract step and names the package it was reading — which is why "bump rolldown / pin bun / refresh the lockfile" would each have appeared to work by changing which bytes were in flight, while fixing nothing.
`scripts/ci/retry.sh` already exists for this and its header already diagnosed the cause ("the runner box executes many jobs in parallel and its network drops packets under that load … Wrap every single-shot network command in CI with this instead"). `bun install` was the one class still unwrapped. Now wrapped at all nine Linux sites: ci.yml (web + docs-site), arch, deb, rpm, web-screenshots, sdk-publish, plugin-kit-publish (both). **3 attempts, not 5** — a genuinely stale lockfile fails deterministically under `--frozen-lockfile`, and 10s+20s is enough to ride out a burst without making that honest failure wait 90 s. The two `windows-host.yml` installs are left alone (pwsh, different box).
Verified: all seven workflows parse; the helper resolves from `web/`, `docs-site/` and `sdk/` (the three working-directory shapes); the wrapper recovers a command that fails once then succeeds; and the wrapped command installs all 1138 docs-site packages locally.
## 2. The release notes, re-measured on current main
v0.31.0 was never tagged, so this is still 0.31.0 — `Cargo.toml` does not move. **173 commits since v0.30.0 (115 non-merge).**
**The number is now forced:** C ABI **24 → 25** (`punktfunk_set_log_callback`, addition only), so `include/punktfunk_core.h` is no longer byte-identical to v0.30.0 — which the first cut claimed. Also corrected: openapi stamp 0.29.0 → **0.31.0** (stamp only), and the SDK / plugin-kit rows now record cuts that **happened** (`sdk-v0.1.5`, `plugin-kit-v0.4.3`).
**Rewritten, not appended to** — both described work superseded before it ever shipped, so a reader must not see a within-cycle correction as a second bug:
- the **Gaming Mode takeover** (it idles the autologin now and leaves the display manager running; "Switch to Desktop" used to hang until reboot), and
- the **Android ASurfaceControl** colour/system-bar fixes, folded into the new-presenter story.
**Folded in:** the Windows per-user installer (the Steam overlay/Big Picture fix, and the release's one real user action — MSIX users re-pair once), the guided Linux installer (preview), send-logs completing on Apple and Android, KWin 6.6 repair, Deck Wake-on-LAN, portable Playnite covers, cancel-connect, NixOS builds failing outright, the docs overhaul, and the Virtual displays tab fix.
Gates: fmt clean, `cargo test -p punktfunk-core --lib` **273 passed**, Play notes **456/500** and unique, both openapi copies identical.
⚠ The C ABI harness still could not run here (no libopus on this machine) and this time the header **did** change, so ABI 25's C compile is owed to CI — named in *Verification status*.
⚠ Owed outside this repo: `platforms.json` changed (the Windows download), so punktfunk-website needs `bun run sync-platforms` committed.
After merge: `git tag -a v0.31.0 … && git push origin v0.31.0`, wait for green, dispatch `announce`.
#320 merged but v0.31.0 was never tagged, and 69 more commits (41
non-merge) landed on top of it. The version stays 0.31.0 and Cargo.toml
does not move; the notes, Play notes and CHANGELOG section are
re-measured on the new tip.
THE NUMBER IS NOW FORCED, where the first cut's was a judgement call.
The C ABI moves 24 -> 25: punktfunk_set_log_callback plus the
PunktfunkLogCb typedef (#338's core half), so an embedder can hear the
core's tracing lines. Addition only — no declaration moved, no repr(C)
struct grew a field, and an embedder that never calls it is byte-
compatible with v24. include/punktfunk_core.h is therefore NO LONGER
byte-identical to the v0.30.0 tag, which the first cut's table and both
"nothing versioned moves" paragraphs claimed.
Table re-measured on d801cb72. Two rows changed since the first cut and
two were already corrected in-tree by their own commits:
* C ABI 24 -> 25 (above).
* api/openapi.json 0.29.0 -> 0.31.0 — the STAMP only. No management
surface moved; the file had been left two versions stale and #337's
regenerate-and-diff caught it. Both copies cmp identical.
* SDK 0.1.5 and plugin-kit 0.4.3 were cut during the cycle (#348,
#334), so the rows the first cut wrote as "owed" are now past tense.
The registry still skips 0.4.2 on purpose.
Everything else holds: wire 2, driver protocol 6 / min 3, gamepad
channel 3, plugin index schema 1, host event schema 1, edition 2024,
MSRV 1.85, 27 crate dirs, gamescope +pfhdr8 with no new patches. The
PKGBUILD pfhdr7 discrepancy is unchanged and still pre-existing.
REWRITTEN RATHER THAN APPENDED TO, because the intervening work never
shipped and a user must not read a within-cycle correction as a second
bug:
* The Gaming Mode takeover section. The first cut described "stops the
login manager first, then steps in" — which #342 replaced before
shipping, after a field report that a stopped display manager leaves
the box unable to service Steam's own Switch to Desktop (it hung
until reboot, and could not even be detected around: every trace of
that switch is written by the component we had stopped). End state:
idle the autologin over the gamescope-session-plus@ template, leave
the DM running. #343 then deleted the whole 142-line stop/linger
chain the old approach needed. Two shipped facts became false with
it — the takeover no longer stops the DM and no longer needs the
punktfunk group (that group still gates the usbip pad nodes) — and
the notes' Before-you-update says so for Deck/Bazzite operators.
* The Android ASC entries. HDR seeded from the codec's echo, untagged
SDR raising blacks to grey, and the console's dispose re-showing the
system bars over the stream are all regressions against THIS cycle's
unreleased presenter, so they are folded into the Android story, not
listed as fixes users hit.
NEW USER-FACING WORK, in rough order of how much it changes:
* Windows client (#349): the default download becomes a signed
per-user Inno Setup installer + portable zip; MSIX stays for the
Store. This is the Steam fix — MSIX puts the exe under WindowsApps,
which Steam's picker cannot browse, and alias activation defeats
overlay injection. Before-you-update carries the one action in the
release: an MSIX user who wants it must Remove-AppxPackage first and
will re-pair once, because pairing identity lives with the package.
* install.sh (#345/#346): one command from nothing to a pairable Linux
host, running platforms.json's lines verbatim, with Sunshine
coexistence, --dry-run, --uninstall and env twins. Shipped PREVIEW
on purpose and the notes say so.
* Send logs to host completes on Apple (#338) and Android (#339) — the
two legs 0.30's notes named as follow-ups.
* KWin 6.6 (#344/#331): it refuses to stream an output it created
disabled, in the session's language, logged nowhere, and persists
the disable against the stable per-client name so it repeats
forever. Enable and retry; and a translated refusal no longer burns
all 8 retries.
* Deck Wake-on-LAN (#347, closes#322), portable Playnite covers
(#330), plugin-kit republish on startup/manual (#334), console touch
gestures and idle gates (#333), cancel-connect (#329), NixOS host
builds failing outright (#328 + #332 + #336).
* Docs overhaul (#337/#340/#343/#346): get-started track, Switching
from Sunshine, screenshots, and data/platforms.json as the single
source for install facts with seven CI drift gates over it.
Gates on this tree: cargo fmt --all --check clean (no drift this time —
the first cut needed a whitespace commit, which is already in main);
cargo metadata --offline ok; cargo test -p punktfunk-core --lib 273
passed; Play notes gate verbatim 456/500 and unique; voice scan clean
outside For developers; both openapi copies cmp identical at 0.31.0.
⚠ The C ABI harness still did not run here — no libopus on this MacBook
— and this time the header DID change, so ABI 25's C compile is owed to
CI. Named in Verification status rather than left to be found.
⚠ Owed outside this repo: platforms.json changed (the Windows download),
so punktfunk-website needs `bun run sync-platforms` committed — the step
#346 added to docs/releases/README.md, and this is its first exercise.
docs-site died on `error: Fail extracting tarball for
"@rolldown/binding-linux-x64-gnu"` (run 19630, 2026-08-20). The message
points at the package; the package is fine.
MEASURED, because the message invites the wrong fix:
* The tarball's sha512 matches docs-site/bun.lock exactly, and it is
an ordinary 3-entry npm tgz — same gzip framing, same modes, no pax
headers — as the 1.2.0 one that installs fine. Only the payload
differs in size (20.6 MB vs 19.0 MB of .node).
* bun 1.3.13 AND 1.3.14 both extract that exact tarball from disk in
under 80 ms. So it is not the bun bump the floating oven/bun:1 tag
brought in, and not a format bun stopped accepting.
* In the SAME run, the web job installed the same registry over the
same network and passed — it was 25 s ahead of docs-site.
* Run 19632, seven minutes later, installed the identical lockfile
and passed.
So: a transient truncation, not a bad package. bun streams
download-and-extract, so a tarball cut off mid-stream surfaces at the
extract step and names the package it was reading — which is why this
looks like `@rolldown/binding-linux-x64-gnu` is broken and why the
obvious fixes (bump rolldown, pin bun, refresh the lockfile) would all
have "worked" by changing which bytes were in flight, and none of them
would have fixed anything.
scripts/ci/retry.sh already exists for precisely this and its header
already diagnosed it: "the runner box executes many jobs in parallel and
its network drops packets under that load … Wrap every single-shot
network command in CI with this instead." `bun install` is a single-shot
network command and was the one class still unwrapped, so it is wrapped
now at all nine Linux sites — ci.yml (web, docs-site), arch, deb, rpm,
web-screenshots, sdk-publish and plugin-kit-publish (both installs).
3 attempts, not retry.sh's usual 5: a genuinely stale lockfile fails
deterministically under --frozen-lockfile, and 10s+20s of backoff is
enough to outlive a load burst without making that honest failure wait
a minute and a half.
The two windows-host.yml installs are left alone: pwsh, and a Windows
box that is not the contended runner.
Verified: all seven workflows still parse; the helper resolves from
web/, docs-site/ and sdk/ (the three working-directory shapes used);
the wrapper recovers a command that fails once and succeeds on the
retry; and `bash ../scripts/ci/retry.sh 3 bun install --frozen-lockfile
--ignore-scripts` in docs-site installs all 1138 packages, so the
lockfile is sound and the wrapper does not change the command.
Not done, deliberately: docs-site's lockfile still pins rolldown 1.1.2
where web has 1.2.0. That difference is real but it is not this bug,
and refreshing a lockfile to chase a network flake would have buried it.
170 -> 173 commits since v0.30.0 (115 non-merge, counted on this branch).
One is user-facing and is in the notes: the web console's Virtual
displays page rendered the Streamed-screen and session-lifetime cards
below the tab shell, so both tabs showed them and the Live tab read as
a duplicate of Configuration. They are policy surfaces and now sit
inside Configuration.
The other three are CHANGELOG-only: the guides trim's second round, the
fifth get-started screenshot (a client's host list), and the workflow
change that made it reachable — linux-client-screenshots now publishes
its PNGs to the generic registry as well, because the v3 artifact store
is browser-only and nothing could pull the shots out of it.
The CI bullet also records this branch's own retry.sh wrapping, since a
reader hitting 'Fail extracting tarball' deserves to find the diagnosis
rather than repeat it.
Gates re-run on this tip: fmt clean, punktfunk-core --lib 273 passed,
Play notes 456/500, both openapi copies identical at 0.31.0.
enricobuehler
changed title from 0.31.0 — re-cut the release notes on the current main to 0.31.0 release notes, re-cut on current main — and the `bun install` CI flake2026-08-20 07:37:27 +00:00
Re-measured every version-table row against the tree rather than
trusting the earlier cut: ABI 25, wire 2, driver 6/min 3, gamepad 3,
index schema 1, event schema 1, edition 2024, MSRV 1.85, 27 crate dirs,
39 workspace members, SDK 0.1.5, plugin-kit 0.4.3, openapi 0.31.0 with
both copies byte-identical, gamescope +pfhdr8 with 10 patch files and
no diff against the tag. All match what the table claims. The C header
diff against v0.30.0 removes exactly two things — the old ABI define and
the ex11 doc paragraph that stated the superseded hi-res rule — so
'addition only' holds.
Three fixes to the text itself:
* The commit count is now taken at the tip being released (170/113 at
d7fa5847) instead of at this branch, where it moved every time I
committed to it.
* The two NixOS items in Before-you-update are one item. They were
separated by three unrelated bullets, so the audience that has to
act on both — builds are failing AND there is now a cache — had to
find them in two places.
* A TL;DR line read 'it repaired itself on no retry', which says the
opposite of what it means.
Checked and left alone: the Gaming Mode 'Switch to Desktop' entry is
presented as a bug users hit, and that is right — v0.30.0's takeover
does stop the display manager on mask-fragile flavors
(gamescope.rs:259 at the tag), so it shipped. Only the mask-vs-stop
detail moved inside this cycle.
Gates: fmt clean, punktfunk-core --lib 273 passed, cargo metadata ok,
Play notes 456/500 and unique, voice scan finds no internal names in the
notes body.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two things, separable — the CI fix is its own commit (
fd4f032d) if you want it merged ahead of the notes.1.
ci: retry bun install— fixes the red docs-site jobdocs-site died on
error: Fail extracting tarball for "@rolldown/binding-linux-x64-gnu". The package is fine; the message points at the wrong thing. Measured before changing anything:docs-site/bun.lockexactly, and it is an ordinary 3-entry npm tgz — same gzip framing, same modes, no pax headers — as the 1.2.0 one that installs fine. Only the payload size differs (20.6 MB vs 19.0 MB of.node).oven/bun:1tag brought in nor a format bun stopped accepting.webjob installed the same registry over the same network and passed, 25 s ahead of docs-site.So it is a transient truncation.
bun installstreams download-and-extract, so a tarball cut off mid-stream fails at the extract step and names the package it was reading — which is why "bump rolldown / pin bun / refresh the lockfile" would each have appeared to work by changing which bytes were in flight, while fixing nothing.scripts/ci/retry.shalready exists for this and its header already diagnosed the cause ("the runner box executes many jobs in parallel and its network drops packets under that load … Wrap every single-shot network command in CI with this instead").bun installwas the one class still unwrapped. Now wrapped at all nine Linux sites: ci.yml (web + docs-site), arch, deb, rpm, web-screenshots, sdk-publish, plugin-kit-publish (both). 3 attempts, not 5 — a genuinely stale lockfile fails deterministically under--frozen-lockfile, and 10s+20s is enough to ride out a burst without making that honest failure wait 90 s. The twowindows-host.ymlinstalls are left alone (pwsh, different box).Verified: all seven workflows parse; the helper resolves from
web/,docs-site/andsdk/(the three working-directory shapes); the wrapper recovers a command that fails once then succeeds; and the wrapped command installs all 1138 docs-site packages locally.2. The release notes, re-measured on current main
v0.31.0 was never tagged, so this is still 0.31.0 —
Cargo.tomldoes not move. 173 commits since v0.30.0 (115 non-merge).The number is now forced: C ABI 24 → 25 (
punktfunk_set_log_callback, addition only), soinclude/punktfunk_core.his no longer byte-identical to v0.30.0 — which the first cut claimed. Also corrected: openapi stamp 0.29.0 → 0.31.0 (stamp only), and the SDK / plugin-kit rows now record cuts that happened (sdk-v0.1.5,plugin-kit-v0.4.3).Rewritten, not appended to — both described work superseded before it ever shipped, so a reader must not see a within-cycle correction as a second bug:
Folded in: the Windows per-user installer (the Steam overlay/Big Picture fix, and the release's one real user action — MSIX users re-pair once), the guided Linux installer (preview), send-logs completing on Apple and Android, KWin 6.6 repair, Deck Wake-on-LAN, portable Playnite covers, cancel-connect, NixOS builds failing outright, the docs overhaul, and the Virtual displays tab fix.
Gates: fmt clean,
cargo test -p punktfunk-core --lib273 passed, Play notes 456/500 and unique, both openapi copies identical.⚠ The C ABI harness still could not run here (no libopus on this machine) and this time the header did change, so ABI 25's C compile is owed to CI — named in Verification status.
⚠ Owed outside this repo:
platforms.jsonchanged (the Windows download), so punktfunk-website needsbun run sync-platformscommitted.After merge:
git tag -a v0.31.0 … && git push origin v0.31.0, wait for green, dispatchannounce.d801cb72, folding in the 41 commits since #320bun install— a truncated tarball reads as a corrupt packagedocs-site died on `error: Fail extracting tarball for "@rolldown/binding-linux-x64-gnu"` (run 19630, 2026-08-20). The message points at the package; the package is fine. MEASURED, because the message invites the wrong fix: * The tarball's sha512 matches docs-site/bun.lock exactly, and it is an ordinary 3-entry npm tgz — same gzip framing, same modes, no pax headers — as the 1.2.0 one that installs fine. Only the payload differs in size (20.6 MB vs 19.0 MB of .node). * bun 1.3.13 AND 1.3.14 both extract that exact tarball from disk in under 80 ms. So it is not the bun bump the floating oven/bun:1 tag brought in, and not a format bun stopped accepting. * In the SAME run, the web job installed the same registry over the same network and passed — it was 25 s ahead of docs-site. * Run 19632, seven minutes later, installed the identical lockfile and passed. So: a transient truncation, not a bad package. bun streams download-and-extract, so a tarball cut off mid-stream surfaces at the extract step and names the package it was reading — which is why this looks like `@rolldown/binding-linux-x64-gnu` is broken and why the obvious fixes (bump rolldown, pin bun, refresh the lockfile) would all have "worked" by changing which bytes were in flight, and none of them would have fixed anything. scripts/ci/retry.sh already exists for precisely this and its header already diagnosed it: "the runner box executes many jobs in parallel and its network drops packets under that load … Wrap every single-shot network command in CI with this instead." `bun install` is a single-shot network command and was the one class still unwrapped, so it is wrapped now at all nine Linux sites — ci.yml (web, docs-site), arch, deb, rpm, web-screenshots, sdk-publish and plugin-kit-publish (both installs). 3 attempts, not retry.sh's usual 5: a genuinely stale lockfile fails deterministically under --frozen-lockfile, and 10s+20s of backoff is enough to outlive a load burst without making that honest failure wait a minute and a half. The two windows-host.yml installs are left alone: pwsh, and a Windows box that is not the contended runner. Verified: all seven workflows still parse; the helper resolves from web/, docs-site/ and sdk/ (the three working-directory shapes used); the wrapper recovers a command that fails once and succeeds on the retry; and `bash ../scripts/ci/retry.sh 3 bun install --frozen-lockfile --ignore-scripts` in docs-site installs all 1138 packages, so the lockfile is sound and the wrapper does not change the command. Not done, deliberately: docs-site's lockfile still pins rolldown 1.1.2 where web has 1.2.0. That difference is real but it is not this bug, and refreshing a lockfile to chase a network flake would have buried it.0.31.0 — re-cut the release notes on the current mainto 0.31.0 release notes, re-cut on current main — and the `bun install` CI flakeRe-measured every version-table row against the tree rather than trusting the earlier cut: ABI 25, wire 2, driver 6/min 3, gamepad 3, index schema 1, event schema 1, edition 2024, MSRV 1.85, 27 crate dirs, 39 workspace members, SDK 0.1.5, plugin-kit 0.4.3, openapi 0.31.0 with both copies byte-identical, gamescope +pfhdr8 with 10 patch files and no diff against the tag. All match what the table claims. The C header diff against v0.30.0 removes exactly two things — the old ABI define and the ex11 doc paragraph that stated the superseded hi-res rule — so 'addition only' holds. Three fixes to the text itself: * The commit count is now taken at the tip being released (170/113 atd7fa5847) instead of at this branch, where it moved every time I committed to it. * The two NixOS items in Before-you-update are one item. They were separated by three unrelated bullets, so the audience that has to act on both — builds are failing AND there is now a cache — had to find them in two places. * A TL;DR line read 'it repaired itself on no retry', which says the opposite of what it means. Checked and left alone: the Gaming Mode 'Switch to Desktop' entry is presented as a bug users hit, and that is right — v0.30.0's takeover does stop the display manager on mask-fragile flavors (gamescope.rs:259 at the tag), so it shipped. Only the mask-vs-stop detail moved inside this cycle. Gates: fmt clean, punktfunk-core --lib 273 passed, cargo metadata ok, Play notes 456/500 and unique, voice scan finds no internal names in the notes body.