
2026/10/13 4:40
ZX Spectrum 向けのデモシーンイントロを作成する
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
著者は、標準的な 128KB の ZX Spectrum にて手書きのレトロなイントロを成功裏に作成し、高度に最適化されたアセンブリ言語プログラミングにより複雑なビジュアルとオーディオを実現した。2 ヵ月間(2025 年 12 月から開始)にわたり、約 19KB のコード(具体的には 10287 バイトのメインルーチン)を書き下し、内蔵 ROM を書き換えることでカスタムフォント「Cartoon」を用いたスムーズなスクロールテキストを作成し、また Amiga モジュール「Milkman」を変換して 3 チャネル AY サウンドトラックを生成した。限られたメモリ制限を管理するため、アセットは BASIC ロードに分割され、ルーチンはオーバーヘッドを避けるために特定のアドレスでハードコーディングされた。技術的な最適化としては、Z80 RL 命令を用いてスクロールテキストの回転を実現し、スキャンライン衝突を避けるために Ruby で生成した座標テーブルを事前計算してスターフィールド効果を再現し、さらにサウンドチップから直接振幅レジスタを読み取ってバウンス VU メーターを実装している。プロジェクトは 1980 年代後半の Amiga クrack イントロに触発され、本物の Spectrum +3上でローカル TNFS サポートのある Makefile 駆動のビルドシステムを用いて開発された。最終的な.tap ファイルは Web ブラウザまたは pouet.net を経由して入手でき、レトロなハードウェア上で高忠実度な体験を提供し、極限を効率的に達成したコーディングの実証として立っている。
本文
レトロ風の『クラック・イントロ』開発記録:Speccy, Z80 アセンブリと 3 チャンネル AY サウンドの冒険
更新日: 2026 年 10 月 12 日
今すぐダウンロード
- 確認サイト: pouet.net
イントロダクション
-
開発経緯
- 80 年代後半に Sinclair BASIC や雑誌の汇编(アセンブリ)プログラムをタイピングし、友人宅の Amiga クラック・イントロを ZX スペクトラムで再現しようとした過去がある。
- 当時多大な時間を費やしたが成功せず、現在はようやく実現できた。
- 開発期間: 約 1 年(2025 年 12 月 13 日以降)。
- コード生産速度: 平均約 455 バイト/週。
-
作品概要
- プラットフォーム: 標準仕様の 128KB スペクトラム(Sinclair ZX Spectrum)。
- サイズ: コード約 10,287 バイト、フル・タペイメージ 19,552 バイト。
- 特徴: 80 年代後半の流行語をすべて盛り込んだレトロ風デモシーン・イントロ。
- スムーズなスクロールテキスト & 「leet」カスタムフォント
- 3 チャンネル AY サウンドトラック
- VU メーターによるバウンス効果
- パララックス星空
Be Kind, Rewind(背景と着想)
-
Amiga クラックトロの衝撃
- 8 ビット機のカセットロード時代、Amiga のグラフィック・サウンド・ゲームライブラリに圧倒された。
- 特に違法コピーされたゲームの冒頭にある**「イントロ(クラックトロ)」**に魅了された。
- 音楽、グラフィック、エフェクトが組み合わさったミニチュア芸術作品としての側面がある。
-
当時のトレンド要素
- スクリーン上部のクールなロゴ(血液垂れ流しやメタル風など)。
- エフェクト上に浮かぶ誇らしげなテキスト(スクロールするパララックス星空や 3D オブジェクトなど)。
- 仲間へのオマージュ、クラッカーたちへの挨拶、違法 BBS システムの広告。
-
音楽的インスピレーション
- 『レミングス』のような 90 年代初頭の流行楽曲(Skid Row など)が典型的な Amiga クラックトロとして定着した。
- 「サイン波スクリーン」「星空」「自慢げなテキスト」 の融合が最高だったと振り返る。
-
プロジェクトのきっかけ
- Amiga からスペクトラムへ機種変更、Z80 コードの手応えを再び感じたい。
- 友人「Desertkun」(新しい WiFi ネットワークカード開発者)から、「なぜ Z80 アセンブリを書かないか?」という挑戦を受けたことがトリガーになった。
- 独立したイントロを作成し、「シーン」系の作品作家になりたいと決意した。
開発環境
-
基本スタック
- Pasmo アセンブラを Makefile で駆動するビルドシステム。
のフォーク版(BASIC ファイル向け)。zmakebas- テスト用 FUSE エミュレーター。
-
ハードウェアテスト
ファイルをローカル TNFS サーバーにアップロードし、+3 で実行。.tap
-
重要ツール
- Web ベースのアセンブルエディタ
- Z80 命令の参照、サンプルコードの実行、レジスタ状態確認が可能。
のステップ実行や、ビットシフト時の Carry フラグ動作などを視覚的に理解できた。djnz
- メモ帳とペン(デバッグツール)
- 上位言語とは異なる構造のため、バイトとビットマップを手書き図に描画。
- レジスタフラグの表を作成し、画面メモリーのチャンクを作業範囲で追跡。
- 犬たちによる「ガチョウの代わり役(Rubber Ducking)」: デバッグ中の独り言を聞きながら思考整理を行った。
- Web ベースのアセンブルエディタ
スタート地点と Hello World
-
初期設定
を確認し、ロゴと背景(ステージ)の読み込みを実装。Hello, world!- TNFS サイトのヘッダー風デザインを採用し、青いガイドバーを追加。
- 画面メモリー: アドレス
から開始(16384 進)、長さは 6912 バイト。$8000
-
基本命令の理解
: ULA を介してボーダー色を設定。out
: Pasmo でバイナリファイルを読み込み。incbin
: Z80 のブロックコピー命令による画面描画。ldir
org $8000 screen EQU $4000 ; 画面メモリーの先頭アドレス scrlen EQU 6912 ula EQU $FE ; ULA I/O ポート black EQU 0 ; コーナーカラー start: ld a,black ; ボーダーを黒に設定 out (ula),a ; ロゴヘッダー画像をコピー ld hl,header ; ソースアドレス ld de,screen ; デスティネーション ld bc,scrlen ldir ; ブロックコピー hold: halt ; 次のフレームへ待機 jr hold header: incbin "assets/header.scr" end start
- ROM ルーチンの活用
- カーソル制御 (
,at
,row
) と ROM のcol
ルーチンを使用してテキスト表示を実現。pr_string - PR_STRING は各文字へのループ処理を示しており、レジスタの動きを追う練習となった。
- カーソル制御 (
スタイル追加:フォントと「leet」感
- 問題点: 標準フォントがデモシーンの雰囲気不足だった。
- 解決策: Jim Blimey 氏のコレクションから「カートゥーン風フォント」を採用。
- 実装ポイント:
- ROM のフォント参照アドレス(
)を書き換え、独自のフォントデータを読み込み。$5C36
- ROM のフォント参照アドレス(
chars EQU $5C36 ; フォント参照アドレス ld hl,myfont-256 ; HL = フォントアドレス(256 オフセット調整) ld (chars),hl ; ROM を新しいフォントに指示 ; ... コードの続き ... myfont: include "textfont.asm" ; カートゥーン風フォント
- 成果: 「31337(leet)」な雰囲気になり、2 週間での進展が感じられた。
呪われた画面レイアウトとスクロールテキスト
-
Speccy の奇妙なメモリー配置
- ビットマップ部分は交互行(interleaved rows)で構成され、1 ピクセル進めるごとに 8 ライン下がる構造になっている。
- このため、X/Y アドレスをメモリーアドレスに変換する複雑な処理が必要となる。
-
画面移動の「安易な手」(Tricks)
- 交互行の特性を活かし、
/inc l
で横方向、dec l
/inc h
で縦方向を素早く移動可能。dec h - キャラクターセル(32 ピクセル)の全移動には、HL レジスタの High バイトに 32 を加算する処理を追加。
- 交互行の特性を活かし、
-
滑らかなスクロールの実装
- ROM の印刷ルーチンを使わず、フォントデータを直接読み込み、ピクセルごとにストリーミング描画。
- 「回転左(Rotate Left)」命令 (
) を活用:rl- バイトを左へシフトし、最下位ビット(Carry フラグ)に新たなピクセルを挿入。
- 32 ビット分ループして、画面全体を 1 ピクセル移動させる。
dot EQU %00000001 ; 右端のピクセルのみ点灯 newdot: ld a,dot ; ドット形状を読み込み ld (lineend),a ; ライン末尾に保存 frame: halt ; フレーム待機 ld hl,lineend ; HL = ライン末尾(右端) shift: rl (hl) ; バイトを左に回転(ピクセルを左へ移動) dec hl ; 次のバイトへ指す djnz shift ; 32 ピクセル分ループ jr c,newdot ; Carry フラグがセット=ドットが画面外に出たのでリセット jr frame // カードに描画中の位置にピクセルが移動し、次のフレームへ
- 描画ロジックの効率化
- ASCII コードからフォントデータを取得し、
(左シフト)でビット操作を高速化。add a,a - フレームごとに 256 バイトを再描画するため、アセンブリ言語特有の処理速度の重要性を実感した。
- ASCII コードからフォントデータを取得し、
音楽:Amiga モジュールから 3 チャンネル AY へ
-
ハードウェアの制約
- Speccy は初期モデルでは単なる「ビーパ」だが、128KB モデルには 3 チャンネル AY-3-8912 サウンドチップが搭載。
- Amiga の多チャンネル音楽(モジュール)を移植する必要があるため、工夫が必要だった。
-
楽曲の選定:「Milkman」
- 1989 年リリースの「Sounds Of Gnome」から、短くコード進行が単純なトラックを選び。
- 4 チャンネルの Amiga モデルを 3 チャンネルへ圧縮し、VU メーターやグラウンディングサウンドを追加。
-
コンバート手法:Vortex Tracker II
- OpenMPT で元のモジュールを読み込み、チャンネルと音高スライドダウン(例:A#からベースノートへ)を調整。
- 「装飾(Ornaments)」: ノートを高速切り替えさせ、2 チャンネルで 3 和音(トリアド)を構築する技術を使用。
- 例: C メジャーコードを C(1 チャン)、D#(2 チャン)+G(装飾) で再現。
-
再生ルーチンの実装
- アーカイブから取得したバイナリオブジェクト(PT3Play)をアドレス
に読み込み、ハードコーディングして使用。$C000
- アーカイブから取得したバイナリオブジェクト(PT3Play)をアドレス
player EQU $C000 ; プレイヤー起動アドレス plinit EQU player+3 plplay EQU player+5 ld hl,moduleimg ; モジュールファイルアドレス call plinit ; 初期化 halt ; フレーム待機 hold: call plplay ; 1 フレームごとの再生 call scroll ; スクロール処理(他) jr hold ; ループ moduleimg: incbin "assets/music.pt3"
星空:パララックスと衝突回避
-
古典的アプローチの問題
- ライン全体をシフトすると他のオブジェクトがぼやけ(smear)、フレーム速度が遅くなる。
- テキスト描画との兼ね合いで、星を描画する前に「占有セル」を確認する必要があった。
-
事前計算された座標テーブル
- Ruby スクリプトを使用して、重複しない Y 座標の配置を生成し、ランダム数値(RNG)を排除。
- 画面アドレス直接指定により、フレームごとの変換コストを削減。
stdata: defb 109, 25, 88,182, 42, 96, ... ; X,Y 座標とマスクのテーブル ; 32 つの星分のデータを定義
-
衝突チェックと移動
- 「タイプライター」ルーチン: テキスト描画時に占有セルをマーキング(1 に設定)。
- 星を描画前に
演算で占有確認を行い、被っている場合はスキップ。OR
でビットパターンを左へシフトし、Carry フラグから新しいピクセルを投入。rlca
-
視覚的効果
- スペース文字(空白)は描画せず、星がテキストの隙間をすり抜ける演出を実現。
- 全大文字フォントを使用することで、ブロック状のキャラクタと星空のコントラストを強調。
雑記:VU メーターとその他のエフェクト
-
VU メーター
- 複雑なイコライザーではなく、振幅値を読み取ってバーの高さを変動させるシンプルな実装。
- AY チップのレジスタ
を書き出して読み込み、チャンネル A/B/C の振幅を取得。$FFFD
-
Twinkle, Twinkle(ロゴ背景の星)
- ランダムな瞬き効果を実現。
- 擬似乱数生成器として、ROM の初期部分からアドレスをインクリメントして「ランダム」バイトを選択するテクニックを使用。
-
スペースアウト(メモリ管理)
- アセット(画像・バイナリ)をプログラム内に
で読み込むとメモリー不足に陥る問題が発生。incbin - 解決策として、多段階 BASIC テープローダーを採用し、個別のコードブロックに分けてロードした。
- アセット(画像・バイナリ)をプログラム内に
10 CLEAR 32767 20 BORDER 0: PAPER 0: INK 0: CLS 30 LOAD "" CODE ; メインプログラム 40 LOAD "" CODE ; プレイヤー($C000) 50 LOAD "" CODE ; 背景画像 60 RANDOMIZE USR 32768
Z80 の禅:総括と感謝
-
プロジェクトの特性
- 子供時代の夢をかなえ、ハードウェアに近いレベルでのコーディングを楽しめた。
- バイナリ操作やレジスタ処理を通じて、「機械そのもの」として思考する訓練になった。
- 挫折と再挑戦を繰り返した longest(最長)な開発プロジェクトとなった。
-
学びの重要性
- 現代のツールや GitHub リポジトリ、YouTube チューターがあったからこその達成感がある。
- もしカセットレコーダーとメモ帳のみで実現していたら、どれほど長期化いただろうかと想像する。
-
アセンブリ言語への敬意
- 精神衛生を優先し、日常業務での使用は控えるが、ハードウェアハッキングや極限の試みとしては最適。
- 80 年代の子供たち(寝室コーディング)やシーネラーたちの情熱に感謝し、グラスを持ち上げて敬意を表す。
「ハッカー、グラフィシャン、プロトラッカーたちにアピールします」