KITAQ 開發系統
使用手冊 2026.09

KITAQ SERIES • 參考版

KITAQGB

Game Boy C 語言程式設計

從第一行程式,到執行、觀察與複測。
原始碼快照:2026 年 9 月 14 日 · 繁體中文版: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

各作業系統的範例建置

PowerShell 範例建置使用根目錄的 Windows 程式。在 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分別按照字母名稱發音,整個名稱讀作 kee-tah-KYOO jee bee。

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。語言、函式庫和呼叫慣例與桌面 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 程式。在 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. 基本語法與型別

敘述以 ; 結尾,多個敘述用 { } 分組。// 開始單行註解,/* ... */ 表示區塊註解,名稱區分大小寫。入口請寫成 void main()。本 GB 版本的 void main(void) 會造成語法錯誤,不要直接照搬 FC 的入口宣告。

型別意義與範圍
u8無號 8 位元整數,0~255
s8有號 8 位元整數,-128~127
u16無號 16 位元整數,0~65535
s16有號 16 位元整數,-32768~32767
void無傳回值
T*指向 T 型別資料的指標

入門時建議使用這些短型別名稱。不要假設 int、long、float、double 或標準標頭與桌面環境相同。本版 char 是無號 8 位元資料;需要負數時請明確使用 s8 或 s16。

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

(u16) 是型別轉換。小型別已經溢位的值,再指定給大變數也無法找回遺失的位元;應在計算前擴大運算元的型別。小數單位的移動可使用定點數函式庫。

5. 運算式與運算子

類別運算子範例或意義
算術+ - * / %n / 10 求整數商,n % 10 求餘數
比較== != < <= > >=lives == 0 判斷相等
邏輯! && / ||非、兩條件都成立、任一條件成立
位元運算& / | / ^ ~ << >>按鈕遮罩與其他位元集合
指定= += -= 等x += 1 更新數值
遞增遞減++ --i++ 增加 1
條件選擇condition ? A : B依條件選擇數值
指標&variable / *p取得位址或存取指向的資料

請區分 = 與 ==。複雜運算式用括號表明意圖,不要把太多函式呼叫和副作用塞進同一個敘述。避免除以零及陣列越界。sizeof 傳回位元組數,offsetof 傳回結構成員的位移。

運算式結果與求值

比較運算子 ==、!=、<、<=、>、>= 與邏輯運算子 !、&&、|| 在假時回傳 0,真時回傳 1。結果可存入 u16、作為參數傳遞或回傳,也可參與算術運算。例如 score = 500 + (lives != 0); 在仍有生命時得到 501,否則得到 500。

&& 在左運算元為零時不求值右運算元;|| 在左運算元非零時不求值右運算元。在 pointer != 0 && pointer->active != 0 中,空指標會阻止成員存取。16 位元值的兩個位元組都會參與真假判斷,因此 256 為真。位元運算子 & 和 | 不會短路求值。

++value 回傳更新後的值,value++ 回傳原本的值。這些運算子也可用於陣列元素、解參照的指標與結構成員。buffer[index()]++ 只會呼叫 index() 一次。對 u16 *p 而言,p++ 前進兩個位元組以指向下一個元素,(*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 會前進兩個位元組,指向所選陣列的第二個元素;不會複製陣列。

condition ? yes : no 先求值條件,再只求值被選中的分支。條件與兩個分支都可包含位移量由變數決定的移位運算。例如 on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; 依所選位元決定使用 4 或 2。被 && 或 || 略過的右運算元內,函式呼叫也不會執行。for 迴圈中的 continue 會先執行一次更新運算式,再重新判斷條件;在 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 繼續執行下一個,請明寫 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 開始,四個元素的合法索引為 0~3。player.x 存取成員,pointer->x 透過指標存取成員。剖析器接受結構、聯合與列舉,但配置取決於型別及 __packed / __aligned 屬性。與硬體或二進位格式交換資料前,請檢查 sizeof。

預設的 Legacy ABI 將參數與區域儲存空間放在固定位置。不要假設能像桌面環境一樣遞迴或從中斷重入。__stackcall 和 --abi=stack 是進階呼叫慣例選項。混用慣例時,請檢查 ABI 報告並驗證執行結果。--abi=stack 不支援透過函式指標呼叫回呼。完整的系統與場景回呼範例,請依其建置命令使用預設 Legacy ABI。

8. 多檔案與前置處理器

型別、常數和宣告放在標頭檔,函式本體放在 .c 檔。使用 #pragma once 或標頭保護巨集避免重複引入。條件式編譯支援 #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 加入標頭搜尋目錄。引入標頭不等於連結實作,建置命令仍須列出所需 .c 檔。請按需選擇;不分用途加入全部函式庫來源,可能造成暫存器定義或中斷處理常式重複。

9. ROM、記憶體與分頁

ROM 儲存程式碼與常數,WRAM 儲存變數,VRAM 儲存圖像,OAM 儲存精靈描述。切換 bank 會改變 CPU 位址對應到的實際記憶體。單一 16 位元指標無法辨識另一個 bank 的資料。

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 專用功能。ROM 標頭旗標不會代替你實作遊戲內的模式切換邏輯。

10. 安全更新圖像

VBlank 是顯示影格之間的空白期間。時機不對的 VRAM/OAM 寫入可能造成畫面損壞或更新遺失。初始素材應在關閉顯示時載入,平常更新則用安全內建函式或 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 彙整建置結果。明確產生 --emit-ai-metadata JSON,能讓 SARAKURA 的輸入關係更清楚。

12. 從第一個錯誤讀起

先看首個錯誤的檔名、行號和 KQ 診斷編號;後續錯誤可能只是第一個語法問題引起的連鎖反應。未定義符號應檢查宣告、實作,以及是否納入建置。ROM 超出容量時,請查看素材大小、函式大小與 bank 配置。

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、kitaqfc、kokura、kurosaki、sarakura、kitaq-docs 儲存庫與 game-gb 或 game-fc 專案位於同一個上層目錄。請從該目錄執行,並依實際環境調整路徑。

需求

  • 遊戲名稱:<填寫>
  • 類型與核心玩法:<填寫>
  • 操作方式及成功、失敗條件:<填寫>
  • 必備畫面、關卡、敵人與道具:<填寫>
  • 美術風格、背景音樂與音效:<填寫,並註明提供素材的路徑>
  • 存檔、通訊、周邊設備及其他需求:<填寫,或無>
  • 專案目錄:<填寫>
  • 再散布條件:<例如,自行撰寫的程式與原創素材可採 MIT 授權公開>
  • 目標機型:<初代 Game Boy / 同時支援 GB 與 CGB / CGB 專用>
  • 效能目標:<例如,一般遊玩時每秒更新遊戲邏輯 60 次;註明高負載場景可接受的表現>

請執行的工作

請以 KITAQGB 及其函式庫實作遊戲。使用 KOKURA 執行與除錯,使用 SARAKURA 整理診斷並比較修正前後的結果。

持續重複以下流程,直到符合驗收標準:具體化規格 → 實作小幅變更 → 建置 → 輸入操作並觀察 → 調查原因 → 修正 → 在相同條件下重新驗證。不可只因提出計畫、提供程式碼或編譯成功,就認定工作完成。

確認環境與驗收標準

  1. 閱讀工作目錄的指示、各工具 README、HTML 手冊,以及所用函式庫的標頭檔和實作。記錄執行檔路徑及版本或 SHA-256;以實際 --help 輸出確認命令,以原始碼確認 API。
  2. 為輸入、畫面、聲音、遊戲流程和更新頻率訂出可判定的驗收標準。例如,按下再放開 START 後開始遊戲;碰撞減少一條命;暫停時指定聲音靜音,繼續後恢復播放。
  3. 只針對重要的模糊需求提問,一般可復原的實作決策請自主處理。不得自行降低需求或驗收標準。
  4. 先用隨附的小範例串接編譯器、模擬器與 SARAKURA。這只能確認工具銜接,不能代表所需遊戲已完成。

先完成可玩的最小流程

  • 使用 KITAQGB 的 C 方言與 void main()。不要假定桌面 C 或 GBDK API 能直接使用。除了宣告,也要把必要的 .c 實作納入建置;確認初始化順序、單位、正負號、範圍、緩衝區生命週期及 ROM 分頁。
  • 規劃 VRAM/OAM 更新、VBlank、中斷、堆疊、ROM/WRAM 分頁,以及圖塊與精靈數量限制。傳輸佇列的總容量、剩餘容量,與實體 VRAM 的容量、可用空間是不同概念。
  • DMG 遊戲不得依賴 CGB 專用功能。若支援兩者,請分別驗證各硬體模式。
  • 英文字母、數字與符號使用提供的原創 ascii.c 字型,並確認字元與圖塊的對應關係。
  • 先串起開機、標題畫面、可操控角色、成功或失敗與重新開始,再擴充內容。
  • 保留可編輯的圖形、音樂、音效原始檔及產生步驟,並確認建置確實讀取匯出的資料。
  • 原始碼註解使用英文,進度報告使用繁體中文。SARAKURA 的標準報告維持英文。

對應每次建置與執行結果

以 out/iter-001 等目錄區分每輪輸出。記錄命令、結束碼,以及原始碼、素材、工具、ROM、中繼資料的雜湊值。建置失敗後,不可執行殘留的舊 ROM。配置映射、原始碼映射與除錯資訊必須和 ROM 來自同一次建置。

以下是基本的 DMG 檢查範例。請準備 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 標頭與模擬器機型設定一致。範例輸入在兩段放開按鈕的時間之間按一次 START。執行 300 影格不代表測試完整個遊戲。

檢查畫面、聲音、狀態與效能

  • 保存輸入情境,區分按下、按住與放開。走過規格中的全部路徑:開機、開始、移動、動作、碰撞、捲動、關卡切換、遊戲結束、重新開始、暫停,以及適用的存檔與通訊。
  • 保留關鍵影格 PNG、輸入資料、執行報告、診斷 JSONL、WAV 和必要的狀態、記憶體觀測。確認實際到達的影格數與停止原因。務必開啟圖片查看;一張截圖無法證明移動或輸入反應。將計數器、座標與狀態變化和預期值比對,也要檢查畫面邊緣、圖塊與屬性邊界及精靈密集情境。
  • 檢查音樂、音效、同時發聲、斷音、暫停與恢復。僅產生 WAV 不能證明聲音正確。無法試聽時,請區分已執行的波形、數值檢查與尚未確認的聽感。
  • 測量高負載場景的目標 CPU 工作量、遊戲更新與傳輸量;FC 還要計入 NMI 工作。主機上模擬器的執行速度不等於遊戲更新頻率,也不能證明實機速度。使用 --allow-unimplemented 後能繼續執行,不代表未實作功能已受支援。

分析、修正並重新驗證

  • 將待測 ROM 的建置中繼資料與該次執行的診斷 JSONL 交給 SARAKURA。CPU 追蹤或一般執行報告不能取代它。--frames 指定分析條件;SARAKURA 不會執行 ROM,也不會自動修改原始碼。
  • 閱讀 report.html、ai_diagnostics.json、repair_prompt.md、retest_plan.json,並與重現步驟、畫面、聲音及原始碼核對。區分推測的位置、原因與已確認事實,也要區分正常等待迴圈與當機。逐項判讀警告,記錄未支援事件與分析限制。不可透過隱藏警告或縮短測試來取得通過結果。
  • 將問題縮減成最小重現案例,修正原因後重新建置。若根源在編譯器或模擬器,應與遊戲程式問題分開確認,並為工具修正加入回歸驗證。
  • 重新驗證時,保持輸入、亂數種子、機型與影像制式、Mapper、觀測影格及診斷設定一致。新 ROM 使用相符的中繼資料;程式或 RAM 配置變更後,不可直接沿用即時存檔。
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

診斷差異應搭配操作、畫面與聲音的驗收結果判斷。相同失敗反覆發生時,請重新檢視證據與假設,不要漫無根據地繼續修改。

完成條件與交付內容

以交付的原始碼與設定建置最終 ROM,再執行所有必要情境。僅使用無敵狀態、自動測試輸入或另一種 Mapper,無法驗證最終版本的正常遊玩。提供需求與測試對照表,說明剩餘警告的原因,明列未驗證、未支援項目。未做實機測試時,請標註「實機未驗證」。

交付原始碼、工具與函式庫識別資訊、可編輯素材、可重現的建置與測試指令稿、ROM、最終驗證證據,以及說明安裝、操作與已知限制的 README。視需要附上重播資料和測試驅動程式。只在明確授權範圍內公開或對外傳送檔案。驗證後刪除不必要的中間建置與暫存追蹤,但保留原始碼、素材、最終成果及必要的回歸證據。

若環境或權限阻礙必要檢查,請回報確切的重現步驟與所需處理,不得標記為完成。

原始碼位置

編譯器原始碼與專案檔位於儲存庫內的同名子目錄,執行檔位於儲存庫根目錄,函式庫位於 lib/。專案路徑與建置需求請參閱目錄結構說明。

完整範例程式

程式、共用標頭、提供的 ascii.c 與轉換素材均已附上。m_ 開頭的名稱是本手冊的輔助函式。請搭配預期結果和驗證紀錄閱讀建置命令,並從各並列儲存庫的上層目錄執行。

批次建置指令碼 / 結果與畫面擷取

顯示提供的字元集 gb_font.c

目的: 了解提供的原創字型包含哪些字母、數字與標點符號,以及如何繪製。

運作方式: m_init 準備顯示與字型。m_text 繪製各列,m_put 則以字元碼 34 繪製雙引號。

操作方式與預期結果: 請確認有 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。前面的零來自三位數十進位格式。

第一次顯示文字:畫面顯示 HELLO WORLD 與 042。前面的零來自三位數十進位格式。
顯示範例:畫面顯示 HELLO WORLD 與 042。前面的零來自三位數十進位格式。

下載原始碼 / 驗證狀態

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

目的: 學習如何保留超出八位元範圍的中間結果。

運作方式: (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,得到 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

目的: 學習如何將值分組,並透過函式與指標存取。

運作方式: 程式複製包含 (10,20) 的 Point,再透過指標將 1 至 4 寫入陣列。它將函式結果 30 加上陣列值 1、2、4 與常數 5。

操作方式與預期結果: 總和顯示為 042。請追蹤結構複製、ptr[i] 寫入與函式傳回值。

函式、陣列、結構與指標:總和顯示為 042。請追蹤結構複製、ptr[i] 寫入與函式傳回值。
顯示範例:總和顯示為 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

目的: 將旗標打包成一個位元組,並將緩衝區複製至另一個儲存區。

運作方式: __memset 清除四個位元組。程式設定位元 3、切換位元 1,再以 __memcpy 複製全部四個位元組。

操作方式與預期結果: 目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。

位元旗標與緩衝區複製:目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。
顯示範例:目的緩衝區的第一個位元組顯示為 010,其中位元 3 貢獻 8,位元 1 貢獻 2。

下載原始碼 / 驗證狀態

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、相乘,再轉回整數。

運作方式: fix_from_int 將 3、4 轉成 Q8.8,fix_mul 將兩者相乘,fix_to_int 再將乘積轉回整數。

操作方式與預期結果: 012 表示 3 × 4 = 12,是轉換後的整數,不是內部放大 256 倍的值。

Q8.8 定點數相乘:012 表示 3 × 4 = 12,是轉換後的整數,不是內部放大 256 倍的值。
顯示範例:012 表示 3 × 4 = 12,是轉換後的整數,不是內部放大 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 重設物件池。entity_create 在 (20,30) 建立型別 1;程式先檢查失敗 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

目的: 將具名遊戲值保存在診斷緩衝區,與畫面顯示分開處理。

運作方式: 程式初始化除錯功能,將影格標記設為 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

目的: 學習如何將位置軌跡儲存在環形緩衝區中。

運作方式: chain_init 掛接可容納四個點的陣列,接著 chain_push_head 記錄 (20,30)。

操作方式與預期結果: 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,再顯示 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

目的: 學習如何選取 ID,以區分標題畫面、遊戲進行等場景。

運作方式: 程式登錄兩個以零初始化的 SceneDef 項目,再以 scene_change 選取 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 像素。接著 Camera_WorldToScreenX 轉換世界座標 X=52。

操作方式與預期結果: 052 減去 010 得到畫面上的 042。這是座標計算,不會捲動背景。

將世界座標轉換為畫面座標:052 減去 010,畫面便顯示 042。這是座標計算範例,不會捲動背景。
顯示範例:052 減去 010 得到畫面上的 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

目的: 嘗試以次數與數值,精簡表示重複的位元組。

運作方式: 壓縮位元組 {3,42,0} 代表重複三次 42,再以零次數結束標記收尾。rle_decode 寫入三位元組目的緩衝區。

操作方式與預期結果: 最後一個位元組 out[2] 顯示為 042。解壓縮後的三個位元組應全部為 42。

將 RLE 資料解壓縮至 RAM:最後一個位元組 out[2] 顯示為 042。解壓縮後的三個位元組應全部為 42。
顯示範例:最後一個位元組 out[2] 顯示為 042。解壓縮後的三個位元組應全部為 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

目的: 了解棋盤遊戲的格子座標如何對應到儲存位置。

運作方式: 以 16 位元組陣列儲存 4×4 棋盤。程式清空棋盤、在格子 (2,1) 寫入 42,再讀回同一格。

操作方式與預期結果: 042 是讀回的值。程式不繪製 4×4 棋盤本身。

讀寫棋盤格子:042 是讀回的值。程式不繪製 4×4 棋盤本身。
顯示範例: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();  }
}
將方盒推進一個物理步驟 gb_physics.c

