Aposemati is a free, open-source, Apple-only phone-as-camera tool: the iPhone shoots, the Mac is the viewfinder, shutter and gallery. It is the successor to — and a hard course-correction of — diy-continuity-camera-clone-research.

The name

Aposematism = warning coloration in nature. The literal opposite of camouflage. It is a pun on Camo.

Repo: /Users/levander/coding/aposemati Spec: /Users/levander/coding/aposemati/docs/superpowers/specs/2026-08-18-aposemati-design.md

🟢 STATUS: THIS IS WORKING SOFTWARE, NOT RESEARCH — 2026-08-18

The first live two-device run captured two photos at 8064x6048 (48.8 MP) from an iPhone 16 Pro to a Mac over a link reported as awdl0 — direct, written to ~/Pictures/Aposemati/2026-08-18/. That is ~17x the pixels Continuity Camera can deliver.

68 commits, 219 tests, zero warnings, clean tree on branch phase0-phase1-link-and-shutterunmerged and unpushed. Both apps are installed and running on real hardware. Start at aposemati-phase0-phase1-build.

The review loop is closed, and the final whole-branch review has now run (2026-08-19, six reviewers, all 68 commits at once) — no finding blocks the branch, and the long-unexplained POSIX 57 during pairing is solved. Two of the three Criticals are real but unreachable through the Phase 1 shutter path, sitting under Phase 2 and Phase 4; the third is the POSIX 57 root cause. Nothing from the review is applied yetaposemati-whole-branch-review.

🚶 The last untested product claim is the walk to the spot where Camo drops, with the apps. Phase 0 proved the probe holds awdl0 there at 156 Mbps — but that was two Macs moving zeros.

🟢 PHASE 2 IS COMPLETE — live preview, 2026-08-25

33 commits, 418 tests, both apps building, complete at c29f84d. JPEG frames from the phone through a 12-rung quality ladder, fragmented into QUIC datagrams, reassembled against a 200 ms deadline on the Mac, decoded and displayed. A lost fragment costs exactly one frame, never the stream — measured on two real QUIC ends, not asserted. Start at aposemati-phase2-live-preview.

⏸️ The verdict is deliberately blank pending hardware. docs/superpowers/phase2-results.md is a measurement gate: the two readings that decide whether JPEG-per-frame is enough — the buffer dimension the phone delivers, and datagram loss p on awdl0 — need two devices, and no device build was permitted. One reading decides two verdict rows at once: 4032×2268 → 240 fragments / 69.0 Mbps (passes), 1920×1080 → 408 fragments / 117.4 Mbps, which fails a derived 50%-of-throughput bar.

🔴 Hardware checklist item 1 is not preview at all — it is Phase 1’s stills. AVCaptureVideoDataOutput now joins the capture session unconditionally, so 48 MP + ZSL must be re-verified with it attached before anything else is measured.

Decisions made 2026-08-18

DecisionValue
NameAposemati
LicensingOpen source, public repo, free
PlatformsApple only — no Windows, Android or Linux
AmbitionA serious personal tool, not a toy
v1 scopeFull-sensor 48MP stills + on-device video recording
RolesiPhone shoots. Mac is viewfinder / shutter / gallery.
Virtual cameraDeferred to phase 7
Defining constraintOff-router direct transport
Use caseBoth devices together, far from the router

The defining constraint, restated

The product is the transport. Both devices are in the same place, far from any access point. Anything that hairpins through a router inherits Camo’s failure mode. This thesis came from the original brief and survived the invalidation of that brief’s Wi-Fi Aware architecture.

Architecture

Four channels, not one stream

Never stream the thing you are keeping

Camo streams video to the Mac and records the stream, so recording quality is bounded by transport quality. Aposemati records where the sensor is and ships the file afterward.

ChannelPayloadLatencyDeliveryConnection
Preview⚠️ as built: 1080p JPEG per frame, 12-rung ladder (HEVC/H.264 is the open question)CriticalDroppable⚠️ as built: its own connection to its own listener on a separate port, datagrams — not A
ControlShutter, focus point, settings, framingLowGuaranteedA — stream
StillsFull-res HEIF (see the capability matrix)IrrelevantGuaranteedB — stream
VideoRecorded on-device at full quality, transferred afterIrrelevantGuaranteedB — stream

