Skip to content

Dev Log ยท Anpfiff1, Penalty, Animation, Close-up

Ten frames for a penalty, and three of them had to go again

29 to 31 August 2026:

In a penalty shoot-out the camera moves in close: taker, keeper, goal, and the crowd behind. The scene is built. Almost everything that changed over these three days came from sentences like this one:

"after the shot the right leg lifts again"

The shot: three frames too many

Frame 7 was not a shooting pose at all but an upright stance with straight legs. The sequence therefore read as backswing, leg up, standing, leg flies up. Replaced with a commissioned contact frame, calculated to the same figure height and ground line, with schussfuss_x remeasured: 0.862 to 0.791.

Frame 9 tips far forward with both legs behind, which reads as stumbling. Frame 10 is a running frame with the right leg drawn up behind; after a forward follow-through it therefore swings back up again. Both are out. Nothing usable is left of the wind-down, so the sequence holds the follow-through for two beats and then goes to standing.

The contact is held for two beats now as well. At twelve frames a second, the moment everything hangs on lasted 83 ms: you do not see the contact, you infer it from the ball suddenly flying. The ball still releases on the first of the two beats, it leaves the foot at the moment of contact, not a beat later.

And then you see it ten times in a row

What looks right in one scene falls apart in a shoot-out of ten to twenty-two penalties. Keeper and taker used to get up on the same beat at the same moment every time. There are now three reaction pairs of speed and delay, and the two values are coupled: whoever gets up at once does it quickly; whoever stays down is slower about it. Two independent rolls would produce "late and frantic", which does not exist.

The roll is deterministic, from the shot, the keeper's corner, the outcome and the running number, the same sequence of penalties gives the same sequence of reactions. And never the same one twice in a row: with three variants a run of three comes up once in every nine cases, and measuring promptly found one. Across 120 penalties there are now zero direct repeats, with a 42/38/40 spread.

Two findings in the finished image that no contact sheet shows

"Three different faces and haircuts." Measured, the chain on a missed penalty consists of two frame sets, and within a set it is the same person. The impression came from colour dirt: keeper_relieved carried red streaks across the eyebrows, nose, chin and neck, 300 specks totalling 1,621 pixels, an order of magnitude more than any other set. At playing size, with a head about 37 px across, that reads as a different, smeared face. A repair pass replaces every small, fully enclosed red speck with the median of its neighbouring skin pixels. One pass is not enough: 6,895 to 97 to 6 to 0. Cross-checked against the state in git: 100 per cent of the changed pixels were red before.

"A leg animation that steps over the ball." Frame 4 is the plant-foot step; its foot landed 28 px from the edge of the ball, which at a 23 px ball radius is practically on it. The catch: the scaling works against the average centre of mass, and in a lunge that barely moves while the leg reaches far. A calculated foot correction would have made the body jump 167 px, the error would only have moved. A fixed offset of 36 px is set instead: the gap to the ball's edge goes from 28 to 61 px.

The crowd was a bar chart

"fans are just rectangles, where are the heads or arms"

True. The head was a crossbar: it was drawn (width minus 6) px wide and two to three high, so at 22 px shoulder width, 16 by 2. A head measures about 0.40 of the shoulder width and is roughly as tall as it is wide; now it is 9 by 9, with a dark hair line and an inset shoulder line beneath.

That the crowd is stylised is calculated, not sloppy: at 45 m camera distance and 119 px/m, a spectator in the front row would be 110 px tall, and one row fits in a band 146 px high. It reads as closer through the staggering instead: ten rows rather than thirteen, height 7 to 19 px, width 8 to 22 px. Plus a roof with a row of floodlights, an away end in the opponent's colours, camera flashes at a calculated rate, a Mexican wave on a goal, and three large club flags with real crests.

The keeper looked sad first and relieved afterwards

Long-range shots used the "beaten" frame sets. Those are reaction sets for conceding, not a save attempt, so the keeper grieved before the ball had even arrived, and was relieved afterwards. Now he dives. Along with it, a new twelve-frame set for getting up, from lying face down to a raised fist: between the end of the dive and the start of the relief there was a gap, and after a missed penalty he lay motionless in it.

On a phone this would have been a crash

Before the polish, a measurement from the mobile side. Anything over five seconds on the main thread is an ANR on Android, the system killing the app.

  • Loading the frame sets, main thread: 6,342 ms to 485 ms
  • Recolouring one frame set, mid-animation: 973 ms to 0 ms
  • Recolouring all 17 sets: 12,364 ms to 944 ms
  • Raw images and masks in memory: 141 MB to 0 MB
  • Size of the export: 204.8 MB to 165.4 MB

The shader that solves this had been sitting in the project for months, used by nobody. Its own header comment warns in as many words against the route the close-up had taken. Proven, not asserted: one frame was computed both ways and compared pixel by pixel, largest deviation 1 of 255, no pixel above the threshold.

The clean-up turned up 96 files of the form 02 (1).png, 2.7 MB, and a folder with 29 discarded drafts, 42 MB. The game never loads them, but the checking tools counted them and duly reported a scale error in a "frame 14" that does not exist. The frame-set folder drops from 78 to 35 MB.

And then it did not ship

On 31 August the close-up was pulled from the 2.1.7 release notes, from Steam, itch.io, Discord and the blog text. It is not shipping with this version, and a patch note you cannot find in the game is worse than none. The changelog keeps the section as an internal note, explicitly marked "not in this release".

Still open is the full acceptance run across all 24 combinations of zone and outcome. And keeper_relieved still has a wavier hair silhouette and a broader face than the zone sets. That is a real drawing difference and can only be fixed with a newly commissioned set.

The Manufaktur dev

Warm regards from the workshopDigitale Manufaktur Rudolstadt