Skip to content
C.W.K.
Lesson 04 of 05 · published

Measure the Camera, Never Gate on It

~18 min · calibration, dead-reckoning, measurement, computer-vision, failure-modes

Level 0Closed Lens
0 XP0/45 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

Turning by Numbers

A person turns a game camera by feel. Pippa turns it by a stick push of some strength for some milliseconds, and without numbers she could only say "a bit left". Dad's request on 2026-11-08 was units: 15 degrees, 90, 180. So the camera's curve is measured per game and kept as data: a full push of one game turns 260 degrees a second, a half push 28. A full push's full turns (the view coming back to its first frame) give exact degrees a second and the game's scale; slower pushes are read from the sideways shift of the moving pixels and labelled approximate.

A Measurement Is Knowledge, Never a Gate

The first version of this got one thing badly wrong. In Solo run 2 a measurement failed, and the code took that failure as a reason to refuse play_camera for the whole session. Pippa, unable to turn the camera, steered with sideways headings instead, and swung the view around one big tree for ten minutes. Dad's correction (Coop 1109-19): he had meant that Pippa should be able to calibrate the sensitivity herself, and instead the code had blocked something.

The rule since then: a measurement is knowledge, never a gate. Calibration runs in code with no brain at an unattended session's first run, when the solo_calibrate setting is on and the game's curve is missing or old: the stick is held at the plan's pushes, every push is read from the frames, kept per game, written into the notebook as seen, and the camera is turned back after each. A push that cannot be read is kept as a failure with its reason. The run starts either way.

The Push That Speeds Up

Measuring well also means finding what the first measurement assumed. A turn was planned as degrees divided by the push's kept rate, as if the camera reached that rate the instant the stick moved. Followed frame by frame on the far field, a full push actually starts at about 125 degrees a second and reaches 260 only 0.57 s in:

PushPlannedTurned
288 ms75°48°
346 ms (five pushes)90°54° to 58°
577 ms (two pushes)150°111°

Worse, the hands' own count of her turns used the same plan, so it said 90 where the view had turned 57. Her heading was 33 degrees off after one turn, and no count of her own could have told her, because the count and the plan shared the error. A measured push now carries its ramp (start_deg_per_s, full_after_s), and one curve is read both ways: how far a push turns, and how long to push for a turn. A 90-degree turn is now held for 491 ms, not 346.

Counting Turns Drifts. Looking at the Sky Does Not.

Even with the ramp right, a heading built by adding up how far the view turned is good for seconds, not for a walk. On a 14-minute recording, a bearing carried by turns alone was 1 degree off after 7 seconds, and 60 to 84 degrees off after 45 seconds of walking and 170 degrees of turning. The reason is geometry: a third-person camera circles the player, so near ground moves less on the picture than far ground does, and walking moves the near scene sideways in a way that reads as turning.

Dad's design (2026-11-11): a person finds their bearing by the far mountains. Whatever is far enough (sky, clouds that stand still, distant peaks) looks the same from anywhere in a place. So the far field is kept once as a panorama by heading and elevation, and each frame is found in it: where it is found is the heading. Nothing is added up, so a walk, a hit or a camera reset costs nothing. A view is cut into blocks that vote on their own; blocks seeing the far field agree, blocks seeing something near agree with nobody, and if not enough agree, the view is unread, never guessed. The sky held still to 0.1 degrees between two days' recordings, and a live read took 23 ms.

One Wrong Number Spreads

The camera's focal length (how wide the view is) decides how many degrees a pixel is. Its first value, 0.65 of the frame's width, was read off the fastest steps of a 577 ms push against the game's 260 degrees a second. That push had not reached its rate, the same ramp that broke the turn plan. Every angle read before the correction was 7 percent too large. The fix measured it a way that needs no rate at all: a slow turn on the spot until the same heading came back, which happens at exactly 360 degrees. The answer is 0.70, which means 71 degrees across. When one measurement depends on another, an error in the first one quietly spreads into the second. The best fix is a measurement that does not rely on the first one at all.

Code

One camera curve read both ways: how far a push turns, and how long to push for a turn (runnable sketch; its linear ramp reproduces the measured turns)·python
START = 125.0   # degrees a second when the push begins
FULL = 260.0    # the kept full-push rate
RAMP = 0.57     # seconds until FULL is reached


def turn_of(ms):
    t = ms / 1000
    accel = (FULL - START) / RAMP
    if t <= RAMP:
        return START * t + accel * t * t / 2
    return START * RAMP + accel * RAMP * RAMP / 2 + FULL * (t - RAMP)


def push_ms(degrees, lo=0.0, hi=5000.0):
    for _ in range(60):                      # the same curve, inverted by bisection
        mid = (lo + hi) / 2
        lo, hi = (mid, hi) if turn_of(mid) < degrees else (lo, mid)
    return round(hi)


for ms in (288, 346, 577):
    naive = ms / 1000 * FULL
    print(f"{ms} ms: planned {naive:.0f} deg, turns {turn_of(ms):.1f} deg")
print("push for 90 deg:", push_ms(90), "ms")

External links

Exercise

Run the sketch, then add drift: each push's real turn is turn_of(ms) times a random factor between 0.95 and 1.05. Simulate 40 pushes planned as 90 degrees each and track two headings: one that adds up planned turns, one that is "read" from the true heading with noise of 2 degrees, but returns None one time in five. After 40 pushes, how far off is the summed heading, and how far is the last read one?
Hint
The summed heading's error is a random walk: a single push can shrink it, but it drifts further out as pushes pile up. The read heading's error stays bounded however many pushes came before. A None means this push has no fresh reading: keep the last read heading, mark it stale with how many pushes old it is, and never report it as current.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.