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, IPA는 /ˌ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입니다. ‘아이들의 상상을 게임보이의 숲에서 진짜 모험으로 바꾸는 도구’라는 의미를 담았습니다. 작은 아이디어나 낙서, 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주소 얻기 또는 가리키는 값 접근

=와 ==를 구별하세요. 복잡한 식은 괄호로 의도를 드러내고, 한 문장에 함수 호출과 부수 효과를 과도하게 섞지 마세요. 0으로 나누거나 배열 범위를 벗어나지 않도록 주의하세요. sizeof는 바이트 크기, offsetof는 구조체 멤버의 오프셋을 구합니다.

식의 결과와 평가 순서

비교 연산자 ==, !=, <, <=, >, >=와 논리 연산자 !, &&, ||는 거짓이면 0, 참이면 1을 반환합니다. 결과를 u16에 저장하거나 인수·반환값으로 전달하거나 산술 연산에 사용할 수 있습니다. 예를 들어 score = 500 + (lives != 0);은 잔기가 있으면 501, 없으면 500을 만듭니다.

&&는 왼쪽 피연산자가 0이면 오른쪽 피연산자를 평가하지 않습니다. ||는 왼쪽이 0이 아니면 오른쪽을 평가하지 않습니다. pointer != 0 && pointer->active != 0에서는 포인터가 NULL이면 멤버에 접근하지 않습니다. 16비트 값은 두 바이트를 모두 사용하여 참·거짓을 판단하므로 256도 참입니다. 비트 연산자 &와 |는 단락 평가를 하지 않습니다.

++value는 갱신한 값을 반환하고, value++는 갱신 전 값을 반환합니다. 배열 원소, 역참조한 포인터, 구조체 멤버에도 사용할 수 있습니다. buffer[index()]++는 index()를 한 번만 호출합니다. u16 *p에서 p++는 다음 원소를 가리키도록 2바이트 이동하고, (*p)++는 가리키는 값을 증가시킵니다.

sizeof(array)는 배열 전체의 바이트 크기를 반환하고, sizeof(pointer)는 2입니다. u16 values[9];를 선언하면 sizeof(values)는 18입니다. ROM 배열, 지역 배열, 배열 멤버에도 적용됩니다. sizeof(function())은 함수를 호출하지 않고 반환형을 조사합니다.

함수의 선언과 정의는 매개변수의 자료형과 순서가 일치해야 합니다. 매개변수 이름은 달라도 되며, 함수 본문은 정의에 쓴 이름을 사용합니다. 예를 들어 u8 next(u8 input);의 정의를 u8 next(u8 value) { return (u8)(value + 1); }로 작성할 수 있습니다. 매개변수와 지역 변수는 같은 이름의 전역 변수를 가립니다.

?:로 배열이나 문자열을 선택하면 선택한 원소형을 가리키는 포인터가 됩니다. show(ready ? "READY" : "WAIT");처럼 곧바로 인수로 전달할 수 있습니다. u16 배열 a, b에서 (ready ? a : b) + 1은 선택한 배열의 두 번째 원소를 가리키도록 2바이트 이동합니다. 배열을 복사하는 것은 아닙니다.

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부터 시작합니다. 원소가 4개면 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에는 스프라이트 정보가 들어갑니다. 뱅크 전환은 CPU 주소에 보이는 실제 메모리를 바꾸는 기능입니다. 16비트 포인터 하나만으로는 다른 뱅크의 데이터를 특정할 수 없습니다.

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

__prg_rom은 데이터를 ROM에 배치합니다. __location(0xFF40) 같은 속성은 고정 주소를, __wram / __hram은 메모리 영역을 지정합니다. #pragma bank, #pragma fixed_bank를 쓸 때는 맵 파일을 확인하고 인터럽트가 사용하는 코드와 데이터에 항상 접근할 수 있게 하세요.

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

--cgb=dmg는 DMG용, --cgb=cgb는 양쪽 모드용, --cgb=cgb_only는 CGB 전용임을 선언합니다. 양쪽 모드를 지원하는 게임은 CGB 전용 기능을 쓰기 전에 하드웨어를 감지해야 합니다. 헤더의 플래그가 게임 내부의 모드별 처리를 대신 구현해 주지는 않습니다.

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 용량 초과라면 리소스·함수 크기와 뱅크 배치를 살펴보세요.

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을 읽고 재현 절차·화면·소리·소스와 대조하세요. 추정한 소스 위치와 원인을 확인된 사실과 구분하고, 정상 대기 루프와 멈춤 버그를 구분하세요. 경고를 개별 판단하고 미지원 이벤트와 분석 한계를 기록하세요. 필터로 경고를 숨기거나 테스트를 줄여 합격시키지 마세요.
  • 문제를 최소 재현 예제로 줄이고 원인을 수정한 뒤 다시 빌드하세요. 컴파일러나 에뮬레이터가 원인이면 게임 코드와 분리해 결함을 확인하고 도구 수정에 회귀 검증을 추가하세요.
  • 입력·난수 시드·기종 및 영상 방식·매퍼·관측 프레임·진단 설정을 맞춰 재검증하세요. 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에서 모든 필수 시나리오를 다시 실행하세요. 무적 상태·자동 시험 입력·다른 매퍼만으로 최종 빌드의 일반 플레이를 검증했다고 하지 마세요. 요구 사항과 시험의 대응표, 남은 경고의 이유, 미확인·미지원 항목을 명시하세요. 실기 시험을 하지 않았다면 ‘실기 미확인’으로 표시하세요.

소스, 도구·라이브러리 식별 정보, 편집 가능한 소재, 재현 가능한 빌드·검증 스크립트, 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가 표시됩니다. 앞의 0은 십진수를 세 자리로 표시하기 위한 것입니다.

첫 텍스트 표시: 화면에 HELLO WORLD와 042가 표시됩니다. 앞의 0은 십진수를 세 자리로 표시하기 위한 것입니다.
표시 예: 화면에 HELLO WORLD와 042가 표시됩니다. 앞의 0은 십진수를 세 자리로 표시하기 위한 것입니다.

소스 내려받기 / 검증 상태

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


// Initialize the display/font, write HELLO WORLD and a three-digit 042,
// then wait for frames so the result remains visible.
void main() {
    m_init();
    m_text(2,3,"HELLO");
    m_text(2,5,"HELLO WORLD");
    m_number(42);
    while (1) { m_wait();  }
}
더 넓은 타입의 중간값으로 연산 gb_arithmetic.c

목적: 8비트로 표현할 수 없는 중간 결과를 보관하는 방법을 알아봅니다.

동작 원리: (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으로 4바이트를 지웁니다. 비트 3을 설정하고 비트 1을 반전한 뒤 __memcpy로 4바이트 모두를 복사합니다.

조작 방법과 기대 결과: 복사 대상의 첫 바이트가 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은 점 4개를 담을 수 있는 배열을 연결합니다. 그다음 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를 선택하는 방법을 알아봅니다.

동작 원리: 0으로 초기화한 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를 세 번 반복한 뒤 반복 횟수 0으로 끝낸다는 뜻입니다. rle_decode는 3바이트 대상 버퍼에 씁니다.

조작 방법과 기대 결과: 마지막 바이트 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), 중력 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가 이를 2바이트 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에 전달합니다. 8비트 위치 값은 255 다음에 0으로 돌아갑니다.

조작 방법과 기대 결과: SCROLL이라는 글자가 왼쪽으로 이동합니다. 시간에 따라 위치가 바뀌는지 확인하세요. 스크린샷 한 장으로는 움직임을 확인할 수 없습니다.

배경을 가로로 스크롤: SCROLL이라는 글자가 왼쪽으로 이동합니다. 시간에 따라 위치가 바뀌는지 확인하세요. 스크린샷 한 장으로는 움직임을 확인할 수 없습니다.
표시 예: SCROLL이라는 글자가 왼쪽으로 이동합니다. 시간에 따라 위치가 바뀌는지 확인하세요. 스크린샷 한 장으로는 움직임을 확인할 수 없습니다.

소스 내려받기 / 검증 상태

powershell
.\kitaqgb\kitaqgb.exe kitaqgb/lib/scroll.c kitaq-docs/samples/gb_scroll.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_scroll.gb --profile=dev --rst-disable --stack-bank=fixed
c
// Scroll the background horizontally.
// Expected: title moves left as SCX increases
#include "gb_common.h"
#include "scroll.h"
u8 phase;
// Advance an 8-bit horizontal scroll position once per frame, wrapping naturally
// after 255. Background motion demonstrates the changing scroll register.
void main() {
    m_init();
    m_text(2,3,"SCROLL");
    Scroll_SetBg(0,0);
    while (1) { m_wait(); phase++; Scroll_SetBg(phase,0); }
}
타일 갱신을 큐에 등록 gb_vram_queue.c

목적: 그리기 요청과 이를 적용하는 VRAM 전송을 분리합니다.

동작 원리: vram_queue_bg_tile이 타일 좌표 (3,8)에 타일 52를 예약하고 vram_flush가 전송합니다. 제공된 글꼴에서 타일 52는 숫자 4입니다.

조작 방법과 기대 결과: 배경 타일 좌표 (3,8)에 숫자 4 하나가 표시되는지 확인하세요. 타일 갱신이며 숫자 카운터가 아닙니다.

타일 갱신을 큐에 등록: 배경 타일 좌표 (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)입니다. 중력은 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와 데이터 기술자

소재 라이브러리는 ROM 뱅크에 저장한 소재를 8비트 ID로 찾습니다. asset.c를 포함하고, FC에서는 bank.c도 포함합니다. 패딩 없는 6바이트 AssetDesc에는 1바이트 유형, 1바이트 소재 뱅크 번호, 16비트 주소, 바이트 단위 길이가 들어갑니다. 목록과 소재 본체는 서로 다른 뱅크에 둘 수 있습니다.

조회 전에 목록을 등록하세요. 등록 정보는 공유 전역 상태이므로 조회 중에 바꾸면 안 됩니다. 라이브러리는 ID와 asset_get의 출력 포인터를 검사하지만 등록된 목록, 소재 주소, 카트리지 내용의 유효성은 검사하지 않습니다.

RAW 또는 TILES 소재를 찾아 __vram_copy로 desc.len바이트 전체를 전송합니다. 호출 전에 소재 본체의 ROM 뱅크를 선택해야 합니다. GB 함수는 desc.ptr을 직접 사용하며 전송할 때 desc.bank를 선택하지 않습니다.

__farmemcpy

ROM 뱅크를 잠시 전환하고 src에서 dst로 주소가 증가하는 순서대로 len바이트를 복사한 뒤, 컴파일러가 저장한 ROM 뱅크로 돌아갑니다. 이 기종에서 __farmemcpy와 __far_memcpy는 같은 구현을 사용합니다.

사용 형식

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

매개변수

dst

쓰기 대상 메모리입니다. 실제 복사량을 모두 담을 공간을 확보하세요. 0바이트 전송을 제외하면 널 포인터나 잘못된 대상 주소를 검사하지 않습니다.

bank

원본 ROM 매핑을 선택하는 8비트 뱅크 값입니다. 카트리지와 컴파일러의 뱅크 배치에서 지원하는 번호를 쓰며, 원본 주소와 별도로 지정합니다.

src

선택한 ROM 뱅크에서 읽을 수 있는 바이트열을 가리키는 근거리 포인터입니다. 포인터 자체에는 뱅크 번호가 없습니다.

len

바이트 수이며 범위는 0~65535입니다. 0이면 메모리를 복사하지 않지만 뱅크 선택과 복구 과정은 실행합니다.

반환값

없음(void).

사용 조건과 제한

원본 전체가 의도한 ROM 매핑에서 읽히고 대상 전체가 쓰기 가능한 메모리 안에 있어야 합니다. 뱅크 창 경계에서 다음 뱅크로 넘어가거나, memmove처럼 겹치는 범위를 처리하거나, VRAM 쓰기 시점을 제어하지 않습니다. VRAM에는 그래픽 전송 API를 사용하세요.

예제의 호출 코드는 고정 뱅크 0에 두어 원본 뱅크를 바꿔도 실행 중인 코드가 사라지지 않도록 합니다. 전송은 동기식입니다. 전송 도중 인터럽트에서 다른 뱅크 전환 복사를 실행하지 마세요. 이 함수만으로 프로그램 전체의 인터럽트 안전성이 확보되지는 않습니다.

GB 보조 루틴은 컴파일러가 보관하는 ROM 뱅크 상태로 복귀 뱅크를 정합니다. 이를 갱신하는 __bankswitch를 사용하세요. 매퍼에 직접 쓰면 상태가 갱신되지 않습니다. HRAM의 복귀 뱅크 저장 공간은 공유되므로 중첩 전송으로 덮어쓰면 안전하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

뱅크 1이 선택된 상태에서 소재 뱅크 2의 패턴 두 개, 32바이트를 복사합니다. 모든 복사 바이트와 뱅크 1 복구를 검사하고, 길이 0 복사에서 확인값 0x55가 그대로인지 검증합니다.

예상 화면의 빨간 줄은 픽셀 좌표 (16,32)에서 시작하며 72×8픽셀입니다. 8×8 삼각형, 테두리 두께 1픽셀인 8×8 사각형, 왼쪽 네 열을 채운 타일을 세 번 반복합니다. 삼각형은 왼쪽 위의 1픽셀에서 맨 아래 줄의 8픽셀까지 넓어집니다.

GB에서 (16,64)의 파란 삼각형과 테두리는 __farmemcpy 결과이며, (16,96)의 초록색 쌍은 __far_memcpy 결과입니다. 각 쌍은 16×8픽셀입니다. CGB는 이 색으로, DMG는 흰 바탕에 검은색으로 같은 모양을 표시합니다.

예상 상태 표시는 FAILED CHECKS 000과 FIRST FAILURE 00입니다. 검증은 300프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.

__farmemcpy: DMG
DMG
__farmemcpy: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\asset_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\asset_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
구현과 소스

kitaqgb/lib/asset.h:17

__vram_copy

__vram_copy은 컴파일러가 __vram_memcpy과 같은 처리로 바꾸는 별칭입니다. 길이 단위는 동일하게 바이트이며 즉시 CPU 전송과 LCD 접근 조건도 같습니다.

src에서 dst로 len바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.

사용 형식

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

매개변수

dst

대상의 첫 바이트 주소이며 보통 VRAM 0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.

src

원본 바이트 포인터입니다. 호출이 끝날 때까지 메모리와 뱅크 매핑이 유효해야 합니다. 데이터는 즉시 읽으며 나중의 flush를 위해 보관하지 않습니다. 원본 뱅크를 자동으로 전환하지도 않습니다. 앞방향 복사이므로 memmove처럼 겹침을 처리하지 않습니다. 겹치지 않는 범위를 사용하세요.

len

연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_copy으로 타일 (2,3), 픽셀 (16,24)에 맵 데이터 3바이트를 씁니다. 24 × 8 영역에 왼쪽부터 [1,2,3] 패턴이 나타나야 하고 이웃 칸은 비어 있어야 합니다. 완성 프로그램은 각 복사와 채우기를 한 행씩 배치합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_copy: DMG
DMG
__vram_copy: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/asset.h:18

bank.h — 뱅크를 고려한 코드·데이터 접근

ROM 뱅크는 카트리지 저장 공간을 CPU 주소 공간에 매핑하는 단위입니다. 서로 다른 뱅크가 같은 CPU 주소에 나타날 수 있으므로 16비트 포인터만으로는 뱅크가 있는 데이터를 식별할 수 없습니다. BankPtr은 1바이트 뱅크 번호와 2바이트 포인터를 담은 패딩 없는 3바이트 설명자입니다. farptr_make로 만들고 farptr_read8, farptr_read16 또는 farptr_read로 데이터에 접근합니다.

GB 예제는 MBC5 카트리지를 사용합니다. 고정 뱅크 0의 코드는 $0000–$3FFF에서 계속 접근할 수 있고, $4000–$7FFF에는 뱅크 1·2·3을 선택합니다. bank_switch와 호출 후 실행할 코드는 고정 ROM에 배치하세요. 라이브러리의 뱅크 번호를 조회하기 전에 bank_switch로 기록을 초기화하세요. GB의 far_call은 콜백 포인터를 받으며, __farcall에는 함수 이름을 전달합니다.

__farcall_ptr

지정한 뱅크의 실행 시점 주소에 있는 인수 없는 콜백을 호출합니다. 고정 ROM의 보조 코드가 호출마다 뱅크를 CPU 스택에 저장하고 반환 시 복원합니다. 이 내장 함수는 값을 반환하지 않습니다.

사용 형식

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

매개변수

bank

부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.

func

매개변수가 없는 콜백의 유효한 실행 주소입니다. 함수 이름이나 실행 시점 포인터로 지정할 수 있습니다. 널 포인터와 잘못된 뱅크·주소 조합은 실행 중 검사하지 않습니다.

반환값

없음(void).

사용 조건과 제한

이 호출은 사용자 인수를 전달하거나 콜백의 인터럽트 재진입성을 보장하지 않습니다. 콜백 자체의 데이터, 스택, 인터럽트 사용을 올바르게 관리하세요. 뱅크 복귀는 중첩 및 반복 호출로 검사했지만 실제 하드웨어와 임의의 인터럽트 타이밍은 이 예제의 검증 범위가 아닙니다.

이 GB 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

완성 프로그램의 이 지점에서 __farcall_ptr는 callback_count를 3으로 만듭니다. 콜백은 뱅크 3에서 D3을 읽습니다. 이후 home2를 근거리로 읽어 B2인지 검사함으로써 뱅크 2가 매핑된 상태로 복귀했는지 확인합니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 코드 조각은 __farpeek16로 뱅크 3의 payload+1를 읽고 5C7A를 확인합니다. 완성 프로그램은 home2의 근거리 읽기로 뱅크 2 매핑이 복원되었는지도 확인합니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 코드 조각은 __farpeek8로 뱅크 3의 payload를 읽고 D3를 확인합니다. 완성 프로그램은 home2의 근거리 읽기로 뱅크 2 매핑이 복원되었는지도 확인합니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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 성분 함수는 한 색, 범위 함수는 연속된 색 워드, 전체 팔레트 함수는 네 워드를 씁니다. 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픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. 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픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. CGB 검사에서는 CGB MODE가 1입니다. 실기와 LCD 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.

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

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/cgb_palette.h:10

