fix(deck-slice): stop fabricating hand speed from a collapsed interval #911
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "deck-slice-speed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes item 2 — and it is not just a diagnostic bug. Now covers two independent mechanisms; the second was found because your run's intermittency did not fit the first.
Stacked on #910.
The cut path shares the computation
You asked me to check. It does —
blade.speedfeeds both the diagnostic andis_cutting(), so inflated values bypass the threshold and cause accidental slices. That is the real severity.Mechanism 1 — a fabricated interval
Your body-scale hypothesis was reasonable but was not the cause; it was already floored. The culprit was the time denominator: speed was measured against the game's receive time,
Time.get_ticks_msec()is millisecond-resolution, and the client drains every buffered line in one poll — so batched poses shared one timestamp, dt collapsed to zero, and the old code clamped it to 0.1ms. A normalised movement of 0.0001 became 4 body-heights/sec. The unbounded spikes also explain the monotonic-then-pinned shape.Reproduced with a deliberately gentle wave (~1 body-height/sec) sent in batches: before 4.00 peak and one spurious slice; after 0.63 and none.
Fixed by timing against the daemon's
ts_ms(the right clock was already in the protocol) and dropping unmeasurable intervals rather than clamping.Mechanism 2 — a teleported keypoint
Correct timing does not help when the position jumps. A third of the frame in one entirely honest 16.7ms interval computes ~79–94 body-heights/sec — nearly 120x the cut threshold. Causes: MoveNet swapping left/right wrists as arms cross, losing and reacquiring joints through occlusion, and the tracker handing an existing player id to a different body when two people pass.
This is the better fit for your intermittency. A steady timing bug would be steady; tracking discontinuities are sporadic by construction.
Demonstrated, against a stream whose wrist jumps a third of the frame twice a second:
And the batched-message reproduction is unchanged at 0.62 with zero rejections, so the guard costs nothing it should not.
Samples above 30 body-heights/sec are rejected. Measured reality is 7–14, so the ceiling is over twice the fastest motion ever observed. Rejected, not clamped — clamping to the ceiling would still be ~37x the threshold and would still slice. Position is kept and re-anchored so the next frame does not spike off the same jump. Rejections are counted into the debug readout, since this class of fault was hard to find precisely because it left no trace.
Body scale was hardened too, as you suggested: full 2D torso length rather than the vertical span alone (which collapses when a player leans while the torso is plainly still there), floored at 0.10.
Should this be fixed upstream too? Yes — and one part is a live daemon bug
You asked me to record the judgement, not act on it.
The game-side filter should stay regardless. A consumer that trusts a stream absolutely is fragile, the game owns its own threshold semantics, and defence in depth is right at a process boundary.
But two things belong in the daemon:
Player identity is squarely the daemon's job.
PlayerIdis documented as stable across occlusion and never recycled — a game should not have to defend against that guarantee being violated. Bbox IoU alone cannot disambiguate two people crossing, which is exactly when it breaks. The fix is adding keypoint similarity (OKS) to the assignment cost intrack.rs, not just box overlap.The daemon has the same exposure in its own recognisers, and it is currently live.
recognise.rs:325computes(dx*dx+dy*dy).sqrt() / dt / scale— the identical formula — and compares it againstswipe_speed1.5 andpunch_speed2.0. A teleport at 79–94 blows past both, so the daemon is emitting phantom swipe and punch events to every consumer on tracking discontinuities today. That is not hypothetical and it is not fixed by anything in this PR; it needs the same plausibility rejection applied daemon-side, where it would protect every future game instead of each one reinventing the guard.A per-keypoint plausibility guard in the daemon would cover both, and the daemon already computes body scale so it has everything it needs. Happy to take that as a follow-up whenever you want it.
Item 3 — window geometry
Not a game bug.
project.godotsetsstretch/aspect="expand", which keeps the base width and expands the other axis to the window's aspect. Under a tiling compositor the game never gets the 1280x720 it requests — niri sizes it to the column — so 1280x4551 implies roughly 1:3.6. The letterbox coping is the system working as intended. Removing the oddity would mean going fullscreen, which is a behaviour change beyond a bug fix; say the word.Checks
nix build .#deck-slicegreen (includes the framing check phase). Both reproductions verified from source. No daemon started and the camera untouched — synthetic streams only.Untested
Superseded by #915, which consolidates the whole markerless-pose-input program into a single WIP branch against
main. Every commit from this PR is preserved there — nothing was squashed, so the root-cause writeups in the commit messages are intact. Closing here; review happens on #915.Pull request closed