目的: 初始化世界與方盒剛體,再根據速度更新位置。

運作方式: 剛體從位置 (40,40)、速度 (2,0)、零重力開始。程式呼叫 kq2d_step 一次。

操作方式與預期結果: 042 是算出的 X 座標,此範例以整數像素為單位。不繪製方盒或連續動畫。

將方盒推進一個物理步驟:042 是算出的 X 座標,此範例以整數像素為單位。不繪製方盒或連續動畫。
顯示範例:042 是算出的 X 座標,此範例以整數像素為單位。不繪製方盒或連續動畫。

下載原始碼 / 驗證狀態

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 所需的引數。

運作方式: far_data_read 使用記憶體庫引數 0 與長度 2,將位元組 7、42 複製到 copied。

操作方式與預期結果: 目的緩衝區的第二個位元組顯示為 042。此範例讀取固定 ROM,不示範不同可切換記憶體庫之間的傳輸。

指定記憶體庫引數讀取 ROM 資料:目的緩衝區的第二個位元組顯示為 042。此範例讀取固定 ROM,不示範不同可切換記憶體庫之間的傳輸。
顯示範例:目的緩衝區的第二個位元組顯示為 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 取得位元組資料。

運作方式: 素材 0 的型別為 RAW、記憶體庫為 0,包含指向 {7,42} 的指標及長度 2。asset_load_raw 將其複製到兩位元組 RAM 緩衝區。

操作方式與預期結果: 第二個位元組顯示為 042。這示範素材查找與複製,不是影像繪製。

依素材 ID 載入資料:第二個位元組顯示為 042。這示範素材查找與複製,不是影像繪製。
顯示範例:第二個位元組顯示為 042。這示範素材查找與複製,不是影像繪製。

下載原始碼 / 驗證狀態

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();  }
}
配置一顆子彈 gb_danmaku.c

目的: 初始化子彈池,並在生成子彈後檢查使用狀況。

運作方式: danmaku_reset 後,程式呼叫 danmaku_spawn(40,40,16,0) 並顯示 dm_count。

操作方式與預期結果: 001 是已配置的子彈數。此處沒有子彈更新迴圈或背景合成器,因此不繪製移動子彈。

配置一顆子彈: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,並將其傳給 Scroll_SetBg。八位元位置值超過 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 將圖塊 52 排入圖塊座標 (3,8),vram_flush 再執行傳輸。提供的字型中,圖塊 52 是數字 4。

操作方式與預期結果: 請確認背景圖塊座標 (3,8) 出現一個 4。這是圖塊更新,不是數字計數器。

將圖塊更新加入佇列:請確認背景圖塊座標 (3,8) 出現一個 4。這是圖塊更新,不是數字計數器。
顯示範例:請確認背景圖塊座標 (3,8) 出現一個 4。這是圖塊更新,不是數字計數器。

下載原始碼 / 驗證狀態

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) 一次。上限 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();  }
}
將圓形推進一個物理步驟 gb_circle.c

目的: 設定圓形剛體,並根據速度更新位置。

運作方式: 啟用中的圓形半徑為 4、位置為 (40,40)、速度為 (2,0)。重力為零,linear_damping_q8 為 255;kq2dc_step 執行一次。

操作方式與預期結果: 042 是算出的整數 X 座標。此範例不繪製圓形外框或碰撞動畫。

將圓形推進一個物理步驟:042 是算出的整數 X 座標。此範例不繪製圓形外框或碰撞動畫。
顯示範例:042 是算出的整數 X 座標。此範例不繪製圓形外框或碰撞動畫。

下載原始碼 / 驗證狀態

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 在通道 ID 1 啟動 tone。主迴圈每影格呼叫一次 Audio_Update。

操作方式與預期結果: 顯示 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 個項目。展開可查看語法、引數、原始碼說明與範例。呼叫片段依賴周邊初始化,不能單獨建置 ROM。引數傳遞範例只說明如何將備妥的值交給 API,不代表已完成裝置層級驗證。

asset.h — 素材 ID 與資料描述

素材函式庫透過8位元ID查找ROM記憶體區塊中的素材。請包含asset.c;FC版還需要bank.c。緊密排列的AssetDesc占6位元組,依序儲存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

目標可寫入記憶體,容量必須足以容納實際複製的全部位元組。除了零位元組傳送外,不會檢查null或無效目標位址。

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複製兩個圖案,共32位元組。程式逐一比較位元組,檢查區塊1已恢復,並確認零長度複製不會改變0x55哨兵值。

預期圖像中,紅色一列從像素座標(16,32)開始,占72×8像素。依序重複8×8三角形、邊框厚1像素的8×8空心正方形、填滿左側4欄的圖塊,共三組。三角形從左上角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的len位元組複製到dst,完成後才回傳,不將操作加入佇列。來源和目的位址每次增加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,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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 — 考慮 bank 的程式碼與資料存取

ROM 區塊是映射到 CPU 位址空間的一塊卡匣儲存區域。不同區塊可出現在相同 CPU 位址,因此只靠 16 位元指標無法確定跨區塊資料。BankPtr 是緊密排列的三位元組描述子,包含一位元組區塊編號與兩位元組指標。使用 farptr_make 建立,再透過 farptr_read8、farptr_read16 或 farptr_read 存取資料。

GB 範例使用 MBC5 卡匣:固定區塊 0 的程式碼可持續透過 $0000–$3FFF 存取,區塊 1、2、3 則映射到 $4000–$7FFF。請將 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 區塊介面只攜帶 8 位元編號,不設定 MBC5 的第 9 位元。仍須遵守卡匣規則,包括 mapper 對編號 0 的處理。範例明確使用 MBC5 區塊 1–3;不要混用未記錄的 mapper 直接寫入。

使用範例

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。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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 映射中可讀取。這些讀取不檢查指標、不裁切位址,也不會在視窗邊界自動切換到下一個 ROM 區塊。讀取一個字組需要連續兩個有效位元組。

遠端讀取與複製是同步操作,會使用編譯器共用工作區。操作期間不要讓中斷執行另一筆區塊傳輸並覆寫同一工作區。GB 複製輔助程式還使用一個共用 HRAM 位置保存返回區塊。範例從固定程式碼執行,不設置競爭的區塊傳輸中斷。

這些 GB 區塊介面只攜帶 8 位元編號,不設定 MBC5 的第 9 位元。仍須遵守卡匣規則,包括 mapper 對編號 0 的處理。範例明確使用 MBC5 區塊 1–3;不要混用未記錄的 mapper 直接寫入。

使用範例

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

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式片段使用 __farpeek16 讀取區塊 3 中的 payload+1 並檢查 5C7A。完整程式還透過近指標讀取 home2,確認已還原區塊 2 的映射。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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 位址讀取一個位元組後還原原區塊。這是 far_data_read8 使用的內建函式。

語法

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

參數

bank

無號 8 位元 ROM 區塊編號。呼叫時應與函式配置一致,讀取時用來選擇來源映射。請使用目標卡匣支援的範圍。

addr

指向所選ROM區塊中可讀取位元組的近指標,指標本身不含區塊編號。

回傳值

以 u8(0–255)傳回讀到的位元組。

使用條件與限制

目標位元組或字組的全部內容必須在選定 CPU 映射中可讀取。這些讀取不檢查指標、不裁切位址,也不會在視窗邊界自動切換到下一個 ROM 區塊。讀取一個字組需要連續兩個有效位元組。

遠端讀取與複製是同步操作,會使用編譯器共用工作區。操作期間不要讓中斷執行另一筆區塊傳輸並覆寫同一工作區。GB 複製輔助程式還使用一個共用 HRAM 位置保存返回區塊。範例從固定程式碼執行,不設置競爭的區塊傳輸中斷。

這些 GB 區塊介面只攜帶 8 位元編號,不設定 MBC5 的第 9 位元。仍須遵守卡匣規則,包括 mapper 對編號 0 的處理。範例明確使用 MBC5 區塊 1–3;不要混用未記錄的 mapper 直接寫入。

使用範例

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

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式片段使用 __farpeek8 讀取區塊 3 中的 payload 並檢查 D3。完整程式還透過近指標讀取 home2,確認已還原區塊 2 的映射。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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 背景與 OBJ 調色盤

cgb_palette.h與cgb_palette.c提供RGB15封裝及CGB調色盤即時更新。背景/視窗使用BG函式,精靈使用OBJ函式。單色及RGB分量函式修改一色,範圍函式寫入連續色彩字組,整組調色盤函式寫入4個字組。raw範圍函式使用絕對色彩槽位,暫存器內建函式使用位元組位置。透過地圖屬性或OAM另外指定調色盤歸屬。請如完整範例所示,只將實作包含至程式一次。

調色盤操作與具模式保護的暫存器寫入,在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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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)寫入一個位元組。__cgb_safe_set_bgpd是同一暫存器的別名。對應的索引暫存器選取位置;完整RGB15色彩需要低位及高位兩個位元組。

語法

c
void __cgb_safe_set_bcpd(u8 value);

參數

value

一個位元組,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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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為1時,資料寫入後自動遞增。位元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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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)寫入一個位元組。__cgb_safe_set_obpd是同一暫存器的別名。對應的索引暫存器選取位置;完整RGB15色彩需要低位及高位兩個位元組。

語法

c
void __cgb_safe_set_ocpd(u8 value);

參數

value

一個位元組,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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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為1時,資料寫入後自動遞增。位元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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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 使用其視窗映射表。attr 形式只寫屬性,cgb 形式同時寫圖塊編號與屬性。bulk 形式在指定位置傳輸連續位元組。請依目的地及寫入資料選擇入口函式。

個別屬性位元可用 KQ_CGB_ATTR_PAL 等巨集,合併欄位則用 KQ_CGB_ATTR。bank 欄位選擇存放圖樣的 VRAM 記憶體庫,不是 ROM 記憶體庫。一般圖塊編號寫入使用目前 VRAM 記憶體庫,因此執行圖塊/屬性合併操作前請選取記憶體庫零。DMG 上只寫屬性的操作不做任何事;合併操作仍會寫入圖塊編號。

