크라우니 게임툴스 "레디" 출시 (gametools.crowny.org:9625)
개요
발주 디자인 핸드오프(/Users/ef/Downloads/design_handoff_ready/)를 정독해 카드게임·투표용 실시간 준비확인 도구 "레디"를 신규 구축·라이브했다.
최대 50명(기본 25)이 8.7" 갤럭시탭을 터치해 Ready를 알리고, 딜러가 전원 Ready를 확인한 뒤 게임을 시작한다.
gametools.crowny.org는 게임 도구가 계속 추가될 도메인이며, 도구별 경로(/ready)로 라우팅한다.
- 코드:
/Users/ef/crowny-gametools - 포트: 9625 (crowny-ports.sh 등록, gateway.yaml 반영)
- 공개: https://gametools.crowny.org 라이브 (와일드카드
*.crowny.org인증서로 커버 — SAN 확장 불필요)
무엇을 했나
1. 계약화 (SSOT 우선)
프로토타입(Ready.dc.html)은 "디자인 레퍼런스"이지 배포 코드가 아니라는 발주 명시에 따라,
먼저 SPEC.md로 API·화면·상태·토큰을 계약화한 뒤 그 계약으로 병렬 구현했다.
디자인 원본은 디자인이식.sh register로 물리 잠금(프로젝트명 크라우니게임툴스레디).2. 순수 한선씨 서버 (서버.한선, 14,769 큐브)
- 상태 SSOT = 전역 병렬배열 인메모리(룸코드·좌석수·phase·좌석비트·버전·heartbeat·바카라·갱신).
- 좌석 ready는 문자열 비트("0"/"1")로 관리 — 배열 생성 없이 부분()+연결로 갱신.
- 영속화는 변경 시에만
data/방.psvappend, 부팅 1회만 파싱(읽기 라우트에서 분리() 0). - 요청마다
메모리마커/맵힙마커복원 + 200요청마다문자열GC()(아래 결함 참조). - 라우트:
//ready/{room}/dealer/{room}/trial/{room}+/api/{room/new,state,seat,seats,deal,standby,beat,config}+ 정적(css/js/qr/png) +/health.
3. 실시간 동기화 = 250ms 폴링
WebSocket은 한선씨 VM 제약상 채택하지 않고 초경량 폴링으로 설계. 공개 도메인 실측 레이턴시 47~157ms → 발주 목표 <300ms 충족. 재접속·새로고침 시 서버 상태로 즉시 복원.4. 화면 4종 (디자인 원본 그대로)
랜딩(룸 생성/참가) · 참가자(전체화면 Ready 토글, 100ms 홀드/1000ms 취소, Wake Lock, 원형 확장 0.5s) · 딜러(그리드·테이블·카운트 3레이아웃 + 인원 스테퍼 + QR) · 시험운영(컴팩트 그리드 + 좌하단 바카라 PIP).5. 자체 구현으로 외부 의존 0
- QR 인코더(
web/qr.js): GF(256) 리드-솔로몬·8마스크 페널티 선택·BCH 포맷/버전정보까지 자체 구현.
VNDetectBarcodesRequest) 디코더로 왕복 검증 5종 전부 일치(URL·코드·100자·한글·v9).
- 바카라 엔진(
바카라.한선): 서드카드 룰 SSOT. 100회 배분 실측 전 항목 PASS.
6. 한선씨 동반본 (헌법 준수)
공통.한선(좌석·타원좌표·바카라파싱·진행률·대기목록·상태텍스트·토큰) ·
참가자/딜러/시험운영.한선(홀드판정·딜가능·오프라인표시·PIP) + 공통시험/화면시험/바카라시험.한선.
화면 판정 로직 44/44 PASS, HTML/JS와 판정 불일치 0.발견·수리한 결함 (전부 실측 재현 후 수리)
| # | 결함 | 영향 | 수리 |
|---|---|---|---|
| 1 | 좌석 인덱스 off-by-one — 서버가 0-based로 처리 | ?seat=3이 4번 좌석을 켬. 계약 위반 | 1-based 규약으로 통일(seat-1), heartbeat 경로도 동일 수정 |
| 2 | 문자열 핸들 누수 → exit43 — 8,000 요청에 480k 상한의 90% 도달 | 폴링 서버가 수 분 내 사망 | 부팅 후 문자열보호() + 200요청마다 문자열GC(). 재소크 20,000 요청 경고 0·크래시 0 |
| 3 | 시험운영 노룸 오버레이 상시 노출 — 인라인 display 중복 선언(뒤가 이김) | 시험운영 화면 전체가 가려짐 | 중복 제거. 타 페이지 동일 결함 없음 확인 |
| 4 | 카운트 뷰 " / N" 임의 축소(font-size:0.5em) | 원본 미이행 | 제거(부모 크기 상속) |
| 5 | 카운트 뷰 상단 정렬 — height:100%가 flex:1 부모(불확정 높이)에서 무너짐 | 원본은 패널 세로 중앙 | 패널을 컬럼 플렉스 + #countView{flex:1} |
| 6 | 시험운영 패널이 내용 높이로 축소 | 원본은 flex:1로 남은 높이 채움 | 패널 flex:1; padding:18px 복원 |
| 7 | 참가자 재접속 복원이 0.5s 애니메이션을 탐 | 발주 "즉시 복원" 위반, 복귀 중 빨강으로 보임 | 첫 서버상태 반영만 무애니메이션(강제 리플로우로 커밋 후 transition 복원) |
검증 실측
- 서버 종단: health·룸생성·좌석·상태·deal(미전원 거부/전원 허용)·standby·config 전부 PASS
- 재기동 복원: 프로세스 재시작 후 좌석·phase·바카라 결과까지 PSV에서 복원 확인
- 부하: 200 요청 1.64s(8.2ms/req) — 25대 × 4Hz = 100 req/s 여유
- 소크: 20,000 요청, 문자열 조기경고 0, 크래시 0, RSS 34MB
- 50좌석: 그리드 6행 래핑, 겹침·붕괴 없음
- 테이블 배치: 인접 호길이 편차비 1.058(균등), 각도 간격 9.5°~22.2°(의도적 불균등) → 원본의 "좌우 뭉침 방지" 요구 충족
- 이탈(OFFLINE): heartbeat 8초 미수신 좌석만 점선 아웃라인 +
OFFLINE표시 확인 - 4화면 콘솔 에러 0, 골격 게이트(doctype·charset·외부요청0) 4/4
- 디자인 게이트: 신규색 0 · 유실색 0(판정 옴, 점수 79). 신규치수 7건은 원본에 없던 랜딩·좌석선택 화면 —
이식노트.md에 근거 기록 - 공개 HTTPS 전 경로 200, 타 도메인 무영향(crowny.org·docs 200)
발주자 확인 대기 (원본 README Open Questions)
기본안으로 구현하고 이식노트.md·SPEC.md §4에 값을 모아 뒀다 — 회신 시 한 곳만 고치면 된다.
- 룸 플로우 → 4자리 코드 + 참가 URL + QR (코드 입력/QR 병행)
- 딜러 좌석 수동 토글 → 유지(오버라이드, 화면에 명시)
- 참가자 이탈 표시 → heartbeat 8초 + 점선 아웃라인
OFFLINE(신규 색 없음)
관련 파일
- 코드:
/Users/ef/crowny-gametools/(SPEC.md·README.md·이식노트.md동봉) - 상시구동:
org.crowny.gametools.plist(LaunchAgent, KeepAlive) - 디자인 원본(잠금):
/Users/ef/Downloads/design_handoff_ready/ - 학습 래칫: crownycode에 서버·바카라·문자열핸들회수 패턴 학습 완료
- 가드레일 갱신:
루트()정수 반환 · 장수서버 문자열 핸들 누수 2건 추가
육안 검증 (2026-07-29 전 화면 캡처 대조)
25명 룸(18 ready)·8명 시험운영 룸으로 실제 렌더를 캡처해 원본 의도와 대조했다.
- 랜딩 · 딜러 그리드/테이블/카운트 · 참가자 Standby/Ready · 시험운영+PIP · 딜러 다크테마 = 전부 원본 일치 확인
- QR 실물 검증: 딜러 화면 스크린샷에서 QR 영역을 잘라 macOS Vision으로 디코딩 →
http://127.0.0.1:9625/ready/NX3Y정확히 일치 - 다크테마는 스크래치패드 하네스(제품 파일 무수정)로
data-theme=dark강제해 캡처 — 신호색(빨강/초록) 공통 유지 확인 - 검증 편의 겸 실용으로
?layout=·?qr=1파라미터 추가(기본 동작 불변)
크라우니AI 귀속 (2026-07-30 사용자 지시)
crowny://gametools 를 크라우니AI 기본 앱으로 귀속. cad 선례를 전수 대조해 8지점 동수 배선(행만 추가하면 빈 탭 무동작이 되는 알려진 함정 회피).
| 지점 | 내용 |
|---|---|
| 시작페이지 타일 | cad 타일 뒤, 아이콘 ASCII R(한글 심볼 UTF-16LE 함정 회피), 라벨 게임툴스 |
| 북마크 시드 | v22 블록, 중복검사 후 insert (defaultBookmarks()는 의도적 공백이라 미접촉) |
| 주소 정규화 | crowny://gametools* + 별칭 게임툴스·gametools·레디·ready·크라우니게임툴스 |
| 탭 오픈 | caOpenGametoolsInActiveTab → https://gametools.crowny.org |
| 커맨드 라우팅 | 별칭 질의 → 위 함수 |
| 런처 카드 | src/앱목록.psv 1행(url: 규약). 런타임에 소스경로 직접 읽어 재빌드 불필요 |
| 액센트색 | 레디그린 rgb(20,160,95) = 서비스 자신의 Ready 신호색. crowny-browser.m·crowny-ai-engine.m 양쪽 동일값 |
make ai 에러 0(기존 경고만) → install-ai-app.sh 재서명 → open -a CrownyAI "crowny://gametools" 로 탭이 실제로 열리고 랜딩이 렌더되는 것을 육안 확인, 사이드바에 "크라우니 게임툴스" 등록 확인.
미완: 런처 "웹 서비스" 탭 카드 육안 확인 — cliclick 클릭이 타 세션 창 포커스를 교란해 중단(카드 자체는 PSV 파싱·제네릭 디스패치로 동작 경로 확인).잔여
- 실기기(갤럭시탭 8.7") 다중 접속 현장 시험 미실시 — 터치 홀드 100ms/1000ms 체감, Wake Lock 실동작, 25대 동시 폴링은 현장에서만 확정 가능.
- 테마 토글 버튼 클릭 자체(자동↔라이트↔다크 전환)는 Chrome 확장 미연결로 클릭 검증 미실시. 렌더는 양 테마 모두 확인.