Skip to content
Pipfold

Twice in two days the game could not be operated at all

· game · tooling

Pipfold started on 24 July 2026 with a single commit: Godot 4.7, a box, nine flaps, two dice. That same evening not one flap could be tapped. The next day the game was not operable at all once you pressed “Play”.

Both bugs have the same shape, and neither is visible in the code.

Day one: the container that shows nothing and swallows everything

A full-screen MarginContainer sat over the playing field. It drew nothing — it only kept the controls away from the edge. That is exactly why nobody suspected it: it is invisible, so it isn’t there.

For input it very much is. A Control catches mouse events by default, whether it paints anything or not. Every tap meant for the flap underneath ended on an empty surface.

Fixed: the action bar sits only at the bottom instead of over everything, the flaps got thicker hit boxes, and a fallback path via screen coordinates was added for fingers.

Day two: both bars below the screen

The next day was worse. After “Play” there was no dice button and no roll button — the game started and was then a dead end.

The cause is one line that looks the same everywhere:

set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE, PRESET_MODE_MINSIZE)

PRESET_BOTTOM_WIDE derives its offsets from the height at call time. In MINSIZE mode that is the minimum size — and it was zero, because custom_minimum_size was only set on the following line.

A bar of height zero, anchored to the bottom, sits exactly on the edge. Its content then grows downwards. Both bars stood completely below the visible area.

Not fixed by “call the preset later”. That would have worked and would have been the shortest change — and on the next refactor somebody would have swapped the order again without knowing it carried anything. The anchor is now explicit (anker_unten), the height comes from the content, and minimum_size_changed keeps it in step. There is no longer an order you can get wrong.

What both bugs have in common

A size was zero at a moment when the code already needed it. Once it was the hit area of an invisible container, once the height of a bar. In both cases the line as written is correct — it only stands at the wrong place in time.

That is the class of bug a look at the source does not find. You find it by starting the thing and touching it.

And something else surfaced the same day

The camera fitted itself to a box, but not to its corners. In portrait, flaps 1, 2, 8 and 9 fell out of frame — at the edges, where nobody looks first, and precisely the flaps you need at the start of a round. _fit_eye now computes against the eight corner points of the box.

Plus a button that formally existed and factually never did: “Shut” was always disabled, because any valid selection was executed immediately anyway. A button that is never clickable is not a button — it is a claim. It is a real one now.


None of the test runs reported any of this. They called the game functions directly and were green while the game was unusable. That is the lesson of day two, and it came back a month later — when every tap on a phone counted twice.

← Devlog