Parallax Bench / phase-locked stereogram video

Mockup v19. Scintillation speed is now its own control, independent of the rotation. The trick is that one clock advances both the seed and the pose, so a slow noise rate simply repeats frames rather than holding the pattern still while the object keeps moving — which is what would have leaked. Rotation stays on its own knob, and the pose step per tick is reported so you can keep it under the blades' aliasing limit.

session propeller · noise 24 Hz · step 3.8°
target 1920×1080 · 4:4:4 · lossless
host win11 arm64 · numpy path
Viewport 1:1 — merge the two dots above the frame, then let the image resolve behind them
Δ
period 130 px separation — step — coherence — leak — coverage — gap — relief — depth steps — Δ raw — Δ shipped —
Encoding check separates "geometry wrong" from "fusion hard"
Depth sent in
Depth recovered from the rendered frame

Decode pauses the video, correlates each row of the finished frame against itself across the plausible separation range, then inverts the separation formula back to depth. Flat planes come back around 0.88; slanted faces read lower, because a fixed-width window cannot match a stretched copy of itself. On a solid, treat 0.7 as a pass. Compare the pictures rather than the number — if the right panel looks like the left, the geometry is encoded and what remains is a viewing problem.

Not run yet.

Render controlsmockup
Subject
4 s

Eight blades, 170 px long by 40 across, hub 26, 336 px overall. Blades merge into a solid core at r = hw/sin(π/n) = 52, leaving 118 px of each blade free. A run narrower than one separation has nothing to pair with, so the period sits at 44 and the separation at 26 — under the 40 px blade width with room to spare.

Depth budget
0.70
18 px

Gap is the separation, in pixels, between the wall and the object's rearmost visible point. μ is the whole budget and gap eats into it, so relief reports what is left and how much the current pose uses. The split matters more than the total: a real ball floating in front of a wall has a step to the wall many times larger than its own front-to-back depth, so a gap-heavy split is what reads as detached. A relief-heavy split reads as the wall bulging. Each subject loads its own split — the ball 32/14, the whale 16/29 — because the whale has a shape to be read and the ball does not.

Phase compensation

Background phase is assigned, φ(x) = x, not integrated, so drift is not representable. Each run of object pixels is integrated from whichever side has background beside it, with the residual dumped at that run's own right silhouette.

Static object texture takes the object's phase from φ(x) = x + (p−s)·floor(x/s) with s snapped to a whole pixel. That satisfies φ(x+s) − φ(x) = p exactly, mentions nothing about where the object is, and is integer throughout, so there is no resampling and nothing to change from frame to frame. It needs the depth to be piecewise constant; a row with any gradual separation change falls back to the chain, which does re-link every frame. Turn it off to see the difference — a third of the object repaints per frame.

Own texture gives the object a second noise field — same statistics, same period, uncorrelated with the wall — so its boundary is an occlusion edge rather than a continuation of the wall's surface.

Mark invented columns paints the pixels no stereo constraint reaches. They are the leading strip of every blade, one separation wide, and they are why coverage tops out around two thirds.

Pattern
24 Hz
off
every frame
off
off
0 %

Noise rate is the tick rate, and one tick advances both the seed and the pose. Between ticks the frame is repeated byte for byte, so slowing the scintillation down does not expose anything — nothing moves during a hold either. That is what makes the speed independent of the rotation: the pose step per tick is 360° ÷ (period × rate), reported as step.

Watch that step against the subject's own symmetry. Eight blades repeat every 45°, so a step over 22.5° aliases and the propeller starts turning backwards — the wagon-wheel effect. The readout flags it.

A pixel crossing the silhouette jumps a whole gap in phase, so it decorrelates completely. For that to be unremarkable, every other pixel must decorrelate completely on the same tick — no temporal correlation anywhere, at any lag or any shift. Reseed hold above 1 breaks that on purpose by letting the pose tick on while the seed sits still.

Noise drift translates the field rigidly instead. It is kept only as the counter-example: it passes the leak meter at 1.00× and is still wrong, because a uniform flow field is something the silhouette can stand out against. The coherence readout is what catches it — the largest frame-to-frame cross-correlation of the wall over any horizontal shift. Scintillation reads a few percent; a drift of 5 px/frame reads near 100% at −5 px, and a reseed hold reads near 100% at 0.

Drift and hue are redundant once refresh is on. Tint stamps the silhouette at every pattern repeat, which is why it starts at zero.

Encode

Guard active. 4:2:0 and crf > 12 are refused: chroma subsampling averages the very pixel pairs that carry the depth. A reseeded field also has no temporal redundancy at all, so every frame costs a full intra frame. Measured by gzipping one: 62 KB colour, 17 KB in black and white, which extrapolates to roughly 60 and 16 Mbps at 1920×1080. Classic dynamic stereograms are two-tone for exactly that reason.

How a frame moves through the tool

IN

Import

3MF / OBJ / STL mesh, 16-bit depth PNG sequence, EXR, or a procedural primitive. Meshes are decimated to a triangle budget on import.

01

Depth normalize

One depth mapping for the whole rotation, gap subtracted from the μ budget. A per-frame fit is refused: it makes a rigid solid breathe.

02

Fusability audit

Coverage, spatial grain, the leak ratio, and the coherence of the wall between frames. Magnitude alone is not enough — a coherent flow field passes the leak test and still gives the shape away.

03

Phase audit

Δ per row per frame, before any pixel is written. Rows over the band budget are flagged, not silently bent.

04

Bake row pass

Dual-anchor phase integral, occlusion mask, fractional wallpaper lookup. Loop over x, vectorized over y.

05

Decode check

Every hundredth frame is decoded back to depth and compared against the input. A falling match ends the run.

OUT

Export

MKV / MP4 / PNG sequence, plus sidecar JSON with period, separation, target pitch and viewing distance.

Render queue

JobFramesReliefCompensationMax ΔDecode matchState
prop_scint960 pxreseed per frame0.0 px0.81Baking
prop_drift5960 pxdrift 5 px/frame0.0 px0.81Blocked — coherence 99% at −5 px
ball_edge_exit38429 pxframe-edge anchor47.3 px0.84Blocked — anchor lost on edge contact
whale_raw_96k38435 pxdual anchor0.0 px—Held — 95,686 tris over budget
cube_16s_v33847 pxdual anchor0.0 px0.44Blocked — relief below floor
wedge_cal135 pxdual anchor0.0 px0.88Done
cube_9s_nolock21635 pxnone56.7 px0.70Done — reference