Nobody saw the confetti for half a year
· game · design
A shut box is the only event this game knows. There are no points to collect, no levels, no progression — just nine flaps that are down at the end or are not. Until this day that exact moment was the weakest spot in the whole game: one tap too many, an invisible effect, a borrowed sound.
The box waited at the moment of victory
You confirm combinations with a tap on the dice. That is right as long as you could still correct yourself. On the last move you cannot — it clears the box, there is nothing left to weigh up.
So at the moment of victory the box stood there with two upright, golden flaps, waiting for a confirmation whose only alternative was doing nothing.
KlappenRegeln.ist_schlusszug() now decides that in the core: valid and
covers the last open flaps. The board executes it immediately — as a
combination too, and under “plus and minus” with two target numbers.
And then I went looking for the confetti
_ende_konfetti() was in the code: twelve ColorRects falling across the box.
Written some time in spring. I won a round to have a look at it and saw
nothing. Second round, third time. Nothing.
It was broken three ways:
- The rectangles hung off
_ende_overlay— and_ergebnis_spaeter()sets that very overlay invisible on the following line, showing it only 2.4 seconds later. The tweens ran for 1.5 seconds and cleaned themselves up. All of it behind an invisible parent node. - The overlay is a
VBoxContainer. That overrides anypositionyou set — the rectangles would have ended up where the container puts them, not where they were meant to go. - The maths was done against the full screen size. The overlay is a narrow strip along the bottom edge.
Any one of these three would have been enough. Together they survived half a year — because an effect you cannot see is indistinguishable from a modest effect. Nobody ever thought “something is missing”. You think “ah, subtle”.
Not repaired — deleted without replacement. Confetti are foreign coloured rectangles falling across a picture of wood. They have nothing to do with this game; they would be interchangeable. Every game has confetti.
The celebration is now the box itself
Kasten3D.siegwelle() raises every flap in turn by 42° and lets it fall
back into the wood — a wave from the 1 to the highest flap, which turns there
and runs back. Eighteen strikes, about 1.9 seconds.
No new geometry, no foreign rectangles over the picture: the same hinges and the same motion as shutting a flap, only bigger and in sequence.
| Ingredient | already there for | now also does |
|---|---|---|
| flap hinges | shutting, selection, hint | the wave out and back |
| fall tween with impact | one falling flap | eighteen, in time |
KlappenKlang.holz() |
the strike on a move | a scale up and back down |
the synth in klang.gd |
flap and dice | a third voice: the marimba bar |
Nothing in the image is new except the order. In the sound a single voice
was added, and it is made the same way as the strike, only with pitch: a
marimba bar, partials 1 : 3.9 : 10.4. All ten notes are one synthesised bar
shifted by pitch_scale — which shortens the high notes by itself and length
the bass under the final note.
The strikes of the wave run a major pentatonic upwards ahead of it: the flaps are the run-up, the first note is the landing. It starts at the turning point, and the return leg drums along underneath. Because the return takes the same flaps backwards, the scale falls again by itself — an arc, not a slide.
Under “reduced motion” the box stays still and the strikes still come. The rhythm is the celebration, not the wiggling.
How often this happens at all
Before it was decided how big the celebration could be, it had to be settled how rare it is. 20,000 simulated rounds per setup, played with the strategy the hint uses:
| Setup | win | ≤ 2 points | median |
|---|---|---|---|
| 1–9 · 2 dice | 6.8 % (1 in 14) | 12 % | 10 |
| 1–10 · 2 dice | 3.9 % (1 in 25) | 6 % | 16 |
| 1–12 · 3 dice | 6.2 % (1 in 16) | 10 % | 12 |
| 1–12 · 2 dice | 0.37 % (1 in 270) | 0.6 % | 35 |
Due roughly every fourteen rounds. Rare enough for something big, frequent enough that it must not get in the way. That is why the celebration lasts two seconds and not five. Without this table it would have stayed a matter of taste.
The checks were deliberately made to fail
All three new guarantees were broken before they were allowed to stand: rule
reverted → “final move did not fall by itself”; siegwelle() gutted → “0
strikes instead of 18”; lift turned down to 0.4° → “flaps barely rose”.
Two subtleties came out of that:
- The UI run records every flap with its direction and compares the whole sequence (0…8 up, 8…0 back). “Struck 18 times” alone would also be satisfied by the same outward leg twice.
- The lift is checked against a fixed lower bound of 8°, not against
WELLE_HUBitself. Otherwise the check would move with it and dutifully confirm a celebration nobody can see — which is precisely the bug this whole entry came out of.
That is the lesson, and it is uncomfortably general: a guard that measures against its own target value checks nothing. It is always green, even when the target is zero.
The touchstone for any further idea here is therefore not “does that look good?” but “could the object do that?”