__ 內建函式由編譯器產生實作,不必連結 cgb_tile.c。一般寫入路徑會等待 VRAM 可存取,但部分也會改變中斷啟用狀態。_unsafe 與 _fast 形式省略等待,只能在呼叫端保證 VRAM 可寫入時使用,例如 LCD 關閉時。請檢查各項的中斷、記憶體庫及傳輸長度條件。

__cgb_safe_set_vbk

在CGB上透過VBK($FF4F)選取CPU存取的VRAM bank。只寫入value & 1:偶數選0,奇數選1。影響CPU對$8000–$9FFF的存取,與地圖屬性選擇的圖塊資料bank不同。不會自動還原原本的bank。

語法

c
void __cgb_safe_set_vbk(u8 value);

參數

value

在CGB上透過VBK($FF4F)選取CPU存取的VRAM bank。只寫入value & 1:偶數選0,奇數選1。影響CPU對$8000–$9FFF的存取,與地圖屬性選擇的圖塊資料bank不同。不會自動還原原本的bank。

回傳值

無(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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 複製連續的 count 個圖塊編號位元組到 VRAM 位址 dest。數量以位元組計算,不是圖塊圖像數,也不調整列間距。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 VBlank 內完成。

語法

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

參數

dest

起始 VRAM 位元組位址。地圖使用 0x9800–0x9FFF 範圍,整段傳輸應位於目標地圖內。

src

位於 RAM 或目前可見 ROM bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 0;此呼叫不會自動切換。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 複製連續的 count 個圖塊編號位元組到 VRAM 位址 dest。數量以位元組計算,不是圖塊圖像數,也不調整列間距。

不等待 VRAM 可寫入。請在 LCD 關閉時使用,或由呼叫端確保整段傳輸都位於可寫入時間內。

語法

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

參數

dest

起始 VRAM 位元組位址。地圖使用 0x9800–0x9FFF 範圍,整段傳輸應位於目標地圖內。

src

位於 RAM 或目前可見 ROM bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 0;此呼叫不會自動切換。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

不等待 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

以 base 指定地圖起始位址 0x9800 或 0x9C00;位元組位址為 base + 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

以 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

以 base 指定地圖起始位址 0x9800 或 0x9C00;位元組位址為 base + 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

以 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

以 base 指定地圖起始位址 0x9800 或 0x9C00;位元組位址為 base + 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 中連續的 count 個 CGB 屬性位元組複製到 VRAM bank 1 的 dest。圖塊編號不變;dest 是位址,不是座標組。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 VBlank 內完成。

語法

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

參數

dest

起始 VRAM 位元組位址。地圖使用 0x9800–0x9FFF 範圍,整段傳輸應位於目標地圖內。

src

位於 RAM 或目前可見 ROM bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 1 寫入屬性,結束時設為 VBK=0。DMG 不執行屬性寫入。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上,屬性位元組 1,2,3 分別選擇三個預設調色盤,使 A 為紅色、B 為藍色、C 為綠色。DMG 上三個字元均為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 中連續的 count 個 CGB 屬性位元組複製到 VRAM bank 1 的 dest。圖塊編號不變;dest 是位址,不是座標組。

不等待 VRAM 可寫入。請在 LCD 關閉時使用,或由呼叫端確保整段傳輸都位於可寫入時間內。

語法

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

參數

dest

起始 VRAM 位元組位址。地圖使用 0x9800–0x9FFF 範圍,整段傳輸應位於目標地圖內。

src

位於 RAM 或目前可見 ROM bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 1 寫入屬性,結束時設為 VBK=0。DMG 不執行屬性寫入。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上,屬性位元組 1,2,3 分別選擇三個預設調色盤,使 A 為紅色、B 為藍色、C 為綠色。DMG 上三個字元均為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

不等待 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

寫入 LCDC 位元 3 所選的背景地圖:0 對應 0x9800,1 對應 0x9C00,再加上 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

寫入 LCDC 位元 3 所選的背景地圖: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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

寫入 LCDC 位元 3 所選的背景地圖:0 對應 0x9800,1 對應 0x9C00,再加上 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

寫入 LCDC 位元 3 所選的背景地圖: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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

寫入 LCDC 位元 3 所選的背景地圖:0 對應 0x9800,1 對應 0x9C00,再加上 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

寫入 LCDC 位元 3 所選的背景地圖: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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

依序寫入圖塊編號與 CGB 屬性。DMG 只寫入編號。CGB 呼叫前請選取 VRAM bank 0;屬性寫入 bank 1,結束時為 VBK=0。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上,屬性位元組 1,2,3 分別選擇三個預設調色盤,使 A 為紅色、B 為藍色、C 為綠色。DMG 上三個字元均為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

count

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

依序寫入圖塊編號與 CGB 屬性。DMG 只寫入編號。CGB 呼叫前請選取 VRAM bank 0;屬性寫入 bank 1,結束時為 VBK=0。

複製結束前,請確保來源緩衝區及其 ROM bank 都能讀取。沒有回傳值。

範例使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上,屬性位元組 1,2,3 分別選擇三個預設調色盤,使 A 為紅色、B 為藍色、C 為綠色。DMG 上三個字元均為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

不等待 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

寫入 LCDC 位元 6 選用的視窗地圖:0 為 0x9800,1 為 0x9C00。x,y 是地圖座標,不是視窗在畫面上的位置。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

寫入 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

寫入 LCDC 位元 6 選用的視窗地圖:0 為 0x9800,1 為 0x9C00。x,y 是地圖座標,不是視窗在畫面上的位置。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 屬性位元組,不更動圖塊編號。屬性可指定調色盤、圖塊圖像 bank、水平或垂直翻轉及優先順序。

寫入 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 4 應為 0。

回傳值

無(void)。

使用條件與限制

在 CGB 上切換到 VRAM bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 1,結束時為 VBK=0。

寫入 LCDC 位元 6 選用的視窗地圖:0 為 0x9800,1 為 0x9C00。x,y 是地圖座標,不是視窗在畫面上的位置。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 bank 0;屬性寫入 bank 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 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

CGB 上的 A 為藍色:屬性值 2 選擇範例設定的藍色調色盤。DMG 不套用 CGB 屬性,因此同一字元為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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。初始化一次,每個輸入週期更新一次,再依需求查詢。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) 為新按下遮罩。必須存入 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.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於比較三種按鈕遮罩格式,以及將目前狀態與新按下狀態合併的回傳值。同時按住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 是文件分類,不是要求包含名為 intrinsics.h 的 GB 共用標頭。請使用各完整範例所示的標頭與初始化。不必在遊戲中定義內建函式的實作。

有些內建函式使用建置期資訊,有些則在遊戲執行時存取硬體或記憶體。例如 __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。這驗證配置中繼資料;執行時區塊行為由獨立的讀取與回呼檢查驗證。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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 寫入區塊編號的低位元組,並更新編譯器的區塊狀態。呼叫後維持此選擇;不更新 bank_get_current 使用的記錄。

語法

c
void __bankswitch(u8 bank);

參數

bank

無號 8 位元 ROM 區塊編號。呼叫時應與函式配置一致,讀取時用來選擇來源映射。請使用目標卡匣支援的範圍。

回傳值

無(void)。

使用條件與限制

持續生效的區塊切換不能使目前程式碼或返回位址失去映射。請如範例所示,將呼叫位置、使用的 bank_switch 本體及後續程式碼放在共用或固定區塊 0。

函式庫記錄初值為 0,不會查詢卡匣啟動時的映射。請用 bank_switch 初始化。直接呼叫切換內建函式或寫入 mapper 不會更新這份獨立記錄,因此 bank_get_current 無法驗證實際硬體映射。

這些 GB 區塊介面只攜帶 8 位元編號,不設定 MBC5 的第 9 位元。仍須遵守卡匣規則,包括 mapper 對編號 0 的處理。範例明確使用 MBC5 區塊 1–3;不要混用未記錄的 mapper 直接寫入。

使用範例

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。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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)寫入一個位元組。__cgb_safe_set_bcpd是同一暫存器的別名。對應的索引暫存器選取位置;完整RGB15色彩需要低位及高位兩個位元組。

語法

c
__cgb_safe_set_bgpd(/* 書式は実装と呼び出し例を参照 */);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

一個位元組,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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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為1時,資料寫入後自動遞增。位元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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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

寫入HDMA1($FF51),設定CGB DMA來源位址的高位元組。只設定來源,不複製資料或啟動傳輸;低位元組由HDMA2設定。

語法

c
void __cgb_safe_set_hdma1(u8 value);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

暫存器值,型別u8,範圍0~255。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

每區塊16位元組。新傳輸可指定1~128區塊(16~2048位元組),控制值0表示16位元組,不是空傳輸。GDMA在完成前暫停CPU。HBlank DMA在每個可見行的HBlank傳輸一區塊,可跨影格繼續;呼叫端須在重用資料前確認完成。

GDMA應在LCD關閉或夠長的可寫階段使用。HBlank DMA須在LCD運作且非STAT模式0時啟動;範例從VBlank啟動。進行中請勿改變來源bank、目的VBK或來源資料。HALT也會暫停HBlank DMA。這些函式不額外等待、不保護中斷,也不檢查來源/目的範圍。

須另讀$FF55取得狀態:位元7為0表示進行中,$FF表示完成。取消後位元7為1,位元0~6保留剩餘區塊數減1;已複製的位元組不會還原。僅在傳輸進行中發出取消命令,否則將位元7寫為0會啟動GDMA。整個傳輸須位於有效來源記憶體與目的VRAM bank內,不要依賴位址溢位。

這些HDMA暫存器設定函式控制CGB硬體DMA。__vram_copy_dma是CPU複製迴圈的另一編譯器別名,不會因名稱含dma就使用此DMA控制器。

使用範例

c
__cgb_safe_set_hdma1(0xC6); // Source high byte: fixed-WRAM buffer at $C600.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

完整程式使用位於$C600、以16位元組對齊的緩衝區比較三種傳輸。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)。以這些圖塊為目的的兩區塊HBlank傳輸,在同一VBlank內啟動並取消,不傳輸第一區塊。$FF55應為$81,一影格後圖樣仍不變。DMG下前兩行留白,僅這兩個保持檢查圖形顯示為黑色。文字為白底黑字。已在KOKURA測試雙模式DMG/CGB及CGB專用執行,FAILED CHECKS 000也檢查複製位元組及完成狀態;未測試部分傳輸後取消、倍速或實機。

__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

寫入HDMA2($FF52),設定DMA來源位址的低位元組。硬體忽略位元0~3,向下對齊至16位元組邊界。例如HDMA1=$C6、HDMA2=$0F時來源為$C600。不啟動DMA。

語法

c
void __cgb_safe_set_hdma2(u8 value);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

暫存器值,型別u8,範圍0~255。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

每區塊16位元組。新傳輸可指定1~128區塊(16~2048位元組),控制值0表示16位元組,不是空傳輸。GDMA在完成前暫停CPU。HBlank DMA在每個可見行的HBlank傳輸一區塊,可跨影格繼續;呼叫端須在重用資料前確認完成。

GDMA應在LCD關閉或夠長的可寫階段使用。HBlank DMA須在LCD運作且非STAT模式0時啟動;範例從VBlank啟動。進行中請勿改變來源bank、目的VBK或來源資料。HALT也會暫停HBlank DMA。這些函式不額外等待、不保護中斷,也不檢查來源/目的範圍。

須另讀$FF55取得狀態:位元7為0表示進行中,$FF表示完成。取消後位元7為1,位元0~6保留剩餘區塊數減1;已複製的位元組不會還原。僅在傳輸進行中發出取消命令,否則將位元7寫為0會啟動GDMA。整個傳輸須位於有效來源記憶體與目的VRAM bank內,不要依賴位址溢位。

這些HDMA暫存器設定函式控制CGB硬體DMA。__vram_copy_dma是CPU複製迴圈的另一編譯器別名,不會因名稱含dma就使用此DMA控制器。

使用範例

c
__cgb_safe_set_hdma2(0x0F); // Low four bits are ignored by hardware: source remains $C600.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

完整程式使用位於$C600、以16位元組對齊的緩衝區比較三種傳輸。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)。以這些圖塊為目的的兩區塊HBlank傳輸,在同一VBlank內啟動並取消,不傳輸第一區塊。$FF55應為$81,一影格後圖樣仍不變。DMG下前兩行留白,僅這兩個保持檢查圖形顯示為黑色。文字為白底黑字。已在KOKURA測試雙模式DMG/CGB及CGB專用執行,FAILED CHECKS 000也檢查複製位元組及完成狀態;未測試部分傳輸後取消、倍速或實機。

__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

寫入HDMA3($FF53),設定VRAM中DMA目的位址的高位部分。僅位元0~4有效,目的地始終位於$8000–$9FFF。與HDMA4搭配使用;不選取VRAM bank,也不啟動DMA。

語法

c
void __cgb_safe_set_hdma3(u8 value);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

暫存器值,型別u8,範圍0~255。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

每區塊16位元組。新傳輸可指定1~128區塊(16~2048位元組),控制值0表示16位元組,不是空傳輸。GDMA在完成前暫停CPU。HBlank DMA在每個可見行的HBlank傳輸一區塊,可跨影格繼續;呼叫端須在重用資料前確認完成。

GDMA應在LCD關閉或夠長的可寫階段使用。HBlank DMA須在LCD運作且非STAT模式0時啟動;範例從VBlank啟動。進行中請勿改變來源bank、目的VBK或來源資料。HALT也會暫停HBlank DMA。這些函式不額外等待、不保護中斷,也不檢查來源/目的範圍。

須另讀$FF55取得狀態:位元7為0表示進行中,$FF表示完成。取消後位元7為1,位元0~6保留剩餘區塊數減1;已複製的位元組不會還原。僅在傳輸進行中發出取消命令,否則將位元7寫為0會啟動GDMA。整個傳輸須位於有效來源記憶體與目的VRAM bank內,不要依賴位址溢位。

