An iOS Simulator host: one long-lived process a client spawns to drive Apple's iOS Simulator. P0 wraps the public xcrun simctl CLI (lifecycle, install/launch, screenshot); P1 adds live framebuffer streaming (JPEG STREAM_FRAMEs) and touch/button injection through CoreSimulator/SimulatorKit private frameworks, in a crash-isolated capture worker. The stdio wire contract is PROTOCOL.md; the original LinkCode TypeScript client is @linkcode/sim (packages/host/sim in the upstream repository).
macOS-only at runtime (it needs Xcode's simulator tooling); it compiles and answers probe with a structured xcodeMissing error everywhere else, so cargo test can run without Xcode.
Install Nix and devenv. The environment pins Rust and includes Cargo, rustfmt, Clippy, rust-analyzer, and nixfmt.
devenv shell
devenv test # Nix/Rust formatting, Clippy, and tests
cargo build --releaseWith direnv installed, run direnv allow once to activate the environment automatically. Outside the shell, prefix Cargo commands with devenv shell --.
Set APPSIM_DEBUG=1 to print private-framework diagnostics to stderr.
The simulator integration test requires macOS, full Xcode, and an installed iPhone simulator runtime. It boots a simulator and runs only when explicitly requested:
cargo test --test device_loop -- --ignoredbench-encode times the JPEG encode hot path — the per-frame CoreGraphics/ImageIO cost that bounds the framebuffer stream, since a single reader thread does the encode. 1000 / avg_ms is the sustainable frame-rate ceiling. It needs no simulator: it encodes a synthetic BGRA frame across a resolution/quality sweep.
cargo build -p appsim --release
./target/release/appsim bench-encode [iters] # default 120 iters/configOn 2026-07-22 (macOS arm64), 150 iters/config:
| Resolution | Quality | avg ms | p95 ms | Size | fps (avg) | fps (peak) |
|---|---|---|---|---|---|---|
| 1206×2622 (native) | 0.60 | 6.49 | 8.13 | 705K | 154 | 182 |
| 904×1966 | 0.60 | 3.45 | 3.98 | 396K | 290 | 332 |
| 603×1311 | 0.60 | 1.50 | 1.73 | 177K | 665 | 730 |
| 402×874 | 0.60 | 0.77 | 0.95 | 79K | 1294 | 1441 |
| 1206×2622 | 0.30 | 6.39 | 7.74 | 705K | 156 | 181 |
Takeaways: the native-resolution encode ceiling (~154 fps) leaves ~2.5× headroom over 60 fps; cost is set by pixel count (the DCT), not JPEG quality (0.3 ≈ 0.6); downscaling is the real lever (half resolution ≈ 4× cheaper). The synthetic frame is high-entropy, so the numbers are conservative — a mostly-flat real screen encodes a touch faster. Use it to catch encode regressions and size the fps/resolution budget, not as an absolute.
Licensed under either the MIT License or the Apache License, Version 2.0, at your option.
See NOTICE for third-party attributions.