🤖
5131 in / 2000 out / 7131 total tokens
한 커밋에 6700줄을 때려박았다. 경마 게임의 fall 애니메이션, Evolution 기믹, seed 동기화까지 전부 디버깅할 수 있는 도구 모음이다. 왜 이렇게 많이 만들었냐면—버그가 그만큼 많았기 때문이다.
가장 핵심은 horse-devtools.html이다. 2400줄짜리 단일 HTML 파일인데, 브라우저에서 바로 열어서 fall 프로필 파라미터를 실시간으로 튜닝할 수 있다. gravity, bounceDamping, squashStretch 같은 값들 슬라이더로 조절하면 즉시 애니메이션에 반영된다. UE5에서 애니메이션 커브 에디터 쓰던 감각으로 웹에서도 비슷한 게 필요했는데, 매번 코드 수정→저장→새로고침 하던 걸 devtools 하나로 해결했다.
fall-animation-test.html은 animateVehicleFallState 함수를 격리해서 테스트하는 페이지다. 경마 게임 전체를 로드할 필요 없이 fall 모션 로직만 따로 빼서 검증한다. 게임 프로그래머라면 이 감각 공감할 텐데—풀 파이프라인 돌리지 않고 특정 시스템만 단위 테스트하는 거, 서버 사이드면 단위 테스트로 끝나지만 클라이언트 렌더링은 눈으로 확인해야 하니까 결국 HTML 페이지를 만들 수밖에 없다.
finish-stun-compare.html은 탈것별로 finalGap 값을 동적 비교 시각화하는 도구다. 결승선에서 말들이 멈추는 타이밍이 종류마다 다른데, 이 차이를 그래프로 보여주는 거다. 숫자만 보면 150ms 차이가 별거 아닌 것 같지만, 실제 화면에서는 확실히 티가 난다.
Playwright 진단 스크립트가 7개 있는데, 이게 재미있다. diag-evolution-transparent.js는 Evolution 기믹 발동 후 말이 반투명해지는 버그를 잡으려고 만든 거다. 레이스 시작 후 2초, 5초, 8초, 12초마다 모든 .horse 요소의 computed opacity, filter, classList를 캡처해서 비교한다. diag-render-check.js는 더 깊이 들어가서 SVG의 각 path/rect/circle 요소의 fill, stroke, opacity까지 검사한다. 렌더링 버그는 재현이 어려운데, Playwright로 자동화하니까 CI에서도 돌릴 수 있겠다 싶었다.
diag-y-error 시리즈는 1~4까지 있다. 왜 4개냐. 에러 메시지가 그냥 "Y" 하나였기 때문이다. 스택트레이스 없이, 컨텍스트 없이, 그냥 Y. 첫 번째 스크립트에서 console 에러 수집해보고, 두 번째에서 unhandledrejection 잡아보고, 세 번째에서 핸들러 실행 여부 확인하고, 네 번째에서 socket 접근 방식을 찾았다. 4번 만에 원인을 찾았다—코드에서 문자열 처리하다가 undefined 값을 넘겨서 생긴 거였다. 삽질 기록 그 자체를 커밋에 남긴 셈이다.
test-150ms-gap.js와 test-seed-e2e.js는 실제 테스트 케이스다. 150ms gap은 말들 간 결승 도착 간격이 최소 150ms는 되도록 보장하는 로직이고, seed E2E는 같은 seed로 시뮬레이션하면 호스트/게스트 양쪽에서 동일한 결과가 나오는지 검증한다. 멀티플레이어 게임에서 결정론적 시뮬레이션은 생명줄이다. UE5에서 리플리케이션 처리할 때도 클라/서버 간 상태 동기화가 제일 까다로웠는데, 웹 게임이라고 다를 건 없다.
전체적으로 보면 이 커밋은 "버그 잡느라 만든 도구들"의 결과물이다. devtools는 계속 쓸 거고, 진단 스크립트는 버그 잡고 나면 버려도 되지만 남겨두면 회귀 테스트용으로 쓸 수 있다. 6700줄이 많아 보이지만, HTML이 대부분이고 실제 로직은 생각보다 간결하다.
버그 리포트를 위해 만든 도구가 결국 개발 파이프라인의 일부가 된다. 삽질은 배신하지 않는다.