這些HDMA暫存器設定函式控制CGB硬體DMA。__vram_copy_dma是CPU複製迴圈的另一編譯器別名,不會因名稱含dma就使用此DMA控制器。

使用範例

c
__cgb_safe_set_hdma3(0xE0); // Only low five bits select the high VRAM offset.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

完整程式使用位於$C600、以16位元組對齊的緩衝區比較三種傳輸。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)。以這些圖塊為目的的兩區塊HBlank傳輸,在同一VBlank內啟動並取消,不傳輸第一區塊。$FF55應為$81,一影格後圖樣仍不變。DMG下前兩行留白,僅這兩個保持檢查圖形顯示為黑色。文字為白底黑字。已在KOKURA測試雙模式DMG/CGB及CGB專用執行,FAILED CHECKS 000也檢查複製位元組及完成狀態;未測試部分傳輸後取消、倍速或實機。

__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

寫入HDMA4($FF54),設定VRAM目的位址的低位元組。位元0~3被忽略,向下對齊至16位元組邊界。HDMA3=$E0、HDMA4=$1F選取$8010。這是位址設定,不是傳輸長度。

語法

c
void __cgb_safe_set_hdma4(u8 value);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

暫存器值,型別u8,範圍0~255。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

每區塊16位元組。新傳輸可指定1~128區塊(16~2048位元組),控制值0表示16位元組,不是空傳輸。GDMA在完成前暫停CPU。HBlank DMA在每個可見行的HBlank傳輸一區塊,可跨影格繼續;呼叫端須在重用資料前確認完成。

GDMA應在LCD關閉或夠長的可寫階段使用。HBlank DMA須在LCD運作且非STAT模式0時啟動;範例從VBlank啟動。進行中請勿改變來源bank、目的VBK或來源資料。HALT也會暫停HBlank DMA。這些函式不額外等待、不保護中斷,也不檢查來源/目的範圍。

須另讀$FF55取得狀態:位元7為0表示進行中,$FF表示完成。取消後位元7為1,位元0~6保留剩餘區塊數減1;已複製的位元組不會還原。僅在傳輸進行中發出取消命令,否則將位元7寫為0會啟動GDMA。整個傳輸須位於有效來源記憶體與目的VRAM bank內,不要依賴位址溢位。

這些HDMA暫存器設定函式控制CGB硬體DMA。__vram_copy_dma是CPU複製迴圈的另一編譯器別名,不會因名稱含dma就使用此DMA控制器。

使用範例

c
__cgb_safe_set_hdma4(0x1F); // Destination aligns to $8010, not $801F.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

完整程式使用位於$C600、以16位元組對齊的緩衝區比較三種傳輸。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)。以這些圖塊為目的的兩區塊HBlank傳輸,在同一VBlank內啟動並取消,不傳輸第一區塊。$FF55應為$81,一影格後圖樣仍不變。DMG下前兩行留白,僅這兩個保持檢查圖形顯示為黑色。文字為白底黑字。已在KOKURA測試雙模式DMG/CGB及CGB專用執行,FAILED CHECKS 000也檢查複製位元組及完成狀態;未測試部分傳輸後取消、倍速或實機。

__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。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

每區塊16位元組。新傳輸可指定1~128區塊(16~2048位元組),控制值0表示16位元組,不是空傳輸。GDMA在完成前暫停CPU。HBlank DMA在每個可見行的HBlank傳輸一區塊,可跨影格繼續;呼叫端須在重用資料前確認完成。

GDMA應在LCD關閉或夠長的可寫階段使用。HBlank DMA須在LCD運作且非STAT模式0時啟動;範例從VBlank啟動。進行中請勿改變來源bank、目的VBK或來源資料。HALT也會暫停HBlank DMA。這些函式不額外等待、不保護中斷,也不檢查來源/目的範圍。

須另讀$FF55取得狀態:位元7為0表示進行中,$FF表示完成。取消後位元7為1,位元0~6保留剩餘區塊數減1;已複製的位元組不會還原。僅在傳輸進行中發出取消命令,否則將位元7寫為0會啟動GDMA。整個傳輸須位於有效來源記憶體與目的VRAM bank內,不要依賴位址溢位。

這些HDMA暫存器設定函式控制CGB硬體DMA。__vram_copy_dma是CPU複製迴圈的另一編譯器別名,不會因名稱含dma就使用此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位元組對齊的緩衝區比較三種傳輸。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)。以這些圖塊為目的的兩區塊HBlank傳輸,在同一VBlank內啟動並取消,不傳輸第一區塊。$FF55應為$81,一影格後圖樣仍不變。DMG下前兩行留白,僅這兩個保持檢查圖形顯示為黑色。文字為白底黑字。已在KOKURA測試雙模式DMG/CGB及CGB專用執行,FAILED CHECKS 000也檢查複製位元組及完成狀態;未測試部分傳輸後取消、倍速或實機。

__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)寫入一個位元組。__cgb_safe_set_ocpd是同一暫存器的別名。對應的索引暫存器選取位置;完整RGB15色彩需要低位及高位兩個位元組。

語法

c
__cgb_safe_set_obpd(/* 書式は実装と呼び出し例を参照 */);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

一個位元組,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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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為1時,資料寫入後自動遞增。位元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行依序為藍、綠、紅、藍、綠、紅、藍。各欄依序使用單色、RGB分量、索引範圍、整組調色盤、絕對範圍、標準暫存器名稱及暫存器別名寫入。

y=88處的7個8×8格依序顯示:紅色三角形(x=16)、bank 1中線寬1像素的藍色方框(32)、水平翻轉的綠色三角形(48)、垂直翻轉的紅色三角形(64)、雙向翻轉的藍色三角形(80)、位於紅色精靈前方且顏色0區域透出紅色的綠色三角形(96),以及關閉BG優先權後覆蓋三角形的實心紅色精靈(112)。座標以螢幕左上角為原點,單位為像素。空白處為白色,文字為黑色。

FAILED CHECKS 000表示RGB遮罩、含輸入2的布林旗標、調色盤索引回繞、跨調色盤寫入、在槽位31截斷、null/零長度不寫入,以及LCD關閉時的色彩讀回檢查均通過。測試4種組合:雙模式ROM分別在DMG與CGB執行、DMG專用ROM在DMG執行、CGB專用ROM在CGB執行。DMG下CGB MODE為0,三角形皆為黑色,bank與翻轉屬性不影響顯示,最下行最後兩格為黑色方塊。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皆將bank 1映射至$D000–$DFFF,例如8也映射為1。與__svbk_set不同,允許雙模式建置,但不回傳原bank編號。

語法

c
void __cgb_safe_set_svbk(u8 value);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

value

暫存器值,型別u8,範圍0~255。位元意義見該暫存器說明;傳入的是一個位元組,不是指標或位元組數量。

回傳值

無(void)。

使用條件與限制

編譯器以CGB模式檢查保護寫入,DMG模式下不寫入。safe僅指此模式保護,不儲存或還原原暫存器值。

切換bank會影響$D000–$DFFF的所有存取,固定WRAM的$C000–$CFFF保持不變。切換期間所需的堆疊與狀態須放在此視窗之外。範例使用--stack-bank=fixed並明確將狀態放入固定WRAM。須自行協調中斷與使用bank的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 bank 2的$D100保存1,在bank 3相同位址保存2,再讀回兩者並還原至bank 1。讀值分別選擇(16,24)的紅色8×8正方形與(16,48)的藍色1像素寬8×8方框。FAILED CHECKS 000檢查原始選擇值正規化、bank獨立性,以及以常數、變數、運算式、函式回傳值切換時的原編號回傳。另有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

遞增軟體巢狀深度並執行 DI。

語法

c
u8 __critical_enter();

回傳值

以 u8 權杖傳回先前深度:最外層為零,第一層巢狀範圍為一。

使用條件與限制

請依進入的相反順序將權杖傳給 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

降低巢狀深度;最外層權杖為零時執行 EI。

語法

c
void __critical_leave(u8 token);

參數

token

對應 __critical_enter 傳回的權杖。

回傳值

無傳回值。

使用條件與限制

非零權杖不執行 EI。任意傳入零可能在其他範圍仍啟用時開啟中斷;請正確配對呼叫。

使用範例

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

計算兩項無號內積。

語法

c
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

參數

a0

無號雙位元組值,0..65535。

a1

無號雙位元組值,0..65535。

b0

無號係數,0..255。

b1

無號係數,0..255。

回傳值

每項為 floor(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

計算三項無號內積。

語法

c
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

參數

a0

無號雙位元組值,0..65535。

a1

無號雙位元組值,0..65535。

a2

無號雙位元組值,0..65535。

b0

無號係數,0..255。

b1

無號係數,0..255。

b2

無號係數,0..255。

回傳值

每項為 floor(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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 區塊介面只攜帶 8 位元編號,不設定 MBC5 的第 9 位元。仍須遵守卡匣規則,包括 mapper 對編號 0 的處理。範例明確使用 MBC5 區塊 1–3;不要混用未記錄的 mapper 直接寫入。

使用範例

c
__farcall(3,callback);
bank_expect(callback_count==2 && callback_byte==0xD3,16);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

在完整程式的此處,__farcall 使 callback_count 達到 2。回呼從區塊 3 讀取 D3。隨後透過近指標讀取 home2 並確認 B2,驗證傳回時已還原區塊 2。

完整程式示範如何以區塊編號與近位址確定資料、如何跨區塊呼叫回呼,以及如何區分函式庫記錄與實際映射。所有數值皆為十六進位。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 各執行一次回呼。

__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 位元 6,選擇視窗圖塊映射表的基底位址。

語法

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

無號雙位元組值,0..65535。

b

無號係數,0..255。

回傳值

每項為 floor(a*b/256),加到 acc 後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

無號雙位元組值,0..65535。

b

無號係數,0..255。

回傳值

每項為 floor(a*b/256),傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

將兩個無號位元組相乘,傳回高位元組。

語法

c
u8 __mul8x8_hi(u8 a, u8 b);

參數

a

無號位元組,0..255。

b

無號位元組,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 位元值顯示;負數使用二補數位元樣式。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指定的按鈕中選擇一個操作進行連發。存在符合條件的新按下位元時,選擇最低有效位元並立即回傳。否則,只要目前操作仍在遮罩範圍內且仍被按住,就維持該操作。該操作結束後,選擇剩餘按住位元中的最低位元,但不立即產生脈衝。沒有符合條件的按住位元時清除執行狀態。

語法

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會停用所有操作並清除執行狀態。多個位元可提供多個候選,但每次呼叫只回傳選中的一個位元,不回傳組合脈衝遮罩。

回傳值

沒有操作脈衝時回傳0,否則回傳1, 2, 4, 8, 16, 32, 64, 128之一。非零結果是按鈕位元,不是正規化布林值,也不是連發次數。

使用條件與限制

共用遮罩為 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時略過初始等待相等分支,由連發間隔決定首次長按脈衝的時間。單位不是毫秒。

後續連發間隔,範圍0至255次呼叫。正值N表示首次連發之後,每N次有效按住更新產生一次脈衝。0表示初始等待結束後每次有效更新都產生脈衝,因此在該階段0與1均為每次更新一次。等待時間與間隔均為0時,每次按住更新都會產生脈衝。

每個預定輸入週期呼叫一次。每次呼叫都會修改狀態,不會等待VBlank。沒有新按下位元而接手已按住的按鈕時,該次呼叫回傳0並開始新的等待。這組函式每個狀態只處理一個目前操作;input_repeat則為八個按鈕分別維護獨立計時器。

使用範例

c
result=__padrep(actions,keys,trigger,48); // A|B: select A, the lower bit.
    if (result!=0) action_pulses++;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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

僅針對下方向推進一次連發狀態。下鍵剛按下時立即回傳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之一。非零結果是按鈕位元,不是正規化布林值,也不是連發次數。

使用條件與限制

共用遮罩為 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時略過初始等待相等分支,由連發間隔決定首次長按脈衝的時間。單位不是毫秒。

後續連發間隔,範圍0至255次呼叫。正值N表示首次連發之後,每N次有效按住更新產生一次脈衝。0表示初始等待結束後每次有效更新都產生脈衝,因此在該階段0與1均為每次更新一次。等待時間與間隔均為0時,每次按住更新都會產生脈衝。

每個預定輸入週期呼叫一次。每次呼叫都會修改狀態,不會等待VBlank。沒有新按下位元而接手已按住的按鈕時,該次呼叫回傳0並開始新的等待。這組函式每個狀態只處理一個目前操作;input_repeat則為八個按鈕分別維護獨立計時器。

使用範例

c
result=__padrep_down(down,keys,trigger);
    if (result!=0) down_pulses++;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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位元組連發狀態:清除目前操作與兩個計數器,儲存指定的初始等待時間及連發間隔。不讀取按鈕,也不產生連發脈衝。

語法

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

後續連發間隔,範圍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);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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,否則左鍵剛按下時回傳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之一。非零結果是按鈕位元,不是正規化布林值,也不是連發次數。

使用條件與限制

共用遮罩為 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時略過初始等待相等分支,由連發間隔決定首次長按脈衝的時間。單位不是毫秒。

後續連發間隔,範圍0至255次呼叫。正值N表示首次連發之後,每N次有效按住更新產生一次脈衝。0表示初始等待結束後每次有效更新都產生脈衝,因此在該階段0與1均為每次更新一次。等待時間與間隔均為0時,每次按住更新都會產生脈衝。

每個預定輸入週期呼叫一次。每次呼叫都會修改狀態,不會等待VBlank。沒有新按下位元而接手已按住的按鈕時,該次呼叫回傳0並開始新的等待。這組函式每個狀態只處理一個目前操作;input_repeat則為八個按鈕分別維護獨立計時器。

使用範例

c
result=__padrep_lr(lr,keys,trigger);
    if (result!=0) pulses++;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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的別名,兩者進入相同的編譯器實作。單一操作的選擇方式、時間規則、狀態配置與回傳值均相同。較明確的名稱有助於在呼叫位置看出這是透過遮罩選擇操作的連發處理。

從mask指定的按鈕中選擇一個操作進行連發。存在符合條件的新按下位元時,選擇最低有效位元並立即回傳。否則,只要目前操作仍在遮罩範圍內且仍被按住,就維持該操作。該操作結束後,選擇剩餘按住位元中的最低位元,但不立即產生脈衝。沒有符合條件的按住位元時清除執行狀態。

語法

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會停用所有操作並清除執行狀態。多個位元可提供多個候選,但每次呼叫只回傳選中的一個位元,不回傳組合脈衝遮罩。

回傳值

沒有操作脈衝時回傳0,否則回傳1, 2, 4, 8, 16, 32, 64, 128之一。非零結果是按鈕位元,不是正規化布林值,也不是連發次數。

使用條件與限制

共用遮罩為 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時略過初始等待相等分支,由連發間隔決定首次長按脈衝的時間。單位不是毫秒。

後續連發間隔,範圍0至255次呼叫。正值N表示首次連發之後,每N次有效按住更新產生一次脈衝。0表示初始等待結束後每次有效更新都產生脈衝,因此在該階段0與1均為每次更新一次。等待時間與間隔均為0時,每次按住更新都會產生脈衝。

每個預定輸入週期呼叫一次。每次呼叫都會修改狀態,不會等待VBlank。沒有新按下位元而接手已按住的按鈕時,該次呼叫回傳0並開始新的等待。這組函式每個狀態只處理一個目前操作;input_repeat則為八個按鈕分別維護獨立計時器。

使用範例

c
result=__padrep_mask(alias_state,keys,trigger,48);
    if (result!=0) alias_pulses++;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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

保留已設定的等待時間與間隔,取消目前操作並清除兩個經過時間計數器。可在關閉選單或切換輸入對象時使用,避免把先前長按的連發進度帶入下一次操作。

語法

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);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式把預設按鈕狀態送入四個獨立狀態,示範連發具體發生在哪次呼叫。等待時間3、間隔2時,編號0至7的八次呼叫會在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 位元按鈕遮罩。依序選擇各組並讀取兩次以等待訊號穩定,最後將組選擇位元恢復為 0x30。

