1. 에뮬레이터의 구조와 초기 상태
Game Boy는 CPU, PPU(Picture Processing Unit), 8KB의 메모리 등을 갖춘 8비트 휴대용 게임기입니다. 하드웨어의 모든 기능이 메모리 맵(Memory Mapped I/O) 방식으로 제어되는데, 초기 Waterfell 에뮬레이터는 CPU와 기본적인 인터럽트만 구현된 상태였습니다. 특히 오디오(APU)는 구현되지 않았고, 화면 스크롤이나 스프라이트 렌더링에 버그가 많아 상용 게임을 원활하게 플레이하기에는 역부족이었습니다.
2. AI(Claude Code)를 활용한 지식 주입과 오디오 구현
AI가 에뮬레이터의 복잡한 동작을 이해하도록 하기 위해, 발표자는 ‘Pandoc’ 가이드와 같은 Game Boy 하드웨어 명세서를 마크다운 형식으로 변환하여 Claude에게 제공했습니다. 이를 통해 AI는 오디오 시스템의 4개 채널, 믹싱, 샘플 생성 로직을 파악할 수 있었습니다. Ruby와 C 라이브러리인 SDL을 연결하기 위해 ffi 젬(Gem)을 사용하였으며, AI의 도움으로 복잡한 오디오 처리 유닛(APU)을 성공적으로 구현했습니다.
3. 디버깅 및 테스트 자동화
AI는 소리를 직접 들을 수 없기 때문에 오디오 버그를 수정하는 데 한계가 있었습니다. 이를 극복하기 위해 발표자는 특정 메모리 영역과 레지스터 상태를 덤프하는 디버그 도구를 구축했습니다. 또한, 하드웨어의 정확한 동작을 검증하기 위한 ‘Test ROM’들을 실행하고 그 결과를 AI에게 피드백으로 제공했습니다. 이 과정을 통해 인터럽트 시스템의 오류, 타이머 버그, 그리고 화면 스크롤이 멈추는 PPU 문제를 해결할 수 있었습니다.
4. Ruby 성능 최적화와 JIT의 활용
Ruby로 작성된 에뮬레이터의 가장 큰 숙제는 성능이었습니다. 초기에는 프레임 레이트가 매우 낮았으나, 다음과 같은 최적화를 진행했습니다:
- StackProf 활용: stackprof를 통해 병목 지점을 파악하고 불필요한 픽셀 계산을 제거했습니다.
- GC(Garbage Collection) 부하 감소: 루프 내에서 새로운 객체 생성을 최소화하여 GC 발생 빈도를 줄였습니다. 과거 Aaron Patterson과 같은 코어 커미터의 도움으로도 해결하기 힘들었던 객체 생성 문제를 AI와 함께 해결했습니다.
- 입력 폴링 최적화: 매 사이클마다 수행하던 입력 확인을 일정 주기마다 수행하도록 변경했습니다.
- YJIT 활성화: Ruby의 JIT 컴파일러를 사용하여 최종적으로 60 FPS(초당 프레임 수)를 달성했습니다.
5. Game Boy Color로의 확장과 ROM 뱅킹
마지막 단계로 AI에게 Game Boy Color(GBC)의 사양을 학습시켜 약 1시간 만에 GBC 지원 기능을 추가했습니다. 또한 Pokémon Yellow와 같은 대용량 게임에서 사용하는 ROM 뱅킹(ROM Banking) 기술의 복잡성을 해결했습니다. 16비트 주소 버스의 한계를 넘기 위해 여러 개의 메모리 칩을 전환하는 로직을 AI가 정확히 분석하여 구현함으로써, 대작 게임들도 크래시 없이 안정적으로 구동할 수 있게 되었습니다.