workshop private

← all creations

Two Blades

sprites · created 2026-09-12

84×54 pixel projector movement with three loops — a Maltese cross yanking film through a gate, and a shutter whose second blade adds no information, costs you nearly half your light, and is the only reason the picture holds still.

pixel-artcanvassimulation

The film movement of a 35mm projector, drawn as a service-manual elevation: a strip running down through a gate, a Maltese cross pulling it one frame at a time, and a shutter disc sweeping blades across the lit aperture.

A projector does not slide film past a lamp. It stops each frame dead in the gate, shows it, and then yanks the strip down by exactly one frame pitch. Everything else in the machine — and everything in this piece — is a consequence of that.

The pulldown

The yank is the job of a Geneva movement: a crank with a single pin, and a slotted cross on the intermittent sprocket’s shaft. The pin enters a slot, accelerates the cross from a dead stop, decelerates it, and leaves; a locking cam holds the cross rigid the rest of the way round. For an n-slot cross the pin is engaged while the crank turns 180° − 360°/n, and the cross advances 360°/n.

A four-slot cross therefore spends exactly a quarter of every frame period in motion and three quarters standing still. That is not a number the module approximates — crossAngle is the real relation,

β = atan2( sin α , 1/sin(π/n) − cos α )

clamped to ±180/n outside engagement, and the film position is (β + 180/n) / (360/n) of a pitch. Which is why pulldown is one of the loops: at 3, 4 and 6 slots the same code gives you a snatch, a shove and an amble, and the film in the gate moves accordingly.

The first blade

That quarter of a revolution is a problem, because during it the picture in the gate is sliding. So the shutter — on the same one-revolution-per-frame shaft — carries a blade wide enough to be over the aperture for the whole pulldown. Hence the first constraint:

blade width ≥ pulldown arc + the aperture’s own angular width

The second term is the one nobody mentions. The aperture is not a point; it subtends a real angle at the shutter’s axis, so the blade has to be wider than the pulldown by that much just to finish covering the frame before the film starts to move. Cut it narrower and you get travel ghost: the frame moves while lit, and smears. The demo’s fourth readout is exactly this, and dragging the blade slider below the minimum puts red under the light curve and a smear in the averaged screen.

The second blade

Cover the pulldown and you have a working projector that flickers horribly, because you are flashing the screen at the frame rate and 24 Hz is nowhere near the fusion threshold — about 48 Hz at cinema brightness, and it climbs with brightness rather than sitting still.

The fix, and the title, is a second blade: an extra opaque sector that interrupts the same still frame a second time. It adds no information whatsoever. It doubles the flash rate to 48 Hz and the flicker goes away.

It is not free, and the piece is really about the bill. The eye integrates — a flash it cannot resolve is worth its time-average and nothing more (Talbot–Plateau) — so what the audience sees is the open fraction of the disc. On this geometry that is 68% of the lamp with one blade and 37% with two. The second blade buys steadiness with nearly half the light, in a currency nobody in the auditorium can see either half of.

The demo puts those two facts side by side deliberately: on screen, this instant is the aperture right now, and what the eye integrates is the same frame averaged over the window from one pulldown to the next. Add a blade and the right-hand panel gets dimmer while the left-hand one keeps flashing at full lamp brightness. That gap is the whole trade.

Where the two constraints collide

With equal blades you need n_blades × width ≤ 360, and width ≥ pulldown + subtense. A four-slot cross pulls down in 90°, so two blades is about all the disc will hold. Ask for three and the aperture is dark 95% of the time.

Switch the cross to three slots and the pulldown is 60° — a nastier jerk, worse on the perforations, which is why nobody used it for a long feature — and only then does a third blade fit, at 72 Hz and 30% transmission. That is the silent era’s problem and the silent era’s answer: 16 fps needs three flashes per frame to clear fusion, and it can only have them because the movement is quick enough to hide behind a narrower blade.

How many times you can flash the screen is set by how fast the film can be yanked. That is the sentence the piece exists for.

Honest about the geometry

This shutter is small relative to its aperture — an 84-pixel canvas will only hold so much disc — so the aperture subtends about 23° here against roughly 11° on a real machine, where the disc is ten-odd aperture-widths across. The blades in this piece are correspondingly wide (114° against a real ~100°) and the two-blade transmission correspondingly mean (37% against a real 45–50%). The relations are the machine’s; the constants are this drawing’s, and the demo computes them from the pixels rather than quoting them. AP_SUBTENSE is measured off the aperture’s own corners at load.

The drawing is an elevation, not a section: the film is face-on and the mechanism is laid out beside it, which is how a service manual draws a movement and not how the light actually travels.

The loops

Reuse

Source

No .aseprite — the canonical source is source/two-blades.mjs, which is both the kinematics and the pixel drawing. Nothing is keyframed: every frame is renderFrame({ t, frame, blades, bladeW, slots }) evaluated at a phase, and the film position is quantised to whole pixels because a strip that lands between rows is mush rather than a picture. Regenerate everything with:

node source/render.mjs          # export/, media/ loops, thumb, demo copy
node scripts/screenshot-demo.mjs   # media/ demo shots

(needs site/node_modules installed — the scripts resolve Chromium through site/scripts/lib/chromium.mjs.)

validateGeometry() is the cheap guard, and both scripts run it: it checks that each cross indexes a full step, that the film advance starts at 0 and finishes at 1, and that the minimum blade really does cover the pulldown. Change the layout constants and it will tell you if the drawing has stopped being a mechanism.