Tự tay viết intro demoscene cho ZX Spectrum: Hành trình từ BASIC đến Z80 assembly
Một lập trình viên kỳ cựu kể lại quá trình hơn một năm tự viết intro demoscene chạy trên ZX Spectrum 128K, tái hiện phong cách cracktro thập niên 80 với scrolltext, nhạc AY ba kênh, VU meter và starfield parallax. Bài viết là cẩm nang kỹ thuật về Z80 assembly, bộ nhớ màn hình kỳ quặc của Spectrum và những thủ thuật tối ưu cực đoan.
Tự tay viết intro demoscene cho ZX Spectrum: Hành trình từ BASIC đến Z80 assembly
Có những dự án phụ khiến bạn vừa tự hào vừa muốn đập nát màn hình. Với Mark Round, đó là việc tự viết một intro demoscene chạy trên chiếc ZX Spectrum 128K — thứ mà ông mơ mộng từ tận thập niên 80 nhưng mãi mới hoàn thành được sau hơn một năm gõ từng byte assembly.
Kết quả là một đoạn intro 10.287 byte mã, hay 19.552 byte khi đóng gói thành file băng .tap, hội đủ mọi đặc sản của thời hoàng kim: scrolltext chạy mượt với font "leet" tự chế, nhạc AY ba kênh, VU meter nhún nhảy và cánh đồng sao parallax không thể thiếu. Tính ra tốc độ sản xuất code chỉ đạt khoảng 455 byte mỗi tuần.
Băng cassette — biểu tượng của kỷ nguyên nạp game 8-bit
Từ Amiga đến Spectrum: nỗi ám ảnh cracktro
Với một đứa trẻ lớn lên cùng máy tính 8-bit nạp băng cassette, lần đầu nhìn thấy Amiga giống như gặp vật thể từ hành tinh khác. Nhưng thứ khiến Mark mê mẩn hơn cả đồ họa và âm thanh là những đoạn cracktro — phần intro xuất hiện trước các game lậu.
Các nhóm crack phần mềm đã phá khóa bản quyền game trên đĩa mềm rồi gắn vào đó những tác phẩm nhỏ xíu: logo sắc lẹm (càng "metal" càng tốt), dòng chữ tự khoe khoang chạy trên nền hiệu ứng, cánh đồng sao parallax hoặc vật thể 3D xoay, kèm một scrolltext đầy lời chào hỏi đồng nghiệp ẩn danh và quảng cáo cho các BBS bất hợp pháp.
Với một đứa trẻ thập niên 80, đó có lẽ là thứ ngầu nhất trên đời.
Những cái tên như Skid Row, Anthrox hay Endless Piracy đã trở thành huyền thoại, và phong cách thiết kế của họ thì không bao giờ lỗi thời. Demoscene sau này lớn lên từ chính những đoạn intro đó, khi việc crack lùi dần vào quá khứ còn các lập trình viên, họa sĩ và nhạc sĩ thì đẩy phần cứng tới giới hạn tuyệt đối.
Cracktro kinh điển của Lemmings — cánh đồng sao, logo và scrolltext
Nhưng chính những đoạn cracktro "màn hình đơn" đời đầu mới là thứ ám ảnh Mark nhất, bởi chúng gợi lên một nền văn hóa mà ông chỉ có thể trải nghiệm từ bên ngoài. Đó là lý do ông từng dành vô số giờ gõ BASIC trên Spectrum, cố tái tạo chúng trong vô vọng.
Môi trường phát triển tối giản
Bộ công cụ của Mark khá đơn giản và giống hệt stack ông dùng cho TNFS site — một "website" kiểu BBS cổ điển chạy trên Spectrum:
- Makefile điều khiển quá trình build
- Pasmo làm assembler
- Bản fork của zmakebas để tạo file BASIC
- Trình giả lập FUSE để kiểm thử
- Nạp file
.taplên TNFS server nội bộ để chạy thử trên máy thật
Hai công cụ phụ trợ đóng vai trò then chốt. Thứ nhất là asm-editor — trình soạn thảo assembly chạy trên web, cho phép chạy thử code và quan sát thanh ghi thay đổi theo từng bước. Thứ hai, và có lẽ quan trọng nhất: một cuốn sổ tay, cây bút và hai chú chó Biscuit & Pebble. Phần lớn code Z80 chỉ thực sự sáng tỏ sau khi ông vẽ tay từng byte, từng bitmask và những chặng đi dạo buổi tối cùng lũ chó chính là lúc "thuyết trình cho vịt cao su".
Bộ nhớ màn hình: cơn ác mộng mang tên Spectrum
90% nỗi đau của dự án đến từ cách bố trí bộ nhớ màn hình cực kỳ quái dị của Spectrum. Thay vì bitmap tuyến tính như phần cứng hiện đại, màn hình chia làm ba phần, mỗi phần chứa các hàng xen kẽ nhau. Cứ cộng thêm 1 vào địa chỉ màn hình, bạn không nhảy xuống hàng kế tiếp mà nhảy xuống 8 hàng, cho tới khi chạm đáy phần rồi vòng lại hàng ngay dưới điểm xuất phát.
Đổi lại, cách sắp xếp này cho phép những thao tác "rẻ tiền" cực kỳ hiệu quả nếu bạn biết giữ địa chỉ trong cặp thanh ghi HL:
- Sang phải 8 pixel:
inc l - Sang trái 8 pixel:
dec l - Xuống 1 dòng pixel:
inc h - Lên 1 dòng pixel:
dec h - Xuống 1 hàng ký tự: cộng thêm 32 vào L
Thiết kế này hợp lý cho một cỗ máy chủ yếu hiển thị danh sách chương trình BASIC. Chỉ đến khi bạn viết routine demo hay sprite thì nó mới thực sự phát điên.
Scrolltext đầu tiên: phép thuật của lệnh RL
Hiệu ứng đầu tiên Mark muốn có là scrolltext. Amiga có sprite phần cứng, blitter và chip tùy chỉnh; Spectrum thì không có gì cả. Mọi thứ phải "đập thẳng vào kim loại" — dịch bit bằng tay và để CPU gánh hết.
Thủ thuật nằm ở lệnh rl (rotate left). Nó đẩy bit trong cờ carry vào bit phải nhất, còn bit bị đẩy ra khỏi byte sẽ quay lại cờ carry. Chuỗi lệnh rl liên tiếp cho phép các pixel di chuyển xuyên qua màn hình: khi một điểm ảnh "rơi khỏi" ô ký tự này, nó nằm trong cờ carry và lệnh rl kế tiếp sẽ chèn nó vào đầu ô ký tự bên trái. Lặp 32 lần là bạn đã dịch toàn bộ một dòng màn hình sang trái 1 pixel.
Để từng chữ cái hiện ra mượt mà thay vì nhảy cục, routine phải stream từng ký tự theo từng dòng, từng pixel. Nó tra mã ASCII, nạp 8 byte font tương ứng vào buffer, rồi với mỗi dòng dùng chiêu add a,a để dịch trái — vừa nhanh vừa gọn hơn lệnh sla chính thức. Chu kỳ CPU nhanh chóng cộng dồn lại khi bạn vẽ lại 256 byte mỗi frame.
Chỉ để cuộn một dòng chữ qua đáy màn hình, chúng ta phải lặp và vẽ lại 256 pixel mỗi frame!
Điều đó cho thấy assembly nhanh hơn trình thông dịch BASIC của ROM Spectrum đến mức nào, đồng thời khiến bạn thêm nể phục công sức mà các nhóm làm game ngày xưa đã bỏ ra.
Logo kiểu cracktro — một hiệu ứng kinh điển của thập niên 80
Chuyển nhạc từ Amiga sang chip AY
Phần cứng âm thanh của Amiga vượt xa mọi thứ trên thị trường thời đó, còn Spectrum 48K chỉ có tiếng "beeper" thô sơ. Các model 128K về sau mới trang bị chip AY-3-8912 ba kênh — cũng chính là chip được dùng trong Atari ST, và là xương sống của demoscene Spectrum.
Mark chọn chuyển bài "Milkman" từ đĩa nhạc Amiga "Sounds Of Gnome" (1989) của bộ đôi Thụy Điển Mahoney & Kaktus. Bài này lý tưởng để chuyển: chỉ có 7 pattern, đa số dùng chung tiến trình hợp âm, và dễ ép từ 4 kênh Amiga xuống 3 kênh Spectrum.
Thách thức thật sự là làm sao cho nó nghe càng giống bản gốc càng tốt. Công cụ chính là ornament của Vortex Tracker — kỹ thuật cho phép một kênh nhảy qua lại cực nhanh giữa hai cao độ, chẳng hạn 0,4 sẽ khiến kênh đó rung giữa nốt gốc và nốt cao hơn 4 bán cung. Vì diễn ra 25 lần mỗi giây nên tai người nghe như có hai nốt cùng lúc, từ đó dựng được hợp âm ba nốt chỉ với hai kênh, chừa kênh thứ ba cho phần khác.
Chiêu này cũng tạo nên âm sắc "rung rinh" đặc trưng mà giới mộ điệu retro rất mê. Sau nhiều tháng mày mò, Mark hoàn toàn hài lòng với kết quả cuối cùng.
Starfield: bài toán hóc búa nhất
Không có bất kỳ cracktro đàng hoàng nào lại thiếu cánh đồng sao. Cách tiếp cận ngây thơ đầu tiên là rải sao ngẫu nhiên, rồi mỗi scanline áp dụng chiêu rl như scrolltext, dịch mỗi hàng một lượng khác nhau để tạo parallax.
Nó thất bại thảm hại vì hai lý do. Thứ nhất, dịch cả hàng sẽ kéo theo mọi thứ khác trên hàng đó — kể cả logo và chữ. Thứ hai, và nghiêm trọng hơn, là tốc độ: chỉ ba hàng đầu đã ngốn hàng trăm thao tác dịch, vượt xa ngân sách một frame. Mọi thứ bò lê trên màn hình, còn nhạc thì chậm đến mức bạn nghe rõ từng nốt ornament như những nốt riêng biệt.
Giải pháp là theo dõi từng ngôi sao riêng lẻ. Nhưng ngay cả khi đã làm vậy, việc tra cứu tọa độ mỗi frame vẫn quá đắt. Cuối cùng Mark "ăn gian" khá thoải mái theo ba bước:
- Bỏ hẳn RNG: dùng một bảng tọa độ Y, X tính sẵn do script Ruby sinh ra, đảm bảo không có hai sao nào trùng tọa độ Y — nhờ đó tránh được cả một lớp vấn đề như sao chung scanline hay vượt mặt nhau.
- Lưu địa chỉ byte thay vì tọa độ: mỗi bản ghi sao chỉ chứa địa chỉ byte màn hình, con trỏ tới bảng "ô bị chiếm", bitmask và tốc độ di chuyển. Không còn phép chuyển đổi địa chỉ nào mỗi frame.
- Kiểm tra va chạm gần như miễn phí: routine "máy đánh chữ" đánh dấu byte bằng 1 cho mọi ô chứa ký tự văn bản. Kiểm tra xem sao có bị chữ che không chỉ tốn đúng một lệnh
or.
Di chuyển sao giờ đơn giản đến mức dùng lệnh rlca: khi bit quay ra khỏi bit 7 của mask, nó nhảy về bit 0 kèm cờ carry bật — đó chính là tín hiệu để dịch sao sang trái một ô ký tự. Vì chỉ đụng vào đúng những ô mà sao chiếm giữ, chi phí giảm hẳn và parallax giờ đây chỉ còn là chuyện mỗi sao bay vài pixel mỗi frame.
Một chi tiết nhỏ thú vị: các ô chứa dấu cách không bị đánh dấu, nên sao cứ thế trôi xuyên qua những khe hở giữa các từ. Bản cuối dùng toàn chữ IN HOA với font khối vuông vức, khiến hiệu ứng càng thêm tự nhiên.
VU meter và những mảnh ghép cuối
Hiệu ứng VU meter nhún nhảy theo nhạc là một đặc sản khác của demo thời kỳ đầu. Mark từng nghĩ tới bộ cân bằng đồ họa thời gian thực nhưng gạt bỏ vì quá phức tạp. Thay vào đó, ông dùng một dãy thanh nhún nhảy đơn giản, đọc trực tiếp biên độ ba kênh từ các thanh ghi R8, R9, R10 của chip AY qua cổng $FFFD.
Cuối cùng, chỉ để chọn một intro demoscene hoàn chỉnh, Mark đã phải học lại từ đầu cách phần cứng Spectrum thực sự vận hành — từ chip ULA điều khiển viền màn hình, các routine in ký tự trong ROM, đến cả cách nạp font tùy biến bằng cách ghi đè con trỏ font ở địa chỉ $5C36. Đó là hành trình mà ông gọi là vừa là dự án phụ yêu thích nhất, vừa là dự án khiến ông suýt đá văng màn hình nhiều lần nhất.
Với những ai muốn thử sức, thông điệp khá rõ ràng: đừng ngại viết assembly, hãy kiên nhẫn với sổ tay và bút chì, và đừng coi thường sức mạnh của một buổi đi dạo cùng chó.

