# A01 native capture scripts — round 4

AI-assisted; verified with the tests and tools listed above.

Run from the timestamped scratch directory after adjusting absolute paths. The public RPC script takes the synthetic display fixture model from `EVIDENCE_FIXTURE_MODEL`; the private fixture identifier is withheld. It does not appear in these native screenshots. The unused whole-runtime clock-freeze option from the inherited helper is omitted; no global Date freeze was executed. Only display-relative-time defaults and the display-minute snapshot were fixed.

## launch.py

```python
import os,sys,subprocess,json,secrets
from pathlib import Path
r=Path(__file__).parent
side=sys.argv[1]
checkout='/home/user/wt/pr-33042-'+side
kind=sys.argv[2] if len(sys.argv)>2 else 'dev'
env=os.environ.copy()
if kind=='dev':
 token=secrets.token_hex(32)
 (r/(side+'-token.private')).write_text(token)
 env.update(T3CODE_DEV_SEED='0',CLAUDE_CONFIG_DIR=str(r/side/'home/.claude'),CODEX_HOME=str(r/side/'home/.codex'),T3CODE_DEV_AUTH_TOKEN=token)
 cmd=['vp','run','dev','--host','0.0.0.0','--home-dir',str(r/side/'t3')]
else:
 env['REACT_NATIVE_PACKAGER_HOSTNAME']='fs-52.tail131df4.ts.net'
 cmd=['vp','run','dev:client','--port','8087']
 checkout+='/apps/mobile'
log=open(r/(side+'-'+kind+'.log'),'w')
p=subprocess.Popen(cmd,cwd=checkout,env=env,stdout=log,stderr=subprocess.STDOUT,start_new_session=True)
(r/(side+'-'+kind+'-pid.json')).write_text(json.dumps({'pid':p.pid,'cwd':checkout,'cmd':cmd}))
print(p.pid)

```

## stop-owned.py

```python
from pathlib import Path
import os,json,signal,sys
r=Path(__file__).parent
side,kind=sys.argv[1:3]
d=json.loads((r/(side+'-'+kind+'-pid.json')).read_text())
try:os.killpg(d['pid'],signal.SIGTERM);print('Stopped owned process group',d['pid'],d['cmd'])
except ProcessLookupError:print('Already stopped',d['pid'])

```

## seed.py

```python
import json,pathlib,subprocess
root=pathlib.Path('/tmp/lead-33042-r1/evidence-capture-4/run-20261110T1051Z')
workspace=root/'synthetic-project'
workspace.mkdir(exist_ok=True)
subprocess.run(['git','init','-b','master',str(workspace)],check=True,capture_output=True)
(workspace/'README.md').write_text('Synthetic import evidence fixture.\n')
for side in ['before','after']:
 home=root/side/'home'
 transcripts=home/'.claude/projects/-synthetic'
 transcripts.mkdir(parents=True,exist_ok=True)
 for i,title in enumerate(['Imported fixture — waiting status','Second synthetic imported thread']):
  sid=f'11111111-1111-4111-8111-{i+1:012d}'
  records=[{'type':'user','uuid':f'user-{i}','sessionId':sid,'cwd':str(workspace),'timestamp':'2026-11-10T06:00:00.000Z','message':{'role':'user','content':'SYNTHETIC_IMPORT_TOKEN review a tiny fixture'}},{'type':'assistant','uuid':f'assistant-{i}','sessionId':sid,'cwd':str(workspace),'timestamp':'2026-11-10T06:00:10.000Z','message':{'role':'assistant','content':'Synthetic completed answer. The fixture is ready for review.'}},{'type':'custom-title','sessionId':sid,'customTitle':title,'timestamp':'2026-11-10T06:00:10.000Z'}]
  (transcripts/(sid+'.jsonl')).write_text(''.join(json.dumps(x)+'\n' for x in records))
print(workspace)

```

## rpc.mjs