__cgb_safe_set_bcps

CGB의 배경·윈도우 팔레트 바이트 인덱스 레지스터 BCPS/BGPI($FF68)에 씁니다. __cgb_safe_set_bgpi도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.

사용 형식

c
void __cgb_safe_set_bcps(u8 value);

매개변수

value

비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.

반환값

없음(void).

사용 조건과 제한

팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

같은 타일 패턴에도 BG와 OBJ의 별도 팔레트가 적용됨을 배우는 프로그램입니다. 각 삼각형은 8 × 8픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. 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픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. CGB 검사에서는 CGB MODE가 1입니다. 실기와 LCD 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.

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

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/cgb_palette.h:12

__cgb_safe_set_ocps

CGB의 스프라이트(OBJ) 팔레트 바이트 인덱스 레지스터 OCPS/OBPI($FF6A)에 씁니다. __cgb_safe_set_obpi도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.

사용 형식

c
void __cgb_safe_set_ocps(u8 value);

매개변수

value

비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.

반환값

없음(void).

사용 조건과 제한

팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

같은 타일 패턴에도 BG와 OBJ의 별도 팔레트가 적용됨을 배우는 프로그램입니다. 각 삼각형은 8 × 8픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. 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 필드는 ROM 뱅크가 아니라 패턴이 들어 있는 VRAM 뱅크를 선택합니다. 일반 타일 번호 쓰기는 현재 VRAM 뱅크를 사용하므로 타일·속성 결합 작업 전에 뱅크 0을 선택하세요. DMG에서는 속성 전용 작업은 아무것도 하지 않고, 결합 작업은 타일 번호를 씁니다.

__ 내장 함수 구현은 컴파일러가 생성하므로 cgb_tile.c를 링크할 필요가 없습니다. 일반 쓰기 경로는 VRAM 접근이 가능해질 때까지 기다리지만, 일부는 인터럽트 활성 상태도 바꿉니다. _unsafe, _fast 형식은 이 대기를 생략하므로 LCD가 꺼져 있을 때처럼 호출자가 VRAM 쓰기 가능 상태를 보장할 때만 사용하세요. 각 항목의 인터럽트·뱅크·전송 길이 조건을 확인하세요.

__cgb_safe_set_vbk

CGB의 VBK($FF4F)로 CPU가 접근할 VRAM 뱅크를 고릅니다. value & 1만 쓰므로 짝수는 뱅크 0, 홀수는 뱅크 1입니다. CPU의 $8000–$9FFF 접근에 적용되며 맵 속성의 타일 데이터 뱅크 선택과는 별개입니다. 이전 뱅크를 자동 복원하지 않습니다.

사용 형식

c
void __cgb_safe_set_vbk(u8 value);

매개변수

value

CGB의 VBK($FF4F)로 CPU가 접근할 VRAM 뱅크를 고릅니다. value & 1만 쓰므로 짝수는 뱅크 0, 홀수는 뱅크 1입니다. CPU의 $8000–$9FFF 접근에 적용되며 맵 속성의 타일 데이터 뱅크 선택과는 별개입니다. 이전 뱅크를 자동 복원하지 않습니다.

반환값

없음(void).

사용 조건과 제한

팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

같은 타일 패턴에도 BG와 OBJ의 별도 팔레트가 적용됨을 배우는 프로그램입니다. 각 삼각형은 8 × 8픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

base로 맵 시작 주소 0x9800 또는 0x9C00을 지정합니다. 실제 주소는 base + 32*y + x입니다.

VRAM 접근을 기다리지 않습니다. LCD가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.

사용 형식

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

매개변수

base

base로 맵 시작 주소 0x9800 또는 0x9C00을 지정합니다. 실제 주소는 base + 32*y + x입니다.

x, y

8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.

attr

CGB 속성 바이트입니다. 비트 0~2는 팔레트 0~7, 비트 3은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 1에 쓰며 마지막에는 VBK=0이 됩니다.

base로 맵 시작 주소 0x9800 또는 0x9C00을 지정합니다. 실제 주소는 base + 32*y + x입니다.

VRAM 접근을 기다리지 않습니다. LCD가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.

사용 형식

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

매개변수

base

base로 맵 시작 주소 0x9800 또는 0x9C00을 지정합니다. 실제 주소는 base + 32*y + x입니다.

x, y

8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.

tile

0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.

attr

CGB 속성 바이트입니다. 비트 0~2는 팔레트 0~7, 비트 3은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

대상 주소는 항상 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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의 CGB 속성 count바이트를 VRAM 뱅크 1의 dest에 연속 복사합니다. 타일 번호는 유지하며 dest는 좌표가 아닌 바이트 주소입니다.

필요하면 VRAM 쓰기가 가능한 시점까지 기다립니다. LCD가 켜진 경로는 인터럽트를 제어하며 이후 활성화할 수 있습니다. 호출 전 인터럽트 상태 유지나 한 번의 VBlank 안에 끝남을 보장하지 않습니다.

사용 형식

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

매개변수

dest

첫 VRAM 바이트 주소입니다. 맵에는 0x9800~0x9FFF 범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.

src

