# Round-2 rebuild driver

All paths below are synthetic scratch data. Source checkouts are not edited. Canonical launchers are generated by start-server.py. setup-launchers.py is a compatibility entry point to that script. freeze-browser.js is identical to capture-state.js; send-browser.js is the single-thread legacy entry point, superseded by send-sequential.js.

## Fresh server and deterministic wall clock

Install faketime/libfaketime. Run `python3 start-server.py before before-<timestamp>` from scratch, and likewise `after after-<timestamp>`. It launches each checkout's real `vp run dev --home-dir <new take>/home` with `T3CODE_DEV_SEED=0`. It creates the stock mock launcher and synthetic sample-project/README.md. Read actual ports from the private server.log. Pair T3 preview with the private startup link; never upload it or the log/home. No Vite origin overrides are used.

The whole process group (server + Vite) uses libfaketime, normal-speed advancing `@2026-11-02 21:00:00`, `FAKETIME_DONT_FAKE_MONOTONIC=1`, `FAKETIME_DONT_RESET=1`, `FAKETIME_NO_CACHE=1`. Vite compiled and served the app; Node timer and Effect/provider lifecycle completed. Slow x0.01 mode was rejected because it slows real timers. reset-clock.py writes an offset relative to real wall clock, preserving normal advancement, so user events start in a repeatable displayed minute.

The accepted first after take was after-20261102T2113; the independent second was after-rerun-20261102T2120. Both started on separate fresh databases. No database copy, fixture SQL, migration or live T3 data is used.

## Browser seed, strict ordering

Use the T3 preview tools. Evaluate browser-rpc.js after each full navigation. It sends ACKs for every streaming Chunk. Set `window.proofTake` to the take directory name; evaluate seed-browser.js while auth-required is absent. Await server.getConfig: both providers ready with discovered models (default, composer-2, etc.), not the initial default-only health placeholder.

Create `<take>/auth-required`. Reset clock with `python3 reset-clock.py <take> 10`. Evaluate send-sequential.js for proof-droid-thread; it subscribes before dispatching the one message and awaits the committed provider-session-open-failure item. Confirm failed run with getThreadProjection. Only THEN reset to second 40, set `window.proofNext=['proof-generic-thread','proof_generic','Generic ACP sign-in failure']`, and evaluate the same script. This fixes sidebar order: generic above Droid. The first accepted take used an explicit terminal projection check before creating Generic; the final driver uses the streaming milestone.

Open /<environmentId>/proof-droid-thread. Delete only the automatically created unused new thread, whose ID is in the initial post-pair URL, using thread.delete with onlyIfUnused:true. Preserve the two seeded failures. W1/W2 close the thread details panel using its Toggle button; W3 leaves it open. Conversation scroll is 0. Resetting the clock does not rewrite persisted event data; both W1 events display 9:00 PM.

## Still capture

Set theme with `(await import('/src/hooks/useTheme.ts')).setAppearanceMode(theme)` and preview_set_appearance; resize to 1440x900 or 390x900. Set window.proofTheme before capture-state.js. It freezes browser Date at window.proofClock (default 2026-11-02T21:00:00Z; use 21:05:00Z on both sides for W3/D1/P1), blurs the editor, awaits fonts and rejects pending resources; all accepted still pairs are pixel-identical; one narrow preparation snapshot reported one active document animation and the others zero. Preview has no reduced-motion emulation. Move pointer to neutral corner (1438,450 or 388,450) BEFORE EVERY pair so Dismiss error is not hovered.

capture-preview.js shows the exact T3-tool orchestration used (functions store holds sideTab, capture-state.js and stability). It saves two lossless full-viewport PNGs at least 550 ms apart; verify-stability.py requires Diff0=0. If a pair differs or contains syncing/loading state, await the actual rendered terminal state and repeat, retaining the take directory. Desktop PNG exports are 1280x800, proportionally scaled from full 1440x900 CSS; DPR remains 2. Narrow PNGs are 780x1800 from 390x900 CSS.

