# Round 4 capture rebuild

PR [#24651](https://git.cafe/pingdotgg/t3code/pull/24651), before [96fc7b9551](https://git.cafe/pingdotgg/t3code/commit/96fc7b955196637605cbf3b415152ce4dd0e2294), after [4859c3a7d3](https://git.cafe/pingdotgg/t3code/commit/4859c3a7d372dba06a50adee68d157643a305a10).

These are native development clients. No production source patch, snapshot overlay, fake provider, copied database or component harness is used. All fixture threads and content are synthetic. Each row/side runs its own checkout's backend against a fresh scratch base directory. The before and after RPC driver files are byte-identical. Metro 8081 serves the before checkout and 8082 serves the after checkout. The native fingerprints are equal across these revisions: iOS f532a0c5902b69c7f69cc5fe9c807053700a07eb; Android ed36a9cc9856c84a322bb71e6d67744f0a00877d. Clients were installed only with `vp run mobile:install <platform> --session <session> --metro <origin>`.

Place the published scripts in `/tmp/lead-24651-r4/capture`. Each side's `rpc-driver.ts` needs module resolution from its own checkout's `apps/server/node_modules` (the run used a scratch `before/node_modules` or `after/node_modules` symlink, pointing into the respective checkout). `prepare-row.py SIDE ROW ROOT PORT` creates a new timestamped directory, launches a fresh backend with T3CODE_DEV_SEED=0 and anchored-server-clock.mjs, issues a private auth session, seeds 12 garden threads and completes a real Codex response in B. It never uses the live T3 home. A has the initial title `A · Garden streaming`; B is `B · Idle notebook`. Ten stopped garden notes force scrolling. A server-created Scratch/New thread is also visible. The project root is the synthetic scratch git directory `garden-workspace`.

The backend Date clock advances normally from 2026-10-27T12:00:00Z, preserving provider buffering and event ordering. An earlier constant backend clock stalled stream flushes and that take was rejected. The client Date constructor and Date.now are fixed at 2026-10-27T12:00:00Z by freeze-clock.js; native timers are not patched, but elapsed labels consequently stay at Working 0s. Streaming evidence comes from actual assistant text growth and the public thread projection, not elapsed labels or timer notification counts. No FPS or latency comparison is claimed.

Lease explicitly named iPad Pro 13-inch (M5), Pixel Tablet and iPhone 17 Pro through the device-pool skill. All commands carry the same named session and run from the after worktree, which owns the leases. Start Metro on all interfaces with REACT_NATIVE_PACKAGER_HOSTNAME=fs-35.tail131df4.ts.net. The before Metro is `vp run dev:client --port 8081` from the before apps/mobile directory; head is the same at port 8082 from head apps/mobile. `start-process.py` implements the former and `start-row.py` implements each isolated backend.

Install the native client and open `t3code-dev://expo-development-client/?url=<percent-encoded Metro origin>&disableAutoLaunch=1&disableFab=1`. Accept the actual native Open confirmation if it appears. Wait until the expected Metro lists that device's CDP target. `setup-client.py SIDE ROW ROOT PORT METRO SESSION DEVICE_SUBSTRING` removes prior catalog connections using their normal command, pairs the fresh backend through native Linking, chooses dark theme and disables Working shelf, applies the fixed app clock and retains live A/B state subscriptions. The final version waits for connection-removal completion; the initial unawaited version briefly left an extra environment label on Android and was rejected before recording. Android app state was reset on the leased device before its final head capture to obtain a single clean connection.

Before A02/A03 recording, open B and wait for its complete notebook response, dispatch A with the driver's `stream` action (real Codex gpt-6.1-sol, low reasoning), open A and visually confirm rendered assistant text, then open B again and wait for its response to render. `warm-hold.py` mounts the real live thread state, thread, quiet-thread and visible-turn-item atoms for both threads. This extra preload retains zero-TTL data; it neither replaces snapshots nor changes the selector or UI source. Even with warm-open and retained live subscriptions, the native feed remount still showed transient blank panes. Those videos are explicitly rejected as sufficient proof. Do not silently promote them.

For A02 use iPad landscape 1376x1032pt. Home's native primary column stays visible, with A near the bottom beside B selected. `native-path.js` inspects the real mounted NativePrimaryColumnContext. `capture-run.py` taps A, samples its live projection twice across eight seconds, taps B, renames visible A by RPC, stops it and holds the terminal state. For A03 use Pixel Tablet natural landscape 1280x800dp; scroll the sidebar to the bottom before recording so A is visible, then the driver scrolls up/back, renames A, selects A, samples growth, returns to B and stops. Both recordings begin connected with B rendered.

For A04, return normally from B to Chats. Long-press B's actual row; choose `Arrange threads…`; swipe (200,730) to (200,240) so Garden notes 09..02, B, A, New thread and Settled (0) are visible. Keep this same sheet mounted throughout. Start A's real stream by RPC; `capture-run.py` renames A and samples later projection growth, verifies threadArrangementOpenAtom is true before and after the interval, and stops. The arrangement sheet has no visible transcript/tick counter and no Done label; server stop and the public shell/projection establish the stopped end state. Do not claim the sheet displays Done.

Native deep-linking can leave an AgentDeviceRunner breadcrumb in iOS chrome. Before capture, Home to SpringBoard, screenshot to locate the actual app icon, and tap that icon; this returns to the mounted sheet/detail without the breadcrumb. Do not use snapshot on SpringBoard with the app still session-bound, because it reactivates the app. The final iPhone icon was at (62,323), iPad at (788,305); these are observed coordinates, not portable assumptions.

The pool exposes temporary screenshot normalization only. Its iOS charged/100 normalization rendered an animated green charging battery, and repeated RGB comparisons correctly rejected it. No persistent status-bar/time/battery override or host simctl execution is exposed by agent-device 0.21.32. Final iOS captures use stable, natural non-charging chrome; native clock digits differ across sides and are reported. Android screenshot normalization works, using 9:41; natural clock remains in videos. Never edit the status-bar pixels.

`settled-capture.py` takes two full-device screenshots, with at least 500ms between them, converts both to RGB and counts every pixel with any nonzero channel delta. It writes each attempt and accepts only count zero. iPad screenshots use pixel-density 2, phone 3; Android native 2560x1600. It no longer uses RGBA getbbox. `diff-pair.py` computes full-surface absolute RGB differences, Diff0 (any channel >0) and DiffT8 (any channel >8), amplified 255x with saturation, and records counts. `sample-video.py` uses ffmpeg fps=2, builds full-surface contact sheets and separately decodes the last native frame, which sparse native recordings may otherwise omit from half-second samples. Contact sheets are inspection aids; authoritative images and recordings are unedited full surfaces. The native recordings are sparse variable-frame-rate exports and their media time does not equal driver wall time; use narrated media timestamps and raw driver completion times as separate clocks.

Scripts and evidence are scratch only. Stop tracked process groups with stop-process.py, close all named device leases and remove the before worktree. Leave the lead's head worktree intact. Do not post PR comments or edit the body.
