KITAQ DEVELOPMENT SYSTEM
USER'S MANUAL 2026.09

KITAQ SERIES • REFERENCE EDITION

KITAQGB

ゲームボーイ Cプログラミング

初めての一行から、実行・観測・再テストまで。
ローカルソース採取:2026年9月14日

Rust製コンパイラと補助ツール

Rust 1.85以降で、Windows・Linux・macOS ARM・macOS Intel用のコンパイラと全補助ツールをビルドできます。ネイティブ実行ファイルの動作に.NETは不要です。素材処理用の補助ツールもPythonやPillowを必要としません。

Windows用実行ファイルはリポジトリ直下、Linux・macOS用は下表のbin/配下にあります。lib/とライセンス表記を一緒に保持してください。Linux/macOSでは取得した実行ファイルにchmod +xで実行権限を付け、配置先をPATHに追加するかフルパスで実行します。

OSkitaqgb
Windows x64kitaqgb.exe
Linux x64 (musl)bin/linux-x86_64/kitaqgb
Linux x64 (GNU)bin/linux-gnu-x86_64/kitaqgb
macOS ARM64bin/macos-arm64/kitaqgb
macOS Intelbin/macos-x86_64/kitaqgb
macOS Universalbin/macos-universal/kitaqgb

kitaqgb · Rust製コンパイラと補助ツール · GitHub Actions

Windows・Linux・macOS ARM・macOS Intelでネイティブビルドと実行検証が成功しています。KITAQGBは各環境48件のテストと395件の補助ツール検証、KITAQFCは55件と401件が成功しました。Rust 1.85でも確認済みです。PUBLIC_DISTRIBUTION.jsonに配置済みバイナリのハッシュと検証の出典を記録しています。公開GitHub Actionsでもこのソースを独立してビルド・検証します。

保存済みの比較データでROMのバイト列、診断と補助ツールの形式を検証しています。以前のC#版によるエミュレータ検証は、そのソース指紋に対応する過去の記録として残しています。これだけでRust版の全API、実機動作、FDSのBIOS経由のゲーム起動を保証するものではありません。元のPNG変換スクリプトは入手できないため仕様から再実装し、元スクリプトとのバイト一致は確認できません。

--attachは互換性のため受理しますが、ネイティブ実行ファイルへマネージドデバッガを接続しません。PC側のデバッグにはネイティブデバッガを使用してください。

KITAQGB · SHA-256 · KITAQFC · SHA-256

各OSでのサンプルビルド

PowerShellのサンプルビルドは直下のWindows用EXEを使用します。Linux/macOSでは同じC入力とオプションをネイティブコンパイラに渡すか、PowerShell 7を使用します。CHR画像とCソースは別の入力です。font.chrには元のサンプルフォントを保持しています。

sh
kitaqgb game.c -I lib -o game.gb --cart=romonly --romsize=32k

1 KITAQGBを知る

KITAQGBはZachtronicsのNORCALをフォークして発展した、GB/CGB用のC系コンパイラです。

NORCALの名前は北カリフォルニア(Northern California)に由来します。地域の名前を使うこの命名に着想を得て、作者は自身が生まれ育った北九州市をもとにKITAQGBと名付けました。KITAQ + GB、つまり日本の福岡県北九州市の愛称である北九(キタキュー、Kitakyū)とGame Boyを組み合わせた意味です。KITAQは「キタキュー」と読みます。英語話者向けの発音の目安は kee-tah-KYOO、発音記号は /ˌkiːtɑːˈkjuː/ です。最後のQは英語のアルファベットQと同じ音です。KITAQGBはGとBを一文字ずつ読み、「キタキュー・ジー・ビー」と呼びます。

KITAQGBという名前には、二つの意味があります。Kernel-Informed Toolchain for AI-Quality Game Boy Developmentは、対象マシンを理解し、人間のプログラマーと生成AIの双方を支えるツールチェーンという目標を表しています。

もう一つの意味は、Kids' Imagination Transformed into Actual Quests in Game Boy Forestsです。「子どもの想像を、Game Boyの森で本物の冒険へ変える道具」という意味を込めています。小さなアイデアや落書き、AI支援による試作を、実際に遊べる冒険へ変えていくための道具にしたい、という創作面での願いを表しています。

Cファイルを読み、CPU命令へ変換し、ゲーム機が読み込むROMにまとめます。一般のPC用Cコンパイラとは言語・標準ライブラリ・呼び出し規約が異なります。

GBは小さなメモリと8ビットCPUを持ちます。画面は基本的に8×8画素のタイルを並べて作ります。背景とは別に動かす小さな絵がスプライトです。文字も自動的には存在せず、文字のタイルを用意します。本書のサンプルには、作者指定の ascii.c の英数字・記号を使います。GBではビット列を保ったままASCII番号へ配置し、FCでは同じ字形をNESのCHR形式へ変換しています。

2 準備とコンパイラのビルド

Rust 1.85以降で、Windows・Linux・macOS ARM・macOS Intel用のコンパイラと全補助ツールをビルドできます。ネイティブ実行ファイルの動作に.NETは不要です。素材処理用の補助ツールもPythonやPillowを必要としません。

sh
cd kitaqgb
cargo test --locked --tests
cargo build --locked --release

Windows用実行ファイルはリポジトリ直下、Linux・macOS用は下表のbin/配下にあります。lib/とライセンス表記を一緒に保持してください。Linux/macOSでは取得した実行ファイルにchmod +xで実行権限を付け、配置先をPATHに追加するかフルパスで実行します。

OSkitaqgb
Windows x64kitaqgb.exe
Linux x64 (musl)bin/linux-x86_64/kitaqgb
Linux x64 (GNU)bin/linux-gnu-x86_64/kitaqgb
macOS ARM64bin/macos-arm64/kitaqgb
macOS Intelbin/macos-x86_64/kitaqgb
macOS Universalbin/macos-universal/kitaqgb
powershell
.\scripts\build.ps1
.\kitaqgb.exe --help
sh
sh scripts/build.sh

PowerShellのサンプルビルドは直下のWindows用EXEを使用します。Linux/macOSでは同じC入力とオプションをネイティブコンパイラに渡すか、PowerShell 7を使用します。CHR画像とCソースは別の入力です。font.chrには元のサンプルフォントを保持しています。

3 最初のプログラム

同梱 samples/gb_hello.c は gb_common.h の画面準備・文字表示関数を使います。ヘッダーとフォントも一緒に置きます。#include は別ファイルを読み込む指示です。

c
#include "gb_common.h"
void main() {
    m_init();
    m_text(2, 5, "HELLO WORLD");
    m_number(42);
    while (1) { m_wait(); }
}
powershell
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\gb_hello.c -I .\kitaq-docs\samples -o .\out\hello.gb --profile=dev --rst-disable --stack-bank=fixed
.\kokura\kokura-cli.exe .\out\hello.gb --run-frames 120 --png .\out\hello.png --dump-report .\out\hello.json

画面にHELLO WORLDと042が出れば、この例の目標達成です。m_ で始まる関数は本書の学習用ヘルパーで、KITAQGBの標準命令ではありません。定義はサンプルの共通ヘッダーで読めます。__wait_vblank や __vram_copy が本来の組み込み機能です。

4 プログラムの文法

文の終わりに ;、処理のまとまりに { } を使います。// 以降は1行コメント、/* ... */ は複数行コメントです。名前の大文字・小文字は区別します。エントリーポイントは void main() です。このGB版では void main(void) にすると構文エラーになります。 FC版のコードをそのまま移すときの注意点です。

型使い道・範囲
u88ビット符号なし整数、0~255
s88ビット符号付き整数、-128~127
u1616ビット符号なし整数、0~65535
s1616ビット符号付き整数、-32768~32767
void戻り値なし
T*T型のデータを指すポインタ

最初はこの短い型名を使ってください。PC用Cの int、long、float、double や標準ヘッダーの存在を前提にしません。char は本実装では符号なし8ビット型として扱われます。符号が必要な計算には s8 / s16 を明記します。

c
u8 lives = 3;
u16 score;
score = (u16)200 + 100;

(u16) は型変換です。小さな型のまま計算してから大きな型に代入しても、途中で失った桁は戻りません。8ビットの範囲を超える計算では、演算前に型を広げます。小数の動きにはライブラリの固定小数点を使います。

5 式と演算子

種類記号例・意味
算術+ - * / %n / 10 は整数の商、n % 10 は余り
比較== != < <= > >=lives == 0 は等しいかの判定
論理! && / ||条件の否定・両方成立・どちらか成立
ビット& / | / ^ ~ << >>ボタン・フラグなどのビット集合
代入= += -= などx += 1 は値を更新
増減++ --i++ は1を加える
選択条件 ? A : B条件に応じて値を選ぶ
ポインタ&変数 / *pアドレスを得る/指す先を読む

= と == を混同しないでください。複雑な式は括弧で意図を明確にし、呼び出しと副作用を1行へ詰め込みません。ゼロ除算や配列範囲外へのアクセスは避けます。sizeof はバイト数、offsetof は構造体メンバーの位置を得るために使います。

式の結果と評価回数

比較演算子 ==、!=、<、<=、>、>= と論理演算子 !、&&、|| は、偽なら0、真なら1を返します。結果を u16 変数へ保存したり、引数・戻り値・算術式に使えます。例えば score = 500 + (lives != 0); は、残機があれば501、なければ500になります。

&& は左辺が0なら右辺を評価せず、|| は左辺が0以外なら右辺を評価しません。pointer != 0 && pointer->active != 0 では、ポインタが0のとき右辺へアクセスしません。16ビット値は上位バイトも含めて判定するため、256も真です。ビット演算子 &、| には短絡評価がありません。

++value は更新後、value++ は更新前の値を返します。配列要素、ポインタの参照先、構造体メンバーにも使えます。buffer[index()]++ は index() を1回呼びます。u16 *p に対する p++ は次の要素へ2バイト進み、(*p)++ は指している値を増やします。

sizeof(array) は配列全体のバイト数、sizeof(pointer) は2です。例えば u16 values[9]; なら sizeof(values) は18です。ROM配列・ローカル配列・構造体内の配列も対象になります。sizeof(function()) は戻り値の型の大きさを調べるだけで、関数を呼びません。

関数宣言と定義では、引数の型と順番を合わせます。引数名は一致しなくてもよく、関数本体では定義側の名前を使います。例えば u8 next(u8 input); に対して u8 next(u8 value) { return (u8)(value + 1); } と定義できます。引数やローカル変数は同名のグローバル変数を隠します。

?:で配列や文字列を選ぶと、結果は選んだ要素型へのポインタになります。show(ready ? "READY" : "WAIT");のように直接引数へ渡せます。u16配列aとbを選ぶ(ready ? a : b) + 1は、選択先の2番目の要素へ2バイト進みます。配列そのものをコピーする操作ではありません。

condition ? yes : no は条件を評価してから、選ばれた側だけを評価します。条件や選択肢には変数を使ったシフト式も書けます。例えば on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; は対象ビットに応じて4か2を選びます。&&、||で飛ばされる右辺の関数呼び出しも実行されません。ループ内のcontinueでは、forの更新式を1回実行してから条件を再評価します。while・do ... whileでは条件へ進みます。

6 条件分岐と繰り返し

c
if (lives == 0) { game_over = 1; }
else { score = score + 10; }
for (i = 0; i < 4; i++) { table[i] = i; }
while (running != 0) { update_game(); }
do { count++; } while (count < 3);

break はループやswitchを抜け、continue は次の反復へ進み、return は関数から戻ります。switch のcaseから次のcaseへ意図的に進む場合は、本言語の fallthrough; を使います。暗黙の流れ落ちは診断の対象です。動作例は gb_control.c にまとめています。

7 関数・配列・構造体

c
u8 add(u8 a, u8 b) { return (u8)(a + b); }
typedef struct { u8 x; u8 y; } Point;
Point player;
u8 tiles[4];

配列は0から数えます。4要素なら有効な添字は0~3です。player.x はメンバー、pointer->x はポインタで指した構造体のメンバーです。構造体・union・enumの構文があり、レイアウトは型や __packed / __aligned の指定に依存します。バイナリファイルやハードウェアへ渡すときは sizeof を確認します。

標準のLegacy ABIは引数やローカル領域を固定配置する設計です。再帰や割り込みによる再入をPC用Cと同様に扱わないでください。__stackcall や --abi=stack は呼び出し規約に関わる上級指定です。混在するときはビルドのABIレポートと実行を確認します。関数ポインター経由のコールバック呼び出しは--abi=stackでは未対応です。システムやシーンのコールバックを使う完成サンプルは、掲載したビルド例どおり標準のLegacy ABIを使います。

8 ソースの分割とプリプロセッサ

ヘッダーには型・定数・関数宣言を書き、.c に関数本体を書きます。#pragma once またはinclude guardで二重取り込みを防ぎます。#define、#undef、#if、#ifdef、#ifndef、#elif、#else、#endif を使ってコンパイル時にコードを選べます。

powershell
.\kitaqgb\kitaqgb.exe main.c game.c .\kitaqgb\lib\input.c -I .\kitaqgb\lib -o .\out\game.gb --profile=dev

-I はヘッダーを探す場所です。ライブラリのヘッダーをincludeしただけでは本体は結合されません。必要な .c もコマンドへ並べます。すべてのライブラリを一括指定するとレジスター定義や割り込み処理が衝突し得るので、機能に必要なものを選びます。

9 ROM・メモリ・バンク

ROMはプログラムと定数、WRAMは変数、VRAMは画像、OAMはスプライトの情報です。バンクとは、同じCPUアドレスへ見せるメモリの領域を切り替える仕組みです。16ビットのアドレスだけでは、別バンクのデータを区別できません。

c
#pragma bank 1
__prg_rom u8 level_data[] = { 1, 2, 3, 4 };

__prg_rom はROM配置、__location(0xFF40) のような指定は固定アドレス、__wram / __hram はメモリ領域の指定です。#pragma bank や #pragma fixed_bank を使う場合はマップを読み、割り込みで呼ぶコードやデータが常に見えるか確認します。

powershell
.\kitaqgb\kitaqgb.exe main.c -o .\out\game.gbc --cart=mbc5 --romsize=256k --ramsize=32k --cgb=cgb --rom-title=MYGAME --stack-bank=fixed --emit-ai-metadata=.\out\build.json

--cgb=dmg はDMG向け、--cgb=cgb は両モード用、--cgb=cgb_only はCGB専用の宣言です。CGB機能をDMGで動かす場合はハードウェア判定が必要です。ROMヘッダーの宣言だけでゲーム側の両対応が完成するわけではありません。

10 画像を書き込む時間

VBlankは画面の描画区切りです。VRAMやOAMへ無計画に書くと、画面の欠けや更新漏れが起きます。入門では画面OFF中に素材を転送し、通常画面では安全な組み込み関数やVRAMキューを使います。_unsafe / _fast の付いた関数は、呼び出し側が転送可能な時間を保証する必要があります。

OAM DMAのバッファは256バイト境界に配置し、GB版の __oam_dma には転送元アドレスを渡します。FC版の同名命令は引数なしなので混同しないでください。

11 ビルドコマンドと出力

-o は出力先、-O0 / -O1 は最適化、--profile=dev|release|test は複数の設定をまとめたプロファイルです。--no-disasm は逆アセンブル出力を省き、--debug-out=... や --trace-out=... は調査用ファイルの出力先です。速いビルドと詳細な調査で使い分けます。

powershell
.\kitaqgb\kitaqgb.exe main.c -I .\kitaqgb\lib -o .\out\game.gb --profile=dev --diag-json=.\out\compile.json --emit-ai-metadata=.\out\build.json --debug-out=.\out\debug

.map は名前と配置、.funcsizes.txt は関数サイズ、.dbg2.json / .source_map.txt は実行位置とソースを結ぶ情報、.build_report.json はビルドの要約です。SARAKURAへの入力には、明示して出した --emit-ai-metadata のJSONを使うと経路がわかりやすくなります。

12 エラーを読む

エラーの先頭にあるファイル名・行・KQ番号を読みます。後続のエラーは先頭の構文ミスに連鎖していることがあります。まず最初のエラーを直します。未定義シンボルなら宣言・本体・ビルドへの追加を、ROM容量超過なら素材・関数サイズ・バンク配置を確認します。

powershell
.\kitaqgb\kitaqgb.exe kqhelp KQ1002
.\kitaqgb\kitaqgb.exe symfind main --map=.\out\game.map
.\kitaqgb\kitaqgb.exe romdiff .\out\before.gb .\out\after.gb

13 インラインアセンブリ

__asm { ... } にKITAQGBの命令名を書けます。これは他のGBアセンブラのソースをそのまま受け付けるという意味ではありません。例えば LD_A_IMM のような内部形式の命令名があります。引数・戻り値・保持すべきレジスター・スタックを確認してから使います。命令名・オペランド形式は本書の付録を参照してください。

c
void nop_example() {
    __asm { NOP }
}

14 参考例:生成AIを使ったゲーム開発

依頼内容を記入して、プロンプト全文を生成AIに渡してください。実装、エミュレータ検証、SARAKURA解析、修正後の再検証まで含みます。

· Markdown版をダウンロード

以下の「依頼内容」を記入し、このファイル全体を生成AIに渡してください。 コマンドは、kitaqgb、kokura、sarakura、kitaq-docs、game-gb が同じ親フォルダーにある配置を前提にしています。既存環境では実際のパスを使ってください。

依頼内容

  • ゲーム名:〈記入〉
  • ジャンル・遊びの中心となる仕組み:〈記入〉
  • プレイヤーが行う操作と、成功・失敗条件:〈記入〉
  • 必須の画面・ステージ・敵・アイテム:〈記入〉
  • 見た目、BGM、効果音:〈記入。資料がある場合はファイルも指定〉
  • 対象機種:〈初代Game Boy/GB・CGB両対応/CGB専用〉
  • 性能目標:〈例:通常時に毎秒60回のゲーム更新。重い場面の許容条件も記入〉
  • 保存・通信・その他の要件:〈記入。不要なら「なし」〉
  • プロジェクトの保存先:〈例:game-gb〉
  • 再配布条件:〈例:自作コードと素材をMITで公開できる状態にする〉

あなたに実行してほしいこと

KITAQGBと付属ライブラリで、上記のゲームを実装してください。 デバッグと実行検証にはKOKURA、診断の整理と修正前後の比較にはSARAKURAを使います。 「仕様を具体化 → 小さく実装 → ビルド → 操作して観測 → 原因を調べる → 修正 → 同条件で再検証」を、受け入れ条件を満たすまで繰り返してください。計画、コードの提示、コンパイル成功だけで完了にしないでください。

1. 環境と受け入れ条件を確定する

  1. 作業先の指示、各ツールのREADME、対象言語のHTMLマニュアル、使用するライブラリのヘッダーと実装を読んでください。実行ファイルの場所・バージョンまたはSHA-256を記録し、コマンドとAPIは実際の --help とソースで確認してください。
  2. 使用機種、ROMの構成、入力、画面、音、更新頻度について、合格・不合格を判断できる受け入れ条件を書いてください。例えば「STARTを押して離すとタイトルからゲームが始まる」「衝突で残機が1減る」「ポーズ中は指定どおりの無音になり、解除後に音楽が再開する」のように具体化してください。
  3. 重要な仕様の曖昧さだけを確認し、通常の可逆な実装判断は自律的に進めてください。仕様や合格基準を勝手に弱めないでください。
  4. まず付属の小さなサンプルでコンパイラ・KOKURA・SARAKURAの接続を確認してください。これを依頼されたゲームの完成と扱わないでください。

2. 小さく遊べる単位で実装する

  • 最初に「起動・タイトル・操作可能なプレイヤー・成功または失敗・再開」までをつなぎ、その後に内容を増やしてください。
  • KITAQGBのC方言に合わせ、GBのエントリーポイントは void main() を使ってください。一般のデスクトップCやGBDKの関数を、そのまま利用可能だと仮定しないでください。
  • 宣言だけでなく対応する実装ファイルも確認し、必要な .c をビルド対象へ含めてください。初期化順序、値の単位、符号、範囲、バッファ寿命、ROMバンクを確認してください。
  • VRAM・OAM更新、VBlank、割り込み、スタック、ROM/WRAMバンク、タイル・スプライトの上限を設計に含めてください。VRAM転送キューの総容量・空き容量は、物理VRAMの空き容量とは別です。
  • DMGを対象にする場合はCGB専用機能へ依存させないでください。両対応ならDMG/CGBそれぞれの描画と動作を確認してください。
  • 英数字・記号には提供された自作 ascii.c 由来のフォントを使い、使用するコードとタイルの対応を確認してください。画像・音楽・効果音は編集可能な元データと生成手順も保存してください。
  • ソースのコメントは英語、作業報告は日本語で記述してください。SARAKURAの標準レポートは英語のまま利用してください。

3. 各反復でビルドと実行を結び付ける

game-gb/out/iter-001 のように反復ごとの出力先を作り、実行コマンド、コンパイラの終了コード、ROM・メタデータ・ソース・ツールのハッシュを記録してください。 失敗したビルドの後に、残っている別のROMを実行しないでください。ROMと .map、ソースマップ、デバッグ情報は同じビルドのものを使ってください。

以下は親フォルダーから実行する基本形です。main.c、追加のライブラリ、オプション、入力列は実装と試験に合わせて設定してください。

powershell
$iteration = '.\game-gb\out\iter-001'
New-Item -ItemType Directory -Force $iteration | Out-Null

# Include all additional implementation units required by the game.
& '.\kitaqgb\kitaqgb.exe' '.\game-gb\src\main.c' `
  -I '.\kitaqgb\lib' -o "$iteration\game.gb" `
  --profile=dev --rst-disable --stack-bank=fixed --no-disasm `
  "--emit-ai-metadata=$iteration\build.json"
if ($LASTEXITCODE -ne 0) { throw 'Build failed; inspect the build log.' }

# This sequence presses START once, with released intervals on both sides.
& '.\kokura\kokura-cli.exe' "$iteration\game.gb" `
  --hardware dmg --run-frames 300 `
  --input-seq 'NONE:60;START:1;NONE:239' `
  --png "$iteration\frame.png" --record-wav "$iteration\audio.wav" `
  --dump-report "$iteration\run.json" `
  --emit-diagnostics "$iteration\events.jsonl"
if ($LASTEXITCODE -ne 0) { throw 'Emulator run failed; inspect the run log.' }

& '.\sarakura\sarakura.exe' gb analyze `
  --metadata "$iteration\build.json" --events "$iteration\events.jsonl" `
  --frames 300 --out "$iteration\analysis" --fail-on error
if ($LASTEXITCODE -ne 0) { throw 'Inspect the analysis report and fix the cause.' }

--hardware dmg は初代GBの試験用です。両対応・CGB専用の試験では、ROMヘッダーの機種指定とKOKURAの機種指定も合わせてください。300フレームは動作確認の例で、ゲーム全体の試験時間を意味しません。

4. 実際に操作し、画面・音・状態を照合する

  • 入力シナリオをファイルに保存し、押下、保持、解放を区別してください。起動、ゲーム開始、移動、アクション、衝突、ステージ遷移、ゲームオーバー、再開、ポーズなど、仕様にある経路を通してください。
  • 必要なフレームのPNG、入力列、実行レポート、診断JSONL、WAV、必要に応じた状態・メモリ観測を保存してください。実行フレーム数と停止理由も確認してください。
  • 画面は画像として確認してください。カウンターや座標などは期待値とも照合してください。1枚の画面で動きや入力への反応を確認したことにしないでください。
  • BGMや効果音の再生、同時発音、途切れ、ポーズ・再開を確認してください。音声ファイルがあることだけで音が正しいと判断しないでください。試聴できない環境では、その制約と実施できた波形・数値検査を分けて報告してください。
  • 性能は重い場面でも測定してください。ホストPC上のエミュレータの実行速度を、ゲーム内の更新頻度や実機での速度と同一視しないでください。

5. SARAKURAを使って原因を絞り、同条件で再検証する

  • --metadata にはそのROMを作ったビルド情報、--events にはその実行から得た診断JSONLを渡してください。CPUトレースや通常の実行レポートを診断JSONLの代わりにしないでください。
  • report.html、ai_diagnostics.json、repair_prompt.md、retest_plan.json を読み、診断を再現手順・画面・音・該当ソースと照合してください。SARAKURAはROMを実行したり、ソースを自動修正したりするツールではありません。
  • 正常な待機ループと停止不具合を区別し、警告は個別に理由を判断してください。フィルターで非表示にしたり、フレーム数を減らしたりして合格扱いにしないでください。ソース位置や原因の推定は、確認済みの事実と区別してください。
  • 不具合を最小化し、原因に対応する変更を加え、ビルドからやり直してください。ツール側の不具合が疑われる場合は、ゲーム側の問題と切り分ける最小再現例を作り、ツールの修正には回帰検証も付けてください。
  • 修正前後で入力・乱数種・機種・観測フレーム・診断条件を揃え、同じ受け入れ条件を再実行してください。ROMが変わった場合はそのビルドに対応するメタデータを使い、状態ファイルを無条件に使い回さないでください。
powershell
& '.\sarakura\sarakura.exe' baseline-delta `
  --baseline '.\game-gb\out\iter-001\analysis' `
  --current '.\game-gb\out\iter-002\analysis' `
  --out '.\game-gb\out\delta.json' --markdown '.\game-gb\out\delta.md' `
  --fail-on-new error --fail-on-regression error --enforce

差分診断は、操作・表示・音の合格判定と併用してください。同じ失敗を繰り返す場合はログと仮説を見直し、根拠なく試行を続けないでください。

6. 完了条件と納品

完成版のROMと同じソース・設定で、すべての必須シナリオを再実行してください。検証用の無敵状態や自動入力だけで通常プレイを検証済みにしないでください。 必須要件と試験の対応表、残る警告の理由、未確認事項を明示してください。実機で試していない場合は「実機未確認」と記載してください。

納品物は、ソース、使用ライブラリとツールの識別情報、編集可能な素材、再現可能なビルド・検証スクリプト、ROM、最終検証の証拠、起動方法・操作・既知の制限を記したREADMEです。 公開・外部送信は明示された範囲で行ってください。不要な中間ビルドや一時トレースは、その反復の確認後に削除してください。ただし、ソース、素材、最終成果物、必要な回帰証拠を削除しないでください。

実行環境や権限などの障害で必須検証を実施できない場合は、完了とせず、再現手順と必要な対応を具体的に報告してください。

完全なサンプルプログラム

各プログラムの本体、共通ヘッダー、指定ascii.cとその変換素材を同梱しています。説明中のm_*は本書専用ヘルパーです。ビルドと操作・期待結果を一組として参照してください。

一括ビルド用PowerShell / 実行結果・検証画像

英数字・記号の一覧表示 gb_font.c

目的:同梱の自作フォントで使える文字と、その表示方法を確認します。

処理の流れ:m_initで画面とフォントを準備し、m_textで文字列を行ごとに表示します。二重引用符は文字コード34をm_putへ渡して描きます。

操作と期待する結果:大文字26字、小文字26字、数字10字、記号30字が並びます。各文字が欠けずに表示されるか確認してください。操作は不要です。

英数字・記号の一覧表示: 大文字26字、小文字26字、数字10字、記号30字が並びます。各文字が欠けずに表示されるか確認してください。操作は不要です。
表示例:大文字26字、小文字26字、数字10字、記号30字が並びます。各文字が欠けずに表示されるか確認してください。操作は不要です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_font.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_font.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Display the letters, digits and punctuation from the supplied ascii.c font.
// Expected: Uppercase, lowercase, numbers and all 30 supplied punctuation glyphs
#include "gb_common.h"


// Lay out every supplied character category, yielding between rows. The final
// m_put uses character code 34 to display the double quote separately.
void main() {
    m_init();
    m_text(2,3,"FONT");
    m_text(2,5,"ABCDEFGHIJKLMNOP"); m_wait();
    m_text(2,6,"QRSTUVWXYZ"); m_wait();
    m_text(2,8,"0123456789"); m_wait();
    m_text(2,10,"abcdefghijklmnop"); m_wait();
    m_text(2,11,"qrstuvwxyz"); m_wait();
    m_text(2,13,"!#$%&'()*+,-./"); m_wait();
    m_text(2,14,":;<=>?@[]^_`{}~"); m_wait();
    m_put(16,13,34); m_wait();
    while (1) { m_wait();  }
}
最初の文字表示 gb_hello.c

目的:ROMを起動し、文字列と数値を背景に表示する最小の流れを学びます。

処理の流れ:m_initの後にm_textで挨拶を描き、m_numberに42を渡します。最後のループは画面を保ったままフレームを待ちます。

操作と期待する結果:HELLO WORLDと042が表示されます。042は3桁にそろえた十進数42です。

最初の文字表示: HELLO WORLDと042が表示されます。042は3桁にそろえた十進数42です。
表示例:HELLO WORLDと042が表示されます。042は3桁にそろえた十進数42です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_hello.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_hello.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Display a first message on screen.
// Expected: HELLO WORLD and 042
#include "gb_common.h"


// Initialize the display/font, write HELLO WORLD and a three-digit 042,
// then wait for frames so the result remains visible.
void main() {
    m_init();
    m_text(2,3,"HELLO");
    m_text(2,5,"HELLO WORLD");
    m_number(42);
    while (1) { m_wait();  }
}
桁あふれを避ける算術 gb_arithmetic.c

目的:8ビットに収まらない途中結果を16ビットで扱う方法を学びます。

処理の流れ:(u16)200 + 100で300を作り、10で割ってから表示用にu8へ変換します。足し算の前の型変換に注目してください。

操作と期待する結果:030、つまり計算結果30が表示されます。

桁あふれを避ける算術: 030、つまり計算結果30が表示されます。
表示例:030、つまり計算結果30が表示されます。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_arithmetic.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_arithmetic.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Variables, arithmetic and type conversion.
// Expected: 030
#include "gb_common.h"


// Widen before adding 200 + 100, divide the 16-bit sum, and display 30.
// The cast demonstrates avoiding an 8-bit intermediate result.
void main() {
    m_init();
    m_text(2,3,"ARITHMETIC");
    u16 sum;
    sum = (u16)200 + 100;
    m_number((u8)(sum / 10));
    while (1) { m_wait();  }
}
条件分岐と繰り返し gb_control.c

目的:for、continue、while、do/while、switchを小さな計算で試します。

処理の流れ:0~7を加算しますが3はcontinueで飛ばすため合計は25です。whileで30、do/whileで32まで増やし、switchのcase 32で42に置き換えます。

操作と期待する結果:042が表示されます。各ループと分岐を順番に通った計算結果です。

条件分岐と繰り返し: 042が表示されます。各ループと分岐を順番に通った計算結果です。
表示例:042が表示されます。各ループと分岐を順番に通った計算結果です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_control.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_control.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Conditional branches and loops.
// Expected: 042
#include "gb_common.h"


// Exercise loop control and a final branch, then display the selected value 42.
void main() {
    m_init();
    m_text(2,3,"CONTROL");
    u8 i; u8 sum; sum=0;
    for (i=0;i<8;i++) { if (i==3) continue; sum=(u8)(sum+i); }
    while (sum<30) { sum++; }
    do { sum++; } while (sum<32);
    switch (sum) { case 32: sum=42; break; default: sum=0; break; }
    m_number(sum);
    while (1) { m_wait();  }
}
関数・配列・構造体・ポインタ gb_aggregate.c

目的:複数の値をまとめ、関数やポインタを通して扱う方法を学びます。

処理の流れ:Pointの(10,20)を別の構造体へコピーし、ポインタから配列に1~4を書きます。addの戻り値30に配列の1、2、4と定数5を加えます。

操作と期待する結果:合計42が042と表示されます。構造体コピー、ptr[i]による書き込み、関数の戻り値を順に追ってください。

関数・配列・構造体・ポインタ: 合計42が042と表示されます。構造体コピー、ptr[i]による書き込み、関数の戻り値を順に追ってください。
表示例:合計42が042と表示されます。構造体コピー、ptr[i]による書き込み、関数の戻り値を順に追ってください。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_aggregate.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_aggregate.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Functions, arrays, structures and pointers.
// Expected: 042
#include "gb_common.h"

typedef struct { u8 x; u8 y; } Point;
// Add two byte values and explicitly narrow the returned result to u8.
u8 add(u8 a,u8 b) { return (u8)(a+b); }
// Copy a structure, fill an array through a pointer, and combine selected values
// through add() to produce 42. Keep the final screen visible in the idle loop.
void main() {
    m_init();
    m_text(2,3,"AGGREGATE");
    Point a; Point b; u8 i; u8 data[4]; u8* ptr;
    a.x=10; a.y=20; b=a; ptr=data;
    for(i=0;i<4;i++) ptr[i]=(u8)(i+1);
    m_number((u8)(add(b.x,b.y)+ptr[0]+ptr[1]+ptr[3]+5));
    while (1) { m_wait();  }
}
ビット操作とバッファのコピー gb_bits_memory.c

目的:複数のフラグを1バイトにまとめ、バッファを別の領域へコピーします。

処理の流れ:__memsetで4バイトを0にし、ビット3を立て、ビット1を反転します。__memcpyで4バイトすべてを別配列にコピーします。

操作と期待する結果:コピー先の先頭バイトが010になります。ビット3の8とビット1の2を合わせた十進数10です。

ビット操作とバッファのコピー: コピー先の先頭バイトが010になります。ビット3の8とビット1の2を合わせた十進数10です。
表示例:コピー先の先頭バイトが010になります。ビット3の8とビット1の2を合わせた十進数10です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe  kitaq-docs/samples/gb_bits_memory.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_bits_memory.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Bit operations and memory copying.
// Expected: 010
#include "gb_common.h"
void __memset(void* dst,u8 value,u16 len);
void __memcpy(void* dst,const void* src,u16 len);
void __bit_set(u8* base,u16 bit);
void __bit_toggle(u8* base,u16 bit);

// Clear the source bytes, set bits 3 and 1, copy the buffer, and display 8 + 2 = 10.
void main() {
    m_init();
    m_text(2,3,"BITS MEMORY");
    u8 data[4]; u8 copy[4];
    __memset(data,0,4); __bit_set(data,3); __bit_toggle(data,1);
    __memcpy(copy,data,4); m_number(copy[0]);
    while (1) { m_wait();  }
}
押した瞬間だけを数える gb_input.c

目的:ボタンを押し続ける状態と、新しく押した瞬間を区別します。

処理の流れ:毎フレームinput_updateを呼び、input_pressed(BTN_A)が真のときだけカウンターを増やします。

操作と期待する結果:最初は000です。Aを押すと001になり、押し続けても増えません。離して押し直すと002になります。255の次は000に戻ります。

押した瞬間だけを数える: 最初は000です。Aを押すと001になり、押し続けても増えません。離して押し直すと002になります。255の次は000に戻ります。
表示例:最初は000です。Aを押すと001になり、押し続けても増えません。離して押し直すと002になります。255の次は000に戻ります。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/input.c kitaq-docs/samples/gb_input.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_input.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Distinguish a new button press from a held button.
// Expected: 000 initially; A increments once per press
#include "gb_common.h"
#include "input.h"
u8 manual_count;
// Sample buttons once per frame and increment only on a new A press.
// Holding A does not repeatedly increment; the byte-sized counter wraps after 255.
void main() {
    m_init();
    m_text(2,3,"INPUT");
    input_init();
    m_number(0);
    while (1) { m_wait(); input_update(); if(input_pressed(BTN_A)) { manual_count++; m_number(manual_count); } }
}
Q8.8固定小数点の掛け算 gb_fixed.c

目的:整数とQ8.8固定小数点の相互変換、および掛け算の基本を学びます。

処理の流れ:3と4をfix_from_intでQ8.8へ変換し、fix_mulで掛け、fix_to_intで整数へ戻します。

操作と期待する結果:3×4の結果12が012と表示されます。画面に出るのは内部の256倍された値ではなく、整数へ戻した値です。

Q8.8固定小数点の掛け算: 3×4の結果12が012と表示されます。画面に出るのは内部の256倍された値ではなく、整数へ戻した値です。
表示例:3×4の結果12が012と表示されます。画面に出るのは内部の256倍された値ではなく、整数へ戻した値です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/fixed.c kitaq-docs/samples/gb_fixed.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_fixed.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Calculate with signed Q8.8 fixed-point values.
// Expected: 012
#include "gb_common.h"
#include "fixed.h"

// Convert 3 and 4 to Q8.8, multiply them, convert the result back to an
// integer, and display 12.
void main() {
    m_init();
    m_text(2,3,"FIXED");
    fix8 a; fix8 b; a=fix_from_int(3); b=fix_from_int(4);
    m_number((u8)fix_to_int(fix_mul(a,b)));
    while (1) { m_wait();  }
}
固定長プールからの生成 gb_entity.c

目的:動的メモリ確保を使わず、決まった個数の領域からオブジェクトを作ります。

処理の流れ:entity_initで初期化し、種類1のオブジェクトを(20,30)に作ります。IDが失敗値0xFFでないことを確認してvxへ2を設定します。

操作と期待する結果:001は使用中オブジェクトの個数です。IDや座標ではありません。この例は更新・描画を行わないので、動くキャラクターは表示されません。

固定長プールからの生成: 001は使用中オブジェクトの個数です。IDや座標ではありません。この例は更新・描画を行わないので、動くキャラクターは表示されません。
表示例:001は使用中オブジェクトの個数です。IDや座標ではありません。この例は更新・描画を行わないので、動くキャラクターは表示されません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/entity.c kitaq-docs/samples/gb_entity.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_entity.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Allocate an entity from a fixed-capacity object pool.
// Expected: 001
#include "gb_common.h"
#include "entity.h"

// Create one entity and check the 0xFF allocation-failure sentinel before
// accessing its velocity. Display the pool occupancy, not the entity ID.
void main() {
    m_init();
    m_text(2,3,"ENTITY");
    u8 id; entity_init(); id=entity_create(1,20,30);
    if(id!=0xFF) entity_get(id)->vx=2; m_number(entity_count_active());
    while (1) { m_wait();  }
}
診断値をRAMへ記録する gb_debug.c

目的:画面表示とは別に、ゲーム中の変数を診断用バッファへ残します。

処理の流れ:debug_initで初期化し、フレームタグ1を設定してHPという名前で42を記録します。

操作と期待する結果:画面の001は記録件数です。HP=42という値そのものは画面ではなく診断バッファにあります。画面だけでは記録内容までは確認できません。

診断値をRAMへ記録する: 画面の001は記録件数です。HP=42という値そのものは画面ではなく診断バッファにあります。画面だけでは記録内容までは確認できません。
表示例:画面の001は記録件数です。HP=42という値そのものは画面ではなく診断バッファにあります。画面だけでは記録内容までは確認できません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/debug.c kitaq-docs/samples/gb_debug.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_debug.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Record diagnostic values in the debug trace.
// Expected: 001; trace log contains HP=42
#include "gb_common.h"
#include "debug.h"

// Record HP = 42 with frame tag 1 and display the number of trace entries.
// The trace buffer contains the value; the screen displays the count, not HP.
void main() {
    m_init();
    m_text(2,3,"DEBUG");
    debug_init(); debug_set_frame(1); debug_trace_u8("HP",42);
    m_number(debug_get_trace_count());
    while (1) { m_wait();  }
}
座標履歴のリングバッファ gb_chain.c

目的:キャラクターの軌跡などに使う座標履歴の格納方法を学びます。

処理の流れ:4点分の配列をchain_initに渡し、chain_push_headで(20,30)を1点追加します。

操作と期待する結果:042は処理後に表示する固定の目印です。保存した座標や件数を表していません。履歴の内容を確かめるにはtrailとpointsを観察します。追従する胴体の描画例ではありません。

座標履歴のリングバッファ: 042は処理後に表示する固定の目印です。保存した座標や件数を表していません。履歴の内容を確かめるにはtrailとpointsを観察します。追従する胴体の描画例ではありません。
表示例:042は処理後に表示する固定の目印です。保存した座標や件数を表していません。履歴の内容を確かめるにはtrailとpointsを観察します。追従する胴体の描画例ではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/chain.c kitaq-docs/samples/gb_chain.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_chain.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Store a position history in a ring buffer.
// Expected: 042; history contains (20,30)
#include "gb_common.h"
#include "chain.h"
Chain trail; ChainPoint points[4];
// Attach four history slots and record one point. The displayed 42 is a fixed
// completion marker; inspect the chain to verify the stored (20,30) point.
void main() {
    m_init();
    m_text(2,3,"CHAIN");
    chain_init(&trail,points,4); chain_push_head(&trail,20,30);
    m_number(42);
    while (1) { m_wait();  }
}
ライブラリのフレームカウンター gb_system.c

目的:フレーム待機とライブラリが管理する経過フレーム数の関係を確認します。

処理の流れ:system_initの後にsystem_wait_vblankを2回呼び、system_get_frame8の値を表示します。

操作と期待する結果:002が表示されます。その後のループは本書のm_waitを使うため、この表示値は増え続けません。

ライブラリのフレームカウンター: 002が表示されます。その後のループは本書のm_waitを使うため、この表示値は増え続けません。
表示例:002が表示されます。その後のループは本書のm_waitを使うため、この表示値は増え続けません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/system.c kitaq-docs/samples/gb_system.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_system.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Count completed frame waits.
// Expected: 002
#include "gb_common.h"
#include "system.h"

// Reset the library frame count, perform two counted waits, and display 2.
// Subsequent m_wait calls leave that library counter unchanged.
void main() {
    m_init();
    m_text(2,3,"SYSTEM");
    system_init(); system_wait_vblank(); system_wait_vblank();
    m_number(system_get_frame8());
    while (1) { m_wait();  }
}
現在のシーンIDを選ぶ gb_scene.c

目的:タイトル画面やゲーム本編などを区別するシーン番号の選択方法を学びます。

処理の流れ:0で初期化された2件のSceneDefを登録し、scene_change(1)でID 1を選択します。

操作と期待する結果:001はscene_get_currentが返す現在のIDです。この例にはシーンのコールバックや画面切り替えの描画は登録していません。

現在のシーンIDを選ぶ: 001はscene_get_currentが返す現在のIDです。この例にはシーンのコールバックや画面切り替えの描画は登録していません。
表示例:001はscene_get_currentが返す現在のIDです。この例にはシーンのコールバックや画面切り替えの描画は登録していません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/scene.c kitaq-docs/samples/gb_scene.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_scene.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Switch the current scene ID.
// Expected: 001; no callbacks registered
#include "gb_common.h"
#include "scene.h"
SceneDef manual_scenes[2];
// Register two zero-initialized scene entries, select ID 1 and display it.
// No callbacks are registered in this example.
void main() {
    m_init();
    m_text(2,3,"SCENE");
    scene_init(manual_scenes,2); scene_change(1);
    m_number(scene_get_current());
    while (1) { m_wait();  }
}
世界座標から画面座標へ gb_camera.c

目的:カメラ位置を引いて、ゲーム世界の位置を画面内の位置へ変換します。

処理の流れ:Camera_SetにQ8.8の2560、つまりX=10ピクセルを渡します。世界座標X=52をCamera_WorldToScreenXで変換します。

操作と期待する結果:52−10=42なので042と表示されます。このプログラムは座標変換の例で、背景を動かすものではありません。

世界座標から画面座標へ: 52−10=42なので042と表示されます。このプログラムは座標変換の例で、背景を動かすものではありません。
表示例:52−10=42なので042と表示されます。このプログラムは座標変換の例で、背景を動かすものではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/scroll.c kitaqgb/lib/camera.c kitaq-docs/samples/gb_camera.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_camera.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Convert world coordinates to screen coordinates.
// Expected: 042: world x=52 minus camera x=10
#include "gb_common.h"
#include "camera.h"
Camera8_8 cam;
// Set the camera X coordinate to 10 pixels in Q8.8 (10 * 256), then subtract
// it from world X = 52 to display screen X = 42.
void main() {
    m_init();
    m_text(2,3,"CAMERA");
    Camera_Init(&cam); Camera_Set(&cam,2560,0);
    m_number((u8)Camera_WorldToScreenX(&cam,52));
    while (1) { m_wait();  }
}
RLEデータをRAMへ展開する gb_rle.c

目的:同じ値の繰り返しを、個数と値の組で保存する簡単な圧縮形式を試します。

処理の流れ:packedの{3,42,0}は「42を3回、0の個数で終了」です。rle_decodeへ3バイトの展開先を渡します。

操作と期待する結果:最後のバイトout[2]が042と表示されます。展開先の3バイトはすべて42になる想定です。

RLEデータをRAMへ展開する: 最後のバイトout[2]が042と表示されます。展開先の3バイトはすべて42になる想定です。
表示例:最後のバイトout[2]が042と表示されます。展開先の3バイトはすべて42になる想定です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/rle.c kitaq-docs/samples/gb_rle.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_rle.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Decode run-length encoded data.
// Expected: 042; three bytes each equal to 42
#include "gb_common.h"
#include "rpg.h"
__prg_rom u8 packed[] = {3,42,0};
// Decode count/value pair (3,42), terminated by a zero count, into a
// three-byte buffer and display the final decoded byte.
void main() {
    m_init();
    m_text(2,3,"RLE");
    u8 out[3]; rle_decode(out,packed); m_number(out[2]);
    while (1) { m_wait();  }
}
盤面のセルを読み書きする gb_board.c

目的:マス目のゲームで使う2次元座標と、保存用配列の関係を学びます。

処理の流れ:16バイトの配列を4×4の盤面として登録し、0でクリアしてからセル(2,1)へ42を書きます。同じ座標を読み返します。

操作と期待する結果:読み出した42が042と表示されます。4×4の盤面そのものを描画する例ではありません。

盤面のセルを読み書きする: 読み出した42が042と表示されます。4×4の盤面そのものを描画する例ではありません。
表示例:読み出した42が042と表示されます。4×4の盤面そのものを描画する例ではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/slg_board.c kitaq-docs/samples/gb_board.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_board.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Read and write cells in a game board.
// Expected: 042
#include "gb_common.h"
#include "slg.h"
SLGBoard board; u8 cells[16];
// Create a cleared 4-by-4 board in caller-owned storage, write 42 at (2,1),
// and display the value read back from the same cell.
void main() {
    m_init();
    m_text(2,3,"BOARD");
    slg_board_init(&board,4,4,cells); slg_board_clear(&board,0);
    slg_board_set(&board,2,1,42); m_number(slg_board_get(&board,2,1));
    while (1) { m_wait();  }
}
矩形ボディを1ステップ進める gb_physics.c

目的:ワールドと矩形ボディを初期化し、速度から位置を更新する最小の例です。

処理の流れ:X=40、Y=40にボディを作り、速度を(2,0)、重力を0にしてkq2d_stepを1回呼びます。

操作と期待する結果:更新後のX=42が042と表示されます。この例の位置は整数ピクセルです。箱の形や連続したアニメーションを描く例ではありません。

矩形ボディを1ステップ進める: 更新後のX=42が042と表示されます。この例の位置は整数ピクセルです。箱の形や連続したアニメーションを描く例ではありません。
表示例:更新後のX=42が042と表示されます。この例の位置は整数ピクセルです。箱の形や連続したアニメーションを描く例ではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/fixed.c kitaqgb/lib/physics2d.c kitaq-docs/samples/gb_physics.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_physics.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Advance a rectangular body through one physics step.
// Expected: 042: integer pixel coordinates
#include "gb_common.h"
#include "physics2d.h"
KQWorld2D world; KQBody2D boxes[1];
// Create one box at X = 40, set horizontal velocity 2 and zero gravity,
// then display its X coordinate after a single physics step.
void main() {
    m_init();
    m_text(2,3,"PHYSICS");
    kq2d_world_init(&world,boxes,1);
    kq2d_body_init(&boxes[0],40,40,4,4);
    kq2d_body_set_velocity(&boxes[0],2,0);
    world.gravity_x=0; world.gravity_y=0;
    kq2d_step(&world); m_number((u8)boxes[0].x);
    while (1) { m_wait();  }
}
バンクを指定してROMを読む gb_bank.c

目的:ROMのデータをRAMへコピーするAPIの引数を学びます。

処理の流れ:値7と42の2バイトをfar_data_readでcopiedへ読み込みます。バンク引数は0、コピー長は2です。

操作と期待する結果:コピー先の2バイト目が042と表示されます。この例は固定ROMの読み出しで、異なる切替バンク間の転送を実証するものではありません。

バンクを指定してROMを読む: コピー先の2バイト目が042と表示されます。この例は固定ROMの読み出しで、異なる切替バンク間の転送を実証するものではありません。
表示例:コピー先の2バイト目が042と表示されます。この例は固定ROMの読み出しで、異なる切替バンク間の転送を実証するものではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/bank.c kitaq-docs/samples/gb_bank.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_bank.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Read data using an explicit ROM bank.
// Expected: 042; explicit bank argument 0
#include "gb_common.h"
#include "bank.h"
__prg_rom u8 values[] = {7,42}; u8 copied[2];
// Copy two ROM bytes using bank argument zero and display the second byte.
// This introduces the API; it does not test switching between distinct payload banks.
void main() {
    m_init();
    m_text(2,3,"BANK");
    far_data_read(0,values,copied,2); m_number(copied[1]);
    while (1) { m_wait();  }
}
素材IDからデータを読み込む gb_asset.c

目的:素材の位置と長さを表へ登録し、IDで参照する仕組みを学びます。

処理の流れ:ID 0に種類RAW、バンク0、{7,42}のポインタ、長さ2を登録します。asset_load_rawで2バイトのRAMへ読み込みます。

操作と期待する結果:コピー先の2バイト目が042と表示されます。画像表示ではなく、生のバイト列を素材IDで取得する例です。

素材IDからデータを読み込む: コピー先の2バイト目が042と表示されます。画像表示ではなく、生のバイト列を素材IDで取得する例です。
表示例:コピー先の2バイト目が042と表示されます。画像表示ではなく、生のバイト列を素材IDで取得する例です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/asset.c kitaq-docs/samples/gb_asset.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_asset.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Load asset bytes into RAM by asset ID.
// Expected: 042
#include "gb_common.h"
#include "asset.h"
AssetDesc table[1]; __prg_rom u8 values[] = {7,42}; u8 copied[2];
// Register a two-byte raw asset, copy it into RAM and display its second byte.
// This example uses bank zero and a destination sized for the complete payload.
void main() {
    m_init();
    m_text(2,3,"ASSET");
    table[0].type=ASSET_TYPE_RAW; table[0].bank=0;
    table[0].ptr=values; table[0].len=2;
    asset_set_table(0,table,1); asset_load_raw(0,copied,2);
    m_number(copied[1]);
    while (1) { m_wait();  }
}
弾をプールへ1個登録する gb_danmaku.c

目的:弾の管理領域を初期化し、発射後の使用数を調べます。

処理の流れ:danmaku_resetの後にdanmaku_spawn(40,40,16,0)を呼び、dm_countを表示します。

操作と期待する結果:001は登録された弾の個数です。この例には更新ループや背景への弾描画処理がないため、画面に弾が飛ぶ様子は表示されません。

弾をプールへ1個登録する: 001は登録された弾の個数です。この例には更新ループや背景への弾描画処理がないため、画面に弾が飛ぶ様子は表示されません。
表示例:001は登録された弾の個数です。この例には更新ループや背景への弾描画処理がないため、画面に弾が飛ぶ様子は表示されません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/danmaku.c kitaq-docs/samples/gb_danmaku.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_danmaku.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Allocate a bullet and inspect the pool count.
// Expected: 001; numeric pool example, no bullet BG compositor
#include "gb_common.h"
#include "danmaku.h"

// Reset the bullet pool, spawn one bullet and display the active count.
// This numeric example does not install the background bullet compositor.
void main() {
    m_init();
    m_text(2,3,"DANMAKU");
    danmaku_reset(); danmaku_spawn(40,40,16,0);
    m_number(dm_count);
    while (1) { m_wait();  }
}
CGB背景パレットの色を設定する gb_cgb_palette.c

目的:Game Boy ColorのRGB値で、背景と文字の色を指定します。

処理の流れ:VBlank中にLCDを止め、パレット0の色0を白(31,31,31)、色3を紫(12,0,22)へ設定してから表示を再開します。

操作と期待する結果:CGBモードで実行すると白い背景に紫色の文字と042が表示されます。DMGの白黒表示では色の結果を確認できません。

CGB背景パレットの色を設定する: CGBモードで実行すると白い背景に紫色の文字と042が表示されます。DMGの白黒表示では色の結果を確認できません。
表示例:CGBモードで実行すると白い背景に紫色の文字と042が表示されます。DMGの白黒表示では色の結果を確認できません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/cgb_palette.c kitaq-docs/samples/gb_cgb_palette.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_cgb_palette.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb
c
// Set Game Boy Color palette entries.
// Expected: 042 in purple on white; CGB mode
#include "gb_common.h"
#include "cgb_palette.h"

// Set palette-zero background colors to white and purple, then display 42.
// Run the ROM in CGB mode to observe the color result.
void main() {
    m_init();
    m_text(2,3,"CGB PALETTE");
    // Disable the LCD during VBlank so both palette bytes remain accessible.
    m_wait(); M_LCDC = 0;
    cgb_bg_rgb(0,0,31,31,31); cgb_bg_rgb(0,3,12,0,22);
    M_LCDC = 0x91;
    m_number(42);
    while (1) { m_wait();  }
}
背景を横へスクロールする gb_scroll.c

目的:背景の内容を書き換えず、表示開始位置を動かす方法を学びます。

処理の流れ:毎フレームphaseを1増やし、Scroll_SetBgへ渡します。8ビット値なので255の次は0になります。

操作と期待する結果:SCROLLという文字が左へ動きます。画面の数字ではなく、時間を追って文字の位置が変わることを確認してください。

背景を横へスクロールする: SCROLLという文字が左へ動きます。画面の数字ではなく、時間を追って文字の位置が変わることを確認してください。
表示例:SCROLLという文字が左へ動きます。画面の数字ではなく、時間を追って文字の位置が変わることを確認してください。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/scroll.c kitaq-docs/samples/gb_scroll.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_scroll.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Scroll the background horizontally.
// Expected: title moves left as SCX increases
#include "gb_common.h"
#include "scroll.h"
u8 phase;
// Advance an 8-bit horizontal scroll position once per frame, wrapping naturally
// after 255. Background motion demonstrates the changing scroll register.
void main() {
    m_init();
    m_text(2,3,"SCROLL");
    Scroll_SetBg(0,0);
    while (1) { m_wait(); phase++; Scroll_SetBg(phase,0); }
}
タイル更新をキューへ積む gb_vram_queue.c

目的:描画の要求と、VRAMへ実際に書き込むタイミングを分けます。

処理の流れ:vram_queue_bg_tileでタイル座標(3,8)へ番号52を予約し、vram_flushで転送します。同梱フォントの番号52は数字4です。

操作と期待する結果:背景のタイル座標(3,8)に数字4が表示されます。全画面の数値カウンターではなく、1マスへの描画結果です。

タイル更新をキューへ積む: 背景のタイル座標(3,8)に数字4が表示されます。全画面の数値カウンターではなく、1マスへの描画結果です。
表示例:背景のタイル座標(3,8)に数字4が表示されます。全画面の数値カウンターではなく、1マスへの描画結果です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/vram.c kitaq-docs/samples/gb_vram_queue.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_vram_queue.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Queue a VRAM update and flush it at VBlank.
// Expected: 4 at (3,8)
#include "gb_common.h"
#include "vram.h"

// Queue font tile 52 (the digit 4) at tile coordinate (3,8), then wait for
// VBlank and execute the queued transfer.
void main() {
    m_init();
    m_text(2,3,"VRAM QUEUE");
    vram_init();
    vram_queue_bg_tile(3,8,52); vram_flush();
    while (1) { m_wait();  }
}
OAM DMAでスプライトを表示する gb_sprite.c

目的:背景とは別に動かせるスプライトの割り当てと表示を学びます。

処理の流れ:空のプールから最初のスロット0を確保し、タイル65と位置(70,80)を設定します。OBJ表示を有効にして、毎フレームシャドーOAMを転送します。

操作と期待する結果:指定位置に文字Aのスプライトが表示されます。位置は固定です。実際のゲームでは割り当て失敗値を確認してからIDを使います。

OAM DMAでスプライトを表示する: 指定位置に文字Aのスプライトが表示されます。位置は固定です。実際のゲームでは割り当て失敗値を確認してからIDを使います。
表示例:指定位置に文字Aのスプライトが表示されます。位置は固定です。実際のゲームでは割り当て失敗値を確認してからIDを使います。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/sprite.c kitaq-docs/samples/gb_sprite.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_sprite.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Display a sprite through the OAM DMA path.
// Expected: letter A at sprite position
#include "gb_common.h"
#include "sprite.h"

// Allocate the first sprite, choose font tile 65 (A), set its position and
// enable sprite display. Flush shadow OAM after each frame wait.
void main() {
    m_init();
    m_text(2,3,"SPRITE");
    // The freshly reset pool guarantees slot zero for this first allocation.
    // In a running game, check the allocation-failure sentinel before using the returned ID.
    sprite_init(); sprite_set_tile(sprite_alloc(),65);
    sprite_set_pos(0,70,80); M_LCDC=(u8)(M_LCDC|2);
    while (1) { m_wait(); sprite_flush_oam_now(); }
}
シード付き疑似乱数 gb_rng.c

目的:同じ初期値から同じ乱数列を再現する方法を学びます。

処理の流れ:rng_seed(1234)で初期化し、rng_range(10)を1回呼びます。上限10は結果に含まれません。

操作と期待する結果:000~009のいずれかが表示されます。同じプログラムを同じシードで起動すれば同じ値になります。毎フレーム変化する例ではありません。

シード付き疑似乱数: 000~009のいずれかが表示されます。同じプログラムを同じシードで起動すれば同じ値になります。毎フレーム変化する例ではありません。
表示例:000~009のいずれかが表示されます。同じプログラムを同じシードで起動すれば同じ値になります。毎フレーム変化する例ではありません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/rng.c kitaq-docs/samples/gb_rng.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_rng.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Generate a repeatable pseudorandom value from a seed.
// Expected: value 000 through 009; same seed repeats
#include "gb_common.h"
#include "rpg.h"

// Seed the generator with 1234 and display one value in [0,10).
// Restarting with the same seed reproduces the sequence.
void main() {
    m_init();
    m_text(2,3,"RNG");
    rng_seed(1234); m_number(rng_range(10));
    while (1) { m_wait();  }
}
ゲーム進行フラグ gb_flags.c

目的:イベントを達成したかなどの真偽値を、フラグ番号で管理します。

処理の流れ:フラグ12をクリアしてから立て、flag_getで読み返します。

操作と期待する結果:001はフラグ12が真であることを示します。この例はRAM上のフラグ操作で、電源を切っても残るセーブ処理は行いません。

ゲーム進行フラグ: 001はフラグ12が真であることを示します。この例はRAM上のフラグ操作で、電源を切っても残るセーブ処理は行いません。
表示例:001はフラグ12が真であることを示します。この例はRAM上のフラグ操作で、電源を切っても残るセーブ処理は行いません。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/flags.c kitaq-docs/samples/gb_flags.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_flags.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Read and write RAM-backed game-state flags.
// Expected: 001
#include "gb_common.h"
#include "rpg.h"

// Clear and set flag 12, then display its Boolean value. This does not write
// a save file or demonstrate persistence across power cycles.
void main() {
    m_init();
    m_text(2,3,"FLAGS");
    flag_clear(12); flag_set(12); m_number(flag_get(12));
    while (1) { m_wait();  }
}
円形ボディを1ステップ進める gb_circle.c

目的:円の物理ワールドへボディを登録し、速度による位置更新を試します。

処理の流れ:半径4、位置(40,40)、速度(2,0)の円を有効にします。重力0、linear_damping_q8=255でkq2dc_stepを1回呼びます。

操作と期待する結果:更新後のX=42が042と表示されます。円の輪郭や衝突アニメーションを描画する例ではなく、整数座標の更新例です。

円形ボディを1ステップ進める: 更新後のX=42が042と表示されます。円の輪郭や衝突アニメーションを描画する例ではなく、整数座標の更新例です。
表示例:更新後のX=42が042と表示されます。円の輪郭や衝突アニメーションを描画する例ではなく、整数座標の更新例です。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/physics2d_circle.c kitaq-docs/samples/gb_circle.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_circle.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Advance a circular body through one physics step.
// Expected: 042 after one step; this example uses integer pixels
#include "gb_common.h"
#include "physics2d_circle.h"
KQCircleBody2D balls[1]; KQCircleWorld2D world;
// Activate one circle at X = 40 with horizontal velocity 2 and zero gravity.
// Advance one step and display its resulting integer-pixel X coordinate.
void main() {
    m_init();
    m_text(2,3,"CIRCLE");
    kq2dc_world_init(&world,balls,1);
    balls[0].active=1; balls[0].x=40; balls[0].y=40;
    balls[0].radius=4; balls[0].inv_mass_q8=256;
    balls[0].vx=2; balls[0].vy=0;
    world.gravity_x=0; world.gravity_y=0; world.linear_damping_q8=255;
    kq2dc_step(&world); m_number((u8)balls[0].x);
    while (1) { m_wait();  }
}
フレーム更新で効果音を再生する gb_sound.c

目的:効果音データを開始し、毎フレームの更新で最後まで進める方法を学びます。

処理の流れ:Audio_Initの後、Audio_PlaySFXへtoneとチャンネルID 1を渡します。メインループからAudio_Updateを1フレームに1回呼びます。

操作と期待する結果:042を表示しながら短いパルス波の効果音が鳴ります。042は固定の目印で、音量や再生位置ではありません。音の確認には音声出力または録音を使ってください。

フレーム更新で効果音を再生する: 042を表示しながら短いパルス波の効果音が鳴ります。042は固定の目印で、音量や再生位置ではありません。音の確認には音声出力または録音を使ってください。
表示例:042を表示しながら短いパルス波の効果音が鳴ります。042は固定の目印で、音量や再生位置ではありません。音の確認には音声出力または録音を使ってください。

ソースを保存 / 検証状況

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/audio_hwregs_gb.c kitaqgb/lib/audio.c kitaq-docs/samples/gb_sound.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_sound.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Play a Game Boy pulse-channel sound effect.
// Expected: 042 and a short pulse tone
#include "gb_common.h"
#include "audio.h"
__prg_rom u8 tone[] = { 24,0xF0,24,0xE0,24,0xC0,24,0xA0,24,0x80,24,0x60,0 };
// Start a pulse-channel effect and service Audio_Update once per frame
// so the effect stream advances while 42 remains on screen.
void main() {
    m_init();
    m_text(2,3,"SOUND");
    Audio_Init(); Audio_PlaySFX(tone,1); m_number(42);
    while (1) { m_wait(); Audio_Update(); }
}

組み込み命令辞典

全149項目。各項目では、機能、引数、戻り値、制約、使用例と期待する結果を説明します。

asset.h — 素材IDとデータ記述表

素材ライブラリは、ROMバンクに置いた素材を8ビットのIDで検索します。asset.cを取り込み、FC版ではbank.cも取り込みます。パックされた6バイトのAssetDescには、1バイトの種別、1バイトの素材バンク番号、16ビットのアドレス、バイト単位の長さを格納します。一覧と素材本体は別のバンクに配置できます。

検索前に一覧を登録してください。登録情報は共有のグローバル状態なので、検索と同時に変更してはいけません。ライブラリはIDとasset_getの出力ポインタを確認しますが、登録した一覧や素材のアドレス、カートリッジ内容の正しさまでは検査しません。

RAWまたはTILES種別の素材を検索し、__vram_copyでdesc.lenバイトをすべて転送します。呼び出す前に素材本体のROMバンクを選んでください。GB版はdesc.ptrを直接使い、転送時にdesc.bankを選択する処理を持ちません。

__farmemcpy

ROMバンクを一時的に切り替え、srcからdstへアドレスの小さい方からlenバイトをコピーし、コンパイラが保持していたROMバンクへ戻します。この機種では__farmemcpyと__far_memcpyは同じ実装を使います。

書式

c
void __farmemcpy(void* dst, u8 bank, const void* src, u16 len);

引数

dst

書き込み先のメモリです。実際のコピー量を収められる領域を確保してください。0バイト転送以外では、ヌルポインタや無効な書き込み先を検出する処理はありません。

bank

コピー元ROMのバンクを選ぶ8ビット値です。カートリッジとコンパイラのバンク配置に対応する番号を使います。コピー元アドレスとは別に指定します。

src

選んだROMバンク内の読み出せるバイト列を指す近ポインタです。ポインタ自体にはバンク番号を含みません。

len

バイト数を0~65535で指定します。0ならメモリはコピーしませんが、バンクの選択と復帰の処理は実行します。

戻り値

ありません(void)。

使用条件と制限

コピー元全体が目的のROM配置で読め、コピー先全体が書き込み可能な範囲になるようにしてください。バンク窓の端で次のバンクへ進む処理、memmoveのような重なりへの対応、VRAMの安全な書き込み制御はありません。VRAMには描画用の転送APIを使います。

サンプルの呼び出し側コードは固定バンク0に配置し、コピー元の切り替えで実行中のコードが見えなくなることを防ぎます。転送は同期処理です。転送中に割り込み側から別のバンク切り替えコピーを実行したり、この関数だけでアプリ全体の割り込み安全性が確保されると考えたりしないでください。

GB版の補助処理は、コンパイラが保持するROMバンク情報から復帰先を決めます。その情報を更新する__bankswitchを使ってください。マッパーへの直接書き込みでは更新されません。HRAMの復帰バンク保存領域は共有なので、入れ子の転送で上書きする使い方は安全ではありません。

使用例

c
__farmemcpy(buffer,payload_bank,payload_pointer,32);
expect(*asset_bank_marker==0xA1,27); // Source bank 2 was selected only for the copy.
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),28);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

バンク1を選択した状態から、素材のバンク2にある絵柄2枚、32バイトをコピーします。コピーした全バイト、バンク1への復帰、長さ0のコピーで確認値0x55が変わらないことを検査します。

期待画像では、赤い横一列がピクセル座標(16,32)から始まり、72×8ピクセルを占めます。8×8の三角形、1ピクセル枠の8×8の正方形、左4列を塗ったタイルの順に、3回繰り返します。三角形は左上の1ピクセルから最下段の8ピクセルまで広がります。

GB版では、(16,64)の青い三角形と枠が__farmemcpyの結果、(16,96)の緑の組が__far_memcpyの結果です。各組は16×8ピクセルです。CGBはこの配色、DMGは同じ形を白地に黒で表示します。

期待する表示はFAILED CHECKS 000とFIRST FAILURE 00です。検証では300フレーム実行し、文字、図形の形、色を期待値と比較します。これはエミュレータ上の検証であり、実機試験ではありません。

__farmemcpy: DMG
DMG
__farmemcpy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\asset_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\asset_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/asset.h:17

__vram_copy

__vram_copyは、コンパイラで__vram_memcpyと同じ処理に展開される別名です。長さの単位は同じくバイトで、即時のCPU転送とLCDへのアクセス条件も同じです。

srcからdstへlenバイトを、アドレスが増える順に直ちにコピーします。転送が終わってから戻り、キューへの登録は行いません。転送元と転送先は1バイトずつ進み、タイルマップの行間隔やタイル形式の変換は扱いません。

書式

c
void __vram_copy(u16 dst, const void* src, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

src

転送元のバイトポインターです。呼び出し中は転送元のメモリーとバンクの対応を有効に保ってください。データはその場で読み、後のflush用に保持しません。転送元バンクの自動切り替えも行いません。前方へのコピーなので、memmoveのような重なりへの対応はありません。範囲が重ならないようにしてください。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_copy(0x9862,strip,3); // The copy alias uses the same CPU transfer.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_copyでタイル座標(2,3)、ピクセル座標(16,24)に3バイトのマップデータを書き込みます。24×8の領域に、左から[1,2,3]のパターンが並ぶのが期待結果です。隣接するセルは空白のままです。完成プログラムでは、検証するコピーや埋め込みを1行ずつ並べています。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_copy: DMG
DMG
__vram_copy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/asset.h:18

bank.h — バンク番号付きのコード・データアクセス

ROMバンクは、カートリッジ内の記憶領域をCPUのアドレス空間へ割り当てる単位です。同じCPUアドレスに別のバンクを表示できるため、16ビットのポインターだけではバンク付きデータを特定できません。BankPtrは、1バイトのバンク番号と2バイトのポインターを詰めた3バイトの記述子です。farptr_makeで作成し、farptr_read8、farptr_read16、farptr_readで内容にアクセスします。

GBの例はMBC5カートリッジを使用します。固定バンク0のコードは$0000〜$3FFFから常に参照でき、$4000〜$7FFFにバンク1・2・3を選択します。bank_switchと、その後に実行を続けるコードは固定ROMに配置してください。ライブラリのバンク番号を調べる前に、bank_switchで記録を初期化します。GBのfar_callはコールバックポインターを受け付け、__farcallには関数名を渡します。

__farcall_ptr

指定バンク内の実行時アドレスにある、引数なしのコールバックを呼びます。固定ROM内の補助処理が呼び出しごとのバンクをCPUスタックへ保存し、戻るときに復元します。この組み込み関数は値を返しません。

書式

c
void __farcall_ptr(u8 bank, const void* func);

引数

bank

符号なし8ビットのROMバンク番号です。呼び出しでは関数の配置と一致させ、読み出しでは転送元のマッピングを指定します。対象カートリッジで有効な範囲を使用してください。

func

引数のないコールバックの、有効な実行アドレスです。関数名または実行時のポインターを指定できます。ヌルや、バンクとアドレスの組み合わせの誤りを実行時には検査しません。

戻り値

ありません(void)。

使用条件と制限

これらの呼び出しは、ユーザー引数を渡したり、コールバックを割り込みに対して再入可能にしたりするものではありません。コールバック自身のデータ・スタック・割り込みの使い方を適切に管理してください。バンク復帰は入れ子呼び出しと反復呼び出しで検証していますが、この例で実機や任意の割り込みタイミングを検証したわけではありません。

GBのこれらのバンクAPIは8ビットの番号を扱い、MBC5の9ビット目は設定しません。バンク0の扱いなど、カートリッジ固有の規則も適用されます。例ではMBC5のバンク1〜3を明示的に使います。記録に反映されないマッパーへの直接書き込みと混在させないでください。

使用例

c
requested_bank=3;requested_callback=callback;
__farcall_ptr(requested_bank,requested_callback); // Runtime bank and address.
bank_expect(callback_count==3 && callback_byte==0xD3,17);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完成プログラムのこの位置では、__farcall_ptrによってcallback_countが3になります。コールバックはバンク3からD3を読みます。その後にhome2を近ポインターで読んでB2を確認し、バンク2が選ばれた状態で復帰したことを確かめます。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__farcall_ptr: DMG
DMG
__farcall_ptr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/bank.h:15

kitaqgb/kitaqgb/CodeGenerator.cs:8335

csharp
if (funcName == "__farcall_ptr")
            {
                if (args.Length != 2) Error(expr, "__farcall_ptr(bank, func) expects 2 arguments");
                CType callbackType = TypeOf(args[1]);
                if (callbackType.IsPointer) callbackType = callbackType.Subtype;
                if (callbackType != null && callbackType.IsFunction &&
                    (callbackType.ParamTypes ?? Array.Empty<CType>()).Length != 0)
                    Error(expr, "__farcall_ptr requires a callback with no parameters");
                CompileIntoA(args[0]);
                EmitAsm("PUSH_AF");
                CompileIntoHL(args[1]);
                EmitAsm("POP_AF");
                UseFarCallPointerBank0Helper = true;
                EmitAsm("CALL", new AsmOperand("__kq_farcall_pointer_bank0", AddressMode.Absolute));
                RecordCallEdge(expr, "<indirect-far>", -1, "farcall_pointer", viaThunk: true,
                    viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                return;
            }
__farpeek16

バンクを一時的に選び、addrとaddr+1からリトルエンディアンの16ビット値を読んで復帰します。far_data_read16が利用する組み込み関数です。

書式

c
u16 __farpeek16(u8 bank, const void* addr);

引数

bank

符号なし8ビットのROMバンク番号です。呼び出しでは関数の配置と一致させ、読み出しでは転送元のマッピングを指定します。対象カートリッジで有効な範囲を使用してください。

addr

選んだROMバンク内の読み出せるバイト列を指す近ポインタです。ポインタ自体にはバンク番号を含みません。

戻り値

読み出したリトルエンディアンのワードをu16(0〜65535)で返します。

使用条件と制限

対象のバイトまたはワード全体を、選択したCPUマッピングで読める必要があります。ポインターの検査、アドレスの切り詰め、ウィンドウ境界での次バンクへの自動切り替えは行いません。ワード読み出しには連続する有効な2バイトが必要です。

遠方読み出しとコピーは同期処理で、コンパイラの共有作業領域を使います。処理中に割り込みから別のバンク転送を実行し、同じ作業領域を上書きしないでください。GBのコピー補助処理は、復帰バンクの保存にもHRAMの共有領域を1か所使います。例では競合する転送割り込みを設けず、固定コードから実行します。

GBのこれらのバンクAPIは8ビットの番号を扱い、MBC5の9ビット目は設定しません。バンク0の扱いなど、カートリッジ固有の規則も適用されます。例ではMBC5のバンク1〜3を明示的に使います。記録に反映されないマッパーへの直接書き込みと混在させないでください。

使用例

c
bank_expect(__farpeek16(3,payload+1)==0x5C7A,14);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この断片では__farpeek16でバンク3のpayload+1を読み、5C7Aを確認します。完成プログラムでは、home2の近ポインター読み出しでもバンク2への復帰を確認します。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__farpeek16: DMG
DMG
__farpeek16: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/bank.h:13

kitaqgb/kitaqgb/CodeGenerator.cs:8295

csharp
if (funcName == "__farpeek16")
            {
                if (args.Length != 2) Error(expr, "__farpeek16(bank, addr) expects 2 arguments");

                CompileIntoA(args[0]); EmitPushAAsWord(); // bank
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("ADD_SP_IMM", new AsmOperand(-2, AddressMode.Relative)); // scratch
                // At SP: scratch, saved address, saved bank; each occupies two bytes.

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("PUSH_HL"); // dst = &scratch

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // bank

                EmitAsm("POP_HL"); // dst
                EmitAsm("LD_BC_IMM", new AsmOperand(2, AddressMode.Immediate16));
                EmitCallFarMemcpyHelper();

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                LastCallReturnsHL = true;
                return;
            }
__farpeek8

指定バンクを一時的に選び、CPUアドレスから1バイト読んで元のバンクに戻します。far_data_read8が利用する組み込み関数です。

書式

c
u8 __farpeek8(u8 bank, const void* addr);

引数

bank

符号なし8ビットのROMバンク番号です。呼び出しでは関数の配置と一致させ、読み出しでは転送元のマッピングを指定します。対象カートリッジで有効な範囲を使用してください。

addr

選んだROMバンク内の読み出せるバイト列を指す近ポインタです。ポインタ自体にはバンク番号を含みません。

戻り値

読み出したバイトをu8(0〜255)で返します。

使用条件と制限

対象のバイトまたはワード全体を、選択したCPUマッピングで読める必要があります。ポインターの検査、アドレスの切り詰め、ウィンドウ境界での次バンクへの自動切り替えは行いません。ワード読み出しには連続する有効な2バイトが必要です。

遠方読み出しとコピーは同期処理で、コンパイラの共有作業領域を使います。処理中に割り込みから別のバンク転送を実行し、同じ作業領域を上書きしないでください。GBのコピー補助処理は、復帰バンクの保存にもHRAMの共有領域を1か所使います。例では競合する転送割り込みを設けず、固定コードから実行します。

GBのこれらのバンクAPIは8ビットの番号を扱い、MBC5の9ビット目は設定しません。バンク0の扱いなど、カートリッジ固有の規則も適用されます。例ではMBC5のバンク1〜3を明示的に使います。記録に反映されないマッパーへの直接書き込みと混在させないでください。

使用例

c
bank_expect(__farpeek8(3,payload)==0xD3,13);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この断片では__farpeek8でバンク3のpayloadを読み、D3を確認します。完成プログラムでは、home2の近ポインター読み出しでもバンク2への復帰を確認します。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__farpeek8: DMG
DMG
__farpeek8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/bank.h:12

kitaqgb/kitaqgb/CodeGenerator.cs:8262

csharp
if (funcName == "__farpeek8")
            {
                if (args.Length != 2) Error(expr, "__farpeek8(bank, addr) expects 2 arguments");

                CompileIntoA(args[0]); EmitPushAAsWord(); // bank
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("ADD_SP_IMM", new AsmOperand(-2, AddressMode.Relative)); // scratch
                // At SP: scratch, saved address, saved bank; each occupies two bytes.

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("PUSH_HL"); // dst = &scratch

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // bank

                EmitAsm("POP_HL"); // dst
                EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
                EmitCallFarMemcpyHelper();

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                return;
            }

cgb_palette.h — CGBのBG/OBJパレット

cgb_palette.hとcgb_palette.cは、RGB15値の生成とCGBパレットの即時更新を提供します。背景・ウィンドウにはBG系、スプライトにはOBJ系の関数を使います。1色指定・RGB成分指定は1色、範囲指定は連続する色ワード、全パレット指定は4ワードを書き込みます。raw範囲指定は色の通し番号、レジスタ組み込み関数はバイト位置を使います。パレットの割り当てはマップ属性やOAMで別途指定します。完全版サンプルのように実装をプログラムへ1回だけ組み込んでください。

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

背景・ウィンドウ用とOBJ用のパレットは別々の記憶領域です。ライブラリの色書き込みは索引の自動増分を有効にし、索引レジスタを変更したままにします。OBJの色番号0は保存・読み出しができても、スプライト描画では透明です。

__cgb_is_cgb

コンパイラのCGBモード判定を返します。--cgb=dmgを明示したビルドでは0、--cgb=cgb_onlyでは1に定数化され、兼用の--cgb=cgbでは実行時に判定します。物理的な機種の全機能を調べる関数ではなく、選択された実行モードを示します。

書式

c
u8 __cgb_is_cgb();

戻り値

u8で、DMGモードなら0、CGBモードなら1です。

使用例

c
color_mode=__cgb_is_cgb(); // 0 in DMG mode, 1 in CGB mode for this dual-mode ROM.
if (color_mode>1) failures++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_is_cgb: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_is_cgb: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_is_cgb: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_is_cgb: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_palette.h:8

宣言の注記(原文)
Safe here means CGB hardware gating. Palette data writes still require an accessible
PPU phase (for example LCD off or a sufficiently short VBlank update).
__cgb_safe_set_bcpd

CGBの背景・ウィンドウ用パレットのデータレジスタBCPD/BGPD($FF69)へ1バイト書き込みます。__cgb_safe_set_bgpdも同じレジスタを操作します。書き込み位置は対応する索引レジスタで選びます。RGB15の1色は下位・上位の2バイトで指定します。

書式

c
void __cgb_safe_set_bcpd(u8 value);

引数

value

1バイト(0〜255)。RGB15の下位バイト、上位バイトの順に書きます。あらかじめ索引の自動増分を有効にするか、各バイトの位置を明示的に選んでください。データ書き込み自体が自動増分を有効にすることはありません。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_bcpd(0xE0); __cgb_safe_set_bcpd(0x03); // Green word 0x03E0, low then high.
expect_color(0,27,992);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_bcpd: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_bcpd: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_bcpd: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_bcpd: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_palette.h:10

__cgb_safe_set_bcps

CGBの背景・ウィンドウ用パレットのバイト位置レジスタBCPS/BGPI($FF68)へ書き込みます。__cgb_safe_set_bgpiも同じレジスタを操作します。次のデータレジスタ読み書きの位置を選ぶ処理であり、この呼び出しだけでは色を変更しません。

書式

c
void __cgb_safe_set_bcps(u8 value);

引数

value

ビット0〜5でバイト位置(0〜63)を指定し、ビット7を立てるとデータ書き込み後に位置が自動で進みます。ビット6は未使用です。色スロットnの先頭はバイト2nです。63の次は0へ戻るため、レジスタを直接操作する場合の範囲管理は呼び出し側で行います。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_bcps(0xB6); // BG byte 54, with auto-increment enabled (slot 27 low byte).

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_bcps: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_bcps: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_bcps: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_bcps: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_palette.h:9

__cgb_safe_set_ocpd

CGBのスプライト(OBJ)用パレットのデータレジスタOCPD/OBPD($FF6B)へ1バイト書き込みます。__cgb_safe_set_obpdも同じレジスタを操作します。書き込み位置は対応する索引レジスタで選びます。RGB15の1色は下位・上位の2バイトで指定します。

書式

c
void __cgb_safe_set_ocpd(u8 value);

引数

value

1バイト(0〜255)。RGB15の下位バイト、上位バイトの順に書きます。あらかじめ索引の自動増分を有効にするか、各バイトの位置を明示的に選んでください。データ書き込み自体が自動増分を有効にすることはありません。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_ocpd(31); __cgb_safe_set_ocpd(0); // Red sprite color.
expect_color(1,27,31);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_ocpd: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_ocpd: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_ocpd: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_ocpd: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_palette.h:12

__cgb_safe_set_ocps

CGBのスプライト(OBJ)用パレットのバイト位置レジスタOCPS/OBPI($FF6A)へ書き込みます。__cgb_safe_set_obpiも同じレジスタを操作します。次のデータレジスタ読み書きの位置を選ぶ処理であり、この呼び出しだけでは色を変更しません。

書式

c
void __cgb_safe_set_ocps(u8 value);

引数

value

ビット0〜5でバイト位置(0〜63)を指定し、ビット7を立てるとデータ書き込み後に位置が自動で進みます。ビット6は未使用です。色スロットnの先頭はバイト2nです。63の次は0へ戻るため、レジスタを直接操作する場合の範囲管理は呼び出し側で行います。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_ocps(0xB6); // OBJ byte 54; this does not select a BG color.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_ocps: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_ocps: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_ocps: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_ocps: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_palette.h:11

cgb_tile.h — CGBのタイル番号と属性

cgb_tile.hは、タイルマップ操作の組み込み命令の宣言と、CGBのタイル属性を組み立てるマクロをまとめたヘッダーです。タイル番号は表示する絵柄を選び、属性はパレット、絵柄のVRAMバンク、左右・上下反転、背景の優先度を指定します。属性はタイル画像そのものを書き換える機能ではありません。

__settile系はマップ0x9800、__settileat系は指定したマップ先頭、__settilebg系はLCDCが選ぶ背景マップ、__settilewin系はLCDCが選ぶウィンドウマップを使います。attrは属性だけ、cgbはタイル番号と属性の両方を書きます。bulkは指定アドレスから連続したバイト列を転送します。用途に合わせて、書き込み先と書く内容の両方から命令を選んでください。

KQ_CGB_ATTR_PALなどのマクロで各属性のビットを作り、KQ_CGB_ATTRでまとめて指定できます。bankは絵柄を読むVRAMバンクで、ROMバンク番号ではありません。通常のタイル番号書き込みは選択中のVRAMバンクを使うため、タイルと属性を一緒に設定する前にバンク0を選択してください。DMGでは属性だけの操作は何もせず、タイルと属性を組み合わせた操作はタイル番号だけを書きます。

__で始まる命令はコンパイラが実装を生成するため、これらのためにcgb_tile.cをリンクする必要はありません。通常の書き込み経路はVRAMアクセスを待ちますが、割り込み許可状態を変更するものがあります。_unsafeと_fastはその待機を省くため、LCD停止中など、呼び出し側が書き込み可能な期間を保証できる場面で使います。各命令の割り込み・バンク・転送長の条件も確認してください。

__cgb_safe_set_vbk

CGBでVBK($FF4F)を操作し、CPUがアクセスするVRAMバンクを選びます。書き込むのはvalue & 1だけで、偶数ならバンク0、奇数ならバンク1です。対象はCPUの$8000〜$9FFFへのアクセスであり、マップ属性で指定するタイルデータのバンクとは別です。元のバンクへ自動では戻りません。

書式

c
void __cgb_safe_set_vbk(u8 value);

引数

value

CGBでVBK($FF4F)を操作し、CPUがアクセスするVRAMバンクを選びます。書き込むのはvalue & 1だけで、偶数ならバンク0、奇数ならバンク1です。対象はCPUの$8000〜$9FFFへのアクセスであり、マップ属性で指定するタイルデータのバンクとは別です。元のバンクへ自動では戻りません。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
if (color_mode) {
    __cgb_safe_set_vbk(3); // Only bit zero is kept: CPU accesses now use bank 1.
    if ((P_VBK&1)!=1) failures++;
    __vram_copy(0x8010,shape1,16);
    __cgb_safe_set_vbk(2); // Bit zero is clear, so return to bank 0.
    if ((P_VBK&1)!=0) failures++;
}

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_vbk: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_vbk: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_vbk: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_vbk: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:28

宣言の注記(原文)
Write the VRAM bank selection only on CGB. Safe here means hardware-mode guarded, not PPU-timed.
__settile

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. The destination is always `0x9800 + 32*y + x`, regardless of which
// maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile(2, 3, 'A');
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settile: DMG
DMG
__settile: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:31

宣言の注記(原文)
Write a tile number to map 9800, waiting for VRAM access when necessary.
__settile_bulk

src から dest のVRAMアドレスへ、連続するタイル番号を count バイトコピーします。個々のタイル画像を転送する命令ではありません。行末での折り返し調整も行いません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile_bulk(u16 dest, const u8* src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_bulk
// Copy `count` consecutive tile-number bytes from `src` to the VRAM byte address `dest`.
// The count is in bytes, not tile-image units; no row-stride adjustment is made. Waits
// for writable VRAM when necessary. The LCD-on path uses interrupt masking and can
// enable interrupts afterward; it is not a promise to preserve the caller's interrupt
// state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_bulk(0x9862, example_tiles, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settile_bulk: DMG
DMG
__settile_bulk: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_bulk.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_bulk.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:48

宣言の注記(原文)
Copy count sequential bytes to dest using the current VRAM bank and access-checked transfer.
__settile_bulk_fast

src から dest のVRAMアドレスへ、連続するタイル番号を count バイトコピーします。個々のタイル画像を転送する命令ではありません。行末での折り返し調整も行いません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settile_bulk_fast(u16 dest, const u8* src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_bulk_fast
// Copy `count` consecutive tile-number bytes from `src` to the VRAM byte address `dest`.
// The count is in bytes, not tile-image units; no row-stride adjustment is made. Does
// not wait for VRAM access. Call only while the LCD is off or within a caller-managed
// writable interval with enough time for the whole transfer.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_bulk_fast(0x9862, example_tiles, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settile_bulk_fast: DMG
DMG
__settile_bulk_fast: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_bulk_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_bulk_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:50

宣言の注記(原文)
Copy count sequential bytes to dest without VRAM access waits; retain source-bank visibility.
__settile_unsafe

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settile_unsafe(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_unsafe
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. The destination is always `0x9800 + 32*y + x`, regardless of which
// maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_unsafe(2, 3, 'A');
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settile_unsafe: DMG
DMG
__settile_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:33

宣言の注記(原文)
Write a tile number to map 9800 without waiting for writable VRAM.
__settileat

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settileat(u16 base, u8 x, u8 y, u8 tile);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileat
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. `base` explicitly selects the map (`0x9800` or `0x9C00`); the byte
// address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileat: DMG
DMG
__settileat: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileat.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileat.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:35

宣言の注記(原文)
Write at base+32*y+x with VRAM access checks; supply a valid tile-map base.
__settileat_unsafe

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settileat_unsafe(u16 base, u8 x, u8 y, u8 tile);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileat_unsafe
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. `base` explicitly selects the map (`0x9800` or `0x9C00`); the byte
// address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settileat_unsafe(0x9C00, 2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileat_unsafe: DMG
DMG
__settileat_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileat_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileat_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:37

宣言の注記(原文)
Use an explicit map base without waiting for VRAM access.
__settileatattr

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settileatattr(u16 base, u8 x, u8 y, u8 attr);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileatattr
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. `base` explicitly selects the map
// (`0x9800` or `0x9C00`); the byte address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settileatattr(0x9C00, 2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileatattr: DMG
DMG
__settileatattr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileatattr.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileatattr.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:62

宣言の注記(原文)
Write attributes in bank one at the explicit map base on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settileatattr_unsafe

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settileatattr_unsafe(u16 base, u8 x, u8 y, u8 attr);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileatattr_unsafe
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. `base` explicitly selects the map
// (`0x9800` or `0x9C00`); the byte address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settileatattr_unsafe(0x9C00, 2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileatattr_unsafe: DMG
DMG
__settileatattr_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileatattr_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileatattr_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:64

宣言の注記(原文)
Write attributes in bank one at the explicit map base on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settileatcgb

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settileatcgb(u16 base, u8 x, u8 y, u8 tile, u8 attr);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileatcgb
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. `base` explicitly selects the map (`0x9800` or `0x9C00`); the
// byte address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileatcgb(0x9C00, 2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileatcgb: DMG
DMG
__settileatcgb: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileatcgb.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileatcgb.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:66

宣言の注記(原文)
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. Use the access-checked write path.
__settileatcgb_unsafe

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settileatcgb_unsafe(u16 base, u8 x, u8 y, u8 tile, u8 attr);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileatcgb_unsafe
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. `base` explicitly selects the map (`0x9800` or `0x9C00`); the
// byte address is `base + 32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileatcgb_unsafe(0x9C00, 2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileatcgb_unsafe: DMG
DMG
__settileatcgb_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileatcgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileatcgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:68

宣言の注記(原文)
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. No VRAM access waits are inserted.
__settileattr

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settileattr(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileattr
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. The destination is always `0x9800
// + 32*y + x`, regardless of which maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9800, 2, 3, 'A');
    __settileattr(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileattr: DMG
DMG
__settileattr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileattr.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileattr.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:53

宣言の注記(原文)
Write attributes in bank one at map 9800 on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settileattr_bulk

src のCGB属性を count バイト、VRAMバンク1の dest へ連続コピーします。タイル番号は変更しません。dest は (x,y) 座標ではなくアドレスです。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settileattr_bulk(u16 dest, const u8* src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileattr_bulk
// Copy `count` consecutive CGB attribute bytes from `src` to `dest` in VRAM bank 1. Tile
// numbers are untouched; the destination is a byte address, not an `(x,y)` pair. Waits
// for writable VRAM when necessary. The LCD-on path uses interrupt masking and can
// enable interrupts afterward; it is not a promise to preserve the caller's interrupt
// state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settile_bulk(0x9862, example_tiles, 3);
    __settileattr_bulk(0x9862, example_attrs, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 1) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9864, 1) != 3) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性列 1,2,3 がそれぞれ用意したパレットを選び、A は赤、B は青、C は緑になります。DMGでは3文字とも黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settileattr_bulk: DMG
DMG
__settileattr_bulk: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileattr_bulk.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileattr_bulk.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:89

宣言の注記(原文)
Copy count attributes to dest in bank one on CGB, then leave VBK zero; skip on DMG. Transfers use access checks.
__settileattr_bulk_fast

src のCGB属性を count バイト、VRAMバンク1の dest へ連続コピーします。タイル番号は変更しません。dest は (x,y) 座標ではなくアドレスです。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settileattr_bulk_fast(u16 dest, const u8* src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileattr_bulk_fast
// Copy `count` consecutive CGB attribute bytes from `src` to `dest` in VRAM bank 1. Tile
// numbers are untouched; the destination is a byte address, not an `(x,y)` pair. Does
// not wait for VRAM access. Call only while the LCD is off or within a caller-managed
// writable interval with enough time for the whole transfer.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settile_bulk(0x9862, example_tiles, 3);
    __settileattr_bulk_fast(0x9862, example_attrs, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 1) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9864, 1) != 3) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性列 1,2,3 がそれぞれ用意したパレットを選び、A は赤、B は青、C は緑になります。DMGでは3文字とも黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settileattr_bulk_fast: DMG
DMG
__settileattr_bulk_fast: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileattr_bulk_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileattr_bulk_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:91

宣言の注記(原文)
Copy count attributes to dest in bank one on CGB, then leave VBK zero; skip on DMG. The caller supplies writable VRAM timing.
__settileattr_unsafe

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settileattr_unsafe(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settileattr_unsafe
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. The destination is always `0x9800
// + 32*y + x`, regardless of which maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9800, 2, 3, 'A');
    __settileattr_unsafe(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settileattr_unsafe: DMG
DMG
__settileattr_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settileattr_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settileattr_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:55

宣言の注記(原文)
Write attributes in bank one at map 9800 on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilebg

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilebg(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 3 selects the background map: zero uses `0x9800`, one uses
// `0x9C00`. The destination is that base plus `32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilebg(2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebg: DMG
DMG
__settilebg: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:43

宣言の注記(原文)
Select the background map from LCDC bit 3, then write with access checks.
__settilebg_unsafe

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilebg_unsafe(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg_unsafe
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 3 selects the background map: zero uses `0x9800`, one uses
// `0x9C00`. The destination is that base plus `32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilebg_unsafe(2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebg_unsafe: DMG
DMG
__settilebg_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:45

宣言の注記(原文)
Select the LCDC background map and write without access waits.
__settilebgattr

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilebgattr(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebgattr
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 3 selects the background
// map: zero uses `0x9800`, one uses `0x9C00`. The destination is that base plus `32*y +
// x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settilebgattr(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebgattr: DMG
DMG
__settilebgattr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebgattr.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebgattr.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:80

宣言の注記(原文)
Write attributes in bank one at the LCDC background map on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settilebgattr_unsafe

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilebgattr_unsafe(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebgattr_unsafe
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 3 selects the background
// map: zero uses `0x9800`, one uses `0x9C00`. The destination is that base plus `32*y +
// x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settilebgattr_unsafe(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebgattr_unsafe: DMG
DMG
__settilebgattr_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebgattr_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebgattr_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:82

宣言の注記(原文)
Write attributes in bank one at the LCDC background map on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilebgcgb

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilebgcgb(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebgcgb
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 3 selects the background map: zero uses `0x9800`, one
// uses `0x9C00`. The destination is that base plus `32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilebgcgb(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebgcgb: DMG
DMG
__settilebgcgb: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebgcgb.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebgcgb.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:84

宣言の注記(原文)
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. Use the access-checked write path.
__settilebgcgb_unsafe

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilebgcgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebgcgb_unsafe
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 3 selects the background map: zero uses `0x9800`, one
// uses `0x9C00`. The destination is that base plus `32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilebgcgb_unsafe(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilebgcgb_unsafe: DMG
DMG
__settilebgcgb_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebgcgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebgcgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:86

宣言の注記(原文)
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. No VRAM access waits are inserted.
__settilecgb

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilecgb(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilecgb
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. The destination is always `0x9800 + 32*y + x`, regardless of
// which maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilecgb(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilecgb: DMG
DMG
__settilecgb: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:57

宣言の注記(原文)
Write tile and CGB attributes at map 9800; DMG receives only the tile write. Use the access-checked write path.
__settilecgb_bulk

tile_src のタイル番号と attr_src の属性を、それぞれ count バイトずつ dest から同じマップ範囲へ転送します。番号と属性は交互に並べず、別々の配列で渡します。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilecgb_bulk(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

tile_src, attr_src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilecgb_bulk
// Copy `count` tile numbers from `tile_src`, then `count` attributes from `attr_src`, to
// the same map addresses starting at `dest`. These are separate byte arrays, not
// interleaved pairs. Waits for writable VRAM when necessary. The LCD-on path uses
// interrupt masking and can enable interrupts afterward; it is not a promise to preserve
// the caller's interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilecgb_bulk(0x9862, example_tiles, example_attrs, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 1) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9864, 1) != 3) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性列 1,2,3 がそれぞれ用意したパレットを選び、A は赤、B は青、C は緑になります。DMGでは3文字とも黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settilecgb_bulk: DMG
DMG
__settilecgb_bulk: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb_bulk.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb_bulk.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:93

宣言の注記(原文)
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. Transfers use access checks.
__settilecgb_bulk_fast

tile_src のタイル番号と attr_src の属性を、それぞれ count バイトずつ dest から同じマップ範囲へ転送します。番号と属性は交互に並べず、別々の配列で渡します。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilecgb_bulk_fast(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);

引数

dest

先頭のVRAMバイトアドレスです。マップには 0x9800~0x9FFF の範囲を使い、転送全体が目的のマップ内に収まるようにしてください。

tile_src, attr_src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

count

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

コピーが終わるまで、元バッファとそのROMバンクを読み取り可能な状態に保ってください。戻り値はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilecgb_bulk_fast
// Copy `count` tile numbers from `tile_src`, then `count` attributes from `attr_src`, to
// the same map addresses starting at `dest`. These are separate byte arrays, not
// interleaved pairs. Does not wait for VRAM access. Call only while the LCD is off or
// within a caller-managed writable interval with enough time for the whole transfer.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
const u8 example_tiles[] = {'A','B','C'};
const u8 example_attrs[] = {1,2,3};

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilecgb_bulk_fast(0x9862, example_tiles, example_attrs, 3);
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9863, 0) != 'B') { failures++; }
    if (tile_example_read(0x9864, 0) != 'C') { failures++; }
    if (tile_example_read(0x9865, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 1) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9864, 1) != 3) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性列 1,2,3 がそれぞれ用意したパレットを選び、A は赤、B は青、C は緑になります。DMGでは3文字とも黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settilecgb_bulk_fast: DMG
DMG
__settilecgb_bulk_fast: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb_bulk_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb_bulk_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:95

宣言の注記(原文)
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. The caller supplies writable VRAM timing.
__settilecgb_unsafe

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilecgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilecgb_unsafe
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. The destination is always `0x9800 + 32*y + x`, regardless of
// which maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilecgb_unsafe(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilecgb_unsafe: DMG
DMG
__settilecgb_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:59

宣言の注記(原文)
Write tile and CGB attributes at map 9800; DMG receives only the tile write. No VRAM access waits are inserted.
__settilewin

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilewin(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewin
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilewin(2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewin: DMG
DMG
__settilewin: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewin.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewin.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:39

宣言の注記(原文)
Select the window map from LCDC bit 6, then write a tile with access checks.
__settilewin_unsafe

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilewin_unsafe(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewin_unsafe
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilewin_unsafe(2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewin_unsafe: DMG
DMG
__settilewin_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewin_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewin_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:41

宣言の注記(原文)
Select the LCDC window map and write without access waits.
__settilewinattr

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilewinattr(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewinattr
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 6 selects the window map:
// zero uses `0x9800`, one uses `0x9C00`. These are map coordinates, not the window's
// screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settilewinattr(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewinattr: DMG
DMG
__settilewinattr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewinattr.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewinattr.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:71

宣言の注記(原文)
Write attributes in bank one at the LCDC window map on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settilewinattr_unsafe

CGBの属性バイトだけを書き換え、タイル番号は変更しません。属性ではパレット、タイル画像のバンク、左右・上下反転、優先度を指定します。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilewinattr_unsafe(u8 x, u8 y, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

CGBではVRAMバンク1へ切り替えて属性を書き、終了時は VBK=0 にします。DMGでは属性の書き込みを行いません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewinattr_unsafe
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 6 selects the window map:
// zero uses `0x9800`, one uses `0x9C00`. These are map coordinates, not the window's
// screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settileat(0x9C00, 2, 3, 'A');
    __settilewinattr_unsafe(2, 3, KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewinattr_unsafe: DMG
DMG
__settilewinattr_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewinattr_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewinattr_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:73

宣言の注記(原文)
Write attributes in bank one at the LCDC window map on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilewincgb

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilewincgb(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewincgb
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilewincgb(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewincgb: DMG
DMG
__settilewincgb: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewincgb.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewincgb.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:75

宣言の注記(原文)
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. Use the access-checked write path.
__settilewincgb_unsafe

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

LCDCのビット6で選択中のウィンドウマップへ書き込みます。0なら 0x9800、1なら 0x9C00 です。x,y はマップ内の座標であり、画面上のウィンドウ位置ではありません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
void __settilewincgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilewincgb_unsafe
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __settilewincgb_unsafe(2, 3, 'A', KQ_CGB_ATTR(2, 0, 0, 0, 0));
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C62, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9862, 1) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、属性値2によりサンプルで用意した青のパレットが選ばれ、A が青く表示されます。DMGではCGB属性を適用しないので、同じ文字が黒く表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settilewincgb_unsafe: DMG
DMG
__settilewincgb_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewincgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewincgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/cgb_tile.h:77

宣言の注記(原文)
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. No VRAM access waits are inserted.

input.h — ボタンの現在値・押下・解放・リピート

連続移動は input_down、一度だけの動作は input_pressed、ボタンを離したときの停止は input_released、メニューの繰り返し操作は input_repeat と使い分けます。初期化は一度、更新は入力処理1回につき一度行い、その後は必要なだけ調べます。btn_down・btn_pressed・btn_released・btn_repeat はそれぞれの取得関数の別名です。

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

リピート時間の単位はミリ秒ではなく更新回数です。新たな押下で直ちに出力し、既定の INPUT_REPEAT_DELAY=18、INPUT_REPEAT_RATE=5 では19回後、以降6回ごとに出力します。各ボタンのカウンタは独立しています。設定を変更する場合は input.c のコンパイルにも同じマクロ値を適用してください。呼び出し側のヘッダーだけを変更してもライブラリの設定は変わりません。

__readpadex

全ボタンを読み、渡された前回の押下マスクと比較します。下位バイトに現在値、上位バイトに新たな押下ビット current & ~previous を詰めて返します。前回値は内部に保存せず、解放マスクやリピートマスクも返しません。

書式

c
u16 __readpadex(u8 prev_keys);

引数

prev_keys

前回の __readpad() の戻り値、または前回の __readpadex() の下位バイトです。最初は0を渡し、次回に備えて新しい下位バイトを保存してください。

戻り値

u16 を返します。(u8)result が現在のマスク、(u8)(result >> 8) が新たな押下マスクです。8ビット変数に代入すると押下ビットが失われるため、16ビット変数で受けてください。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

使用例

c
packed_first=__readpadex(0); // First sample: current 0x11, new presses 0x11.
current_keys=(u8)packed_first;
press_keys=(u8)(packed_first>>8);
packed_held=__readpadex(current_keys); // Held without a change: new presses are 0.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

3種類のボタンマスクと、現在値・押下値をまとめた戻り値を比較するプログラムです。Aと右を同時に押します。ALL KEYSは17、DIRECTIONSとBUTTONS LOWはそれぞれ1になります。前回値を0にするとEX CURRENTとEX NEW PRESSは共に17です。その現在値を次の読み取りに渡すとHELD PRESSは0になります。FAILED CHECKSは0が正常です。取得した値は画面に残るため、ボタンを離しても消えません。

__readpadex: DMG
DMG
__readpadex: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
実装とソース

kitaqgb/lib/input.h:28

intrinsics — コンパイラ組み込み命令

この節はKITAQGBのコード生成器が直接処理する組み込み関数をまとめています。intrinsicsは説明書内の分類名で、GB用のintrinsics.hという共通ヘッダーを取り込む指示ではありません。各項目の完成サンプルにあるヘッダーと初期化処理を使ってください。組み込み関数の本体をゲーム側で定義する必要はありません。

組み込み関数には、ビルド時の情報を使うものと、ゲーム実行中に機器やメモリを操作するものがあります。たとえば__bankof(symbol)は名前で指定したデータ・関数の配置バンクを求め、__bankswitch(bank)は実行時にROMの見える範囲を切り替えます。バンク番号、CPUアドレス、ポインターを区別して引数を渡してください。

__で始まる名前だけでは、範囲検査、VRAMアクセスの待機、割り込み中の安全性は保証されません。たとえば__vram_memcpy_unsafeでは、呼び出し側がVRAMへ書ける期間を確保します。個別説明の引数の単位・有効範囲・バンク配置・割り込み条件を確認し、用途に合った命令を選んでください。

__bankof

名前で指定したデータまたは関数に割り当てられた論理ROMバンク番号を取得します。配置情報を解決するもので、現在選ばれているバンクの取得やポインター参照ではありません。

書式

c
u8 bank = __bankof(symbol);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

symbol

コンパイラまたはリンカーが配置を把握しているROMデータや関数の名前です。ポインター変数を指定しても参照先の意味にはなりません。実行時の式はシンボル名として渡せません。

戻り値

u8のバンク番号です。0が共通ROMを表す場合がありますが、すべての対象で切り替え先として有効な番号とは限りません。

使用例

c
bank_expect(__bankof(payload)==3,3);
bank_expect(__bankof(callback)==3,4);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

サンプルではpayloadとcallbackが両方ともバンク3に配置されていることを確認します。これは配置情報の検証であり、実行時のバンク動作は別の読み出しとコールバック検査で確認します。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__bankof: DMG
DMG
__bankof: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:432

kitaqgb/kitaqgb/CodeGenerator.cs:7360

csharp
if (funcName == "__bankof")
            {
                if (args.Length != 1) Program.Error("__bankof(symbol) expects 1 argument");
                if (!args[0].Match(Tag.Name, out string symName)) Program.Error("__bankof(symbol) requires a symbol name argument");
                if (TryGetKnownRomBankForSymbol(symName, out int knownBank))
                {
                    EmitAsm("LD_A_IMM", new AsmOperand(knownBank & 0xFF, AddressMode.Immediate));
                    return;
                }
                EmitAsm("LD_A_IMM", new AsmOperand(symName, ImmediateModifier.Bank));
                return;
            }
__bankswitch

カートリッジの$2000へバンク番号の下位1バイトを書き、コンパイラ側のバンク状態を更新します。選択は呼び出し後も続きます。bank_get_current用の記録は更新しません。

書式

c
void __bankswitch(u8 bank);

引数

bank

符号なし8ビットのROMバンク番号です。呼び出しでは関数の配置と一致させ、読み出しでは転送元のマッピングを指定します。対象カートリッジで有効な範囲を使用してください。

戻り値

ありません(void)。

使用条件と制限

切り替えを維持する操作では、実行中のコードや戻り先を見えなくしてはいけません。サンプルのように、この呼び出し、使用するbank_switch本体、その後に続くコードを共通・固定バンク0に置いてください。

ライブラリの記録は0で始まり、カートリッジの起動時のマッピングを調べるものではありません。bank_switchで初期化してください。組み込み関数による直接切り替えやマッパーへの直接書き込みではこの記録を更新しないため、bank_get_currentで実際のハードウェア状態を検証することはできません。

GBのこれらのバンクAPIは8ビットの番号を扱い、MBC5の9ビット目は設定しません。バンク0の扱いなど、カートリッジ固有の規則も適用されます。例ではMBC5のバンク1〜3を明示的に使います。記録に反映されないマッパーへの直接書き込みと混在させないでください。

使用例

c
__bankswitch(1);
bank_expect(home1[0]==0xA1,19);
bank_expect(bank_get_current()==2,20); // Intrinsics bypass the library record.
__bankswitch(2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

最初のbank_switch(2)でバンク2を記録して選びます。その後の直接切り替えではバンク1を選び、home1からA1を読みますが、bank_get_currentの記録は2です。戻すとhome2から再びB2を読めます。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__bankswitch: DMG
DMG
__bankswitch: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/bank.h:11

kitaqgb/kitaqgb/CodeGenerator.cs:7268

csharp
if (funcName == "__bankswitch")
            {
                if (args.Length != 1) Program.Error("__bankswitch(bank) expects 1 argument");
                CompileIntoA(args[0]);
                if (CurrentFunctionBank == 0)
                {
                    EmitAsm("LD_MEM_A", new AsmOperand(0x2000, AddressMode.Absolute));
                    EmitStoreA(RomBankVar);
                }
                else
                {
                    UseBankSwitchBank0Helper = true;
                    EmitAsm("CALL", new AsmOperand("__kq_bankswitch_bank0", AddressMode.Absolute));
                }
                return;
            }
__cgb_safe_set_bgpd

CGBの背景・ウィンドウ用パレットのデータレジスタBCPD/BGPD($FF69)へ1バイト書き込みます。__cgb_safe_set_bcpdも同じレジスタを操作します。書き込み位置は対応する索引レジスタで選びます。RGB15の1色は下位・上位の2バイトで指定します。

書式

c
__cgb_safe_set_bgpd(/* 書式は実装と呼び出し例を参照 */);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

1バイト(0〜255)。RGB15の下位バイト、上位バイトの順に書きます。あらかじめ索引の自動増分を有効にするか、各バイトの位置を明示的に選んでください。データ書き込み自体が自動増分を有効にすることはありません。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_bgpd(31); __cgb_safe_set_bgpd(0); // Alias of BCPD: red at slot 31.
expect_color(0,31,31);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_bgpd: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_bgpd: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_bgpd: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_bgpd: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1865

__cgb_safe_set_bgpi

CGBの背景・ウィンドウ用パレットのバイト位置レジスタBCPS/BGPI($FF68)へ書き込みます。__cgb_safe_set_bcpsも同じレジスタを操作します。次のデータレジスタ読み書きの位置を選ぶ処理であり、この呼び出しだけでは色を変更しません。

書式

c
__cgb_safe_set_bgpi(/* 書式は実装と呼び出し例を参照 */);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

ビット0〜5でバイト位置(0〜63)を指定し、ビット7を立てるとデータ書き込み後に位置が自動で進みます。ビット6は未使用です。色スロットnの先頭はバイト2nです。63の次は0へ戻るため、レジスタを直接操作する場合の範囲管理は呼び出し側で行います。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_bgpi(0xBE); // Alias of BCPS: BG byte 62, auto-increment enabled.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_bgpi: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_bgpi: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_bgpi: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_bgpi: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1863

__cgb_safe_set_hdma1

CGB DMA転送元アドレスの上位バイトをHDMA1($FF51)へ書き込みます。転送元の設定だけを行い、コピーや転送開始は行いません。下位バイトはHDMA2で指定します。

書式

c
void __cgb_safe_set_hdma1(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

1ブロックは16バイトです。新規転送は1〜128ブロック(16〜2048バイト)で、制御値0は空の転送ではなく16バイトを意味します。GDMAは完了までCPUを止めます。HBlank DMAは表示行のHBlankごとに1ブロックを転送し、フレームをまたいで続きます。データを再利用する前に、呼び出し側で完了を確認してください。

GDMAはLCD停止中、または転送が収まる長さの書き込み可能期間で使用します。HBlank DMAはLCD動作中のSTATモード0以外で開始し、このサンプルではVBlankから開始します。動作中は転送元バンク、転送先VBK、転送元データを変更しないでください。HALT中はHBlank DMAも停止します。これらの関数は待機・割り込み保護・転送範囲の検査を追加しません。

状態は$FF55を別途読みます。ビット7が0なら動作中、$FFなら完了です。中止後はビット7が1になり、ビット0〜6に残りブロック数−1が残ります。転送済みのバイトは元に戻りません。動作中でない状態でビット7を0にして書くとGDMAが始まるので、中止は動作中にだけ行ってください。転送全体が有効な転送元領域と転送先VRAMバンクに収まるようにし、アドレスのあふれに依存しないでください。

これらのHDMAレジスタ設定関数は、CGBのハードウェアDMAを操作します。__vram_copy_dmaは別の組み込み関数で、CPUによるコピー処理の別名です。その名前でも、このDMAコントローラを使うわけではありません。

使用例

c
__cgb_safe_set_hdma1(0xC6); // Source high byte: fixed-WRAM buffer at $C600.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完全版プログラムは、$C600の16バイト境界に置いたバッファで3種類の転送処理を比較します。GDMAは32バイトを$8010へコピーし、画面(16,24)に上端左1画素から下端8画素まで広がる赤い8×8の三角形、(24,24)に赤い1画素幅の四角い枠を表示します。HBlank DMAは32バイトを$8030へコピーし、(16,48)に青い4×8の左半分、(24,48)に青い8×8の正方形を表示します。数値による代用ではなく、転送されたタイルパターンそのものです。

CANCELED行には、CPUであらかじめ書き込んだ緑の4×8の半タイル(16,72)と緑の8×8の正方形(24,72)があります。この領域を転送先とする2ブロックのHBlank DMAを、同じVBlank中に開始・中止して最初のブロックも転送させません。$FF55は$81になり、1フレーム後も両パターンが変わらないことを確かめます。DMGでは上の2行は空白で、この2つの保持確認用図形だけが黒で表示されます。文字は白地に黒です。FAILED CHECKS 000では転送バイトと完了状態も確認します。KOKURAで兼用ROMのDMG/CGB実行とCGB専用実行を検証し、一部転送後の中止・倍速・実機は未検証です。

__cgb_safe_set_hdma1: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma1: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma1: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1871

__cgb_safe_set_hdma2

DMA転送元アドレスの下位バイトをHDMA2($FF52)へ書き込みます。ハードウェアがビット0〜3を無視し、16バイト境界へ切り下げます。例えばHDMA1=$C6、HDMA2=$0Fなら転送元は$C600です。DMAは開始しません。

書式

c
void __cgb_safe_set_hdma2(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

1ブロックは16バイトです。新規転送は1〜128ブロック(16〜2048バイト)で、制御値0は空の転送ではなく16バイトを意味します。GDMAは完了までCPUを止めます。HBlank DMAは表示行のHBlankごとに1ブロックを転送し、フレームをまたいで続きます。データを再利用する前に、呼び出し側で完了を確認してください。

GDMAはLCD停止中、または転送が収まる長さの書き込み可能期間で使用します。HBlank DMAはLCD動作中のSTATモード0以外で開始し、このサンプルではVBlankから開始します。動作中は転送元バンク、転送先VBK、転送元データを変更しないでください。HALT中はHBlank DMAも停止します。これらの関数は待機・割り込み保護・転送範囲の検査を追加しません。

状態は$FF55を別途読みます。ビット7が0なら動作中、$FFなら完了です。中止後はビット7が1になり、ビット0〜6に残りブロック数−1が残ります。転送済みのバイトは元に戻りません。動作中でない状態でビット7を0にして書くとGDMAが始まるので、中止は動作中にだけ行ってください。転送全体が有効な転送元領域と転送先VRAMバンクに収まるようにし、アドレスのあふれに依存しないでください。

これらのHDMAレジスタ設定関数は、CGBのハードウェアDMAを操作します。__vram_copy_dmaは別の組み込み関数で、CPUによるコピー処理の別名です。その名前でも、このDMAコントローラを使うわけではありません。

使用例

c
__cgb_safe_set_hdma2(0x0F); // Low four bits are ignored by hardware: source remains $C600.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完全版プログラムは、$C600の16バイト境界に置いたバッファで3種類の転送処理を比較します。GDMAは32バイトを$8010へコピーし、画面(16,24)に上端左1画素から下端8画素まで広がる赤い8×8の三角形、(24,24)に赤い1画素幅の四角い枠を表示します。HBlank DMAは32バイトを$8030へコピーし、(16,48)に青い4×8の左半分、(24,48)に青い8×8の正方形を表示します。数値による代用ではなく、転送されたタイルパターンそのものです。

CANCELED行には、CPUであらかじめ書き込んだ緑の4×8の半タイル(16,72)と緑の8×8の正方形(24,72)があります。この領域を転送先とする2ブロックのHBlank DMAを、同じVBlank中に開始・中止して最初のブロックも転送させません。$FF55は$81になり、1フレーム後も両パターンが変わらないことを確かめます。DMGでは上の2行は空白で、この2つの保持確認用図形だけが黒で表示されます。文字は白地に黒です。FAILED CHECKS 000では転送バイトと完了状態も確認します。KOKURAで兼用ROMのDMG/CGB実行とCGB専用実行を検証し、一部転送後の中止・倍速・実機は未検証です。

__cgb_safe_set_hdma2: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma2: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma2: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1872

__cgb_safe_set_hdma3

VRAM内のDMA転送先アドレスの上位部分をHDMA3($FF53)へ書き込みます。有効なのはビット0〜4で、転送先は常に$8000〜$9FFF内です。HDMA4と組み合わせて指定します。VRAMバンクの選択やDMA開始は行いません。

書式

c
void __cgb_safe_set_hdma3(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

1ブロックは16バイトです。新規転送は1〜128ブロック(16〜2048バイト)で、制御値0は空の転送ではなく16バイトを意味します。GDMAは完了までCPUを止めます。HBlank DMAは表示行のHBlankごとに1ブロックを転送し、フレームをまたいで続きます。データを再利用する前に、呼び出し側で完了を確認してください。

GDMAはLCD停止中、または転送が収まる長さの書き込み可能期間で使用します。HBlank DMAはLCD動作中のSTATモード0以外で開始し、このサンプルではVBlankから開始します。動作中は転送元バンク、転送先VBK、転送元データを変更しないでください。HALT中はHBlank DMAも停止します。これらの関数は待機・割り込み保護・転送範囲の検査を追加しません。

状態は$FF55を別途読みます。ビット7が0なら動作中、$FFなら完了です。中止後はビット7が1になり、ビット0〜6に残りブロック数−1が残ります。転送済みのバイトは元に戻りません。動作中でない状態でビット7を0にして書くとGDMAが始まるので、中止は動作中にだけ行ってください。転送全体が有効な転送元領域と転送先VRAMバンクに収まるようにし、アドレスのあふれに依存しないでください。

これらのHDMAレジスタ設定関数は、CGBのハードウェアDMAを操作します。__vram_copy_dmaは別の組み込み関数で、CPUによるコピー処理の別名です。その名前でも、このDMAコントローラを使うわけではありません。

使用例

c
__cgb_safe_set_hdma3(0xE0); // Only low five bits select the high VRAM offset.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完全版プログラムは、$C600の16バイト境界に置いたバッファで3種類の転送処理を比較します。GDMAは32バイトを$8010へコピーし、画面(16,24)に上端左1画素から下端8画素まで広がる赤い8×8の三角形、(24,24)に赤い1画素幅の四角い枠を表示します。HBlank DMAは32バイトを$8030へコピーし、(16,48)に青い4×8の左半分、(24,48)に青い8×8の正方形を表示します。数値による代用ではなく、転送されたタイルパターンそのものです。

CANCELED行には、CPUであらかじめ書き込んだ緑の4×8の半タイル(16,72)と緑の8×8の正方形(24,72)があります。この領域を転送先とする2ブロックのHBlank DMAを、同じVBlank中に開始・中止して最初のブロックも転送させません。$FF55は$81になり、1フレーム後も両パターンが変わらないことを確かめます。DMGでは上の2行は空白で、この2つの保持確認用図形だけが黒で表示されます。文字は白地に黒です。FAILED CHECKS 000では転送バイトと完了状態も確認します。KOKURAで兼用ROMのDMG/CGB実行とCGB専用実行を検証し、一部転送後の中止・倍速・実機は未検証です。

__cgb_safe_set_hdma3: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma3: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma3: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1873

__cgb_safe_set_hdma4

VRAM転送先アドレスの下位バイトをHDMA4($FF54)へ書き込みます。ビット0〜3は無視され、16バイト境界へ切り下げられます。HDMA3=$E0、HDMA4=$1Fなら転送先は$8010です。転送長ではなくアドレスの設定です。

書式

c
void __cgb_safe_set_hdma4(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

1ブロックは16バイトです。新規転送は1〜128ブロック(16〜2048バイト)で、制御値0は空の転送ではなく16バイトを意味します。GDMAは完了までCPUを止めます。HBlank DMAは表示行のHBlankごとに1ブロックを転送し、フレームをまたいで続きます。データを再利用する前に、呼び出し側で完了を確認してください。

GDMAはLCD停止中、または転送が収まる長さの書き込み可能期間で使用します。HBlank DMAはLCD動作中のSTATモード0以外で開始し、このサンプルではVBlankから開始します。動作中は転送元バンク、転送先VBK、転送元データを変更しないでください。HALT中はHBlank DMAも停止します。これらの関数は待機・割り込み保護・転送範囲の検査を追加しません。

状態は$FF55を別途読みます。ビット7が0なら動作中、$FFなら完了です。中止後はビット7が1になり、ビット0〜6に残りブロック数−1が残ります。転送済みのバイトは元に戻りません。動作中でない状態でビット7を0にして書くとGDMAが始まるので、中止は動作中にだけ行ってください。転送全体が有効な転送元領域と転送先VRAMバンクに収まるようにし、アドレスのあふれに依存しないでください。

これらのHDMAレジスタ設定関数は、CGBのハードウェアDMAを操作します。__vram_copy_dmaは別の組み込み関数で、CPUによるコピー処理の別名です。その名前でも、このDMAコントローラを使うわけではありません。

使用例

c
__cgb_safe_set_hdma4(0x1F); // Destination aligns to $8010, not $801F.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完全版プログラムは、$C600の16バイト境界に置いたバッファで3種類の転送処理を比較します。GDMAは32バイトを$8010へコピーし、画面(16,24)に上端左1画素から下端8画素まで広がる赤い8×8の三角形、(24,24)に赤い1画素幅の四角い枠を表示します。HBlank DMAは32バイトを$8030へコピーし、(16,48)に青い4×8の左半分、(24,48)に青い8×8の正方形を表示します。数値による代用ではなく、転送されたタイルパターンそのものです。

CANCELED行には、CPUであらかじめ書き込んだ緑の4×8の半タイル(16,72)と緑の8×8の正方形(24,72)があります。この領域を転送先とする2ブロックのHBlank DMAを、同じVBlank中に開始・中止して最初のブロックも転送させません。$FF55は$81になり、1フレーム後も両パターンが変わらないことを確かめます。DMGでは上の2行は空白で、この2つの保持確認用図形だけが黒で表示されます。文字は白地に黒です。FAILED CHECKS 000では転送バイトと完了状態も確認します。KOKURAで兼用ROMのDMG/CGB実行とCGB専用実行を検証し、一部転送後の中止・倍速・実機は未検証です。

__cgb_safe_set_hdma4: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma4: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma4: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1874

__cgb_safe_set_hdma5

HDMA5($FF55)へ書き込み、DMAを開始するか、動作中のHBlank転送を中止します。新規転送ではビット0〜6に「ブロック数−1」、ビット7にGDMA(0)またはHBlank DMA(1)を指定します。HBlank転送中にビット7を0にして書くと残りを中止し、新しいGDMAは開始しません。

書式

c
void __cgb_safe_set_hdma5(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

1ブロックは16バイトです。新規転送は1〜128ブロック(16〜2048バイト)で、制御値0は空の転送ではなく16バイトを意味します。GDMAは完了までCPUを止めます。HBlank DMAは表示行のHBlankごとに1ブロックを転送し、フレームをまたいで続きます。データを再利用する前に、呼び出し側で完了を確認してください。

GDMAはLCD停止中、または転送が収まる長さの書き込み可能期間で使用します。HBlank DMAはLCD動作中のSTATモード0以外で開始し、このサンプルではVBlankから開始します。動作中は転送元バンク、転送先VBK、転送元データを変更しないでください。HALT中はHBlank DMAも停止します。これらの関数は待機・割り込み保護・転送範囲の検査を追加しません。

状態は$FF55を別途読みます。ビット7が0なら動作中、$FFなら完了です。中止後はビット7が1になり、ビット0〜6に残りブロック数−1が残ります。転送済みのバイトは元に戻りません。動作中でない状態でビット7を0にして書くとGDMAが始まるので、中止は動作中にだけ行ってください。転送全体が有効な転送元領域と転送先VRAMバンクに収まるようにし、アドレスのあふれに依存しないでください。

これらのHDMAレジスタ設定関数は、CGBのハードウェアDMAを操作します。__vram_copy_dmaは別の組み込み関数で、CPUによるコピー処理の別名です。その名前でも、このDMAコントローラを使うわけではありません。

使用例

c
__cgb_safe_set_hdma5(1); // LCD is off: GDMA, (1+1)*16 = 32 bytes.
if (color_mode && D_CONTROL!=255) failures++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完全版プログラムは、$C600の16バイト境界に置いたバッファで3種類の転送処理を比較します。GDMAは32バイトを$8010へコピーし、画面(16,24)に上端左1画素から下端8画素まで広がる赤い8×8の三角形、(24,24)に赤い1画素幅の四角い枠を表示します。HBlank DMAは32バイトを$8030へコピーし、(16,48)に青い4×8の左半分、(24,48)に青い8×8の正方形を表示します。数値による代用ではなく、転送されたタイルパターンそのものです。

CANCELED行には、CPUであらかじめ書き込んだ緑の4×8の半タイル(16,72)と緑の8×8の正方形(24,72)があります。この領域を転送先とする2ブロックのHBlank DMAを、同じVBlank中に開始・中止して最初のブロックも転送させません。$FF55は$81になり、1フレーム後も両パターンが変わらないことを確かめます。DMGでは上の2行は空白で、この2つの保持確認用図形だけが黒で表示されます。文字は白地に黒です。FAILED CHECKS 000では転送バイトと完了状態も確認します。KOKURAで兼用ROMのDMG/CGB実行とCGB専用実行を検証し、一部転送後の中止・倍速・実機は未検証です。

__cgb_safe_set_hdma5: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma5: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma5: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1875

__cgb_safe_set_obpd

CGBのスプライト(OBJ)用パレットのデータレジスタOCPD/OBPD($FF6B)へ1バイト書き込みます。__cgb_safe_set_ocpdも同じレジスタを操作します。書き込み位置は対応する索引レジスタで選びます。RGB15の1色は下位・上位の2バイトで指定します。

書式

c
__cgb_safe_set_obpd(/* 書式は実装と呼び出し例を参照 */);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

1バイト(0〜255)。RGB15の下位バイト、上位バイトの順に書きます。あらかじめ索引の自動増分を有効にするか、各バイトの位置を明示的に選んでください。データ書き込み自体が自動増分を有効にすることはありません。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_obpd(0); __cgb_safe_set_obpd(0x7C); // Alias of OCPD: blue at slot 31.
expect_color(1,31,31744);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_obpd: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_obpd: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_obpd: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_obpd: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1869

__cgb_safe_set_obpi

CGBのスプライト(OBJ)用パレットのバイト位置レジスタOCPS/OBPI($FF6A)へ書き込みます。__cgb_safe_set_ocpsも同じレジスタを操作します。次のデータレジスタ読み書きの位置を選ぶ処理であり、この呼び出しだけでは色を変更しません。

書式

c
__cgb_safe_set_obpi(/* 書式は実装と呼び出し例を参照 */);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

ビット0〜5でバイト位置(0〜63)を指定し、ビット7を立てるとデータ書き込み後に位置が自動で進みます。ビット6は未使用です。色スロットnの先頭はバイト2nです。63の次は0へ戻るため、レジスタを直接操作する場合の範囲管理は呼び出し側で行います。

戻り値

ありません(void)。

使用条件と制限

パレット操作とガード付きレジスタ書き込みは、DMGモードでは何も書き込みません。ここでsafeはCGBモードの判定を指し、PPUへのアクセス待ちや割り込み禁止を意味しません。LCD停止中、または書き込み可能なPPU期間に収まる短い更新で使用してください。VBlank待ちや複数バイト更新の割り込み保護は行いません。

使用例

c
__cgb_safe_set_obpi(0xBE); // Alias of OCPS: select the OBJ store's last word.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

同じタイルパターンでも、BGとOBJで別々のパレットの色が使われることを学ぶプログラムです。三角形は8×8画素で、上端左の1画素から下端の8画素まで広がります。x=16・32・48・64・80・96・112の位置で、y=24のBG行は赤・青・緑・赤・青・緑・赤、y=64のOBJ行は青・緑・赤・青・緑・赤・青になります。各列では順に、1色指定、RGB成分指定、番号付き範囲、全パレット、通し番号の範囲、標準名のレジスタ、別名のレジスタによる書き込みを使います。

y=88の7個の8×8マスは、x=16から順に、赤い三角形、バンク1の青い1画素幅の四角い枠(32)、左右反転した緑の三角形(48)、上下反転した赤の三角形(64)、両方向に反転した青の三角形(80)、赤いスプライトより手前の緑の三角形(96)、優先フラグなしの比較用にBGを覆う赤い正方形のスプライト(112)です。x=96ではBGの色番号0の部分から赤が見えます。座標は画面左上を原点とする画素単位です。余白は白で、文字は黒です。

FAILED CHECKS 000は、RGBのマスク、2を含む真偽フラグ、パレット番号の折り返し、パレット境界を越える書き込み、スロット31での打ち切り、null・長さ0で何も書かないこと、LCD停止中の色の読み戻しが通ったことを示します。兼用ROMのDMG/CGB実行、DMG専用ROMのDMG実行、CGB専用ROMのCGB実行の4条件を検証します。DMGではCGB MODEが0で、三角形は黒、バンク・反転属性による見た目の変化はなく、最下段の最後の2マスは黒い正方形です。CGBの検証ではCGB MODEが1です。実機とLCD表示中のパレットアクセス競合は未検証です。

__cgb_safe_set_obpi: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_obpi: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_obpi: --cgb=dmg / DMG
--cgb=dmg / DMG
__cgb_safe_set_obpi: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1867

__cgb_safe_set_svbk

CGBでWRAM選択レジスタSVBK($FF70)へ値を直接書き込みます。ハードウェアはビット0〜2を使い、値0と1はいずれもバンク1を$D000〜$DFFFに割り当てます。例えば8もバンク1になります。__svbk_setとは異なり兼用ビルドでも使えますが、切り替え前のバンク番号は返しません。

書式

c
void __cgb_safe_set_svbk(u8 value);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

value

レジスタへ渡すu8値(0〜255)。各ビットの意味は上記のとおりです。ポインタや転送バイト数ではなく、レジスタ用の1バイトを渡します。

戻り値

ありません(void)。

使用条件と制限

コンパイラがCGBモードを判定して書き込みをガードし、DMGモードでは何も書き込みません。safeはこのモード判定だけを意味します。以前のレジスタ値の保存・復元は行いません。

バンク切り替えは$D000〜$DFFFへのすべてのアクセスに影響し、固定WRAMの$C000〜$CFFFは変わりません。切り替え中に必要なスタックと変数は、切り替わる領域の外に置いてください。サンプルでは--stack-bank=fixedと固定WRAMへの明示的な変数配置を使います。割り込み処理やバンクを使うDMAとの調整は呼び出し側で行います。これらのAPIは割り込み禁止区間を作りません。

使用例

c
__cgb_safe_set_svbk(0); // Raw selector 0 maps effective bank 1; this API returns void.
if ((W_SELECTOR&7)!=0) failures++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

CGB専用の完全版プログラムは、WRAMバンク2の$D100に1、バンク3の同じアドレスに2を保存し、両方を読み戻してバンク1へ戻します。その値で(16,24)の赤い8×8正方形と、(16,48)の青い1画素幅の8×8の四角い枠を選びます。FAILED CHECKS 000は、直接指定値の正規化、バンクの独立性、定数・変数・式・関数の戻り値を使った切り替え前バンクの返却を確認します。別の6件の不正ビルド用テストで、DMG・兼用指定のKQ2103と、定数バンク0・8のKQ2102を確認します。実行検証はKOKURAで行い、実機と割り込みによる同時バンク変更は未検証です。

__cgb_safe_set_svbk: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_wram_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_wram_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb_only --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:1862

__critical_enter

ソフトウェアのネスト深さを1増やしてDIを実行します。

書式

c
u8 __critical_enter();

戻り値

増加前の深さをu8トークンとして返します。外側は0、次の内側は1です。

使用条件と制限

トークンは逆順にleaveへ渡し、256段の周回を起こさないでください。以前のハードウェアIMEは保存しません。もともとDI状態を保つ必要がある場面には使わないでください。

使用例

c
*((u8*)0xFFFF)=0;outer=__critical_enter();inner=__critical_enter();result[7]=outer;result[8]=inner;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__critical_enter: DMG
DMG
__critical_enter: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7970

kitaqgb/kitaqgb/CodeGenerator.cs:7970

csharp
if (funcName == "__critical_enter")
            {
                if (args.Length != 0) Error(expr, "__critical_enter() expects 0 arguments");

                EmitLoadA(CriticalDepthVar);
                EmitAsm("LD_B_A"); // token = old depth
                EmitAsm("INC_A");
                EmitStoreA(CriticalDepthVar);
                EmitAsm("DI");
                EmitAsm("LD_A_B");
                return;
            }
__critical_leave

ネスト深さを減らし、外側のトークン0ならEIで割り込みを再開します。

書式

c
void __critical_leave(u8 token);

引数

token

対応する__critical_enterの返したトークン。

戻り値

戻り値はありません。

使用条件と制限

非0トークンではEIしません。任意の0を渡すと、ほかの区間が残っていても有効化するため、必ず正しく対応させます。

使用例

c
__critical_leave(inner);__critical_leave(outer);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__critical_leave: DMG
DMG
__critical_leave: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7984

kitaqgb/kitaqgb/CodeGenerator.cs:7984

csharp
if (funcName == "__critical_leave")
            {
                if (args.Length != 1) Error(expr, "__critical_leave(token) expects 1 argument");

                CompileIntoA(args[0]);
                EmitAsm("LD_B_A"); // token

                AsmOperand clAfterDec = MakeUniqueLabel("critleave_afterdec");
                AsmOperand clDone = MakeUniqueLabel("critleave_done");

                EmitLoadA(CriticalDepthVar);
                EmitAsm("OR_A");
                EmitAsm("JR_Z", clAfterDec);
                EmitAsm("DEC_A");
                EmitStoreA(CriticalDepthVar);
                EmitLabel(clAfterDec);

                // Re-enable only for outermost token (token==0).
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("JR_NZ", clDone);
                EmitAsm("EI");
                EmitLabel(clDone);
                return;
            }
__dot2_q8_8

符号なし2項の内積を計算します。

書式

c
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

引数

a0

符号なし16ビット値0~65535。

a1

符号なし16ビット値0~65535。

b0

符号なし8ビット係数0~255。

b1

符号なし8ビット係数0~255。

戻り値

各積をfloor(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[36]=__dot2_q8_8(513,769,64,127);
v0=513;v1=769;v2=64;v3=127;
result[37]=__dot2_q8_8(v0,v1,v2,v3);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[36]=509, result[37]=509 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__dot2_q8_8: DMG
DMG
__dot2_q8_8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11575

kitaqgb/kitaqgb/CodeGenerator.cs:11575

csharp
if (funcName == "__dot2_q8_8")
{
    if (args.Length != 4) Error(expr, "__dot2_q8_8 requires 4 arguments");
    LastCallReturnsHL = true;

    // Fast paths when coefficients are compile-time constants.
    // Special-case: 0 / power-of-two coefficients (u8) => shift instead of mul.
    if (TryGetU8Const(args[2], out int ax0) && (ax0 == 0) && TryGetU8Const(args[3], out int ay0) && (ay0 == 0))
    {
        CompileDiscard(args[0]);
        CompileDiscard(args[1]);
        EmitAsm("XOR_A");
        EmitAsm("LD_H_A");
        EmitAsm("LD_L_A");
        return;
    }
    if (TryGetU8Const(args[2], out int axOnly) && (axOnly == 0))
    {
        CompileDiscard(args[0]);
        // only y*ay
        if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
        {
            CompileIntoHL(args[1]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[3]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitMul16x8Shr8_HL("__dot2_y");
        }
        return;
    }
    if (TryGetU8Const(args[3], out int ayOnly) && (ayOnly == 0))
    {
        // only x*ax
        if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
        {
            CompileIntoHL(args[0]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[2]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitMul16x8Shr8_HL("__dot2_x");
        }
        // Preserve the scaled result while evaluating the zero-product input.
        EmitAsm("PUSH_HL");
        CompileDiscard(args[1]);
        EmitAsm("POP_HL");
        return;
    }

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitMul16x8Shr8_HL("__dot2_x");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitMul16x8Shr8_HL("__dot2_y");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__dot3_q8_8

符号なし3項の内積を計算します。

書式

c
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

引数

a0

符号なし16ビット値0~65535。

a1

符号なし16ビット値0~65535。

a2

符号なし16ビット値0~65535。

b0

符号なし8ビット係数0~255。

b1

符号なし8ビット係数0~255。

b2

符号なし8ビット係数0~255。

戻り値

各積をfloor(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[6]=__dot3_q8_8(513,769,1025,64,127,3);
v0=513;v1=769;v2=1025;v3=64;v4=127;v5=3;
result[7]=__dot3_q8_8(v0,v1,v2,v3,v4,v5);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[6]=521, result[7]=521 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__dot3_q8_8: DMG
DMG
__dot3_q8_8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11550

kitaqgb/kitaqgb/CodeGenerator.cs:11550

csharp
if (funcName == "__dot3_q8_8")
            {
                if (args.Length != 6) Error(expr, "__dot3_q8_8 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: false, q1_7: false, labelPrefix: "__dot3_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: false, q1_7: false, labelPrefix: "__dot3_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: false, q1_7: false, labelPrefix: "__dot3_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__farcall

指定したバンク番号を使って、引数のない名前付き関数を呼び出します。バンク切り替えが必要な場合は、固定ROM内の補助処理が関数の終了後に呼び出し元のバンクを復元します。

書式

c
__farcall(bank, function_name);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

bank

符号なし8ビットのROMバンク番号です。呼び出しでは関数の配置と一致させ、読み出しでは転送元のマッピングを指定します。対象カートリッジで有効な範囲を使用してください。

func

宣言済みの、引数のない関数名です。この名前指定の組み込み呼び出しには、関数アドレスを格納した変数を渡せません。bank引数をコールバックの引数として渡すこともありません。

戻り値

戻り値の型は呼び出した関数に従います。voidのコールバックは値を返さず、u8・u16のコールバックはその値を返します。掲載サンプルはvoidのコールバックを使います。

使用条件と制限

これらの呼び出しは、ユーザー引数を渡したり、コールバックを割り込みに対して再入可能にしたりするものではありません。コールバック自身のデータ・スタック・割り込みの使い方を適切に管理してください。バンク復帰は入れ子呼び出しと反復呼び出しで検証していますが、この例で実機や任意の割り込みタイミングを検証したわけではありません。

GBのこれらのバンクAPIは8ビットの番号を扱い、MBC5の9ビット目は設定しません。バンク0の扱いなど、カートリッジ固有の規則も適用されます。例ではMBC5のバンク1〜3を明示的に使います。記録に反映されないマッパーへの直接書き込みと混在させないでください。

使用例

c
__farcall(3,callback);
bank_expect(callback_count==2 && callback_byte==0xD3,16);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

完成プログラムのこの位置では、__farcallによってcallback_countが2になります。コールバックはバンク3からD3を読みます。その後にhome2を近ポインターで読んでB2を確認し、バンク2が選ばれた状態で復帰したことを確かめます。

完成プログラムでは、バンク番号と近アドレスによるデータの指定、バンクをまたぐコールバック、ライブラリの記録と実際のマッピングの区別を学べます。数値はすべて16進数です。BANK INとBANK OUTは02、BYTE HEXはD3、WORD HEXは5C7A、FAILED CHECKSとFIRST FAILUREはどちらも00が期待値です。CGBとFCは青字、DMGは黒字で表示します。

CALL COUNTは03です。far_call、__farcall、__farcall_ptrがそれぞれ1回コールバックを実行します。

__farcall: DMG
DMG
__farcall: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\banked_data_calls.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7288

kitaqgb/kitaqgb/CodeGenerator.cs:7288

csharp
if (funcName == "__farcall")
            {
                if (args.Length != 2) Program.Error("__farcall(bank, func) expects 2 arguments");
                if (!args[1].Match(Tag.Name, out string targetName)) Program.Error("__farcall(bank, func) requires 2nd argument to be a function name");

                // A named far call supplies no arguments. Reject data symbols
                // and callbacks that would otherwise read stale argument storage.
                CFunctionInfo targetInfo;
                if (!Functions.TryGetValue(targetName, out targetInfo))
                {
                    Error(expr, "__farcall requires a declared function name");
                    return;
                }
                if ((targetInfo.Parameters ?? Array.Empty<FieldInfo>()).Length != 0)
                {
                    Error(expr, "__farcall requires a callback with no parameters");
                    return;
                }
                Expr bankExpr = FoldConstants(args[0]);
                int targetBank = -1;
                if (targetInfo != null)
                {
                    targetBank = targetInfo.RomBank;
                    PrepareStructReturnDestination(expr, targetInfo.ReturnType, targetInfo, targetName);
                    if (targetInfo.ReturnType != null)
                        LastCallReturnsHL = ReturnsInHL(expr, targetInfo.ReturnType);
                }

                // A known bank permits direct dispatch when safe, otherwise request a fixed-bank thunk.
                if (TryResolveBankExprToConstU8(args[0], out int bankLiteral))
                {
                    int optimizedBank = bankLiteral & 0xFF;
                    bool farcallDirectOK = IsDirectCallBankSafe(optimizedBank, CurrentFunctionBank);
                    if (!farcallDirectOK)
                    {
                        string farcallThunkName = EnsureBank0Thunk(
                            targetName,
                            optimizedBank,
                            isStackCall: targetInfo != null && targetInfo.IsStackCall,
                            stackArgBytes: 0);
                        EmitAsm("CALL", new AsmOperand(farcallThunkName, AddressMode.Absolute));
                        RecordCallEdge(expr, targetName, optimizedBank, "farcall_bank_thunk", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                    }
                    else
                    {
                        if (Program.CheckBankCalls && !InUnsafe && optimizedBank != 0)
                        {
                            AsmOperand ok = MakeUniqueLabel("kq_bank_ok");
                            EmitLoadA(RomBankVar);
                            EmitAsm("CP_IMM", new AsmOperand(optimizedBank & 0xFF, AddressMode.Immediate));
                            EmitAsm("JP_Z", ok);
                            EmitAsm("JP", EnsureCheckTrap());
                            EmitLabel(ok);
                        }

                        EmitAsm("CALL", new AsmOperand(targetName, AddressMode.Absolute));
                        RecordCallEdge(expr, targetName, optimizedBank, "farcall_direct", viaThunk: false, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                    }
                    return;
                }

                // A = requested bank argument (runtime value)
                CompileIntoA(bankExpr);

                // Always dispatch via bank0 helper thunk.
                string thunkName = EnsureBank0RuntimeFarcallThunk(targetName);
                EmitAsm("CALL", new AsmOperand(thunkName, AddressMode.Absolute));
                RecordCallEdge(expr, targetName, targetBank, "farcall_intrinsic", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                return;
            }
__getwinmapbase

LCDCのbit6からウィンドウのタイルマップ先頭を求めます。

書式

c
u16 __getwinmapbase();

戻り値

$9800または$9C00。タイル画像の格納先アドレスではありません。

使用例

c
M_LCDC=0;result[2]=__getwinmapbase();M_LCDC=0x40;result[3]=__getwinmapbase();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__getwinmapbase: DMG
DMG
__getwinmapbase: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10642

kitaqgb/kitaqgb/CodeGenerator.cs:10642

csharp
if (funcName == "__getwinmapbase")
{
    if (args.Length != 0) Error(expr, "__getwinmapbase requires 0 arguments");

    AsmOperand win_base0 = MakeUniqueLabel("getwinbase_base0");
    AsmOperand win_done  = MakeUniqueLabel("getwinbase_done");

    EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
    EmitAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)); // bit6 (Window tile map display select)
    EmitAsm("JR_Z", win_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
    EmitAsm("JR", win_done);
    EmitLabel(win_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
    EmitLabel(win_done);
    LastCallReturnsHL = true;
    return;
}
__mac16

符号なし積和を計算します。

書式

c
u16 __mac16(u16 acc, u16 a, u8 b);

引数

acc

加算する16ビットの累積値。

a

符号なし16ビット値0~65535。

b

符号なし8ビット係数0~255。

戻り値

各積をfloor(a*b/256)で求め、accへ加えます。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[34]=__mac16(65500,513,127);
v0=65500;v1=513;v2=127;
result[35]=__mac16(v0,v1,v2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[34]=218, result[35]=218 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__mac16: DMG
DMG
__mac16: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11499

kitaqgb/kitaqgb/CodeGenerator.cs:11499

csharp
if (funcName == "__mac16")
            {
                if (args.Length != 3) Error(expr, "__mac16 requires 3 arguments");

                // mul = (a*b)>>8
                EmitDotTermToHL(args[1], args[2], signed: false, q1_7: false, labelPrefix: "__mac16_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__mul16x8

符号なし乗算を計算します。

書式

c
u16 __mul16x8(u16 a, u8 b);

引数

a

符号なし16ビット値0~65535。

b

符号なし8ビット係数0~255。

戻り値

各積をfloor(a*b/256)で求めます。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[24]=__mul16x8(513,127);
v0=513;v1=127;
result[25]=__mul16x8(v0,v1);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[24]=254, result[25]=254 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__mul16x8: DMG
DMG
__mul16x8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11467

kitaqgb/kitaqgb/CodeGenerator.cs:11467

csharp
if (funcName == "__mul16x8")
            {
                if (args.Length != 2) Error(expr, "__mul16x8 requires 2 arguments");

                EmitDotTermToHL(args[0], args[1], signed: false, q1_7: false, labelPrefix: "__mul16x8");
                LastCallReturnsHL = true;
                return;
            }
__mul8x8_hi

8ビット同士を掛け、積の上位8ビットを取り出します。

書式

c
u8 __mul8x8_hi(u8 a, u8 b);

引数

a

符号なし8ビット値0~255。

b

符号なし8ビット値0~255。

戻り値

floor(a*b/256)、0~254。

使用例

c
// Compare literal coefficients with values read from RAM.
result[4]=__mul8x8_hi(255,255);
v0=255;v1=255;
result[5]=__mul8x8_hi(v0,v1);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[4]=254, result[5]=254 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__mul8x8_hi: DMG
DMG
__mul8x8_hi: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11452

kitaqgb/kitaqgb/CodeGenerator.cs:11452

csharp
if (funcName == "__mul8x8_hi")
            {
                if (args.Length != 2) Error(expr, "__mul8x8_hi requires 2 arguments");

                CompileIntoA(args[0]);
                EmitAsm("PUSH_AF");
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_AF");
                EmitAsm("LD_E_A");
                EmitMul8x8ToHL_EC("__mul8x8_hi");
                EmitAsm("LD_A_H");
                return;
            }
__padrep

maskで対象にしたボタンから、1つの操作を選んでリピートさせます。対象ボタンが新たに押された場合は、立っている最下位ビットを選んで直ちに返します。それ以外は、選択中の操作が対象に含まれ、押され続けている限り維持します。その操作が終わると、残っている押下中の最下位ビットを選びますが、その場ではパルスを返しません。対象の押下中ビットがなくなると実行時の状態をクリアします。

書式

c
u8 __padrep(u8* state, u8 keys, u8 trigger, u8 mask);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

keys

GBのBTN_*配置による、現在押している全ボタンのマスクです。__readpad、__readpadexの下位バイト、またはinput_currentから取得した値を渡します。リピート処理自体はハードウェアを読み取りません。

trigger

今回新しく押されたビットを、keysと同じ配置で渡します。通常はkeys & ~previous_keys、または__readpadexの上位バイトです。keysにも含まれるビットだけを指定してください。毎回押下中マスクを渡すと、呼び出すたびに新しい押下として待ち時間を最初から数え直してしまいます。

mask

対象にするGBのボタンビットを|で組み合わせます。0なら全操作を無効にして実行時の状態をクリアします。複数ビットを指定すると候補は増えますが、1回の呼び出しで返すのは選択された1ビットだけです。複数操作のパルスをまとめたマスクにはなりません。

戻り値

操作のパルスが発生しない場合は0、それ以外は1, 2, 4, 8, 16, 32, 64, 128のいずれかです。0以外の値はボタンのビットであり、0か1に正規化した真偽値やリピート回数ではありません。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

初期待ち時間です。0〜255で指定し、リピート処理の呼び出し回数を数えます。正の値Nでは、押した瞬間のパルスからちょうどN回後の呼び出しで最初の長押しリピートが発生します。0の場合は初期待ち時間との一致判定を通らず、リピート間隔が最初の長押しパルスの時期を決めます。単位はミリ秒ではありません。

2回目以降のリピート間隔です。0〜255の呼び出し回数で指定します。正の値Nでは、最初のリピート後、対象ボタンを押している更新N回ごとに発生します。0では初期待ち時間が終わった後の対象更新ごとに発生するため、その段階では0も1も毎回パルスを返します。待ち時間と間隔の両方が0なら、押している更新のたびにパルスを返します。

入力を処理する周期ごとに1回呼び出してください。呼び出すたびに状態を変更し、VBlank待ちは行いません。新しい押下ビットなしで押下中のボタンを選び直した場合、その呼び出しでは0を返し、新たな待ち時間を開始します。この関数群は1つの状態につき1操作を選択します。一方、input_repeatは8ボタンそれぞれの独立したタイマーを使います。

使用例

c
result=__padrep(actions,keys,trigger,48); // A|B: select A, the lower bit.
    if (result!=0) action_pulses++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep: DMG
DMG
__padrep: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11249

csharp
{
    if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");

    AsmOperand pm_done        = MakeUniqueLabel("padrepM_done");
    AsmOperand pm_ret0        = MakeUniqueLabel("padrepM_ret0");
    AsmOperand pm_no_trig     = MakeUniqueLabel("padrepM_notrig");
    AsmOperand pm_reset_all   = MakeUniqueLabel("padrepM_reset");
    AsmOperand pm_dir0        = MakeUniqueLabel("padrepM_dir0");
    AsmOperand pm_have_dir    = MakeUniqueLabel("padrepM_havedir");
    AsmOperand pm_first_rep   = MakeUniqueLabel("padrepM_firstrep");
    AsmOperand pm_ret_dir     = MakeUniqueLabel("padrepM_retdir");
    AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
    AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger, E = mask
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
    EmitAsm("POP_HL");

    // trigMasked = trigger & mask -> D
    EmitAsm("LD_A_C");
    EmitAsm("AND_E");
    EmitAsm("LD_D_A");
    EmitAsm("OR_A");
    EmitAsm("JP_Z", pm_no_trig);

    // select = lowbit(trigMasked) = x & -x
    EmitAsm("LD_A_D");
    EmitAsm("CPL");
    EmitAsm("INC_A");
    EmitAsm("AND_D");
    EmitAsm("LD_C_A"); // save selected in C

    // dirMask=selected, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [0]=selected
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); Emi
__padrep_down

下方向だけを対象としてリピート状態を1回進めます。下が新たに押されたら直ちに8を返し、カウンターを最初から数え直します。下を押し続けると設定したタイミングでパルスが発生し、離すと実行時の状態をクリアします。他のボタンではこのリピート処理は開始されません。

書式

c
u8 __padrep_down(u8* state, u8 keys, u8 trigger);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

keys

GBのBTN_*配置による、現在押している全ボタンのマスクです。__readpad、__readpadexの下位バイト、またはinput_currentから取得した値を渡します。リピート処理自体はハードウェアを読み取りません。

trigger

今回新しく押されたビットを、keysと同じ配置で渡します。通常はkeys & ~previous_keys、または__readpadexの上位バイトです。keysにも含まれるビットだけを指定してください。毎回押下中マスクを渡すと、呼び出すたびに新しい押下として待ち時間を最初から数え直してしまいます。

戻り値

操作のパルスが発生しない場合は0、それ以外は8のいずれかです。0以外の値はボタンのビットであり、0か1に正規化した真偽値やリピート回数ではありません。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

初期待ち時間です。0〜255で指定し、リピート処理の呼び出し回数を数えます。正の値Nでは、押した瞬間のパルスからちょうどN回後の呼び出しで最初の長押しリピートが発生します。0の場合は初期待ち時間との一致判定を通らず、リピート間隔が最初の長押しパルスの時期を決めます。単位はミリ秒ではありません。

2回目以降のリピート間隔です。0〜255の呼び出し回数で指定します。正の値Nでは、最初のリピート後、対象ボタンを押している更新N回ごとに発生します。0では初期待ち時間が終わった後の対象更新ごとに発生するため、その段階では0も1も毎回パルスを返します。待ち時間と間隔の両方が0なら、押している更新のたびにパルスを返します。

入力を処理する周期ごとに1回呼び出してください。呼び出すたびに状態を変更し、VBlank待ちは行いません。新しい押下ビットなしで押下中のボタンを選び直した場合、その呼び出しでは0を返し、新たな待ち時間を開始します。この関数群は1つの状態につき1操作を選択します。一方、input_repeatは8ボタンそれぞれの独立したタイマーを使います。

使用例

c
result=__padrep_down(down,keys,trigger);
    if (result!=0) down_pulses++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep_down: DMG
DMG
__padrep_down: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11069

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_down requires 3 arguments (state*, keys, trigger)");

    AsmOperand pd_done        = MakeUniqueLabel("padrepD_done");
    AsmOperand pd_ret0        = MakeUniqueLabel("padrepD_ret0");
    AsmOperand pd_no_trig     = MakeUniqueLabel("padrepD_notrig");
    AsmOperand pd_dir0        = MakeUniqueLabel("padrepD_dir0");
    AsmOperand pd_have_dir    = MakeUniqueLabel("padrepD_havedir");
    AsmOperand pd_first_rep   = MakeUniqueLabel("padrepD_firstrep");
    AsmOperand pd_ret_dir     = MakeUniqueLabel("padrepD_retdir");
    AsmOperand pd_skip_dasinc = MakeUniqueLabel("padrepD_skipdasinc");
    AsmOperand pd_skip_arrinc = MakeUniqueLabel("padrepD_skiparrinc");
    AsmOperand pd_reset_all   = MakeUniqueLabel("padrepD_reset");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    EmitAsm("POP_HL");

    // --- Trigger Down? (bit3 = 0x08) ---
    EmitAsm("LD_A_C");
    EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate));
    EmitAsm("JP_Z", pd_no_trig);

    // dirMask=0x08, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_IMM", new AsmOperand(0x08, AddressMode.Immediate));
    EmitAsm("LD_HL_A"); // [0]=0x08
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // dasCnt=0
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A"); // arrCnt=0
    EmitAsm("POP_HL");

    EmitAsm
__padrep_init

呼び出し側が用意した5バイトのリピート状態を初期化します。選択中の操作と2つのカウンターを0にし、指定された初期待ち時間とリピート間隔を保存します。ボタンの読み取りやリピートパルスの発生は行いません。

書式

c
void __padrep_init(u8* state, u8 das, u8 arr);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

das

初期待ち時間です。0〜255で指定し、リピート処理の呼び出し回数を数えます。正の値Nでは、押した瞬間のパルスからちょうどN回後の呼び出しで最初の長押しリピートが発生します。0の場合は初期待ち時間との一致判定を通らず、リピート間隔が最初の長押しパルスの時期を決めます。単位はミリ秒ではありません。

arr

2回目以降のリピート間隔です。0〜255の呼び出し回数で指定します。正の値Nでは、最初のリピート後、対象ボタンを押している更新N回ごとに発生します。0では初期待ち時間が終わった後の対象更新ごとに発生するため、その段階では0も1も毎回パルスを返します。待ち時間と間隔の両方が0なら、押している更新のたびにパルスを返します。

戻り値

ありません(void)。

使用例

c
__padrep_init(lr,3,2);
// Active action and counters are zero; delay and interval are retained.
expect(lr[0],0); expect(lr[1],3); expect(lr[2],2);
expect(lr[3],0); expect(lr[4],0);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep_init: DMG
DMG
__padrep_init: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10808

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_init requires 3 arguments (state*, das, arr)");

    // Evaluate args with register safety
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // das
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // arr
    EmitAsm("POP_HL");

    // dirMask = 0; dasCnt=0; arrCnt=0; store params
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0
    EmitAsm("INC_HL");
    EmitAsm("LD_A_B");
    EmitAsm("LD_HL_A"); // [1]=das
    EmitAsm("INC_HL");
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [2]=arr
    EmitAsm("INC_HL");
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [3]=dasCnt
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A"); // [4]=arrCnt
    return;
}

// 2.21 __padrep_reset(state_ptr) -> void
// Resets runtime counters for PadRepeatState while keeping das/arr params:
// dirMask=0, dasCnt=0, arrCnt=0
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__padrep_reset")
{
    if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");

    CompileIntoHL(args[0]);

    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0 (dirMask)

    // [3]=0, [4]=0
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("LD_HL_A");
    EmitAsm("INC_HL"); // -> [4]
    EmitAsm("LD_HL_A");
    EmitAsm("POP_HL");
    return;
}

// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new
__padrep_lr

左右どちらか1つの操作について、リピート状態を1回進めます。新たに右が押されたら直ちに1、そうでなく左が押されたら2を返します。両方が同時に新しく押された場合は右を優先します。新しい押下がなく両方向を押し続けている間は0を返してカウンターを停止し、どちらも押していなければ実行時の状態をクリアします。

書式

c
u8 __padrep_lr(u8* state, u8 keys, u8 trigger);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

keys

GBのBTN_*配置による、現在押している全ボタンのマスクです。__readpad、__readpadexの下位バイト、またはinput_currentから取得した値を渡します。リピート処理自体はハードウェアを読み取りません。

trigger

今回新しく押されたビットを、keysと同じ配置で渡します。通常はkeys & ~previous_keys、または__readpadexの上位バイトです。keysにも含まれるビットだけを指定してください。毎回押下中マスクを渡すと、呼び出すたびに新しい押下として待ち時間を最初から数え直してしまいます。

戻り値

操作のパルスが発生しない場合は0、それ以外は1, 2のいずれかです。0以外の値はボタンのビットであり、0か1に正規化した真偽値やリピート回数ではありません。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

初期待ち時間です。0〜255で指定し、リピート処理の呼び出し回数を数えます。正の値Nでは、押した瞬間のパルスからちょうどN回後の呼び出しで最初の長押しリピートが発生します。0の場合は初期待ち時間との一致判定を通らず、リピート間隔が最初の長押しパルスの時期を決めます。単位はミリ秒ではありません。

2回目以降のリピート間隔です。0〜255の呼び出し回数で指定します。正の値Nでは、最初のリピート後、対象ボタンを押している更新N回ごとに発生します。0では初期待ち時間が終わった後の対象更新ごとに発生するため、その段階では0も1も毎回パルスを返します。待ち時間と間隔の両方が0なら、押している更新のたびにパルスを返します。

入力を処理する周期ごとに1回呼び出してください。呼び出すたびに状態を変更し、VBlank待ちは行いません。新しい押下ビットなしで押下中のボタンを選び直した場合、その呼び出しでは0を返し、新たな待ち時間を開始します。この関数群は1つの状態につき1操作を選択します。一方、input_repeatは8ボタンそれぞれの独立したタイマーを使います。

使用例

c
result=__padrep_lr(lr,keys,trigger);
    if (result!=0) pulses++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep_lr: DMG
DMG
__padrep_lr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10864

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");

    AsmOperand pr_done        = MakeUniqueLabel("padrep_done");
    AsmOperand pr_ret0        = MakeUniqueLabel("padrep_ret0");
    AsmOperand pr_check_left  = MakeUniqueLabel("padrep_chkL");
    AsmOperand pr_no_trig     = MakeUniqueLabel("padrep_notrig");
    AsmOperand pr_reset_all   = MakeUniqueLabel("padrep_reset");
    AsmOperand pr_dir0        = MakeUniqueLabel("padrep_dir0");
    AsmOperand pr_have_dir    = MakeUniqueLabel("padrep_havedir");
    AsmOperand pr_first_rep   = MakeUniqueLabel("padrep_firstrep");
    AsmOperand pr_ret_dir     = MakeUniqueLabel("padrep_retdir");
    AsmOperand pr_skip_dasinc = MakeUniqueLabel("padrep_skipdasinc");
    AsmOperand pr_skip_arrinc = MakeUniqueLabel("padrep_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    EmitAsm("POP_HL");

    // --- Trigger Right? (bit0) ---
    EmitAsm("LD_A_C");
    EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
    EmitAsm("JP_Z", pr_check_left);

    // dirMask=0x01, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_IMM", new AsmOperand(0x01, AddressMode.Immediate));
    EmitAsm("LD_HL_A"); // [0]=0x01
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // dasCnt=0
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A")
__padrep_mask

__padrep_maskは__padrepの別名で、コンパイラ内では同じ実装に入ります。1操作の選択方法、タイミング、状態の配置、戻り値も共通です。マスクで対象を指定する処理であることを呼び出し箇所で明確にしたい場合に使えます。

maskで対象にしたボタンから、1つの操作を選んでリピートさせます。対象ボタンが新たに押された場合は、立っている最下位ビットを選んで直ちに返します。それ以外は、選択中の操作が対象に含まれ、押され続けている限り維持します。その操作が終わると、残っている押下中の最下位ビットを選びますが、その場ではパルスを返しません。対象の押下中ビットがなくなると実行時の状態をクリアします。

書式

c
u8 __padrep_mask(u8* state, u8 keys, u8 trigger, u8 mask);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

keys

GBのBTN_*配置による、現在押している全ボタンのマスクです。__readpad、__readpadexの下位バイト、またはinput_currentから取得した値を渡します。リピート処理自体はハードウェアを読み取りません。

trigger

今回新しく押されたビットを、keysと同じ配置で渡します。通常はkeys & ~previous_keys、または__readpadexの上位バイトです。keysにも含まれるビットだけを指定してください。毎回押下中マスクを渡すと、呼び出すたびに新しい押下として待ち時間を最初から数え直してしまいます。

mask

対象にするGBのボタンビットを|で組み合わせます。0なら全操作を無効にして実行時の状態をクリアします。複数ビットを指定すると候補は増えますが、1回の呼び出しで返すのは選択された1ビットだけです。複数操作のパルスをまとめたマスクにはなりません。

戻り値

操作のパルスが発生しない場合は0、それ以外は1, 2, 4, 8, 16, 32, 64, 128のいずれかです。0以外の値はボタンのビットであり、0か1に正規化した真偽値やリピート回数ではありません。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

初期待ち時間です。0〜255で指定し、リピート処理の呼び出し回数を数えます。正の値Nでは、押した瞬間のパルスからちょうどN回後の呼び出しで最初の長押しリピートが発生します。0の場合は初期待ち時間との一致判定を通らず、リピート間隔が最初の長押しパルスの時期を決めます。単位はミリ秒ではありません。

2回目以降のリピート間隔です。0〜255の呼び出し回数で指定します。正の値Nでは、最初のリピート後、対象ボタンを押している更新N回ごとに発生します。0では初期待ち時間が終わった後の対象更新ごとに発生するため、その段階では0も1も毎回パルスを返します。待ち時間と間隔の両方が0なら、押している更新のたびにパルスを返します。

入力を処理する周期ごとに1回呼び出してください。呼び出すたびに状態を変更し、VBlank待ちは行いません。新しい押下ビットなしで押下中のボタンを選び直した場合、その呼び出しでは0を返し、新たな待ち時間を開始します。この関数群は1つの状態につき1操作を選択します。一方、input_repeatは8ボタンそれぞれの独立したタイマーを使います。

使用例

c
result=__padrep_mask(alias_state,keys,trigger,48);
    if (result!=0) alias_pulses++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep_mask: DMG
DMG
__padrep_mask: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11249

csharp
{
    if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");

    AsmOperand pm_done        = MakeUniqueLabel("padrepM_done");
    AsmOperand pm_ret0        = MakeUniqueLabel("padrepM_ret0");
    AsmOperand pm_no_trig     = MakeUniqueLabel("padrepM_notrig");
    AsmOperand pm_reset_all   = MakeUniqueLabel("padrepM_reset");
    AsmOperand pm_dir0        = MakeUniqueLabel("padrepM_dir0");
    AsmOperand pm_have_dir    = MakeUniqueLabel("padrepM_havedir");
    AsmOperand pm_first_rep   = MakeUniqueLabel("padrepM_firstrep");
    AsmOperand pm_ret_dir     = MakeUniqueLabel("padrepM_retdir");
    AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
    AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger, E = mask
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
    EmitAsm("POP_HL");

    // trigMasked = trigger & mask -> D
    EmitAsm("LD_A_C");
    EmitAsm("AND_E");
    EmitAsm("LD_D_A");
    EmitAsm("OR_A");
    EmitAsm("JP_Z", pm_no_trig);

    // select = lowbit(trigMasked) = x & -x
    EmitAsm("LD_A_D");
    EmitAsm("CPL");
    EmitAsm("INC_A");
    EmitAsm("AND_D");
    EmitAsm("LD_C_A"); // save selected in C

    // dirMask=selected, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [0]=selected
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); Emi
__padrep_reset

設定済みの待ち時間と間隔を保ったまま、選択中の操作を解除し、2つの経過カウンターを0にします。メニューを閉じるときや入力先を切り替えるときに使うと、それまでの長押しによるリピートの進み具合を次の操作へ持ち越さずに済みます。

書式

c
void __padrep_reset(u8* state);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

state

書き込み可能な連続5バイトのRAMを指すポインターです。例えばu8 state[5]を用意します。配置は[0]選択中のボタンビット、[1]初期待ち時間、[2]リピート間隔、[3]初期待ち時間の経過カウンター、[4]リピート間隔の経過カウンターです。独立して動かす処理ごとに別の状態を用意し、使用前に初期化してください。ヌルポインター、5バイト未満の領域、方向専用処理と汎用処理での同じ状態の共有は使えません。

戻り値

ありません(void)。

使用例

c
__padrep_reset(lr);
expect(lr[0],0); expect(lr[1],3); expect(lr[2],2);
expect(lr[3],0); expect(lr[4],0);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

決まったボタン状態を独立した4つの状態領域に入力し、どの呼び出しでリピートが発生するかを学ぶプログラムです。待ち時間3・間隔2で、0〜7と番号を付けた8回の呼び出しを行うと、0、3、5、7回目にパルスが発生します。そのためRIGHT PULSES、DOWN PULSES、ACTION PULSES、ALIAS PULSESはすべて004になります。汎用処理にはAとBを同時に渡し、A(16)が選ばれることを確認します。初期化、リセット、左右の優先順位、解放、すでに押されている方向の選択、待ち時間0、式を引数に渡す場合も調べます。CHECKSは057、FAILED CHECKSは000が正常です。一定の入力による状態処理の例なので、実際にボタンを押すのを待つプログラムではありません。

__padrep_reset: DMG
DMG
__padrep_reset: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10840

csharp
{
    if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");

    CompileIntoHL(args[0]);

    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0 (dirMask)

    // [3]=0, [4]=0
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("LD_HL_A");
    EmitAsm("INC_HL"); // -> [4]
    EmitAsm("LD_HL_A");
    EmitAsm("POP_HL");
    return;
}

// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new trigger, simultaneous Left/Right holds suppress movement without resetting counters.
// A single held direction is adopted without immediate movement when no direction is active.
// Subsequent updates use the five-byte state's initial delay and repeat interval; ARR=0 repeats each update.
if (funcName == "__padrep_lr")
{
    if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");

    AsmOperand pr_done        = MakeUniqueLabel("padrep_done");
    AsmOperand pr_ret0        = MakeUniqueLabel("padrep_ret0");
    AsmOperand pr_check_left  = MakeUniqueLabel("padrep_chkL");
    AsmOperand pr_no_trig     = MakeUniqueLabel("padrep_notrig");
    AsmOperand pr_reset_all   = MakeUniqueLabel("padrep_reset");
    AsmOperand pr_dir0        = MakeUniqueLabel("padrep_dir0");
    AsmOperand pr_have_dir    = MakeUniqueLabel("padrep_havedir");
    AsmOperand pr_first_rep   = MakeUniqueLabel("padrep_firstrep");
    AsmOperand pr_ret_dir     = MakeUniqueLabel("padrep
__readpad

JOYPから方向キーと操作ボタンを読み、共通の8ビットマスクにまとめます。各グループを選択して安定待ちを含む2回の読込を行い、最後にグループ選択ビットを 0x30 に戻します。

書式

c
u8 __readpad();

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

戻り値

共通の BTN_* 形式の8ビットマスクです。同時に複数のビットが立つ場合があります。0は該当ボタンなしです。

使用条件と制限

共通マスクは BTN_RIGHT=0x01、BTN_LEFT=0x02、BTN_UP=0x04、BTN_DOWN=0x08、BTN_A=0x10、BTN_B=0x20、BTN_SELECT=0x40、BTN_START=0x80 です。ビット1が押下中を表します。GB・FCの input_* はこの形式ですが、FCの生のパッド関数は別のビット順です。

この呼び出しはハードウェアの状態を新たに読みます。input_* や nes_pad1_* の保存状態は更新せず、押下・解放イベントやリピートも生成しません。必要なら読み取った値を保存し、自分で保持した前回マスクと比較して変化を求めてください。

使用例

c
all_keys=__readpad(); // A + Right: 0x11, decimal 17.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

3種類のボタンマスクと、現在値・押下値をまとめた戻り値を比較するプログラムです。Aと右を同時に押します。ALL KEYSは17、DIRECTIONSとBUTTONS LOWはそれぞれ1になります。前回値を0にするとEX CURRENTとEX NEW PRESSは共に17です。その現在値を次の読み取りに渡すとHELD PRESSは0になります。FAILED CHECKSは0が正常です。取得した値は画面に残るため、ボタンを離しても消えません。

__readpad: DMG
DMG
__readpad: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10669

csharp
{
    if (args.Length != 0) Error(expr, "__readpad requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    // Directions (P1=0x20)
    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1); // dummy reads for settle
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    EmitAsm("LD_C_A"); // dpad in C

    // Buttons (P1=0x10)
    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    // shift into upper nibble (<<4)
    EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA");
    EmitAsm("OR_C");

    // Restore P1 selection (0x30) without losing return value
    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.17 __readpaddir() -> u8 in A (bits0..3 only)
// Returns dpad nibble (Right,Left,Up,Down) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpaddir")
{
    if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm
__readpadbtn

JOYPの操作ボタンだけを下位4ビットで読みます。A=1、B=2、Select=4、Start=8です。共通形式の BTN_A~BTN_START は上位4ビットなので、この戻り値には使えません。JOYPの選択状態は 0x30 に戻します。

書式

c
u8 __readpadbtn();

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

戻り値

上記のビット順による0~15のマスクです。複数のビットが立つ場合があるため、全体が1かどうかではなく result & bit で特定のボタンを調べます。

使用条件と制限

この呼び出しはハードウェアの状態を新たに読みます。input_* や nes_pad1_* の保存状態は更新せず、押下・解放イベントやリピートも生成しません。必要なら読み取った値を保存し、自分で保持した前回マスクと比較して変化を求めてください。

使用例

c
buttons=__readpadbtn(); // A: 1, not BTN_A (16).

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

3種類のボタンマスクと、現在値・押下値をまとめた戻り値を比較するプログラムです。Aと右を同時に押します。ALL KEYSは17、DIRECTIONSとBUTTONS LOWはそれぞれ1になります。前回値を0にするとEX CURRENTとEX NEW PRESSは共に17です。その現在値を次の読み取りに渡すとHELD PRESSは0になります。FAILED CHECKSは0が正常です。取得した値は画面に残るため、ボタンを離しても消えません。

__readpadbtn: DMG
DMG
__readpadbtn: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10729

csharp
{
    if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadex")
{
    if (args.Length != 1) Error(expr, "__readpadex requires 1 argument (prev_keys)");

    // prev -> B
    CompileIntoA(args[0]);
    EmitAsm("LD_B_A");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    // Directions (P1=0x20)
    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    EmitAsm("LD_C_A"); // dpad in C

    // Buttons (P1=0x10)
    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1)
__readpaddir

JOYPの方向キーだけを読みます。結果のbit 0~3は右=1、左=2、上=4、下=8で、bit 4~7は0です。操作ボタンは含めず、JOYPの選択状態を 0x30 に戻します。

書式

c
u8 __readpaddir();

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

戻り値

上記のビット順による0~15のマスクです。複数のビットが立つ場合があるため、全体が1かどうかではなく result & bit で特定のボタンを調べます。

使用条件と制限

この呼び出しはハードウェアの状態を新たに読みます。input_* や nes_pad1_* の保存状態は更新せず、押下・解放イベントやリピートも生成しません。必要なら読み取った値を保存し、自分で保持した前回マスクと比較して変化を求めてください。

使用例

c
directions=__readpaddir(); // Right: 1. The held A button is not included.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

3種類のボタンマスクと、現在値・押下値をまとめた戻り値を比較するプログラムです。Aと右を同時に押します。ALL KEYSは17、DIRECTIONSとBUTTONS LOWはそれぞれ1になります。前回値を0にするとEX CURRENTとEX NEW PRESSは共に17です。その現在値を次の読み取りに渡すとHELD PRESSは0になります。FAILED CHECKSは0が正常です。取得した値は画面に残るため、ボタンを離しても消えません。

__readpaddir: DMG
DMG
__readpaddir: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10706

csharp
{
    if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.18 __readpadbtn() -> u8 in A (bits0..3 only)
// Returns buttons nibble (A,B,Select,Start) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadbtn")
{
    if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a c
__sdot2_q1_7

符号付き2項の内積を計算します。

書式

c
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);

引数

a0

符号付き16ビットのビット列。数値は−32768~32767。

a1

符号付き16ビットのビット列。数値は−32768~32767。

b0

符号付き8ビットのビット列。係数は−128~127。

b1

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積を2*trunc(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[20]=__sdot2_q1_7(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[21]=__sdot2_q1_7(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[22]=__sdot2_q1_7(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[23]=__sdot2_q1_7(v0,v1,v2,v3);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[20]=1018, result[21]=1018, result[22]=64518, result[23]=64518 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__sdot2_q1_7: DMG
DMG
__sdot2_q1_7: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11732

kitaqgb/kitaqgb/CodeGenerator.cs:11732

csharp
if (funcName == "__sdot2_q1_7")
{
    if (args.Length != 4) Error(expr, "__sdot2_q1_7 requires 4 arguments");
    LastCallReturnsHL = true;

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: true, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_q1_7_x");
        EmitAsm("ADD_HL_HL");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: true, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_q1_7_y");
        EmitAsm("ADD_HL_HL");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__sdot2_q8_8

符号付き2項の内積を計算します。

書式

c
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

引数

a0

符号付き16ビットのビット列。数値は−32768~32767。

a1

符号付き16ビットのビット列。数値は−32768~32767。

b0

符号付き8ビットのビット列。係数は−128~127。

b1

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積をtrunc(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[30]=__sdot2_q8_8(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[31]=__sdot2_q8_8(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[32]=__sdot2_q8_8(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[33]=__sdot2_q8_8(v0,v1,v2,v3);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[30]=509, result[31]=509, result[32]=65027, result[33]=65027 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__sdot2_q8_8: DMG
DMG
__sdot2_q8_8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11654

kitaqgb/kitaqgb/CodeGenerator.cs:11654

csharp
if (funcName == "__sdot2_q8_8")
{
    if (args.Length != 4) Error(expr, "__sdot2_q8_8 requires 4 arguments");
    LastCallReturnsHL = true;

    // Fast paths when coefficients are compile-time constants.
    // Special-case: 0 / power-of-two coefficients (s8 two's complement) => arithmetic shift instead of mul.
    if (TryGetS8Const(args[2], out int sax0) && (sax0 == 0) && TryGetS8Const(args[3], out int say0) && (say0 == 0))
    {
        CompileDiscard(args[0]);
        CompileDiscard(args[1]);
        EmitAsm("XOR_A");
        EmitAsm("LD_H_A");
        EmitAsm("LD_L_A");
        return;
    }
    if (TryGetS8Const(args[2], out int saxOnly) && (saxOnly == 0))
    {
        CompileDiscard(args[0]);
        // only y*ay
        if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
        {
            CompileIntoHL(args[1]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[3]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitSMul16x8Shr8_HL("__sdot2_y");
        }
        return;
    }
    if (TryGetS8Const(args[3], out int sayOnly) && (sayOnly == 0))
    {
        // only x*ax
        if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
        {
            CompileIntoHL(args[0]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[2]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitSMul16x8Shr8_HL("__sdot2_x");
        }
        // Preserve the scaled result while evaluating the zero-product input.
        EmitAsm("PUSH_HL");
        CompileDiscard(args[1]);
        EmitAsm("POP_HL");
        return;
    }

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_x");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_y");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__sdot3_q1_7

符号付き3項の内積を計算します。

書式

c
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

引数

a0

符号付き16ビットのビット列。数値は−32768~32767。

a1

符号付き16ビットのビット列。数値は−32768~32767。

a2

符号付き16ビットのビット列。数値は−32768~32767。

b0

符号付き8ビットのビット列。係数は−128~127。

b1

符号付き8ビットのビット列。係数は−128~127。

b2

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積を2*trunc(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[16]=__sdot3_q1_7(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[17]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[18]=__sdot3_q1_7(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[19]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[16]=1042, result[17]=1042, result[18]=64494, result[19]=64494 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__sdot3_q1_7: DMG
DMG
__sdot3_q1_7: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11790

kitaqgb/kitaqgb/CodeGenerator.cs:11790

csharp
if (funcName == "__sdot3_q1_7")
            {
                if (args.Length != 6) Error(expr, "__sdot3_q1_7 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__sdot3_q8_8

符号付き3項の内積を計算します。

書式

c
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

引数

a0

符号付き16ビットのビット列。数値は−32768~32767。

a1

符号付き16ビットのビット列。数値は−32768~32767。

a2

符号付き16ビットのビット列。数値は−32768~32767。

b0

符号付き8ビットのビット列。係数は−128~127。

b1

符号付き8ビットのビット列。係数は−128~127。

b2

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積をtrunc(a*b/256)で求めて合計します。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[8]=__sdot3_q8_8(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[9]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[10]=__sdot3_q8_8(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[11]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[8]=521, result[9]=521, result[10]=65015, result[11]=65015 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__sdot3_q8_8: DMG
DMG
__sdot3_q8_8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11767

kitaqgb/kitaqgb/CodeGenerator.cs:11767

csharp
if (funcName == "__sdot3_q8_8")
            {
                if (args.Length != 6) Error(expr, "__sdot3_q8_8 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: true, q1_7: false, labelPrefix: "__sdot3_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: true, q1_7: false, labelPrefix: "__sdot3_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: true, q1_7: false, labelPrefix: "__sdot3_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__settile_fast

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

書き込み先は常に 0x9800 + 32*y + x です。LCDCで選んだBG・ウィンドウ用マップには追従しません。

VRAMアクセス待ちは行いません。LCD停止中か、転送全体が収まる書き込み可能時間を呼び出し側で確保した場合に使用してください。

書式

c
__settile_fast(x, y, tile);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_fast
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. The destination is always `0x9800 + 32*y + x`, regardless of which
// maps LCDC selects for the background and window.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
void __settile_fast(u8 x, u8 y, u8 tile);

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_fast(2, 3, 'A');
    if (tile_example_read(0x9862, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C62, 0) != 0) { failures++; }
    tile_example_finish(failures, 0);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settile_fast: DMG
DMG
__settile_fast: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:9321

csharp
{
                if (args.Length != 3) Error(expr, "__settile_unsafe requires 3 arguments");

                // Evaluate args left-to-right (matches normal call semantics)
                // Preserve x,y while evaluating tile.
                CompileIntoA(args[0]);
                EmitAsm("LD_L_A");
                EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("PUSH_HL"); // x

                CompileIntoA(args[1]);
                EmitAsm("LD_L_A");
                EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("PUSH_HL"); // y

                CompileIntoA(args[2]);
                EmitAsm("LD_C_A"); // tile

                // Restore y -> A, x -> B
                EmitAsm("POP_HL");
                EmitAsm("LD_A_L");
                EmitAsm("LD_D_A"); // y temp

                EmitAsm("POP_HL");
                EmitAsm("LD_A_L");
                EmitAsm("LD_B_A"); // x

                EmitAsm("LD_A_D"); // y

                UsedSetTileFastCore = true;
                EmitAsm("CALL", new AsmOperand("__settile_fast_core", AddressMode.Absolute));
                return;
            }

            // 2.6b __settile_bulk(dest_u16, src_u16, count_u8)
            // v9: bulk tile-map copy (LCD ON safe with wait+DI/EI inside core).
            if (funcName == "__settile_bulk")
            {
                if (args.Length != 3) Error(expr, "__settile_bulk requires 3 arguments");

                Expr bulkCountExpr = FoldConstants(args[2]);

                // dest
__settilebg16_buf

RAM上のタイルマップバッファに16×16ピクセルのセルを書き込みます。8×8タイル4枚の番号は上段が tile,tile+1、下段が tile+2,tile+3 で、256で折り返します。VRAMへの転送は行いません。

書式

c
__settilebg16_buf(buf, x, y, tile);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

buf

タイル1行あたり32バイトのRAMバッファです。32×32のマップ全体なら1024バイト必要です。左上は 64*y + 2*x バイト目で、そこから 0,1,32,33 の位置に書き込みます。

x, y

16×16ピクセルのセル単位です。32×32タイルのバッファ全体では、それぞれ0~15です。1タイルを書き込む関数の8×8タイル座標とは単位が異なります。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg16_buf
// Write a 16×16-pixel cell into a RAM tile-map buffer: the four 8×8 tile numbers are
// `tile,tile+1` on top and `tile+2,tile+3` below. Values wrap at 256. This does not
// transfer anything to VRAM.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    __settilebg16_buf(example_tiles, 1, 1, 'A');
    // The buffer operation must not write to VRAM yet.
    if (tile_example_read(0x9C42, 0) != 0) { failures++; }
    // Transfer the prepared cell so its four characters can be seen.
    __settilebg16_flush(example_tiles, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

タイル座標 (2,2) に、上段 AB、下段 CD が表示されます。これはセル座標 (1,1) の16×16ピクセル1セルです。バッファ書き込みの例ではRAM上の準備でVRAMが変化しないことを確認してから転送します。転送関数の例では、先にRAMの各バイトを設定してから転送します。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=2)、ピクセル座標 (16,16) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
AB
CD
__settilebg16_buf: DMG
DMG
__settilebg16_buf: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16_buf.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16_buf.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10564

csharp
{
                if (args.Length != 4) Error(expr, "__settilebg16_buf requires 4 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[1], args[2], args[3], incrementQuad: true, labelPrefix: "settilebg16buf");
                return;
            }

            // Update tile and attribute RAM buffers separately, repeating the attribute across the cell.
            if (funcName == "__settilebg16cgb_buf")
            {
                if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
                EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
                return;
            }

            // Flush both tile rows belonging to the requested buffered cell row.
            if (funcName == "__settilebg16_flush")
            {
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIn
__settilebg16_flush

RAMバッファから隣接するタイル2行を、LCDCで選んだBGマップへ転送します。コピー元は 64*y + x バイト目で、その行と32バイト下の行から、それぞれ count バイトコピーします。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
__settilebg16_flush(buf, y, x, count);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

buf

行間隔が32バイトの読み取り可能なタイルマップバッファです。マップ全体なら1024バイトです。2バッファ版はタイル番号と属性を同じ配置の別々の配列で受け取り、交互には並べません。

y, x, count

引数の順序は y,x,count です。y は16ピクセルのセル行(0~15)、x と count は8ピクセルのタイル単位です。x+count <= 32 に収めてください。セル (2,3) の1個分なら y=3,x=4,count=2 です。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg16_flush
// Transfer two adjacent tile rows from the RAM buffer to the LCDC-selected BG map. The
// source offset is `64*y + x`; `count` bytes are copied from that row and from the row
// 32 bytes below it. Waits for writable VRAM when necessary. The LCD-on path uses
// interrupt masking and can enable interrupts afterward; it is not a promise to preserve
// the caller's interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    example_tiles[66]='A'; example_tiles[67]='B';
    example_tiles[98]='C'; example_tiles[99]='D';
    __settilebg16_flush(example_tiles, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

タイル座標 (2,2) に、上段 AB、下段 CD が表示されます。これはセル座標 (1,1) の16×16ピクセル1セルです。バッファ書き込みの例ではRAM上の準備でVRAMが変化しないことを確認してから転送します。転送関数の例では、先にRAMの各バイトを設定してから転送します。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=2)、ピクセル座標 (16,16) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
AB
CD
__settilebg16_flush: DMG
DMG
__settilebg16_flush: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16_flush.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16_flush.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10581

csharp
{
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
                    AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
                    EmitRuntimeCgbCheckCall();
                    EmitAsm("OR_A");
                    // Two unrolled row copies can exceed the relative-branch range.
                    EmitAsm("JP_Z", skip);
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                    EmitAsm("JR", done);
__settilebg16cgb_buf

RAM上のタイルマップバッファに16×16ピクセルのセルを書き込みます。8×8タイル4枚の番号は上段が tile,tile+1、下段が tile+2,tile+3 で、256で折り返します。VRAMへの転送は行いません。

別の属性バッファの対応する4セルにも、同じ attr を書き込みます。どちらも通常のRAMなので、DMG上でもバッファの準備処理は行われます。

書式

c
__settilebg16cgb_buf(tilebuf, attrbuf, x, y, tile, attr);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

tilebuf, attrbuf

タイル1行あたり32バイトのRAMバッファです。32×32のマップ全体なら1024バイト必要です。左上は 64*y + 2*x バイト目で、そこから 0,1,32,33 の位置に書き込みます。

x, y

16×16ピクセルのセル単位です。32×32タイルのバッファ全体では、それぞれ0~15です。1タイルを書き込む関数の8×8タイル座標とは単位が異なります。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

attr

CGBの属性バイトです。ビット0~2はパレット0~7、ビット3は画像データのバンク、ビット5・6は左右・上下反転、ビット7はBG・ウィンドウの優先度です。未使用のビット4は0にしてください。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg16cgb_buf
// Write a 16×16-pixel cell into a RAM tile-map buffer: the four 8×8 tile numbers are
// `tile,tile+1` on top and `tile+2,tile+3` below. Values wrap at 256. This does not
// transfer anything to VRAM. Also write the same `attr` byte to all four corresponding
// cells in the separate attribute buffer. Both buffers are ordinary RAM, so this
// preparation also runs on DMG.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    __settilebg16cgb_buf(example_tiles, example_attrs, 1, 1, 'A', 2);
    // The buffer operation must not write to VRAM yet.
    if (tile_example_read(0x9C42, 0) != 0) { failures++; }
    // Transfer the prepared cell so its four characters can be seen.
    __settilebg16cgb_flush(example_tiles, example_attrs, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    if (example_attrs[66] != 2) { failures++; }
    if (example_attrs[99] != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C42, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C63, 1) != 2) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

タイル座標 (2,2) に、上段 AB、下段 CD が表示されます。これはセル座標 (1,1) の16×16ピクセル1セルです。バッファ書き込みの例ではRAM上の準備でVRAMが変化しないことを確認してから転送します。転送関数の例では、先にRAMの各バイトを設定してから転送します。

CGBでは4セルの属性がすべて2なので、4文字とも青くなります。DMGでは黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=2)、ピクセル座標 (16,16) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
AB
CD
__settilebg16cgb_buf: DMG
DMG
__settilebg16cgb_buf: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16cgb_buf.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16cgb_buf.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10572

csharp
{
                if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
                EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
                return;
            }

            // Flush both tile rows belonging to the requested buffered cell row.
            if (funcName == "__settilebg16_flush")
            {
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand ski
__settilebg16cgb_flush

RAMバッファから隣接するタイル2行を、LCDCで選んだBGマップへ転送します。コピー元は 64*y + x バイト目で、その行と32バイト下の行から、それぞれ count バイトコピーします。

タイル番号とCGB属性を続けて書き込みます。DMGではタイル番号だけを書きます。CGBではVRAMバンク0を選んでから呼び出してください。属性はバンク1へ書き、終了時は VBK=0 になります。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
__settilebg16cgb_flush(tilebuf, attrbuf, y, x, count);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

tilebuf, attrbuf

行間隔が32バイトの読み取り可能なタイルマップバッファです。マップ全体なら1024バイトです。2バッファ版はタイル番号と属性を同じ配置の別々の配列で受け取り、交互には並べません。

y, x, count

引数の順序は y,x,count です。y は16ピクセルのセル行(0~15)、x と count は8ピクセルのタイル単位です。x+count <= 32 に収めてください。セル (2,3) の1個分なら y=3,x=4,count=2 です。

戻り値

ありません(void)。

使用条件と制限

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilebg16cgb_flush
// Transfer two adjacent tile rows from the RAM buffer to the LCDC-selected BG map. The
// source offset is `64*y + x`; `count` bytes are copied from that row and from the row
// 32 bytes below it. Write a tile number and its CGB attribute. On DMG, only the tile-
// number write is performed. On CGB, enter with VRAM bank 0 selected; attributes use
// bank 1 and the call finishes with `VBK=0`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    example_tiles[66]='A'; example_tiles[67]='B';
    example_tiles[98]='C'; example_tiles[99]='D';
    example_attrs[66]=2; example_attrs[67]=2;
    example_attrs[98]=2; example_attrs[99]=2;
    __settilebg16cgb_flush(example_tiles, example_attrs, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    if (example_attrs[66] != 2) { failures++; }
    if (example_attrs[99] != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C42, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C63, 1) != 2) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

タイル座標 (2,2) に、上段 AB、下段 CD が表示されます。これはセル座標 (1,1) の16×16ピクセル1セルです。バッファ書き込みの例ではRAM上の準備でVRAMが変化しないことを確認してから転送します。転送関数の例では、先にRAMの各バイトを設定してから転送します。

CGBでは4セルの属性がすべて2なので、4文字とも青くなります。DMGでは黒です。

期待画像:次のタイル配置が、タイル座標 (x=2, y=2)、ピクセル座標 (16,16) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
AB
CD
__settilebg16cgb_flush: DMG
DMG
__settilebg16cgb_flush: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16cgb_flush.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16cgb_flush.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:10589

csharp
{
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
                    AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
                    EmitRuntimeCgbCheckCall();
                    EmitAsm("OR_A");
                    // Two unrolled row copies can exceed the relative-branch range.
                    EmitAsm("JP_Z", skip);
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                    EmitAsm("JR", done);
                    EmitLabel(skip);
                    EmitLabel(done);
                }
                return;
            }

            // 2.14 __getbgmapbase() -> u16 in HL
// Returns the BG tilemap base address selected by LCDC bit3:
// bit3=0 => 0x9800, bit3=1 => 0x9C00
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__getbgmapbase")
{
    if (args.Length != 0) Error(expr, "__getb
__smac16

符号付き積和を計算します。

書式

c
u16 __smac16(u16 acc, u16 a, u8 b);

引数

acc

加算する16ビットの累積値。

a

符号付き16ビットのビット列。数値は−32768~32767。

b

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積をtrunc(a*b/256)で求め、accへ加えます。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[26]=__smac16(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[27]=__smac16(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[28]=__smac16(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[29]=__smac16(v0,v1,v2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[26]=92, result[27]=92, result[28]=65372, result[29]=65372 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__smac16: DMG
DMG
__smac16: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11516

kitaqgb/kitaqgb/CodeGenerator.cs:11516

csharp
if (funcName == "__smac16")
            {
                if (args.Length != 3) Error(expr, "__smac16 requires 3 arguments");

                // mul = (a*b)>>8 (signed)
                EmitDotTermToHL(args[1], args[2], signed: true, q1_7: false, labelPrefix: "__smac16_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__smac16_q1_7

符号付き積和を計算します。

書式

c
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);

引数

acc

加算する16ビットの累積値。

a

符号付き16ビットのビット列。数値は−32768~32767。

b

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積を2*trunc(a*b/256)で求め、accへ加えます。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[0]=__smac16_q1_7(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[1]=__smac16_q1_7(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[2]=__smac16_q1_7(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[3]=__smac16_q1_7(v0,v1,v2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[0]=220, result[1]=220, result[2]=65244, result[3]=65244 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__smac16_q1_7: DMG
DMG
__smac16_q1_7: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11533

kitaqgb/kitaqgb/CodeGenerator.cs:11533

csharp
if (funcName == "__smac16_q1_7")
            {
                if (args.Length != 3) Error(expr, "__smac16_q1_7 requires 3 arguments");

                // mul = (a*b)>>7 (approx via >>8 then *2)
                EmitDotTermToHL(args[1], args[2], signed: true, q1_7: true, labelPrefix: "__smac16_q1_7_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__smul16x8_q1_7

符号付き乗算を計算します。

書式

c
u16 __smul16x8_q1_7(u16 a, u8 b);

引数

a

符号付き16ビットのビット列。数値は−32768~32767。

b

符号付き8ビットのビット列。係数は−128~127。

戻り値

各積を2*trunc(a*b/256)で求めます。戻り値は下位16ビットです。

使用条件と制限

GBのこれらの乗算は積を256で割り、符号付きでは0方向に切り捨てます。Q1.7版はその結果を2倍するため、厳密な128除算と最大1だけ異なります。各項を別々に丸め、最後の加算は16ビットで周回します。

使用例

c
// Compare literal coefficients with values read from RAM.
result[12]=__smul16x8_q1_7(-513,-64);
v0=-513;v1=-64;
result[13]=__smul16x8_q1_7(v0,v1);
// Reverse coefficient signs and verify negative products.
result[14]=__smul16x8_q1_7(-513,64);
v0=-513;v1=64;
result[15]=__smul16x8_q1_7(v0,v1);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

固定値とRAM変数で同じ計算を行う例です。画面には代表値を符号なし16ビットで表示し、負数は2の補数の値になります。GBは固定小数点の縮小を行い、FCは整数積を返すため、同じ入力でも両機種の結果は異なります。

この抜粋で保存する16ビット値は result[12]=256, result[13]=256, result[14]=65280, result[15]=65280 です。固定値とRAM変数を使った計算は、それぞれ同じ値になります。画面は代表値を表示し、全結果はresult配列に保存します。

__smul16x8_q1_7: DMG
DMG
__smul16x8_q1_7: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:11490

kitaqgb/kitaqgb/CodeGenerator.cs:11490

csharp
if (funcName == "__smul16x8_q1_7")
            {
                if (args.Length != 2) Error(expr, "__smul16x8_q1_7 requires 2 arguments");

                EmitDotTermToHL(args[0], args[1], signed: true, q1_7: true, labelPrefix: "__smul16x8_q1_7");
                LastCallReturnsHL = true;
                return;
            }
__sram_read8

CPUアドレスからSRAMの1バイトを読みます。

書式

c
u8 __sram_read8(u16 address);

引数

address

$A000~$BFFFのCPUアドレス。

戻り値

読み取ったu8。RAMの存在や値の有効性は検査しません。

使用条件と制限

アクセス前に$0000へ0x0Aを書いてRAMを有効にし、終了時は0を書いて無効にします。バンク選択・範囲検査は行いません。呼び出し側でRAMバンクを設定してください。チェックサムや保存ヘッダーも扱いません。

使用例

c
*((u8*)0x4000)=0;result[12]=__sram_read8(0xA006);result[19]=*((u8*)0xA006);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

7・42・255の3バイトを保存し、長さ不一致・無効スロット・破損チェックサムを試します。読み込み成功時の中央値は42、破損時には出力先77を保持し、消去後の検査は0です。

__sram_read8: DMG
DMG
__sram_read8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_save.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_save.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=8k
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:8825

kitaqgb/kitaqgb/CodeGenerator.cs:8825

csharp
if (funcName == "__sram_read8")
            {
                if (args.Length != 1) Error(expr, "__sram_read8(addr) expects 1 argument");

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                EmitAsm("LD_A_IMM", new AsmOperand(0x0A, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));

                EmitAsm("POP_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A");

                EmitAsm("LD_A_IMM", new AsmOperand(0x00, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
                EmitAsm("LD_A_B");
                return;
            }
__sram_write8

CPUアドレスへSRAMの1バイトを書きます。

書式

c
void __sram_write8(u16 address, u8 value);

引数

address

$A000~$BFFFのCPUアドレス。

value

書き込む0~255の値。

戻り値

戻り値はありません。

使用条件と制限

アクセス前に$0000へ0x0Aを書いてRAMを有効にし、終了時は0を書いて無効にします。バンク選択・範囲検査は行いません。呼び出し側でRAMバンクを設定してください。チェックサムや保存ヘッダーも扱いません。

使用例

c
__sram_write8(0xA006,8);result[20]=*((u8*)0xA006);result[13]=save_exists(0);readback[0]=77;result[14]=save_read(0,readback,3);result[15]=readback[0];

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

7・42・255の3バイトを保存し、長さ不一致・無効スロット・破損チェックサムを試します。読み込み成功時の中央値は42、破損時には出力先77を保持し、消去後の検査は0です。

__sram_write8: DMG
DMG
__sram_write8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_save.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_save.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=8k
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:8846

kitaqgb/kitaqgb/CodeGenerator.cs:8846

csharp
if (funcName == "__sram_write8")
            {
                if (args.Length != 2) Error(expr, "__sram_write8(addr, value) expects 2 arguments");

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");

                EmitAsm("LD_A_IMM", new AsmOperand(0x0A, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));

                EmitAsm("POP_HL");
                EmitAsm("LD_A_C");
                EmitAsm("LD_HL_A");

                EmitAsm("LD_A_IMM", new AsmOperand(0x00, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
                return;
            }
__svbk_get

$D000〜$DFFFに割り当てられた実効WRAMバンク番号を読みます。SVBKのビット0〜2を取り出し、値0をバンク1へ正規化します。返すのはバンク番号であり、RAM容量・アドレス・ポインタではありません。

書式

c
u8 __svbk_get();

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

戻り値

現在の実効バンク番号(u8、1〜7)。レジスタの値0は1として返します。

使用条件と制限

__svbk_getと__svbk_setには--cgb=cgb_onlyまたは#pragma rom_cgb cgb_onlyが必要です。DMG用・兼用ビルドでは、実行時の条件で呼び出しを避ける場合でもKQ2103になります。ガード付きの直接設定関数は、異なる仕様がプログラムに適する場合に使ってください。

バンク切り替えは$D000〜$DFFFへのすべてのアクセスに影響し、固定WRAMの$C000〜$CFFFは変わりません。切り替え中に必要なスタックと変数は、切り替わる領域の外に置いてください。サンプルでは--stack-bank=fixedと固定WRAMへの明示的な変数配置を使います。割り込み処理やバンクを使うDMAとの調整は呼び出し側で行います。これらのAPIは割り込み禁止区間を作りません。

使用例

c
if (__svbk_get()!=1) failures++; // Normalize raw selector zero to effective bank 1.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

CGB専用の完全版プログラムは、WRAMバンク2の$D100に1、バンク3の同じアドレスに2を保存し、両方を読み戻してバンク1へ戻します。その値で(16,24)の赤い8×8正方形と、(16,48)の青い1画素幅の8×8の四角い枠を選びます。FAILED CHECKS 000は、直接指定値の正規化、バンクの独立性、定数・変数・式・関数の戻り値を使った切り替え前バンクの返却を確認します。別の6件の不正ビルド用テストで、DMG・兼用指定のKQ2103と、定数バンク0・8のKQ2102を確認します。実行検証はKOKURAで行い、実機と割り込みによる同時バンク変更は未検証です。

__svbk_get: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_wram_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_wram_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb_only --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7387

csharp
{
                if (args.Length != 0) Error(expr, "__svbk_get() expects 0 arguments");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_get() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");
                EmitReadNormalizedSvbkIntoA();
                return;
            }

            // Select a WRAM bank and return the prior normalized bank; diagnose out-of-range constant requests.
            if (funcName == "__svbk_set")
            {
                if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");

                Expr bankExpr = FoldConstants(args[0]);
                if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
                    Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);

                EmitReadNormalizedSvbkIntoA();
                EmitAsm("LD_B_A");
                CompileIntoA(bankExpr);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
                EmitAsm("LD_A_B");
                return;
            }

            // __cgb_safe_set_* (safe CGB-only register write wrappers)
            if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
            {
__svbk_set

$D000〜$DFFFに割り当てるWRAMバンクを切り替え、切り替え前の実効バンク番号を返します。呼び出し元のバンクへ戻す必要があれば戻り値を保存してください。メモリの内容をコピー・消去することや、バンクを自動で元に戻すことはありません。

書式

c
u8 __svbk_set(u8 bank);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

bank

指定するバンク番号はu8の1〜7です。範囲外の定数はKQ2102になります。動的な値の範囲検査はこの組み込み関数では行わないため、レジスタのマスク処理に頼らず呼び出し前に検査してください。

戻り値

切り替え前の実効バンク番号(u8、1〜7)。新たに選んだバンク番号ではありません。

使用条件と制限

__svbk_getと__svbk_setには--cgb=cgb_onlyまたは#pragma rom_cgb cgb_onlyが必要です。DMG用・兼用ビルドでは、実行時の条件で呼び出しを避ける場合でもKQ2103になります。ガード付きの直接設定関数は、異なる仕様がプログラムに適する場合に使ってください。

バンク切り替えは$D000〜$DFFFへのすべてのアクセスに影響し、固定WRAMの$C000〜$CFFFは変わりません。切り替え中に必要なスタックと変数は、切り替わる領域の外に置いてください。サンプルでは--stack-bank=fixedと固定WRAMへの明示的な変数配置を使います。割り込み処理やバンクを使うDMAとの調整は呼び出し側で行います。これらのAPIは割り込み禁止区間を作りません。

使用例

c
previous_bank=__svbk_set(requested_bank);
if(previous_bank!=1 || __svbk_get()!=2) failures++;
*((u8*)0xD100)=1; // This byte belongs to bank 2.
previous_bank=__svbk_set(next_bank()); // Dynamic argument evaluates to 3.
if(previous_bank!=2 || __svbk_get()!=3) failures++;
*((u8*)0xD100)=2; // Same CPU address, different physical RAM.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

CGB専用の完全版プログラムは、WRAMバンク2の$D100に1、バンク3の同じアドレスに2を保存し、両方を読み戻してバンク1へ戻します。その値で(16,24)の赤い8×8正方形と、(16,48)の青い1画素幅の8×8の四角い枠を選びます。FAILED CHECKS 000は、直接指定値の正規化、バンクの独立性、定数・変数・式・関数の戻り値を使った切り替え前バンクの返却を確認します。別の6件の不正ビルド用テストで、DMG・兼用指定のKQ2103と、定数バンク0・8のKQ2102を確認します。実行検証はKOKURAで行い、実機と割り込みによる同時バンク変更は未検証です。

__svbk_set: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_wram_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_wram_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb_only --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7397

csharp
{
                if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");

                Expr bankExpr = FoldConstants(args[0]);
                if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
                    Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);

                EmitReadNormalizedSvbkIntoA();
                EmitAsm("LD_B_A");
                CompileIntoA(bankExpr);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
                EmitAsm("LD_A_B");
                return;
            }

            // __cgb_safe_set_* (safe CGB-only register write wrappers)
            if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
            {
                if (args.Length != 1) Program.Error(funcName + "(value_u8) expects 1 argument");

                if (IsKnownCgbRuntimeTrue())
                {
                    CompileIntoA(args[0]);
                    if (cgbOffset == 0x4F)
                        EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
                    EmitAsm("LDH_MEM_A", new AsmOperand(cgbOffset, AddressMode.HighMem));
                    CgbGuardedWriteCount++;
                    CgbGuardedRegisters.Add(cgbReg);
                    return;
__vram_memcpy_unsafe

srcからdstへlenバイトを、アドレスが増える順に直ちにコピーします。転送が終わってから戻り、キューへの登録は行いません。転送元と転送先は1バイトずつ進み、タイルマップの行間隔やタイル形式の変換は扱いません。

書式

c
void __vram_memcpy_unsafe(u16 dst, const void* src, u16 len);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

src

転送元のバイトポインターです。呼び出し中は転送元のメモリーとバンクの対応を有効に保ってください。データはその場で読み、後のflush用に保持しません。転送元バンクの自動切り替えも行いません。前方へのコピーなので、memmoveのような重なりへの対応はありません。範囲が重ならないようにしてください。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCDやSTATによる待機を一切行わず、CPUで直接書き込みます。LCDを停止するか、管理下の書き込み可能期間に全転送が収まるようにしてください。VBlankに入っただけで、任意の長さの転送が安全になるわけではありません。この組み込み関数は転送のためのDIやEIを出力せず、クリッピングや範囲検査も行いません。

使用例

c
// The LCD is off throughout these unchecked writes.
__vram_memcpy_unsafe(0x98C2,strip,computed_length());

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_memcpy_unsafeでタイル座標(2,6)、ピクセル座標(16,48)に3バイトのマップデータを書き込みます。24×8の領域に、左から[1,2,3]のパターンが並ぶのが期待結果です。隣接するセルは空白のままです。完成プログラムでは、検証するコピーや埋め込みを1行ずつ並べています。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_memcpy_unsafe: DMG
DMG
__vram_memcpy_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:8104

csharp
{
                if (args.Length != 3) Error(expr, "__vram_memcpy_unsafe(dst, src, len) expects 3 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                // Use the same count specialization without emitting LCD-mode waits.
                Expr vramCopyUnsafeLen = FoldConstants(args[2]);
                EmitLengthExprIntoBC(vramCopyUnsafeLen);
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst

                if (vramCopyUnsafeLen.Match(Tag.Integer, out int vramCopyUnsafeLenConst) &&
                    TryEmitVramMemcpyConstCount(false, vramCopyUnsafeLenConst, "vramcpyu_small"))
                    return;

                AsmOperand vcuLoop = MakeUniqueLabel("vramcpyu_loop");
                AsmOperand vcuDone = MakeUniqueLabel("vramcpyu_done");
                EmitLabel(vcuLoop);
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_Z", vcuDone);
                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");
                EmitAsm("DEC_BC");
                EmitAsm("JR", vcuLoop);
                EmitLabel(vcuDone);
                return;
            }

            // __vram_memset(dst_ptr, value_u8, len_u16)
            // Safe VRAM fill: if LCD is on, each byte write is gated by STAT mode 0/1.
            if (funcName == "__vram_memset")
            {
                if (args.Length != 3) Error(expr, "__vram_memset(dst, value, l
__vram_memset_unsafe

dstから連続するlenバイトを、1バイトのvalueで直ちに埋めます。繰り返すのは1バイトで、タイルパターン全体ではありません。マップのバイトを埋める場合はタイル番号を繰り返し、パターンのバイトを埋める場合は画素のビットプレーンを書き換えます。

書式

c
void __vram_memset_unsafe(u16 dst, u8 value, u16 len);

この呼び出し書式の引数名は、コンパイラ組み込み機能の意味を示す説明用の名前です。別途追加するCの宣言ではありません。

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

value

0〜255の1バイトを繰り返し書きます。画面での意味は、転送先がタイル番号、画素のビットプレーン、その他のバイト列のどれかによって変わります。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCDやSTATによる待機を一切行わず、CPUで直接書き込みます。LCDを停止するか、管理下の書き込み可能期間に全転送が収まるようにしてください。VBlankに入っただけで、任意の長さの転送が安全になるわけではありません。この組み込み関数は転送のためのDIやEIを出力せず、クリッピングや範囲検査も行いません。

使用例

c
__vram_memset_unsafe(0x9942,1,computed_length()); // Safe here because the LCD is off.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_memset_unsafeでタイル座標(2,10)、ピクセル座標(16,80)から3バイトのマップをタイル番号1で埋めます。塗りつぶした24×8の横棒と、空白の隣接セルが期待結果です。長さは変数や関数の戻り値から計算し、その場合も3バイトすべてに値1が入ることを確認します。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_memset_unsafe: DMG
DMG
__vram_memset_unsafe: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:8196

csharp
{
                if (args.Length != 3) Error(expr, "__vram_memset_unsafe(dst, value, len) expects 3 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoA(args[1]); EmitAsm("LD_D_A"); // value
                // Specialize a constant unsafe fill or fall back to its unchecked byte loop.
                Expr vramSetUnsafeLen = FoldConstants(args[2]);
                EmitLengthExprIntoBC(vramSetUnsafeLen);
                EmitAsm("POP_HL"); // dst

                if (vramSetUnsafeLen.Match(Tag.Integer, out int vramSetUnsafeLenConst) &&
                    TryEmitVramMemsetConstCount(false, vramSetUnsafeLenConst, "vramsetu_small"))
                    return;

                AsmOperand vsuLoop = MakeUniqueLabel("vramsetu_loop");
                AsmOperand vsuDone = MakeUniqueLabel("vramsetu_done");
                EmitLabel(vsuLoop);
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_Z", vsuDone);
                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");
                EmitAsm("DEC_BC");
                EmitAsm("JR", vsuLoop);
                EmitLabel(vsuDone);
                return;
            }

            // __far_memcpy(dst_ptr, bank_u8, src_ptr, len_u16)
            // Copies from switchable ROM bank via a bank0 helper and restores original bank.
            if (funcName == "__far_memcpy")
            {
                if (args.Length != 4) Error(expr, "__far_memcpy(dst, bank, src, len) expects 4 arguments");

                CompileIntoHL(
__wait_ly

LCD動作中にLYが指定値と一致するまで待ちます。

書式

c
void __wait_ly(u8 ly);

引数

ly

走査線0~153。LCD動作中に154以上を指定すると戻りません。

戻り値

戻り値はありません。

使用条件と制限

すでにその走査線なら即座に戻り、過ぎていれば次フレームまで待ちます。LCD停止中も即座に戻ります。

使用例

c
M_LCDC=0x91;__wait_ly(80);result[9]=*((u8*)0xFF44);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__wait_ly: DMG
DMG
__wait_ly: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/kitaqgb/CodeGenerator.cs:7483

kitaqgb/kitaqgb/CodeGenerator.cs:7483

csharp
if (funcName == "__wait_ly")
            {
                if (args.Length != 1) Error(expr, "__wait_ly(target) expects 1 argument");

                CompileIntoA(args[0]);
                EmitAsm("LD_B_A");

                AsmOperand wlyLoop = MakeUniqueLabel("waitly_loop");
                AsmOperand wlyDone = MakeUniqueLabel("waitly_done");

                // If LCD is off, LY is not meaningful; return.
                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
                EmitAsm("JR_Z", wlyDone);

                EmitLabel(wlyLoop);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_B");
                EmitAsm("JR_NZ", wlyLoop);
                EmitLabel(wlyDone);
                return;
            }

physics2d.h — 2Dの運動と衝突

矩形bodyの積分・AABB接触と、個別に使える面応答を提供します。中心と半サイズで矩形を表し、active=0は処理対象外、inv_mass_q8<=0は固定物体です。KQRectは中心形式ではなく左上+幅・高さです。bodyの反発係数は矩形ペアのソルバーでは使わず、面応答ではKQSurface2Dの係数を使います。

座標・半径・半サイズ・速度・重力は、呼び出し側で統一した整数単位を使います。1回のstepが1時間刻みです。サンプルは1単位=1ピクセルです。位置を自動的にQ8.8へ変換したり、描画したりはしません。

worldは配列を借りて使います。配列やbodyの領域確保・寿命管理は呼び出し側の責任です。worldの初期化だけでは配列要素は初期化されません。

__smul16x8

符号付き16ビット値と符号付き8ビット係数を掛け、積/256の端数を0方向へ切り捨てます。

書式

c
s16 __smul16x8(s16 a, s8 b);

引数

a

倍率を掛ける符号付き16ビット値。

b

符号付き8ビット係数。−128~127を指定します。

戻り値

符号付き16ビットの積/256を返します。

使用条件と制限

係数の範囲は−128~127で、256が1.0という倍率です。積の端数は正負どちらも0方向へ切り捨てます。例:−257×64/256は−64です。

使用例

c
result[13]=__smul16x8(-257,64);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

左が衝突前、右が1刻み後です。赤い16×16の箱は中心(24,24)・速度(4,16)から中心(28,36)へ進み、青い床の上でY速度が0になります。各図の原点は画面(8,48)と(88,48)です。DMGではすべて黒で描きます。数値検証では初期値、設定、積分、矩形・点判定、符号付き倍率も確認します。

__smul16x8: DMG — 左が衝突前、右が1刻み後です。赤い16×16の箱は中心(24,24)・速度(4,16)から中心(28,36)へ進み、青い床の上でY速度が0になります。各図の原点は画面(8,48)と(88,48)です。DMGではすべて黒で描きます。数値検証では初期値、設定、積分、矩形・点判定、符号付き倍率も確認します。
DMG — 左が衝突前、右が1刻み後です。赤い16×16の箱は中心(24,24)・速度(4,16)から中心(28,36)へ進み、青い床の上でY速度が0になります。各図の原点は画面(8,48)と(88,48)です。DMGではすべて黒で描きます。数値検証では初期値、設定、積分、矩形・点判定、符号付き倍率も確認します。
__smul16x8: CGB — 左が衝突前、右が1刻み後です。赤い16×16の箱は中心(24,24)・速度(4,16)から中心(28,36)へ進み、青い床の上でY速度が0になります。各図の原点は画面(8,48)と(88,48)です。DMGではすべて黒で描きます。数値検証では初期値、設定、積分、矩形・点判定、符号付き倍率も確認します。
CGB — 左が衝突前、右が1刻み後です。赤い16×16の箱は中心(24,24)・速度(4,16)から中心(28,36)へ進み、青い床の上でY速度が0になります。各図の原点は画面(8,48)と(88,48)です。DMGではすべて黒で描きます。数値検証では初期値、設定、積分、矩形・点判定、符号付き倍率も確認します。

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\physics_body2d.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\physics_body2d.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=128k --no-cache --no-disasm
実装とソース

kitaqgb/lib/physics2d.h:9

宣言の注記(原文)
Multiply a signed word by a signed byte; divide by 256, truncating toward zero.

kitaqgb/kitaqgb/CodeGenerator.cs:11478

csharp
if (funcName == "__smul16x8")
            {
                if (args.Length != 2) Error(expr, "__smul16x8 requires 2 arguments");

                EmitDotTermToHL(args[0], args[1], signed: true, q1_7: false, labelPrefix: "__smul16x8");
                LastCallReturnsHL = true;
                return;
            }

rpg.h — 乱数、文字、マップ、セーブ、RPG/ADV/SLG

絶対位置を指定する文字表示関数は背景マップへ直接描きます。text_windowは枠付き領域を開き、text_clear_rectは指定範囲を埋め、text_print_xyは制御コードを解釈せずタイル列を書きます。text_print_u8、text_print_u16、text_print_s16は必要なマス数で10進数を表示します。桁数の異なる値に置き換える場合は、表示欄全体を確保して消去します。

rpg.hで宣言された乱数ライブラリ関数の実装はlib/rng.cにあります。コンパイラのGB生成器を共有し、rng_seedが初期化、rng8/rng_next8が1バイト、rng16/rng_next16が2バイトを消費します。rand_range/rng_range・rng_chance・weighted_choiceは、取得した値をゲーム内の選択に使いやすく変換します。ライブラリ関数を呼ぶ場合はrng.cをプログラムと一緒にコンパイルしてください。テストの再現にはシードと呼び出し順を固定します。剰余を使う選択は厳密な確率を保証しません。

RLE展開は個数・値のバイト対と、個数0の終端を使います。rle_decodeは通常のポインターからRAMへ、rle_decode_farはROMバンク付きのポインターからRAMへ展開します。__rle_decode_vramはバンク付きの入力を、LCDの状態を考慮してVRAMへ直接展開します。RAM用の実装はlib/rle.c、直接VRAM用はコンパイラが生成します。いずれも出力バイト数を返し、有効な列と十分な展開先領域を呼び出し側で用意する必要があります。

rpg.hで宣言されたゲーム状態の保存関数は、lib/flags.cに実装されています。三つのflag関数は独立した2048個の真偽ビットを、quest_stateとquest_set_stateはゲーム側で段階を決める64個のバイトを操作します。利用時はflags.cをコンパイルし、番号をこの範囲に収めてください。電源を切った後も進行状況を残すには、別途セーブ処理が必要です。

ウィンドウ内の会話表示は、操作対象の長方形・カーソル・文字速度を1組共有します。text_openで領域を設定し、text_closeで消去し、text_set_speedで文字ごとのVBlank待ちを指定します。text_printとtext_print_farは制御列を解釈し、text_choiceは選択が終わるまで待つ縦並びの一覧です。タイルと領域を用意し、処理が戻るまで待つ呼び出しをゲーム内でどう扱うかを設計します。

RPGのマップ表示・メタタイル・スクリプト・SRAM保存・経路探索を提供します。通常マップは8×8、メタタイルは16×16ピクセルのセルです。マップ読み込みと描画、移動マスク、攻撃マスクは別の処理で、画像の読み込みやイベント実行はゲーム側が組み合わせます。

探索は共有の約4KiB作業領域を使い、再入できません。マップは1024セル以下、探索する距離は254以下にします。0xFFは未到達を表すため、255歩の経路は扱えません。出力配列は現在のマップの幅×高さバイトを用意します。

既定は4スロット×512バイトです。各スロットは6バイトのヘッダーと最大506バイトのデータを持ちます。MBC5と8KiB SRAMのビルド例を示します。処理後はRAMを無効にし、以前のRAMバンクや有効状態は復元しません。

選択メニューは、決定まで待つ関数と、ゲームループから入力・描画を個別に呼ぶ関数を用意しています。所持品メニューは名前と数量を表示します。

__bit_clear

指定したフラグだけを0にします。そのフラグを含むバイト全体を消去するわけではありません。一時的な状態フラグなど、記録した条件を1つだけ解除する際に使います。同じビットを再び解除しても0のままです。

書式

c
void __bit_clear(u8* p, u16 bit_index);

引数

p

CPUからアクセスできるフラグ配列の先頭バイトへのポインターです。この呼び出しで配列の確保や初期化は行いません。設定・解除・反転には書き込み可能なRAMを使ってください。判定処理は読み取りだけを行います。

bit_index

0から始まるu16のビット番号(0〜65535)です。バイト単位のオフセットやビットマスクではありません。番号0は0番目のバイトの0x01、番号7は同じバイトの0x80、番号8は1番目のバイトの0x01を選びます。一般に、バイト位置はindex / 8、マスクは1 << (index % 8)です。Nバイトの配列ならindex < 8*Nを満たし、計算後のCPUアドレスも有効な範囲に収まる必要があります。

戻り値

ありません(void)。

使用条件と制限

必要な記憶領域は呼び出し側で用意します。配列長を渡す引数、範囲外の切り詰め、バンク切り替え、画面への自動転送はありません。大きなビット番号を渡すと、確保した配列の外を読み書きすることがあります。処理中はメモリーマップを維持してください。読み書きに副作用があるハードウェアレジスターを、RAM上のフラグと同じつもりで操作しないでください。

設定・解除・反転は、1バイトを読み取り、対象ビットを変更してから同じバイトへ書き戻します。この一連の処理は不可分ではありません。メインループと割り込み処理の書き込みを調整し、途中で同じバイトの別のビットに加えた変更が失われないようにしてください。

使用例

c
__bit_clear(bit_flags,2);
bit_clear_value=bit_flags[0]; // A6 & FB = A2, decimal 162.
__bit_clear(bit_flags,2);     // Clearing it twice leaves it clear.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

全体プログラムでは、この処理の直前に0番目のバイトが0xA6になっています。ビット2を解除すると0xA2(2進数10100010、10進数162)になり、CLEAR BIT2の行に162を表示します。再びビット2を解除しても値は変わらず、他のフラグは両方の呼び出しで維持されます。

GB版の正常時の表示は、10進数でTEST OFF 000、TEST ON 001、SET BIT1 166、CLEAR BIT2 162、TOGGLE BIT7 034、RESTORE 162、NEXT BYTE 130、FAILED 000です。NEXT BYTEはビット番号9を設定した結果で、0番目のバイトを変えずに1番目のバイトを0x80から0x82へ変更しています。DMGは白地に黒字、CGBは白地に青字で表示します。

プログラムは表示する各状態変化を照合し、不一致の数を表示します。設定・解除・反転の関数そのものには戻り値がなく、対応する行は操作後のRAMの値です。別途、両機種それぞれ136ケースで保護領域を含む520バイト全体をエミュレーター上で比較し、定数・実行時のビット番号、バイト境界・256バイト境界、判定結果の非ゼロのマスク8種類を確認しています。RAMの状態を検証する例であり、図形描画の例ではありません。実機と、割り込み処理による同時書き込みは未検証です。

__bit_clear: DMG
DMG
__bit_clear: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:187

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_set

指定したフラグを1にし、同じバイト内の他の7ビットは維持します。例えば、クエストの完了状態やアイテムの取得状態を記録できます。すでに1のビットを再び指定しても状態は変わりません。オン・オフを反転する操作ではありません。

書式

c
void __bit_set(u8* p, u16 bit_index);

引数

p

CPUからアクセスできるフラグ配列の先頭バイトへのポインターです。この呼び出しで配列の確保や初期化は行いません。設定・解除・反転には書き込み可能なRAMを使ってください。判定処理は読み取りだけを行います。

bit_index

0から始まるu16のビット番号(0〜65535)です。バイト単位のオフセットやビットマスクではありません。番号0は0番目のバイトの0x01、番号7は同じバイトの0x80、番号8は1番目のバイトの0x01を選びます。一般に、バイト位置はindex / 8、マスクは1 << (index % 8)です。Nバイトの配列ならindex < 8*Nを満たし、計算後のCPUアドレスも有効な範囲に収まる必要があります。

戻り値

ありません(void)。

使用条件と制限

必要な記憶領域は呼び出し側で用意します。配列長を渡す引数、範囲外の切り詰め、バンク切り替え、画面への自動転送はありません。大きなビット番号を渡すと、確保した配列の外を読み書きすることがあります。処理中はメモリーマップを維持してください。読み書きに副作用があるハードウェアレジスターを、RAM上のフラグと同じつもりで操作しないでください。

設定・解除・反転は、1バイトを読み取り、対象ビットを変更してから同じバイトへ書き戻します。この一連の処理は不可分ではありません。メインループと割り込み処理の書き込みを調整し、途中で同じバイトの別のビットに加えた変更が失われないようにしてください。

使用例

c
__bit_set(bit_flags,1);
bit_set_value=bit_flags[0]; // A4 | 02 = A6, decimal 166.
__bit_set(bit_flags,1);     // Setting it twice leaves it set.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

0xA4のビット1を設定します。10100100 | 00000010は10100110、つまり0xA6(10進数166)になります。SET BIT1の行が166なら正常です。サンプルでは同じビットをもう一度設定し、0xA6のままであることも確認します。繰り返し設定してもフラグが反転しないことを示す例です。

GB版の正常時の表示は、10進数でTEST OFF 000、TEST ON 001、SET BIT1 166、CLEAR BIT2 162、TOGGLE BIT7 034、RESTORE 162、NEXT BYTE 130、FAILED 000です。NEXT BYTEはビット番号9を設定した結果で、0番目のバイトを変えずに1番目のバイトを0x80から0x82へ変更しています。DMGは白地に黒字、CGBは白地に青字で表示します。

プログラムは表示する各状態変化を照合し、不一致の数を表示します。設定・解除・反転の関数そのものには戻り値がなく、対応する行は操作後のRAMの値です。別途、両機種それぞれ136ケースで保護領域を含む520バイト全体をエミュレーター上で比較し、定数・実行時のビット番号、バイト境界・256バイト境界、判定結果の非ゼロのマスク8種類を確認しています。RAMの状態を検証する例であり、図形描画の例ではありません。実機と、割り込み処理による同時書き込みは未検証です。

__bit_set: DMG
DMG
__bit_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:186

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_test

複数のフラグを各ビットに詰めた配列から、1つのフラグを読み取ります。配列の内容は変更しません。RAMの1バイトで8個のオン・オフ状態を保持できます。結果は対象ビットが0か1かを示しますが、返される数値の形式はGB版とFC版で異なります。

書式

c
u8 __bit_test(const u8* p, u16 bit_index);

引数

p

CPUからアクセスできるフラグ配列の先頭バイトへのポインターです。この呼び出しで配列の確保や初期化は行いません。設定・解除・反転には書き込み可能なRAMを使ってください。判定処理は読み取りだけを行います。

bit_index

0から始まるu16のビット番号(0〜65535)です。バイト単位のオフセットやビットマスクではありません。番号0は0番目のバイトの0x01、番号7は同じバイトの0x80、番号8は1番目のバイトの0x01を選びます。一般に、バイト位置はindex / 8、マスクは1 << (index % 8)です。Nバイトの配列ならindex < 8*Nを満たし、計算後のCPUアドレスも有効な範囲に収まる必要があります。

戻り値

u8で、ビットが0なら0、1なら1を返します。GB版はビットマスクを適用した結果を0または1にそろえて返します。

使用条件と制限

必要な記憶領域は呼び出し側で用意します。配列長を渡す引数、範囲外の切り詰め、バンク切り替え、画面への自動転送はありません。大きなビット番号を渡すと、確保した配列の外を読み書きすることがあります。処理中はメモリーマップを維持してください。読み書きに副作用があるハードウェアレジスターを、RAM上のフラグと同じつもりで操作しないでください。

使用例

c
bit_off=__bit_test(bit_flags,1); // Bit 1 of A4 is clear: zero.
bit_on=__bit_test(bit_flags,7);  // Bit 7 is set: GB returns 1; FC returns 128.
bit_is_on=0;
if(bit_on!=0)bit_is_on=1; // The same nonzero test works on both targets.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

0番目のバイトを0xA4(2進数10100100)にして始めます。ビット1を調べると0を返します。ビット7ではGB版が1、FC版が128を返します。サンプルはその結果が0でないことから共通のオン・オフ値も求め、元のバイトが変わっていないことを照合します。下の画像のTEST OFF・TEST ONを確認してください。

GB版の正常時の表示は、10進数でTEST OFF 000、TEST ON 001、SET BIT1 166、CLEAR BIT2 162、TOGGLE BIT7 034、RESTORE 162、NEXT BYTE 130、FAILED 000です。NEXT BYTEはビット番号9を設定した結果で、0番目のバイトを変えずに1番目のバイトを0x80から0x82へ変更しています。DMGは白地に黒字、CGBは白地に青字で表示します。

プログラムは表示する各状態変化を照合し、不一致の数を表示します。設定・解除・反転の関数そのものには戻り値がなく、対応する行は操作後のRAMの値です。別途、両機種それぞれ136ケースで保護領域を含む520バイト全体をエミュレーター上で比較し、定数・実行時のビット番号、バイト境界・256バイト境界、判定結果の非ゼロのマスク8種類を確認しています。RAMの状態を検証する例であり、図形描画の例ではありません。実機と、割り込み処理による同時書き込みは未検証です。

__bit_test: DMG
DMG
__bit_test: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:185

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_toggle

指定したフラグを反転し、0なら1、1なら0にします。他のビットは維持します。途中で他の処理が書き換えなければ、同じビットを2回反転すると元の状態に戻ります。

書式

c
void __bit_toggle(u8* p, u16 bit_index);

引数

p

CPUからアクセスできるフラグ配列の先頭バイトへのポインターです。この呼び出しで配列の確保や初期化は行いません。設定・解除・反転には書き込み可能なRAMを使ってください。判定処理は読み取りだけを行います。

bit_index

0から始まるu16のビット番号(0〜65535)です。バイト単位のオフセットやビットマスクではありません。番号0は0番目のバイトの0x01、番号7は同じバイトの0x80、番号8は1番目のバイトの0x01を選びます。一般に、バイト位置はindex / 8、マスクは1 << (index % 8)です。Nバイトの配列ならindex < 8*Nを満たし、計算後のCPUアドレスも有効な範囲に収まる必要があります。

戻り値

ありません(void)。

使用条件と制限

必要な記憶領域は呼び出し側で用意します。配列長を渡す引数、範囲外の切り詰め、バンク切り替え、画面への自動転送はありません。大きなビット番号を渡すと、確保した配列の外を読み書きすることがあります。処理中はメモリーマップを維持してください。読み書きに副作用があるハードウェアレジスターを、RAM上のフラグと同じつもりで操作しないでください。

設定・解除・反転は、1バイトを読み取り、対象ビットを変更してから同じバイトへ書き戻します。この一連の処理は不可分ではありません。メインループと割り込み処理の書き込みを調整し、途中で同じバイトの別のビットに加えた変更が失われないようにしてください。

使用例

c
__bit_toggle(bit_flags,7);
bit_toggle_value=bit_flags[0]; // A2 ^ 80 = 22, decimal 34.
__bit_toggle(bit_flags,7);
bit_restore_value=bit_flags[0]; // Two toggles restore A2.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

全体プログラムでは、この処理を0xA2から始めます。ビット7を反転すると0x22(10進数34)になり、TOGGLE BIT7は034になります。もう一度反転すると0xA2へ戻り、RESTOREは162になります。2つの表示から、他のビットを保ったまま両方向に反転できることを確認できます。

GB版の正常時の表示は、10進数でTEST OFF 000、TEST ON 001、SET BIT1 166、CLEAR BIT2 162、TOGGLE BIT7 034、RESTORE 162、NEXT BYTE 130、FAILED 000です。NEXT BYTEはビット番号9を設定した結果で、0番目のバイトを変えずに1番目のバイトを0x80から0x82へ変更しています。DMGは白地に黒字、CGBは白地に青字で表示します。

プログラムは表示する各状態変化を照合し、不一致の数を表示します。設定・解除・反転の関数そのものには戻り値がなく、対応する行は操作後のRAMの値です。別途、両機種それぞれ136ケースで保護領域を含む520バイト全体をエミュレーター上で比較し、定数・実行時のビット番号、バイト境界・256バイト境界、判定結果の非ゼロのマスク8種類を確認しています。RAMの状態を検証する例であり、図形描画の例ではありません。実機と、割り込み処理による同時書き込みは未検証です。

__bit_toggle: DMG
DMG
__bit_toggle: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:188

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__copy16

長さの引数を渡さず、srcからdstへ正確に16バイトをコピーします。CPUからアクセスできるメモリーに置いた16バイトのレコードやGB・FCのタイル1枚分のコピーなどに使えます。16はビット数ではなくバイト数です。画面への画像転送を行う関数ではありません。

書式

c
void __copy16(void* dst, const void* src);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

src

読み取り元の先頭バイトへのポインターです。コピー中は、範囲全体を読み取り可能な状態で保持し、内容を変更しないでください。

戻り値

ありません(void)。

使用条件と制限

コピー元とコピー先には重ならない領域を使ってください。これらの処理は小さいアドレスからコピーし、重なりを考慮するmemmoveの動作は提供しません。まだ読んでいないコピー元へ書き込むと、後続のデータが壊れます。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__copy16で、規則的な値を入れた16バイトをオフセット1からコピーします。memory_checkでコピー元・先の配列全体を照合し、COPY16の行に016が表示されることを確認します。先頭を1バイト空けて保護領域とし、転送末尾より後のバイトも変わっていないことを調べます。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__copy16: DMG
DMG
__copy16: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:177

kitaqgb/kitaqgb/CodeGenerator.cs:8247

csharp
if (funcName == "__copy16" || funcName == "__copy32")
            {
                if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");

                int copyLen = (funcName == "__copy16") ? 16 : 32;
                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst
                EmitRamMemcpyLoop(funcName.TrimStart('_'));
                return;
            }

void EmitRamMemcpyLoop(string labelPrefix)
    {
        AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
        AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");

        EmitLabel(loop);
        EmitAsm("LD_A_B");
        EmitAsm("OR_C");
        EmitAsm("JR_Z", done);
        EmitAsm("LD_A_DE");
        EmitAsm("LDI_HL_A");
        EmitAsm("INC_DE");
        EmitAsm("DEC_BC");
        EmitAsm("JR", loop);
        EmitLabel(done);
    }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__copy32

長さの引数を渡さず、srcからdstへ正確に32バイトをコピーします。CPUからアクセスできるメモリー上の32バイト固定長レコードや、連続した16バイトのタイルパターン2枚分などに使います。小さいアドレスからのバイトコピーであり、32ビット値の一括読み込みやバンクをまたぐ転送ではありません。

書式

c
void __copy32(void* dst, const void* src);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

src

読み取り元の先頭バイトへのポインターです。コピー中は、範囲全体を読み取り可能な状態で保持し、内容を変更しないでください。

戻り値

ありません(void)。

使用条件と制限

コピー元とコピー先には重ならない領域を使ってください。これらの処理は小さいアドレスからコピーし、重なりを考慮するmemmoveの動作は提供しません。まだ読んでいないコピー元へ書き込むと、後続のデータが壊れます。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__copy32で、規則的な値を入れた32バイトをオフセット1からコピーします。memory_checkでコピー元・先の配列全体を照合し、COPY32の行に032が表示されることを確認します。先頭を1バイト空けて保護領域とし、転送末尾より後のバイトも変わっていないことを調べます。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__copy32: DMG
DMG
__copy32: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:178

kitaqgb/kitaqgb/CodeGenerator.cs:8247

csharp
if (funcName == "__copy16" || funcName == "__copy32")
            {
                if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");

                int copyLen = (funcName == "__copy16") ? 16 : 32;
                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst
                EmitRamMemcpyLoop(funcName.TrimStart('_'));
                return;
            }

void EmitRamMemcpyLoop(string labelPrefix)
    {
        AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
        AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");

        EmitLabel(loop);
        EmitAsm("LD_A_B");
        EmitAsm("OR_C");
        EmitAsm("JR_Z", done);
        EmitAsm("LD_A_DE");
        EmitAsm("LDI_HL_A");
        EmitAsm("INC_DE");
        EmitAsm("DEC_BC");
        EmitAsm("JR", loop);
        EmitLabel(done);
    }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__far_memcpy

ROMバンクを一時的に切り替え、srcからdstへアドレスの小さい方からlenバイトをコピーし、コンパイラが保持していたROMバンクへ戻します。この機種では__farmemcpyと__far_memcpyは同じ実装を使います。

書式

c
void __far_memcpy(void* dst, u8 bank, const void* src, u16 len);

引数

dst

書き込み先のメモリです。実際のコピー量を収められる領域を確保してください。0バイト転送以外では、ヌルポインタや無効な書き込み先を検出する処理はありません。

bank

コピー元ROMのバンクを選ぶ8ビット値です。カートリッジとコンパイラのバンク配置に対応する番号を使います。コピー元アドレスとは別に指定します。

src

選んだROMバンク内の読み出せるバイト列を指す近ポインタです。ポインタ自体にはバンク番号を含みません。

len

バイト数を0~65535で指定します。0ならメモリはコピーしませんが、バンクの選択と復帰の処理は実行します。

戻り値

ありません(void)。

使用条件と制限

コピー元全体が目的のROM配置で読め、コピー先全体が書き込み可能な範囲になるようにしてください。バンク窓の端で次のバンクへ進む処理、memmoveのような重なりへの対応、VRAMの安全な書き込み制御はありません。VRAMには描画用の転送APIを使います。

サンプルの呼び出し側コードは固定バンク0に配置し、コピー元の切り替えで実行中のコードが見えなくなることを防ぎます。転送は同期処理です。転送中に割り込み側から別のバンク切り替えコピーを実行したり、この関数だけでアプリ全体の割り込み安全性が確保されると考えたりしないでください。

GB版の補助処理は、コンパイラが保持するROMバンク情報から復帰先を決めます。その情報を更新する__bankswitchを使ってください。マッパーへの直接書き込みでは更新されません。HRAMの復帰バンク保存領域は共有なので、入れ子の転送で上書きする使い方は安全ではありません。

使用例

c
__far_memcpy(buffer,2,asset_shapes,32); // Same backend as __farmemcpy.
expect(*asset_bank_marker==0xA1,29);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),32);
buffer[32]=0x55;
__far_memcpy(buffer+32,2,asset_shapes,0); // Zero count writes no bytes and restores the bank.
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

バンク1を選択した状態から、素材のバンク2にある絵柄2枚、32バイトをコピーします。コピーした全バイト、バンク1への復帰、長さ0のコピーで確認値0x55が変わらないことを検査します。

期待画像では、赤い横一列がピクセル座標(16,32)から始まり、72×8ピクセルを占めます。8×8の三角形、1ピクセル枠の8×8の正方形、左4列を塗ったタイルの順に、3回繰り返します。三角形は左上の1ピクセルから最下段の8ピクセルまで広がります。

GB版では、(16,64)の青い三角形と枠が__farmemcpyの結果、(16,96)の緑の組が__far_memcpyの結果です。各組は16×8ピクセルです。CGBはこの配色、DMGは同じ形を白地に黒で表示します。

期待する表示はFAILED CHECKS 000とFIRST FAILURE 00です。検証では300フレーム実行し、文字、図形の形、色を期待値と比較します。これはエミュレータ上の検証であり、実機試験ではありません。

__far_memcpy: DMG
DMG
__far_memcpy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\asset_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\asset_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:148

__fill_tilemap

__fill_tilemapは、コンパイラで__vram_memsetと同じ処理に展開される別名です。長さの単位は同じくバイトで、即時のCPU転送とLCDへのアクセス条件も同じです。

dstから連続するlenバイトを、1バイトのvalueで直ちに埋めます。繰り返すのは1バイトで、タイルパターン全体ではありません。マップのバイトを埋める場合はタイル番号を繰り返し、パターンのバイトを埋める場合は画素のビットプレーンを書き換えます。

書式

c
void __fill_tilemap(u16 dst, u8 value, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

value

0〜255の1バイトを繰り返し書きます。画面での意味は、転送先がタイル番号、画素のビットプレーン、その他のバイト列のどれかによって変わります。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

開始アドレスとバイト数を明示してください。この別名関数は、背景マップを選択したり、マップ全体を自動で埋めたりはしません。32×32のタイル番号マップ全体を埋める場合は、VRAMバンク0を選択し、マップの基底アドレスと1024バイトを指定します。

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__fill_tilemap(0x9922,1,length_base+length_extra); // Explicit address and byte count, not a whole-map clear.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__fill_tilemapでタイル座標(2,9)、ピクセル座標(16,72)から3バイトのマップをタイル番号1で埋めます。塗りつぶした24×8の横棒と、空白の隣接セルが期待結果です。長さは変数や関数の戻り値から計算し、その場合も3バイトすべてに値1が入ることを確認します。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__fill_tilemap: DMG
DMG
__fill_tilemap: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:157

__getbgmapbase

LCDCのbit3から背景のタイルマップ先頭を求めます。

書式

c
u16 __getbgmapbase();

戻り値

$9800または$9C00。タイル画像の格納先アドレスではありません。

使用例

c
M_LCDC=0;result[0]=__getbgmapbase();M_LCDC=8;result[1]=__getbgmapbase();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__getbgmapbase: DMG
DMG
__getbgmapbase: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:140

kitaqgb/kitaqgb/CodeGenerator.cs:10619

csharp
if (funcName == "__getbgmapbase")
{
    if (args.Length != 0) Error(expr, "__getbgmapbase requires 0 arguments");

    AsmOperand gb_base0 = MakeUniqueLabel("getbgbase_base0");
    AsmOperand gb_done  = MakeUniqueLabel("getbgbase_done");

    EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
    EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate)); // bit3 (BG tile map display select)
    EmitAsm("JR_Z", gb_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
    EmitAsm("JR", gb_done);
    EmitLabel(gb_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
    EmitLabel(gb_done);
    LastCallReturnsHL = true;
    return;
}
__manhattan

2点のX差の絶対値とY差の絶対値を足します。

書式

c
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);

引数

x1

点1のX座標0~255。

y1

点1のY座標0~255。

x2

点2のX座標0~255。

y2

点2のY座標0~255。

戻り値

|x1−x2|+|y1−y2|の下位8ビット。255を超える距離は周回します。

使用例

c
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

矩形の内部・端・外側・幅0を試します。INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1です。距離15、距離の周回254、配列位置64と65280を確認します。

__manhattan: DMG
DMG
__manhattan: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:183

kitaqgb/kitaqgb/CodeGenerator.cs:8516

csharp
if (funcName == "__manhattan")
            {
                if (args.Length != 4) Error(expr, "__manhattan(x1, y1, x2, y2) expects 4 arguments");

                Expr[] foldedManhattanArgs = args.Select(FoldConstants).ToArray();
                if (foldedManhattanArgs[0].Match(Tag.Integer, out int constX1) &&
                    foldedManhattanArgs[1].Match(Tag.Integer, out int constY1) &&
                    foldedManhattanArgs[2].Match(Tag.Integer, out int constX2) &&
                    foldedManhattanArgs[3].Match(Tag.Integer, out int constY2))
                {
                    int dx = Math.Abs((constX1 & 0xFF) - (constX2 & 0xFF));
                    int dy = Math.Abs((constY1 & 0xFF) - (constY2 & 0xFF));
                    EmitAsm("LD_A_IMM", new AsmOperand((dx + dy) & 0xFF, AddressMode.Immediate));
                    return;
                }

                EmitPushExprAsWord(args[0]); // x1
                EmitPushExprAsWord(args[1]); // y1
                EmitPushExprAsWord(args[2]); // x2
                EmitPushExprAsWord(args[3]); // y2

                AsmOperand dxGe = MakeUniqueLabel("manhattan_dx_ge");
                AsmOperand dxDone = MakeUniqueLabel("manhattan_dx_done");
                AsmOperand dyGe = MakeUniqueLabel("manhattan_dy_ge");
                AsmOperand dyDone = MakeUniqueLabel("manhattan_dy_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // x2
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", dxGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_B");
                EmitAsm("JR", dxDone);
                EmitLabel(dxGe);
                EmitAsm("LD_A_B");
                EmitAsm("SUB_C");
                EmitLabel(dxDone);
                EmitAsm("LD_B_A"); // dx

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // y1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // y2
                EmitAsm("LD_A_C");
                EmitAsm("CP_D");
                EmitAsm("JR_NC", dyGe);
                EmitAsm("LD_A_D");
                EmitAsm("SUB_C");
                EmitAsm("JR", dyDone);
                EmitLabel(dyGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_D");
                EmitLabel(dyDone);
                EmitAsm("ADD_B");
                EmitAsm("ADD_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                return;
            }
__map_index

行優先配列のセル位置を1次元インデックスへ変換します。

書式

c
u16 __map_index(u8 x, u8 y, u8 width);

引数

x

列0~255。通常はwidth未満です。

y

行0~255。

width

1行のセル数0~255。

戻り値

y*width+xをu16で返します。座標の範囲検査はありません。

使用例

c
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

矩形の内部・端・外側・幅0を試します。INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1です。距離15、距離の周回254、配列位置64と65280を確認します。

__map_index: DMG
DMG
__map_index: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:181

kitaqgb/kitaqgb/CodeGenerator.cs:8365

csharp
if (funcName == "__map_index")
            {
                if (args.Length != 3) Error(expr, "__map_index(x, y, width) expects 3 arguments");

                Expr xExpr = FoldConstants(args[0]);
                Expr yExpr = FoldConstants(args[1]);
                Expr widthExpr = FoldConstants(args[2]);
                // Apply the width parameter's byte conversion before choosing a shift specialization.
                if (widthExpr.Match(Tag.Integer, out int byteWidth))
                    widthExpr = Expr.Make(Tag.Integer, byteWidth & 0xFF).WithSource(widthExpr.Source);
                if (xExpr.Match(Tag.Integer, out int fullXConst) &&
                    yExpr.Match(Tag.Integer, out int fullYConst) &&
                    widthExpr.Match(Tag.Integer, out int fullWidthConst))
                {
                    // Apply the byte argument widths before calculating the word-sized constant index.
                    int fullIndexConst = (((fullYConst & 0xFF) * (fullWidthConst & 0xFF)) + (fullXConst & 0xFF)) & 0xFFFF;
                    EmitAsm("LD_HL_IMM", new AsmOperand(fullIndexConst, AddressMode.Immediate16));
                    LastCallReturnsHL = true;
                    return;
                }

                if (widthExpr.Match(Tag.Integer, out int widthConst) && widthConst > 0 && IsPow2(widthConst))
                {
                    CompileIntoA(yExpr);
                    EmitAsm("LD_L_A");
                    EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                    int sh = Log2Pow2(widthConst);
                    for (int i = 0; i < sh; i++) EmitAsm("ADD_HL_HL");

                    if (xExpr.Match(Tag.Integer, out int xConst) && xConst == 0)
                    {
                        LastCallReturnsHL = true;
                        return;
                    }

                    CompileIntoA(xExpr);
                    EmitAsm("LD_E_A");
                    EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                    EmitAsm("ADD_HL_DE");
                    LastCallReturnsHL = true;
                    return;
                }

                CompileIntoA(args[0]); EmitPushAAsWord(); // x
                CompileIntoA(args[1]); EmitPushAAsWord(); // y
                CompileIntoA(args[2]); EmitPushAAsWord(); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A"); // y

                EmitMul8x8ToHL_EC("__map_index");

                // Loading the saved X argument uses HL, so preserve the multiplication result first.
                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE");
                EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                LastCallReturnsHL = true;
                return;
            }
__memcpy

srcからdstへ指定したバイト数をコピーします。長さは16ビットなので、RAM上の表全体など、255バイトを超える転送にも使えます。アドレスの小さい方から順に読み書きします。

書式

c
void __memcpy(void* dst, const void* src, u16 count);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

src

読み取り元の先頭バイトへのポインターです。コピー中は、範囲全体を読み取り可能な状態で保持し、内容を変更しないでください。

count

u16で指定するバイト数(0〜65535)です。要素数ではありません。型の最大値は、対象機にその大きさのバッファを確保できることを意味しません。範囲全体を現在のメモリーマップ内に収めてください。

戻り値

ありません(void)。

使用条件と制限

長さ0ではバイトの転送を行いません。ただし引数式の副作用は実行されます。サンプルではポインターを返す2つの関数を引数に渡し、長さ0でも両方が呼ばれたことを確認します。

コピー元とコピー先には重ならない領域を使ってください。これらの処理は小さいアドレスからコピーし、重なりを考慮するmemmoveの動作は提供しません。まだ読んでいないコピー元へ書き込むと、後続のデータが壊れます。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__memcpyで、規則的な値を入れた257バイトをオフセット1からコピーします。memory_checkでコピー元・先の配列全体を照合し、COPY WORDの行に257が表示されることを確認します。先頭を1バイト空けて保護領域とし、転送末尾より後のバイトも変わっていないことを調べます。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__memcpy: DMG
DMG
__memcpy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:142

kitaqgb/kitaqgb/CodeGenerator.cs:9084

csharp
if (funcName == "__memcpy" || funcName == "__memcpy_small")
            {
                // Select byte or word count semantics before optional fixed-array bounds diagnostics.
                bool useU8Count = (funcName == "__memcpy_small");
                if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }

                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false, hasSrc = false;
                int dstBytes = 0, srcBytes = 0;
                string dstName = null, srcName = null;
                if (doCheck)
                {
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
                    hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
                }

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && isConstCountAny)
                {
                    if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                    if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                }

                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    if (normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem8 = normalizedConstCount % 8;
                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
                            AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++)
                            {
                                EmitAsm("LD_A_DE");
                                EmitAsm("LDI_HL_A");
                                EmitAsm("INC_DE");
                            }
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem8; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        return;
                    }

                    int blocks16 = normalizedConstCount / 16;
                    int rem16 = normalizedConstCount % 16;
                    if (blocks16 > 0)
                    {
                        EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                        // Use sixteen-byte groups while keeping the residual copy length known at compile time.
                        AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
                        EmitLabel(loop16);
                        for (int k = 0; k < 16; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        EmitAsm("DEC_B");
                        EmitAsm("JP_NZ", loop16);
                    }
                    for (int k = 0; k < rem16; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck)
                    {
                        if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
                        if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
                    }

                    AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);

                    EmitLabel(smallLoopLabel);
                    EmitAsm("LD_A_DE");
                    EmitAsm("LDI_HL_A");
                    EmitAsm("INC_DE");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[1]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && !isConstCountAny)
                {
                    if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
                    if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
                }
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                EmitAsm("POP_DE");
                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memcpy_small

8ビットの長さを使い、最大255バイトのブロックをコピーします。転送長が1バイトに収まる場合に使います。_smallは長さの型を表しており、特定の実行時間や生成コード量を保証する名前ではありません。

書式

c
void __memcpy_small(void* dst, const void* src, u8 len);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

src

読み取り元の先頭バイトへのポインターです。コピー中は、範囲全体を読み取り可能な状態で保持し、内容を変更しないでください。

len

u8で指定する0〜255のバイト数です。256バイト全体を表すつもりで256を渡してはいけません。この引数の型に収まりません。256バイト以上には_smallの付かない関数を使ってください。

戻り値

ありません(void)。

使用条件と制限

長さ0ではバイトの転送を行いません。ただし引数式の副作用は実行されます。サンプルではポインターを返す2つの関数を引数に渡し、長さ0でも両方が呼ばれたことを確認します。

コピー元とコピー先には重ならない領域を使ってください。これらの処理は小さいアドレスからコピーし、重なりを考慮するmemmoveの動作は提供しません。まだ読んでいないコピー元へ書き込むと、後続のデータが壊れます。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__memcpy_smallで、規則的な値を入れた255バイトをオフセット1からコピーします。memory_checkでコピー元・先の配列全体を照合し、COPY BYTEの行に255が表示されることを確認します。先頭を1バイト空けて保護領域とし、転送末尾より後のバイトも変わっていないことを調べます。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__memcpy_small: DMG
DMG
__memcpy_small: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:175

kitaqgb/kitaqgb/CodeGenerator.cs:9084

csharp
if (funcName == "__memcpy" || funcName == "__memcpy_small")
            {
                // Select byte or word count semantics before optional fixed-array bounds diagnostics.
                bool useU8Count = (funcName == "__memcpy_small");
                if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }

                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false, hasSrc = false;
                int dstBytes = 0, srcBytes = 0;
                string dstName = null, srcName = null;
                if (doCheck)
                {
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
                    hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
                }

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && isConstCountAny)
                {
                    if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                    if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                }

                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    if (normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem8 = normalizedConstCount % 8;
                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
                            AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++)
                            {
                                EmitAsm("LD_A_DE");
                                EmitAsm("LDI_HL_A");
                                EmitAsm("INC_DE");
                            }
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem8; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        return;
                    }

                    int blocks16 = normalizedConstCount / 16;
                    int rem16 = normalizedConstCount % 16;
                    if (blocks16 > 0)
                    {
                        EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                        // Use sixteen-byte groups while keeping the residual copy length known at compile time.
                        AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
                        EmitLabel(loop16);
                        for (int k = 0; k < 16; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        EmitAsm("DEC_B");
                        EmitAsm("JP_NZ", loop16);
                    }
                    for (int k = 0; k < rem16; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck)
                    {
                        if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
                        if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
                    }

                    AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);

                    EmitLabel(smallLoopLabel);
                    EmitAsm("LD_A_DE");
                    EmitAsm("LDI_HL_A");
                    EmitAsm("INC_DE");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[1]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && !isConstCountAny)
                {
                    if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
                    if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
                }
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                EmitAsm("POP_DE");
                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memset

転送先の範囲を同じ1バイトの値で埋めます。長さは16ビットで、大きなバッファの消去や表の初期化に使えます。例えば0x5Aなら5A 5A 5A…を書き込みます。複数バイトの整数を繰り返す操作ではありません。

書式

c
void __memset(void* dst, u8 value, u16 count);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

value

各アドレスに繰り返し書く0〜255の1バイト値です。0なら範囲をゼロで埋めます。u16配列をゼロ以外の数値で初期化したい場合は、各要素に代入してください。

count

u16で指定するバイト数(0〜65535)です。要素数ではありません。型の最大値は、対象機にその大きさのバッファを確保できることを意味しません。範囲全体を現在のメモリーマップ内に収めてください。

戻り値

ありません(void)。

使用条件と制限

長さ0ではバイトの転送を行いません。ただし引数式の副作用は実行されます。サンプルではポインターを返す2つの関数を引数に渡し、長さ0でも両方が呼ばれたことを確認します。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__memsetで、オフセット1からの257バイトを0x5Aで埋めます。memory_checkは対象範囲の全バイトが0x5Aで、前後の保護領域が0xCCのままであり、別のコピー元バッファも変わっていないことを調べます。FILL WORDの行に257が表示されれば正常です。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__memset: DMG
DMG
__memset: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:141

kitaqgb/kitaqgb/CodeGenerator.cs:8926

csharp
if (funcName == "__memset" || funcName == "__memset_small")
            {
                // The small API truncates its count to a byte; the general API uses a word count.
                bool useU8Count = (funcName == "__memset_small");
                if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }

                // -Zcheck: fixed-array range check for memset length
                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false;
                int dstBytes = 0;
                string dstName = null;
                if (doCheck)
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                {
                    Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                    EmitAsm("JP", EnsureCheckTrap());
                    return;
                }

                // Size-specialized memset for small constant counts (<=16): unrolled stores.
                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoA(args[1]);
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                        EmitAsm("LDI_HL_A");
                    return;
                }

                // Size-specialized memset for constant counts beyond 16
                if (isConstCountAny && normalizedConstCount >= 0)
                {
                    if (normalizedConstCount == 0) return;

                    if (normalizedConstCount > 16 && normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem = normalizedConstCount % 8;

                        CompileIntoHL(args[0]);
                        EmitAsm("PUSH_HL");
                        CompileIntoA(args[1]);
                        EmitAsm("POP_HL");

                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Emit groups of eight stores followed by the constant remainder for medium fills.
                            AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }

                    if (normalizedConstCount <= 255)
                    {
                        int blocks16 = normalizedConstCount / 16;
                        int rem = normalizedConstCount % 16;

                        CompileIntoHL(args[0]);
                        EmitAsm("PUSH_HL");
                        CompileIntoA(args[1]);
                        EmitAsm("POP_HL");

                        if (blocks16 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                            // Use sixteen-store groups for larger byte-range constant fills.
                            AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
                            EmitLabel(loop16);
                            for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop16);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[1]);
                    EmitAsm("LD_D_A");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck && hasDst)
                        EmitU8LeqConstOrTrapInA(dstBytes);

                    AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);
                    EmitAsm("LD_A_D");
                    EmitLabel(smallLoopLabel);
                    EmitAsm("LDI_HL_A");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && hasDst && !isConstCountAny)
                    EmitU16LeqConstOrTrap(dstBytes);
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                CompileIntoA(args[1]);
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memset_small

8ビットの長さを使い、連続する最大255バイトを同じ1バイトの値で埋めます。小さな作業バッファの初期化に使えます。必要な長さが255バイトを超える場合は__memsetを選びます。

書式

c
void __memset_small(void* dst, u8 value, u8 len);

引数

dst

書き込み先の先頭バイトへのポインターです。処理全体が収まる連続領域を確保してください。バッファを自動確保する機能はありません。

value

各アドレスに繰り返し書く0〜255の1バイト値です。0なら範囲をゼロで埋めます。u16配列をゼロ以外の数値で初期化したい場合は、各要素に代入してください。

len

u8で指定する0〜255のバイト数です。256バイト全体を表すつもりで256を渡してはいけません。この引数の型に収まりません。256バイト以上には_smallの付かない関数を使ってください。

戻り値

ありません(void)。

使用条件と制限

長さ0ではバイトの転送を行いません。ただし引数式の副作用は実行されます。サンプルではポインターを返す2つの関数を引数に渡し、長さ0でも両方が呼ばれたことを確認します。

処理が完了してから呼び出し元へ戻ります。ROM・RAMのバンク切り替えや、バッファ境界での範囲の切り詰めは行いません。メモリーの割り当てを安定させ、有効な範囲を指定し、同じバッファを使う割り込み処理とのアクセスを調整してください。

GB版では通常のCPUメモリーアクセスです。VRAM・OAMへ安全にアクセスできる時期の待機、DMAの開始、CGBのVRAM・WRAMバンクの選択は行いません。それらには専用の画像転送APIを使ってください。

使用例

c
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__memset_smallで、オフセット1からの255バイトを0x5Aで埋めます。memory_checkは対象範囲の全バイトが0x5Aで、前後の保護領域が0xCCのままであり、別のコピー元バッファも変わっていないことを調べます。FILL BYTEの行に255が表示されれば正常です。

全体プログラムは処理ごとに264バイトのバッファ2つを初期化し、コピー元・先の全バイトと、変更してはいけない前後の保護領域を照合します。正常時の表示はCOPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000です。WORD・BYTEは長さの型を表し、1回にコピーする要素の大きさではありません。数字はサンプルの照合処理が求めた値で、戻り値のない各関数が返した値ではありません。

掲載画像はDMGが白地に黒字、CGBが白地に青字、FCが黒地に青字です。RAM操作を確認する例であり、文字は検査結果を表示するためのものです。図形描画関数の検証ではありません。別途、両機種それぞれ88ケースをエミュレーターで照合し、定数・実行時の長さ0・1・16・17・32・33・64・65・255バイトと、16ビット長の256・257バイトを確認しています。実機では未検証です。

__memset_small: DMG
DMG
__memset_small: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:176

kitaqgb/kitaqgb/CodeGenerator.cs:8926

csharp
if (funcName == "__memset" || funcName == "__memset_small")
            {
                // The small API truncates its count to a byte; the general API uses a word count.
                bool useU8Count = (funcName == "__memset_small");
                if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }

                // -Zcheck: fixed-array range check for memset length
                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false;
                int dstBytes = 0;
                string dstName = null;
                if (doCheck)
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                {
                    Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                    EmitAsm("JP", EnsureCheckTrap());
                    return;
                }

                // Size-specialized memset for small constant counts (<=16): unrolled stores.
                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoA(args[1]);
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                        EmitAsm("LDI_HL_A");
                    return;
                }

                // Size-specialized memset for constant counts beyond 16
                if (isConstCountAny && normalizedConstCount >= 0)
                {
                    if (normalizedConstCount == 0) return;

                    if (normalizedConstCount > 16 && normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem = normalizedConstCount % 8;

                        CompileIntoHL(args[0]);
                        EmitAsm("PUSH_HL");
                        CompileIntoA(args[1]);
                        EmitAsm("POP_HL");

                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Emit groups of eight stores followed by the constant remainder for medium fills.
                            AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }

                    if (normalizedConstCount <= 255)
                    {
                        int blocks16 = normalizedConstCount / 16;
                        int rem = normalizedConstCount % 16;

                        CompileIntoHL(args[0]);
                        EmitAsm("PUSH_HL");
                        CompileIntoA(args[1]);
                        EmitAsm("POP_HL");

                        if (blocks16 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                            // Use sixteen-store groups for larger byte-range constant fills.
                            AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
                            EmitLabel(loop16);
                            for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop16);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[1]);
                    EmitAsm("LD_D_A");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck && hasDst)
                        EmitU8LeqConstOrTrapInA(dstBytes);

                    AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);
                    EmitAsm("LD_A_D");
                    EmitLabel(smallLoopLabel);
                    EmitAsm("LDI_HL_A");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && hasDst && !isConstCountAny)
                    EmitU16LeqConstOrTrap(dstBytes);
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                CompileIntoA(args[1]);
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__rle_decode_vram

ROMバンクを指定した個数・値の列を、RAMの展開用バッファを介さずVRAMへ直接展開します。コンパイラが各組の取得と、LCDの状態を考慮した連続書き込みを行うループを生成し、出力バイト数を返します。圧縮したタイルデータや、バイト単位のタイルマップを読み込む場合に使えます。

書式

c
u16 __rle_decode_vram(u16 dst, u8 bank, const void* src);

引数

dst

最初の出力バイトを置く16ビットのCPUアドレスで、0x8000〜0x9FFFの範囲を指定します。末尾が0x9FFFを越えないよう、連続した領域を確保してください。たとえば0x8860は、符号なしのタイル番号を使う配置ではタイル134の位置です。関数がタイルを割り当てたり、宛先の範囲を検査したりすることはありません。

bank

入力ROMのバンク番号をu8で指定します。カートリッジのマッパーと素材の配置に合う番号・CPUアドレスの組を使ってください。圧縮データの各バイト取得後には、元のROMバンク選択を復元します。展開先のVRAMバンクを選ぶ引数ではなく、MBC5の9ビットのバンク番号も表せません。

src

指定したバンク内の圧縮列を指すCPUポインターです。アドレスは進みますが、バンク番号は変わりません。終端0までの列全体を読み取り可能な配置に収めてください。0x7FFFを越えても、次のROMバンクの先頭へ自動的に続くことはありません。

戻り値

書き込んだバイト数をu16で返します。先頭の個数が0なら何も展開せず0を返し、終端そのものはコピーしません。展開長はu16で表せる範囲に収めてください。戻り値に別のエラーコードはなく、タイルの枚数でもありません。

使用条件と制限

文字列専用ではなく、バイト単位の形式です。たとえば{3, 0x12, 2, 0xA5, 0}は、16進表記で12 12 12 A5 A5の5バイトに展開されます。個数が0でない組の値が0でも、通常のデータとして扱い、終端とは見なしません。

圧縮後のサイズ、展開先の容量、不正データの扱いを指定する引数はありません。素材の作成時や読み込み時に形式を確認し、終端0を付けて、展開長を計算してから呼び出してください。不正な列や終端のない列では、入力・出力とも領域外へ進む可能性があります。

LCD停止中は直接書き込みます。表示中は出力1バイトごとに書き込み可能なLCDモードを待ち、その待機・書き込みをDI/EIで囲みます。そのため長い列は複数フレームにまたがる場合があります。キューに登録する非同期転送ではなく、完了まで戻らない呼び出しです。LCD表示中の経路は各バイト後に割り込みを有効にするため、呼び出し前の割り込み無効状態は保持しません。

CGBでは、VBKで現在選択されているVRAMバンクへ書き込み、その選択を変更しません。サンプルのタイルデータにはVBK 0を使います。出力にVBK 1を選んだ場合も、bank引数は入力ROMの指定です。CGBのパレットやマップ属性を管理する関数ではありません。

バンク付きの読み取り中に、割り込み処理でROMの配置を変更したり、同じ遠距離コピーの補助処理を重ねて呼んだりしないでください。補助処理はバンク管理用の状態を共有します。通常ポインター版では、展開関数と入力データを同時に読める配置にします。関数のバンクへの切り替えで、入力ポインターが指すデータが隠れないようにしてください。

rle_decodeとrle_decode_farはrpg.hで宣言され、lib/rle.cに実装されています。ゲームと一緒に実装を一度だけコンパイルしてください。完全なサンプルでは呼び出し側と展開関数を共通バンクに置くため、#pragma bank 0の後でrle.cをインクルードしています。このサンプルのビルドでは、rle.cをさらに別の入力ファイルとして渡さないでください。__rle_decode_vram自体にはライブラリの実装ファイルは不要です。

使用例

c
vram_count=__rle_decode_vram(0x8860,2,banked_rle); // Expand directly to 48 bytes of tile data.
if(*rle_bank_marker!=0xA1)failures++;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

バンク2の圧縮列を、RAMの展開用バッファを使わずVRAMの0x8860へ直接展開するサンプルです。DIRECT VRAMは048になります。最下段はCGBで青く表示し、RAM経由の段と同じ3種類のタイルが並ぶ必要があります。この表示用サンプルではLCDを止めて転送します。

各段は左から、8×8の塗りつぶし正方形、8×8で太さ1ピクセルの枠、8×8タイルの左半分を埋める4×8の長方形です。タイル列は2・5・8で、ピクセルのx座標では16・40・64です。三段の上端のy座標は32・72・112です。DMGは黒、CGBは段ごとに赤・緑・青で表示します。FAILEDは000になる必要があります。表示数値はバイト数で、1枚16バイトのGBタイル3枚には48バイトの展開先が必要です。

検証は42条件で、空の列、1バイトの繰り返し、値0、255バイトの繰り返し、出力のページ境界を越える510バイト、入力のページ境界を越える列を扱います。宛先と保護領域の528バイト全体、展開長、ROMバンクの復元を照合します。直接VRAM版はDMG・CGBのLCD表示中と停止中、およびCGBのVBK 1も対象です。2枚の表示用サンプル画像はエミュレータの240フレーム後に確認します。不正な素材、割り込みからの並行利用、実機は検証対象に含みません。

__rle_decode_vram: DMG
DMG
__rle_decode_vram: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\rle_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\rle_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:191

宣言の注記(原文)
Compiler intrinsic, separate from the RAM rle_decode helpers below; it is not a bounded-stream API.

kitaqgb/kitaqgb/CodeGenerator.cs:8673

csharp
if (funcName == "__rle_decode_vram")
            {
                if (args.Length != 3) Error(expr, "__rle_decode_vram(dst, bank, src) expects 3 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoA(args[1]); EmitPushAAsWord(); // bank
                CompileIntoHL(args[2]); EmitAsm("PUSH_HL"); // src
                EmitAsm("ADD_SP_IMM", new AsmOperand(-4, AddressMode.Relative)); // total(2) + scratch(2)

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("XOR_A");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_HL_A");

                AsmOperand loop = MakeUniqueLabel("rlevram_loop");
                // Keep total, scratch, source, bank and destination in stack slots across repeated far-memory calls.
                AsmOperand countDone = MakeUniqueLabel("rlevram_count_inc_done");
                AsmOperand valueDone = MakeUniqueLabel("rlevram_value_inc_done");
                AsmOperand finished = MakeUniqueLabel("rlevram_finished");

                EmitLabel(loop);

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("PUSH_HL"); // &scratch

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // src

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // bank

                EmitAsm("POP_HL"); // &scratch
                EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
                EmitCallFarMemcpyHelper();

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // count
                EmitAsm("OR_A");
                EmitAsm("JP_Z", finished);
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_HL_A"); // save count in scratch+1

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitAsm("JR_NC", countDone);
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitLabel(countDone);

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("PUSH_HL"); // &scratch

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // src

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // bank

                EmitAsm("POP_HL"); // &scratch
                EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
                EmitCallFarMemcpyHelper();

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // value

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A");
                EmitAsm("LD_B_IMM", new AsmOperand(0, AddressMode.Immediate)); // count

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E"); // dst
                // Loading dst used D for its high byte. Recover the run value
                // from stack scratch while preserving the completed HL pointer.
                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("POP_HL");

                // Use the LCD-aware fill loop for each decoded run, then store its advanced destination pointer.
                EmitVramMemsetLoop(true, "rlevram_fill");

                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                EmitAsm("POP_DE");
                EmitAsm("LD_A_E");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_D");
                EmitAsm("LD_HL_A"); // save dst

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // count
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_B");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A"); // total += count

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitAsm("JR_NC", valueDone);
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitLabel(valueDone);

                EmitAsm("JP", loop);

                EmitLabel(finished);
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitAsm("ADD_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                LastCallReturnsHL = true;
                return;
            }
__rng8

共有するGBの疑似乱数生成器を1回進め、更新後の上位バイトを返します。rng.cのラッパー関数をリンクせずに1バイトの乱数を取得するときに使う組み込み関数です。

書式

c
u8 __rng8();

戻り値

共有生成器を1回進めて得たu8の値(0〜255)です。これは型と値の範囲を示しており、各バイト値が同じ確率で出るという意味ではありません。

使用条件と制限

GBの更新では、16ビット状態に0x5D17を加えて桁あふれを捨て、更新後の下位バイトと上位バイトの排他的論理和を新たな下位バイトにします。上位バイトを1ビット左回転した値にその下位バイトを加え、更新した2バイトを保存して上位バイトを返します。

状態0も受け付けます。次のバイト取得でGB生成器が0x5AA5に置き換えてから更新するため、__rng_seed(0)の直後の__rng8()は122を返します。毎回シードを設定し直すと、乱数性が増すのではなく列の先頭に戻ります。

各呼び出しは持続する状態を共有します。再現には、同じ対象機種・シード・呼び出し順が必要で、ほかの補助関数による取得もその順番に含まれます。シード設定と取得は一つの実行箇所にまとめてください。今回の確認は、割り込み処理と並行して安全に使えることを保証するものではありません。

GBとFCは異なるアルゴリズムを使い、同じシードでも同じ列になることを保証しません。ゲーム処理向けの決定的な生成器であり、暗号用の乱数源ではありません。検証対象は実装の動作で、統計的な品質や周期ではありません。

ライブラリ関数を使う場合はrpg.hをインクルードし、ゲームのソースと一緒にlib/rng.cをコンパイルします。__rng_seedと__rng8自体はコンパイラが生成するため、この2つだけを使うプログラムにはrng.cは不要です。

使用例

c
__rng_seed(0x1234);
first=__rng8();  // 2: the first draw from this seed on GB.
second=__rng8(); // 34: a second draw advances the same shared state.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

取得のたびに列が進むことを確かめるサンプルです。シード0x1234の後の2回の__rng8で、FIRSTは00002、SECONDは00034になります。同じシードを設定し直すと00002から再開します。

完全なプログラムでは、白地に10進数の結果を表示します。DMGでは黒文字、CGBでは青文字です。FAILEDは00000になる必要があります。画像はこのサンプルのエミュレータ実行結果です。

各条件で32個の出力と、その後の8回のバイト取得を独立したホスト側モデルと照合しました。GBは93条件、FCは18条件で、定数・実行時のシード指定や、GBの範囲・確率・重みの境界条件を含みます。サンプル画面はエミュレータで240フレーム後に確認しました。実機や統計的な品質は未検証です。

__rng8: DMG
DMG
__rng8: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\rng_values.c .\kitaqgb\lib\rng.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\rng_values.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:149

kitaqgb/kitaqgb/CodeGenerator.cs:8881

csharp
if (funcName == "__rng8")
            {
                if (args.Length != 0) Error(expr, "__rng8() expects 0 arguments");

                EmitLoadA(RngStateLoVar);
                EmitAsm("LD_B_A");
                EmitLoadA(RngStateHiVar);
                EmitAsm("LD_C_A");

                // Zero state fallback for deterministic startup.
                AsmOperand rngSeeded = MakeUniqueLabel("rng_seeded");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_NZ", rngSeeded);
                EmitAsm("LD_A_IMM", new AsmOperand(0xA5, AddressMode.Immediate));
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_IMM", new AsmOperand(0x5A, AddressMode.Immediate));
                EmitAsm("LD_C_A");
                EmitLabel(rngSeeded);

                // 16-bit mixed add/xor/rotate step.
                EmitAsm("LD_A_B");
                EmitAsm("ADD_A_IMM", new AsmOperand(0x17, AddressMode.Immediate));
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_C");
                EmitAsm("ADC_IMM", new AsmOperand(0x5D, AddressMode.Immediate));
                EmitAsm("LD_C_A");
                EmitAsm("LD_A_B");
                EmitAsm("XOR_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_C");
                EmitAsm("RLCA");
                EmitAsm("ADD_B");
                EmitAsm("LD_C_A");

                EmitAsm("LD_A_B");
                EmitStoreA(RngStateLoVar);
                EmitAsm("LD_A_C");
                EmitStoreA(RngStateHiVar);
                EmitAsm("LD_A_C");
                return;
            }
__rng_seed

GBの乱数生成器が共有する16ビットの状態を設定します。デバッグで同じ乱数列を再現したいときは、固定したシードを指定します。この関数自体は乱数を取り出しません。

書式

c
void __rng_seed(u16 seed);

引数

seed

0〜65535の符号なし16ビットのシードです。指定した2バイトをそのまま保存します。再現したいゲーム処理の乱数取得を始める前に設定し、使用した値を記録してください。

戻り値

ありません(void)。

使用条件と制限

状態0も受け付けます。次のバイト取得でGB生成器が0x5AA5に置き換えてから更新するため、__rng_seed(0)の直後の__rng8()は122を返します。毎回シードを設定し直すと、乱数性が増すのではなく列の先頭に戻ります。

各呼び出しは持続する状態を共有します。再現には、同じ対象機種・シード・呼び出し順が必要で、ほかの補助関数による取得もその順番に含まれます。シード設定と取得は一つの実行箇所にまとめてください。今回の確認は、割り込み処理と並行して安全に使えることを保証するものではありません。

GBとFCは異なるアルゴリズムを使い、同じシードでも同じ列になることを保証しません。ゲーム処理向けの決定的な生成器であり、暗号用の乱数源ではありません。検証対象は実装の動作で、統計的な品質や周期ではありません。

ライブラリ関数を使う場合はrpg.hをインクルードし、ゲームのソースと一緒にlib/rng.cをコンパイルします。__rng_seedと__rng8自体はコンパイラが生成するため、この2つだけを使うプログラムにはrng.cは不要です。

使用例

c
__rng_seed(0x1234);
repeated=__rng8(); // Reseeding reproduces the first byte, 2.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

テスト用の乱数列を初めから再現するサンプルです。シード0x1234でFIRSTとRESEEDEDはともに00002、SECONDは00034になります。ライブラリのシード設定も同じ状態を初期化するため、RNG8も00002です。

完全なプログラムでは、白地に10進数の結果を表示します。DMGでは黒文字、CGBでは青文字です。FAILEDは00000になる必要があります。画像はこのサンプルのエミュレータ実行結果です。

各条件で32個の出力と、その後の8回のバイト取得を独立したホスト側モデルと照合しました。GBは93条件、FCは18条件で、定数・実行時のシード指定や、GBの範囲・確率・重みの境界条件を含みます。サンプル画面はエミュレータで240フレーム後に確認しました。実機や統計的な品質は未検証です。

__rng_seed: DMG
DMG
__rng_seed: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\rng_values.c .\kitaqgb\lib\rng.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\rng_values.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:150

kitaqgb/kitaqgb/CodeGenerator.cs:8868

csharp
if (funcName == "__rng_seed")
            {
                if (args.Length != 1) Error(expr, "__rng_seed(seed) expects 1 argument");

                CompileIntoHL(args[0]);
                EmitAsm("LD_A_L");
                EmitStoreA(RngStateLoVar);
                EmitAsm("LD_A_H");
                EmitStoreA(RngStateHiVar);
                return;
            }
__scroll_bg_set

背景のX/YスクロールをSCX/SCYへ即時設定し、内部の現在値・保留値も同期します。

書式

c
void __scroll_bg_set(u8 scx, u8 scy);

引数

scx

Xスクロール0~255ピクセル。

scy

Yスクロール0~255ピクセル。

戻り値

戻り値はありません。

使用条件と制限

保留中の背景更新フラグを消します。画面途中の書き込みはその後の走査へ影響するので、通常はVBlankで呼びます。

使用例

c
__scroll_bg_set(19,27);result[5]=*((u8*)0xFF43);result[6]=*((u8*)0xFF42);__scroll_bg_set(0,0);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__scroll_bg_set: DMG
DMG
__scroll_bg_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:151

kitaqgb/kitaqgb/CodeGenerator.cs:7507

csharp
if (funcName == "__scroll_bg_set")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_set(scx, scy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX

                EmitAsm("LD_A_B");
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__settile_col

src の連続するタイル番号を、LCDCで選んだBGマップの (x,y) から下へ len 個コピーします。コピー元は1バイト、コピー先は32バイトずつ進みます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile_col(u8 x, u8 y, const u8* src, u8 len);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

len

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

横方向なら x+len <= 32、縦方向なら y+len <= 32 に収めてください。マップの端で座標を折り返す処理はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_col
// Copy `len` successive tile numbers from `src` vertically, starting at `(x,y)` in the
// LCDC-selected BG map. The source advances one byte and the destination 32 bytes per
// tile. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_col(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9CA2, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
B
C
__settile_col: DMG
DMG
__settile_col: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_col.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_col.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/rpg.h:156

__settile_rect

LCDCで選択したBGマップの幅 w、高さ h の矩形を、同じタイル番号で埋めます。行の間隔は32バイトです。画像の配列をコピーする処理ではなく、同じタイルを繰り返し配置します。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

w, h

8×8タイル単位の幅と高さです。x+w <= 32、y+h <= 32 に収めてください。いずれかが0なら書き込みません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_rect
// Fill a `w` by `h` rectangle in the LCDC-selected BG map with one tile number. Rows are
// 32 bytes apart; this repeats a tile, rather than copying an image array. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_rect(2, 3, 3, 2, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C84, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C65, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
AAA
AAA
__settile_rect: DMG
DMG
__settile_rect: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_rect.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_rect.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/rpg.h:154

__settile_row

src の連続するタイル番号を、LCDCで選んだBGマップの (x,y) から右へ len 個コピーします。コピー元・先は1タイルごとに1バイト進みます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile_row(u8 x, u8 y, const u8* src, u8 len);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

len

バイト数で、0~255です。0なら何も転送しません。転送数以上の読み取り可能なデータを用意してください。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

横方向なら x+len <= 32、縦方向なら y+len <= 32 に収めてください。マップの端で座標を折り返す処理はありません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_row
// Copy `len` successive tile numbers from `src` horizontally, starting at `(x,y)` in the
// LCDC-selected BG map. Source and destination advance by one byte per tile. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_row(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C64, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C82, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
__settile_row: DMG
DMG
__settile_row: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_row.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_row.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/rpg.h:155

__settile_xy

タイルマップの1セルにタイル番号を書き込みます。登録済みのタイル画像を選ぶ操作であり、画像の画素データは変更しません。

LCDCのビット3で選択中のBGマップへ書き込みます。ビットが0なら 0x9800、1なら 0x9C00 を先頭とし、32*y + x を加えます。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settile_xy(u8 x, u8 y, u8 tile);

引数

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

tile

0~255のタイル番号です。番号から画像データを参照する方式はLCDCのビット4で決まります。VRAMのバイトアドレスではありません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settile_xy
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 3 selects the background map: zero uses `0x9800`, one uses
// `0x9C00`. The destination is that base plus `32*y + x`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_xy(2, 3, 'A');
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9862, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
A
__settile_xy: DMG
DMG
__settile_xy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_xy.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_xy.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/rpg.h:153

__settilemap_rect

隙間なく並んだ w*h 個のタイル番号を、base で指定したマップの (x,y) から矩形としてコピーします。コピー元は1行 w バイト、コピー先の行間隔は32バイトです。

必要に応じてVRAMへ書き込めるタイミングを待ちます。LCD動作中の経路では割り込みを禁止・許可するため、呼び出し前の割り込み状態の保持や、1回のVBlank内での完了を保証するものではありません。

書式

c
void __settilemap_rect(u16 base, u8 x, u8 y, u8 w, u8 h, const u8* src);

引数

base

base にマップ先頭の 0x9800 または 0x9C00 を指定します。書き込み先は base + 32*y + x です。

x, y

8×8タイル単位の座標で、それぞれ0~31です。書き込み範囲全体を32×32のマップ内に収めてください。座標の切り詰めは行いません。

w, h

8×8タイル単位の幅と高さです。x+w <= 32、y+h <= 32 に収めてください。いずれかが0なら書き込みません。

src

RAMまたは参照可能なROMバンク上に、隙間なく並べたデータです。タイル列にはタイル番号、属性列にはCGB属性を格納します。その場でコピーし、ポインタは保存しません。

戻り値

ありません(void)。

使用条件と制限

タイル番号の書き込みには現在選択中のVRAMバンクを使います。CGBでは通常バンク0を選んでください。この関数自体はバンクを切り替えません。

使用例は gb_common.h を使います。m_init() は付属のASCIIフォントを文字コードと同じタイル番号に読み込みます。m_wait(); M_LCDC=0; はVBlank中にLCDを停止し、待機なしの転送を可能にします。最後のLCDC値で表示するマップを選びます。

使用例

c
// __settilemap_rect
// Copy a tightly packed `w*h` array of tile numbers to a rectangle at `(x,y)` in the map
// selected by `base`. Source rows have `w` bytes; destination rows have a 32-byte
// stride. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C','D','E','F'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilemap_rect(0x9C00, 2, 3, 3, 2, example_tiles);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C84, 0) != 'F') { failures++; }
    if (tile_example_read(0x9C65, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

期待する結果

選択したマップに、書き込んだ文字や塗りつぶした範囲が表示されます。プログラムは書き込み先のタイル番号と、CGBでは属性も読み戻して比較し、すべて一致すると FAILED CHECKS 000 と表示します。両マップは最初に0で埋め、対象の呼び出し前にはBG・ウィンドウを 0x9C00 に設定するため、固定マップと選択マップの違いも確認できます。書き込みはLCDを停止して行います。LCD動作中のタイミングを測定するサンプルではありません。

CGBでは、サンプルの文字と結果表示が白地に青で表示されます。準備処理でパレット0の描画色を青に設定し、タイルマップの属性は変更していません。DMGでは同じ文字が白地に黒で表示されます。

期待画像:次のタイル配置が、タイル座標 (x=2, y=3)、ピクセル座標 (16,24) から表示されます。1文字が8×8の1タイルです。上から下、左から右に読み、周囲のタイルは空白になります。

text
ABC
DEF
__settilemap_rect: DMG
DMG
__settilemap_rect: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilemap_rect.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilemap_rect.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
実装とソース

kitaqgb/lib/rpg.h:158

__tile_addr

32タイル幅のマップで、1セルのCPUアドレスを計算します。

書式

c
u16 __tile_addr(u16 base, u8 x, u8 y);

引数

base

背景/ウィンドウのマップ先頭。通常$9800/$9C00。

x

列0~31を指定します。

y

行0~31を指定します。

戻り値

base + y*32 + xの下位16ビット。メモリの読み書きはしません。

使用例

c
result[4]=__tile_addr(0x9800,3,2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__tile_addr: DMG
DMG
__tile_addr: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:180

kitaqgb/kitaqgb/CodeGenerator.cs:8355

csharp
if (funcName == "__tile_addr")
            {
                if (args.Length != 3) Error(expr, "__tile_addr(base, x, y) expects 3 arguments");

                EmitTileMapAddressIntoHL(args[0], args[1], args[2]);
                LastCallReturnsHL = true;
                return;
            }
__vram_copy_dma

__vram_copy_dmaは、コンパイラで__vram_memcpyと同じ処理に展開される別名です。長さの単位は同じくバイトで、即時のCPU転送とLCDへのアクセス条件も同じです。

srcからdstへlenバイトを、アドレスが増える順に直ちにコピーします。転送が終わってから戻り、キューへの登録は行いません。転送元と転送先は1バイトずつ進み、タイルマップの行間隔やタイル形式の変換は扱いません。

書式

c
void __vram_copy_dma(u16 dst, const void* src, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

src

転送元のバイトポインターです。呼び出し中は転送元のメモリーとバンクの対応を有効に保ってください。データはその場で読み、後のflush用に保持しません。転送元バンクの自動切り替えも行いません。前方へのコピーなので、memmoveのような重なりへの対応はありません。範囲が重ならないようにしてください。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

この名前でDMAエンジンを選んだり、後のHBlankに処理を予約したりはしません。呼び出し元は完了まで待ちます。LCD動作中は共通のCPUポーリングでモード0(HBlank)または1(VBlank)を待ち、CGBのHDMAは設定しません。

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_copy_dma(0x98A2,strip,3); // This alias also uses CPU writes, not a DMA engine.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_copy_dmaでタイル座標(2,5)、ピクセル座標(16,40)に3バイトのマップデータを書き込みます。24×8の領域に、左から[1,2,3]のパターンが並ぶのが期待結果です。隣接するセルは空白のままです。完成プログラムでは、検証するコピーや埋め込みを1行ずつ並べています。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_copy_dma: DMG
DMG
__vram_copy_dma: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:163

__vram_copy_hblank

__vram_copy_hblankは、コンパイラで__vram_memcpyと同じ処理に展開される別名です。長さの単位は同じくバイトで、即時のCPU転送とLCDへのアクセス条件も同じです。

srcからdstへlenバイトを、アドレスが増える順に直ちにコピーします。転送が終わってから戻り、キューへの登録は行いません。転送元と転送先は1バイトずつ進み、タイルマップの行間隔やタイル形式の変換は扱いません。

書式

c
void __vram_copy_hblank(u16 dst, const void* src, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

src

転送元のバイトポインターです。呼び出し中は転送元のメモリーとバンクの対応を有効に保ってください。データはその場で読み、後のflush用に保持しません。転送元バンクの自動切り替えも行いません。前方へのコピーなので、memmoveのような重なりへの対応はありません。範囲が重ならないようにしてください。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

この名前でDMAエンジンを選んだり、後のHBlankに処理を予約したりはしません。呼び出し元は完了まで待ちます。LCD動作中は共通のCPUポーリングでモード0(HBlank)または1(VBlank)を待ち、CGBのHDMAは設定しません。

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_copy_hblank(0x9882,strip,3); // Immediate safe copy; no scheduled HBlank DMA.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_copy_hblankでタイル座標(2,4)、ピクセル座標(16,32)に3バイトのマップデータを書き込みます。24×8の領域に、左から[1,2,3]のパターンが並ぶのが期待結果です。隣接するセルは空白のままです。完成プログラムでは、検証するコピーや埋め込みを1行ずつ並べています。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_copy_hblank: DMG
DMG
__vram_copy_hblank: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:162

__vram_fill

__vram_fillは、コンパイラで__vram_memsetと同じ処理に展開される別名です。長さの単位は同じくバイトで、即時のCPU転送とLCDへのアクセス条件も同じです。

dstから連続するlenバイトを、1バイトのvalueで直ちに埋めます。繰り返すのは1バイトで、タイルパターン全体ではありません。マップのバイトを埋める場合はタイル番号を繰り返し、パターンのバイトを埋める場合は画素のビットプレーンを書き換えます。

書式

c
void __vram_fill(u16 dst, u8 value, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

value

0〜255の1バイトを繰り返し書きます。画面での意味は、転送先がタイル番号、画素のビットプレーン、その他のバイト列のどれかによって変わります。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_fill(0x9902,1,computed_length());

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_fillでタイル座標(2,8)、ピクセル座標(16,64)から3バイトのマップをタイル番号1で埋めます。塗りつぶした24×8の横棒と、空白の隣接セルが期待結果です。長さは変数や関数の戻り値から計算し、その場合も3バイトすべてに値1が入ることを確認します。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_fill: DMG
DMG
__vram_fill: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:161

__vram_memcpy

srcからdstへlenバイトを、アドレスが増える順に直ちにコピーします。転送が終わってから戻り、キューへの登録は行いません。転送元と転送先は1バイトずつ進み、タイルマップの行間隔やタイル形式の変換は扱いません。

書式

c
void __vram_memcpy(u16 dst, const void* src, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

src

転送元のバイトポインターです。呼び出し中は転送元のメモリーとバンクの対応を有効に保ってください。データはその場で読み、後のflush用に保持しません。転送元バンクの自動切り替えも行いません。前方へのコピーなので、memmoveのような重なりへの対応はありません。範囲が重ならないようにしてください。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_memcpy(0x9842,strip,length_base+length_extra); // Three map bytes at tile (2,2).

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_memcpyでタイル座標(2,2)、ピクセル座標(16,16)に3バイトのマップデータを書き込みます。24×8の領域に、左から[1,2,3]のパターンが並ぶのが期待結果です。隣接するセルは空白のままです。完成プログラムでは、検証するコピーや埋め込みを1行ずつ並べています。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_memcpy: DMG
DMG
__vram_memcpy: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:146

__vram_memset

dstから連続するlenバイトを、1バイトのvalueで直ちに埋めます。繰り返すのは1バイトで、タイルパターン全体ではありません。マップのバイトを埋める場合はタイル番号を繰り返し、パターンのバイトを埋める場合は画素のビットプレーンを書き換えます。

書式

c
void __vram_memset(u16 dst, u8 value, u16 len);

引数

dst

転送先の先頭バイトアドレスで、通常はVRAMの0x8000~0x9FFFです。全範囲が有効になるようにしてください。CGBでは現在のVRAMバンクを使い、マップ領域のバンク0にはタイル番号、バンク1には属性があります。LCDCによるマップの選択、行末での折り返し、VRAMの確保は行いません。

value

0〜255の1バイトを繰り返し書きます。画面での意味は、転送先がタイル番号、画素のビットプレーン、その他のバイト列のどれかによって変わります。

len

連続したバイト数を0〜65535で指定します。タイル数ではありません。全範囲が有効な転送元と書き込み可能な転送先に収まるようにしてください。範囲チェックや16ビットアドレスの折り返し防止は行いません。

戻り値

ありません(void)。

使用条件と制限

LCD動作中は、各バイトの書き込み前にDIで割り込みを禁止してSTATのモード0または1を待ち、書き込み後にEIを実行します。LCD停止中は通常のCPUループを使います。処理は複数フレームにまたがる場合があり、呼び出し前の割り込み禁止状態は保持しません。DMAレジスタの設定も行いません。長さ0ではデータを転送しません。

使用例

c
__vram_memset(0x98E2,1,length_base+length_extra); // Three solid tiles, not three tile patterns.

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

この例では__vram_memsetでタイル座標(2,7)、ピクセル座標(16,56)から3バイトのマップをタイル番号1で埋めます。塗りつぶした24×8の横棒と、空白の隣接セルが期待結果です。長さは変数や関数の戻り値から計算し、その場合も3バイトすべてに値1が入ることを確認します。

付属の独自画像では、タイル1が8×8全体の塗りつぶし、2が左4列の塗りつぶし、3が幅1ピクセルから8ピクセルへ1行ずつ広がる左寄せの三角形です。DMGでは白地に黒の図形を表示し、CGBでは処理ごとに色を分けて文字を黒に保ちます。

ピクセル座標(16,88)の正方形は、安全版・待機なし版の長さ0の呼び出し後も残ります。LCD動作中の安全なコピーと埋め込みでは、(16,96)と(32,96)に正方形を追加します。FAILED CHECKSの期待値は000です。エミュレータの検証では、数値だけでなく描画された画素も比較します。この例のCGBはバンク0を使い、他のバンクや実機はここでは検証していません。

CGBでは、MEMCPYからFILL UNSAFEまでの各行を、赤・青・緑の順で繰り返して表示します。ZERO COUNTの正方形は赤です。LCD ONの正方形は左が青、右が緑で、文字は黒のままです。検証では、描画した各画素がこれらの色になっていることも確認します。

__vram_memset: DMG
DMG
__vram_memset: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
実装とソース

kitaqgb/lib/rpg.h:147

__wait_vblank

LCDが動作していれば、現在のVBlankを抜けて次のLY≧144まで待ちます。

書式

c
void __wait_vblank();

戻り値

戻り値はありません。

使用条件と制限

LCD停止中は即座に戻ります。割り込みの有効化やフレームカウンターの更新は行わず、LYをポーリングします。

使用例

c
__wait_vblank();result[10]=*((u8*)0xFF44);M_LCDC=0;__wait_vblank();__wait_ly(200);result[11]=1;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

背景とウィンドウのマップ切り替え、スクロール、ネスト、走査線待ちを学びます。先頭アドレスは38912/39936、トークンは0/1、待ったLYは80/144です。LCD停止中の待ちが戻ることも確認します。

__wait_vblank: DMG
DMG
__wait_vblank: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:138

kitaqgb/kitaqgb/CodeGenerator.cs:7451

csharp
if (funcName == "__wait_vblank")
            {
                if (args.Length != 0) Error(expr, "__wait_vblank() expects 0 arguments");

                AsmOperand wbRet = MakeUniqueLabel("waitvb_ret");
                AsmOperand wbExitVblank = MakeUniqueLabel("waitvb_exit");
                AsmOperand wbEnterVblank = MakeUniqueLabel("waitvb_enter");

                // If LCD is off, return immediately.
                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
                EmitAsm("JR_Z", wbRet);

                // while (LY >= 144) {}
                EmitLabel(wbExitVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);
                EmitAsm("JR", wbExitVblank);

                // while (LY < 144) {}
                EmitLabel(wbEnterVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);

                EmitLabel(wbRet);
                return;
            }
__xy_in_rect

点が左上を含み右端・下端を含まない矩形内にあるか調べます。

書式

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

引数

x

点のX座標0~255。

y

点のY座標0~255。

rx

矩形左端0~255。

ry

矩形上端0~255。

rw

幅0~255。

rh

高さ0~255。

戻り値

内部なら1、それ以外は0。幅または高さ0は空です。

使用条件と制限

差分で比較するのでrx+rwが255を超えても左端へ周回しません。

使用例

c
result[0]=__xy_in_rect(10,20,10,20,3,2);result[1]=__xy_in_rect(13,20,10,20,3,2);result[2]=__xy_in_rect(9,20,10,20,3,2);result[3]=__xy_in_rect(255,1,250,0,10,2);result[4]=__xy_in_rect(10,20,10,20,0,2);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

矩形の内部・端・外側・幅0を試します。INSIDE=1、RIGHT EDGE=0、LEFT EDGE=0、HIGH X=1です。距離15、距離の周回254、配列位置64と65280を確認します。

__xy_in_rect: DMG
DMG
__xy_in_rect: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
実装とソース

kitaqgb/lib/rpg.h:182

kitaqgb/kitaqgb/CodeGenerator.cs:8436

csharp
if (funcName == "__xy_in_rect")
            {
                if (args.Length != 6) Error(expr, "__xy_in_rect(x, y, rx, ry, rw, rh) expects 6 arguments");

                Expr[] foldedRectArgs = args.Select(FoldConstants).ToArray();
                if (foldedRectArgs[0].Match(Tag.Integer, out int constX) &&
                    foldedRectArgs[1].Match(Tag.Integer, out int constY) &&
                    foldedRectArgs[2].Match(Tag.Integer, out int constRx) &&
                    foldedRectArgs[3].Match(Tag.Integer, out int constRy) &&
                    foldedRectArgs[4].Match(Tag.Integer, out int constRw) &&
                    foldedRectArgs[5].Match(Tag.Integer, out int constRh))
                {
                    int x = constX & 0xFF;
                    int y = constY & 0xFF;
                    int rx = constRx & 0xFF;
                    int ry = constRy & 0xFF;
                    int rw = constRw & 0xFF;
                    int rh = constRh & 0xFF;
                    bool inside = x >= rx && y >= ry && (x - rx) < rw && (y - ry) < rh;
                    if (inside) EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    else EmitAsm("XOR_A");
                    return;
                }

                EmitPushExprAsWord(args[0]); // x
                EmitPushExprAsWord(args[1]); // y
                EmitPushExprAsWord(args[2]); // rx
                EmitPushExprAsWord(args[3]); // ry
                EmitPushExprAsWord(args[4]); // rw
                EmitPushExprAsWord(args[5]); // rh

                AsmOperand ret0 = MakeUniqueLabel("xyrect_ret0");
                AsmOperand done = MakeUniqueLabel("xyrect_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rx
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rw
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // y
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // ry
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rh
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR", done);
                EmitLabel(ret0);
                EmitAsm("XOR_A");
                EmitLabel(done);
                EmitAsm("ADD_SP_IMM", new AsmOperand(12, AddressMode.Relative));
                return;
            }

scroll.h — 背景・ウィンドウ・分割スクロール

背景とウィンドウの即時・保留スクロール、および最大8件の走査線分割を扱います。取得関数の管理値、実際のレジスター値、保留値の違いに注意してください。

__scroll_bg_add

管理中の背景座標に符号付きの移動量を加え、即時反映します。

書式

c
void __scroll_bg_add(s8 dx, s8 dy);

引数

dx

横方向の符号付きピクセル差分、−128~127。

dy

縦方向の符号付きピクセル差分、−128~127。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。 計算結果は256で周回します。加算元は保留値や分割時の一時レジスター値ではありません。

使用例

c
__scroll_bg_add(-20,3);result[22]=M_SCX;result[23]=M_SCY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_add: DMG
DMG
__scroll_bg_add: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_add: DMG
DMG
__scroll_bg_add: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:37

kitaqgb/kitaqgb/CodeGenerator.cs:7835

csharp
if (funcName == "__scroll_bg_add")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_add(dx, dy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_HL");
                EmitAsm("LD_B_L");

                EmitLoadA(ScrollBgXCurVar);
                EmitAsm("ADD_B");
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX

                EmitLoadA(ScrollBgYCurVar);
                EmitAsm("ADD_C");
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_set_buffered

背景の横・縦スクロールを次回反映用としてまとめて保存します。

書式

c
void __scroll_bg_set_buffered(u8 scx, u8 scy);

引数

scx

背景の横スクロール。0~255ピクセルで、増やすと背景が左へ移動します。

scy

背景の縦スクロール。0~255ピクセルで、増やすと背景が上へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

管理中の現在値とレジスターは変えません。__scroll_flush または Scroll_Flush が必要です。バッファーへの設定そのものはVBlankを待ちません。

使用例

c
__scroll_bg_set_buffered(16,24);result[14]=M_SCX;result[15]=__scroll_bg_x_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_set_buffered: DMG
DMG
__scroll_bg_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_set_buffered: DMG
DMG
__scroll_bg_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:22

kitaqgb/kitaqgb/CodeGenerator.cs:7671

csharp
if (funcName == "__scroll_bg_set_buffered")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_set_buffered(scx, scy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LD_A_B");
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_x_get

管理中の背景横スクロール値を取得します。

書式

c
u8 __scroll_bg_x_get();

戻り値

管理中の現在値を0~255の u8 で返します。WXには7ピクセルのオフセットを含みます。

使用条件と制限

ハードウェアレジスターの読み取りではありません。未反映のバッファー、直接レジスターへ書いた値、走査線分割中の一時値は取得しません。

使用例

c
result[6]=__scroll_bg_x_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_x_get: DMG
DMG
__scroll_bg_x_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_x_get: DMG
DMG
__scroll_bg_x_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:13

kitaqgb/kitaqgb/CodeGenerator.cs:7571

csharp
if (funcName == "__scroll_bg_x_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_bg_x_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollBgXCurVar);
                return;
            }
__scroll_bg_x_set

背景の横スクロールだけを即時設定します。

書式

c
void __scroll_bg_x_set(u8 scx);

引数

scx

背景の横スクロール。0~255ピクセルで、増やすと背景が左へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。

使用例

c
__scroll_bg_x_set(250);result[4]=M_SCX;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_x_set: DMG
DMG
__scroll_bg_x_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_x_set: DMG
DMG
__scroll_bg_x_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:11

kitaqgb/kitaqgb/CodeGenerator.cs:7535

csharp
if (funcName == "__scroll_bg_x_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_x_set(scx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
                EmitLoadA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_x_set_buffered

背景の横だけを保留値として置き換えます。

書式

c
void __scroll_bg_x_set_buffered(u8 scx);

引数

scx

背景の横スクロール。0~255ピクセルで、増やすと背景が左へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

もう一方の軸の保留値は保持します。現在値やレジスターはflushまで変わりません。先に座標ペアを初期化してください。

使用例

c
__scroll_bg_x_set_buffered(32);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_x_set_buffered: DMG
DMG
__scroll_bg_x_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_x_set_buffered: DMG
DMG
__scroll_bg_x_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:23

kitaqgb/kitaqgb/CodeGenerator.cs:7693

csharp
if (funcName == "__scroll_bg_x_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_x_set_buffered(scx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_y_get

管理中の背景縦スクロール値を取得します。

書式

c
u8 __scroll_bg_y_get();

戻り値

管理中の現在値を0~255の u8 で返します。WXには7ピクセルのオフセットを含みます。

使用条件と制限

ハードウェアレジスターの読み取りではありません。未反映のバッファー、直接レジスターへ書いた値、走査線分割中の一時値は取得しません。

使用例

c
result[7]=__scroll_bg_y_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_y_get: DMG
DMG
__scroll_bg_y_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_y_get: DMG
DMG
__scroll_bg_y_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:14

kitaqgb/kitaqgb/CodeGenerator.cs:7580

csharp
if (funcName == "__scroll_bg_y_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_bg_y_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollBgYCurVar);
                return;
            }
__scroll_bg_y_set

背景の縦スクロールだけを即時設定します。

書式

c
void __scroll_bg_y_set(u8 scy);

引数

scy

背景の縦スクロール。0~255ピクセルで、増やすと背景が上へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。

使用例

c
__scroll_bg_y_set(2);result[5]=M_SCY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_y_set: DMG
DMG
__scroll_bg_y_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_y_set: DMG
DMG
__scroll_bg_y_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:12

kitaqgb/kitaqgb/CodeGenerator.cs:7553

csharp
if (funcName == "__scroll_bg_y_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_y_set(scy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
                EmitLoadA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_y_set_buffered

背景の縦だけを保留値として置き換えます。

書式

c
void __scroll_bg_y_set_buffered(u8 scy);

引数

scy

背景の縦スクロール。0~255ピクセルで、増やすと背景が上へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

もう一方の軸の保留値は保持します。現在値やレジスターはflushまで変わりません。先に座標ペアを初期化してください。

使用例

c
__scroll_bg_y_set_buffered(40);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_bg_y_set_buffered: DMG
DMG
__scroll_bg_y_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_bg_y_set_buffered: DMG
DMG
__scroll_bg_y_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:24

kitaqgb/kitaqgb/CodeGenerator.cs:7707

csharp
if (funcName == "__scroll_bg_y_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_y_set_buffered(scy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_flush

保留中の背景・ウィンドウ座標を管理中の値とハードウェアへ反映し、保留フラグを消します。

書式

c
void __scroll_flush();

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

保留のない座標ペアは変更しません。割り込み禁止やVBlank待ちは行わないため、呼び出し側で安全なタイミングを確保します。

使用例

c
__scroll_flush();result[18]=M_SCX;result[19]=M_SCY;result[20]=M_WX;result[21]=M_WY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_flush: DMG
DMG
__scroll_flush: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_flush: DMG
DMG
__scroll_flush: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:28

kitaqgb/kitaqgb/CodeGenerator.cs:7771

csharp
if (funcName == "__scroll_flush")
            {
                if (args.Length != 0) Error(expr, "__scroll_flush() expects 0 arguments");
                EnsureScrollState();

                AsmOperand flushSkipBg = MakeUniqueLabel("scroll_flush_skip_bg");
                AsmOperand flushSkipWin = MakeUniqueLabel("scroll_flush_skip_win");

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitAsm("JR_Z", flushSkipBg);
                EmitLoadA(ScrollBgXNextVar);
                EmitStoreA(ScrollBgXCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
                EmitLoadA(ScrollBgYNextVar);
                EmitStoreA(ScrollBgYCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
                EmitLabel(flushSkipBg);

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitAsm("JR_Z", flushSkipWin);
                EmitLoadA(ScrollWinXNextVar);
                EmitStoreA(ScrollWinXCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
                EmitLoadA(ScrollWinYNextVar);
                EmitStoreA(ScrollWinYCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
                EmitLabel(flushSkipWin);

                EmitAsm("XOR_A");
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_split_commit

現在のウィンドウ表示状態を保存し、分割表の最初の走査線イベントを予約します。

書式

c
void __scroll_split_commit();

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

分割表は共有で最大8件です。追加は即時適用もソートも行わず、9件目以降を無視します。安全なフレーム間でreset→push→commitを行い、割り込みハンドラーが完了できる間隔を空けてください。予約行と実際の画素への反映開始は、割り込み遅延により一致するとは限りません。 空でなければIEのVBlank/STATビットを立ててEIを実行します。空の表ではLYC割り込みを停止しますが、IEやCPUの割り込み許可を元に戻しません。

使用例

c
__scroll_split_commit();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

画面を3段に分け、背景Xを0→8→16へ変える例です。24×16矩形が画面の(32,16)・(24,80)・(16,112)に並びます。予約イベントはLY=64と104です。DMGでは黒、CGBでは赤で表示します。

__scroll_split_commit: DMG
DMG
__scroll_split_commit: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_split_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_split_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:33

kitaqgb/kitaqgb/CodeGenerator.cs:7959

csharp
if (funcName == "__scroll_split_commit")
            {
                if (args.Length != 0) Error(expr, "__scroll_split_commit() expects 0 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_commit_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_push

指定走査線で背景スクロールを変更するエントリーを分割表の末尾へ追加します。

書式

c
void __scroll_split_push(u8 ly, u8 scx, u8 scy);

引数

ly

予約する走査線番号。可視イベントは0~143、昇順で追加します。0の先頭エントリーはVBlank中に適用されます。

scx

背景の横スクロール。0~255ピクセルで、増やすと背景が左へ移動します。

scy

背景の縦スクロール。0~255ピクセルで、増やすと背景が上へ移動します。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

分割表は共有で最大8件です。追加は即時適用もソートも行わず、9件目以降を無視します。安全なフレーム間でreset→push→commitを行い、割り込みハンドラーが完了できる間隔を空けてください。予約行と実際の画素への反映開始は、割り込み遅延により一致するとは限りません。

使用例

c
__scroll_split_push(64,8,0);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

画面を3段に分け、背景Xを0→8→16へ変える例です。24×16矩形が画面の(32,16)・(24,80)・(16,112)に並びます。予約イベントはLY=64と104です。DMGでは黒、CGBでは赤で表示します。

__scroll_split_push: DMG
DMG
__scroll_split_push: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_split_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_split_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:31

kitaqgb/kitaqgb/CodeGenerator.cs:7907

csharp
if (funcName == "__scroll_split_push")
            {
                if (args.Length != 3) Error(expr, "__scroll_split_push(ly, scx, scy) expects 3 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;

                CompileIntoA(args[0]);
                EmitStoreA(ScrollSplitTmpLyVar);
                CompileIntoA(args[1]);
                EmitStoreA(ScrollSplitTmpScxVar);
                CompileIntoA(args[2]);
                EmitStoreA(ScrollSplitTmpScyVar);
                EmitAsm("XOR_A");
                EmitStoreA(ScrollSplitTmpWxVar);
                EmitStoreA(ScrollSplitTmpWyVar);
                EmitAsm("LD_A_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate));
                EmitStoreA(ScrollSplitTmpFlagsVar);
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_push_ex

フラグで背景・ウィンドウ・表示状態・背景色0の操作を選ぶ分割エントリーを追加します。

書式

c
void __scroll_split_push_ex(u8 ly, u8 scx, u8 scy, u8 wx, u8 wy, u8 flags);

引数

ly

予約する走査線番号。可視イベントは0~143、昇順で追加します。0の先頭エントリーはVBlank中に適用されます。

scx

背景の横スクロール。0~255ピクセルで、増やすと背景が左へ移動します。

scy

背景の縦スクロール。0~255ピクセルで、増やすと背景が上へ移動します。

wx

ハードウェアのWX値、0~255。画面X座標に7を加えます。画面左端なら7です。

wy

ウィンドウ上端の画面Y座標、0~255。可視画面の行は0~143です。

flags

操作ビット:BG=1、WIN座標=2、表示=4、非表示=8、CGB背景パレット0の色0=16。未知のビットは捨てます。色0指定時はscx/scyをRGB15の下位/上位バイトに使います。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

分割表は共有で最大8件です。追加は即時適用もソートも行わず、9件目以降を無視します。安全なフレーム間でreset→push→commitを行い、割り込みハンドラーが完了できる間隔を空けてください。予約行と実際の画素への反映開始は、割り込み遅延により一致するとは限りません。 表示・非表示の両ビットがあれば表示が優先します。色0はCGB専用で、背景座標との同時指定は同じscx/scyのバイトを両目的に使います。

使用例

c
__scroll_split_push_ex(104,16,0,7,0,KQ_SCROLL_SPLIT_USE_BG|KQ_SCROLL_SPLIT_WIN_HIDE);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

画面を3段に分け、背景Xを0→8→16へ変える例です。24×16矩形が画面の(32,16)・(24,80)・(16,112)に並びます。予約イベントはLY=64と104です。DMGでは黒、CGBでは赤で表示します。

__scroll_split_push_ex: DMG
DMG
__scroll_split_push_ex: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_split_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_split_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:32

kitaqgb/kitaqgb/CodeGenerator.cs:7929

csharp
if (funcName == "__scroll_split_push_ex")
            {
                if (args.Length != 6) Error(expr, "__scroll_split_push_ex(ly, scx, scy, wx, wy, flags) expects 6 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;

                if (TryGetU8Const(args[5], out int splitFlagsConst))
                {
                    int unknownBits = splitFlagsConst & ~ScrollSplitFlagMask;
                    if (unknownBits != 0)
                        Warning(args[5], string.Format("__scroll_split_push_ex() ignores unknown flag bits 0x{0:X2}", unknownBits));
                }

                CompileIntoA(args[0]);
                EmitStoreA(ScrollSplitTmpLyVar);
                CompileIntoA(args[1]);
                EmitStoreA(ScrollSplitTmpScxVar);
                CompileIntoA(args[2]);
                EmitStoreA(ScrollSplitTmpScyVar);
                CompileIntoA(args[3]);
                EmitStoreA(ScrollSplitTmpWxVar);
                CompileIntoA(args[4]);
                EmitStoreA(ScrollSplitTmpWyVar);
                CompileIntoA(args[5]);
                EmitStoreA(ScrollSplitTmpFlagsVar);
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_reset

分割表の件数と進行位置を0にし、分割処理とSTATのLYC割り込みを無効にします。

書式

c
void __scroll_split_reset();

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

分割表は共有で最大8件です。追加は即時適用もソートも行わず、9件目以降を無視します。安全なフレーム間でreset→push→commitを行い、割り込みハンドラーが完了できる間隔を空けてください。予約行と実際の画素への反映開始は、割り込み遅延により一致するとは限りません。 表のバイト列、IE、CPUの割り込み許可状態は消去・解除しません。

使用例

c
__scroll_split_reset();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

画面を3段に分け、背景Xを0→8→16へ変える例です。24×16矩形が画面の(32,16)・(24,80)・(16,112)に並びます。予約イベントはLY=64と104です。DMGでは黒、CGBでは赤で表示します。

__scroll_split_reset: DMG
DMG
__scroll_split_reset: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_split_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_split_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:30

kitaqgb/kitaqgb/CodeGenerator.cs:7897

csharp
if (funcName == "__scroll_split_reset")
            {
                if (args.Length != 0) Error(expr, "__scroll_split_reset() expects 0 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_reset_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_win_add

管理中のウィンドウ座標に符号付きの移動量を加え、即時反映します。

書式

c
void __scroll_win_add(s8 dx, s8 dy);

引数

dx

横方向の符号付きピクセル差分、−128~127。

dy

縦方向の符号付きピクセル差分、−128~127。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。 計算結果は256で周回します。加算元は保留値や分割時の一時レジスター値ではありません。

使用例

c
__scroll_win_add(4,-40);result[24]=M_WX;result[25]=M_WY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_add: DMG
DMG
__scroll_win_add: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_add: DMG
DMG
__scroll_win_add: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:38

kitaqgb/kitaqgb/CodeGenerator.cs:7866

csharp
if (funcName == "__scroll_win_add")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_add(dx, dy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_HL");
                EmitAsm("LD_B_L");

                EmitLoadA(ScrollWinXCurVar);
                EmitAsm("ADD_B");
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX

                EmitLoadA(ScrollWinYCurVar);
                EmitAsm("ADD_C");
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_hide

LCDCのウィンドウ表示ビットと管理中の表示状態を無効にします。

書式

c
void __scroll_win_hide();

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

座標やマップ選択は変更しません。表示にはLCD・背景/ウィンドウ機能が有効で、ウィンドウ座標が可視範囲にある必要があります。

使用例

c
__scroll_win_hide();result[27]=M_LCDC&32;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_hide: DMG
DMG
__scroll_win_hide: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_hide: DMG
DMG
__scroll_win_hide: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:36

kitaqgb/kitaqgb/CodeGenerator.cs:7821

csharp
if (funcName == "__scroll_win_hide")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_hide() expects 0 arguments");
                EnsureScrollState();

                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate));
                EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
                EmitAsm("XOR_A");
                EmitStoreA(ScrollWinVisibleVar);
                return;
            }
__scroll_win_set

ウィンドウのWX・WYを即時設定します。

書式

c
void __scroll_win_set(u8 wx, u8 wy);

引数

wx

ハードウェアのWX値、0~255。画面X座標に7を加えます。画面左端なら7です。

wy

ウィンドウ上端の画面Y座標、0~255。可視画面の行は0~143です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。

使用例

c
__scroll_win_set(15,8);result[8]=M_WX;result[9]=M_WY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_set: DMG
DMG
__scroll_win_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_set: DMG
DMG
__scroll_win_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:16

kitaqgb/kitaqgb/CodeGenerator.cs:7589

csharp
if (funcName == "__scroll_win_set")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_set(wx, wy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX

                EmitAsm("LD_A_B");
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_set_buffered

ウィンドウのWX・WYを次回反映用としてまとめて保存します。

書式

c
void __scroll_win_set_buffered(u8 wx, u8 wy);

引数

wx

ハードウェアのWX値、0~255。画面X座標に7を加えます。画面左端なら7です。

wy

ウィンドウ上端の画面Y座標、0~255。可視画面の行は0~143です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

管理中の現在値とレジスターは変えません。__scroll_flush または Scroll_Flush が必要です。バッファーへの設定そのものはVBlankを待ちません。

使用例

c
__scroll_win_set_buffered(39,32);result[16]=M_WX;result[17]=__scroll_win_x_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_set_buffered: DMG
DMG
__scroll_win_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_set_buffered: DMG
DMG
__scroll_win_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:25

kitaqgb/kitaqgb/CodeGenerator.cs:7721

csharp
if (funcName == "__scroll_win_set_buffered")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_set_buffered(wx, wy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LD_A_B");
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_show

LCDCのウィンドウ表示ビットと管理中の表示状態を有効にします。

書式

c
void __scroll_win_show();

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

座標やマップ選択は変更しません。表示にはLCD・背景/ウィンドウ機能が有効で、ウィンドウ座標が可視範囲にある必要があります。

使用例

c
__scroll_win_show();result[26]=M_LCDC&32;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_show: DMG
DMG
__scroll_win_show: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_show: DMG
DMG
__scroll_win_show: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:35

kitaqgb/kitaqgb/CodeGenerator.cs:7807

csharp
if (funcName == "__scroll_win_show")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_show() expects 0 arguments");
                EnsureScrollState();

                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate));
                EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
                EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
                EmitStoreA(ScrollWinVisibleVar);
                return;
            }
__scroll_win_x_get

管理中のウィンドウWXを取得します。

書式

c
u8 __scroll_win_x_get();

戻り値

管理中の現在値を0~255の u8 で返します。WXには7ピクセルのオフセットを含みます。

使用条件と制限

ハードウェアレジスターの読み取りではありません。未反映のバッファー、直接レジスターへ書いた値、走査線分割中の一時値は取得しません。

使用例

c
result[12]=__scroll_win_x_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_x_get: DMG
DMG
__scroll_win_x_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_x_get: DMG
DMG
__scroll_win_x_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:19

kitaqgb/kitaqgb/CodeGenerator.cs:7653

csharp
if (funcName == "__scroll_win_x_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_x_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollWinXCurVar);
                return;
            }
__scroll_win_x_set

ウィンドウのWXだけを即時設定します。

書式

c
void __scroll_win_x_set(u8 wx);

引数

wx

ハードウェアのWX値、0~255。画面X座標に7を加えます。画面左端なら7です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。

使用例

c
__scroll_win_x_set(23);result[10]=M_WX;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_x_set: DMG
DMG
__scroll_win_x_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_x_set: DMG
DMG
__scroll_win_x_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:17

kitaqgb/kitaqgb/CodeGenerator.cs:7617

csharp
if (funcName == "__scroll_win_x_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_x_set(wx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
                EmitLoadA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_x_set_buffered

ウィンドウのWXだけを保留値として置き換えます。

書式

c
void __scroll_win_x_set_buffered(u8 wx);

引数

wx

ハードウェアのWX値、0~255。画面X座標に7を加えます。画面左端なら7です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

もう一方の軸の保留値は保持します。現在値やレジスターはflushまで変わりません。先に座標ペアを初期化してください。

使用例

c
__scroll_win_x_set_buffered(55);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_x_set_buffered: DMG
DMG
__scroll_win_x_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_x_set_buffered: DMG
DMG
__scroll_win_x_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:26

kitaqgb/kitaqgb/CodeGenerator.cs:7743

csharp
if (funcName == "__scroll_win_x_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_x_set_buffered(wx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_y_get

管理中のウィンドウWYを取得します。

書式

c
u8 __scroll_win_y_get();

戻り値

管理中の現在値を0~255の u8 で返します。WXには7ピクセルのオフセットを含みます。

使用条件と制限

ハードウェアレジスターの読み取りではありません。未反映のバッファー、直接レジスターへ書いた値、走査線分割中の一時値は取得しません。

使用例

c
result[13]=__scroll_win_y_get();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_y_get: DMG
DMG
__scroll_win_y_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_y_get: DMG
DMG
__scroll_win_y_get: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:20

kitaqgb/kitaqgb/CodeGenerator.cs:7662

csharp
if (funcName == "__scroll_win_y_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_y_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollWinYCurVar);
                return;
            }
__scroll_win_y_set

ウィンドウのWYだけを即時設定します。

書式

c
void __scroll_win_y_set(u8 wy);

引数

wy

ウィンドウ上端の画面Y座標、0~255。可視画面の行は0~143です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

待機は行いません。即時設定・加算は対象の座標ペアの保留値を現在値で置き換え、保留フラグを消します。軸単独の即時設定でも、同じペアのもう一方の保留値を取り消します。

使用例

c
__scroll_win_y_set(16);result[11]=M_WY;

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_y_set: DMG
DMG
__scroll_win_y_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_y_set: DMG
DMG
__scroll_win_y_set: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:18

kitaqgb/kitaqgb/CodeGenerator.cs:7635

csharp
if (funcName == "__scroll_win_y_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_y_set(wy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
                EmitLoadA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_y_set_buffered

ウィンドウのWYだけを保留値として置き換えます。

書式

c
void __scroll_win_y_set_buffered(u8 wy);

引数

wy

ウィンドウ上端の画面Y座標、0~255。可視画面の行は0~143です。

戻り値

戻り値はありません。変更される状態と反映時期は、この項目の説明に従います。

使用条件と制限

もう一方の軸の保留値は保持します。現在値やレジスターはflushまで変わりません。先に座標ペアを初期化してください。

使用例

c
__scroll_win_y_set_buffered(48);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

即時値と保留値、符号付き加算と周回を学ぶ例です。保留中の背景Xは250、flush後は16。ウィンドウWXは23から39へ変わり、背景Xへの−20加算は252、WYへの−40加算は248になります。見た目の例では背景矩形と独立したウィンドウ枠も示します。

__scroll_win_y_set_buffered: DMG
DMG
__scroll_win_y_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_scroll_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_scroll_intrinsics.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k

使用例

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

カメラ(8,16)で赤い32×24矩形は画面(24,16)に表示されます。ウィンドウは画面(96,80)に64×64の青い枠、(112,96)に緑の16×16正方形を表示します。DMGではいずれも黒です。

__scroll_win_y_set_buffered: DMG
DMG
__scroll_win_y_set_buffered: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch2_camera_view.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch2_camera_view.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k
実装とソース

kitaqgb/lib/scroll.h:27

kitaqgb/kitaqgb/CodeGenerator.cs:7757

csharp
if (funcName == "__scroll_win_y_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_y_set_buffered(wy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }

sprite.h — OBJの確保、配置、メタスプライト、アニメーション

スプライトは通常8×8または8×16ピクセルの移動可能な表示物体で、OAM(Object Attribute Memory)の項目で指定します。スロットを確保し、作業用OAMへ位置・タイル・属性を設定してから転送します。複数スロットを1物体として扱うのがメタスプライトです。GBは40スロット・1走査線10個、FCは64スロット・1走査線8個が上限です。

GB属性はビット4がDMGのOBP1選択、5・6がX・Y反転、7が背景の色番号0以外より奥への配置です。CGBでは0〜2がOBJパレット、3がタイルデータのVRAMバンクで、DMG用パレット選択ビットはCGB本来の色選択には使いません。

例ではGBの完全版スプライトライブラリを使います。KQ_SPRITE_MINIMAL_RUNTIMEを定義するとsprite.cからsprite_free、sprite_set_pos、sprite_count_used、2つの走査線照会、metasprite_draw、anim_updateが除外されます。これらを使う場合は定義しないでください。

__oam_dma

メモリ上の1ページから160バイトをハードウェアOAM(0xFE00–0xFE9F)へ転送し、40個すべてのスプライト情報を置き換えます。転送が完了してから呼び出し元に戻ります。

書式

c
void __oam_dma(u16 src_ptr);

引数

src_ptr

転送元のバイトアドレスです。上位バイトだけを使うため、0xC480を渡すと0xC400–0xC49Fが転送されます。256バイト境界から160バイトの読み出し可能な領域を用意してください。ハードウェアが対応するページ(0x00–0xDF)を使い、必要なROM・RAMバンクを選択しておきます。コンパイラはアドレスや境界への整列を検査しません。

戻り値

ありません(void)。

使用条件と制限

各スプライトはY、X、タイル番号、属性の4バイトです。画面上の座標は(X−8, Y−16)になります。未使用の項目はYを0にすると非表示にできます。この組み込み関数は生のデータを転送するもので、スプライトの確保やライブラリのカウンタ・シャドーバッファの更新は行いません。

VBlank中、またはLCDを停止した状態で呼び出してください。関数自身はVBlankを待たず、LCDの設定も変更しません。ハードウェア転送にはCPUのマシンサイクルで160サイクルかかり、関数の準備処理と復帰の時間が加わります。

この呼び出しを含むプログラムでは、転送用ルーチンのためにHRAMを8バイト確保します。ルーチンは呼び出すたびに初期化され、DMA中はHRAM上で待機します。その間は割り込み要求を一時的にマスクしますが、IEとIMEの有効・無効状態は維持されます。割り込み処理は転送完了まで遅延します。

使用例

c
// raw_oam is a 160-byte array at address 0xC400.
m_wait();
__oam_dma((u16)raw_oam);

呼び出し部分のコード例です。完全なプログラムに組み込む際は、記載した初期化とバッファの条件を満たしてください。

期待する結果

サンプルはOAMの1ページ分を直接組み立てます。(88,32)に8×8の右向き矢印、(88,56)にその左右反転、(88,80)に8×8の四角を表示します。CGBでは矢印の輪郭が赤、軸が緑、先端の一部が青で、四角は緑です。DMGでは濃淡で表示します。他のスロットはすべて非表示です。検証ではエミュレータの両ハードウェアモードで実際の画像ピクセルを照合しています。実機では未検証です。

__oam_dma: DMG
DMG
__oam_dma: CGB
CGB

使用例の完全なプログラム

ビルドコマンド

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\oam_dma.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\oam_dma.gb --cgb=cgb --profile=dev --rst-disable --stack-bank=fixed --no-cache --no-disasm
実装とソース

kitaqgb/lib/sprite.h:46

csharp
// Copy one 160-byte, page-aligned OAM source and return after DMA completes.
            if (funcName == "__oam_dma")
            {
                if (args.Length != 1) Error(expr, "__oam_dma(src_ptr) expects 1 argument");

                CompileIntoHL(args[0]); // Evaluate once; hardware uses only the high byte.
                // Mask interrupt sources without changing IME, including calls made inside an ISR.
                EmitAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute));
                EmitAsm("PUSH_AF");
                EmitAsm("XOR_A");
                EmitAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute));
                // LDH [FF46],A; LD B,40; DEC B; JR NZ,-3; RET.
                // Initialize on every call, so no startup copy or readiness flag is needed.
                byte[] dmaStub = { 0xE0, 0x46, 0x06, 0x28, 0x05, 0x20, 0xFD, 0xC9 };
                for (int i = 0; i < dmaStub.Length; i++)
                {
                    EmitAsm("LD_A_IMM", new AsmOperand(dmaStub[i], AddressMode.Immediate));
                    EmitAsm("LDH_MEM_A", new AsmOperand((OamDmaStubAddr + i) & 0xFF, AddressMode.HighMem));
                }
                EmitAsm("LD_A_H");
                // CALL pushes before DMA starts; RET reads the stack only after the wait.
                EmitAsm("CALL", new AsmOperand(OamDmaStubAddr, AddressMode.Absolute));
                EmitAsm("POP_AF");
                EmitAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute));
                return;
            }

コマンド書式辞典

下記のヘルプは本版で実行して採取しました。省略可能な引数、既定値、列挙値は採取結果に従ってください。説明用コマンドの入力ファイルは自分の出力名に合わせます。

kitaqgb
help
KITAQGB native Rust compiler

Commands:
  disasm ROM -o dis.s
  header ROM -o patched.gb [--title=NAME --cgb=dmg|cgb|cgb_only ...]
  assemble-ir INPUT.assembly.json -o output.gb [header options]
  tokenize SOURCE.c ... -I INCLUDE_DIR -o tokens.json
  parse SOURCE.c ... -I INCLUDE_DIR -o ast.json
  lower SOURCE.c ... -I INCLUDE_DIR -o lowered.json
  emit-ir SOURCE.c ... -I INCLUDE_DIR -o raw.assembly.json
  compile SOURCE.c ... -I INCLUDE_DIR -o output.gb [-O0|-O1]
  optimize-ir INPUT.assembly.json -o optimized.json [-O0|-O1]
  strip-ir INPUT.assembly.json -o stripped.json [--strip-keep=LABELS]

C compilation, aggregates, banked calls and hardware intrinsics are implemented and covered by differential tests. Utility commands: kqhelp, symfind, src2asm, romdiff, template, snippet, conventions, fixhint, irsum, attrviz, recipe, test.

Development and recovery:
  SOURCE.c ... --watch [--watch-max-builds=N]
  devserver SOURCE.c ... [--once --poll-ms=N --debounce-ms=N --max-builds=N]
  recipe [--history=FILE --out=FILE --script=FILE --last=N --stdout]
  SOURCE.c ... --minimize[=FILE] [--minimize-out=FILE --minimize-work=DIR --minimize-quick --minimize-trace=final|all]
  SOURCE.c ... --minimize-on-fail --repro-pack[=DIR]
  SOURCE.c ... --debug-output --disasm-changed[=GIT_BASE]
Watch loops stop with Ctrl+C; the Rust API also accepts a CancellationToken. See PORT_STATUS.md for executed verification and compatibility limits.

開発補助コマンドの書式

以下は現在のsrc/debug_tools.rs / src/vibe_tools.rsにある書式です。ROM差分、シンボル探索、テンプレートなどは通常のCコンパイルと別のサブコマンドです。

usage
usage: kitaqgb kqhelp <KQxxxx>
usage
usage: kitaqgb symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]
usage
usage: kitaqgb src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]
usage
usage: kitaqgb romdiff <old.gb> <new.gb> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]
usage
usage: kitaqgb template [out.c] [--out=<file.c>] [--cgb] [--overwrite] [--no-build-script] [--no-readme]
usage
usage: kitaqgb conventions [--out=<file>|--stdout]
usage
usage: kitaqgb snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]
usage
usage: kitaqgb fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]
usage
usage: kitaqgb irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]
usage
usage: kitaqgb devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>
usage
usage: kitaqgb recipe [--history=<file>] [--last=N] [--out=<file>] [--script=<file>] [--stdout]

パーサーが認識する長いオプション索引

完全な綴りを現在のsrc/cli.rsから採取。ここには互換・調査用の指定も含みます。値を必要とするかはヘルプと対応する処理を参照してください。

--abi--abi-diff-report--abi-verify--attach--auto-minimize--bank-sim--cache--cart--cgb--cgb-consistency--cross-bank-report--debug-out--debug-output--deps-out--dest--diag-json--disasm--disasm-changed--disasm-changed-out--emit-ai-metadata--emit-path-manifest--farcall-suggest--fast--fast-build--func-size-report--header-logo--help--hotspot-report--include-dir--machine-readable--max-errors--minimize-on-fail--no-banner--no-cache--no-disasm--no-header-logo--no-rst--opt-diff--permissive--profile--ramsize--repro-check--repro-pack--rom-header--rom-title--romsize--rst--rst-disable--rst-enable--rst-exclude--rst-max-calls--rst-max-vectors--rst-off--rst-on--rst-report--rst-safe--rst-speed-safe--rst-unsafe--rst-use-38--sgb--stack-bank--stack-reserve--stack-top--strict--trace--trace-out--verify-cgb-symbols--version--vlist--vlist-out

付録:アセンブリ命令索引

src/asm_info.rsの命令表を採取しています。これはコンパイラ内部の綴り・オペランド形式の索引です。分岐先やメモリアドレスを持つ行は書式例であり、単独で実行するプログラムではありません。レジスターの保持やフラグ変化は呼び出し規約とコード生成を参照してください。

opcode命令名形式書式例
00NOPIMPNOP
F3DIIMPDI
FBEIIMPEI
76HALTIMPHALT
10STOPIMM8STOP #1
C9RETIMPRET
C0RET_NZIMPRET_NZ
C8RET_ZIMPRET_Z
D0RET_NCIMPRET_NC
D8RET_CIMPRET_C
D9RETIIMPRETI
C3JPABSJP 0xC000
E9JP_HLIMPJP_HL
18JRRELJR +target
20JR_NZRELJR_NZ +target
28JR_ZRELJR_Z +target
30JR_NCRELJR_NC +target
38JR_CRELJR_C +target
CDCALLABSCALL 0xC000
C4CALL_NZABSCALL_NZ 0xC000
CCCALL_ZABSCALL_Z 0xC000
D4CALL_NCABSCALL_NC 0xC000
DCCALL_CABSCALL_C 0xC000
C2JP_NZABSJP_NZ 0xC000
CAJP_ZABSJP_Z 0xC000
D2JP_NCABSJP_NC 0xC000
DAJP_CABSJP_C 0xC000
C7RST_00IMPRST_00
CFRST_08IMPRST_08
D7RST_10IMPRST_10
DFRST_18IMPRST_18
E7RST_20IMPRST_20
EFRST_28IMPRST_28
F7RST_30IMPRST_30
FFRST_38IMPRST_38
3ELD_A_IMMIMM8LD_A_IMM #1
06LD_B_IMMIMM8LD_B_IMM #1
0ELD_C_IMMIMM8LD_C_IMM #1
16LD_D_IMMIMM8LD_D_IMM #1
1ELD_E_IMMIMM8LD_E_IMM #1
26LD_H_IMMIMM8LD_H_IMM #1
2ELD_L_IMMIMM8LD_L_IMM #1
7FLD_A_AIMPLD_A_A
78LD_A_BIMPLD_A_B
79LD_A_CIMPLD_A_C
7ALD_A_DIMPLD_A_D
7BLD_A_EIMPLD_A_E
7CLD_A_HIMPLD_A_H
7DLD_A_LIMPLD_A_L
47LD_B_AIMPLD_B_A
40LD_B_BIMPLD_B_B
41LD_B_CIMPLD_B_C
42LD_B_DIMPLD_B_D
43LD_B_EIMPLD_B_E
44LD_B_HIMPLD_B_H
45LD_B_LIMPLD_B_L
4FLD_C_AIMPLD_C_A
48LD_C_BIMPLD_C_B
49LD_C_CIMPLD_C_C
4ALD_C_DIMPLD_C_D
4BLD_C_EIMPLD_C_E
4CLD_C_HIMPLD_C_H
4DLD_C_LIMPLD_C_L
57LD_D_AIMPLD_D_A
50LD_D_BIMPLD_D_B
51LD_D_CIMPLD_D_C
52LD_D_DIMPLD_D_D
53LD_D_EIMPLD_D_E
54LD_D_HIMPLD_D_H
55LD_D_LIMPLD_D_L
5FLD_E_AIMPLD_E_A
58LD_E_BIMPLD_E_B
59LD_E_CIMPLD_E_C
5ALD_E_DIMPLD_E_D
5BLD_E_EIMPLD_E_E
5CLD_E_HIMPLD_E_H
5DLD_E_LIMPLD_E_L
67LD_H_AIMPLD_H_A
60LD_H_BIMPLD_H_B
61LD_H_CIMPLD_H_C
62LD_H_DIMPLD_H_D
63LD_H_EIMPLD_H_E
64LD_H_HIMPLD_H_H
65LD_H_LIMPLD_H_L
6FLD_L_AIMPLD_L_A
68LD_L_BIMPLD_L_B
69LD_L_CIMPLD_L_C
6ALD_L_DIMPLD_L_D
6BLD_L_EIMPLD_L_E
6CLD_L_HIMPLD_L_H
6DLD_L_LIMPLD_L_L
EALD_MEM_AABSLD_MEM_A 0xC000
FALD_A_MEMABSLD_A_MEM 0xC000
E0LDH_MEM_ALDHLDH_MEM_A 0x40
F0LDH_A_MEMLDHLDH_A_MEM 0x40
E2LD_C_MEM_AIMPLD_C_MEM_A
F2LD_A_MEM_CIMPLD_A_MEM_C
77LD_HL_AIMPLD_HL_A
7ELD_A_HLIMPLD_A_HL
46LD_B_HLIMPLD_B_HL
4ELD_C_HLIMPLD_C_HL
56LD_D_HLIMPLD_D_HL
5ELD_E_HLIMPLD_E_HL
66LD_H_HLIMPLD_H_HL
6ELD_L_HLIMPLD_L_HL
70LD_HL_BIMPLD_HL_B
71LD_HL_CIMPLD_HL_C
72LD_HL_DIMPLD_HL_D
73LD_HL_EIMPLD_HL_E
74LD_HL_HIMPLD_HL_H
75LD_HL_LIMPLD_HL_L
36LD_HL_REF_IMMIMM8LD_HL_REF_IMM #1
12LD_DE_AIMPLD_DE_A
1ALD_A_DEIMPLD_A_DE
02LD_BC_AIMPLD_BC_A
0ALD_A_BCIMPLD_A_BC
22LDI_HL_AIMPLDI_HL_A
2ALDI_A_HLIMPLDI_A_HL
32LDD_HL_AIMPLDD_HL_A
3ALDD_A_HLIMPLDD_A_HL
01LD_BC_IMMIMM16LD_BC_IMM #0xC000
11LD_DE_IMMIMM16LD_DE_IMM #0xC000
21LD_HL_IMMIMM16LD_HL_IMM #0xC000
31LD_SP_IMMIMM16LD_SP_IMM #0xC000
F9LD_SP_HLIMPLD_SP_HL
F8LD_HL_SP_IMMRELLD_HL_SP_IMM +target
E8ADD_SP_IMMRELADD_SP_IMM +target
08LD_MEM_SPIMM16LD_MEM_SP #0xC000
C5PUSH_BCIMPPUSH_BC
C1POP_BCIMPPOP_BC
D5PUSH_DEIMPPUSH_DE
D1POP_DEIMPPOP_DE
E5PUSH_HLIMPPUSH_HL
E1POP_HLIMPPOP_HL
F5PUSH_AFIMPPUSH_AF
F1POP_AFIMPPOP_AF
3CINC_AIMPINC_A
04INC_BIMPINC_B
0CINC_CIMPINC_C
14INC_DIMPINC_D
1CINC_EIMPINC_E
24INC_HIMPINC_H
2CINC_LIMPINC_L
34INC_HL_REFIMPINC_HL_REF
3DDEC_AIMPDEC_A
05DEC_BIMPDEC_B
0DDEC_CIMPDEC_C
15DEC_DIMPDEC_D
1DDEC_EIMPDEC_E
25DEC_HIMPDEC_H
2DDEC_LIMPDEC_L
35DEC_HL_REFIMPDEC_HL_REF
87ADD_AIMPADD_A
80ADD_BIMPADD_B
81ADD_CIMPADD_C
82ADD_DIMPADD_D
83ADD_EIMPADD_E
84ADD_HIMPADD_H
85ADD_LIMPADD_L
86ADD_HL_REFIMPADD_HL_REF
C6ADD_A_IMMIMM8ADD_A_IMM #1
8FADC_AIMPADC_A
88ADC_BIMPADC_B
89ADC_CIMPADC_C
8AADC_DIMPADC_D
8BADC_EIMPADC_E
8CADC_HIMPADC_H
8DADC_LIMPADC_L
8EADC_HL_REFIMPADC_HL_REF
CEADC_IMMIMM8ADC_IMM #1
97SUB_AIMPSUB_A
90SUB_BIMPSUB_B
91SUB_CIMPSUB_C
92SUB_DIMPSUB_D
93SUB_EIMPSUB_E
94SUB_HIMPSUB_H
95SUB_LIMPSUB_L
96SUB_HL_REFIMPSUB_HL_REF
D6SUB_IMMIMM8SUB_IMM #1
9FSBC_AIMPSBC_A
98SBC_BIMPSBC_B
99SBC_CIMPSBC_C
9ASBC_DIMPSBC_D
9BSBC_EIMPSBC_E
9CSBC_HIMPSBC_H
9DSBC_LIMPSBC_L
9ESBC_HL_REFIMPSBC_HL_REF
DESBC_IMMIMM8SBC_IMM #1
A7AND_AIMPAND_A
A0AND_BIMPAND_B
A1AND_CIMPAND_C
A2AND_DIMPAND_D
A3AND_EIMPAND_E
A4AND_HIMPAND_H
A5AND_LIMPAND_L
A6AND_HL_REFIMPAND_HL_REF
E6AND_IMMIMM8AND_IMM #1
B7OR_AIMPOR_A
B0OR_BIMPOR_B
B1OR_CIMPOR_C
B2OR_DIMPOR_D
B3OR_EIMPOR_E
B4OR_HIMPOR_H
B5OR_LIMPOR_L
B6OR_HL_REFIMPOR_HL_REF
F6OR_IMMIMM8OR_IMM #1
AFXOR_AIMPXOR_A
A8XOR_BIMPXOR_B
A9XOR_CIMPXOR_C
AAXOR_DIMPXOR_D
ABXOR_EIMPXOR_E
ACXOR_HIMPXOR_H
ADXOR_LIMPXOR_L
AEXOR_HL_REFIMPXOR_HL_REF
EEXOR_IMMIMM8XOR_IMM #1
BFCP_AIMPCP_A
B8CP_BIMPCP_B
B9CP_CIMPCP_C
BACP_DIMPCP_D
BBCP_EIMPCP_E
BCCP_HIMPCP_H
BDCP_LIMPCP_L
BECP_HL_REFIMPCP_HL_REF
FECP_IMMIMM8CP_IMM #1
03INC_BCIMPINC_BC
0BDEC_BCIMPDEC_BC
13INC_DEIMPINC_DE
1BDEC_DEIMPDEC_DE
23INC_HLIMPINC_HL
2BDEC_HLIMPDEC_HL
33INC_SPIMPINC_SP
3BDEC_SPIMPDEC_SP
09ADD_HL_BCIMPADD_HL_BC
19ADD_HL_DEIMPADD_HL_DE
29ADD_HL_HLIMPADD_HL_HL
39ADD_HL_SPIMPADD_HL_SP
07RLCAIMPRLCA
17RLAIMPRLA
0FRRCAIMPRRCA
1FRRAIMPRRA
CBPREFIX_CBIMPPREFIX_CB
2FCPLIMPCPL
3FCCFIMPCCF
37SCFIMPSCF
27DAAIMPDAA