Lobsters

Writing a Demoscene Intro for the ZX Spectrum

ZX Spectrum 데모신 인트로 만들기

저자는 Z80 어셈블리로 128KB ZX Spectrum에서 작동하는 데모신 인트로를 만들며 화면 메모리 구조, 픽셀 스크롤, AY 음악 재생, 별무리 효과를 구현한 과정을 설명합니다. 최종 프로그램은 코드 10,287바이트이며, 메모리 제약과 프레임 처리 시간을 고려해 효과를 설계한 방법을 소개합니다.

AI 요약

저자는 어린 시절 ZX Spectrum에서 BASIC과 간단한 어셈블리로 흉내 내던 게임 인트로를 수십 년 뒤 Z80 어셈블리로 완성했습니다. 128KB ZX Spectrum에서 실행되는 결과물에는 스크롤 텍스트, 3채널 AY 음악, 움직이는 VU 미터와 시차를 둔 별무리가 들어갑니다. 코드는 10,287바이트이며, 카세트 이미지 전체는 19,552바이트입니다. 개발에는 Makefile, Pasmo 어셈블러, zmakebas 포크와 FUSE 에뮬레이터를 사용하고, 실제 기기인 Spectrum +3에서도 테스트했습니다.

화면 메모리와 텍스트 출력

Spectrum 화면 메모리는 주소 16,384번지부터 시작하며, 화면 데이터와 색상 속성 정보를 합쳐 6,912바이트를 차지합니다. 픽셀 데이터는 일반적인 선형 비트맵과 달리 세 구역으로 나뉘고, 각 구역 안에서도 행이 뒤섞여 있습니다. 주소에 1을 더해도 바로 다음 픽셀 행으로 가지 않고 8행 아래로 이동하는 구조입니다. 색상은 8×8 픽셀 칸마다 잉크와 종이 색 하나씩만 지정해 컬러 클래시 현상이 생깁니다.

이 구조는 텍스트 표시에 유리합니다. 레지스터 쌍 HL이 화면 주소를 가리킬 때 L을 늘리면 가로로 8픽셀, H를 늘리면 픽셀 행 하나만큼 이동합니다. 반면 임의의 X·Y 좌표를 화면 주소로 바꾸려면 별도 계산이 필요합니다. 저자는 화면 이미지를 먼저 메모리에 복사하고, ROM의 문자열 출력 루틴과 사용자 글꼴을 활용해 기본 화면을 구성했습니다. 이후 ROM 글꼴 주소를 바꿔 별도 폰트를 사용했습니다.

스크롤 텍스트는 한 문자씩 옮기는 대신 글꼴 데이터를 직접 읽어 한 줄씩, 한 픽셀씩 화면에 넣습니다. 한 문자 데이터를 8바이트 버퍼에 불러오고, Z80의 RL 명령으로 각 바이트를 회전시킵니다. 이때 밀려난 비트는 캐리 플래그를 거쳐 다음 화면 바이트로 전달됩니다. 한 행 전체를 이동하려면 32바이트를 차례로 처리합니다. 저자는 이 방식으로 프레임마다 256픽셀을 다시 그려 부드러운 스크롤을 구현했습니다.

AY 음악과 프레임 처리

Spectrum 128 모델의 AY-3-8912 칩은 3개 채널을 제공합니다. 저자는 Amiga 모듈 음악 ‘Milkman’을 Vortex Tracker II용으로 옮겼습니다. 원곡의 4개 채널을 3개로 줄이면서 음색 샘플과 오너먼트를 활용했습니다. 오너먼트는 음높이를 빠르게 바꿔 두 음을 동시에 내는 듯한 아르페지오 효과를 만들며, 제한된 채널로 화음을 구성하는 데 쓰입니다.

Vortex Tracker 재생 루틴은 주소 $C000에 고정된 바이너리로 불러왔습니다. 초기화 루틴을 호출한 뒤 매 프레임 재생 함수를 실행합니다. 별무리 효과를 처음에는 화면 행 전체를 이동하는 방식으로 구현했지만, 처리량이 너무 커 프레임 안에 끝나지 않았습니다. 음악 재생까지 느려져 아르페지오 음이 따로 들릴 정도였습니다.

별무리 최적화

저자는 별의 좌표를 매 프레임 계산하는 대신, Ruby 스크립트로 32개의 X·Y 위치를 미리 생성했습니다. Y 좌표가 겹치지 않도록 하고, 로고와 가이드 바가 있는 영역은 제외했습니다. 초기화 과정에서 좌표를 화면 주소와 비트 마스크로 바꾸면 매 프레임 좌표 변환을 생략할 수 있습니다. 별의 마스크를 회전하다 비트가 경계를 넘으면 다음 문자 칸으로 이동합니다.

텍스트와 별이 겹치는 문제는 화면 문자 칸마다 점유 여부를 기록해 해결했습니다. 텍스트 출력 루틴은 문자가 들어간 칸을 점유 상태로 표시합니다. 별은 해당 칸의 점유 값을 확인하고, 값이 있으면 그리지 않은 채 이동을 계속합니다. 이 방식으로 화면 전체를 다시 그리는 비용을 피하면서 텍스트 위에 별이 나타나지 않도록 했습니다. 별마다 이동 속도를 달리해 시차 효과도 구현했습니다.

메모리 구성과 마무리

프로그램 중반에는 배경 이미지와 음악, 재생 루틴을 코드 안에 함께 넣으면서 메모리가 부족해졌습니다. 특히 재생 루틴은 주소가 $C000으로 고정돼 있었고, 데이터를 복사해 배치하는 과정에서 저장 공간을 두 배로 사용했습니다. 저자는 프로그램을 여러 코드 블록으로 나눠 각 데이터가 필요한 메모리 주소에 직접 적재하도록 BASIC 테이프 로더를 구성했습니다. 로더는 메인 프로그램, 재생 루틴, 배경 화면을 차례로 불러옵니다.

저자는 어셈블리 작업에서 레지스터와 플래그, 비트 배열을 종이에 그려가며 동작을 확인했습니다. 화면 주소와 처리량을 직접 다뤄야 했던 경험은 오래된 하드웨어와 당시 데모신 제작자들의 작업을 이해하는 계기가 됐다고 말합니다. 완성된 데모는 .tap 파일로 내려받을 수 있고, 에뮬레이터와 웹 브라우저에서도 실행할 수 있습니다.

원문: Mark Round / 번역·요약: Trawling