語法

c
u8 __readpad();

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

回傳值

共用 BTN_* 格式的 8 位元遮罩。多個按鈕可以同時設為 1;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.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於比較三種按鈕遮罩格式,以及將目前狀態與新按下狀態合併的回傳值。同時按住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 操作按鈕組,回傳低四位:A=1、B=2、Select=4、Start=8。這不同於占高四位的共用 BTN_A~BTN_START。讀取後將 JOYP 選擇恢復為 0x30。

語法

c
u8 __readpadbtn();

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

回傳值

依上述位元順序回傳 0~15 的遮罩。可能同時設定多個位元;應以 result & bit 檢查特定按鈕,而非判斷整個結果是否等於 1。

使用條件與限制

此呼叫重新讀取硬體狀態,不更新 input_* 或 nes_pad1_* 的快取,也不產生按下、放開或重複事件。需要偵測變化時,請保存讀取結果,與自行保存的前次遮罩比較。

使用範例

c
buttons=__readpadbtn(); // A: 1, not BTN_A (16).

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於比較三種按鈕遮罩格式,以及將目前狀態與新按下狀態合併的回傳值。同時按住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 的方向鍵組。結果的位元 0~3 為右=1、左=2、上=4、下=8,位元 4~7 為 0。不包含操作按鈕,讀取後將 JOYP 選擇恢復為 0x30。

語法

c
u8 __readpaddir();

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

回傳值

依上述位元順序回傳 0~15 的遮罩。可能同時設定多個位元;應以 result & bit 檢查特定按鈕,而非判斷整個結果是否等於 1。

使用條件與限制

此呼叫重新讀取硬體狀態,不更新 input_* 或 nes_pad1_* 的快取,也不產生按下、放開或重複事件。需要偵測變化時,請保存讀取結果,與自行保存的前次遮罩比較。

使用範例

c
directions=__readpaddir(); // Right: 1. The held A button is not included.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此程式用於比較三種按鈕遮罩格式,以及將目前狀態與新按下狀態合併的回傳值。同時按住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

計算兩項帶正負號的內積。

語法

c
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);

參數

a0

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a1

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b0

帶正負號的位元組樣式,係數為 −128..127。

b1

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 2*trunc(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

計算兩項帶正負號的內積。

語法

c
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

參數

a0

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a1

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b0

帶正負號的位元組樣式,係數為 −128..127。

b1

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 trunc(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

計算三項帶正負號的內積。

語法

c
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

參數

a0

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a1

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a2

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b0

帶正負號的位元組樣式,係數為 −128..127。

b1

帶正負號的位元組樣式,係數為 −128..127。

b2

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 2*trunc(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

計算三項帶正負號的內積。

語法

c
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

參數

a0

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a1

表示 −32768..32767 的帶正負號 16 位元位元樣式。

a2

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b0

帶正負號的位元組樣式,係數為 −128..127。

b1

帶正負號的位元組樣式,係數為 −128..127。

b2

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 trunc(a*b/256);加總後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

目標位址固定為 0x9800 + 32*y + x,不會跟隨 LCDC 的背景或視窗地圖設定。

不等待 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 圖塊編號上排為 tile,tile+1,下排為 tile+2,tile+3,超過 255 後回繞。不傳輸至 VRAM。

語法

c
__settilebg16_buf(buf, x, y, tile);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

buf

每圖塊列 32 位元組的 RAM 緩衝區。完整 32×32 地圖需要 1024 位元組。左上角偏移為 64*y + 2*x,四個寫入位置相对此處為 0,1,32,33。

x, y

以 16×16 像素單元為單位的座標;完整 32×32 圖塊緩衝區中各為 0~15。與單圖塊寫入函式使用的 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

圖塊座標 (2,2) 處顯示上排 AB、下排 CD,即單元座標 (1,1) 的一個 16×16 像素單元。緩衝區寫入範例先確認 RAM 準備操作未改變 VRAM,再傳輸;傳輸函式範例則先逐位元組設定 RAM 資料,再執行傳輸。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=2)、像素座標 (16,16) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 緩衝區中相鄰的兩列圖塊傳輸到 LCDC 所選背景地圖。來源偏移為 64*y + x,從該列及其下方相隔 32 位元組的一列各複製 count 位元組。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 VBlank 內完成。

語法

c
__settilebg16_flush(buf, y, x, count);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

buf

可讀的圖塊地圖緩衝區,列間距為 32 位元組,完整地圖占 1024 位元組。雙緩衝區形式接收配置相同的獨立圖塊編號與屬性陣列,兩者不交錯儲存。

y, x, count

參數順序為 y,x,count。y 是 16 像素單元列(0~15),x 與 count 以 8 像素圖塊計。須滿足 x+count <= 32。傳輸單元 (2,3) 時使用 y=3,x=4,count=2。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

圖塊座標 (2,2) 處顯示上排 AB、下排 CD,即單元座標 (1,1) 的一個 16×16 像素單元。緩衝區寫入範例先確認 RAM 準備操作未改變 VRAM,再傳輸;傳輸函式範例則先逐位元組設定 RAM 資料,再執行傳輸。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=2)、像素座標 (16,16) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 圖塊編號上排為 tile,tile+1,下排為 tile+2,tile+3,超過 255 後回繞。不傳輸至 VRAM。

也會在獨立屬性緩衝區的對應四個單元寫入相同的 attr。兩個緩衝區均為一般 RAM,因此 DMG 也會執行此準備操作。

語法

c
__settilebg16cgb_buf(tilebuf, attrbuf, x, y, tile, attr);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

tilebuf, attrbuf

每圖塊列 32 位元組的 RAM 緩衝區。完整 32×32 地圖需要 1024 位元組。左上角偏移為 64*y + 2*x,四個寫入位置相对此處為 0,1,32,33。

x, y

以 16×16 像素單元為單位的座標;完整 32×32 圖塊緩衝區中各為 0~15。與單圖塊寫入函式使用的 8×8 圖塊座標不同。

tile

0~255 的圖塊編號。LCDC 位元 4 決定編號如何定址圖塊影像資料;這不是 VRAM 位元組位址。

attr

CGB 屬性位元組:位元 0~2 選擇調色盤 0~7,位元 3 選擇影像資料 bank,位元 5、6 分別控制水平及垂直翻轉,位元 7 設定背景/視窗優先順序。未使用的位元 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

圖塊座標 (2,2) 處顯示上排 AB、下排 CD,即單元座標 (1,1) 的一個 16×16 像素單元。緩衝區寫入範例先確認 RAM 準備操作未改變 VRAM,再傳輸;傳輸函式範例則先逐位元組設定 RAM 資料,再執行傳輸。

CGB 上四個單元的屬性皆為 2,因此四個字元皆為藍色;DMG 上為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=2)、像素座標 (16,16) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 緩衝區中相鄰的兩列圖塊傳輸到 LCDC 所選背景地圖。來源偏移為 64*y + x,從該列及其下方相隔 32 位元組的一列各複製 count 位元組。

依序寫入圖塊編號與 CGB 屬性。DMG 只寫入編號。CGB 呼叫前請選取 VRAM bank 0;屬性寫入 bank 1,結束時為 VBK=0。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 VBlank 內完成。

語法

c
__settilebg16cgb_flush(tilebuf, attrbuf, y, x, count);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

tilebuf, attrbuf

可讀的圖塊地圖緩衝區,列間距為 32 位元組,完整地圖占 1024 位元組。雙緩衝區形式接收配置相同的獨立圖塊編號與屬性陣列,兩者不交錯儲存。

y, x, count

參數順序為 y,x,count。y 是 16 像素單元列(0~15),x 與 count 以 8 像素圖塊計。須滿足 x+count <= 32。傳輸單元 (2,3) 時使用 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

圖塊座標 (2,2) 處顯示上排 AB、下排 CD,即單元座標 (1,1) 的一個 16×16 像素單元。緩衝區寫入範例先確認 RAM 準備操作未改變 VRAM,再傳輸;傳輸函式範例則先逐位元組設定 RAM 資料,再執行傳輸。

CGB 上四個單元的屬性皆為 2,因此四個字元皆為藍色;DMG 上為黑色。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=2)、像素座標 (16,16) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 trunc(a*b/256),加到 acc 後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 2*trunc(a*b/256),加到 acc 後傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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

表示 −32768..32767 的帶正負號 16 位元位元樣式。

b

帶正負號的位元組樣式,係數為 −128..127。

回傳值

每項為 2*trunc(a*b/256),傳回低 16 位元。

使用條件與限制

GB 會將乘積除以 256;帶正負號的結果朝零截斷。Q1.7 版本再將截斷值乘二,因此可能與直接除以 128 的精確結果相差一。各項分別取整,最後加總以 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 位元值顯示;負數使用二補數位元樣式。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 位元組。

語法

c
u8 __sram_read8(u16 address);

參數

address

位於 $A000..$BFFF 的 CPU 位址。

回傳值

傳回該位元組,不檢查 RAM 是否存在或資料是否有效。

使用條件與限制

存取前將 0x0A 寫入 $0000 以啟用 RAM,完成後寫入零以停用。不選擇記憶體庫或驗證位址;RAM 記憶體庫須由呼叫端設定。不管理存檔標頭或檢查碼。

使用範例

c
*((u8*)0x4000)=0;result[12]=__sram_read8(0xA006);result[19]=*((u8*)0xA006);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

儲存 7/42/255 三個位元組,再測試長度不符、無效插槽及損壞的檢查碼。成功載入時中間位元組為 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 位元組。

語法

c
void __sram_write8(u16 address, u8 value);

參數

address

位於 $A000..$BFFF 的 CPU 位址。

value

位元組值 0..255。

回傳值

無傳回值。

使用條件與限制

存取前將 0x0A 寫入 $0000 以啟用 RAM,完成後寫入零以停用。不選擇記憶體庫或驗證位址;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 三個位元組,再測試長度不符、無效插槽及損壞的檢查碼。成功載入時中間位元組為 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 bank編號。讀取SVBK位元0~2,並將原始選擇值0正規化為1。回傳的是bank編號,不是RAM容量、位址或指標。

語法

c
u8 __svbk_get();

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

回傳值

目前有效bank編號,u8,1~7;原始選擇值0回傳為1。

使用條件與限制

__svbk_get與__svbk_set要求--cgb=cgb_only或#pragma rom_cgb cgb_only。DMG及雙模式建置會報KQ2103,即使執行時條件會略過呼叫。僅在不同介面約定適用時,才使用具保護的原始設定函式。

切換bank會影響$D000–$DFFF的所有存取,固定WRAM的$C000–$CFFF保持不變。切換期間所需的堆疊與狀態須放在此視窗之外。範例使用--stack-bank=fixed並明確將狀態放入固定WRAM。須自行協調中斷與使用bank的DMA;這些API不建立臨界區。

使用範例

c
if (__svbk_get()!=1) failures++; // Normalize raw selector zero to effective bank 1.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

CGB專用完整程式在WRAM bank 2的$D100保存1,在bank 3相同位址保存2,再讀回兩者並還原至bank 1。讀值分別選擇(16,24)的紅色8×8正方形與(16,48)的藍色1像素寬8×8方框。FAILED CHECKS 000檢查原始選擇值正規化、bank獨立性,以及以常數、變數、運算式、函式回傳值切換時的原編號回傳。另有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 bank,並回傳切換前的有效編號。如需還原呼叫端的bank,請保存回傳值。不會複製或清空記憶體內容,也不會自動還原bank。

語法

c
u8 __svbk_set(u8 bank);

此呼叫格式中的參數名稱用於說明編譯器內建功能,並非需要另外加入的 C 宣告。

參數

bank

要求的bank編號為u8,範圍1~7。越界常數產生KQ2102。此內建函式不檢查動態值範圍;請在呼叫前驗證,不要依賴暫存器遮罩。