W1: dark/light at each width. At narrow widths also dismiss the same banner and capture supplementary full timeline; reload to restore the in-memory banner for the next state. W2: reload W1, 1440 dark; record, hold 3 seconds, click Dismiss error, move pointer away, hold 3 seconds, stop. Tool overhead changes the encoded click time: analyze-recordings.py checks 20 fps extracted frames. Before disappearance is in (4.40,4.45] seconds, after in (4.00,4.05]. Caption these observed times, not the requested click time. narrate-and-extract.sh adds narration delayed until 4.50 seconds, after both banners are absent. No Show full error button exists for W1's shorter baseline ID. W3: separate Generic thread, 1440 dark, panel open, proofClock 21:05. Before its final recapture the Generic catalog was refreshed identically on both sides with the marker temporarily absent, using server.refreshProviders {instanceId:proof_generic,refreshModels:true,cwd:sample-project,fresh:true}; restore the marker afterward. The healthy mock can expose either legacy Default or config-option Auto names for its default slug; the final W3 pair sets thread.model-selection.set to composer-2 on both already-failed threads, producing a stable identical Composer 2 selector without a new message/run. The captured failure itself was generated with default on both sides.

## D1 and P1 extra real-app fixtures

Run setup-extra-fixtures.py for the accepted before/after take names (adjust its two literals for a rebuild), evaluate seed-extra-browser.js and await healthy model discovery. seed-parent-browser.js creates a healthy hanging stock-mock parent. Reset to second 70, run seed-child-browser.js. This is a real app-owned delegated task targeting the failing Droid instance. Await the parent subagent record status failed, then open its childThreadId. Capture D1 1440 dark with details closed. Its longer baseline banner offers Show full error; this is a static collapsed-state capture, no expansion/navigation/timing claim. Reload both sides after setting thread.model-selection.set to default to avoid a transient Default/Auto discrepancy. Both accepted frames show Auto. Parent remains a synthetic hanging mock until server shutdown; no real model/account runs.

For P1, the model-write-shim.mjs forwards the stock mock's session/new with authentication disabled. After healthy probing create model-auth-required, reset to second 100, and evaluate seed-model-browser.js (composer-2). The shim then rejects only session/set_config_option with -32000 Authentication required. This is labelled a protocol harness through the real app, not the repository replay test. Capture proof-model-thread 1440 dark, details open. Minimal sanitized method/error-code transcripts are included. D1/P1 were created before final D1 capture, so both sidebars include Model write, Generic and Droid in that order. Archive parent/model only after web evidence, restoring the original mobile list.

## Native Android

See mobile-driver.md and mobile-clock.js. The real client is installed through mobile:install, served by before/after Metro, paired to the same respective isolated backend. No narrow web substitution. Only static content/list states are claimed. The native system clock is set manually to the same displayed minute on each side, and app Date is frozen independently through CDP. Settings and navigation are fixture setup, not claimed interaction evidence.

## Final products and cleanup

Run sanitize-transcript.py (strict allowlist), verify-stability.py, analyze-recordings.py, make-diffs.py and make-sheets.py. Diff0 is max RGB >0, magenta binary amplification; DiffT max RGB >16, amber. Inspect every accepted capture and diff. upload-evidence.py uploads an explicit public allowlist using ghdrop upload -m, saving exact stdout and upload-map.json; never archives the scratch tree. build-report.py writes the URL-backed evidence.md/index after uploads. Upload those too. Stop only recorded process groups, close preview/device session, remove only the detached before worktree; leave the lead's checkout and round-1 evidence untouched.

Resume the entire public upload and report/index publication with `python3 /tmp/lead-29548-r1/evidence-capture-2/finish-publication.py`. It skips already-uploaded identical content by SHA256, preserves ghdrop stdout verbatim, publishes evidence.md, then generates the URL-backed index without rewriting that evidence file.
