Files
agent-tools/hitbox-tool
claude f8924d89f7 hitbox-tool: per-frame collision boxes from sprite sheet alpha -> JSON
- parses the spritec _WxH_CxR naming convention incl. auto-detection of
  integer-upscaled sheets (256x64 named 8x8_4x1 -> 64x64 cells)
- tight alpha bbox per frame with threshold/shrink/pad tuning, row-major
  indices, empty-frame flags; boxes relative to frame origin
- show command draws frames + box outline in the terminal for verification
- go tests: scanning, threshold, shrink/pad clamping, name parsing,
  scale inference, JSON roundtrip

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmdG9GqfSWCzts7AkDwRDh
2026-07-14 07:12:05 +02:00
..

hitbox-tool (hitbox)

Scans sprite sheet PNGs and writes per-frame collision boxes as JSON, computed from the alpha channel. Closes the loop with pixel-sprite-maker: render a sheet with spritec, scan it with hitbox, and your game gets both graphics and hitboxes without a human drawing rectangles. Go, zero dependencies, single static binary.

Build

# Arch/Garuda: sudo pacman -S go
cd hitbox-tool
go build -o build/hitbox .        # or the VS Code task "build hitbox-tool"
go test ./...

Usage

# spritec-named sheets need no flags — layout is parsed from the name,
# and integer upscales are auto-detected (a _8x8_4x1 sheet that is
# 256x64 px was rendered at --scale 8, so cells are 64x64):
hitbox scan walk_8x8_4x1.png -o walk.hitbox.json

hitbox scan boss.png --cell 32x32 -o boss.json   # explicit frame size
hitbox scan portrait.png                          # whole image = one frame, JSON to stdout

hitbox show walk_8x8_4x1.png --frame 2            # verify visually in the terminal

# tuning:
--threshold 128     # ignore faint pixels (alpha < 128)
--shrink 1          # tighter, more forgiving hitboxes (n px per side)
--pad 2             # generous hitboxes (e.g. pickups)

Output

{
  "image": "walk_8x8_4x1.png",
  "cellW": 8, "cellH": 8, "cols": 4, "rows": 1,
  "alphaThreshold": 1,
  "frames": [
    { "index": 0, "col": 0, "row": 0, "empty": false,
      "box": { "x": 1, "y": 0, "w": 5, "h": 8 } },
    { "index": 1, "col": 1, "row": 0, "empty": true, "box": null }
  ]
}
  • Boxes are relative to each frame's top-left corner.
  • Frame order is row-major (index = row * cols + col), matching spritec sheets.
  • Cells with no solid pixels get "empty": true.

In game code:

hit = px >= frameX + box.x && px < frameX + box.x + box.w
   && py >= frameY + box.y && py < frameY + box.y + box.h

hitbox show draws each frame with # for solid pixels and + for the box outline, so an agent can verify the result without an image viewer.