回傳值

切換前的有效bank編號,u8,1~7。回傳原bank,不是新選取的bank。

使用條件與限制

__svbk_get與__svbk_set要求--cgb=cgb_only或#pragma rom_cgb cgb_only。DMG及雙模式建置會報KQ2103,即使執行時條件會略過呼叫。僅在不同介面約定適用時,才使用具保護的原始設定函式。

切換bank會影響$D000–$DFFF的所有存取,固定WRAM的$C000–$CFFF保持不變。切換期間所需的堆疊與狀態須放在此視窗之外。範例使用--stack-bank=fixed並明確將狀態放入固定WRAM。須自行協調中斷與使用bank的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 bank 2的$D100保存1,在bank 3相同位址保存2,再讀回兩者並還原至bank 1。讀值分別選擇(16,24)的紅色8×8正方形與(16,48)的藍色1像素寬8×8方框。FAILED CHECKS 000檢查原始選擇值正規化、bank獨立性,以及以常數、變數、運算式、函式回傳值切換時的原編號回傳。另有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的len位元組複製到dst,完成後才回傳,不將操作加入佇列。來源和目的位址每次增加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,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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

立即以單一位元組value填滿從dst開始的連續len位元組。重複的是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的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

len

連續位元組數,範圍0至65535,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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實心橫條,相鄰格為空白。長度由變數或函式回傳值計算;三個位元組仍須全部寫入值1。

附帶的自製圖形中,圖塊1為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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 運作時,超過 153 的值不會完成等待。

回傳值

無傳回值。

使用條件與限制

目前已在目標掃描線時立即返回;若已經過該線,則等到下一影格。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 運動與碰撞

提供盒體積分、AABB 接觸處理,以及可選用的表面反應求解器。物體使用中心與半長表示;active=0 會排除該欄位,inv_mass_q8<=0 則視為靜態物體。KQRect 使用左上角座標與寬度/高度。盒體配對接觸不使用物體的 restitution 欄位;表面反應則使用 KQSurface2D 係數。

位置、半徑、各軸半長、速度與重力應使用一致的整數單位。每一步代表一個模擬刻度;範例中一單位等於一像素。這些函式不會自動將位置轉為 Q8.8,也不負責繪製。

世界僅借用陣列。呼叫端負責配置並持有物體儲存空間,且須在使用期間維持其有效。初始化世界不會初始化陣列元素。

__smul16x8

將帶正負號 16 位元值乘以帶正負號 8 位元係數,再除以 256,向零截斷。

語法

c
s16 __smul16x8(s16 a, s8 b);

參數

a

要縮放的帶正負號 16 位元值。

b

範圍為 -128..127 的帶正負號位元組係數。

回傳值

傳回縮放後的帶正負號 16 位元乘積。

使用條件與限制

係數範圍為 -128..127,以 256 代表 1.0。乘積不分正負皆向零截斷:-257*64/256 傳回 -64。

使用範例

c
result[13]=__smul16x8(-257,64);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

左側為初始狀態,右側為一個刻度後。紅色 16x16 盒體的中心為 (24,24)、速度為 (4,16),之後到達 (28,36),在藍色地板上使 Y 速度歸零。面板原點位於螢幕座標 (8,48) 與 (88,48)。DMG 上所有標記皆為黑色。RAM 檢查也涵蓋初始化、設定函式、積分、矩形/點查詢與帶正負號的縮放運算。

__smul16x8: DMG — 左側為初始狀態,右側為一個刻度後。紅色 16x16 盒體的中心為 (24,24)、速度為 (4,16),之後到達 (28,36),在藍色地板上使 Y 速度歸零。面板原點位於螢幕座標 (8,48) 與 (88,48)。DMG 上所有標記皆為黑色。RAM 檢查也涵蓋初始化、設定函式、積分、矩形/點查詢與帶正負號的縮放運算。
DMG — 左側為初始狀態,右側為一個刻度後。紅色 16x16 盒體的中心為 (24,24)、速度為 (4,16),之後到達 (28,36),在藍色地板上使 Y 速度歸零。面板原點位於螢幕座標 (8,48) 與 (88,48)。DMG 上所有標記皆為黑色。RAM 檢查也涵蓋初始化、設定函式、積分、矩形/點查詢與帶正負號的縮放運算。
__smul16x8: CGB — 左側為初始狀態,右側為一個刻度後。紅色 16x16 盒體的中心為 (24,24)、速度為 (4,16),之後到達 (28,36),在藍色地板上使 Y 速度歸零。面板原點位於螢幕座標 (8,48) 與 (88,48)。DMG 上所有標記皆為黑色。RAM 檢查也涵蓋初始化、設定函式、積分、矩形/點查詢與帶正負號的縮放運算。
CGB — 左側為初始狀態,右側為一個刻度後。紅色 16x16 盒體的中心為 (24,24)、速度為 (4,16),之後到達 (28,36),在藍色地板上使 Y 速度歸零。面板原點位於螢幕座標 (8,48) 與 (88,48)。DMG 上所有標記皆為黑色。RAM 檢查也涵蓋初始化、設定函式、積分、矩形/點查詢與帶正負號的縮放運算。

完整範例程式

建置命令

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 將數值轉為十進位,僅占所需格數。替換不同長度的值時,須預留並清空完整欄位。

rpg.h 宣告的 RNG 函式實作於 lib/rng.c,共用編譯器的 GB 產生器:rng_seed 重設狀態,rng8/rng_next8 消耗一個位元組,rng16/rng_next16 消耗兩個,rand_range/rng_range、rng_chance 與 weighted_choice 則調整取值以供遊戲選擇。呼叫這些函式時,請隨程式編譯 rng.c。要讓測試可重現,須固定種子與呼叫順序;以取餘數進行的選擇不保證精確機率。

RLE 解碼使用 count/value 位元組配對與零 count 終止符。rle_decode 由一般指標讀取並解壓至 RAM;rle_decode_far 由指定 ROM 記憶體庫的指標讀取至 RAM;__rle_decode_vram 則以考慮 LCD 時序的寫入,將記憶體庫輸入直接解壓至 VRAM。RAM 函式位於 lib/rle.c,直接 VRAM 操作由編譯器產生。三者皆傳回輸出位元組數,且需要有效串流與足夠目的空間。

rpg.h 宣告的遊戲狀態儲存由 lib/flags.c 實作。三個旗標函式操作 2048 個獨立布林位元;quest_state 與 quest_set_state 則操作 64 個獨立位元組,代表遊戲定義的階段。使用時須編譯 flags.c 並遵守 ID 範圍。若進度須在關機後保留,請使用獨立存檔系統。

視窗對話共用一個作用中矩形、游標及字元速度設定。text_open 初始化區域,text_close 清除區域,text_set_speed 設定逐字 VBlank 等待。text_print 與 text_print_far 解讀控制串流,text_choice 執行阻塞式垂直選單。請載入圖塊、安排適當區域大小,並規劃遊戲如何處理這些阻塞呼叫。

提供地圖/複合圖塊繪製、腳本、SRAM 存檔及尋路。一般格子為 8×8 像素,複合圖塊為 16×16。載入/繪製、移動遮罩及攻擊遮罩各自獨立;遊戲負責協調圖形載入與事件執行。

搜尋使用約 4 KiB 的共用工作區,且不可重入。地圖最多 1024 格,探索距離最多 254。距離 0xFF 表示未到達,因此無法表示 255 步的路徑。成本/遮罩輸出需要 width*height 個可寫入位元組。

預設為四個 512 位元組插槽,每個含六位元組標頭及最多 506 位元組資料。範例使用搭配 8 KiB SRAM 的 MBC5。呼叫結束時會停用 RAM,不還原先前的記憶體庫或啟用狀態。

選單同時支援等待選擇完成的阻塞模式,以及由遊戲迴圈分別呼叫輸入與繪圖的模式。道具選單會顯示名稱及數量。

__bit_clear

不清除同一位元組的其他內容,只將所選旗標清為 0。可用於移除暫時狀態等單一條件。再次清除同一位元後仍為 0。

語法

c
void __bit_clear(u8* p, u16 bit_index);

參數

p

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit_index

從零起算的 u16 位元索引(0–65535),不是位元組偏移或位元遮罩。索引 0 選取位元組 0 的遮罩 0x01,索引 7 選取位元組 0 的 0x80,索引 8 選取位元組 1 的 0x01。一般公式為位元組偏移 index / 8,遮罩 1 << (index % 8)。N 位元組陣列須滿足 index < 8*N,且 CPU 位址範圍有效。

回傳值

無(void)。

使用條件與限制

呼叫端必須提供足夠空間。不提供陣列長度引數、邊界裁切、記憶體庫切換或自動圖形上傳。過大的位元編號可能選到配置陣列外很遠的位元組。請保持記憶體映射穩定;不可將這些 RAM 旗標輔助函式當作讀寫具有副作用的硬體暫存器之捷徑。

設定、清除及反轉會先讀取一個位元組、修改所選位元,再寫回。此序列不是原子更新。請協調主迴圈與中斷處理常式的寫入,避免期間對同一位元組其他位元的更新遺失。

使用範例

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(二進位 10100010,十進位 162);CLEAR BIT2 列應顯示 162。再次清除位元 2,結果不變;其他已儲存旗標在兩次呼叫後都保留。

GB 預期的十進位各列為: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,使 0x80 變為 0x82,而不修改位元組 0。DMG 為白底黑字,CGB 為白底藍字。

程式檢查每個顯示的狀態轉換,並顯示失敗次數。設定、清除及反轉本身沒有傳回值;各列顯示操作後的 RAM 位元組。另以模擬器在每平台 136 個案例中,比對含保護區之緩衝區的全部 520 位元組,涵蓋跨位元組及 256 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 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。例如可標記任務已完成或道具已取得。已為 1 的位元再次設定後仍不變;此操作不是切換。

語法

c
void __bit_set(u8* p, u16 bit_index);

參數

p

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit_index

從零起算的 u16 位元索引(0–65535),不是位元組偏移或位元遮罩。索引 0 選取位元組 0 的遮罩 0x01,索引 7 選取位元組 0 的 0x80,索引 8 選取位元組 1 的 0x01。一般公式為位元組偏移 index / 8,遮罩 1 << (index % 8)。N 位元組陣列須滿足 index < 8*N,且 CPU 位址範圍有效。

回傳值

無(void)。

使用條件與限制

呼叫端必須提供足夠空間。不提供陣列長度引數、邊界裁切、記憶體庫切換或自動圖形上傳。過大的位元編號可能選到配置陣列外很遠的位元組。請保持記憶體映射穩定;不可將這些 RAM 旗標輔助函式當作讀寫具有副作用的硬體暫存器之捷徑。

設定、清除及反轉會先讀取一個位元組、修改所選位元,再寫回。此序列不是原子更新。請協調主迴圈與中斷處理常式的寫入,避免期間對同一位元組其他位元的更新遺失。

使用範例

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(十進位 166)。SET BIT1 列應顯示 166。範例再次設定該位元,確認仍為 0xA6,示範重複設定不會反轉旗標。

GB 預期的十進位各列為: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,使 0x80 變為 0x82,而不修改位元組 0。DMG 為白底黑字,CGB 為白底藍字。

程式檢查每個顯示的狀態轉換,並顯示失敗次數。設定、清除及反轉本身沒有傳回值;各列顯示操作後的 RAM 位元組。另以模擬器在每平台 136 個案例中,比對含保護區之緩衝區的全部 520 位元組,涵蓋跨位元組及 256 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 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

不修改陣列,從打包位元陣列讀取一個旗標。每個 RAM 位元組可容納八個獨立的開關旗標。結果表示所選位元是清除或設定狀態;其數值表示方式依目標平台而異。

語法

c
u8 __bit_test(const u8* p, u16 bit_index);

參數

p

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit_index

從零起算的 u16 位元索引(0–65535),不是位元組偏移或位元遮罩。索引 0 選取位元組 0 的遮罩 0x01,索引 7 選取位元組 0 的 0x80,索引 8 選取位元組 1 的 0x01。一般公式為位元組偏移 index / 8,遮罩 1 << (index % 8)。N 位元組陣列須滿足 index < 8*N,且 CPU 位址範圍有效。

回傳值

u8:位元清除時恰為 0,設定時為 1。GB 實作會將套用遮罩後的位元組正規化為這兩個值。

使用條件與限制

呼叫端必須提供足夠空間。不提供陣列長度引數、邊界裁切、記憶體庫切換或自動圖形上傳。過大的位元編號可能選到配置陣列外很遠的位元組。請保持記憶體映射穩定;不可將這些 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(二進位 10100100)。測試位元 1 傳回 0;測試位元 7 在 GB 傳回 1,在 FC 傳回 128。範例也將此非零結果轉成共用開關旗標,並確認原始位元組未改變。請查看下方畫面的 TEST OFF 與 TEST ON。

GB 預期的十進位各列為: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,使 0x80 變為 0x82,而不修改位元組 0。DMG 為白底黑字,CGB 為白底藍字。

程式檢查每個顯示的狀態轉換,並顯示失敗次數。設定、清除及反轉本身沒有傳回值;各列顯示操作後的 RAM 位元組。另以模擬器在每平台 136 個案例中,比對含保護區之緩衝區的全部 520 位元組,涵蓋跨位元組及 256 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 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,其他位元保留。若兩次呼叫之間沒有其他程式寫入該位元,對同一位元呼叫兩次即可還原原始狀態。

語法

c
void __bit_toggle(u8* p, u16 bit_index);

參數

p

指向 CPU 可存取記憶體中旗標陣列首位元組的指標。此呼叫不配置或初始化陣列。設定、清除及反轉操作須使用可寫入 RAM;測試操作只讀取。

bit_index

從零起算的 u16 位元索引(0–65535),不是位元組偏移或位元遮罩。索引 0 選取位元組 0 的遮罩 0x01,索引 7 選取位元組 0 的 0x80,索引 8 選取位元組 1 的 0x01。一般公式為位元組偏移 index / 8,遮罩 1 << (index % 8)。N 位元組陣列須滿足 index < 8*N,且 CPU 位址範圍有效。

