A game where you slice fruit (and not bombs!) with your ninja-knife-hands.
  • GDScript 87.6%
  • Nix 9.2%
  • Shell 3.2%
Find a file
Daniel Flanagan 15ada03b04
All checks were successful
/ check (push) Successful in 12s
docs: answer the recorded objection to an apply step, in place
`_on_setting_changed` carries a decision AGAINST a confirmation step —
"a dwell UI is slow enough already" — and the play-area guard is exactly
such a step. Left alone, the two read as a contradiction, and the reader
who finds the comment first has no way to tell which one is current.

The objection is about cost, and it is right: confirming every setting
doubles the length of every adjustment and buys nothing for values that
are one press from being changed back. What it does not cover is the one
class where a bad value removes the ability to undo itself, which is the
mapping. The asymmetry is not that the mapping matters more — it is that
every other setting leaves the way back intact.

So both comments now say so, from their own side. No behaviour changes:
the guard was already scoped to `play_reach` and already ran on close
rather than per press. This records WHY that scope is the whole
argument rather than an implementation detail somebody may later widen
"for consistency".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtnYWFX9eMBH7oK3gmcMMs
2026-08-10 13:36:30 -05:00
.forgejo/workflows chore: stand up deck-slice as its own repository 2026-08-04 15:54:21 -05:00
doc fix: make the play area a fraction of what can be DETECTED 2026-08-10 12:47:08 -05:00
issues feat: say "I can see you but not your hands", and stop colours drifting 2026-08-07 11:36:26 -05:00
nix feat: the corner walk, as a testable state machine 2026-08-10 13:33:28 -05:00
project docs: answer the recorded objection to an apply step, in place 2026-08-10 13:36:30 -05:00
steam feat(steam): prepare publishing — scripts, depot, checklist. Upload nothing. 2026-08-07 10:32:48 -05:00
.gitignore build: consume the branding addon from godot-branding, not a vendored copy 2026-08-05 20:28:36 -05:00
flake.lock docs: jpeg-encoder's IJG term is an obligation, not an alternative 2026-08-05 20:53:38 -05:00
flake.nix docs: there is no submodule route, and the docs said there was 2026-08-05 23:54:50 -05:00
LICENSE chore: license under GPL-3.0-or-later, and plan the release 2026-08-05 14:20:44 -05:00
README.md feat: the words match the game — punching, gloves, no slicing 2026-08-06 16:48:45 -05:00
THIRD-PARTY.md docs: jpeg-encoder's IJG term is an obligation, not an alternative 2026-08-05 20:53:38 -05:00

deck-slice — Fruit Punch Party

The repository and package are deck-slice; the game is called Fruit Punch Party. The product name lives in one constant (TITLE in project/title_screen.gd) and the repo name is a separate, coordinated change because it is a flake input URL elsewhere.

A game where your hands are boxing gloves. Fruit is flung up from the bottom of the screen; you punch it, and it bursts in the direction your fist was going. Up to six players at once, each with their own colour and score. Godot 4, GDScript, CC0 art.

2.5D: lit, tumbling 3D fruit that burst into splatter when hit, over a game whose logic is strictly 2D. An orthographic camera maps one world unit to one screen pixel, so the letterbox mapping that gameplay depends on is untouched. --flat reverts to the original drawn shapes and is kept working.

Anti-aliasing, glow and ambient occlusion are settings on a Graphics page, each with a measured frame-time cost and pixel-diff in doc/deck-slice.md. Shadows are deliberately absent rather than off-by-default: nothing in the scene could receive one.

It opens on a title screen that says whether the camera is working, and on a first run explains the gestures nobody can guess before letting you press Play.

It opens on a title screen that says whether the camera is working, and on a first run explains the gestures nobody can guess before letting you press Play.

It is the first real consumer of deck-pose, the markerless body-tracking daemon, and exists as much to prove that daemon's socket protocol is usable as to be a game.

Licence

GPL-3.0-or-later — see LICENSE.

Bundled art and audio are Kenney's, under CC0; the Godot engine is MIT; the model weights and several dependencies are Apache-2.0. Those keep their own terms, and a binary distribution must carry THIRD-PARTY.md alongside LICENSE.

GPLv3 rather than GPLv2 is a requirement rather than a preference: Apache-2.0 is compatible with the former and not the latter, and both the model weights and much of the Rust dependency tree are Apache-2.0.

Read this first

  • doc/deck-slice.md — how to run it, every tunable flag, the framing model, and what the tests do and deliberately do not cover.
  • deck-pose's doc/protocol.md — the wire format this game consumes. Read it before touching project/pose_client.gd or project/video_client.gd.

Running it

Two processes. The daemon owns the camera; the game reads its socket. Order does not matter — the game polls for the socket and reconnects on its own.

nix run .#deck-pose -- --headless   # terminal 1
nix run .#deck-slice                # terminal 2

Both default to $XDG_RUNTIME_DIR/deck-pose.sock, so the common case needs no arguments. On the steamdeck host the daemon is socket-activated by the NixOS module and deck-slice-launch starts the socket for you.

Settings, without a terminal

Every tunable is reachable with your hands: hold a hand in the top-left corner box for ~1.5s (or press Esc) to open the settings screen, then hold over a button until the ring around your hand fills. Bring both hands together to grab the highlighted row and slide it — steppers for nudging, clap-to-grab for sweeping.

Controller, keyboard and mouse are first-class too, not a camera-absent fallback: d-pad/arrows to move, right stick to slide, A/Enter to press, B/Esc to close, mouse wheel to adjust.

Precedence is default < environment < flags < saved settings — the in-game screen wins, because a value you changed with your hands should stick. --reset-settings is the way back if a saved value makes the game unplayable. See doc/deck-slice.md.

Outputs

output what
packages.<system>.deck-slice the game + deck-slice-launch + a .desktop entry (also .default)
packages.<system>.deck-pose re-exported from the input, so nix run .#deck-pose works
overlays.default adds pkgs.deck-slice
checks.<system> package (build + seven test suites), protocol-drift

The protocol-drift check

checks.protocol-drift is why deck-pose is a flake input rather than a link in a document. It reads the message types and event kinds the pinned daemon can emit, straight out of its source, and fails if the game names none of them.

Before the split, a daemon change that added or renamed an event showed up in the same diff as the game consuming it. It no longer does, and the failure mode is silent: the game's match stops matching and the feature quietly does nothing. Nothing errors, nothing logs.

Naming a kind is a low bar on purpose — it proves someone looked at it, not that the handling is right. project/main.gd lists the kinds this game ignores and why.

Building and testing

nix build
nix flake check

The framing, view-mapping, blade-geometry, dwell-timing, clap-gesture, settings-rules and settings-screen tests run inside the build (pure arithmetic — no camera, no socket, no clock). project/tests/render_test.gd reads back real pixels and is deliberately not in the build: Godot's headless renderer produces none, so it needs Xvfb and software Mesa. nix develop provides them; see the header of that file.