```javascript
import fs from 'node:fs';
import {createHash,randomUUID} from 'node:crypto';
import {createRequire} from 'node:module';
const require=createRequire('/home/user/wt/pr-33042-after/apps/server/package.json');
const WebSocket=require('/home/user/wt/pr-33042-after/node_modules/.pnpm/ws@8.22.0_bufferutil@4.1.0_utf-8-validate@6.0.6/node_modules/ws');
const root=new URL('.',import.meta.url).pathname;
const side=process.argv[2],action=process.argv[3]||'setup';
const port=side==='after'?14054:15443;
const token=fs.readFileSync(root+side+'-token.private','utf8');
const cookieName='t3_dev_session_'+createHash('sha256').update(token).digest('hex');
const ws=new WebSocket(`ws://127.0.0.1:${port}/ws?orchestrationProtocol=2`,{headers:{Cookie:`${cookieName}=${token}`}});
await new Promise((r,j)=>{ws.on('open',r);ws.on('error',j)});
let id=0;
const pending=new Map();
ws.on('message',data=>{let frames=JSON.parse(data);for(const m of Array.isArray(frames)?frames:[frames]){const p=pending.get(String(m.requestId));if(p){if(m._tag==='Exit'){pending.delete(String(m.requestId));m.exit._tag==='Success'?p.resolve(m.exit.value):p.reject(Error(JSON.stringify(m.exit)))}else if(m._tag==='Chunk'){p.resolve(m.values);pending.delete(String(m.requestId));}}}});
function rpc(tag,payload){return new Promise((resolve,reject)=>{const requestId=String(++id);pending.set(requestId,{resolve,reject});ws.send(JSON.stringify({_tag:'Request',id:requestId,tag,payload,headers:[]}));})}
async function command(type,threadId,extra={}){return rpc('orchestration.dispatchCommand',{type,commandId:randomUUID(),threadId,...extra})}
const primary='import:claudeAgent:11111111-1111-4111-8111-000000000001';
try{
if(action==='setup'){
 console.log('scan',JSON.stringify(await rpc('agentSessions.scan',{})));
 console.log('create',JSON.stringify(await rpc('projects.mutate',{type:'project.create',commandId:randomUUID(),projectId:'evidence-project',title:'Synthetic import evidence',workspaceRoot:root+'synthetic-project'})));
 console.log('import',JSON.stringify(await rpc('agentSessions.import',{projectId:'evidence-project',expectedWorkspaceRoot:root+'synthetic-project'})));
}else if(action==='unsettle'||action==='settle')console.log(JSON.stringify(await command('thread.'+action,primary,action==='unsettle'?{reason:'user'}:{})));
else if(action==='clean'){
 const frames=await rpc('orchestration.subscribeShell',{});
 const snapshot=frames.find(x=>x.snapshot?.threads?.length).snapshot;
 for(const t of snapshot.threads){if(!t.id.startsWith('import:'))await command('thread.delete',t.id);else await command('thread.model-selection.set',t.id,{modelSelection:{instanceId:'claudeAgent',model:process.env.EVIDENCE_FIXTURE_MODEL}})}
 for(const p of snapshot.projects)if(p.id!=='evidence-project')await rpc('projects.mutate',{type:'project.delete',commandId:randomUUID(),projectId:p.id});
 console.log('Only two synthetic imported threads remain.');
}else if(action==='shell') console.log(JSON.stringify(await rpc('orchestration.subscribeShell',{})));
else console.log(JSON.stringify(await rpc(action,JSON.parse(process.argv[4]||'{}'))));
}finally{ws.close()}

```

## capture.py

```python
from pathlib import Path
import subprocess,time,sys,json,datetime
r=Path(__file__).parent
name=sys.argv[1]
ledger=[]
for take in ['a','b']:
 path=r/(name+'-'+take+'.png')
 started=datetime.datetime.now(datetime.timezone.utc).isoformat()
 p=subprocess.run(['agent-device','screenshot',str(path),'--pixel-density','3','--normalize-status-bar','--session','pr33042-native-r4'],cwd='/home/user/wt/pr-33042-after',capture_output=True,text=True)
 ledger.append({'file':path.name,'started':started,'completed':datetime.datetime.now(datetime.timezone.utc).isoformat(),'code':p.returncode,'stdout':p.stdout,'stderr':p.stderr})
 print(p.stdout,p.stderr)
 if p.returncode:break
 if take=='a':time.sleep(.5)
(r/(name+'-capture.json')).write_text(json.dumps(ledger,indent=2))