Corrected during design: two connections, not one

The original design put all four channels on one QUIC connection. QUIC eliminates head-of-line blocking between streams, but all streams on a connection share one congestion controller, and Network.framework exposes no cross-stream priority — every function in quic_options.h was enumerated. A multi-megabyte still would contend with the preview’s congestion window and reproduce the exact “Camo is laggy” failure this project exists to fix.

Two connections give independent congestion windows and allow serviceClass = .interactiveVideo on A against .background on B. On a direct link a second handshake is negligible.

Corrected again by Phase 2: it is three connections, and the spec has not caught up

Preview does not ride as datagrams beside the control stream on A. NWConnectionGroup + NWMultiplexGroup did not work, so the code opens a third NWConnection to a second NWListener on its own port. The §4.1 claim is satisfied better than described — preview is isolated from stills rather than sharing a window with control — but the durable spec still records the architecture that was planned, not the one that shipped, and that spec is what the H.264 planner will open. .interactiveVideo is set; nothing sets .background anywhere.

And one layer below the split: NetworkTransport.queue is a single serial DispatchQueue both connections start on, worth ~40% of the measured interference under saturation. Deliberately not fixed — The architectural claim was measured, and it holds — with a caveat one layer down.

The consequence worth stating plainly: video can be ProRes, 4K60 or 10-bit HLG regardless of what the wireless link can sustain, because the link only carries it after the fact. Camo structurally cannot do this.

Framing is metadata, never pixels — REVERSED 2026-08-26, see below

This section describes the design that was intended, not the one that shipped

Framing shipped destructive: the crop is applied to the saved still and the uncropped original is never written to disk at all. The owner chose this deliberately and reaffirmed it when the contradiction was found. Reasoning and cost are in The reversal of “framing is metadata”.

The original intent below is kept because the arguments for it are still good, and because auto-framing (Phase 6’s remaining half) may want it back.

All framing — manual crop, zoom, pan, aspect ratio, mirror, rotate, level, and auto-framing — was to be stored as a transform alongside the asset. The full-resolution 48MP original is never modified.

Auto-framing (the Center Stage equivalent) is therefore a subject-tracking function that writes the crop rect, not a filter that consumes the frame. This is also how Aposemati escapes Camo’s exclusion matrix.

Prior art: Photo Booth already works this way — non-destructive effects plus File > Export Original…. See 2. Photo Booth is NOT low-res any more.

Modules

ModulePlatformContents
AposematiCoreShared Swift package, no platform codeWire protocol, message types, Transport protocol + backends, capture settings model, asset metadata incl. framing transform
Aposemati CameraiOS / iPadOSAVCaptureSession, preview encoder, still capture, on-device recorder, transport publisher
AposematimacOSTransport browser, preview renderer, controls, library; later hosts the camera system extension

Hard platform boundaries that cannot be shared: the system extension (macOS only), AVCaptureMultiCamSession (iOS only), Apple Log and HDR colour spaces (iOS only), AVCaptureDeviceTypeDeskViewCamera (macOS only), preview view hosting, and all entitlements.

There is no SwiftUI camera view

Zero camera/capture/video/player symbols across 675 public structs in the macOS 26.2 SwiftUI interface. Preview means wrapping AVCaptureVideoPreviewLayer or rendering through AVSampleBufferDisplayLayer.

Phases

The phases were renumbered during execution

Phase 1 became link + shutter + stills (preview moved out to Phase 2), because the four-channel architecture lets preview be added later without touching the transfer path. The table below is the executed numbering.