RAM 또는 현재 읽을 수 있는 ROM 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성 배열 1,2,3이 각각 준비된 팔레트를 선택하여 A는 빨간색, B는 파란색, C는 초록색이 됩니다. DMG에서는 세 문자 모두 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (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의 CGB 속성 count바이트를 VRAM 뱅크 1의 dest에 연속 복사합니다. 타일 번호는 유지하며 dest는 좌표가 아닌 바이트 주소입니다.

VRAM 접근을 기다리지 않습니다. LCD가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.

사용 형식

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

매개변수

dest

첫 VRAM 바이트 주소입니다. 맵에는 0x9800~0x9FFF 범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.

src

RAM 또는 현재 읽을 수 있는 ROM 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성 배열 1,2,3이 각각 준비된 팔레트를 선택하여 A는 빨간색, B는 파란색, C는 초록색이 됩니다. DMG에서는 세 문자 모두 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

대상 주소는 항상 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호와 CGB 속성을 함께 씁니다. DMG에서는 번호만 씁니다. CGB에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 1에 쓰며 마지막에는 VBK=0이 됩니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성 배열 1,2,3이 각각 준비된 팔레트를 선택하여 A는 빨간색, B는 파란색, C는 초록색이 됩니다. DMG에서는 세 문자 모두 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

count

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호와 CGB 속성을 함께 씁니다. DMG에서는 번호만 씁니다. CGB에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 1에 쓰며 마지막에는 VBK=0이 됩니다.

복사가 끝날 때까지 원본 버퍼와 해당 ROM 뱅크를 읽을 수 있게 유지하세요. 반환값은 없습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성 배열 1,2,3이 각각 준비된 팔레트를 선택하여 A는 빨간색, B는 파란색, C는 초록색이 됩니다. DMG에서는 세 문자 모두 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewin
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewin_unsafe
// Write one tile-number byte to the tile map; this selects an existing tile image, not
// new pixel data. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewinattr
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 6 selects the window map:
// zero uses `0x9800`, one uses `0x9C00`. These are map coordinates, not the window's
// screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 속성 바이트만 바꾸고 타일 번호는 유지합니다. 속성은 팔레트, 타일 이미지 뱅크, 좌우·상하 반전과 우선순위를 지정합니다.

LCDC 비트 6이 선택한 윈도 맵을 사용합니다. 0이면 0x9800, 1이면 0x9C00입니다. x,y는 맵 좌표이며 화면의 윈도 위치가 아닙니다.

VRAM 접근을 기다리지 않습니다. LCD가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.

사용 형식

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

매개변수

x, y

8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.

attr

CGB 속성 바이트입니다. 비트 0~2는 팔레트 0~7, 비트 3은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

CGB에서는 VRAM 뱅크 1에 쓴 뒤 VBK=0으로 끝납니다. DMG에서는 속성을 쓰지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewinattr_unsafe
// Write only the CGB attribute byte; leave the tile number unchanged. Attribute bits
// select palette, tile-data bank, flips and priority. LCDC bit 6 selects the window map:
// zero uses `0x9800`, one uses `0x9C00`. These are map coordinates, not the window's
// screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewincgb
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 1에 쓰며 마지막에는 VBK=0이 됩니다.

LCDC 비트 6이 선택한 윈도 맵을 사용합니다. 0이면 0x9800, 1이면 0x9C00입니다. x,y는 맵 좌표이며 화면의 윈도 위치가 아닙니다.

VRAM 접근을 기다리지 않습니다. LCD가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.

사용 형식

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

매개변수

x, y

8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.

tile

0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.

attr

CGB 속성 바이트입니다. 비트 0~2는 팔레트 0~7, 비트 3은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilewincgb_unsafe
// Write a tile number and its CGB attribute. On DMG, only the tile-number write is
// performed. On CGB, enter with VRAM bank 0 selected; attributes use bank 1 and the call
// finishes with `VBK=0`. LCDC bit 6 selects the window map: zero uses `0x9800`, one uses
// `0x9C00`. These are map coordinates, not the window's screen position.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 속성값 2가 예제에서 준비한 파란색 팔레트를 선택하므로 A가 파란색으로 표시됩니다. DMG에는 CGB 속성을 적용하지 않아 같은 문자가 검은색입니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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)는 새 누름 마스크입니다. 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에 배치되었는지 확인합니다. 이는 배치 정보 검사이며, 실행 시 뱅크 동작은 별도의 읽기와 콜백 검사로 확인합니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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로 초기화하세요. 내장 함수나 매퍼에 대한 직접 쓰기는 이 별도 기록을 갱신하지 않으므로 bank_get_current로 실제 하드웨어 매핑을 검증할 수는 없습니다.

이 GB 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.

사용 예

c
__bankswitch(1);
bank_expect(home1[0]==0xA1,19);
bank_expect(bank_get_current()==2,20); // Intrinsics bypass the library record.
__bankswitch(2);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

처음 bank_switch(2)는 뱅크 2를 기록하고 선택합니다. 이후 직접 전환으로 뱅크 1을 선택해 home1에서 A1을 읽어도 bank_get_current는 여전히 2입니다. 복귀하면 home2에서 다시 B2를 읽습니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. CGB 검사에서는 CGB MODE가 1입니다. 실기와 LCD 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.

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

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1865

__cgb_safe_set_bgpi

CGB의 배경·윈도우 팔레트 바이트 인덱스 레지스터 BCPS/BGPI($FF68)에 씁니다. __cgb_safe_set_bcps도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.

사용 형식

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

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.

반환값

없음(void).

사용 조건과 제한

팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.

사용 예

c
__cgb_safe_set_bgpi(0xBE); // Alias of BCPS: BG byte 62, auto-increment enabled.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

같은 타일 패턴에도 BG와 OBJ의 별도 팔레트가 적용됨을 배우는 프로그램입니다. 각 삼각형은 8 × 8픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. CGB 검사에서는 CGB MODE가 1입니다. 실기와 LCD 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.

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

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1863

__cgb_safe_set_hdma1

CGB DMA 원본 주소의 상위 바이트를 HDMA1($FF51)에 씁니다. 원본 위치만 설정하며 복사하거나 전송을 시작하지 않습니다. 하위 바이트는 HDMA2로 지정합니다.

사용 형식

c
void __cgb_safe_set_hdma1(u8 value);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

레지스터에 쓸 u8 값(0~255). 각 비트의 의미는 위 설명을 따릅니다. 포인터나 바이트 수가 아닌 레지스터 값 한 바이트입니다.

반환값

없음(void).

사용 조건과 제한

컴파일러가 CGB 모드를 확인해 쓰기를 보호하며 DMG 모드에서는 쓰지 않습니다. safe는 이 모드 확인만 뜻합니다. 이전 레지스터 값을 저장하거나 복원하지 않습니다.

한 블록은 16바이트입니다. 새 전송은 1~128블록(16~2048바이트)이므로 제어값 0은 빈 전송이 아닌 16바이트입니다. GDMA는 완료까지 CPU를 멈춥니다. HBlank DMA는 표시 행의 HBlank마다 한 블록씩 전송하며 프레임을 넘어 계속됩니다. 데이터를 재사용하기 전에 호출자가 완료를 확인해야 합니다.

GDMA는 LCD가 꺼져 있거나 전송을 마칠 만큼 긴 쓰기 가능 구간에 사용합니다. HBlank DMA는 LCD가 켜진 상태에서 STAT 모드 0 이외에 시작하며 예제는 VBlank에서 시작합니다. 동작 중 원본 뱅크, 대상 VBK, 원본 데이터를 바꾸지 마세요. HALT 중에는 HBlank DMA도 멈춥니다. 이 함수는 대기·인터럽트 보호·범위 검사를 추가하지 않습니다.

상태는 $FF55를 따로 읽습니다. 비트 7이 0이면 진행 중, $FF이면 완료입니다. 취소 후 비트 7은 1이고 비트 0~6에는 남은 블록 수-1이 남습니다. 이미 복사한 바이트는 되돌리지 않습니다. 비활성 상태에서 비트 7을 끄고 쓰면 GDMA가 시작되므로 취소는 활성 상태에서만 하세요. 전체 전송이 유효한 원본 메모리와 대상 VRAM 뱅크 안에 들어가야 하며 주소 넘침에 의존하지 마세요.

이 HDMA 레지스터 설정 함수들은 CGB 하드웨어 DMA를 제어합니다. __vram_copy_dma는 CPU 복사 루프의 별도 컴파일러 별칭이며 이름과 달리 이 DMA 컨트롤러를 사용하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

전체 프로그램은 $C600의 16바이트 정렬 버퍼로 세 전송 경로를 비교합니다. GDMA는 32바이트를 $8010에 복사하여 (16,24)에 왼쪽 위 한 픽셀에서 맨 아래 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 DMA를 같은 VBlank 안에서 시작·취소하여 첫 블록도 복사하지 않습니다. $FF55는 $81이어야 하며 한 프레임 뒤에도 두 패턴은 그대로여야 합니다. DMG에서는 위 두 행이 비어 있고 이 두 보존 확인 도형만 검정으로 보입니다. 글자는 흰 바탕에 검정입니다. FAILED CHECKS 000은 복사 바이트와 완료 상태도 확인합니다. KOKURA에서 겸용 DMG/CGB 및 CGB 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.

__cgb_safe_set_hdma1: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma1: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma1: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1871

__cgb_safe_set_hdma2

DMA 원본 주소의 하위 바이트를 HDMA2($FF52)에 씁니다. 하드웨어는 비트 0~3을 무시하여 16바이트 경계로 내림 정렬합니다. HDMA1=$C6, HDMA2=$0F이면 원본은 $C600입니다. DMA를 시작하지 않습니다.

사용 형식

c
void __cgb_safe_set_hdma2(u8 value);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

레지스터에 쓸 u8 값(0~255). 각 비트의 의미는 위 설명을 따릅니다. 포인터나 바이트 수가 아닌 레지스터 값 한 바이트입니다.

반환값

없음(void).

사용 조건과 제한

컴파일러가 CGB 모드를 확인해 쓰기를 보호하며 DMG 모드에서는 쓰지 않습니다. safe는 이 모드 확인만 뜻합니다. 이전 레지스터 값을 저장하거나 복원하지 않습니다.

한 블록은 16바이트입니다. 새 전송은 1~128블록(16~2048바이트)이므로 제어값 0은 빈 전송이 아닌 16바이트입니다. GDMA는 완료까지 CPU를 멈춥니다. HBlank DMA는 표시 행의 HBlank마다 한 블록씩 전송하며 프레임을 넘어 계속됩니다. 데이터를 재사용하기 전에 호출자가 완료를 확인해야 합니다.

GDMA는 LCD가 꺼져 있거나 전송을 마칠 만큼 긴 쓰기 가능 구간에 사용합니다. HBlank DMA는 LCD가 켜진 상태에서 STAT 모드 0 이외에 시작하며 예제는 VBlank에서 시작합니다. 동작 중 원본 뱅크, 대상 VBK, 원본 데이터를 바꾸지 마세요. HALT 중에는 HBlank DMA도 멈춥니다. 이 함수는 대기·인터럽트 보호·범위 검사를 추가하지 않습니다.

상태는 $FF55를 따로 읽습니다. 비트 7이 0이면 진행 중, $FF이면 완료입니다. 취소 후 비트 7은 1이고 비트 0~6에는 남은 블록 수-1이 남습니다. 이미 복사한 바이트는 되돌리지 않습니다. 비활성 상태에서 비트 7을 끄고 쓰면 GDMA가 시작되므로 취소는 활성 상태에서만 하세요. 전체 전송이 유효한 원본 메모리와 대상 VRAM 뱅크 안에 들어가야 하며 주소 넘침에 의존하지 마세요.

이 HDMA 레지스터 설정 함수들은 CGB 하드웨어 DMA를 제어합니다. __vram_copy_dma는 CPU 복사 루프의 별도 컴파일러 별칭이며 이름과 달리 이 DMA 컨트롤러를 사용하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

전체 프로그램은 $C600의 16바이트 정렬 버퍼로 세 전송 경로를 비교합니다. GDMA는 32바이트를 $8010에 복사하여 (16,24)에 왼쪽 위 한 픽셀에서 맨 아래 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 DMA를 같은 VBlank 안에서 시작·취소하여 첫 블록도 복사하지 않습니다. $FF55는 $81이어야 하며 한 프레임 뒤에도 두 패턴은 그대로여야 합니다. DMG에서는 위 두 행이 비어 있고 이 두 보존 확인 도형만 검정으로 보입니다. 글자는 흰 바탕에 검정입니다. FAILED CHECKS 000은 복사 바이트와 완료 상태도 확인합니다. KOKURA에서 겸용 DMG/CGB 및 CGB 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.

__cgb_safe_set_hdma2: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma2: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma2: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1872

__cgb_safe_set_hdma3

VRAM 안의 DMA 대상 주소 상위 부분을 HDMA3($FF53)에 씁니다. 비트 0~4만 쓰며 대상은 항상 $8000–$9FFF입니다. HDMA4와 함께 지정합니다. VRAM 뱅크 선택이나 DMA 시작은 하지 않습니다.

사용 형식

c
void __cgb_safe_set_hdma3(u8 value);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

레지스터에 쓸 u8 값(0~255). 각 비트의 의미는 위 설명을 따릅니다. 포인터나 바이트 수가 아닌 레지스터 값 한 바이트입니다.

반환값

없음(void).

사용 조건과 제한

컴파일러가 CGB 모드를 확인해 쓰기를 보호하며 DMG 모드에서는 쓰지 않습니다. safe는 이 모드 확인만 뜻합니다. 이전 레지스터 값을 저장하거나 복원하지 않습니다.

한 블록은 16바이트입니다. 새 전송은 1~128블록(16~2048바이트)이므로 제어값 0은 빈 전송이 아닌 16바이트입니다. GDMA는 완료까지 CPU를 멈춥니다. HBlank DMA는 표시 행의 HBlank마다 한 블록씩 전송하며 프레임을 넘어 계속됩니다. 데이터를 재사용하기 전에 호출자가 완료를 확인해야 합니다.

GDMA는 LCD가 꺼져 있거나 전송을 마칠 만큼 긴 쓰기 가능 구간에 사용합니다. HBlank DMA는 LCD가 켜진 상태에서 STAT 모드 0 이외에 시작하며 예제는 VBlank에서 시작합니다. 동작 중 원본 뱅크, 대상 VBK, 원본 데이터를 바꾸지 마세요. HALT 중에는 HBlank DMA도 멈춥니다. 이 함수는 대기·인터럽트 보호·범위 검사를 추가하지 않습니다.

상태는 $FF55를 따로 읽습니다. 비트 7이 0이면 진행 중, $FF이면 완료입니다. 취소 후 비트 7은 1이고 비트 0~6에는 남은 블록 수-1이 남습니다. 이미 복사한 바이트는 되돌리지 않습니다. 비활성 상태에서 비트 7을 끄고 쓰면 GDMA가 시작되므로 취소는 활성 상태에서만 하세요. 전체 전송이 유효한 원본 메모리와 대상 VRAM 뱅크 안에 들어가야 하며 주소 넘침에 의존하지 마세요.

이 HDMA 레지스터 설정 함수들은 CGB 하드웨어 DMA를 제어합니다. __vram_copy_dma는 CPU 복사 루프의 별도 컴파일러 별칭이며 이름과 달리 이 DMA 컨트롤러를 사용하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

전체 프로그램은 $C600의 16바이트 정렬 버퍼로 세 전송 경로를 비교합니다. GDMA는 32바이트를 $8010에 복사하여 (16,24)에 왼쪽 위 한 픽셀에서 맨 아래 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 DMA를 같은 VBlank 안에서 시작·취소하여 첫 블록도 복사하지 않습니다. $FF55는 $81이어야 하며 한 프레임 뒤에도 두 패턴은 그대로여야 합니다. DMG에서는 위 두 행이 비어 있고 이 두 보존 확인 도형만 검정으로 보입니다. 글자는 흰 바탕에 검정입니다. FAILED CHECKS 000은 복사 바이트와 완료 상태도 확인합니다. KOKURA에서 겸용 DMG/CGB 및 CGB 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.

__cgb_safe_set_hdma3: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma3: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma3: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1873

__cgb_safe_set_hdma4

VRAM 대상 주소의 하위 바이트를 HDMA4($FF54)에 씁니다. 비트 0~3은 무시되어 16바이트 경계로 내림 정렬됩니다. HDMA3=$E0, HDMA4=$1F이면 $8010입니다. 전송 길이가 아닌 주소를 설정합니다.

사용 형식

c
void __cgb_safe_set_hdma4(u8 value);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

레지스터에 쓸 u8 값(0~255). 각 비트의 의미는 위 설명을 따릅니다. 포인터나 바이트 수가 아닌 레지스터 값 한 바이트입니다.

반환값

없음(void).

사용 조건과 제한

컴파일러가 CGB 모드를 확인해 쓰기를 보호하며 DMG 모드에서는 쓰지 않습니다. safe는 이 모드 확인만 뜻합니다. 이전 레지스터 값을 저장하거나 복원하지 않습니다.

한 블록은 16바이트입니다. 새 전송은 1~128블록(16~2048바이트)이므로 제어값 0은 빈 전송이 아닌 16바이트입니다. GDMA는 완료까지 CPU를 멈춥니다. HBlank DMA는 표시 행의 HBlank마다 한 블록씩 전송하며 프레임을 넘어 계속됩니다. 데이터를 재사용하기 전에 호출자가 완료를 확인해야 합니다.

GDMA는 LCD가 꺼져 있거나 전송을 마칠 만큼 긴 쓰기 가능 구간에 사용합니다. HBlank DMA는 LCD가 켜진 상태에서 STAT 모드 0 이외에 시작하며 예제는 VBlank에서 시작합니다. 동작 중 원본 뱅크, 대상 VBK, 원본 데이터를 바꾸지 마세요. HALT 중에는 HBlank DMA도 멈춥니다. 이 함수는 대기·인터럽트 보호·범위 검사를 추가하지 않습니다.

상태는 $FF55를 따로 읽습니다. 비트 7이 0이면 진행 중, $FF이면 완료입니다. 취소 후 비트 7은 1이고 비트 0~6에는 남은 블록 수-1이 남습니다. 이미 복사한 바이트는 되돌리지 않습니다. 비활성 상태에서 비트 7을 끄고 쓰면 GDMA가 시작되므로 취소는 활성 상태에서만 하세요. 전체 전송이 유효한 원본 메모리와 대상 VRAM 뱅크 안에 들어가야 하며 주소 넘침에 의존하지 마세요.

이 HDMA 레지스터 설정 함수들은 CGB 하드웨어 DMA를 제어합니다. __vram_copy_dma는 CPU 복사 루프의 별도 컴파일러 별칭이며 이름과 달리 이 DMA 컨트롤러를 사용하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

전체 프로그램은 $C600의 16바이트 정렬 버퍼로 세 전송 경로를 비교합니다. GDMA는 32바이트를 $8010에 복사하여 (16,24)에 왼쪽 위 한 픽셀에서 맨 아래 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 DMA를 같은 VBlank 안에서 시작·취소하여 첫 블록도 복사하지 않습니다. $FF55는 $81이어야 하며 한 프레임 뒤에도 두 패턴은 그대로여야 합니다. DMG에서는 위 두 행이 비어 있고 이 두 보존 확인 도형만 검정으로 보입니다. 글자는 흰 바탕에 검정입니다. FAILED CHECKS 000은 복사 바이트와 완료 상태도 확인합니다. KOKURA에서 겸용 DMG/CGB 및 CGB 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.

__cgb_safe_set_hdma4: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma4: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma4: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1874

__cgb_safe_set_hdma5

HDMA5($FF55)에 써서 DMA를 시작하거나 진행 중인 HBlank 전송을 취소합니다. 새 전송에서는 비트 0~6이 블록 수-1, 비트 7이 GDMA(0) 또는 HBlank DMA(1)를 뜻합니다. HBlank 전송 중 비트 7을 끄고 쓰면 남은 작업을 취소하며 새 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에서 시작합니다. 동작 중 원본 뱅크, 대상 VBK, 원본 데이터를 바꾸지 마세요. HALT 중에는 HBlank DMA도 멈춥니다. 이 함수는 대기·인터럽트 보호·범위 검사를 추가하지 않습니다.

상태는 $FF55를 따로 읽습니다. 비트 7이 0이면 진행 중, $FF이면 완료입니다. 취소 후 비트 7은 1이고 비트 0~6에는 남은 블록 수-1이 남습니다. 이미 복사한 바이트는 되돌리지 않습니다. 비활성 상태에서 비트 7을 끄고 쓰면 GDMA가 시작되므로 취소는 활성 상태에서만 하세요. 전체 전송이 유효한 원본 메모리와 대상 VRAM 뱅크 안에 들어가야 하며 주소 넘침에 의존하지 마세요.

이 HDMA 레지스터 설정 함수들은 CGB 하드웨어 DMA를 제어합니다. __vram_copy_dma는 CPU 복사 루프의 별도 컴파일러 별칭이며 이름과 달리 이 DMA 컨트롤러를 사용하지 않습니다.

사용 예

c
__cgb_safe_set_hdma5(1); // LCD is off: GDMA, (1+1)*16 = 32 bytes.
if (color_mode && D_CONTROL!=255) failures++;

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

전체 프로그램은 $C600의 16바이트 정렬 버퍼로 세 전송 경로를 비교합니다. GDMA는 32바이트를 $8010에 복사하여 (16,24)에 왼쪽 위 한 픽셀에서 맨 아래 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 DMA를 같은 VBlank 안에서 시작·취소하여 첫 블록도 복사하지 않습니다. $FF55는 $81이어야 하며 한 프레임 뒤에도 두 패턴은 그대로여야 합니다. DMG에서는 위 두 행이 비어 있고 이 두 보존 확인 도형만 검정으로 보입니다. 글자는 흰 바탕에 검정입니다. FAILED CHECKS 000은 복사 바이트와 완료 상태도 확인합니다. KOKURA에서 겸용 DMG/CGB 및 CGB 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.

__cgb_safe_set_hdma5: --cgb=cgb / DMG
--cgb=cgb / DMG
__cgb_safe_set_hdma5: --cgb=cgb / CGB
--cgb=cgb / CGB
__cgb_safe_set_hdma5: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_dma_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_dma_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1875

__cgb_safe_set_obpd

CGB의 스프라이트(OBJ) 팔레트 데이터 레지스터 OCPD/OBPD($FF6B)에 한 바이트를 씁니다. __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픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. CGB 검사에서는 CGB MODE가 1입니다. 실기와 LCD 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.

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

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_palette_attributes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_palette_attributes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:1869

__cgb_safe_set_obpi

CGB의 스프라이트(OBJ) 팔레트 바이트 인덱스 레지스터 OCPS/OBPI($FF6A)에 씁니다. __cgb_safe_set_ocps도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.

사용 형식

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

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.

반환값

없음(void).

사용 조건과 제한

팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.

사용 예

c
__cgb_safe_set_obpi(0xBE); // Alias of OCPS: select the OBJ store's last word.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

같은 타일 패턴에도 BG와 OBJ의 별도 팔레트가 적용됨을 배우는 프로그램입니다. 각 삼각형은 8 × 8픽셀로, 맨 위 왼쪽 한 픽셀에서 맨 아래 8픽셀까지 넓어집니다. x = 16, 32, 48, 64, 80, 96, 112에서 y = 24의 BG 행은 빨강·파랑·초록·빨강·파랑·초록·빨강, y = 64의 OBJ 행은 파랑·초록·빨강·파랑·초록·빨강·파랑입니다. 각 열은 한 색, RGB 성분, 인덱스 범위, 전체 팔레트, 절대 범위, 기본 레지스터 이름, 별칭 레지스터 이름을 차례로 사용합니다.

y = 88의 8 × 8칸 일곱 개는 x = 16부터 차례로 빨간 삼각형, 뱅크 1의 파란 1픽셀 두께 사각 테두리(32), 좌우 반전한 초록 삼각형(48), 상하 반전한 빨간 삼각형(64), 양방향 반전한 파란 삼각형(80), 빨간 스프라이트 앞의 초록 삼각형(96), 우선순위 비교용으로 BG 앞을 덮는 빨간 정사각형 스프라이트(112)입니다. x = 96에서는 BG 색 번호 0 부분으로 빨강이 보입니다. 좌표는 화면 왼쪽 위 기준의 픽셀 단위이며 여백은 흰색, 글자는 검은색입니다.

FAILED CHECKS 000은 RGB 마스크, 2를 포함한 참·거짓 플래그, 팔레트 인덱스 순환, 팔레트 경계를 넘는 쓰기, 슬롯 31에서 제한, null·길이 0 무동작, LCD를 끈 상태의 색 읽기 검사가 통과했음을 뜻합니다. 겸용 ROM의 DMG/CGB 실행, DMG 전용의 DMG 실행, CGB 전용의 CGB 실행 등 네 조합을 검사합니다. DMG에서는 CGB MODE가 0이고 삼각형은 검정이며 뱅크·반전 속성은 표시를 바꾸지 않고 맨 아래 마지막 두 칸은 검은 정사각형입니다. 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 모두 $D000–$DFFF에 뱅크 1을 매핑합니다. 8도 뱅크 1입니다. __svbk_set과 달리 겸용 빌드에서도 사용할 수 있지만 이전 뱅크 번호는 반환하지 않습니다.

사용 형식

c
void __cgb_safe_set_svbk(u8 value);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

value

레지스터에 쓸 u8 값(0~255). 각 비트의 의미는 위 설명을 따릅니다. 포인터나 바이트 수가 아닌 레지스터 값 한 바이트입니다.

반환값

없음(void).

사용 조건과 제한

컴파일러가 CGB 모드를 확인해 쓰기를 보호하며 DMG 모드에서는 쓰지 않습니다. safe는 이 모드 확인만 뜻합니다. 이전 레지스터 값을 저장하거나 복원하지 않습니다.

뱅크 전환은 $D000–$DFFF의 모든 접근에 영향을 주며 고정 WRAM $C000–$CFFF는 그대로입니다. 전환 중 필요한 스택과 상태는 전환 창 밖에 두세요. 예제는 --stack-bank=fixed와 명시적 고정 WRAM 배치를 사용합니다. 인터럽트·뱅크 DMA와의 조정은 호출자 책임이며 이 API는 임계 구역을 만들지 않습니다.

사용 예

c
__cgb_safe_set_svbk(0); // Raw selector 0 maps effective bank 1; this API returns void.
if ((W_SELECTOR&7)!=0) failures++;

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

CGB 전용 전체 프로그램은 WRAM 뱅크 2의 $D100에 1, 뱅크 3의 같은 주소에 2를 저장한 뒤 둘을 읽고 뱅크 1로 돌아옵니다. 이 값으로 (16,24)의 빨간 8 × 8 정사각형과 (16,48)의 파란 1픽셀 두께 8 × 8 사각 테두리를 고릅니다. FAILED CHECKS 000은 원시 선택값 정규화, 뱅크 독립성, 상수·변수·식·함수 반환값으로 지정했을 때 이전 뱅크 반환을 확인합니다. 별도 잘못된 빌드 테스트 여섯 개로 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 토큰으로 반환합니다. 가장 바깥 범위는 0, 첫 중첩 범위는 1입니다.

사용 조건과 제한

토큰은 진입의 역순으로 leave에 전달하고, 깊이가 256에서 래핑되지 않게 하세요. 기존 하드웨어 IME는 저장하지 않으므로 이전 DI 상태를 보존해야 하는 곳에서는 이 쌍을 사용하지 마세요.

사용 예

c
*((u8*)0xFFFF)=0;outer=__critical_enter();inner=__critical_enter();result[7]=outer;result[8]=inner;

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.

__critical_enter: DMG
DMG
__critical_enter: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:7970

kitaqgb/kitaqgb/CodeGenerator.cs:7970

csharp
if (funcName == "__critical_enter")
            {
                if (args.Length != 0) Error(expr, "__critical_enter() expects 0 arguments");

                EmitLoadA(CriticalDepthVar);
                EmitAsm("LD_B_A"); // token = old depth
                EmitAsm("INC_A");
                EmitStoreA(CriticalDepthVar);
                EmitAsm("DI");
                EmitAsm("LD_A_B");
                return;
            }
__critical_leave

중첩 깊이를 줄이고, 바깥 범위의 토큰 0이면 EI를 실행합니다.

사용 형식

c
void __critical_leave(u8 token);

매개변수

token

대응하는 __critical_enter가 반환한 토큰입니다.

반환값

반환값은 없습니다.

사용 조건과 제한

0이 아닌 토큰은 EI를 실행하지 않습니다. 임의로 0을 전달하면 다른 범위가 활성 상태여도 인터럽트가 활성화될 수 있으므로 호출 쌍을 정확히 맞추세요.

사용 예

c
__critical_leave(inner);__critical_leave(outer);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.

__critical_leave: DMG
DMG
__critical_leave: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:7984

kitaqgb/kitaqgb/CodeGenerator.cs:7984

csharp
if (funcName == "__critical_leave")
            {
                if (args.Length != 1) Error(expr, "__critical_leave(token) expects 1 argument");

                CompileIntoA(args[0]);
                EmitAsm("LD_B_A"); // token

                AsmOperand clAfterDec = MakeUniqueLabel("critleave_afterdec");
                AsmOperand clDone = MakeUniqueLabel("critleave_done");

                EmitLoadA(CriticalDepthVar);
                EmitAsm("OR_A");
                EmitAsm("JR_Z", clAfterDec);
                EmitAsm("DEC_A");
                EmitStoreA(CriticalDepthVar);
                EmitLabel(clAfterDec);

                // Re-enable only for outermost token (token==0).
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("JR_NZ", clDone);
                EmitAsm("EI");
                EmitLabel(clDone);
                return;
            }
__dot2_q8_8

부호 없는 두 항의 내적을 계산합니다.

사용 형식

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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[36]=__dot2_q8_8(513,769,64,127);
v0=513;v1=769;v2=64;v3=127;
result[37]=__dot2_q8_8(v0,v1,v2,v3);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[36]=509, result[37]=509. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__dot2_q8_8: DMG
DMG
__dot2_q8_8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11575

kitaqgb/kitaqgb/CodeGenerator.cs:11575

csharp
if (funcName == "__dot2_q8_8")
{
    if (args.Length != 4) Error(expr, "__dot2_q8_8 requires 4 arguments");
    LastCallReturnsHL = true;

    // Fast paths when coefficients are compile-time constants.
    // Special-case: 0 / power-of-two coefficients (u8) => shift instead of mul.
    if (TryGetU8Const(args[2], out int ax0) && (ax0 == 0) && TryGetU8Const(args[3], out int ay0) && (ay0 == 0))
    {
        CompileDiscard(args[0]);
        CompileDiscard(args[1]);
        EmitAsm("XOR_A");
        EmitAsm("LD_H_A");
        EmitAsm("LD_L_A");
        return;
    }
    if (TryGetU8Const(args[2], out int axOnly) && (axOnly == 0))
    {
        CompileDiscard(args[0]);
        // only y*ay
        if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
        {
            CompileIntoHL(args[1]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[3]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitMul16x8Shr8_HL("__dot2_y");
        }
        return;
    }
    if (TryGetU8Const(args[3], out int ayOnly) && (ayOnly == 0))
    {
        // only x*ax
        if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
        {
            CompileIntoHL(args[0]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[2]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitMul16x8Shr8_HL("__dot2_x");
        }
        // Preserve the scaled result while evaluating the zero-product input.
        EmitAsm("PUSH_HL");
        CompileDiscard(args[1]);
        EmitAsm("POP_HL");
        return;
    }

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitMul16x8Shr8_HL("__dot2_x");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitMul16x8Shr8_HL("__dot2_y");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__dot3_q8_8

부호 없는 세 항의 내적을 계산합니다.

사용 형식

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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[6]=__dot3_q8_8(513,769,1025,64,127,3);
v0=513;v1=769;v2=1025;v3=64;v4=127;v5=3;
result[7]=__dot3_q8_8(v0,v1,v2,v3,v4,v5);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[6]=521, result[7]=521. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__dot3_q8_8: DMG
DMG
__dot3_q8_8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11550

kitaqgb/kitaqgb/CodeGenerator.cs:11550

csharp
if (funcName == "__dot3_q8_8")
            {
                if (args.Length != 6) Error(expr, "__dot3_q8_8 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: false, q1_7: false, labelPrefix: "__dot3_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: false, q1_7: false, labelPrefix: "__dot3_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: false, q1_7: false, labelPrefix: "__dot3_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__farcall

지정한 뱅크 번호로 인수가 없는 이름 있는 함수를 호출합니다. 뱅크 전환이 필요하면 고정 ROM의 보조 코드가 함수 반환 후 호출자의 뱅크를 복원합니다.

사용 형식

c
__farcall(bank, function_name);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

bank

부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.

func

선언된 매개변수 없는 함수의 이름입니다. 이 이름 기반 호출 내장 함수에는 함수 주소를 담은 변수를 전달할 수 없습니다. bank 인수는 콜백 매개변수로 전달되지 않습니다.

반환값

반환형은 호출한 함수에 따릅니다. void 콜백은 값을 반환하지 않고 u8·u16 콜백은 해당 값을 반환합니다. 화면 예제는 void 콜백을 사용합니다.

사용 조건과 제한

이 호출은 사용자 인수를 전달하거나 콜백의 인터럽트 재진입성을 보장하지 않습니다. 콜백 자체의 데이터, 스택, 인터럽트 사용을 올바르게 관리하세요. 뱅크 복귀는 중첩 및 반복 호출로 검사했지만 실제 하드웨어와 임의의 인터럽트 타이밍은 이 예제의 검증 범위가 아닙니다.

이 GB 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

완성 프로그램의 이 지점에서 __farcall는 callback_count를 2으로 만듭니다. 콜백은 뱅크 3에서 D3을 읽습니다. 이후 home2를 근거리로 읽어 B2인지 검사함으로써 뱅크 2가 매핑된 상태로 복귀했는지 확인합니다.

완성 프로그램은 뱅크 번호와 근거리 주소로 데이터를 지정하는 방법, 뱅크를 넘는 콜백, 라이브러리 기록과 실제 매핑의 구별을 보여 줍니다. 모든 표시 숫자는 16진수입니다. BANK IN과 BANK OUT은 02, BYTE HEX는 D3, WORD HEX는 5C7A, FAILED CHECKS와 FIRST FAILURE는 모두 00이어야 합니다. CGB와 FC는 파란 글자, DMG는 검은 글자를 표시합니다.

CALL COUNT는 03입니다. far_call, __farcall, __farcall_ptr이 각각 콜백을 한 번 호출합니다.

__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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[34]=__mac16(65500,513,127);
v0=65500;v1=513;v2=127;
result[35]=__mac16(v0,v1,v2);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[34]=218, result[35]=218. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__mac16: DMG
DMG
__mac16: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11499

kitaqgb/kitaqgb/CodeGenerator.cs:11499

csharp
if (funcName == "__mac16")
            {
                if (args.Length != 3) Error(expr, "__mac16 requires 3 arguments");

                // mul = (a*b)>>8
                EmitDotTermToHL(args[1], args[2], signed: false, q1_7: false, labelPrefix: "__mac16_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__mul16x8

부호 없는 곱셈을 수행합니다.

사용 형식

c
u16 __mul16x8(u16 a, u8 b);

매개변수

a

부호 없는 워드 0~65535입니다.

b

부호 없는 계수 0~255입니다.

반환값

각 항은 floor(a*b/256)이며 하위 16비트를 반환합니다.

사용 조건과 제한

GB에서는 곱을 256으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[24]=__mul16x8(513,127);
v0=513;v1=127;
result[25]=__mul16x8(v0,v1);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[24]=254, result[25]=254. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__mul16x8: DMG
DMG
__mul16x8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11467

kitaqgb/kitaqgb/CodeGenerator.cs:11467

csharp
if (funcName == "__mul16x8")
            {
                if (args.Length != 2) Error(expr, "__mul16x8 requires 2 arguments");

                EmitDotTermToHL(args[0], args[1], signed: false, q1_7: false, labelPrefix: "__mul16x8");
                LastCallReturnsHL = true;
                return;
            }
__mul8x8_hi

부호 없는 두 바이트를 곱하고 상위 바이트를 반환합니다.

사용 형식

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비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[4]=254, result[5]=254. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__mul8x8_hi: DMG
DMG
__mul8x8_hi: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11452

kitaqgb/kitaqgb/CodeGenerator.cs:11452

csharp
if (funcName == "__mul8x8_hi")
            {
                if (args.Length != 2) Error(expr, "__mul8x8_hi requires 2 arguments");

                CompileIntoA(args[0]);
                EmitAsm("PUSH_AF");
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_AF");
                EmitAsm("LD_E_A");
                EmitMul8x8ToHL_EC("__mul8x8_hi");
                EmitAsm("LD_A_H");
                return;
            }
__padrep

mask로 지정한 버튼 중 하나를 골라 반복합니다. 대상 버튼을 새로 누르면 켜진 비트 중 가장 낮은 비트를 선택해 즉시 반환합니다. 그렇지 않으면 현재 동작이 대상에 포함되고 계속 눌려 있는 동안 유지합니다. 그 동작이 끝나면 남은 눌림 비트 중 가장 낮은 비트를 선택하되 즉시 펄스를 내지는 않습니다. 대상 버튼이 모두 놓이면 실행 상태를 지웁니다.

사용 형식

c
u8 __padrep(u8* state, u8 keys, u8 trigger, u8 mask);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

state

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 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 중 하나입니다. 0이 아닌 값은 버튼 비트이며, 0과 1로 정규화된 논리값이나 반복 횟수가 아닙니다.

사용 조건과 제한

공통 마스크는 BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80입니다. 1인 비트가 눌림을 뜻합니다. GB와 FC의 input_*는 이 형식을 쓰지만 FC의 원시 패드 함수는 비트 순서가 다릅니다.

초기 대기 시간으로, 반복 함수 호출 횟수 0~255를 지정합니다. 양수 N이면 첫 입력 펄스에서 정확히 N회 호출 뒤에 첫 장기 누름 반복이 발생합니다. 0이면 초기 대기 시간과의 일치 분기를 건너뛰므로 반복 간격이 첫 반복 시점을 결정합니다. 밀리초 단위가 아닙니다.

이후 반복 간격으로 0~255회 호출을 지정합니다. 양수 N이면 첫 반복 이후 대상 버튼을 누른 상태의 갱신 N회마다 펄스가 발생합니다. 0은 초기 대기가 끝난 뒤 대상 갱신마다 발생하므로, 이 단계에서는 0과 1 모두 매번 펄스를 반환합니다. 대기 시간과 간격이 모두 0이면 누르고 있는 매 갱신에서 펄스가 발생합니다.

입력 처리 주기마다 한 번 호출하세요. 호출할 때마다 상태가 바뀌며 VBlank를 기다리지 않습니다. 새 입력 비트 없이 이미 눌린 버튼을 선택하면 그 호출에서는 0을 반환하고 새 대기를 시작합니다. 이 함수들은 상태 하나당 동작 하나를 선택합니다. 반면 input_repeat는 8개 버튼마다 독립적인 타이머를 사용합니다.

사용 예

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

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 u8 state[5]를 준비합니다. 배치는 [0] 선택된 버튼 비트, [1] 초기 대기 시간, [2] 반복 간격, [3] 초기 대기 경과 카운터, [4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.

keys

GB의 BTN_* 비트 배치로 표현한 현재 전체 눌림 마스크입니다. __readpad, __readpadex의 하위 바이트 또는 input_current에서 얻은 값을 전달합니다. 반복 함수 자체는 하드웨어를 읽지 않습니다.

trigger

이번 갱신에서 새로 눌린 비트를 keys와 같은 배치로 전달합니다. 보통 keys & ~previous_keys 또는 __readpadex의 상위 바이트입니다. keys에도 있는 비트만 포함해야 합니다. 매번 눌림 마스크를 그대로 전달하면 호출마다 새 입력으로 처리되어 초기 대기가 다시 시작됩니다.

반환값

동작 펄스가 없으면 0, 있으면 8 중 하나입니다. 0이 아닌 값은 버튼 비트이며, 0과 1로 정규화된 논리값이나 반복 횟수가 아닙니다.

사용 조건과 제한

공통 마스크는 BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80입니다. 1인 비트가 눌림을 뜻합니다. GB와 FC의 input_*는 이 형식을 쓰지만 FC의 원시 패드 함수는 비트 순서가 다릅니다.

초기 대기 시간으로, 반복 함수 호출 횟수 0~255를 지정합니다. 양수 N이면 첫 입력 펄스에서 정확히 N회 호출 뒤에 첫 장기 누름 반복이 발생합니다. 0이면 초기 대기 시간과의 일치 분기를 건너뛰므로 반복 간격이 첫 반복 시점을 결정합니다. 밀리초 단위가 아닙니다.

이후 반복 간격으로 0~255회 호출을 지정합니다. 양수 N이면 첫 반복 이후 대상 버튼을 누른 상태의 갱신 N회마다 펄스가 발생합니다. 0은 초기 대기가 끝난 뒤 대상 갱신마다 발생하므로, 이 단계에서는 0과 1 모두 매번 펄스를 반환합니다. 대기 시간과 간격이 모두 0이면 누르고 있는 매 갱신에서 펄스가 발생합니다.

입력 처리 주기마다 한 번 호출하세요. 호출할 때마다 상태가 바뀌며 VBlank를 기다리지 않습니다. 새 입력 비트 없이 이미 눌린 버튼을 선택하면 그 호출에서는 0을 반환하고 새 대기를 시작합니다. 이 함수들은 상태 하나당 동작 하나를 선택합니다. 반면 input_repeat는 8개 버튼마다 독립적인 타이머를 사용합니다.

사용 예

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바이트 반복 상태를 초기화합니다. 선택된 동작과 두 카운터를 0으로 만들고, 지정한 초기 대기 시간과 반복 간격을 저장합니다. 버튼을 읽거나 반복 펄스를 만들지는 않습니다.

사용 형식

c
void __padrep_init(u8* state, u8 das, u8 arr);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

state

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 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

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 u8 state[5]를 준비합니다. 배치는 [0] 선택된 버튼 비트, [1] 초기 대기 시간, [2] 반복 간격, [3] 초기 대기 경과 카운터, [4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.

keys

GB의 BTN_* 비트 배치로 표현한 현재 전체 눌림 마스크입니다. __readpad, __readpadex의 하위 바이트 또는 input_current에서 얻은 값을 전달합니다. 반복 함수 자체는 하드웨어를 읽지 않습니다.

trigger

이번 갱신에서 새로 눌린 비트를 keys와 같은 배치로 전달합니다. 보통 keys & ~previous_keys 또는 __readpadex의 상위 바이트입니다. keys에도 있는 비트만 포함해야 합니다. 매번 눌림 마스크를 그대로 전달하면 호출마다 새 입력으로 처리되어 초기 대기가 다시 시작됩니다.

반환값

동작 펄스가 없으면 0, 있으면 1, 2 중 하나입니다. 0이 아닌 값은 버튼 비트이며, 0과 1로 정규화된 논리값이나 반복 횟수가 아닙니다.

사용 조건과 제한

공통 마스크는 BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80입니다. 1인 비트가 눌림을 뜻합니다. GB와 FC의 input_*는 이 형식을 쓰지만 FC의 원시 패드 함수는 비트 순서가 다릅니다.

초기 대기 시간으로, 반복 함수 호출 횟수 0~255를 지정합니다. 양수 N이면 첫 입력 펄스에서 정확히 N회 호출 뒤에 첫 장기 누름 반복이 발생합니다. 0이면 초기 대기 시간과의 일치 분기를 건너뛰므로 반복 간격이 첫 반복 시점을 결정합니다. 밀리초 단위가 아닙니다.

이후 반복 간격으로 0~255회 호출을 지정합니다. 양수 N이면 첫 반복 이후 대상 버튼을 누른 상태의 갱신 N회마다 펄스가 발생합니다. 0은 초기 대기가 끝난 뒤 대상 갱신마다 발생하므로, 이 단계에서는 0과 1 모두 매번 펄스를 반환합니다. 대기 시간과 간격이 모두 0이면 누르고 있는 매 갱신에서 펄스가 발생합니다.

입력 처리 주기마다 한 번 호출하세요. 호출할 때마다 상태가 바뀌며 VBlank를 기다리지 않습니다. 새 입력 비트 없이 이미 눌린 버튼을 선택하면 그 호출에서는 0을 반환하고 새 대기를 시작합니다. 이 함수들은 상태 하나당 동작 하나를 선택합니다. 반면 input_repeat는 8개 버튼마다 독립적인 타이머를 사용합니다.

사용 예

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

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 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 중 하나입니다. 0이 아닌 값은 버튼 비트이며, 0과 1로 정규화된 논리값이나 반복 횟수가 아닙니다.

사용 조건과 제한

공통 마스크는 BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80입니다. 1인 비트가 눌림을 뜻합니다. GB와 FC의 input_*는 이 형식을 쓰지만 FC의 원시 패드 함수는 비트 순서가 다릅니다.

초기 대기 시간으로, 반복 함수 호출 횟수 0~255를 지정합니다. 양수 N이면 첫 입력 펄스에서 정확히 N회 호출 뒤에 첫 장기 누름 반복이 발생합니다. 0이면 초기 대기 시간과의 일치 분기를 건너뛰므로 반복 간격이 첫 반복 시점을 결정합니다. 밀리초 단위가 아닙니다.

이후 반복 간격으로 0~255회 호출을 지정합니다. 양수 N이면 첫 반복 이후 대상 버튼을 누른 상태의 갱신 N회마다 펄스가 발생합니다. 0은 초기 대기가 끝난 뒤 대상 갱신마다 발생하므로, 이 단계에서는 0과 1 모두 매번 펄스를 반환합니다. 대기 시간과 간격이 모두 0이면 누르고 있는 매 갱신에서 펄스가 발생합니다.

입력 처리 주기마다 한 번 호출하세요. 호출할 때마다 상태가 바뀌며 VBlank를 기다리지 않습니다. 새 입력 비트 없이 이미 눌린 버튼을 선택하면 그 호출에서는 0을 반환하고 새 대기를 시작합니다. 이 함수들은 상태 하나당 동작 하나를 선택합니다. 반면 input_repeat는 8개 버튼마다 독립적인 타이머를 사용합니다.

사용 예

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

설정된 대기 시간과 간격은 유지하면서 선택된 동작을 취소하고 두 경과 카운터를 0으로 만듭니다. 메뉴를 닫거나 입력 대상을 바꿀 때 사용하면 이전에 누르고 있던 버튼의 반복 진행 상태가 다음 조작으로 이어지지 않습니다.

사용 형식

c
void __padrep_reset(u8* state);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

state

쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어 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비트 마스크입니다. 여러 버튼 비트가 동시에 설정될 수 있으며 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의 동작 버튼만 하위 4비트로 읽습니다. A=1, B=2, Select=4, Start=8입니다. 공통 마스크 BTN_A~BTN_START는 상위 4비트이므로 이 결과와 직접 비교하면 안 됩니다. JOYP 선택은 0x30으로 되돌립니다.

사용 형식

c
u8 __readpadbtn();

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

반환값

위 비트 순서의 0~15 마스크입니다. 여러 비트가 설정될 수 있으므로 전체 값이 1인지 비교하지 말고 result & bit로 특정 버튼을 확인하세요.

사용 조건과 제한

이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

세 가지 버튼 마스크 형식과 현재 상태·새 입력을 묶은 반환값을 비교하는 프로그램입니다. 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 마스크입니다. 여러 비트가 설정될 수 있으므로 전체 값이 1인지 비교하지 말고 result & bit로 특정 버튼을 확인하세요.

사용 조건과 제한

이 호출은 하드웨어 상태를 새로 읽습니다. 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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[20]=__sdot2_q1_7(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[21]=__sdot2_q1_7(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[22]=__sdot2_q1_7(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[23]=__sdot2_q1_7(v0,v1,v2,v3);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[20]=1018, result[21]=1018, result[22]=64518, result[23]=64518. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__sdot2_q1_7: DMG
DMG
__sdot2_q1_7: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11732

kitaqgb/kitaqgb/CodeGenerator.cs:11732

csharp
if (funcName == "__sdot2_q1_7")
{
    if (args.Length != 4) Error(expr, "__sdot2_q1_7 requires 4 arguments");
    LastCallReturnsHL = true;

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: true, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_q1_7_x");
        EmitAsm("ADD_HL_HL");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: true, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_q1_7_y");
        EmitAsm("ADD_HL_HL");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__sdot2_q8_8

부호 있는 두 항의 내적을 계산합니다.

사용 형식

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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[30]=__sdot2_q8_8(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[31]=__sdot2_q8_8(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[32]=__sdot2_q8_8(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[33]=__sdot2_q8_8(v0,v1,v2,v3);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[30]=509, result[31]=509, result[32]=65027, result[33]=65027. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__sdot2_q8_8: DMG
DMG
__sdot2_q8_8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11654

kitaqgb/kitaqgb/CodeGenerator.cs:11654

csharp
if (funcName == "__sdot2_q8_8")
{
    if (args.Length != 4) Error(expr, "__sdot2_q8_8 requires 4 arguments");
    LastCallReturnsHL = true;

    // Fast paths when coefficients are compile-time constants.
    // Special-case: 0 / power-of-two coefficients (s8 two's complement) => arithmetic shift instead of mul.
    if (TryGetS8Const(args[2], out int sax0) && (sax0 == 0) && TryGetS8Const(args[3], out int say0) && (say0 == 0))
    {
        CompileDiscard(args[0]);
        CompileDiscard(args[1]);
        EmitAsm("XOR_A");
        EmitAsm("LD_H_A");
        EmitAsm("LD_L_A");
        return;
    }
    if (TryGetS8Const(args[2], out int saxOnly) && (saxOnly == 0))
    {
        CompileDiscard(args[0]);
        // only y*ay
        if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
        {
            CompileIntoHL(args[1]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[3]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitSMul16x8Shr8_HL("__sdot2_y");
        }
        return;
    }
    if (TryGetS8Const(args[3], out int sayOnly) && (sayOnly == 0))
    {
        // only x*ax
        if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
        {
            CompileIntoHL(args[0]);
            EmitAsm("PUSH_HL");
            CompileIntoA(args[2]);
            EmitAsm("LD_C_A");
            EmitAsm("POP_HL");
            EmitSMul16x8Shr8_HL("__sdot2_x");
        }
        // Preserve the scaled result while evaluating the zero-product input.
        EmitAsm("PUSH_HL");
        CompileDiscard(args[1]);
        EmitAsm("POP_HL");
        return;
    }

    // x*ax
    if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
    {
        CompileIntoHL(args[0]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[2]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_x");
    }
    EmitAsm("PUSH_HL");

    // y*ay
    if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
    {
        CompileIntoHL(args[1]);
        EmitAsm("PUSH_HL");
        CompileIntoA(args[3]);
        EmitAsm("LD_C_A");
        EmitAsm("POP_HL");
        EmitSMul16x8Shr8_HL("__sdot2_y");
    }
    EmitAsm("POP_DE");
    EmitAsm("ADD_HL_DE");
    return;
}
__sdot3_q1_7

부호 있는 세 항의 내적을 계산합니다.

사용 형식

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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[16]=__sdot3_q1_7(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[17]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[18]=__sdot3_q1_7(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[19]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[16]=1042, result[17]=1042, result[18]=64494, result[19]=64494. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__sdot3_q1_7: DMG
DMG
__sdot3_q1_7: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11790

kitaqgb/kitaqgb/CodeGenerator.cs:11790

csharp
if (funcName == "__sdot3_q1_7")
            {
                if (args.Length != 6) Error(expr, "__sdot3_q1_7 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__sdot3_q8_8

부호 있는 세 항의 내적을 계산합니다.

사용 형식

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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[8]=__sdot3_q8_8(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[9]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[10]=__sdot3_q8_8(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[11]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[8]=521, result[9]=521, result[10]=65015, result[11]=65015. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__sdot3_q8_8: DMG
DMG
__sdot3_q8_8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11767

kitaqgb/kitaqgb/CodeGenerator.cs:11767

csharp
if (funcName == "__sdot3_q8_8")
            {
                if (args.Length != 6) Error(expr, "__sdot3_q8_8 requires 6 arguments");
                LastCallReturnsHL = true;

                // x*ax
                EmitDotTermToHL(args[0], args[3], signed: true, q1_7: false, labelPrefix: "__sdot3_x");
                EmitAsm("PUSH_HL");

                // y*ay
                EmitDotTermToHL(args[1], args[4], signed: true, q1_7: false, labelPrefix: "__sdot3_y");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                EmitAsm("PUSH_HL");

                // z*az
                EmitDotTermToHL(args[2], args[5], signed: true, q1_7: false, labelPrefix: "__sdot3_z");
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                return;
            }
__settile_fast

타일 맵의 한 셀에 타일 번호를 씁니다. 이미 등록된 타일 이미지를 선택하며 픽셀 데이터는 바꾸지 않습니다.

대상 주소는 항상 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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이며 256에서 순환합니다. 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으로 채우고 해당 호출 전에는 BG·윈도 맵을 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로 선택한 BG 맵으로 전송합니다. 원본 오프셋은 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilebg16_flush
// Transfer two adjacent tile rows from the RAM buffer to the LCDC-selected BG map. The
// source offset is `64*y + x`; `count` bytes are copied from that row and from the row
// 32 bytes below it. Waits for writable VRAM when necessary. The LCD-on path uses
// interrupt masking and can enable interrupts afterward; it is not a promise to preserve
// the caller's interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    example_tiles[66]='A'; example_tiles[67]='B';
    example_tiles[98]='C'; example_tiles[99]='D';
    __settilebg16_flush(example_tiles, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

타일 좌표 (2,2)에 위쪽 AB, 아래쪽 CD가 표시됩니다. 셀 좌표 (1,1)에 해당하는 16×16픽셀 셀 하나입니다. 버퍼 쓰기 예제는 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이며 256에서 순환합니다. 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은 이미지 데이터 뱅크, 비트 5·6은 좌우·상하 반전, 비트 7은 BG·윈도 우선순위입니다. 사용하지 않는 비트 4는 0으로 두세요.

반환값

없음(void).

사용 조건과 제한

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilebg16cgb_buf
// Write a 16×16-pixel cell into a RAM tile-map buffer: the four 8×8 tile numbers are
// `tile,tile+1` on top and `tile+2,tile+3` below. Values wrap at 256. This does not
// transfer anything to VRAM. Also write the same `attr` byte to all four corresponding
// cells in the separate attribute buffer. Both buffers are ordinary RAM, so this
// preparation also runs on DMG.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

void main() {
    u8 failures;
    failures = 0;
    tile_example_begin();
    __memset(example_tiles, 0, 1024);
    __memset(example_attrs, 0, 1024);
    __settilebg16cgb_buf(example_tiles, example_attrs, 1, 1, 'A', 2);
    // The buffer operation must not write to VRAM yet.
    if (tile_example_read(0x9C42, 0) != 0) { failures++; }
    // Transfer the prepared cell so its four characters can be seen.
    __settilebg16cgb_flush(example_tiles, example_attrs, 1, 2, 2);
    if (tile_example_read(0x9C42, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C43, 0) != 'B') { failures++; }
    if (tile_example_read(0x9C62, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 'D') { failures++; }
    if (example_tiles[66] != 'A') { failures++; }
    if (example_tiles[99] != 'D') { failures++; }
    if (tile_example_read(0x9C44, 0) != 0) { failures++; }
    if (example_attrs[66] != 2) { failures++; }
    if (example_attrs[99] != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C42, 1) != 2) { failures++; }
    if (__cgb_is_cgb() && tile_example_read(0x9C63, 1) != 2) { failures++; }
    tile_example_finish(failures, 1);
}

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

타일 좌표 (2,2)에 위쪽 AB, 아래쪽 CD가 표시됩니다. 셀 좌표 (1,1)에 해당하는 16×16픽셀 셀 하나입니다. 버퍼 쓰기 예제는 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로 선택한 BG 맵으로 전송합니다. 원본 오프셋은 64*y + x이며 그 행과 32바이트 아래 행에서 각각 count바이트를 복사합니다.

타일 번호와 CGB 속성을 함께 씁니다. DMG에서는 번호만 씁니다. CGB에서는 뱅크 0을 선택한 뒤 호출하세요. 속성은 뱅크 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으로 채우고 해당 호출 전에는 BG·윈도 맵을 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으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[26]=__smac16(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[27]=__smac16(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[28]=__smac16(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[29]=__smac16(v0,v1,v2);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[26]=92, result[27]=92, result[28]=65372, result[29]=65372. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__smac16: DMG
DMG
__smac16: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11516

kitaqgb/kitaqgb/CodeGenerator.cs:11516

csharp
if (funcName == "__smac16")
            {
                if (args.Length != 3) Error(expr, "__smac16 requires 3 arguments");

                // mul = (a*b)>>8 (signed)
                EmitDotTermToHL(args[1], args[2], signed: true, q1_7: false, labelPrefix: "__smac16_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__smac16_q1_7

부호 있는 곱셈 누산을 수행합니다.

사용 형식

c
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);

매개변수

acc

더할 16비트 누산값입니다.

a

−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.

b

−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.

반환값

각 항 2*trunc(a*b/256)을 acc에 더하고 하위 16비트를 반환합니다.

사용 조건과 제한

GB에서는 곱을 256으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[0]=__smac16_q1_7(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[1]=__smac16_q1_7(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[2]=__smac16_q1_7(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[3]=__smac16_q1_7(v0,v1,v2);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[0]=220, result[1]=220, result[2]=65244, result[3]=65244. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__smac16_q1_7: DMG
DMG
__smac16_q1_7: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11533

kitaqgb/kitaqgb/CodeGenerator.cs:11533

csharp
if (funcName == "__smac16_q1_7")
            {
                if (args.Length != 3) Error(expr, "__smac16_q1_7 requires 3 arguments");

                // mul = (a*b)>>7 (approx via >>8 then *2)
                EmitDotTermToHL(args[1], args[2], signed: true, q1_7: true, labelPrefix: "__smac16_q1_7_mul");

                // acc + mul
                EmitAsm("PUSH_HL");
                CompileIntoHL(args[0]);
                EmitAsm("POP_DE");
                EmitAsm("ADD_HL_DE");
                LastCallReturnsHL = true;
                return;
            }
__smul16x8_q1_7

부호 있는 곱셈을 수행합니다.

사용 형식

c
u16 __smul16x8_q1_7(u16 a, u8 b);

매개변수

a

−32768~32767을 나타내는 부호 있는 16비트 비트 패턴입니다.

b

−128~127 계수를 나타내는 부호 있는 바이트 비트 패턴입니다.

반환값

각 항은 2*trunc(a*b/256)이며 하위 16비트를 반환합니다.

사용 조건과 제한

GB에서는 곱을 256으로 나누며, 부호 있는 결과는 0 방향으로 버립니다. Q1.7 버전은 그렇게 버린 값을 두 배로 하므로 정확히 128로 나눈 결과와 1 차이 날 수 있습니다. 각 항을 따로 반올림하며, 마지막 덧셈은 16비트에서 래핑됩니다.

사용 예

c
// Compare literal coefficients with values read from RAM.
result[12]=__smul16x8_q1_7(-513,-64);
v0=-513;v1=-64;
result[13]=__smul16x8_q1_7(v0,v1);
// Reverse coefficient signs and verify negative products.
result[14]=__smul16x8_q1_7(-513,64);
v0=-513;v1=64;
result[15]=__smul16x8_q1_7(v0,v1);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

리터럴 입력과 RAM에 저장된 값을 비교합니다. 대표 결과는 부호 없는 16비트 값으로 표시하며 음수는 2의 보수 비트 패턴을 사용합니다. GB는 고정소수점 곱의 크기를 조정하지만 FC는 정수 곱을 반환하므로 같은 입력이라도 결과가 같지 않을 수 있습니다.

이 발췌 코드는 다음 16비트 값을 저장합니다: result[12]=256, result[13]=256, result[14]=65280, result[15]=65280. 리터럴 입력으로 계산한 각 결과는 대응하는 RAM 입력 결과와 같습니다. 화면에는 대표값을 표시하고, result 배열에는 모든 결과를 보관합니다.

__smul16x8_q1_7: DMG
DMG
__smul16x8_q1_7: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_math.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_math.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:11490

kitaqgb/kitaqgb/CodeGenerator.cs:11490

csharp
if (funcName == "__smul16x8_q1_7")
            {
                if (args.Length != 2) Error(expr, "__smul16x8_q1_7 requires 2 arguments");

                EmitDotTermToHL(args[0], args[1], signed: true, q1_7: true, labelPrefix: "__smul16x8_q1_7");
                LastCallReturnsHL = true;
                return;
            }
__sram_read8

CPU 주소에서 SRAM 1바이트를 읽습니다.

사용 형식

c
u8 __sram_read8(u16 address);

매개변수

address

$A000~$BFFF 범위의 CPU 주소입니다.

반환값

RAM 존재 여부나 데이터 유효성을 검사하지 않고 바이트를 반환합니다.

사용 조건과 제한

접근 전에 $0000에 0x0A를 써서 RAM을 활성화하고, 끝난 뒤 0을 써서 비활성화합니다. 뱅크 선택이나 주소 검사는 하지 않으므로 호출자가 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 1바이트를 씁니다.

사용 형식

c
void __sram_write8(u16 address, u8 value);

매개변수

address

$A000~$BFFF 범위의 CPU 주소입니다.

value

바이트 값 0~255입니다.

반환값

반환값은 없습니다.

사용 조건과 제한

접근 전에 $0000에 0x0A를 써서 RAM을 활성화하고, 끝난 뒤 0을 써서 비활성화합니다. 뱅크 선택이나 주소 검사는 하지 않으므로 호출자가 RAM 뱅크를 설정해야 합니다. 저장 헤더나 체크섬은 관리하지 않습니다.

사용 예

c
__sram_write8(0xA006,8);result[20]=*((u8*)0xA006);result[13]=save_exists(0);readback[0]=77;result[14]=save_read(0,readback,3);result[15]=readback[0];

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

7/42/255의 세 바이트를 저장한 뒤 길이 불일치, 잘못된 슬롯, 손상된 체크섬을 검사합니다. 불러오기에 성공하면 가운데 바이트는 42입니다. 읽기에 실패하면 대상의 77을 유지하며, 지운 뒤 검증하면 0을 반환합니다.

__sram_write8: DMG
DMG
__sram_write8: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_save.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_save.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=8k
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:8846

kitaqgb/kitaqgb/CodeGenerator.cs:8846

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

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");

                EmitAsm("LD_A_IMM", new AsmOperand(0x0A, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));

                EmitAsm("POP_HL");
                EmitAsm("LD_A_C");
                EmitAsm("LD_HL_A");

                EmitAsm("LD_A_IMM", new AsmOperand(0x00, AddressMode.Immediate));
                EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
                return;
            }
__svbk_get

$D000–$DFFF에 매핑된 실제 WRAM 뱅크 번호를 읽습니다. SVBK 비트 0~2를 읽고 원시 값 0을 뱅크 1로 정규화합니다. RAM 용량, 주소, 포인터가 아닌 뱅크 번호입니다.

사용 형식

c
u8 __svbk_get();

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

반환값

현재 실제 뱅크 번호(u8, 1~7). 원시 선택값 0은 1로 반환합니다.

사용 조건과 제한

__svbk_get과 __svbk_set에는 --cgb=cgb_only 또는 #pragma rom_cgb cgb_only가 필요합니다. DMG·겸용 빌드는 실행 중 조건으로 호출을 건너뛰더라도 KQ2103으로 거부합니다. 보호된 원시 설정 함수는 그 다른 동작 조건이 프로그램에 맞을 때 사용하세요.

뱅크 전환은 $D000–$DFFF의 모든 접근에 영향을 주며 고정 WRAM $C000–$CFFF는 그대로입니다. 전환 중 필요한 스택과 상태는 전환 창 밖에 두세요. 예제는 --stack-bank=fixed와 명시적 고정 WRAM 배치를 사용합니다. 인터럽트·뱅크 DMA와의 조정은 호출자 책임이며 이 API는 임계 구역을 만들지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

CGB 전용 전체 프로그램은 WRAM 뱅크 2의 $D100에 1, 뱅크 3의 같은 주소에 2를 저장한 뒤 둘을 읽고 뱅크 1로 돌아옵니다. 이 값으로 (16,24)의 빨간 8 × 8 정사각형과 (16,48)의 파란 1픽셀 두께 8 × 8 사각 테두리를 고릅니다. FAILED CHECKS 000은 원시 선택값 정규화, 뱅크 독립성, 상수·변수·식·함수 반환값으로 지정했을 때 이전 뱅크 반환을 확인합니다. 별도 잘못된 빌드 테스트 여섯 개로 DMG·겸용의 KQ2103과 상수 뱅크 0·8의 KQ2102를 확인합니다. KOKURA 실행 검사이며 실기와 인터럽트에 의한 동시 뱅크 전환은 미검사입니다.

__svbk_get: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_wram_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_wram_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb_only --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:7387

csharp
{
                if (args.Length != 0) Error(expr, "__svbk_get() expects 0 arguments");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_get() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");
                EmitReadNormalizedSvbkIntoA();
                return;
            }

            // Select a WRAM bank and return the prior normalized bank; diagnose out-of-range constant requests.
            if (funcName == "__svbk_set")
            {
                if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");

                Expr bankExpr = FoldConstants(args[0]);
                if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
                    Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);

                EmitReadNormalizedSvbkIntoA();
                EmitAsm("LD_B_A");
                CompileIntoA(bankExpr);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
                EmitAsm("LD_A_B");
                return;
            }

            // __cgb_safe_set_* (safe CGB-only register write wrappers)
            if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
            {
__svbk_set

$D000–$DFFF의 WRAM 뱅크를 바꾸고 바꾸기 전의 실제 뱅크 번호를 반환합니다. 호출자 뱅크를 복원하려면 이 반환값을 저장하세요. 메모리 내용을 복사·초기화하지 않으며 뱅크를 자동 복원하지 않습니다.

사용 형식

c
u8 __svbk_set(u8 bank);

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

bank

요청할 뱅크는 u8의 1~7입니다. 범위 밖 상수는 KQ2102 오류입니다. 동적 값의 범위는 이 내장 함수가 검사하지 않으므로 레지스터 마스크에 의존하지 말고 호출 전에 확인하세요.

반환값

전환 전의 실제 뱅크 번호(u8, 1~7). 새로 선택한 뱅크가 아니라 이전 뱅크입니다.

사용 조건과 제한

__svbk_get과 __svbk_set에는 --cgb=cgb_only 또는 #pragma rom_cgb cgb_only가 필요합니다. DMG·겸용 빌드는 실행 중 조건으로 호출을 건너뛰더라도 KQ2103으로 거부합니다. 보호된 원시 설정 함수는 그 다른 동작 조건이 프로그램에 맞을 때 사용하세요.

뱅크 전환은 $D000–$DFFF의 모든 접근에 영향을 주며 고정 WRAM $C000–$CFFF는 그대로입니다. 전환 중 필요한 스택과 상태는 전환 창 밖에 두세요. 예제는 --stack-bank=fixed와 명시적 고정 WRAM 배치를 사용합니다. 인터럽트·뱅크 DMA와의 조정은 호출자 책임이며 이 API는 임계 구역을 만들지 않습니다.

사용 예

c
previous_bank=__svbk_set(requested_bank);
if(previous_bank!=1 || __svbk_get()!=2) failures++;
*((u8*)0xD100)=1; // This byte belongs to bank 2.
previous_bank=__svbk_set(next_bank()); // Dynamic argument evaluates to 3.
if(previous_bank!=2 || __svbk_get()!=3) failures++;
*((u8*)0xD100)=2; // Same CPU address, different physical RAM.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

CGB 전용 전체 프로그램은 WRAM 뱅크 2의 $D100에 1, 뱅크 3의 같은 주소에 2를 저장한 뒤 둘을 읽고 뱅크 1로 돌아옵니다. 이 값으로 (16,24)의 빨간 8 × 8 정사각형과 (16,48)의 파란 1픽셀 두께 8 × 8 사각 테두리를 고릅니다. FAILED CHECKS 000은 원시 선택값 정규화, 뱅크 독립성, 상수·변수·식·함수 반환값으로 지정했을 때 이전 뱅크 반환을 확인합니다. 별도 잘못된 빌드 테스트 여섯 개로 DMG·겸용의 KQ2103과 상수 뱅크 0·8의 KQ2102를 확인합니다. KOKURA 실행 검사이며 실기와 인터럽트에 의한 동시 뱅크 전환은 미검사입니다.

__svbk_set: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\cgb_wram_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\cgb_wram_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb_only --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:7397

csharp
{
                if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
                if (!Program.IsCgbOnlyTargetRequested())
                    Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");

                Expr bankExpr = FoldConstants(args[0]);
                if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
                    Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);

                EmitReadNormalizedSvbkIntoA();
                EmitAsm("LD_B_A");
                CompileIntoA(bankExpr);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
                EmitAsm("LD_A_B");
                return;
            }

            // __cgb_safe_set_* (safe CGB-only register write wrappers)
            if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
            {
                if (args.Length != 1) Program.Error(funcName + "(value_u8) expects 1 argument");

                if (IsKnownCgbRuntimeTrue())
                {
                    CompileIntoA(args[0]);
                    if (cgbOffset == 0x4F)
                        EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
                    EmitAsm("LDH_MEM_A", new AsmOperand(cgbOffset, AddressMode.HighMem));
                    CgbGuardedWriteCount++;
                    CgbGuardedRegisters.Add(cgbReg);
                    return;
__vram_memcpy_unsafe

src에서 dst로 len바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.

사용 형식

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

이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.

매개변수

dst

대상의 첫 바이트 주소이며 보통 VRAM 0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.

src

원본 바이트 포인터입니다. 호출이 끝날 때까지 메모리와 뱅크 매핑이 유효해야 합니다. 데이터는 즉시 읽으며 나중의 flush를 위해 보관하지 않습니다. 원본 뱅크를 자동으로 전환하지도 않습니다. 앞방향 복사이므로 memmove처럼 겹침을 처리하지 않습니다. 겹치지 않는 범위를 사용하세요.

len

연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD/STAT 대기를 모두 생략하고 CPU로 직접 씁니다. LCD를 끄거나 관리 가능한 쓰기 구간 안에 전체 전송이 끝나게 하세요. VBlank에 들어갔다고 해서 임의로 긴 전송이 안전해지는 것은 아닙니다. 이 내장 함수는 전송을 위한 DI나 EI를 생성하지 않으며 클리핑이나 범위 검사도 하지 않습니다.

사용 예

c
// The LCD is off throughout these unchecked writes.
__vram_memcpy_unsafe(0x98C2,strip,computed_length());

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_memcpy_unsafe으로 타일 (2,6), 픽셀 (16,48)에 맵 데이터 3바이트를 씁니다. 24 × 8 영역에 왼쪽부터 [1,2,3] 패턴이 나타나야 하고 이웃 칸은 비어 있어야 합니다. 완성 프로그램은 각 복사와 채우기를 한 행씩 배치합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_memcpy_unsafe: DMG
DMG
__vram_memcpy_unsafe: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:8104

csharp
{
                if (args.Length != 3) Error(expr, "__vram_memcpy_unsafe(dst, src, len) expects 3 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                // Use the same count specialization without emitting LCD-mode waits.
                Expr vramCopyUnsafeLen = FoldConstants(args[2]);
                EmitLengthExprIntoBC(vramCopyUnsafeLen);
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst

                if (vramCopyUnsafeLen.Match(Tag.Integer, out int vramCopyUnsafeLenConst) &&
                    TryEmitVramMemcpyConstCount(false, vramCopyUnsafeLenConst, "vramcpyu_small"))
                    return;

                AsmOperand vcuLoop = MakeUniqueLabel("vramcpyu_loop");
                AsmOperand vcuDone = MakeUniqueLabel("vramcpyu_done");
                EmitLabel(vcuLoop);
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_Z", vcuDone);
                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");
                EmitAsm("DEC_BC");
                EmitAsm("JR", vcuLoop);
                EmitLabel(vcuDone);
                return;
            }

            // __vram_memset(dst_ptr, value_u8, len_u16)
            // Safe VRAM fill: if LCD is on, each byte write is gated by STAT mode 0/1.
            if (funcName == "__vram_memset")
            {
                if (args.Length != 3) Error(expr, "__vram_memset(dst, value, l
__vram_memset_unsafe

dst부터 연속된 len바이트를 한 바이트 값 value로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.

사용 형식

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를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD/STAT 대기를 모두 생략하고 CPU로 직접 씁니다. LCD를 끄거나 관리 가능한 쓰기 구간 안에 전체 전송이 끝나게 하세요. VBlank에 들어갔다고 해서 임의로 긴 전송이 안전해지는 것은 아닙니다. 이 내장 함수는 전송을 위한 DI나 EI를 생성하지 않으며 클리핑이나 범위 검사도 하지 않습니다.

사용 예

c
__vram_memset_unsafe(0x9942,1,computed_length()); // Safe here because the LCD is off.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_memset_unsafe으로 타일 (2,10), 픽셀 (16,80)부터 맵 3바이트를 타일 번호 1로 채웁니다. 채워진 24 × 8 막대가 나타나고 이웃 칸은 비어 있어야 합니다. 개수는 변수나 함수 결과로 계산하며 이 경우에도 세 바이트 모두 값 1이어야 합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_memset_unsafe: DMG
DMG
__vram_memset_unsafe: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/kitaqgb/CodeGenerator.cs:8196

csharp
{
                if (args.Length != 3) Error(expr, "__vram_memset_unsafe(dst, value, len) expects 3 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoA(args[1]); EmitAsm("LD_D_A"); // value
                // Specialize a constant unsafe fill or fall back to its unchecked byte loop.
                Expr vramSetUnsafeLen = FoldConstants(args[2]);
                EmitLengthExprIntoBC(vramSetUnsafeLen);
                EmitAsm("POP_HL"); // dst

                if (vramSetUnsafeLen.Match(Tag.Integer, out int vramSetUnsafeLenConst) &&
                    TryEmitVramMemsetConstCount(false, vramSetUnsafeLenConst, "vramsetu_small"))
                    return;

                AsmOperand vsuLoop = MakeUniqueLabel("vramsetu_loop");
                AsmOperand vsuDone = MakeUniqueLabel("vramsetu_done");
                EmitLabel(vsuLoop);
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_Z", vsuDone);
                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");
                EmitAsm("DEC_BC");
                EmitAsm("JR", vsuLoop);
                EmitLabel(vsuDone);
                return;
            }

            // __far_memcpy(dst_ptr, bank_u8, src_ptr, len_u16)
            // Copies from switchable ROM bank via a bank0 helper and restores original bank.
            if (funcName == "__far_memcpy")
            {
                if (args.Length != 4) Error(expr, "__far_memcpy(dst, bank, src, len) expects 4 arguments");

                CompileIntoHL(
__wait_ly

LCD가 동작 중이면 LY가 목표 행과 같아질 때까지 기다립니다.

사용 형식

c
void __wait_ly(u8 ly);

매개변수

ly

주사선 0~153입니다. 153을 넘는 값은 LCD 동작 중에 대기가 끝나지 않습니다.

반환값

반환값은 없습니다.

사용 조건과 제한

이미 목표 행이면 즉시 반환합니다. 그 행이 지났으면 다음 프레임까지 기다립니다. 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의 계수를 사용합니다.

위치, 반지름, 반크기, 속도, 중력에는 일관된 정수 단위를 사용하세요. 한 단계는 시뮬레이션 1틱입니다. 샘플에서는 1단위가 1픽셀입니다. 이 함수들은 위치를 Q8.8로 자동 변환하거나 화면에 그리지 않습니다.

월드는 호출자가 소유한 배열을 빌려 사용합니다. 호출자가 물체 저장 공간을 할당하고 소유하며, 사용하는 동안 유효하게 유지해야 합니다. 월드를 초기화해도 배열 원소는 초기화되지 않습니다.

__smul16x8

부호 있는 16비트 값에 부호 있는 8비트 계수를 곱하고 256으로 나눈 뒤 0 방향으로 버림합니다.

사용 형식

c
s16 __smul16x8(s16 a, s8 b);

매개변수

a

배율을 적용할 부호 있는 16비트 값입니다.

b

-128..127 범위의 부호 있는 바이트 계수입니다.

반환값

배율 적용 결과를 부호 있는 16비트 값으로 반환합니다.

사용 조건과 제한

계수 범위는 -128..127이며, 256이 1.0인 배율을 사용합니다. 양수와 음수 모두 0 방향으로 버림합니다. -257*64/256은 -64를 반환합니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (8,48)과 (88,48)입니다. DMG에서는 모든 표시가 검은색입니다. RAM 검사로 초기화, 설정 함수, 적분, 사각형·점 질의, 부호 있는 배율 연산도 확인합니다.

__smul16x8: DMG — 왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (8,48)과 (88,48)입니다. DMG에서는 모든 표시가 검은색입니다. RAM 검사로 초기화, 설정 함수, 적분, 사각형·점 질의, 부호 있는 배율 연산도 확인합니다.
DMG — 왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (8,48)과 (88,48)입니다. DMG에서는 모든 표시가 검은색입니다. RAM 검사로 초기화, 설정 함수, 적분, 사각형·점 질의, 부호 있는 배율 연산도 확인합니다.
__smul16x8: CGB — 왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (8,48)과 (88,48)입니다. DMG에서는 모든 표시가 검은색입니다. RAM 검사로 초기화, 설정 함수, 적분, 사각형·점 질의, 부호 있는 배율 연산도 확인합니다.
CGB — 왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (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은 1바이트, rng16/rng_next16은 2바이트를 소비하고, rand_range/rng_range, rng_chance, weighted_choice는 게임 선택에 맞게 추출을 가공합니다. 이 라이브러리 함수를 호출하면 rng.c를 함께 컴파일하세요. 재현 가능한 테스트에는 시드와 호출 순서를 고정하세요. 나머지 연산 기반 선택은 정확한 확률을 보장하지 않습니다.

RLE는 count/value 바이트 쌍과 count=0 종료자를 사용합니다. rle_decode는 일반 포인터에서 RAM으로, rle_decode_far는 ROM 뱅크 지정 포인터에서 RAM으로 풉니다. __rle_decode_vram은 뱅크 입력을 LCD 접근 시점을 고려해 VRAM으로 직접 풉니다. RAM 함수는 lib/rle.c에 있고 직접 VRAM 연산은 컴파일러가 생성합니다. 세 가지 모두 출력 바이트 수를 반환하며, 유효한 스트림과 충분한 대상 공간이 필요합니다.

rpg.h에 선언된 게임 상태 저장 기능은 lib/flags.c가 구현합니다. 플래그 함수 3개는 서로 독립적인 불리언 비트 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입니다. 로드·그리기, 이동 마스크, 공격 마스크는 별도 작업이며, 그래픽 로드와 이벤트 실행 순서는 게임이 관리합니다.

탐색에는 약 4KiB의 공유 작업 공간을 사용하며 재진입할 수 없습니다. 맵은 1024셀, 탐색 거리는 254 이하로 제한하세요. 거리 0xFF는 미도달을 뜻하므로 255단계 경로는 표현할 수 없습니다. 비용·마스크 출력에는 width*height바이트의 쓰기 가능한 공간이 필요합니다.

기본 구성은 512바이트 슬롯 4개이며, 각 슬롯은 6바이트 헤더와 최대 506바이트 데이터를 담습니다. 샘플은 8KiB SRAM이 있는 MBC5를 사용합니다. 호출이 끝나면 RAM을 비활성화하며, 이전 뱅크나 활성 상태로 복원하지 않습니다.

메뉴는 선택이 끝날 때까지 기다리는 방식과 게임 루프에서 입력·그리기를 따로 호출하는 방식을 모두 지원합니다. 소지품 메뉴에는 항목 이름과 수량이 표시됩니다.

__bit_clear

같은 바이트의 나머지는 지우지 않고 선택한 플래그만 0으로 지웁니다. 일시적 상태 플래그처럼 저장된 조건 하나를 해제할 때 사용하세요. 같은 비트를 다시 지워도 0을 유지합니다.

사용 형식

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

매개변수

p

CPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.

bit_index

바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는 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(2진수 10100010, 10진수 162)가 되므로 CLEAR BIT2 행에는 162가 표시되어야 합니다. 비트 2를 다시 지워도 결과는 같으며, 다른 저장 플래그는 두 호출 모두에서 유지됩니다.

GB에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. DMG는 흰 바탕에 검은 글씨, CGB는 흰 바탕에 파란 글씨입니다.

프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

__bit_clear: DMG
DMG
__bit_clear: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:187

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_set

같은 바이트의 나머지 7비트를 보존하며 선택한 플래그를 1로 설정합니다. 예를 들어 퀘스트 완료나 아이템 획득을 기록할 수 있습니다. 이미 1인 비트를 다시 설정해도 그대로이며, 토글 동작이 아닙니다.

사용 형식

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

매개변수

p

CPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.

bit_index

바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는 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(10진수 166)이 됩니다. SET BIT1 행에는 166이 표시되어야 합니다. 샘플은 같은 비트를 다시 설정한 뒤에도 0xA6인지 확인하여 반복 설정이 플래그를 반전하지 않음을 보여 줍니다.

GB에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. DMG는 흰 바탕에 검은 글씨, CGB는 흰 바탕에 파란 글씨입니다.

프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

__bit_set: DMG
DMG
__bit_set: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:186

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_test

비트 배열을 변경하지 않고 플래그 하나를 읽습니다. RAM 1바이트에 독립된 켜짐·꺼짐 플래그 8개를 저장할 수 있습니다. 결과는 선택한 비트가 꺼져 있는지 켜져 있는지를 나타내며, 숫자 표현은 대상 기종에 따라 다릅니다.

사용 형식

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

매개변수

p

CPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.

bit_index

바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는 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(2진수 10100100)로 시작합니다. 비트 1 검사 결과는 0입니다. 비트 7은 GB에서 1, FC에서 128을 반환합니다. 샘플은 이 0이 아닌 결과를 공통 켜짐·꺼짐 플래그로 바꾸고 원래 바이트가 유지되는지도 확인합니다. 아래 화면의 TEST OFF와 TEST ON을 보세요.

GB에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. DMG는 흰 바탕에 검은 글씨, CGB는 흰 바탕에 파란 글씨입니다.

프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

__bit_test: DMG
DMG
__bit_test: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:185

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_toggle

선택한 플래그를 반전하여 0은 1, 1은 0으로 만듭니다. 나머지 비트는 유지합니다. 두 호출 사이에 다른 코드가 그 비트를 쓰지 않는다면, 같은 비트에 두 번 호출하여 원래 상태로 되돌릴 수 있습니다.

사용 형식

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

매개변수

p

CPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 RAM을 사용하세요. 검사는 읽기만 합니다.

bit_index

바이트 오프셋이나 비트 마스크가 아닌, 0부터 시작하는 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(10진수 34)가 되어 TOGGLE BIT7에 034가 표시됩니다. 다시 반전하면 0xA2로 돌아가며 RESTORE는 162를 표시합니다. 두 결과는 다른 비트를 유지하면서 양방향으로 반전되는 것을 보여 줍니다.

GB에서 기대하는 10진수 표시값은 TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000입니다. NEXT BYTE는 두 번째 바이트의 인덱스 9를 설정하여 0x80을 0x82로 바꾼 결과이며, 바이트 0은 변경하지 않습니다. DMG는 흰 바탕에 검은 글씨, CGB는 흰 바탕에 파란 글씨입니다.

프로그램은 화면에 표시하는 모든 상태 변화를 검사하고 실패 횟수를 보여 줍니다. 설정·해제·반전 자체는 반환값이 없으며, 해당 행은 변경 후 RAM 바이트를 표시합니다. 별도의 에뮬레이터 검사는 플랫폼마다 136가지 경우에서 보호 영역이 있는 버퍼 전체 520바이트를 비교합니다. 여기에는 바이트와 256바이트 경계를 넘는 상수·런타임 인덱스, 0이 아닌 비트 검사 마스크 8가지가 모두 포함됩니다. 이는 그리기 시연이 아니라 RAM 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.

__bit_toggle: DMG
DMG
__bit_toggle: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:188

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__copy16

길이 인수 없이 src에서 dst로 정확히 16바이트를 복사합니다. CPU가 접근할 수 있는 메모리에 있는 16바이트 레코드나 GB·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로 일정한 패턴의 16바이트를 오프셋 1부터 복사합니다. 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로 일정한 패턴의 32바이트를 오프셋 1부터 복사합니다. 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

쓰기 대상 메모리입니다. 실제 복사량을 모두 담을 공간을 확보하세요. 0바이트 전송을 제외하면 널 포인터나 잘못된 대상 주소를 검사하지 않습니다.

bank

원본 ROM 매핑을 선택하는 8비트 뱅크 값입니다. 카트리지와 컴파일러의 뱅크 배치에서 지원하는 번호를 쓰며, 원본 주소와 별도로 지정합니다.

src

선택한 ROM 뱅크에서 읽을 수 있는 바이트열을 가리키는 근거리 포인터입니다. 포인터 자체에는 뱅크 번호가 없습니다.

len

바이트 수이며 범위는 0~65535입니다. 0이면 메모리를 복사하지 않지만 뱅크 선택과 복구 과정은 실행합니다.

반환값

없음(void).

사용 조건과 제한

원본 전체가 의도한 ROM 매핑에서 읽히고 대상 전체가 쓰기 가능한 메모리 안에 있어야 합니다. 뱅크 창 경계에서 다음 뱅크로 넘어가거나, memmove처럼 겹치는 범위를 처리하거나, VRAM 쓰기 시점을 제어하지 않습니다. VRAM에는 그래픽 전송 API를 사용하세요.

예제의 호출 코드는 고정 뱅크 0에 두어 원본 뱅크를 바꿔도 실행 중인 코드가 사라지지 않도록 합니다. 전송은 동기식입니다. 전송 도중 인터럽트에서 다른 뱅크 전환 복사를 실행하지 마세요. 이 함수만으로 프로그램 전체의 인터럽트 안전성이 확보되지는 않습니다.

GB 보조 루틴은 컴파일러가 보관하는 ROM 뱅크 상태로 복귀 뱅크를 정합니다. 이를 갱신하는 __bankswitch를 사용하세요. 매퍼에 직접 쓰면 상태가 갱신되지 않습니다. HRAM의 복귀 뱅크 저장 공간은 공유되므로 중첩 전송으로 덮어쓰면 안전하지 않습니다.

사용 예

c
__far_memcpy(buffer,2,asset_shapes,32); // Same backend as __farmemcpy.
expect(*asset_bank_marker==0xA1,29);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),32);
buffer[32]=0x55;
__far_memcpy(buffer+32,2,asset_shapes,0); // Zero count writes no bytes and restores the bank.
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

뱅크 1이 선택된 상태에서 소재 뱅크 2의 패턴 두 개, 32바이트를 복사합니다. 모든 복사 바이트와 뱅크 1 복구를 검사하고, 길이 0 복사에서 확인값 0x55가 그대로인지 검증합니다.

예상 화면의 빨간 줄은 픽셀 좌표 (16,32)에서 시작하며 72×8픽셀입니다. 8×8 삼각형, 테두리 두께 1픽셀인 8×8 사각형, 왼쪽 네 열을 채운 타일을 세 번 반복합니다. 삼각형은 왼쪽 위의 1픽셀에서 맨 아래 줄의 8픽셀까지 넓어집니다.

GB에서 (16,64)의 파란 삼각형과 테두리는 __farmemcpy 결과이며, (16,96)의 초록색 쌍은 __far_memcpy 결과입니다. 각 쌍은 16×8픽셀입니다. CGB는 이 색으로, DMG는 흰 바탕에 검은색으로 같은 모양을 표시합니다.

예상 상태 표시는 FAILED CHECKS 000과 FIRST FAILURE 00입니다. 검증은 300프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.

__far_memcpy: DMG
DMG
__far_memcpy: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\asset_banks.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\asset_banks.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:148

__fill_tilemap

__fill_tilemap은 컴파일러가 __vram_memset과 같은 처리로 바꾸는 별칭입니다. 길이 단위는 동일하게 바이트이며 즉시 CPU 전송과 LCD 접근 조건도 같습니다.

dst부터 연속된 len바이트를 한 바이트 값 value로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.

사용 형식

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를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

시작 주소와 바이트 수를 명시하세요. 이 별칭은 배경 맵을 선택하거나 전체 맵을 자동으로 채우지 않습니다. 32 × 32 타일 번호 맵 전체를 채우려면 VRAM 뱅크 0을 선택하고 맵 시작 주소와 1024바이트를 지정하세요.

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__fill_tilemap(0x9922,1,length_base+length_extra); // Explicit address and byte count, not a whole-map clear.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __fill_tilemap으로 타일 (2,9), 픽셀 (16,72)부터 맵 3바이트를 타일 번호 1로 채웁니다. 채워진 24 × 8 막대가 나타나고 이웃 칸은 비어 있어야 합니다. 개수는 변수나 함수 결과로 계산하며 이 경우에도 세 바이트 모두 값 1이어야 합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__fill_tilemap: DMG
DMG
__fill_tilemap: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:157

__getbgmapbase

LCDC 비트 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);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

__manhattan: DMG
DMG
__manhattan: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/lib/rpg.h:183

kitaqgb/kitaqgb/CodeGenerator.cs:8516

csharp
if (funcName == "__manhattan")
            {
                if (args.Length != 4) Error(expr, "__manhattan(x1, y1, x2, y2) expects 4 arguments");

                Expr[] foldedManhattanArgs = args.Select(FoldConstants).ToArray();
                if (foldedManhattanArgs[0].Match(Tag.Integer, out int constX1) &&
                    foldedManhattanArgs[1].Match(Tag.Integer, out int constY1) &&
                    foldedManhattanArgs[2].Match(Tag.Integer, out int constX2) &&
                    foldedManhattanArgs[3].Match(Tag.Integer, out int constY2))
                {
                    int dx = Math.Abs((constX1 & 0xFF) - (constX2 & 0xFF));
                    int dy = Math.Abs((constY1 & 0xFF) - (constY2 & 0xFF));
                    EmitAsm("LD_A_IMM", new AsmOperand((dx + dy) & 0xFF, AddressMode.Immediate));
                    return;
                }

                EmitPushExprAsWord(args[0]); // x1
                EmitPushExprAsWord(args[1]); // y1
                EmitPushExprAsWord(args[2]); // x2
                EmitPushExprAsWord(args[3]); // y2

                AsmOperand dxGe = MakeUniqueLabel("manhattan_dx_ge");
                AsmOperand dxDone = MakeUniqueLabel("manhattan_dx_done");
                AsmOperand dyGe = MakeUniqueLabel("manhattan_dy_ge");
                AsmOperand dyDone = MakeUniqueLabel("manhattan_dy_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // x2
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", dxGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_B");
                EmitAsm("JR", dxDone);
                EmitLabel(dxGe);
                EmitAsm("LD_A_B");
                EmitAsm("SUB_C");
                EmitLabel(dxDone);
                EmitAsm("LD_B_A"); // dx

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // y1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // y2
                EmitAsm("LD_A_C");
                EmitAsm("CP_D");
                EmitAsm("JR_NC", dyGe);
                EmitAsm("LD_A_D");
                EmitAsm("SUB_C");
                EmitAsm("JR", dyDone);
                EmitLabel(dyGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_D");
                EmitLabel(dyDone);
                EmitAsm("ADD_B");
                EmitAsm("ADD_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                return;
            }
__map_index

셀 좌표를 행 우선 배열 인덱스로 변환합니다.

사용 형식

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

매개변수

x

열 0~255이며, 보통 width보다 작아야 합니다.

y

행 0~255입니다.

width

한 행의 셀 수 0~255입니다.

반환값

y*width+x를 u16으로 반환합니다. 좌표는 검사하지 않습니다.

사용 예

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

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

__map_index: DMG
DMG
__map_index: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/lib/rpg.h:181

kitaqgb/kitaqgb/CodeGenerator.cs:8365

csharp
if (funcName == "__map_index")
            {
                if (args.Length != 3) Error(expr, "__map_index(x, y, width) expects 3 arguments");

                Expr xExpr = FoldConstants(args[0]);
                Expr yExpr = FoldConstants(args[1]);
                Expr widthExpr = FoldConstants(args[2]);
                // Apply the width parameter's byte conversion before choosing a shift specialization.
                if (widthExpr.Match(Tag.Integer, out int byteWidth))
                    widthExpr = Expr.Make(Tag.Integer, byteWidth & 0xFF).WithSource(widthExpr.Source);
                if (xExpr.Match(Tag.Integer, out int fullXConst) &&
                    yExpr.Match(Tag.Integer, out int fullYConst) &&
                    widthExpr.Match(Tag.Integer, out int fullWidthConst))
                {
                    // Apply the byte argument widths before calculating the word-sized constant index.
                    int fullIndexConst = (((fullYConst & 0xFF) * (fullWidthConst & 0xFF)) + (fullXConst & 0xFF)) & 0xFFFF;
                    EmitAsm("LD_HL_IMM", new AsmOperand(fullIndexConst, AddressMode.Immediate16));
                    LastCallReturnsHL = true;
                    return;
                }

                if (widthExpr.Match(Tag.Integer, out int widthConst) && widthConst > 0 && IsPow2(widthConst))
                {
                    CompileIntoA(yExpr);
                    EmitAsm("LD_L_A");
                    EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                    int sh = Log2Pow2(widthConst);
                    for (int i = 0; i < sh; i++) EmitAsm("ADD_HL_HL");

                    if (xExpr.Match(Tag.Integer, out int xConst) && xConst == 0)
                    {
                        LastCallReturnsHL = true;
                        return;
                    }

                    CompileIntoA(xExpr);
                    EmitAsm("LD_E_A");
                    EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                    EmitAsm("ADD_HL_DE");
                    LastCallReturnsHL = true;
                    return;
                }

                CompileIntoA(args[0]); EmitPushAAsWord(); // x
                CompileIntoA(args[1]); EmitPushAAsWord(); // y
                CompileIntoA(args[2]); EmitPushAAsWord(); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A"); // y

                EmitMul8x8ToHL_EC("__map_index");

                // Loading the saved X argument uses HL, so preserve the multiplication result first.
                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE");
                EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                LastCallReturnsHL = true;
                return;
            }
__memcpy

src에서 dst로 지정한 바이트 수만큼 복사합니다. 길이가 16비트이므로 RAM의 테이블 전체처럼 255바이트를 넘는 데이터도 복사할 수 있습니다. 낮은 주소부터 차례로 읽고 씁니다.

사용 형식

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

매개변수

dst

쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.

src

읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.

count

u16으로 지정하는 바이트 수(0~65535)이며 원소 개수가 아닙니다. 자료형의 최댓값만큼 큰 버퍼를 실제 기기에서 확보할 수 있다는 뜻은 아닙니다. 전체 범위가 현재 메모리 매핑 안에 들어가야 합니다.

반환값

없음(void).

사용 조건과 제한

길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.

원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.

작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.

GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.

사용 예

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __memcpy로 일정한 패턴의 257바이트를 오프셋 1부터 복사합니다. memory_check가 원본과 대상 배열 전체를 비교하며, COPY WORD 행에는 257가 표시되어야 합니다. 버퍼 첫 바이트는 보호 영역으로 남기고, 복사 범위 뒤쪽 바이트도 바뀌지 않았는지 확인합니다.

전체 프로그램은 작업마다 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로 일정한 패턴의 255바이트를 오프셋 1부터 복사합니다. 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

대상 영역을 같은 1바이트 값으로 채웁니다. 길이는 16비트이며 큰 버퍼를 지우거나 테이블을 초기화할 때 유용합니다. 예를 들어 0x5A를 지정하면 5A 5A 5A…를 씁니다. 여러 바이트로 된 정수를 반복해서 쓰는 기능은 아닙니다.

사용 형식

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

매개변수

dst

쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.

value

각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다. u16 배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.

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바이트를 같은 1바이트 값으로 채웁니다. 작은 작업 버퍼를 초기화할 때 적합합니다. 필요한 길이가 255바이트를 넘으면 __memset을 사용하세요.

사용 형식

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

매개변수

dst

쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.

value

각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다. u16 배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.

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

ROM 뱅크를 지정한 count/value 스트림을 RAM 출력 버퍼 없이 VRAM에 직접 풉니다. 컴파일러는 쌍을 읽고 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 포인터입니다. 주소는 증가하지만 뱅크 번호는 고정됩니다. 종료용 0까지 전체 스트림을 읽기 가능한 매핑 안에 두세요. 0x7FFF를 넘는다고 다음 ROM 뱅크로 자동 이어지지 않습니다.

반환값

쓴 바이트 수를 u16으로 반환합니다. 처음부터 count가 0이면 아무것도 쓰지 않고 0을 반환합니다. 종료자는 복사하지 않습니다. 해제 후 길이가 u16 범위에 들어가야 합니다. 별도 오류 코드는 없으며 반환값은 타일 수가 아닙니다.

사용 조건과 제한

이 인코딩은 텍스트가 아니라 바이트 단위입니다. 예를 들어 {3, 0x12, 2, 0xA5, 0}은 12 12 12 A5 A5의 5바이트로 풀립니다. 이 출력 표기의 값은 16진수입니다. count가 0이 아닌 쌍의 value=0은 종료 표식이 아니라 일반 데이터입니다.

압축 크기, 출력 용량, 잘못된 데이터 처리를 위한 인수는 없습니다. 에셋을 만들거나 로드할 때 검증하고, 종료용 0을 넣고, 디코딩 전에 해제 후 크기를 계산하세요. 잘못되거나 끝나지 않는 스트림은 소스 밖을 읽고 대상 밖에 쓸 수 있습니다.

LCD가 꺼져 있으면 반복 구간을 직접 씁니다. 켜져 있으면 출력 바이트마다 쓰기 가능한 LCD 모드를 기다리며 대기와 쓰기를 DI/EI로 감쌉니다. 긴 스트림은 여러 프레임에 걸칠 수 있으며, 큐 전송이 아닌 동기 호출입니다. LCD가 켜진 경로는 처음에 인터럽트가 꺼져 있었는지를 보존하지 않고 매 바이트 후 인터럽트를 활성화합니다.

CGB에서는 현재 선택된 VBK 뱅크에 쓰며 선택 상태는 바꾸지 않습니다. 샘플의 타일 데이터에는 VBK 0을 선택하세요. 출력에 VBK 1을 선택해도 bank 인수는 여전히 ROM 입력을 지정합니다. 디코더는 CGB 팔레트나 맵 속성을 관리하지 않습니다.

뱅크 지정 읽기가 진행되는 동안 인터럽트 루틴이 ROM 매핑을 바꾸거나 far-copy 보조 함수를 재사용하지 않게 하세요. 보조 함수들은 뱅크 관리 상태를 공유합니다. 일반 포인터 디코더와 소스는 서로 호환되는 매핑에 배치하세요. 함수의 뱅크로 전환했을 때 일반 소스 포인터가 가려져서는 안 됩니다.

rle_decode와 rle_decode_far는 rpg.h에 선언되어 있고 lib/rle.c에 구현되어 있습니다. 게임과 함께 이 구현을 한 번만 컴파일하세요. 전체 예제는 호출자와 디코더를 공통 뱅크에 두기 위해 #pragma bank 0 뒤에 rle.c를 포함합니다. 이 예제에서는 rle.c를 별도 입력으로도 전달하지 마세요. __rle_decode_vram 내장 함수 자체에는 라이브러리 구현 파일이 필요 없습니다.

사용 예

c
vram_count=__rle_decode_vram(0x8860,2,banked_rle); // Expand directly to 48 bytes of tile data.
if(*rle_bank_marker!=0xA1)failures++;

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 뱅크 2의 스트림을 RAM 출력 버퍼 없이 VRAM 0x8860에 직접 풉니다. DIRECT VRAM은 048로 표시됩니다. 아래쪽 줄은 CGB에서 파란색이며 RAM 경로의 줄들과 같은 세 타일 무늬가 나타나야 합니다. 이 학습용 프로그램은 LCD가 꺼진 상태에서 업로드합니다.

각 줄은 왼쪽부터 채워진 8×8 정사각형, 테두리 두께 1픽셀인 8×8 사각형, 8×8 타일의 왼쪽 절반을 채운 4×8 직사각형입니다. 타일 열은 2, 5, 8(픽셀 x = 16, 40, 64)이고 세 줄의 시작 픽셀 y는 32, 72, 112입니다. DMG는 검은색, CGB는 줄별 빨강·초록·파랑입니다. FAILED는 000이어야 합니다. 숫자는 바이트 수로, 16바이트 GB 타일 세 개에 해제된 48바이트가 필요합니다.

검증은 빈 스트림, 1바이트 반복, 값 0인 데이터, 255바이트 반복, 대상 페이지를 넘는 510바이트 출력, 소스 페이지를 넘는 압축 데이터 등 42가지 경우를 포함합니다. 관찰한 대상·보호 영역 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의 라이브러리 래퍼를 링크하지 않고 1바이트가 필요할 때 사용합니다.

사용 형식

c
u8 __rng8();

반환값

공유 생성기를 한 번 진행시킨 뒤 얻는 u8 결과(0–255)입니다. 값의 자료형과 범위를 설명하는 것이며 모든 바이트의 확률이 같다는 뜻은 아닙니다.

사용 조건과 제한

GB 단계는 16비트 상태에 0x5D17을 순환 덧셈하고, 갱신된 하위 바이트와 상위 바이트의 XOR을 하위 바이트로 만든 뒤, 상위 바이트를 왼쪽으로 1비트 회전한 값에 그 하위 바이트를 더합니다. 두 갱신 바이트를 저장하고 상위 바이트를 반환합니다.

상태 0도 허용됩니다. 다음 바이트 추출에서 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).

사용 조건과 제한

상태 0도 허용됩니다. 다음 바이트 추출에서 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로 선택한 BG 맵의 (x,y)부터 아래로 복사합니다. 원본은 1바이트, 대상은 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

len

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

가로 복사는 x+len <= 32, 세로 복사는 y+len <= 32를 지키세요. 맵 가장자리에서 좌표가 자동으로 순환하지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settile_col
// Copy `len` successive tile numbers from `src` vertically, starting at `(x,y)` in the
// LCDC-selected BG map. The source advances one byte and the destination 32 bytes per
// tile. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_col(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9CA2, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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로 선택한 BG 맵에서 너비 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settile_rect
// Fill a `w` by `h` rectangle in the LCDC-selected BG map with one tile number. Rows are
// 32 bytes apart; this repeats a tile, rather than copying an image array. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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로 선택한 BG 맵의 (x,y)부터 오른쪽으로 복사합니다. 원본과 대상 주소가 타일마다 1바이트씩 증가합니다.

필요하면 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

len

바이트 수이며 0~255입니다. 0이면 복사하지 않습니다. 이 수만큼 읽을 수 있는 원본 데이터를 준비하세요.

반환값

없음(void).

사용 조건과 제한

타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

가로 복사는 x+len <= 32, 세로 복사는 y+len <= 32를 지키세요. 맵 가장자리에서 좌표가 자동으로 순환하지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settile_row
// Copy `len` successive tile numbers from `src` horizontally, starting at `(x,y)` in the
// LCDC-selected BG map. Source and destination advance by one byte per tile. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_row(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C64, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C82, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

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

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

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.

반환값

없음(void).

사용 조건과 제한

타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.

예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.

사용 예

c
// __settilemap_rect
// Copy a tightly packed `w*h` array of tile numbers to a rectangle at `(x,y)` in the map
// selected by `base`. Source rows have `w` bytes; destination rows have a 32-byte
// stride. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C','D','E','F'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilemap_rect(0x9C00, 2, 3, 3, 2, example_tiles);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C84, 0) != 'F') { failures++; }
    if (tile_example_read(0x9C65, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

예상 결과

선택한 맵에 쓴 문자나 채운 영역이 표시됩니다. 프로그램은 대상 타일 번호와 CGB 속성을 다시 읽어 비교하며, 모두 일치하면 FAILED CHECKS 000을 표시합니다. 두 맵을 먼저 0으로 채우고 해당 호출 전에는 BG·윈도 맵을 0x9C00으로 설정하므로 고정 맵과 선택된 맵의 차이도 확인할 수 있습니다. LCD를 끈 상태에서 쓰며, LCD 동작 중의 타이밍을 측정하는 예제는 아닙니다.

CGB에서는 예제 문자와 결과 문구가 흰 바탕에 파란색으로 표시됩니다. 초기 설정에서 팔레트 0의 글자색을 파란색으로 지정하며, 타일 맵 속성은 변경하지 않습니다. DMG에서는 같은 문자가 흰 바탕에 검은색으로 표시됩니다.

예상 이미지: 다음 타일 패턴이 타일 좌표 (x=2, y=3), 픽셀 좌표 (16,24)에서 시작합니다. 문자 하나는 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에서 dst로 len바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.

사용 형식

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

매개변수

dst

대상의 첫 바이트 주소이며 보통 VRAM 0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.

src

원본 바이트 포인터입니다. 호출이 끝날 때까지 메모리와 뱅크 매핑이 유효해야 합니다. 데이터는 즉시 읽으며 나중의 flush를 위해 보관하지 않습니다. 원본 뱅크를 자동으로 전환하지도 않습니다. 앞방향 복사이므로 memmove처럼 겹침을 처리하지 않습니다. 겹치지 않는 범위를 사용하세요.

len

연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

이 이름으로 DMA 엔진을 선택하거나 나중의 HBlank에 작업을 예약하지 않습니다. 호출한 쪽은 완료까지 기다립니다. LCD가 켜져 있으면 같은 CPU 폴링으로 모드 0(HBlank) 또는 1(VBlank)을 기다리며 CGB HDMA는 설정하지 않습니다.

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__vram_copy_dma(0x98A2,strip,3); // This alias also uses CPU writes, not a DMA engine.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_copy_dma으로 타일 (2,5), 픽셀 (16,40)에 맵 데이터 3바이트를 씁니다. 24 × 8 영역에 왼쪽부터 [1,2,3] 패턴이 나타나야 하고 이웃 칸은 비어 있어야 합니다. 완성 프로그램은 각 복사와 채우기를 한 행씩 배치합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_copy_dma: DMG
DMG
__vram_copy_dma: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:163

__vram_copy_hblank

__vram_copy_hblank은 컴파일러가 __vram_memcpy과 같은 처리로 바꾸는 별칭입니다. 길이 단위는 동일하게 바이트이며 즉시 CPU 전송과 LCD 접근 조건도 같습니다.

src에서 dst로 len바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.

사용 형식

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

매개변수

dst

대상의 첫 바이트 주소이며 보통 VRAM 0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.

src

원본 바이트 포인터입니다. 호출이 끝날 때까지 메모리와 뱅크 매핑이 유효해야 합니다. 데이터는 즉시 읽으며 나중의 flush를 위해 보관하지 않습니다. 원본 뱅크를 자동으로 전환하지도 않습니다. 앞방향 복사이므로 memmove처럼 겹침을 처리하지 않습니다. 겹치지 않는 범위를 사용하세요.

len

연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

이 이름으로 DMA 엔진을 선택하거나 나중의 HBlank에 작업을 예약하지 않습니다. 호출한 쪽은 완료까지 기다립니다. LCD가 켜져 있으면 같은 CPU 폴링으로 모드 0(HBlank) 또는 1(VBlank)을 기다리며 CGB HDMA는 설정하지 않습니다.

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__vram_copy_hblank(0x9882,strip,3); // Immediate safe copy; no scheduled HBlank DMA.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_copy_hblank으로 타일 (2,4), 픽셀 (16,32)에 맵 데이터 3바이트를 씁니다. 24 × 8 영역에 왼쪽부터 [1,2,3] 패턴이 나타나야 하고 이웃 칸은 비어 있어야 합니다. 완성 프로그램은 각 복사와 채우기를 한 행씩 배치합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_copy_hblank: DMG
DMG
__vram_copy_hblank: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:162

__vram_fill

__vram_fill은 컴파일러가 __vram_memset과 같은 처리로 바꾸는 별칭입니다. 길이 단위는 동일하게 바이트이며 즉시 CPU 전송과 LCD 접근 조건도 같습니다.

dst부터 연속된 len바이트를 한 바이트 값 value로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.

사용 형식

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를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__vram_fill(0x9902,1,computed_length());

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_fill으로 타일 (2,8), 픽셀 (16,64)부터 맵 3바이트를 타일 번호 1로 채웁니다. 채워진 24 × 8 막대가 나타나고 이웃 칸은 비어 있어야 합니다. 개수는 변수나 함수 결과로 계산하며 이 경우에도 세 바이트 모두 값 1이어야 합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_fill: DMG
DMG
__vram_fill: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:161

__vram_memcpy

src에서 dst로 len바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.

사용 형식

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

매개변수

dst

대상의 첫 바이트 주소이며 보통 VRAM 0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.

src

원본 바이트 포인터입니다. 호출이 끝날 때까지 메모리와 뱅크 매핑이 유효해야 합니다. 데이터는 즉시 읽으며 나중의 flush를 위해 보관하지 않습니다. 원본 뱅크를 자동으로 전환하지도 않습니다. 앞방향 복사이므로 memmove처럼 겹침을 처리하지 않습니다. 겹치지 않는 범위를 사용하세요.

len

연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__vram_memcpy(0x9842,strip,length_base+length_extra); // Three map bytes at tile (2,2).

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_memcpy으로 타일 (2,2), 픽셀 (16,16)에 맵 데이터 3바이트를 씁니다. 24 × 8 영역에 왼쪽부터 [1,2,3] 패턴이 나타나야 하고 이웃 칸은 비어 있어야 합니다. 완성 프로그램은 각 복사와 채우기를 한 행씩 배치합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_memcpy: DMG
DMG
__vram_memcpy: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:146

__vram_memset

dst부터 연속된 len바이트를 한 바이트 값 value로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.

사용 형식

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를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.

반환값

없음(void).

사용 조건과 제한

LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.

사용 예

c
__vram_memset(0x98E2,1,length_base+length_extra); // Three solid tiles, not three tile patterns.

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 __vram_memset으로 타일 (2,7), 픽셀 (16,56)부터 맵 3바이트를 타일 번호 1로 채웁니다. 채워진 24 × 8 막대가 나타나고 이웃 칸은 비어 있어야 합니다. 개수는 변수나 함수 결과로 계산하며 이 경우에도 세 바이트 모두 값 1이어야 합니다.

제공하는 자체 제작 그래픽에서 타일 1은 8 × 8 전체 채움, 2는 왼쪽 네 열 채움, 3은 행마다 한 픽셀씩 넓어져 폭 1에서 8이 되는 왼쪽 정렬 삼각형입니다. DMG는 흰 바탕에 검은 도형을 표시하고 CGB는 처리별로 색을 구분하되 글자는 검은색으로 유지합니다.

픽셀 (16,88)의 정사각형은 안전 버전과 대기 없는 버전을 길이 0으로 호출한 뒤에도 남아야 합니다. LCD가 켜진 상태의 안전 복사와 채우기는 (16,96)과 (32,96)에 정사각형을 추가합니다. FAILED CHECKS는 000이어야 합니다. 에뮬레이터 검증은 숫자뿐 아니라 그려진 픽셀도 비교합니다. 이 예제의 CGB는 뱅크 0을 사용하며 다른 뱅크와 실제 기기는 여기서 검증하지 않습니다.

CGB에서는 MEMCPY부터 FILL UNSAFE까지 각 행을 빨강·파랑·초록 순서로 반복합니다. ZERO COUNT 정사각형은 빨간색입니다. LCD ON은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.

__vram_memset: DMG
DMG
__vram_memset: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\vram_memory_shapes.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\vram_memory_shapes.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
구현과 소스

kitaqgb/lib/rpg.h:147

__wait_vblank

LCD가 동작 중이면 현재 VBlank가 끝나기를 기다린 다음, 다음 LY≥144를 기다립니다.

사용 형식

c
void __wait_vblank();

반환값

반환값은 없습니다.

사용 조건과 제한

LCD가 꺼져 있으면 즉시 반환합니다. 인터럽트를 활성화하거나 라이브러리 프레임 카운터를 증가시키지 않고 LY를 폴링합니다.

사용 예

c
__wait_vblank();result[10]=*((u8*)0xFF44);M_LCDC=0;__wait_vblank();__wait_ly(200);result[11]=1;

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.

__wait_vblank: DMG
DMG
__wait_vblank: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_display.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_display.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/lib/rpg.h:138

kitaqgb/kitaqgb/CodeGenerator.cs:7451

csharp
if (funcName == "__wait_vblank")
            {
                if (args.Length != 0) Error(expr, "__wait_vblank() expects 0 arguments");

                AsmOperand wbRet = MakeUniqueLabel("waitvb_ret");
                AsmOperand wbExitVblank = MakeUniqueLabel("waitvb_exit");
                AsmOperand wbEnterVblank = MakeUniqueLabel("waitvb_enter");

                // If LCD is off, return immediately.
                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
                EmitAsm("JR_Z", wbRet);

                // while (LY >= 144) {}
                EmitLabel(wbExitVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);
                EmitAsm("JR", wbExitVblank);

                // while (LY < 144) {}
                EmitLabel(wbEnterVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);

                EmitLabel(wbRet);
                return;
            }
__xy_in_rect

왼쪽·위쪽을 포함하고 오른쪽·아래쪽을 제외하는 사각형 안에 있는지 검사합니다.

사용 형식

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

매개변수

x

점의 X 좌표 0~255입니다.

y

점의 Y 좌표 0~255입니다.

rx

사각형의 왼쪽 좌표 0~255입니다.

ry

사각형의 위쪽 좌표 0~255입니다.

rw

너비 0~255입니다.

rh

높이 0~255입니다.

반환값

안쪽이면 1, 아니면 0을 반환합니다. 너비나 높이가 0이면 비어 있습니다.

사용 조건과 제한

좌표 차이를 사용하므로 오른쪽 경계가 255를 넘어도 왼쪽으로 래핑되지 않습니다.

사용 예

c
result[0]=__xy_in_rect(10,20,10,20,3,2);result[1]=__xy_in_rect(13,20,10,20,3,2);result[2]=__xy_in_rect(9,20,10,20,3,2);result[3]=__xy_in_rect(255,1,250,0,10,2);result[4]=__xy_in_rect(10,20,10,20,0,2);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

내부, 경계, 외부 점, 너비 0을 검사합니다. INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1입니다. 거리 15, 래핑된 거리 254, 배열 인덱스 64/65280도 확인합니다.

__xy_in_rect: DMG
DMG
__xy_in_rect: CGB
CGB

사용 예의 전체 프로그램

빌드 명령

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\batch3_coordinates.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.gb --no-cache --no-disasm --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=64k --ramsize=none
구현과 소스

kitaqgb/lib/rpg.h:182

kitaqgb/kitaqgb/CodeGenerator.cs:8436

csharp
if (funcName == "__xy_in_rect")
            {
                if (args.Length != 6) Error(expr, "__xy_in_rect(x, y, rx, ry, rw, rh) expects 6 arguments");

                Expr[] foldedRectArgs = args.Select(FoldConstants).ToArray();
                if (foldedRectArgs[0].Match(Tag.Integer, out int constX) &&
                    foldedRectArgs[1].Match(Tag.Integer, out int constY) &&
                    foldedRectArgs[2].Match(Tag.Integer, out int constRx) &&
                    foldedRectArgs[3].Match(Tag.Integer, out int constRy) &&
                    foldedRectArgs[4].Match(Tag.Integer, out int constRw) &&
                    foldedRectArgs[5].Match(Tag.Integer, out int constRh))
                {
                    int x = constX & 0xFF;
                    int y = constY & 0xFF;
                    int rx = constRx & 0xFF;
                    int ry = constRy & 0xFF;
                    int rw = constRw & 0xFF;
                    int rh = constRh & 0xFF;
                    bool inside = x >= rx && y >= ry && (x - rx) < rw && (y - ry) < rh;
                    if (inside) EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    else EmitAsm("XOR_A");
                    return;
                }

                EmitPushExprAsWord(args[0]); // x
                EmitPushExprAsWord(args[1]); // y
                EmitPushExprAsWord(args[2]); // rx
                EmitPushExprAsWord(args[3]); // ry
                EmitPushExprAsWord(args[4]); // rw
                EmitPushExprAsWord(args[5]); // rh

                AsmOperand ret0 = MakeUniqueLabel("xyrect_ret0");
                AsmOperand done = MakeUniqueLabel("xyrect_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rx
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rw
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // y
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // ry
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rh
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR", done);
                EmitLabel(ret0);
                EmitAsm("XOR_A");
                EmitLabel(done);
                EmitAsm("ADD_SP_IMM", new AsmOperand(12, AddressMode.Relative));
                return;
            }

scroll.h — 배경·윈도·분할 스크롤

배경·윈도우의 즉시 및 버퍼 스크롤과 최대 8개의 래스터 분할을 제어합니다. 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에는 하드웨어의 7픽셀 오프셋이 포함됩니다.

사용 조건과 제한

하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.

사용 예

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에는 하드웨어의 7픽셀 오프셋이 포함됩니다.

사용 조건과 제한

하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.

사용 예

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

반환값

반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.

사용 조건과 제한

공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 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을 사용하고 오름차순으로 추가하세요. 첫 항목이 0이면 VBlank 중에 적용됩니다.

scx

가로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 왼쪽으로 이동합니다.

scy

세로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 위쪽으로 이동합니다.

반환값

반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.

사용 조건과 제한

공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 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을 사용하고 오름차순으로 추가하세요. 첫 항목이 0이면 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의 하위·상위 바이트로 재사용합니다.

반환값

반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.

사용 조건과 제한

공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 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();

반환값

반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.

사용 조건과 제한

공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 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에는 하드웨어의 7픽셀 오프셋이 포함됩니다.

사용 조건과 제한

하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.

사용 예

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에는 하드웨어의 7픽셀 오프셋이 포함됩니다.

사용 조건과 제한

하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.

사용 예

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(Object Attribute Memory) 항목으로 지정합니다. 슬롯을 확보하고 섀도 OAM에 위치, 타일, 속성을 설정한 뒤 전송합니다. 여러 슬롯을 하나의 오브젝트로 묶은 것이 메타스프라이트입니다. GB는 40슬롯과 주사선당 10개, FC는 64슬롯과 주사선당 8개를 제공합니다.

GB 속성은 비트 4가 DMG OBP1 선택, 5/6이 X/Y 반전, 7이 색 번호 0이 아닌 배경 뒤로 배치하는 기능입니다. 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

메모리 페이지에서 하드웨어 OAM(0xFE00–0xFE9F)으로 160바이트를 전송하여 스프라이트 40개의 정보를 모두 갱신합니다. 전송이 끝난 뒤 호출한 코드로 돌아갑니다.

사용 형식

c
void __oam_dma(u16 src_ptr);

매개변수

src_ptr

전송 원본의 바이트 주소입니다. 상위 바이트만 사용하므로 0xC480을 전달하면 0xC400–0xC49F를 읽습니다. 256바이트 경계에서 시작하는 읽기 가능한 160바이트를 준비하세요. 하드웨어가 지원하는 페이지(0x00–0xDF)를 사용하고 필요한 ROM 또는 RAM 뱅크를 선택해 두어야 합니다. 컴파일러는 주소나 정렬 여부를 검사하지 않습니다.

반환값

없음(void).

사용 조건과 제한

각 항목은 Y, X, 타일 번호, 속성의 4바이트입니다. 화면 좌표는 (X−8, Y−16)입니다. 사용하지 않는 항목은 Y를 0으로 설정하면 숨길 수 있습니다. 이 내장 함수는 원시 데이터만 복사하며 스프라이트 할당이나 라이브러리의 카운터 및 섀도 버퍼 갱신은 하지 않습니다.

VBlank 중이거나 LCD가 꺼져 있을 때 호출하세요. 함수 자체는 VBlank를 기다리거나 LCD 설정을 변경하지 않습니다. 하드웨어 전송에는 CPU 머신 사이클 160회가 필요하며, 함수 준비와 복귀 시간은 별도로 추가됩니다.

이 호출을 포함하는 프로그램은 전송 루틴용 HRAM 8바이트를 확보합니다. 루틴은 호출할 때마다 초기화되며 DMA가 진행되는 동안 HRAM에서 대기합니다. 인터럽트 요청은 일시적으로 마스킹하지만 IE와 IME의 활성화 상태는 보존합니다. 인터럽트 처리는 전송이 끝날 때까지 지연됩니다.

사용 예

c
// raw_oam is a 160-byte array at address 0xC400.
m_wait();
__oam_dma((u16)raw_oam);

호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.

예상 결과

이 예제는 OAM 한 페이지를 직접 구성합니다. (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