test(hud): stop measuring the builder CPU jitter (ENG-11404) - #1085
Merged
Conversation
andrewgazelka
enabled auto-merge
July 30, 2026 03:07
`smash-hud-e2e` failed in CI with one assertion:
FAIL a new CPU reading that moves only the label costs one packet
carrying only the label: ['add']
`['add']` was the entire packet history for that bar: the Add, then twenty
seconds of nothing. Nothing sent a wrong action; nothing was sent at all, and
the message could not tell those two apart. That ambiguity is most of why this
took a night to find, so the replacement distinguishes them in its own text --
the same break now reads `0 of 0 retitle(s)`.
The mechanism is the inverse of the obvious one. The builder was not too busy,
it was too quiet and too large. The CPU label is a whole percent of ONE core
(`server_load.rs`, `percent()` rounds `cores * 100.0` into a `u32`), the game
server idles at about 0.01 cores, and vc1-nix has 128 of them. So the label sat
at `CPU 1% of 12800%` and consecutive one-second windows rounded to the same
integer. `operation()` correctly sent nothing, and the check timed out waiting
for a packet that was right not to exist. It moved 0.8 s after the deadline.
Looking for contention would have found nothing and read as the hypothesis
being wrong.
There were two of these, not one. `load_check`'s disconnect liveness claim
waited on the same `count("update_name") >= 2` and had been passing on the same
jitter; removing the first assertion shifted the phase and turned the second
red on the next run. Fixing only the reported one would have left a landmine
that fires after the fix lands.
What replaces them:
* The "an update carries only the update" claim moves to the lobby countdown
bar, whose label moves on the server's own tick clock (`whole_seconds` is a
`ceil`) rather than the host's load.
* The liveness claim causes the change it observes: the surviving viewer
toggles `/serverload` and the Remove then Add have to come back.
* The CPU and memory packet counts are logged, not asserted, for the reason the
memory bar already carried.
* `egress::boss_bar` gains a truth-table test over `operation()`, all 16
subsets of the four fields. This is a test-only addition; no runtime code
changes.
On whether it will stay green, the run count is the weak argument. Six green
runs would say little. The margin is the claim worth making: the assertion
needs 3 label-only retitles out of the ~9 second-flips in a countdown, and
measured 7, 7, 7, 7, 7, 8, 8 across seven runs. It is nowhere near its
threshold, which is what predicts tomorrow. Do not tighten the threshold
without re-measuring that spread.
Residual, stated rather than hidden: the countdown timer advances by wall-clock
`dt`, so a server crawling below roughly 7 tps for a whole countdown would
collide every second-flip with a fill step and turn them all into Adds. Several
other assertions here fail first at that tick rate, but it is not zero.
Not redundant with `diff_check`, and this was measured rather than argued.
Making `operation()` return `None` for a title-only change freezes every
countdown on every screen; `diff_check` passes all four rules over 780 packets
(0 empty, 0 wasteful, 0 orphaned) and this check is the only one that goes red.
`diff_check`'s rules are all "no packet did X", so they can only judge packets
that exist and a missing packet is invisible to them. The volume guard does not
help either: progress updates dominate the count. The two are opposites, and
each is blind exactly where the other looks. The reasoning is written next to
the assertion so it is not resolved the wrong way later.
andrewgazelka
force-pushed
the
fix/hud-scoreboard-packet
branch
from
July 30, 2026 03:10
4876485 to
ab2507a
Compare
Benchmark Results for generalComparing to ec8c9fd |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the enforced
smash-hud-e2efailure. ENG-11404.What was failing
['add']was the entire packet history for that bar: the Add, then twenty seconds of nothing. Nothing sent a wrong action; nothing was sent at all, and the message could not tell those two apart. The replacement distinguishes them in its own text: the same break now reads0 of 0 retitle(s).The mechanism, which is the inverse of the obvious one
The builder was not too busy. It was too quiet and too large.
The CPU label is a whole percent of ONE core (
crates/hyperion/src/egress/server_load.rs,percent()roundscores * 100.0into au32). The game server idles at about 0.01 cores andvc1-nixhas 128 of them, so the label sat atCPU 1% of 12800%and consecutive one-second windows rounded to the same integer.operation()correctly sent nothing, and the check timed out waiting for a packet that was right not to exist:It moved 0.8 s after the deadline. Hunting for contention would have found nothing and read as the hypothesis being wrong.
The defect was in the check, not the game.
There were two coin flips, not one
load_check's disconnect liveness claim waited on the samecount("update_name") >= 2and had been passing on the same jitter. Removing the first assertion shifted the phase and turned the second red on the very next run:Fixing only the reported one would have left a landmine that fires after the fix lands.
What replaces them
whole_secondsis aceil) rather than the host's load./serverload, and the Remove then Add have to come back.egress::boss_bargains a truth-table test overoperation(), all 16 subsets of the four fields.Why it should stay green: the margin, not the run count
Six green runs would say little. The assertion needs 3 label-only retitles out of the ~9 second-flips in a countdown, and measured 7, 7, 7, 7, 7, 8, 8 across seven runs. It is nowhere near its threshold, which is the thing that predicts tomorrow. Do not tighten the threshold without re-measuring that spread.
Residual: the countdown timer advances by wall-clock
dt, so a server crawling below roughly 7 tps for a whole countdown would collide every second-flip with a fill step and turn them all into Adds. Several other assertions here fail first at that tick rate, but it is not zero.Negative controls
Breaking
operation()'s title-only branch to send a whole-bar Add turns it red for the right reason:Two controls aimed at the disconnect assertion both failed to reach it. The first broke
/serverload's re-add and killed the gate at the first/serverload, before the assertion ran. The second used a realistic bug shape (add_if_newmatching(Sent, Wildcard)instead of(Sent, viewer)); two neighbouring checks went red and this one passed, because the admin already held the bars.So that assertion is deterministic and green but its incremental value over the three earlier toggle checks is unproven, and it is labelled
STATUS: UNPROVENin the source next to itself, with both failed attempts written out and a description of the defect that would actually distinguish it (one needing a stale(Sent, dead_viewer)pair, since this is the only bar check that runs after a socket has died). Anyone later deciding whether to delete it as redundant gets the evidence rather than a guess.Honest tally: 6 green runs of the fixed gate, 1 red baseline, 3 induced reds from deliberate breaks, and 1 control that would not fire.
Not redundant with
diff_check, and this was measuredMaking
operation()returnNonefor a title-only change freezes every countdown on every screen:diff_checkpasses all four rules; this check is the only one in the suite that goes red. Its rules are all "no packet did X", so they can only judge packets that exist, and the volume guard does not help because progress updates dominate the count. The two claims are opposites and each is blind exactly where the other looks. That reasoning is written next to the assertion so it is not resolved the wrong way later.bedwars / shared engine
boss_bar.rsis engine code every mode shares, so worth stating: a bedwars client sees nothing different, because no runtime code changed. The whole Rust diff is one insertion hunk,@@ -490,0 +491,83 @@ mod tests, and#[cfg(test)]starts at line 410.operation()is untouched.Who reads the path:
crates/hyperion/src/egress/mod.rs:50-51importsBossBarModuleandServerLoadModuleunconditionally, so every event including bedwars gets the drive system and the/serverloadbars (an engine command,crates/hyperion-clap/src/lib.rs:621).events/smash/src/adapter.rs:520-578is the only game-mode consumer.events/bedwars/src/has no boss bar reference at all.Caveat: because bedwars draws no bars of its own, no bedwars gate exercises this path, and the new wire-level coverage is smash-only. The truth-table unit test is the mode-independent part.
Verification
nix build .#checks.x86_64-linux.smash-hud-e2e -Lonvc1-nix: 50 PASS, 0 FAIL,rc=0. Seven green runs total against a 1-for-1 red baseline.cargo test -p hyperion --lib egress::boss_bar: 5 passed. Watched the new test fail under the induced break (left: "add" right: "update_name").cargo fmt --check -p hyperionandcargo clippy -p hyperion --lib --tests: clean.Unrelated, filed separately
checks.*.{fmt,lint,test,ci,deny}arewriteShellApplicationderivations that build the script rather than run it, sonix flake checkcannot fail on them. This briefly made me report a clean lint that was not.events/smash/src/module/visuals.rs:418and:525, owned by another change in flight.