PhaseDeliverableStatus
0Transport spike — measure throughput at real rangePASSED on hardwareaposemati-phase0-field-measurements
1Link, pairing, control channel, shutter → full-res still in the Mac libraryWORKINGaposemati-phase0-phase1-build
2Live preview on the Mac (droppable QUIC datagrams)COMPLETE, verified on hardware at 30 fpsaposemati-phase2-live-preview, aposemati-crop-and-framing. ⭐ A permanent-freeze defect was found on hardware and fixed 2026-08-26aposemati-loopback-blind-spot
3Burst captureNot started
4On-device video recording with background transfer; asset lifecycleNot started
5Manual controls and colour pipelineNot started
6Framing: crop/zoom/pan, aspect ratios, mirror/rotate/level, auto-framing🟡 crop/zoom/pan SHIPPED 2026-08-26 (destructive, not metadata) — aposemati-crop-and-framing. Aspect/mirror/rotate/level and auto-framing not started
7CMIO virtual camera — live webcam output. Where “mirrors Camo” becomes trueNot started

Phase 0 was not optional — and it earned its place

Apple published no throughput figure for any candidate transport, and no third-party benchmark existed. Building phases 1–4 on an unmeasured link is precisely how the prior research brief went wrong.

Phase 0 not only passed, it caught two things that would have poisoned everything downstream: a probe that was timing the close handshake rather than the transfer (making every reading land near the pass threshold by coincidence), and the discovery that QUIC does not support TLS-PSK, which moved the pairing model mid-build.

Out of scope

The homelab receiver is dead

telep-mainframe’s AX210 has no antenna attached to its u.FL connector — it cannot be a wireless peer at all, independent of any Linux NAN driver question. See telep-mainframe.

Also out: Windows, Android, Linux clients. Apple only.

Notes in this project

NoteCovers
aposemati-phase0-phase1-buildSTART HERE. The working build: live run, architecture as built, what is unverified, how to build.
aposemati-phase2-live-previewPhase 2, complete. Live preview over QUIC datagrams; the loss table and the H.264 gate; the ranked hardware checklist; the two-device protocol whose verdict is still blank.
aposemati-peer-restart-inference-boundarySeven rebuilds of one restart detector. Peer data must never move the watermark backwards; the fifth answer was to delete the mechanism.
aposemati-loopback-blind-spotLoopback cannot reproduce “a send that succeeds into nothing”. Why 546 tests missed a permanently frozen viewfinder; the missing stateUpdateHandler; hand-run mutation evidence.
aposemati-whole-branch-reviewThe final review of all 68 commits: POSIX 57 root cause, the 250 ms listener margin, the C1/C2 mutual exclusivity decision, a ~70%-real test suite.
aposemati-crop-and-framingPhase 6, first half. Crop/zoom/pan shipped destructive, reversing “framing is metadata”; the .photo preset’s hidden 17 fps format; the two hardware defects.
aposemati-phase0-field-measurements339 / 156 Mbps on awdl0, rtt 37 ms unchanged at range, 0.011% retransmission.
aposemati-pairing-security-modelCommit-reveal SAS, the 0.77 s machine-in-the-middle, in-memory identity, UI as security boundary.
aposemati-network-framework-gotchasNWBrowser.Result.interfaces, never filter by interface type, QUIC has no TLS-PSK, .ready proves nothing.
swift-uncancellable-continuation-trapThe withCheckedContinuation hang that appeared three times and cost two fix rounds.
swift-character-isnumber-digit-trapCharacter.isNumber accepts Arabic-Indic digits. Hit three times in one codebase.
aposemati-apple-signing-gotchasWWDR G3 missing → 0 valid identities; device registration; security-scoped bookmarks.
aposemati-build-process-lessonsVerify by execution; passing tests are not evidence; mutation testing; trace a symptom to its single throw site; commit as you go.
aposemati-build-install-runBuild/install/run commands that work — Aposemati.appAposematiCamera.app, and xcodebuild and devicectl use different UUIDs for the same phone.
aposemati-transport-findingsWi-Fi Aware is iOS-only. AWDL is alive. Surviving transport candidates. USB bandwidth trap.
aposemati-apple-api-measurementsContinuity Camera’s 1920x1440 ceiling, VideoToolbox low-latency numbers, Core Image costs, CMIO extension gotchas, distribution.
camo-studio-teardownCamo Studio 2.8.2 at binary level — pricing, the exclusion matrix, absences, architecture.
diy-continuity-camera-clone-research⚠️ Superseded. Kept as a record; its Wi-Fi Aware architecture is invalidated.