回傳值

無(void)。

使用條件與限制

呼叫端必須提供足夠空間。不提供陣列長度引數、邊界裁切、記憶體庫切換或自動圖形上傳。過大的位元編號可能選到配置陣列外很遠的位元組。請保持記憶體映射穩定;不可將這些 RAM 旗標輔助函式當作讀寫具有副作用的硬體暫存器之捷徑。

設定、清除及反轉會先讀取一個位元組、修改所選位元,再寫回。此序列不是原子更新。請協調主迴圈與中斷處理常式的寫入,避免期間對同一位元組其他位元的更新遺失。

使用範例

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(十進位 34),因此 TOGGLE BIT7 顯示 034。再次反轉會還原為 0xA2,RESTORE 顯示 162。這兩個結果示範在保留其他位元的同時,完成雙向反轉。

GB 預期的十進位各列為: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,使 0x80 變為 0x82,而不修改位元組 0。DMG 為白底黑字,CGB 為白底藍字。

程式檢查每個顯示的狀態轉換,並顯示失敗次數。設定、清除及反轉本身沒有傳回值;各列顯示操作後的 RAM 位元組。另以模擬器在每平台 136 個案例中,比對含保護區之緩衝區的全部 520 位元組,涵蓋跨位元組及 256 位元組邊界的常數/執行階段索引,以及八種可能的非零位元測試遮罩。這些是 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/NES 圖塊。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 從偏移量 1 開始複製 16 位元組的規律資料。memory_check 會比對整個來源與目標陣列;COPY16 列應顯示 016。緩衝區首位元組保留作為保護區,並檢查複製範圍之後的位元組也未改變。

完整程式會在每次操作前初始化兩個 264 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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 位元組圖塊圖樣。它依位址遞增方向逐位元組複製,並非一次 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 從偏移量 1 開始複製 32 位元組的規律資料。memory_check 會比對整個來源與目標陣列;COPY32 列應顯示 032。緩衝區首位元組保留作為保護區,並檢查複製範圍之後的位元組也未改變。

完整程式會在每次操作前初始化兩個 264 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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

目標可寫入記憶體,容量必須足以容納實際複製的全部位元組。除了零位元組傳送外,不會檢查null或無效目標位址。

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複製兩個圖案,共32位元組。程式逐一比較位元組,檢查區塊1已恢復,並確認零長度複製不會改變0x55哨兵值。

預期圖像中,紅色一列從像素座標(16,32)開始,占72×8像素。依序重複8×8三角形、邊框厚1像素的8×8空心正方形、填滿左側4欄的圖塊,共三組。三角形從左上角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存取規則。

立即以單一位元組value填滿從dst開始的連續len位元組。重複的是1位元組,不是整個圖塊圖樣。填滿對映表位元組會重複圖塊編號;填滿圖樣位元組則會改變編碼後的像素位元平面。

語法

c
void __fill_tilemap(u16 dst, u8 value, u16 len);

參數

dst

目的首位元組位址,通常位於VRAM 0x8000–0x9FFF。整個範圍必須有效。CGB使用目前VRAM記憶體庫;對映表區域的記憶體庫0存放圖塊編號,記憶體庫1存放屬性。這些函式不依LCDC選擇對映表,不在列尾換列,也不配置VRAM。

value

重複寫入0至255的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

len

連續位元組數,範圍0至65535,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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實心橫條,相鄰格為空白。長度由變數或函式回傳值計算;三個位元組仍須全部寫入值1。

附帶的自製圖形中,圖塊1為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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 位元 3,選擇背景圖塊映射表的基底位址。

語法

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

將兩點間 X 差與 Y 差的絕對值相加。

語法

c
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);

參數

x1

第一點的 X 座標,0..255。

y1

第一點的 Y 座標,0..255。

x2

第二點的 X 座標,0..255。

y2

第二點的 Y 座標,0..255。

回傳值

傳回 |x1−x2|+|y1−y2| 的低位元組;超過 255 的距離會回捲。

使用範例

c
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度: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

將格子座標轉為逐列儲存的陣列索引。

語法

c
u16 __map_index(u8 x, u8 y, u8 width);

參數

x

欄,0..255,通常小於 width。

y

列,0..255。

width

每列格子數,0..255。

回傳值

以 u16 傳回 y*width+x;不驗證座標。

使用範例

c
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度: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 位元數值,可用於複製超過 255 位元組的資料,例如 RAM 中的整張表。讀寫依位址遞增順序進行。

語法

c
void __memcpy(void* dst, const void* src, u16 count);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

count

以 u16 指定的位元組數(0~65535),而非元素個數。型別的最大值不代表目標機器能提供同樣大小的有效緩衝區。整個範圍必須位於目前的記憶體映射之內。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 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 從偏移量 1 開始複製 257 位元組的規律資料。memory_check 會比對整個來源與目標陣列;COPY WORD 列應顯示 257。緩衝區首位元組保留作為保護區,並檢查複製範圍之後的位元組也未改變。

完整程式會在每次操作前初始化兩個 264 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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 位元組的資料區塊。傳輸長度能以一個位元組表示時,可使用此形式。_small 表示長度的位元寬度,並不保證特定的執行時間或產生的程式碼大小。

語法

c
void __memcpy_small(void* dst, const void* src, u8 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

src

指向來源區域第一個可讀取位元組的指標。複製期間,整個來源區域必須維持可存取且內容不變。

len

以 u8 指定的位元組數,範圍為 0~255。不要傳入 256 來表示完整的 256 位元組區塊,這個參數的型別無法表示該長度。256 位元組以上請使用名稱不含 _small 的對應函式。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 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 從偏移量 1 開始複製 255 位元組的規律資料。memory_check 會比對整個來源與目標陣列;COPY BYTE 列應顯示 255。緩衝區首位元組保留作為保護區,並檢查複製範圍之後的位元組也未改變。

完整程式會在每次操作前初始化兩個 264 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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

以相同的單一位元組值填滿目標區域。長度為 16 位元,適合清空較大的緩衝區或初始化表。例如,填入值為 0x5A 時會寫入 5A 5A 5A…,並非重複寫入一個多位元組整數。

語法

c
void __memset(void* dst, u8 value, u16 count);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

value

在每個目標位址重複寫入的單一位元組值,範圍為 0~255。使用 0 可將該區域清為零。如需將 u16 陣列初始化為非零數值,應逐一對元素賦值。

count

以 u16 指定的位元組數(0~65535),而非元素個數。型別的最大值不代表目標機器能提供同樣大小的有效緩衝區。整個範圍必須位於目前的記憶體映射之內。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 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 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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 個位元組填入同一個單一位元組值。適合初始化小型工作緩衝區。所需長度超過 255 位元組時,請使用 __memset。

語法

c
void __memset_small(void* dst, u8 value, u8 len);

參數

dst

指向目標區域第一個可寫入位元組的指標。必須預先準備足夠的連續空間;函式不會自動配置緩衝區。

value

在每個目標位址重複寫入的單一位元組值,範圍為 0~255。使用 0 可將該區域清為零。如需將 u16 陣列初始化為非零數值,應逐一對元素賦值。

len

以 u8 指定的位元組數,範圍為 0~255。不要傳入 256 來表示完整的 256 位元組區塊,這個參數的型別無法表示該長度。256 位元組以上請使用名稱不含 _small 的對應函式。

回傳值

無(void)。

使用條件與限制

長度為 0 時不傳輸任何位元組,但引數運算式中的副作用仍會發生。範例將兩個回傳指標的函式作為引數,並確認即使複製長度為 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 位元組緩衝區,接著檢查來源與目標的每一個位元組,以及前後不應改變的保護區域。正確顯示為 COPY WORD 257、FILL WORD 257、COPY BYTE 255、FILL BYTE 255、COPY16 016、COPY32 032、ZERO CALLS 002、FAILED 000。WORD/BYTE 表示長度的型別,而非複製元素的大小。這些數字來自範例的檢查程序,並不是這些無回傳值函式的回傳值。

擷取畫面中,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

將指定記憶體庫的 count/value 串流直接解壓至 VRAM,無須 RAM 中間輸出緩衝區。編譯器產生迴圈,逐對讀取,使用考慮 LCD 存取時序的 VRAM 寫入輔助函式填入重複資料,最後傳回輸出位元組數。適用於載入壓縮圖塊資料或以位元組表示的圖塊地圖。

語法

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

參數

dst

第一個輸出位元組的 16 位元 CPU 位址,範圍須在 0x8000–0x9FFF。請保留足夠的連續位元組,且不得越過 0x9FFF。例如在無號圖塊資料配置中,0x8860 為圖塊 134。函式不配置圖塊,也不檢查目的範圍。

bank

來源 ROM 記憶體庫編號,型別為 u8。記憶體庫與 CPU 位址須符合卡匣映射器及資產位置。每次讀取編碼位元組後都會還原記憶體庫選擇。它不是目的 VRAM 記憶體庫選擇,也無法表示 9 位元 MBC5 記憶體庫編號。

src

指向指定記憶體庫內編碼串流的 CPU 指標。函式會遞增位址,但記憶體庫編號固定。包含結尾零在內的完整串流須位於可讀映射中;越過 0x7FFF 不會自動接續到下一個 ROM 記憶體庫。

回傳值

以 u16 傳回已寫入位元組數。一開始就是零 count 時,不輸出任何資料並傳回 0。終止符不會複製。解壓長度須能以 u16 表示;回傳值沒有獨立錯誤碼,也不是圖塊數。

使用條件與限制

此編碼以位元組為單位,不是文字。例如 {3, 0x12, 2, 0xA5, 0} 會解壓為五個位元組:12 12 12 A5 A5。此輸出列以十六進位表示。count 非零的配對中,value 為零仍是一般資料,不是結束標記。

沒有編碼大小、輸出容量或畸形資料處理參數。請在建立或載入資產時驗證,提供結尾零,並在解碼前計算解壓大小。無效或未終止的串流可能讀出來源範圍,並寫出目的範圍。

LCD 關閉時直接寫入重複資料。開啟時,每個輸出位元組都會等待可寫入的 LCD 模式,並以 DI/EI 包住等待與寫入。因此長串流可能跨多個影格;這是同步呼叫,不是佇列傳輸。LCD 開啟路徑會在每個位元組後啟用中斷,不保留原先停用中斷的狀態。

CGB 寫入使用目前選定的 VBK 記憶體庫,且不改變選擇。範例圖塊資料應選 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 的串流直接解壓至 VRAM 0x8860,無須 RAM 輸出緩衝區。DIRECT VRAM 顯示 048。最下列在 CGB 上為藍色,且必須與 RAM 路徑的列呈現相同三種圖塊圖樣。此教學程式在 LCD 關閉時上傳。

每列由左至右為填滿的 8×8 正方形、邊框厚一像素的 8×8 方框,以及占 8×8 圖塊左半部的實心 4×8 矩形。圖塊欄為 2、5、8(像素 x = 16、40、64);三列起始像素 y 為 32、72、112。DMG 圖形為黑色;CGB 各列依序為紅、綠、藍。FAILED 必須為 000。數字代表位元組數:三個各 16 位元組的 GB 圖塊需要解壓 48 位元組。

驗證矩陣包含 42 個案例:空串流、單位元組重複、零值資料、255 位元組重複、跨目的分頁的 510 位元組輸出,以及跨來源分頁的編碼資料。比對全部 528 個觀察到的目的/保護位元組、輸出長度及還原的 ROM 映射。直接 VRAM 案例涵蓋 DMG、CGB 的 LCD 開/關,以及 CGB VBK 1。兩個教學畫面在模擬器執行 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 偽亂數產生器推進一次,傳回新的高位元組。若只需要一個位元組而不想連結 rng.c 包裝函式,可使用此內建函式。

語法

c
u8 __rng8();

回傳值

共用產生器推進一次後的 u8 結果(0–255)。這描述值的型別與範圍,不代表每個位元組等機率。

使用條件與限制

GB 步驟先對 16 位元狀態加上 0x5D17 並允許回繞,將更新後的低位元組與高位元組做 XOR,再把該低位元組加到高位元組左循環一位的結果。儲存兩個更新後位元組,並傳回高位元組。

接受零狀態。下一次取位元組時,GB 產生器會在推進前替換為 0x5AA5,因此 __rng_seed(0) 之後呼叫 __rng8() 會傳回 122。反覆設定種子會重啟序列,不會增加隨機性。

這些呼叫共用持續保留的狀態。重現需要相同平台、種子與呼叫順序,包括其他輔助函式發出的呼叫。請在同一執行環境中設定種子與取值;這些檢查未證明從中斷處理常式並行使用的安全性。

GB 與 FC 使用不同演算法,不保證相同種子產生相同序列。它們是確定性的遊戲用產生器,不是密碼學亂數來源;測試檢查實作行為,不檢查統計品質或週期。

使用函式庫函式時,請引入 rpg.h,並隨遊戲編譯 lib/rng.c。__rng_seed 與 __rng8 內建函式由編譯器產生;只使用這兩者的程式不需要 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 後,前兩次 __rng8 呼叫顯示 FIRST 00002 與 SECOND 00034。再次設定種子便從 00002 重新開始。

完整程式在白底上顯示十進位結果: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

無號 16 位元種子,範圍 0–65535。兩個位元組照原值儲存。請在要重現的遊戲取值序列開始前選好並記錄種子。

回傳值

無(void)。

使用條件與限制

接受零狀態。下一次取位元組時,GB 產生器會在推進前替換為 0x5AA5,因此 __rng_seed(0) 之後呼叫 __rng8() 會傳回 122。反覆設定種子會重啟序列,不會增加隨機性。

這些呼叫共用持續保留的狀態。重現需要相同平台、種子與呼叫順序,包括其他輔助函式發出的呼叫。請在同一執行環境中設定種子與取值;這些檢查未證明從中斷處理常式並行使用的安全性。

GB 與 FC 使用不同演算法,不保證相同種子產生相同序列。它們是確定性的遊戲用產生器,不是密碼學亂數來源;測試檢查實作行為,不檢查統計品質或週期。

使用函式庫函式時,請引入 rpg.h,並隨遊戲編譯 lib/rng.c。__rng_seed 與 __rng8 內建函式由編譯器產生;只使用這兩者的程式不需要 rng.c 目的檔。

使用範例

c
__rng_seed(0x1234);
repeated=__rng8(); // Reseeding reproduces the first byte, 2.

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例展示重設測試序列。種子 0x1234 後,FIRST 與 RESEEDED 皆為 00002,SECOND 為 00034。函式庫種子會重設同一狀態,因此 RNG8 也顯示 00002。

完整程式在白底上顯示十進位結果: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

立即寫入背景 SCX/SCY,並同步軟體的目前值與待套用值。

語法

c
void __scroll_bg_set(u8 scx, u8 scy);

參數

scx

水平捲動,0..255 像素。

scy

垂直捲動,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 中連續的 len 個圖塊編號,從 LCDC 所選背景地圖的 (x,y) 起向下複製。來源位址每次增加一位元組,目標位址增加 32 位元組。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

len

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 所選背景地圖中寬 w、高 h 的矩形。列間距為 32 位元組;這是重複放置同一圖塊,而非複製影像陣列。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 中連續的 len 個圖塊編號,從 LCDC 所選背景地圖的 (x,y) 起向右複製。每複製一個圖塊,來源與目標位址各增加一位元組。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

len

位元組數,範圍 0~255。為 0 時不複製;來源資料至少要有這麼多可讀位元組。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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

將圖塊編號寫入圖塊地圖的一個格子。這是選用既有圖塊圖像,不會修改像素資料。

寫入 LCDC 位元 3 所選的背景地圖:0 對應 0x9800,1 對應 0x9C00,再加上 32*y + x。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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) 為起點的矩形。來源資料每列 w 位元組,目標列間距為 32 位元組。