```

## record-flow.py

```python
from pathlib import Path
import sys,subprocess,datetime,time,json
r=Path(__file__).parent
side=sys.argv[1]
name=sys.argv[2] if len(sys.argv)>2 else side+'-A01-native'
ledger=[]
def call(args):
 start=datetime.datetime.now(datetime.timezone.utc).isoformat()
 p=subprocess.run(['agent-device',*args,'--session','pr33042-native-r4'],cwd='/home/user/wt/pr-33042-after',text=True,capture_output=True)
 ledger.append(dict(args=args,start=start,end=datetime.datetime.now(datetime.timezone.utc).isoformat(),code=p.returncode,stdout=p.stdout,stderr=p.stderr))
 (r/(name+'-flow-ledger.json')).write_text(json.dumps(ledger,indent=2))
 print(p.stdout,p.stderr,flush=True)
 if p.returncode:raise RuntimeError(p.stderr)
 return p.stdout
call(['record','start',str(r/(name+'.mp4')),'--scope','device','--hide-touches'])
time.sleep(2)
try:
 call(['longpress','label="Imported fixture — waiting status"','--settle'])
 time.sleep(1)
 call(['press','label="Un-settle"','--settle'])
 if side=='before':
  snapshot=call(['snapshot','-i'])
  if '[cell] "Imported fixture — waiting status"' not in snapshot:call(['press','label="1 working thread"','--settle'])
 time.sleep(4)
finally:
 call(['record','stop'])

```

## native-controls.mjs

```javascript
import fs from 'node:fs';
import {createRequire} from 'node:module';
const require=createRequire('/home/user/wt/pr-33042-after/apps/server/package.json');
const WebSocket=require('/home/user/wt/pr-33042-after/node_modules/.pnpm/ws@8.22.0_bufferutil@4.1.0_utf-8-validate@6.0.6/node_modules/ws');
const action=process.argv[2]||'info';
const expression=fs.existsSync(action)?fs.readFileSync(action,'utf8'):action==='info'?`({now:Date.now(),pixelRatio:globalThis.devicePixelRatio})`:action;
const targets=await(await fetch('http://127.0.0.1:8087/json/list')).json();
const t=targets.find(t=>t.appId==='com.t3tools.t3code.dev');
if(!t)throw Error('No native app inspector connected');
const ws=new WebSocket(t.webSocketDebuggerUrl,{origin:"http://fs-52.tail131df4.ts.net:8087"});await new Promise((r,j)=>{ws.onopen=r;ws.onerror=e=>j(Error(e.message))});
ws.send(JSON.stringify({id:1,method:'Runtime.evaluate',params:{expression,returnByValue:true,awaitPromise:true}}));
await new Promise((r,j)=>{ws.onmessage=e=>{const v=JSON.parse(e.data);if(v.id===1){console.log(JSON.stringify(v.result));r()}};setTimeout(()=>j(Error('inspector timeout')),10000).unref()});ws.close();

```

## capture-controls.js

```javascript
(()=>{
 const modules=globalThis.__r.getModules();
 const load=suffix=>{const entry=[...modules.entries()].find(([,v])=>v.verboseName?.endsWith(suffix));if(!entry)throw Error(suffix);return globalThis.__r(entry[0]);};
 const clock=load('packages/client-runtime/src/clock.ts');
 clock.minuteTick.getSnapshot=()=> '2026-11-10T12:00';
 const time=load('src/lib/time.ts');
 if(!globalThis.evidenceRelativeTime)globalThis.evidenceRelativeTime=time.relativeTime;
 time.relativeTime=(input,nowMs=Date.parse('2026-11-10T12:00:00Z'))=>globalThis.evidenceRelativeTime(input,nowMs);
 const dimensions=load('react-native/Libraries/Utilities/Dimensions.js').default.get('window');
 const platform=load('react-native/Libraries/Utilities/Platform.ios.js').default;
 return {displayClock:'2026-11-10T12:00',dimensions,os:platform.Version,constants:platform.constants};
})()

```

## install-display-clock.py

```python
from pathlib import Path
import sys
r=Path(__file__).parent
for side,checkout in [('before','/home/user/wt/pr-33042-before'),('after','/home/user/wt/pr-33042-after')]:
 p=Path(checkout)/'apps/mobile/src/lib/time.ts'
 backup=r/(side+'-time.ts.original.private')
 if sys.argv[1]=='install':
  if not backup.exists():backup.write_text(p.read_text())
  p.write_text(backup.read_text().replace('nowMs: number = Date.now()',"nowMs: number = Date.parse('2026-11-10T12:00:00.000Z')"))
 else:p.write_text(backup.read_text())

