← 목록
기타 2026-07-12 9KB 읽기 9분

벡터 직결 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()
1회 재호출(무한루프 금지 — 실패 시 기존 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 블록과 runAutoSeqclick: 스텝 2곳 모두 이 메서드로 교체.

검증 (실행+출력 근거)

  • ①빌드: -fsyntax-only 2구성(-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:등호"
CROWNY_AUTOSHOT=...` 3회 실행 — 프로세스 자기종료 3/3(각 ~3초 내, 강제 kill 불요), 종료 후 crownyc run /tmp/앱상주서버.toau 잔존 0(ps 확인, 매회). 3회 모두 event=액션(click)·event=KEY(key)·event=액션(hit) 3개 CB_VEC_RTT 로그 정상 발생.
  • ④실좌표: 계산기 상주서버(스크래치 컴파일)에 직접 벡터|계산기|렌더|1280|742
질의해 실물 HIT 좌표 확보(숫자:7 = x255 y220 w183 h99, 이 캔버스는 뷰 실제 크기를 그대로 반영 — 800×600 고정이 아니었음, 초기 가정 오류를 발견해 정정). 중심점 (346,269)로 CROWNY_AUTOCLICK 실행 → 로그 `[CB_VEC_RTT] service=계산기 event=액션 ms=3.241` 발생(적대검증이 11/11 미스했던 바로 그 시나리오가 이제 성공). 계산기 화면 스크린샷에서도 "7"이 표시됨으로 육안 확인.
  • ⑤회귀: --selftest-inputv2 2구성 모두 SELFTEST_INPUTV2 19/19 PASS, 종료 후
프로세스/자식 잔존 0.
  • 디버깅 경위(투명성): 처음 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
게이트 보강 1건 추가)
  • SSOT: 2026-07-12-벡터직결-계약.md(스크래치패드), 선행 문서:
2026-07-12-벡터직결-B-ObjC.md, 2026-07-12-벡터직결-C-통합검증.md
  • 가드레일: ~/.claude/templates/한선씨-가드레일.md
  • 크라우니코드: 해당 없음 — 이번 작업은 순수 ObjC 플랫폼 글루(NSTask 생명주기,
NSEvent 좌표 합성, GCD 워치독)로 "기존 코드 직접수정" 예외에 해당, 신규 .한선 로직 없음(한선씨 코드는 P0-1 담당A 소관).

잔여 이슈

  • 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)로 진행 중.