必要時等待 VRAM 可寫入。LCD 開啟時的路徑會暫停中斷,結束後可能啟用中斷;不保證保留呼叫前的中斷狀態,也不保證在一次 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 bank 中的連續位元組。圖塊資料流存放圖塊編號,屬性資料流存放 CGB 屬性位元組。函式立即複製,不保留指標。

回傳值

無(void)。

使用條件與限制

圖塊編號會寫入目前選取的 VRAM bank。CGB 上請選用編號所在的 bank 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,呼叫前將背景/視窗地圖選為 0x9C00,因此能區分固定地圖與所選地圖的寫入。範例在 LCD 關閉時寫入,不量測 LCD 開啟時的時序。

在 CGB 上,範例字元與結果文字以白底藍字顯示。初始化時將調色盤 0 的繪製顏色設為藍色,不變更圖塊映射的屬性。在 DMG 上,相同字元顯示為白底黑字。

預期影像:以下圖塊排列從圖塊座標 (x=2, y=3)、像素座標 (16,24) 開始顯示。每個字元占一個 8×8 圖塊。由上至下、由左至右閱讀,周圍圖塊保持空白。

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 圖塊的映射表中某一格的 CPU 位址。

語法

c
u16 __tile_addr(u16 base, u8 x, u8 y);

參數

base

圖塊映射表基底位址,通常為 $9800/$9C00。

x

欄,通常為 0..31。

y

列,通常為 0..31。

回傳值

不存取記憶體,傳回 (base + y*32 + x) 模 65536 的結果。

使用範例

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的len位元組複製到dst,完成後才回傳,不將操作加入佇列。來源和目的位址每次增加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,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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的len位元組複製到dst,完成後才回傳,不將操作加入佇列。來源和目的位址每次增加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,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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存取規則。

立即以單一位元組value填滿從dst開始的連續len位元組。重複的是1位元組,不是整個圖塊圖樣。填滿對映表位元組會重複圖塊編號;填滿圖樣位元組則會改變編碼後的像素位元平面。

語法

c
void __vram_fill(u16 dst, u8 value, u16 len);

參數

dst

目的首位元組位址,通常位於VRAM 0x8000–0x9FFF。整個範圍必須有效。CGB使用目前VRAM記憶體庫;對映表區域的記憶體庫0存放圖塊編號,記憶體庫1存放屬性。這些函式不依LCDC選擇對映表,不在列尾換列,也不配置VRAM。

value

重複寫入0至255的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

len

連續位元組數,範圍0至65535,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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實心橫條,相鄰格為空白。長度由變數或函式回傳值計算;三個位元組仍須全部寫入值1。

附帶的自製圖形中,圖塊1為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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的len位元組複製到dst,完成後才回傳,不將操作加入佇列。來源和目的位址每次增加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,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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

立即以單一位元組value填滿從dst開始的連續len位元組。重複的是1位元組,不是整個圖塊圖樣。填滿對映表位元組會重複圖塊編號;填滿圖樣位元組則會改變編碼後的像素位元平面。

語法

c
void __vram_memset(u16 dst, u8 value, u16 len);

參數

dst

目的首位元組位址,通常位於VRAM 0x8000–0x9FFF。整個範圍必須有效。CGB使用目前VRAM記憶體庫;對映表區域的記憶體庫0存放圖塊編號,記憶體庫1存放屬性。這些函式不依LCDC選擇對映表,不在列尾換列,也不配置VRAM。

value

重複寫入0至255的單一位元組。顯示意義取決於目標是圖塊索引、像素位元平面還是其他位元組結構。

len

連續位元組數,範圍0至65535,不是圖塊數。整個範圍應位於有效來源與可寫目標區域內;API不檢查邊界,也不防止十六位元位址回繞。

回傳值

無(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實心橫條,相鄰格為空白。長度由變數或函式回傳值計算;三個位元組仍須全部寫入值1。

附帶的自製圖形中,圖塊1為8×8實心正方形,2只填左側四欄,3為靠左的三角形,每列加寬1像素,從1像素增至8像素。DMG顯示白底黑圖形;CGB依操作區分顏色,文字保持黑色。

像素(16,88)的正方形應在安全版和無等待版的零長度呼叫後保留。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。寬或高為零時視為空集合。

使用條件與限制

使用座標差值,因此右邊界超過 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);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

測試內部、邊界、外部點及零寬度: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 — 背景、視窗與分割捲動

控制背景/視窗的立即與緩衝捲動,以及最多八個光柵分割。請區分 getter 管理的目前值、即時硬體暫存器與待套用值。

__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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。WX 包含硬體的七像素偏移。

使用條件與限制

這不是讀取硬體暫存器的操作。不包含尚未提交的緩衝值、直接寫入暫存器的值,以及光柵分割暫時套用的值。

使用範例

c
result[6]=__scroll_bg_x_get();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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

僅將背景 X 存為待套用值。

語法

c
void __scroll_bg_x_set_buffered(u8 scx);

參數

scx

背景水平捲動量,為 0–255 像素;數值增加時背景向左移動。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

保留另一軸的待套用值。提交前目前值及暫存器不變;請先初始化座標對。

使用範例

c
__scroll_bg_x_set_buffered(32);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。WX 包含硬體的七像素偏移。

使用條件與限制

這不是讀取硬體暫存器的操作。不包含尚未提交的緩衝值、直接寫入暫存器的值,以及光柵分割暫時套用的值。

使用範例

c
result[7]=__scroll_bg_y_get();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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

僅將背景 Y 存為待套用值。

語法

c
void __scroll_bg_y_set_buffered(u8 scy);

參數

scy

背景垂直捲動量,為 0–255 像素;數值增加時背景向上移動。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

保留另一軸的待套用值。提交前目前值及暫存器不變;請先初始化座標對。

使用範例

c
__scroll_bg_y_set_buffered(40);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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();

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

共用表格可容納八筆項目。新增時不會立即套用或排序,超出容量的項目會被忽略。請在安全的影格邊界以 reset→push→commit 順序重建,並在事件間保留足夠間隔,讓處理函式能完成。中斷延遲會使排定的掃描線與第一個受影響像素不一定一致。 表格非空時,會在 IE 啟用 VBlank/STAT 並執行 EI。空表格會停用 LYC 來源,但不還原 IE 或 CPU 中斷狀態。

使用範例

c
__scroll_split_commit();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

以 BG 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,並須以遞增順序新增。若第一筆為零,會在 VBlank 期間套用。

scx

背景水平捲動量,為 0–255 像素;數值增加時背景向左移動。

scy

背景垂直捲動量,為 0–255 像素;數值增加時背景向上移動。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

共用表格可容納八筆項目。新增時不會立即套用或排序,超出容量的項目會被忽略。請在安全的影格邊界以 reset→push→commit 順序重建,並在事件間保留足夠間隔,讓處理函式能完成。中斷延遲會使排定的掃描線與第一個受影響像素不一定一致。

使用範例

c
__scroll_split_push(64,8,0);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

以 BG 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,並須以遞增順序新增。若第一筆為零,會在 VBlank 期間套用。

scx

背景水平捲動量,為 0–255 像素;數值增加時背景向左移動。

scy

背景垂直捲動量,為 0–255 像素;數值增加時背景向上移動。

wx

原始 WX 位元組,範圍為 0–255;將所需畫面 X 座標加上 7。左邊緣使用 7。

wy

視窗頂端的畫面 Y 像素位置,範圍為 0–255;可見的掃描列為 0–143。

flags

操作位元:BG=1、視窗位置=2、顯示=4、隱藏=8、CGB 背景調色盤 0/色彩 0=16。未知位元會被捨棄。色彩 0 模式將 scx/scy 改作 RGB15 的低位元組/高位元組。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

共用表格可容納八筆項目。新增時不會立即套用或排序,超出容量的項目會被忽略。請在安全的影格邊界以 reset→push→commit 順序重建,並在事件間保留足夠間隔,讓處理函式能完成。中斷延遲會使排定的掃描線與第一個受影響像素不一定一致。 顯示與隱藏位元同時設定時,以顯示為優先。色彩 0 功能僅限 CGB;與 BG 移動合用時,兩者共用相同的 scx/scy 位元組。

使用範例

c
__scroll_split_push_ex(104,16,0,7,0,KQ_SCROLL_SPLIT_USE_BG|KQ_SCROLL_SPLIT_WIN_HIDE);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

以 BG 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

清除分割項目數與索引、停用分割處理,並清除 STAT 的 LYC 中斷啟用位元。

語法

c
void __scroll_split_reset();

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

共用表格可容納八筆項目。新增時不會立即套用或排序,超出容量的項目會被忽略。請在安全的影格邊界以 reset→push→commit 順序重建,並在事件間保留足夠間隔,讓處理函式能完成。中斷延遲會使排定的掃描線與第一個受影響像素不一定一致。 不會清除表格中的位元組、清除 IE 或停用 CPU 中斷。

使用範例

c
__scroll_split_reset();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

以 BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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

將視窗的兩個座標存為待提交值。

語法

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();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。WX 包含硬體的七像素偏移。

使用條件與限制

這不是讀取硬體暫存器的操作。不包含尚未提交的緩衝值、直接寫入暫存器的值,以及光柵分割暫時套用的值。

使用範例

c
result[12]=__scroll_win_x_get();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

保留另一軸的待套用值。提交前目前值及暫存器不變;請先初始化座標對。

使用範例

c
__scroll_win_x_set_buffered(55);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。WX 包含硬體的七像素偏移。

使用條件與限制

這不是讀取硬體暫存器的操作。不包含尚未提交的緩衝值、直接寫入暫存器的值,以及光柵分割暫時套用的值。

使用範例

c
result[13]=__scroll_win_y_get();

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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;

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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。

回傳值

無傳回值。本項說明受影響的狀態及其生效時機。

使用條件與限制

保留另一軸的待套用值。提交前目前值及暫存器不變;請先初始化座標對。

使用範例

c
__scroll_win_y_set_buffered(48);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

此範例說明立即值、待套用值及帶正負號加法的回捲。BG X 在提交前為 250,提交後為 16;WX 從 23 變成 39。BG 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 的項目描述。先分配欄位,在工作副本中設定位置、圖塊與屬性,再進行傳輸。複合精靈將多個欄位組成一個物件。GB 有 40 個欄位,每條掃描線最多 10 個;FC 有 64 個,每條掃描線最多 8 個。

GB 屬性:位元 4 選擇 DMG 的 OBP1;位元 5/6 控制 X/Y 翻轉;位元 7 將精靈置於非零背景色編號之後。CGB 的位元 0..2 選擇物件調色盤,位元 3 選擇圖塊資料 VRAM 區塊;DMG 調色盤選擇位元不用於原生 CGB 色彩。

範例使用完整的 GB 精靈函式庫。定義 KQ_SPRITE_MINIMAL_RUNTIME 後,sprite.c 不再包含 sprite_free、sprite_set_pos、sprite_count_used、兩個掃描線查詢函式、metasprite_draw 與 anim_update。需要這些函式時請勿定義該巨集。

__oam_dma

將記憶體頁面中的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設定。硬體傳送需要160個CPU機器週期,另須加上函式準備與返回的時間。

包含此呼叫的程式會為傳送常式保留8位元組HRAM。每次呼叫都會初始化常式,並在DMA運作期間於HRAM中等待。中斷來源會暫時被遮罩,但IE與IME的啟用狀態保持不變。中斷服務會延後至傳送完成後執行。

使用範例

c
// raw_oam is a 160-byte array at address 0xC400.
m_wait();
__oam_dma((u16)raw_oam);

這是呼叫部分的程式碼範例。放入完整程式前,請滿足所述初始化與緩衝區條件。

預期結果

範例直接建立完整的OAM頁面:在(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.

開發命令語法

這些用法取自 Program.DebugTools.cs 與 Program.VibeTools.cs。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