- Rust 80.4%
- Nix 8.1%
- Python 6.6%
- Objective-C 2.5%
- OpenSCAD 1.7%
- Other 0.7%
|
All checks were successful
/ check (push) Successful in 8s
The model crop decides where a person can be seen at all, and until now nothing outside inference could know it. Coordinates are folded back out to full-frame, which made the crop look like a private implementation detail — and it is, for interpreting a position. It is not private for deciding where a consumer may place something a player has to reach. deck-slice sized its play area as a fraction of the FRAME while this daemon detected in half of it, so the band between the two was reachable in the game and invisible here. The settings screen's own buttons sat in that band and could not be pressed. Every message type matched, every test passed, and neither repository could have caught it alone: each number was defensible where it was written. So `detect` goes on every pose message, and doc/protocol.md §3 states the invariant it exists for: a consumer's play region must be DERIVED from it, never sized independently. The value published is the letterbox's SANITISED crop rather than `cfg.model.crop` — a bound taken from a config value that was silently corrected would not hold, which is the one case where it matters. Per-frame rather than sent once, because an adaptive crop would vary it and there is no other channel a consumer is guaranteed to keep receiving. Tested: `the_detect_fraction_matches_the_reach_of_the_model_view` ties the reported number to the geometry through `undo` rather than to the config it came from, so a field that merely echoed the setting fails it; both new decode tests were confirmed to fail against a deliberately wrong value. `every_pose_line_carries_the_detected_region` covers the empty-players case, since a frame with nobody in it still bounds where somebody could be. Additive, so no consumer breaks: an old one sees a field it ignores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CtnYWFX9eMBH7oK3gmcMMs |
||
|---|---|---|
| .forgejo/workflows | ||
| doc | ||
| issues/open | ||
| LICENSES | ||
| macos | ||
| nix | ||
| optional-hardware | ||
| src | ||
| tools | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| NOTICE | ||
| README.md | ||
deck-pose
Markerless full-body pose input: a camera-only body-tracking daemon that publishes a pose stream and discrete gesture events over Unix sockets, so a game can use a body as a controller. Modelled on the Nex Playground.
v4l2 capture (newest-frame-only) → MoveNet MultiPose Lightning via ONNX Runtime → IoU + Hungarian player tracking → One Euro smoothing → gesture recognisers → NDJSON on a socket, plus an optional second socket forwarding the camera's raw MJPEG for a video background.
Licence
GPL-3.0-or-later — see LICENSE.
Dependencies keep their own terms: ONNX Runtime is MIT, the ort bindings and
most of the Rust tree are Apache-2.0/MIT, and the MoveNet weights are
Apache-2.0. GPLv3 rather than GPLv2 is a requirement rather than a preference —
Apache-2.0 is compatible with the former and not the latter, and the weights
and ort are both Apache-2.0.
The consumer's THIRD-PARTY.md carries the full audit, including the
provenance of the model re-export.
Read this first
doc/protocol.md— the wire protocol. This is the API this repo owns and the contract consumers depend on. Consumers live in other repositories now, so a protocol change is not visible in one diff.doc/deck-pose.md— running it, the latency budget, the model rationale, and measurements from real cameras.doc/cross-platform.md— this is Linux only, and what it would take not to be. Read before promising a Windows or macOS build: capture and the publish socket are both platform-bound, and the cost is weeks rather than a weekend.doc/macos-port.md— the macOS port, measured on real hardware and deliberately deferred. Self-contained: build blockers, the capture contract a backend must implement, and the gotchas (CoreML is slower; camera permission fails silently).issues/open/— known open defects. Notably, the daemon still emits phantomswipe/punch/jumpevents on tracking discontinuities.
Known consumer: deck-slice, a Fruit-Ninja-style slice game.
Using it
nix run git+https://git.lyte.dev/lytedev/deck-pose -- --help
As a flake input, with the NixOS module:
{
inputs.deck-pose.url = "git+https://git.lyte.dev/lytedev/deck-pose";
# in your host's modules:
imports = [ inputs.deck-pose.nixosModules.default ];
lyte.deck-pose = {
enable = true;
users = [ "you" ]; # camera access; granting it is a decision
socket = "%t/deck-pose.sock"; # %t, never a literal /run/user/1000
video = {
enable = true;
socket = "%t/deck-pose-video.sock";
};
service.enable = true; # socket-activated; exits when idle
};
}
The module pins the package itself, so importing it works without also applying
overlays.default. Apply the overlay instead (or as well) if you want
pkgs.deck-pose available generally.
Outputs
| output | what |
|---|---|
packages.<system>.deck-pose |
the daemon (also .default) |
overlays.default |
adds pkgs.deck-pose |
nixosModules.default |
the lyte.deck-pose module (also .deck-pose) |
checks.<system> |
package (build + cargo test), clippy, fmt |
devShells.<system>.default |
rust toolchain, ORT_DYLIB_PATH preset, v4l-utils |
Building and testing
nix build
nix flake check
The unit tests are pure arithmetic over the filter, tracker, config and
publisher layers — no camera, no model file, no network, no timezone database —
so they run inside the nix sandbox. Keep them that way: cargo test runs
inside the build derivation, so a test that needs any of those does not fail
loudly, it makes the package unbuildable.