벡터 직결 10ms 파이프라인 — P0-2/3/4 ObjC 수정 완료
개요
2026-07-12-벡터직결-계약.md(SSOT) 부록2 "P0 수정" 중 담당C(ObjC, native/crowny-browser.m
단독 소유) 몫인 P0-2(크래시 후 재기동)·P0-3(종료 신뢰성+VEC_MISMATCH 게이트)·
P0-4(CROWNY_AUTOCLICK 실좌표 오프셋 버그, 적대검증 11/11 미스)를 구현·실측 검증했다.
P0-1(스크롤백 꼬리파싱, 한선씨)은 별도 문서(2026-07-12-앱상주서버-P0-1-스크롤백-꼬리파싱.md).
무엇을 했는지
P0-2 — 크래시 후 1회 재기동
appSrvStart()에NSTask.terminationHandler등록: 자식 종료 시 메인큐에서
gAppSrvTask=nil; gAppSrvPort=0(신원확인 gAppSrvTask==task 후 정리, 재기동 레이스 방지).
appSrvStart()진입부에isRunning가드 추가(gAppSrvTask가 죽은 채 남아있는
- 신규
appSrvRestartIfDown():gAppSrvTask==nil && gAppSrvPort<=0이면appSrvStart()
gAppSrvDownUntil 2s 쿨다운 재사용).
sendVectorEvent:arg:진입부에서gAppSrvPort<=0이면appSrvRestartIfDown()선호출.- 실측 중 발견한 추가 결함(계약 원문엔 없던 것):
renderAndReload의 벡터 패스트패스
cbIsVectorService && gAppSrvPort>0 로, 크래시 직후(포트=0) 캐럿블링크가
0.5s 마다 부르는 "렌더" 이벤트가 sendVectorEvent 자체를 호출하지 않고 레거시
스폰 경로로 새서 재기동이 "다음 클릭/키 입력이 있어야만" 트리거되는 사각지대였다.
가드를 cbIsVectorService(self.service) 단독으로 완화(내부 재기동 판단은
sendVectorEvent 에 위임) → 사용자 조작 없이도 0.5s 이내 자동 재기동.P0-3 — 종료 신뢰성 + VEC_MISMATCH 게이트
appSrvStop(): SIGTERM(-terminate) 후 최대 300ms(20ms×15) 폴링해도 살아있으면
kill(pid, SIGKILL) 직접 전송(고아 crownyc 방지).
- 신규
cbHeadlessTerminate():[NSApp terminate:nil]직전에 5초 워치독(백그라운드
dispatch_after)을 걸어 표준 종료가 지연되면 appSrvStop()→_exit(0) 강제.
AUTOCLICK/AUTOSEQ/AUTONAV 5개 종료 지점([NSApp terminate:nil] 전부) 교체.
- VEC_MISMATCH 게이트: 전역 카운터
gCBVecRTTCount(drawRect 의 CB_VEC_RTT 로그 지점에서
gAutoSeqVecExpected/gAutoSeqVecStartCount. CROWNY_AUTOSEQ 시퀀스 시작 시
click:/key:/hit: 스텝 수를 기대치로 스냅샷, 완료 시 실측 CB_VEC_RTT 증가분과
비교해 불일치면 VEC_MISMATCH 경고 로그(일치 시 VEC_MATCH).P0-4 — CROWNY_AUTOCLICK 실좌표 오프셋 버그
- 원인: AUTOCLICK/AUTOSEQ 의
click:X,Y처리가t.appView.frame.origin + (X,Y)로
frame.origin 은 그 superview(_webContainer) 기준일 뿐
윈도우 기준이 아니고(탭바/네비바/사이드바 오프셋 미반영), 캔버스(top-down, HIT 좌표계)
↔ 뷰-로컬 스케일 변환도 아예 없어(레티나 CW/CH ≠ bounds 일 때 1:1 로 잘못 가정)
실좌표 클릭이 구조적으로 항상 빗나갔다.
- 수정: 신규
- (NSPoint)windowPointForTopDownX:(int)x y:(int)y— mouseDown(:2514 부근)의
cx=round(p.x*sx), cy=round((bh-p.y)*sy) 스케일식을 정확히 역산(isx=bw/rw, isy=bh/rh)해
뷰-로컬 좌표를 구한 뒤 [self convertPoint:local toView:nil] 로 윈도우 좌표 변환
(사이드바·탭바·네비바 오프셋을 뷰 계층이 자동으로 흡수 — 수동 프레임 산수 완전 제거).
AUTOCLICK 블록과 runAutoSeq 의 click: 스텝 2곳 모두 이 메서드로 교체.검증 (실행+출력 근거)
- ①빌드:
-fsyntax-only2구성(-DCROWNY_VM_WEBKIT -DCROWNY_HYBRID/
-DCROWNY_PRODUCT_CROWNY) 모두 exit0, 경고는 기존 미사용 함수 2/6건뿐(내 변경과
무관, 라인번호 확인함) — 신규 경고 0. 실링크 빌드(Makefile 동일 플래그, 프레임워크
전부 포함, 산출물은 스크래치패드 전용, 라이브 미배치) 2구성 모두 exit0.
- ②재기동: 스크래치 빌드를 별도 인스턴스(
CROWNY_AUTOAPP=계산기)로 띄우고
crownyc run /tmp/앱상주서버.toau(PPID=테스트앱, 라이브 CrownyAI.app 아님) 를
kill -9. 로그 순서 확인:
앱상주서버 종료 감지(status=9) — 다음 벡터 이벤트에서 재기동 시도 →
(다음 0.5s 캐럿 렌더 틱에서) 앱상주서버 다운 감지 — 1회 재기동 시도 →
앱상주서버 재기동 성공 port=9802. 이후 [CB_VEC_RTT] service=계산기 event=렌더
로그가 정상 재개(새 포트로 정상 왕복). renderAndReload 가드를 고치기 전에는
이 사이클이 전혀 트리거되지 않았음을 먼저 실측으로 확인(회귀 재현 후 수정 확인).
- ③종료: `CROWNY_AUTOAPP=계산기 CROWNY_AUTOSEQ="click:346,269;key:5;hit:등호"
crownyc run /tmp/앱상주서버.toau 잔존 0(ps 확인, 매회). 3회 모두
event=액션(click)·event=KEY(key)·event=액션(hit) 3개 CB_VEC_RTT 로그 정상 발생.
- ④실좌표: 계산기 상주서버(스크래치 컴파일)에 직접
벡터|계산기|렌더|1280|742를
숫자:7 = x255 y220 w183 h99, 이 캔버스는 뷰 실제 크기를
그대로 반영 — 800×600 고정이 아니었음, 초기 가정 오류를 발견해 정정). 중심점
(346,269)로 CROWNY_AUTOCLICK 실행 → 로그 `[CB_VEC_RTT] service=계산기 event=액션
ms=3.241` 발생(적대검증이 11/11 미스했던 바로 그 시나리오가 이제 성공). 계산기 화면
스크린샷에서도 "7"이 표시됨으로 육안 확인.
- ⑤회귀:
--selftest-inputv22구성 모두SELFTEST_INPUTV2 19/19PASS, 종료 후
- 디버깅 경위(투명성): 처음 800×600 기준으로 계산한 좌표로 클릭했을 때 미스가
char[256] 필드 바이트덤프까지 넣어 파싱/구조체 복사/포인터 동일성을
전수 확인했으나 파싱·저장 전부 정상이었다. 원인은 코드가 아니라 테스트가 잘못된
기준(CW=800,CH=600)으로 타깃 좌표를 계산한 것 — 실제 앱은 뷰 실제 픽셀 크기(예:
1280×742)를 CW/CH 로 보내고 계산기 레이아웃이 그 크기에 맞춰 스케일된다. 이 오해를
correcting 한 뒤(346,269) 정상 매치 확인. 디버그용 임시 NSLog(CB_DEBUG_P04 다수)는
전부 제거하고 최종 빌드로 재검증 완료.관련 파일
/Users/ef/CrownyBrowser/native/crowny-browser.m(단독 수정, P0-2/3/4 +renderAndReload
- SSOT:
2026-07-12-벡터직결-계약.md(스크래치패드), 선행 문서:
2026-07-12-벡터직결-B-ObjC.md, 2026-07-12-벡터직결-C-통합검증.md
- 가드레일:
~/.claude/templates/한선씨-가드레일.md - 크라우니코드: 해당 없음 — 이번 작업은 순수 ObjC 플랫폼 글루(NSTask 생명주기,
잔여 이슈
- VEC_MISMATCH 크로스세션 노이즈:
/tmp/입력상태.psv의__포커스__필드가 여러
appHasFocusedInput() 이 YES 로 오판정되어 캐럿블링크(0.5s)가 계산기에서도
"렌더" 이벤트를 계속 쏜다. CROWNY_AUTOSEQ 실행 중 이 틱이 우연히 한 번 끼면
VEC_MISMATCH 게이트가 "기대 3 / 실측 4"로 경고한다(3회 재현, 매번 정확히 +1) — 이는
진짜 조용한 폴백이 아니라 무관한 캐럿 렌더 이벤트가 섞인 것으로, 각 스텝(click/key/hit)
자체는 로그로 1:1 확인됨(정상). 근본 수정은 appHasFocusedInput() 을 서비스별로
스코프하는 것(계산기는애초 텍스트 캐럿이 없으므로 서비스명 체크 추가가 정답에
가까움) — 이번 P0-2/3/4 범위 밖이라 다음 담당(8앱 이식/최종 E2E)에게 인계.
- P0-1(한선씨 스크롤백 꼬리파싱)은 담당A 소관, 본 문서에서 다루지 않음.
- 8앱(런처·메모·시계·할일·북마크·설정·파일·날씨) 서비스 모듈 이식은 별도 태스크
crowny-knowledge 태스크 #7)로 진행 중.