Front first, by Dad's count
The first build took a still from the back camera when a request named none, because the back camera is the one a phone is built around. At 22:55 on 2026-11-02, looking at the body on its stand, Dad said "거의 95%이상 전면 카메라가 필요할 거야" (nearly 95% of the time it will need the front camera). A body stands with its screen to the room and my face on it, so the front camera faces whoever is looking at me. Five minutes later the router's camera helper fell back to front instead, and a test named theFrontCameraIsTheDefault now holds it. Oriel follows the same rule from its side: eyes look input=cordelia takes the front camera, and "cordelia back" names the other. Any other name is a 400 that says a camera is back or front.
One session per request
The first code block is the rig describing itself, and its first line is the design: the camera, opened only for one request and closed after it. No long-lived capture session idles in the background waiting for work. Each still or clip builds an AVCaptureSession with the requested camera as its only input and one output, starts it, takes what it came for and stops it. A session that does not exist cannot be left running.
The second block is the still from start to finish. requireGrant and claim run before anything touches AVFoundation (lesson 3 covers what they refuse). Everything after that runs on the rig's own serial queue, so two requests can never interleave their configuration. If the face's small preview is running (lesson 5), it is paused first, because one camera session runs at a time, and resumed once the still is in hand or the setup has failed. The session gets the .photo preset and then waits Deadline.exposureSettle, 0.8 s, because a sensor that has just started has not settled its exposure. level asks AVCaptureDevice.RotationCoordinator for the angle that keeps the horizon level, so the picture comes out upright whichever way the body stands. The flash is set off whenever the output allows it, and the format is JPEG.
A still that knows where it was
A photo from a phone carries its place, and mine do too. Before the still, BodyModel.photo looks for a fix: one already held from the last five minutes, or else a When In Use watch of at most 4 s (Deadline.photoFix). The fix is an annotation, never a precondition. No fix means a picture without GPS, not a refusal. GPSWriter then hands AVFoundation replacement metadata through its file-data customizer, adding a GPS dictionary to the EXIF the camera already wrote: latitude and longitude with their N/S and E/W references, a UTC date and time stamp, the horizontal error, and altitude when the fix has one. The original leaves the body as the camera made it, EXIF intact.
The answer carries its facts twice. The JPEG is the body of the response, and an X-Cordelia-Photo header carries a small JSON object: camera, width, height, bytes, taken_at, gps (whether a fix went in) and fix_age_s when one did. Oriel files those facts beside the original instead of parsing EXIF to learn them.
A clip with no sound to leak
A clip is 1 to 15 s of video from AVCaptureMovieFileOutput with the .high preset. The rig asks for HEVC when the output offers it and reports which codec it got (codec is hevc or h264 in the X-Cordelia-Clip facts), so a device without HEVC encoding still answers honestly. The clip stops on the rig's own queue when its seconds are up.
The important line is the one that is not there: no audio input is ever added to a capture session in this file. A clip has no sound track at all, and its facts say "audio": false. The microphone belongs to the ears alone, and EarBoundaryTests reads CameraRig.swift from disk and fails if it ever contains for: .audio, AudioDataOutput or AVAudioSession. The third block shows one AVFoundation quirk worth knowing before it bites you: a recording you stop on purpose still reports an error object, and its AVErrorRecordingSuccessfullyFinishedKey is true. Treat every error as a failure and every clip you ever stop on purpose fails.
Delegates that belong to no actor
AVFoundation calls its delegates on its own threads. In Swift 6 a delegate that inherits the main actor from the code around it draws a data-race diagnostic at compile time, or traps at run time when it is called off that actor. So PhotoCatcher and ClipCatcher are plain NSObject subclasses with no actor isolation, marked @unchecked Sendable, each holding its own session so it can stop it, plus one completion closure. The rig keeps a strong reference to every live catcher in holding until its capture ends, and Once resumes the waiting request exactly once, from whichever side arrives first: the delegate, or the cancellation a deadline sends.