```

## remove-connections.js

```javascript
(async()=>{
 const modules=[...__r.getModules()];
 const load=suffix=>__r(modules.find(([,v])=>v.verboseName?.endsWith(suffix))[0]);
 const registry=load('src/state/atom-registry.ts').appAtomRegistry;
 const catalog=load('src/connection/catalog.ts').environmentCatalog;
 const ids=[...registry.get(catalog.catalogValueAtom).entries.keys()];
 for(const id of ids){const exit=await catalog.remove.run(registry,id);if(exit._tag==='Failure')throw Error('Connection removal failed')}
 return {removedCount:ids.length};
})()

```

## review-video.py

```python
from pathlib import Path
from PIL import Image,ImageDraw
import subprocess,json,sys,math
r=Path(__file__).parent
name=sys.argv[1]
video=r/(name+'.mp4')
meta=json.loads(subprocess.check_output(['ffprobe','-v','quiet','-show_format','-show_streams','-of','json',str(video)]))
duration=float(meta['format']['duration'])
folder=r/(name+'-review');folder.mkdir(exist_ok=True)
frames=[]
for i,t in enumerate(range(math.ceil(duration))):
 dest=folder/f'{i:03d}.png'
 subprocess.run(['ffmpeg','-v','error','-ss',str(t),'-i',str(video),'-frames:v','1','-y',str(dest)],check=True)
 if dest.exists():frames.append((t,dest))
w,h=201,437
sheet=Image.new('RGB',(w*6,(h+25)*math.ceil(len(frames)/6)), '#222222');d=ImageDraw.Draw(sheet)
for i,(t,p) in enumerate(frames):
 im=Image.open(p);im.thumbnail((w,h));x=(i%6)*w;y=(i//6)*(h+25);sheet.paste(im,(x,y+25));d.text((x+5,y+5),f'{t:02d}s',fill='white')
sheet.save(r/(name+'-review.png'))
(r/(name+'-video-meta.json')).write_text(json.dumps(meta,indent=2))
print('duration',duration,'frames',len(frames))

```

## normalize-stills.py

```python
from pathlib import Path
from PIL import Image
import sys
r=Path(__file__).parent
bar=Image.open(r/'before-start-take1-a.png').convert('RGB').crop((0,0,1206,180))
for name in sys.argv[1:]:
 im=Image.open(r/name).convert('RGB')
 assert im.size==(1206,2622)
 im.paste(bar,(0,0))
 im.save(r/(Path(name).stem+'-public.png'))

```

## normalize-video.py

```python
from pathlib import Path
from PIL import Image
import subprocess
r=Path(__file__).parent
bar=r/'video-status-bar.png'
Image.open(r/'before-start-take1-a.png').convert('RGB').crop((0,0,1206,180)).save(bar)
for side in ['before','after']:
 subprocess.run(['ffmpeg','-v','error','-i',str(r/(side+'-unsettle-final.mp4')),'-loop','1','-framerate','30','-i',str(bar),'-filter_complex','[0:v][1:v]overlay=0:0:shortest=1,fps=30','-c:v','libx264','-preset','fast','-crf','18','-pix_fmt','yuv420p','-an','-y',str(r/(side+'-unsettle-public.mp4'))],check=True)

```

## measure.py

```python
from pathlib import Path
from PIL import Image
import numpy as np,json,sys
r=Path(__file__).parent
name,a,b=sys.argv[1:]
x=np.asarray(Image.open(r/a).convert('RGB')).astype(np.int16)
y=np.asarray(Image.open(r/b).convert('RGB')).astype(np.int16)
d=np.abs(x-y).max(axis=2)
report={}
for t in [0,16]:
 m=d>t;ys,xs=np.where(m)
 bbox=None if not m.any() else [int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)]
 im=np.zeros((*m.shape,3),dtype=np.uint8);im[m]=[255,0,255]
 Image.fromarray(im).save(r/(name+('-diff0.png' if t==0 else '-diffT16.png')))
 report[str(t)]={'pixels':int(m.sum()),'bbox':bbox,'outsideStatusBarPixels':int(m[180:].sum()),'belowRowsPixels':int(m[900:].sum())}
(r/(name+'-counts.json')).write_text(json.dumps(report,indent=2));print(name,json.dumps(report))

```
