feat(deck-slice): Fruit-Ninja-style slice game driven by deck-pose (phase 3) #908
Closed
lytedev
wants to merge 1 commit from
deck-slice into deck-pose-events
pull from: deck-slice
merge into: lytedev:deck-pose-events
lytedev:main
lytedev:propose/module-directory-scanner
lytedev:propose/special-args-cleanup
lytedev:propose/limine-fallback
lytedev:propose/household-kiosk
lytedev:propose/xwayland-ei-display-from-client
lytedev:propose/steamdeck-mouse-steps-aside-for-games
lytedev:propose/assistant-watch
lytedev:propose/lpcamm-watch
lytedev:propose/agents-where-information-lives
lytedev:propose/tuwunel-1.9.2
lytedev:propose/pin-flake-registry
lytedev:propose/headscale-0-29-3
lytedev:propose/canary-freshness-per-repo
lytedev:propose/boot-guard-efivars-fix
lytedev:propose/deck-dock-panel-under-niri
lytedev:propose/admindevice-ssh-anything
lytedev:propose/kdeconnect-announce
lytedev:steamdeck-mgba
lytedev:propose/doorbell-no-internet
lytedev:propose/ha-list-tasks
lytedev:propose/ha-music-not-found
lytedev:propose/bc250-40cu-measured
lytedev:propose/multiboot-foxtrot-dragon
lytedev:propose/pitft-stmpe-drive
lytedev:propose/foxtrot-multiboot-prep
lytedev:propose/pitft-no-smartd
lytedev:propose/kexec-luks-reboot
lytedev:propose/foxtrot-multiboot
lytedev:propose/model-eval
lytedev:propose/foxtrot-vm-seats
lytedev:iamb-sender
lytedev:propose/calendar-alarms
lytedev:pinephone-niri
lytedev:push-tsntxvxynzps
lytedev:propose/spoof-reject-cleanups
lytedev:propose/finance-pipeline
lytedev:steamframe-plan
lytedev:pinephone-buildable
lytedev:deck-pose-wip
lytedev:aerc-open-html
lytedev:local-auth-unification
lytedev:iamb-dotfiles
lytedev:esphome-music-panel
lytedev:wifi-system-profile
lytedev:gauge-history
lytedev:validate-gate-fix2
lytedev:validate-slot-no-hugepages
lytedev:pinephone-reenable
lytedev:cyberdeck-research
lytedev:git-anon-ratelimit
lytedev:headscale-basedomain
lytedev:ssd-fast-tier
lytedev:hold-impermanence-flip
lytedev:gate-dummy-eval
lytedev:zen-browser
lytedev:initrd-remote-unlock
lytedev:beefcake-rpool-backup
lytedev:sec-sso-plan
lytedev:sec-mail
lytedev:flab-lid-wake
lytedev:hearth-k8s
lytedev:zulip
lytedev:router-guest-vlan
lytedev:headscale-extra-records
lytedev:beefcake-advertise-lan
lytedev:push-qrvuuqmxtznv
lytedev:voxtype-per-host-model
lytedev:cleanup
lytedev:push-kanidm-opt-in
lytedev:push-voxtype-flake-fix
lytedev:push-uvqqknxvqlvy
lytedev:push-qrqsxkmlrevert
lytedev:fix-kanidm-uid-attr-map
lytedev:nix-on-droid
lytedev:self-hosted-dns
lytedev:fix-runner-tmpfs-revert
lytedev:beefcake-email
lytedev:beefcake-immich
lytedev:firefox-nightly-devedition
lytedev:fix-bun-baseline
lytedev:flab-stuff
lytedev:printer-sftp
lytedev:dragon-vr-monado
lytedev:clever-vr-game-clamshell-mode-detection
lytedev:wireguard-mesh
lytedev:rescue-beefcake-unpushed-nix
No reviewers
Labels
No labels
bug
enhancement
question
scary
upstream
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
lytedev/nix!908
Loading…
Reference in a new issue
No description provided.
Delete branch "deck-slice"
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?
The first real consumer of the pose daemon's socket — and as much a test that the protocol is usable as it is a game.
Stacked on #907, which is stacked on #906. Base is
deck-pose-events. Merge order: #906 → #907 → this.Packaged as
pkgs.deck-slice, not enabled on any host, nothing deployed.What it is
Targets are flung up from the bottom; you slice them by swiping a wrist through them. Up to six players, each with their own blade colour and score. Bombs cost 10 points and are distinguished by shape as well as colour, so a colour-blind player is not the one who finds out by losing. Six GDScript files, no binary assets — it is all reviewable in the diff.
No daemon changes were needed
Godot 4.6 ships
StreamPeerUDS, so the game reads the Unix socket directly. I checked before assuming: I had been ready to add a TCP transport to #907 for this, and it turned out to be unnecessary. Verified against the actual nixpkgs binary (ClassDB.class_exists("StreamPeerUDS") == trueon 4.6.3).One gotcha that cost real time and now carries a comment:
FileAccess.file_existsreturnsfalsefor a Unix socket — it is not a regular file — so the "is the daemon up yet" check goes throughDirAccessinstead. Without that the game silently never connects.Two decisions that shape how it feels
Slicing is driven by the pose stream, not by
swipeevents. A swipe event is one discrete notification per motion — it has a refractory period by design. But one swing can pass through three targets and the player expects all three to fall. So every blade's swept segment is tested against every target every frame. Swipe events drive only the "you swung" feedback, which should fire even on a miss.Hits test the segment the hand travelled, not where it is now. At 60fps a fast swipe moves a hand hundreds of pixels between frames; a point-in-circle test tunnels straight through and the player would swear they hit it.
The view is mirrored. Not cosmetic — without it the game is unplayable in a way people struggle to articulate.
Defensive about the daemon
Polls for the socket, connects whenever it appears, survives a daemon restart, tolerates partial lines and malformed JSON, and caps its read buffer. While disconnected it says so on screen and prints the command to start the daemon — a pose game with no daemon looks identical to one that cannot see you, and "nothing is moving" is not a diagnostic.
Packaging
Project directory plus a wrapper running
godot4 --path, not an exported binary: export would pull ingodotPackages.export-templates-bin(hundreds of MB of prebuilt engine binaries) to produce a.pckof the same GDScript already in the tree.The build runs
godot4 --headless --importtwice, and that is load-bearing rather than cargo-culted — Godot writes its import cache into.godot/inside the project, and a/nix/storepath is read-only at runtime. I verified that a read-only project fails without it. Two passes because the first registers the scripts and the second resolves the scene's references to them.Verified
nix build .#deck-slicesucceeds, and the packaged binary was run end to end against a synthetic pose stream (a wrist swept across the frame at a known speed, emitting the real NDJSON):So: connects, parses, tracks blades, slices, applies the bomb penalty, and handles the daemon vanishing.
Untested — please read this part
/dev/video0for the whole session and I was not going to kill it. The daemon side is validated live (#906/#907); the join between daemon and game has only been exercised against the synthetic stream.Gamescope
Not fought, as instructed — but written down rather than waved at.
lib/doc/deck-slice.mdhas a section listing what Game Mode will actually need: the daemon as a user service started before the session, camera access inside that session, whether a Steam-launched game shares$XDG_RUNTIME_DIRwith a user service, adding it to Steam as a non-Steam game, and how the daemon and game split four cores.76b274b40f5b0b8ff52b5b0b8ff52b8232c55017Superseded 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