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에 추가하거나 전체 경로로 실행하세요.
| OS | kitaqgb |
|---|---|
| Windows x64 | kitaqgb.exe |
| Linux x64 (musl) | bin/linux-x86_64/kitaqgb |
| Linux x64 (GNU) | bin/linux-gnu-x86_64/kitaqgb |
| macOS ARM64 | bin/macos-arm64/kitaqgb |
| macOS Intel | bin/macos-x86_64/kitaqgb |
| macOS Universal | bin/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은 원래 샘플 글꼴을 유지합니다.
kitaqgb game.c -I lib -o game.gb --cart=romonly --romsize=32k완성된 예제 프로그램
각 프로그램과 공통 헤더, 제공된 ascii.c, 변환 리소스를 함께 실었습니다. m_으로 시작하는 이름은 설명서용 도우미입니다. 빌드 명령은 기대 결과와 검증 기록을 함께 읽으세요. 명령은 저장소들이 나란히 놓인 상위 폴더에서 실행합니다.
제공된 문자 집합 표시 gb_font.c
목적: 제공된 독자 제작 글꼴에 어떤 문자·숫자·문장부호가 있는지, 그리고 어떻게 그리는지 알아봅니다.
동작 원리: m_init이 표시와 글꼴을 준비합니다. m_text가 각 행을 그리고, m_put은 문자 코드 34로 큰따옴표를 그립니다.
조작 방법과 기대 결과: 대문자 26개, 소문자 26개, 숫자 10개, 문장부호 30개가 있는지 확인하세요. 모든 글자가 읽기 쉬운지 살펴보세요. 입력 조작은 필요하지 않습니다.

.\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// 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은 십진수를 세 자리로 표시하기 위한 것입니다.

.\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// 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을 나타냅니다.

.\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// 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는 이 반복문과 분기의 최종 결과입니다.

.\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// 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]에 쓰는 과정과 함수 반환값을 따라가 보세요.](../verification/gb_aggregate.png)
.\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// 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를 더한 결과입니다.

.\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// 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으로 돌아갑니다.

.\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// 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배로 저장된 내부값이 아니라 변환한 정수입니다.

.\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// 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나 좌표가 아닙니다. 이 예제는 오브젝트를 갱신하거나 그리지 않으므로 움직이는 캐릭터는 나타나지 않습니다.

.\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// 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는 진단 버퍼에 저장되며, 화면만으로는 그 내용을 보거나 검증할 수 없습니다.

.\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// 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를 살펴보세요. 이 프로그램은 따라오는 몸체를 그리지 않습니다.

.\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// 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를 사용하므로 표시 값이 계속 증가하지 않습니다.

.\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// 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입니다. 이 예제에는 장면 콜백이나 전환 그리기가 등록되어 있지 않습니다.

.\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// 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가 됩니다. 좌표 계산이며 배경을 스크롤하지 않습니다.

.\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// 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여야 합니다.](../verification/gb_rle.png)
.\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// 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 보드 자체를 그리지 않습니다.

.\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// 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 좌표이며, 이 예제의 단위는 정수 픽셀입니다. 상자 모양이나 연속 애니메이션은 그리지 않습니다.

.\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// 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(); }
}직렬 전송 완료와 시간 초과 처리 gb_link.c
목적: 완료된 전송과 제한 시간 안에 끝나지 않은 전송을 구분합니다.
동작 원리: 마스터는 42를 보내고 Link_WaitByte로 기다립니다. 완료되면 수신 바이트를 표시하고, 시간 초과이면 전송을 취소한 뒤 0을 표시합니다.
조작 방법과 기대 결과: 연결되지 않은 상태에서도 전송이 끝나면 255가 반환될 수 있습니다. 시간 초과이면 000을 표시하며, 상대가 연결되어 있으면 받은 바이트를 표시합니다. 결과가 항상 042인 것은 아닙니다.

.\kitaqgb\kitaqgb.exe kitaqgb/lib/link_hwregs_gb.c kitaqgb/lib/link.c kitaq-docs/samples/gb_link.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_link.gb --profile=dev --rst-disable --stack-bank=fixed// Handle serial transfers that do not deliver a byte in time.
// Expected: 255 for disconnected link; 000 if timeout; peer value when connected
#include "gb_common.h"
#include "link.h"
// Start a master transfer of byte 42 and wait with a bounded timeout.
// Display the received byte on completion; cancel and display zero on timeout.
void main() {
m_init();
m_text(2,3,"LINK");
u8 value; Link_InitMaster(); Link_BeginTransfer(42);
if(Link_WaitByte(4,&value)) m_number(value);
else { Link_Cancel(); m_number(0); }
while (1) { m_wait(); }
}뱅크 인수로 ROM 데이터 읽기 gb_bank.c
목적: ROM 데이터를 RAM으로 복사할 때 필요한 인수를 알아봅니다.
동작 원리: far_data_read가 뱅크 인수 0과 길이 2를 사용해 바이트 7과 42를 copied로 복사합니다.
조작 방법과 기대 결과: 복사 대상의 두 번째 바이트가 042로 표시됩니다. 이 예제는 고정 ROM을 읽으며 서로 다른 전환 가능 뱅크 사이의 전송은 보여 주지 않습니다.

.\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// 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로 표시됩니다. 자산 검색과 복사를 보여 주는 예제이며 이미지 렌더링 예제는 아닙니다.

.\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// 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은 할당된 탄 수입니다. 탄 갱신 루프나 배경 합성기가 없으므로 움직이는 탄은 그리지 않습니다.

.\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// 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의 흑백 출력으로는 이 색상을 확인할 수 없습니다.

.\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// 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이라는 글자가 왼쪽으로 이동합니다. 시간에 따라 위치가 바뀌는지 확인하세요. 스크린샷 한 장으로는 움직임을 확인할 수 없습니다.

.\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// 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 하나가 표시되는지 확인하세요. 타일 갱신이며 숫자 카운터가 아닙니다.

.\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// 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를 쓰기 전에 할당 실패 여부를 확인하세요.

.\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// 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까지의 값 하나가 표시됩니다. 같은 프로그램을 같은 시드로 다시 시작하면 같은 값이 나오며, 매 프레임 바뀌지는 않습니다.

.\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// 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의 플래그를 조작하며 전원을 껐다 켠 뒤까지 저장하지는 않습니다.

.\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// 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 좌표입니다. 이 예제는 원 외곽선이나 충돌 애니메이션을 그리지 않습니다.

.\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// 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가 표시된 채로 짧은 펄스 효과음이 재생됩니다. 숫자는 고정 표시이며 음량이나 재생 위치가 아닙니다. 소리는 오디오 출력이나 녹음으로 확인하세요.

.\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// 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는 같은 구현을 사용합니다.
사용 형식
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의 복귀 뱅크 저장 공간은 공유되므로 중첩 전송으로 덮어쓰면 안전하지 않습니다.
사용 예
__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프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.


빌드 명령
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바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.
사용 형식
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은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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 스택에 저장하고 반환 시 복원합니다. 이 내장 함수는 값을 반환하지 않습니다.
사용 형식
void __farcall_ptr(u8 bank, const void* func);매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
func매개변수가 없는 콜백의 유효한 실행 주소입니다. 함수 이름이나 실행 시점 포인터로 지정할 수 있습니다. 널 포인터와 잘못된 뱅크·주소 조합은 실행 중 검사하지 않습니다.
반환값
없음(void).
사용 조건과 제한
이 호출은 사용자 인수를 전달하거나 콜백의 인터럽트 재진입성을 보장하지 않습니다. 콜백 자체의 데이터, 스택, 인터럽트 사용을 올바르게 관리하세요. 뱅크 복귀는 중첩 및 반복 호출로 검사했지만 실제 하드웨어와 임의의 인터럽트 타이밍은 이 예제의 검증 범위가 아닙니다.
이 GB 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.
사용 예
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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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이 사용하는 내장 함수입니다.
사용 형식
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을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.
사용 예
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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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이 사용하는 내장 함수입니다.
사용 형식
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을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.
사용 예
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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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는 실행 중에 판정합니다. 물리 기기의 모든 기능이 아니라 선택된 실행 모드를 나타냅니다.
사용 형식
u8 __cgb_is_cgb();반환값
u8: DMG 모드이면 0, CGB 모드이면 1.
사용 예
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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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 색 하나에는 하위·상위 두 바이트가 필요합니다.
사용 형식
void __cgb_safe_set_bcpd(u8 value);매개변수
value한 바이트(0~255). RGB15의 하위 바이트 다음에 상위 바이트를 씁니다. 먼저 인덱스 자동 증가를 켜거나 각 위치를 직접 선택하세요. 데이터 쓰기 자체가 자동 증가를 켜지는 않습니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.
사용 형식
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를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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 색 하나에는 하위·상위 두 바이트가 필요합니다.
사용 형식
void __cgb_safe_set_ocpd(u8 value);매개변수
value한 바이트(0~255). RGB15의 하위 바이트 다음에 상위 바이트를 씁니다. 먼저 인덱스 자동 증가를 켜거나 각 위치를 직접 선택하세요. 데이터 쓰기 자체가 자동 증가를 켜지는 않습니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.
사용 형식
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를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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 접근에 적용되며 맵 속성의 타일 데이터 뱅크 선택과는 별개입니다. 이전 뱅크를 자동 복원하지 않습니다.
사용 형식
void __cgb_safe_set_vbk(u8 value);매개변수
valueCGB의 VBK(
$FF4F)로 CPU가 접근할 VRAM 뱅크를 고릅니다.value & 1만 쓰므로 짝수는 뱅크 0, 홀수는 뱅크 1입니다. CPU의$8000–$9FFF접근에 적용되며 맵 속성의 타일 데이터 뱅크 선택과는 별개입니다. 이전 뱅크를 자동 복원하지 않습니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile_bulk(u16 dest, const u8* src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settile_bulk_fast(u16 dest, const u8* src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settile_unsafe(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settileat(u16 base, u8 x, u8 y, u8 tile);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settileat_unsafe(u16 base, u8 x, u8 y, u8 tile);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settileatattr(u16 base, u8 x, u8 y, u8 attr);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settileatattr_unsafe(u16 base, u8 x, u8 y, u8 attr);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settileatcgb(u16 base, u8 x, u8 y, u8 tile, u8 attr);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settileatcgb_unsafe(u16 base, u8 x, u8 y, u8 tile, u8 attr);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settileattr(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settileattr_bulk(u16 dest, const u8* src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settileattr_bulk_fast(u16 dest, const u8* src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settileattr_unsafe(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilebg(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilebg_unsafe(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilebgattr(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilebgattr_unsafe(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilebgcgb(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilebgcgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilecgb(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilecgb_bulk(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.tile_src, attr_srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilecgb_bulk_fast(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);매개변수
dest첫 VRAM 바이트 주소입니다. 맵에는
0x9800~0x9FFF범위를 사용하고 전송 전체가 해당 맵 안에 들어가도록 하세요.tile_src, attr_srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilecgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilewin(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilewin_unsafe(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilewinattr(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilewinattr_unsafe(u8 x, u8 y, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilewincgb(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
void __settilewincgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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를 담아 반환합니다. 이전 상태를 내부에 저장하지 않으며 뗌·반복 마스크도 반환하지 않습니다.
사용 형식
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의 원시 패드 함수는 비트 순서가 다릅니다.
사용 예
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이어야 합니다. 관측값은 화면에 고정되므로 버튼을 놓아도 사라지지 않습니다.


빌드 명령
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 뱅크 번호를 구합니다. 배치 정보를 확인하는 기능이며, 현재 선택된 뱅크를 읽거나 포인터를 역참조하지 않습니다.
사용 형식
u8 bank = __bankof(symbol);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
symbol컴파일러나 링커가 배치를 아는 ROM 데이터 또는 함수의 이름입니다. 포인터 변수 이름은 그 변수가 가리키는 대상을 뜻하지 않습니다. 실행 시점의 식을 심벌 이름으로 전달할 수 없습니다.
반환값
u8 뱅크 번호입니다. 0은 공통 ROM을 나타낼 수 있지만 모든 대상에서 유효한 전환 번호는 아닙니다.
사용 예
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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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용 기록은 갱신하지 않습니다.
사용 형식
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을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.
사용 예
__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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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 색 하나에는 하위·상위 두 바이트가 필요합니다.
사용 형식
__cgb_safe_set_bgpd(/* 書式は実装と呼び出し例を参照 */);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
value한 바이트(0~255). RGB15의 하위 바이트 다음에 상위 바이트를 씁니다. 먼저 인덱스 자동 증가를 켜거나 각 위치를 직접 선택하세요. 데이터 쓰기 자체가 자동 증가를 켜지는 않습니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.
사용 형식
__cgb_safe_set_bgpi(/* 書式は実装と呼び出し例を参照 */);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
value비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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로 지정합니다.
사용 형식
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 컨트롤러를 사용하지 않습니다.
사용 예
__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 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.



빌드 명령
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를 시작하지 않습니다.
사용 형식
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 컨트롤러를 사용하지 않습니다.
사용 예
__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 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.



빌드 명령
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 시작은 하지 않습니다.
사용 형식
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 컨트롤러를 사용하지 않습니다.
사용 예
__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 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.



빌드 명령
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입니다. 전송 길이가 아닌 주소를 설정합니다.
사용 형식
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 컨트롤러를 사용하지 않습니다.
사용 예
__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 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.



빌드 명령
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는 시작하지 않습니다.
사용 형식
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 컨트롤러를 사용하지 않습니다.
사용 예
__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 전용 실행을 검사했으며 부분 전송 후 취소·배속·실기는 검사하지 않았습니다.



빌드 명령
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 색 하나에는 하위·상위 두 바이트가 필요합니다.
사용 형식
__cgb_safe_set_obpd(/* 書式は実装と呼び出し例を参照 */);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
value한 바이트(0~255). RGB15의 하위 바이트 다음에 상위 바이트를 씁니다. 먼저 인덱스 자동 증가를 켜거나 각 위치를 직접 선택하세요. 데이터 쓰기 자체가 자동 증가를 켜지는 않습니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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도 같은 레지스터를 사용합니다. 다음 데이터 레지스터 접근 위치를 고르며, 이 호출만으로 색을 쓰지는 않습니다.
사용 형식
__cgb_safe_set_obpi(/* 書式は実装と呼び出し例を参照 */);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
value비트 0~5는 바이트 위치(0~63)이며 비트 7을 켜면 데이터 쓰기 뒤 자동 증가합니다. 비트 6은 쓰지 않습니다. 색 슬롯 n은 바이트 2n부터입니다. 63 다음에는 0으로 돌아가므로 직접 레지스터 쓰기에서는 범위를 직접 관리해야 합니다.
반환값
없음(void).
사용 조건과 제한
팔레트와 보호된 레지스터 쓰기는 DMG 모드에서 아무것도 쓰지 않습니다. 여기서 safe는 CGB 모드 확인을 뜻하며 PPU 접근 대기나 임계 구역을 뜻하지 않습니다. LCD를 끄거나 접근 가능한 PPU 구간 안에 끝나는 짧은 갱신에 사용하세요. VBlank를 기다리거나 여러 바이트 갱신을 인터럽트로부터 보호하지 않습니다.
사용 예
__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 표시 중 팔레트 접근 충돌은 검사하지 않았습니다.




빌드 명령
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과 달리 겸용 빌드에서도 사용할 수 있지만 이전 뱅크 번호는 반환하지 않습니다.
사용 형식
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는 임계 구역을 만들지 않습니다.
사용 예
__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 실행 검사이며 실기와 인터럽트에 의한 동시 뱅크 전환은 미검사입니다.

빌드 명령
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를 실행합니다.
사용 형식
u8 __critical_enter();반환값
이전 깊이를 u8 토큰으로 반환합니다. 가장 바깥 범위는 0, 첫 중첩 범위는 1입니다.
사용 조건과 제한
토큰은 진입의 역순으로 leave에 전달하고, 깊이가 256에서 래핑되지 않게 하세요. 기존 하드웨어 IME는 저장하지 않으므로 이전 DI 상태를 보존해야 하는 곳에서는 이 쌍을 사용하지 마세요.
사용 예
*((u8*)0xFFFF)=0;outer=__critical_enter();inner=__critical_enter();result[7]=outer;result[8]=inner;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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를 실행합니다.
사용 형식
void __critical_leave(u8 token);매개변수
token대응하는 __critical_enter가 반환한 토큰입니다.
반환값
반환값은 없습니다.
사용 조건과 제한
0이 아닌 토큰은 EI를 실행하지 않습니다. 임의로 0을 전달하면 다른 범위가 활성 상태여도 인터럽트가 활성화될 수 있으므로 호출 쌍을 정확히 맞추세요.
사용 예
__critical_leave(inner);__critical_leave(outer);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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
부호 없는 두 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 없는 세 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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의 보조 코드가 함수 반환 후 호출자의 뱅크를 복원합니다.
사용 형식
__farcall(bank, function_name);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
bank부호 없는 8비트 ROM 뱅크 번호입니다. 호출 시에는 함수 배치와 일치해야 하고, 읽기 시에는 원본 매핑을 지정합니다. 대상 카트리지에서 유효한 범위를 사용하세요.
func선언된 매개변수 없는 함수의 이름입니다. 이 이름 기반 호출 내장 함수에는 함수 주소를 담은 변수를 전달할 수 없습니다. bank 인수는 콜백 매개변수로 전달되지 않습니다.
반환값
반환형은 호출한 함수에 따릅니다. void 콜백은 값을 반환하지 않고 u8·u16 콜백은 해당 값을 반환합니다. 화면 예제는 void 콜백을 사용합니다.
사용 조건과 제한
이 호출은 사용자 인수를 전달하거나 콜백의 인터럽트 재진입성을 보장하지 않습니다. 콜백 자체의 데이터, 스택, 인터럽트 사용을 올바르게 관리하세요. 뱅크 복귀는 중첩 및 반복 호출로 검사했지만 실제 하드웨어와 임의의 인터럽트 타이밍은 이 예제의 검증 범위가 아닙니다.
이 GB 뱅크 API는 8비트 번호를 다루며 MBC5의 아홉 번째 뱅크 비트는 설정하지 않습니다. 뱅크 0 처리 등 카트리지별 규칙도 적용됩니다. 예제는 MBC5 뱅크 1~3을 명시적으로 사용합니다. 상태에 반영되지 않는 매퍼 직접 쓰기와 섞지 마세요.
사용 예
__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이 각각 콜백을 한 번 호출합니다.


빌드 명령
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
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을 읽어 윈도우 타일 맵의 시작 주소를 선택합니다.
사용 형식
u16 __getwinmapbase();반환값
$9800 또는 $9C00을 반환합니다. 타일 그래픽 주소가 아니라 타일 맵 시작 주소입니다.
사용 예
M_LCDC=0;result[2]=__getwinmapbase();M_LCDC=0x40;result[3]=__getwinmapbase();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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
부호 없는 곱셈 누산을 수행합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 없는 곱셈을 수행합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 없는 두 바이트를 곱하고 상위 바이트를 반환합니다.
사용 형식
u8 __mul8x8_hi(u8 a, u8 b);매개변수
a부호 없는 바이트 0~255입니다.
b부호 없는 바이트 0~255입니다.
반환값
floor(a*b/256)이며, 범위는 0~254입니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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로 지정한 버튼 중 하나를 골라 반복합니다. 대상 버튼을 새로 누르면 켜진 비트 중 가장 낮은 비트를 선택해 즉시 반환합니다. 그렇지 않으면 현재 동작이 대상에 포함되고 계속 눌려 있는 동안 유지합니다. 그 동작이 끝나면 남은 눌림 비트 중 가장 낮은 비트를 선택하되 즉시 펄스를 내지는 않습니다. 대상 버튼이 모두 놓이면 실행 상태를 지웁니다.
사용 형식
u8 __padrep(u8* state, u8 keys, u8 trigger, u8 mask);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
state쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어
u8 state[5]를 준비합니다. 배치는[0] 선택된 버튼 비트,[1] 초기 대기 시간,[2] 반복 간격,[3] 초기 대기 경과 카운터,[4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.keysGB의
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개 버튼마다 독립적인 타이머를 사용합니다.
사용 예
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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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을 반환하고 카운터를 다시 시작합니다. 계속 누르면 설정된 간격에 따라 펄스가 발생하며, 놓으면 실행 상태를 지웁니다. 다른 버튼으로는 이 반복 동작을 시작할 수 없습니다.
사용 형식
u8 __padrep_down(u8* state, u8 keys, u8 trigger);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
state쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어
u8 state[5]를 준비합니다. 배치는[0] 선택된 버튼 비트,[1] 초기 대기 시간,[2] 반복 간격,[3] 초기 대기 경과 카운터,[4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.keysGB의
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개 버튼마다 독립적인 타이머를 사용합니다.
사용 예
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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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으로 만들고, 지정한 초기 대기 시간과 반복 간격을 저장합니다. 버튼을 읽거나 반복 펄스를 만들지는 않습니다.
사용 형식
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).
사용 예
__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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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을 반환하고 카운터를 멈추며, 둘 다 누르지 않으면 실행 상태를 지웁니다.
사용 형식
u8 __padrep_lr(u8* state, u8 keys, u8 trigger);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
state쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어
u8 state[5]를 준비합니다. 배치는[0] 선택된 버튼 비트,[1] 초기 대기 시간,[2] 반복 간격,[3] 초기 대기 경과 카운터,[4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.keysGB의
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개 버튼마다 독립적인 타이머를 사용합니다.
사용 예
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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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로 지정한 버튼 중 하나를 골라 반복합니다. 대상 버튼을 새로 누르면 켜진 비트 중 가장 낮은 비트를 선택해 즉시 반환합니다. 그렇지 않으면 현재 동작이 대상에 포함되고 계속 눌려 있는 동안 유지합니다. 그 동작이 끝나면 남은 눌림 비트 중 가장 낮은 비트를 선택하되 즉시 펄스를 내지는 않습니다. 대상 버튼이 모두 놓이면 실행 상태를 지웁니다.
사용 형식
u8 __padrep_mask(u8* state, u8 keys, u8 trigger, u8 mask);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
state쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어
u8 state[5]를 준비합니다. 배치는[0] 선택된 버튼 비트,[1] 초기 대기 시간,[2] 반복 간격,[3] 초기 대기 경과 카운터,[4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.keysGB의
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개 버튼마다 독립적인 타이머를 사용합니다.
사용 예
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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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으로 만듭니다. 메뉴를 닫거나 입력 대상을 바꿀 때 사용하면 이전에 누르고 있던 버튼의 반복 진행 상태가 다음 조작으로 이어지지 않습니다.
사용 형식
void __padrep_reset(u8* state);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
state쓰기 가능한 연속된 RAM 5바이트의 포인터입니다. 예를 들어
u8 state[5]를 준비합니다. 배치는[0] 선택된 버튼 비트,[1] 초기 대기 시간,[2] 반복 간격,[3] 초기 대기 경과 카운터,[4] 반복 경과 카운터입니다. 독립적인 반복 처리마다 별도의 상태를 준비하고 사용 전에 초기화하세요. 널 포인터, 5바이트 미만의 영역, 방향 전용 처리와 범용 처리 간의 상태 공유는 지원하지 않습니다.
반환값
없음(void).
사용 예
__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이어야 합니다. 고정 입력으로 상태 처리를 설명하는 예제이며 실제 버튼 입력을 기다리지 않습니다.


빌드 명령
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
{
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으로 되돌립니다.
사용 형식
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_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
사용 예
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이어야 합니다. 관측값은 화면에 고정되므로 버튼을 놓아도 사라지지 않습니다.


빌드 명령
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
{
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으로 되돌립니다.
사용 형식
u8 __readpadbtn();이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
반환값
위 비트 순서의 0~15 마스크입니다. 여러 비트가 설정될 수 있으므로 전체 값이 1인지 비교하지 말고 result & bit로 특정 버튼을 확인하세요.
사용 조건과 제한
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
사용 예
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이어야 합니다. 관측값은 화면에 고정되므로 버튼을 놓아도 사라지지 않습니다.


빌드 명령
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
{
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으로 되돌립니다.
사용 형식
u8 __readpaddir();이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
반환값
위 비트 순서의 0~15 마스크입니다. 여러 비트가 설정될 수 있으므로 전체 값이 1인지 비교하지 말고 result & bit로 특정 버튼을 확인하세요.
사용 조건과 제한
이 호출은 하드웨어 상태를 새로 읽습니다. input_*나 nes_pad1_*의 저장 상태는 갱신하지 않으며 누름·뗌 에지나 반복도 만들지 않습니다. 필요하면 결과를 저장하고 직접 보관한 이전 마스크와 비교해 변화를 구하세요.
사용 예
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이어야 합니다. 관측값은 화면에 고정되므로 버튼을 놓아도 사라지지 않습니다.


빌드 명령
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
{
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
부호 있는 두 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 있는 두 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 있는 세 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 있는 세 항의 내적을 계산합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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가 꺼져 있거나 전체 전송을 마칠 수 있는 쓰기 가능 시간을 호출자가 확보했을 때 사용하세요.
사용 형식
__settile_fast(x, y, tile);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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
{
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 전송은 하지 않습니다.
사용 형식
__settilebg16_buf(buf, x, y, tile);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
buf타일 행당 32바이트인 RAM 버퍼입니다. 전체 32×32 맵은 1024바이트가 필요합니다. 왼쪽 위 위치는
64*y + 2*x이며 그곳에서0,1,32,33오프셋에 씁니다.x, y16×16픽셀 셀 단위 좌표입니다. 전체 32×32 타일 버퍼에서는 각각 0~15입니다. 단일 타일 쓰기 함수의 8×8 타일 좌표와 단위가 다릅니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
AB
CD

빌드 명령
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
{
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 안에 끝남을 보장하지 않습니다.
사용 형식
__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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
AB
CD

빌드 명령
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
{
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에서도 이 준비 작업이 실행됩니다.
사용 형식
__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, y16×16픽셀 셀 단위 좌표입니다. 전체 32×32 타일 버퍼에서는 각각 0~15입니다. 단일 타일 쓰기 함수의 8×8 타일 좌표와 단위가 다릅니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
attrCGB 속성 바이트입니다. 비트 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
AB
CD

빌드 명령
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
{
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 안에 끝남을 보장하지 않습니다.
사용 형식
__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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
AB
CD

빌드 명령
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
{
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
부호 있는 곱셈 누산을 수행합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 있는 곱셈 누산을 수행합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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
부호 있는 곱셈을 수행합니다.
사용 형식
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비트에서 래핑됩니다.
사용 예
// 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 배열에는 모든 결과를 보관합니다.


빌드 명령
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
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바이트를 읽습니다.
사용 형식
u8 __sram_read8(u16 address);매개변수
address$A000~$BFFF 범위의 CPU 주소입니다.
반환값
RAM 존재 여부나 데이터 유효성을 검사하지 않고 바이트를 반환합니다.
사용 조건과 제한
접근 전에 $0000에 0x0A를 써서 RAM을 활성화하고, 끝난 뒤 0을 써서 비활성화합니다. 뱅크 선택이나 주소 검사는 하지 않으므로 호출자가 RAM 뱅크를 설정해야 합니다. 저장 헤더나 체크섬은 관리하지 않습니다.
사용 예
*((u8*)0x4000)=0;result[12]=__sram_read8(0xA006);result[19]=*((u8*)0xA006);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
7/42/255의 세 바이트를 저장한 뒤 길이 불일치, 잘못된 슬롯, 손상된 체크섬을 검사합니다. 불러오기에 성공하면 가운데 바이트는 42입니다. 읽기에 실패하면 대상의 77을 유지하며, 지운 뒤 검증하면 0을 반환합니다.


빌드 명령
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
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바이트를 씁니다.
사용 형식
void __sram_write8(u16 address, u8 value);매개변수
address$A000~$BFFF 범위의 CPU 주소입니다.
value바이트 값 0~255입니다.
반환값
반환값은 없습니다.
사용 조건과 제한
접근 전에 $0000에 0x0A를 써서 RAM을 활성화하고, 끝난 뒤 0을 써서 비활성화합니다. 뱅크 선택이나 주소 검사는 하지 않으므로 호출자가 RAM 뱅크를 설정해야 합니다. 저장 헤더나 체크섬은 관리하지 않습니다.
사용 예
__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을 반환합니다.


빌드 명령
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
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 용량, 주소, 포인터가 아닌 뱅크 번호입니다.
사용 형식
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는 임계 구역을 만들지 않습니다.
사용 예
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 실행 검사이며 실기와 인터럽트에 의한 동시 뱅크 전환은 미검사입니다.

빌드 명령
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
{
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 뱅크를 바꾸고 바꾸기 전의 실제 뱅크 번호를 반환합니다. 호출자 뱅크를 복원하려면 이 반환값을 저장하세요. 메모리 내용을 복사·초기화하지 않으며 뱅크를 자동 복원하지 않습니다.
사용 형식
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는 임계 구역을 만들지 않습니다.
사용 예
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 실행 검사이며 실기와 인터럽트에 의한 동시 뱅크 전환은 미검사입니다.

빌드 명령
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
{
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바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.
사용 형식
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를 생성하지 않으며 클리핑이나 범위 검사도 하지 않습니다.
사용 예
// 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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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
{
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로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.
사용 형식
void __vram_memset_unsafe(u16 dst, u8 value, u16 len);이 호출 형식의 매개변수 이름은 컴파일러 내장 기능을 설명하기 위한 것입니다. 별도로 추가할 C 선언이 아닙니다.
매개변수
dst대상의 첫 바이트 주소이며 보통 VRAM
0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.
반환값
없음(void).
사용 조건과 제한
LCD/STAT 대기를 모두 생략하고 CPU로 직접 씁니다. LCD를 끄거나 관리 가능한 쓰기 구간 안에 전체 전송이 끝나게 하세요. VBlank에 들어갔다고 해서 임의로 긴 전송이 안전해지는 것은 아닙니다. 이 내장 함수는 전송을 위한 DI나 EI를 생성하지 않으며 클리핑이나 범위 검사도 하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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
{
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가 목표 행과 같아질 때까지 기다립니다.
사용 형식
void __wait_ly(u8 ly);매개변수
ly주사선 0~153입니다. 153을 넘는 값은 LCD 동작 중에 대기가 끝나지 않습니다.
반환값
반환값은 없습니다.
사용 조건과 제한
이미 목표 행이면 즉시 반환합니다. 그 행이 지났으면 다음 프레임까지 기다립니다. LCD가 꺼져 있어도 즉시 반환합니다.
사용 예
M_LCDC=0x91;__wait_ly(80);result[9]=*((u8*)0xFF44);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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 방향으로 버림합니다.
사용 형식
s16 __smul16x8(s16 a, s8 b);매개변수
a배율을 적용할 부호 있는 16비트 값입니다.
b-128..127 범위의 부호 있는 바이트 계수입니다.
반환값
배율 적용 결과를 부호 있는 16비트 값으로 반환합니다.
사용 조건과 제한
계수 범위는 -128..127이며, 256이 1.0인 배율을 사용합니다. 양수와 음수 모두 0 방향으로 버림합니다. -257*64/256은 -64를 반환합니다.
사용 예
result[13]=__smul16x8(-257,64);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
왼쪽은 초기 상태, 오른쪽은 1틱 후입니다. 중심 (24,24), 속도 (4,16)인 빨간색 16x16 박스가 (28,36)에 도달하고 파란색 바닥에서 Y 속도가 0이 됩니다. 패널 원점은 화면 좌표 (8,48)과 (88,48)입니다. DMG에서는 모든 표시가 검은색입니다. RAM 검사로 초기화, 설정 함수, 적분, 사각형·점 질의, 부호 있는 배율 연산도 확인합니다.


빌드 명령
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
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을 유지합니다.
사용 형식
void __bit_clear(u8* p, u16 bit_index);매개변수
pCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 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 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
사용 예
__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 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.


빌드 명령
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
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인 비트를 다시 설정해도 그대로이며, 토글 동작이 아닙니다.
사용 형식
void __bit_set(u8* p, u16 bit_index);매개변수
pCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 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 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
사용 예
__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 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.


빌드 명령
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
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개를 저장할 수 있습니다. 결과는 선택한 비트가 꺼져 있는지 켜져 있는지를 나타내며, 숫자 표현은 대상 기종에 따라 다릅니다.
사용 형식
u8 __bit_test(const u8* p, u16 bit_index);매개변수
pCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 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 플래그 도우미를 대신 사용하지 마세요.
사용 예
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 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.


빌드 명령
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
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으로 만듭니다. 나머지 비트는 유지합니다. 두 호출 사이에 다른 코드가 그 비트를 쓰지 않는다면, 같은 비트에 두 번 호출하여 원래 상태로 되돌릴 수 있습니다.
사용 형식
void __bit_toggle(u8* p, u16 bit_index);매개변수
pCPU에서 접근할 수 있는 메모리에 있는 플래그 배열의 첫 바이트 포인터입니다. 이 호출은 배열을 할당하거나 초기화하지 않습니다. 설정·해제·반전에는 쓰기 가능한 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 플래그 도우미를 대신 사용하지 마세요.
설정·해제·반전은 바이트를 읽고 선택한 비트를 바꾼 뒤 다시 씁니다. 이 과정은 원자적 갱신이 아닙니다. 같은 바이트의 다른 비트에 대한 중간 갱신이 사라지지 않도록 메인 루프와 인터럽트 처리기의 쓰기를 조정하세요.
사용 예
__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 상태 검사입니다. 실기와 인터럽트의 동시 쓰기는 검증하지 않았습니다.


빌드 명령
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
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은 비트 수가 아니라 바이트 수이며, 화면에 그래픽을 전송하는 함수는 아닙니다.
사용 형식
void __copy16(void* dst, const void* src);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
반환값
없음(void).
사용 조건과 제한
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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비트 값을 한 번에 읽거나 뱅크를 전환하는 기능은 아닙니다.
사용 형식
void __copy32(void* dst, const void* src);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
반환값
없음(void).
사용 조건과 제한
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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는 같은 구현을 사용합니다.
사용 형식
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의 복귀 뱅크 저장 공간은 공유되므로 중첩 전송으로 덮어쓰면 안전하지 않습니다.
사용 예
__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프레임 동안 실행한 뒤 글자, 도형 마스크, 색을 예상값과 비교합니다. 에뮬레이터 검증이며 실제 하드웨어 시험은 아닙니다.


빌드 명령
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로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.
사용 형식
void __fill_tilemap(u16 dst, u8 value, u16 len);매개변수
dst대상의 첫 바이트 주소이며 보통 VRAM
0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.
반환값
없음(void).
사용 조건과 제한
시작 주소와 바이트 수를 명시하세요. 이 별칭은 배경 맵을 선택하거나 전체 맵을 자동으로 채우지 않습니다. 32 × 32 타일 번호 맵 전체를 채우려면 VRAM 뱅크 0을 선택하고 맵 시작 주소와 1024바이트를 지정하세요.
LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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을 읽어 배경 타일 맵의 시작 주소를 선택합니다.
사용 형식
u16 __getbgmapbase();반환값
$9800 또는 $9C00을 반환합니다. 타일 그래픽 주소가 아니라 타일 맵 시작 주소입니다.
사용 예
M_LCDC=0;result[0]=__getbgmapbase();M_LCDC=8;result[1]=__getbgmapbase();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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 좌표 차이의 절댓값을 더합니다.
사용 형식
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를 넘는 거리는 래핑됩니다.
사용 예
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도 확인합니다.


빌드 명령
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
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
셀 좌표를 행 우선 배열 인덱스로 변환합니다.
사용 형식
u16 __map_index(u8 x, u8 y, u8 width);매개변수
x열 0~255이며, 보통 width보다 작아야 합니다.
y행 0~255입니다.
width한 행의 셀 수 0~255입니다.
반환값
y*width+x를 u16으로 반환합니다. 좌표는 검사하지 않습니다.
사용 예
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도 확인합니다.


빌드 명령
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
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바이트를 넘는 데이터도 복사할 수 있습니다. 낮은 주소부터 차례로 읽고 씁니다.
사용 형식
void __memcpy(void* dst, const void* src, u16 count);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
countu16으로 지정하는 바이트 수(0~65535)이며 원소 개수가 아닙니다. 자료형의 최댓값만큼 큰 버퍼를 실제 기기에서 확보할 수 있다는 뜻은 아닙니다. 전체 범위가 현재 메모리 매핑 안에 들어가야 합니다.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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은 길이의 자료형을 뜻하며 특정 실행 시간이나 생성 코드 크기를 보장하지는 않습니다.
사용 형식
void __memcpy_small(void* dst, const void* src, u8 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
src읽기 원본의 첫 바이트를 가리키는 포인터입니다. 복사하는 동안 전체 범위를 읽을 수 있어야 하며 내용이 바뀌면 안 됩니다.
lenu8로 지정하는 0~255의 바이트 수입니다. 256바이트 블록 전체를 뜻하려고 256을 넘기면 안 됩니다. 이 인수의 자료형에 들어가지 않습니다. 256바이트 이상은_small이 없는 함수를 사용하세요.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
원본과 대상에는 서로 겹치지 않는 영역을 사용하세요. 낮은 주소부터 복사하며, 겹침을 안전하게 처리하는 memmove 기능은 제공하지 않습니다. 아직 읽지 않은 원본 위치에 쓰면 뒤쪽 데이터가 손상될 수 있습니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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…를 씁니다. 여러 바이트로 된 정수를 반복해서 쓰는 기능은 아닙니다.
사용 형식
void __memset(void* dst, u8 value, u16 count);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
value각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다.
u16배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.countu16으로 지정하는 바이트 수(0~65535)이며 원소 개수가 아닙니다. 자료형의 최댓값만큼 큰 버퍼를 실제 기기에서 확보할 수 있다는 뜻은 아닙니다. 전체 범위가 현재 메모리 매핑 안에 들어가야 합니다.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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을 사용하세요.
사용 형식
void __memset_small(void* dst, u8 value, u8 len);매개변수
dst쓰기 대상의 첫 바이트를 가리키는 포인터입니다. 전체 작업에 필요한 연속 공간을 미리 확보하세요. 버퍼를 자동으로 할당하지는 않습니다.
value각 주소에 반복해서 쓸 0~255의 1바이트 값입니다. 0을 쓰면 영역을 0으로 채웁니다.
u16배열을 0이 아닌 수치로 초기화하려면 각 원소에 따로 대입하세요.lenu8로 지정하는 0~255의 바이트 수입니다. 256바이트 블록 전체를 뜻하려고 256을 넘기면 안 됩니다. 이 인수의 자료형에 들어가지 않습니다. 256바이트 이상은_small이 없는 함수를 사용하세요.
반환값
없음(void).
사용 조건과 제한
길이가 0이면 바이트를 전송하지 않습니다. 단, 인수 식의 부수 효과는 실행됩니다. 예제에서는 포인터를 반환하는 함수 두 개를 인수로 넘겨, 복사 길이가 0이어도 두 함수가 모두 호출됨을 확인합니다.
작업을 완료한 뒤 호출한 곳으로 돌아갑니다. ROM·RAM 뱅크를 전환하거나 버퍼 경계에 맞춰 범위를 잘라 주지 않습니다. 매핑을 유지하고 유효한 범위를 지정하며, 같은 버퍼를 쓰는 인터럽트 처리와 접근을 조정하세요.
GB에서는 일반 CPU 메모리 접근으로 동작합니다. VRAM·OAM에 안전하게 접근할 시점을 기다리거나 DMA를 시작하거나 CGB의 VRAM·WRAM 뱅크를 선택하지 않습니다. 해당 작업에는 전용 그래픽 전송 API를 사용하세요.
사용 예
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바이트를 포함합니다. 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
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 쓰기 보조 함수로 반복 구간을 채운 뒤 출력 바이트 수를 반환하는 루프를 생성합니다. 압축 타일 데이터나 바이트 단위 타일 맵을 로드할 때 유용합니다.
사용 형식
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 내장 함수 자체에는 라이브러리 구현 파일이 필요 없습니다.
사용 예
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프레임 후 검사합니다. 잘못된 에셋, 인터럽트의 동시 사용, 실기는 검증 범위에 포함하지 않습니다.


빌드 명령
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
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바이트가 필요할 때 사용합니다.
사용 형식
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 오브젝트가 필요 없습니다.
사용 예
__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프레임 후 검사했습니다. 실기와 통계적 품질은 시험하지 않았습니다.


빌드 명령
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
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비트 상태를 바꿉니다. 디버깅 중 같은 수열을 재현하려면 고정 시드를 사용하세요. 시드 설정 자체는 난수를 생성하지 않습니다.
사용 형식
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 오브젝트가 필요 없습니다.
사용 예
__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프레임 후 검사했습니다. 실기와 통계적 품질은 시험하지 않았습니다.


빌드 명령
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
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를 즉시 쓰고 소프트웨어의 현재값·보류값을 동기화합니다.
사용 형식
void __scroll_bg_set(u8 scx, u8 scy);매개변수
scx가로 스크롤 0~255픽셀입니다.
scy세로 스크롤 0~255픽셀입니다.
반환값
반환값은 없습니다.
사용 조건과 제한
배경 갱신 보류 플래그를 지웁니다. 프레임 도중 쓰면 그 이후 주사에 영향을 주므로 보통 VBlank 중에 호출하세요.
사용 예
__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가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile_col(u8 x, u8 y, const u8* src, u8 len);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A
B
C

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
w, h8×8 타일 단위의 너비와 높이입니다.
x+w <= 32,y+h <= 32를 지키세요. 어느 하나가 0이면 쓰지 않습니다.tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
AAA
AAA

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile_row(u8 x, u8 y, const u8* src, u8 len);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
srcRAM 또는 현재 읽을 수 있는 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 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settile_xy(u8 x, u8 y, u8 tile);매개변수
x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
tile0~255의 타일 번호입니다. 번호가 타일 이미지 데이터를 가리키는 방식은 LCDC 비트 4로 결정됩니다. VRAM 바이트 주소가 아닙니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
A

빌드 명령
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 안에 끝남을 보장하지 않습니다.
사용 형식
void __settilemap_rect(u16 base, u8 x, u8 y, u8 w, u8 h, const u8* src);매개변수
basebase로 맵 시작 주소0x9800또는0x9C00을 지정합니다. 실제 주소는base + 32*y + x입니다.x, y8×8 타일 단위의 좌표이며 각각 0~31입니다. 쓰기 범위 전체가 32×32 맵 안에 들어가야 합니다. 좌표를 자동으로 잘라 주지 않습니다.
w, h8×8 타일 단위의 너비와 높이입니다.
x+w <= 32,y+h <= 32를 지키세요. 어느 하나가 0이면 쓰지 않습니다.srcRAM 또는 현재 읽을 수 있는 ROM 뱅크에 연속으로 놓인 데이터입니다. 타일 배열에는 타일 번호, 속성 배열에는 CGB 속성을 넣습니다. 즉시 복사하며 포인터를 보관하지 않습니다.
반환값
없음(void).
사용 조건과 제한
타일 번호는 현재 선택된 VRAM 뱅크에 씁니다. CGB에서는 타일 번호용 뱅크 0을 선택하세요. 이 호출은 뱅크를 바꾸지 않습니다.
예제는 gb_common.h를 사용합니다. m_init()은 제공된 ASCII 폰트를 문자 코드와 같은 타일 번호로 불러옵니다. m_wait(); M_LCDC=0;은 VBlank 중 LCD를 꺼 대기 없는 전송을 준비합니다. 마지막 LCDC 값으로 표시할 맵을 선택합니다.
사용 예
// __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 타일 하나입니다. 위에서 아래로, 왼쪽에서 오른쪽으로 읽으며 주변 타일은 비어 있습니다.
ABC
DEF

빌드 명령
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 주소를 계산합니다.
사용 형식
u16 __tile_addr(u16 base, u8 x, u8 y);매개변수
base타일 맵 시작 주소입니다. 보통 $9800/$9C00입니다.
x열이며, 보통 0~31입니다.
y행이며, 보통 0~31입니다.
반환값
메모리에 접근하지 않고 (base + y*32 + x)를 65536으로 나눈 나머지를 반환합니다.
사용 예
result[4]=__tile_addr(0x9800,3,2);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
맵 선택, 스크롤, 중첩, 주사선 대기를 익힙니다. 시작 주소는 38912/39936, 중첩 토큰은 0/1, 관측된 목표 LY는 80/144입니다. LCD가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.
사용 형식
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은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.
사용 형식
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은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.
사용 형식
void __vram_fill(u16 dst, u8 value, u16 len);매개변수
dst대상의 첫 바이트 주소이며 보통 VRAM
0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.
반환값
없음(void).
사용 조건과 제한
LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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바이트를 주소가 증가하는 순서로 즉시 복사합니다. 복사가 끝난 뒤 반환하며 큐에 작업을 등록하지 않습니다. 원본과 대상 주소는 한 바이트씩 증가하고 타일맵 행 간격이나 타일 형식 변환은 처리하지 않습니다.
사용 형식
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은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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로 즉시 채웁니다. 타일 패턴 전체가 아닌 한 바이트를 반복합니다. 맵 바이트를 채우면 타일 번호가 반복되고 패턴 바이트를 채우면 픽셀 비트플레인이 바뀝니다.
사용 형식
void __vram_memset(u16 dst, u8 value, u16 len);매개변수
dst대상의 첫 바이트 주소이며 보통 VRAM
0x8000–0x9FFF입니다. 전체 범위가 유효해야 합니다. CGB는 현재 VRAM 뱅크를 사용하며 맵 영역의 뱅크 0은 타일 번호, 뱅크 1은 속성입니다. LCDC에 따른 맵 선택, 행 끝 줄바꿈, VRAM 할당은 하지 않습니다.value0~255의 한 바이트를 반복해서 씁니다. 화면에서의 의미는 대상이 타일 번호인지, 픽셀 비트플레인인지, 다른 바이트 구조인지에 따라 달라집니다.
len연속된 바이트 수로 0~65535를 지정합니다. 타일 수가 아닙니다. 전체 범위가 유효한 원본과 쓰기 가능한 대상에 들어가도록 하세요. 범위 검사나 16비트 주소 순환 방지는 하지 않습니다.
반환값
없음(void).
사용 조건과 제한
LCD가 켜져 있으면 바이트를 쓸 때마다 DI로 인터럽트를 금지한 상태에서 STAT 모드 0 또는 1을 기다리고, 쓰기 후 EI를 실행합니다. LCD가 꺼져 있으면 일반 CPU 루프를 사용합니다. 여러 프레임이 걸릴 수 있고 호출 전 인터럽트 금지 상태는 보존하지 않습니다. DMA 레지스터도 설정하지 않습니다. 길이 0은 데이터를 전송하지 않습니다.
사용 예
__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은 왼쪽 정사각형이 파란색, 오른쪽이 초록색이며 글자는 검은색입니다. 그려진 모든 픽셀이 지정한 색인지도 검사합니다.


빌드 명령
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를 기다립니다.
사용 형식
void __wait_vblank();반환값
반환값은 없습니다.
사용 조건과 제한
LCD가 꺼져 있으면 즉시 반환합니다. 인터럽트를 활성화하거나 라이브러리 프레임 카운터를 증가시키지 않고 LY를 폴링합니다.
사용 예
__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가 꺼졌을 때 대기가 반환되는지도 확인합니다.


빌드 명령
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
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
왼쪽·위쪽을 포함하고 오른쪽·아래쪽을 제외하는 사각형 안에 있는지 검사합니다.
사용 형식
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를 넘어도 왼쪽으로 래핑되지 않습니다.
사용 예
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도 확인합니다.


빌드 명령
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
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
추적 중인 배경 좌표에 부호 있는 변위를 더하고 즉시 적용합니다.
사용 형식
void __scroll_bg_add(s8 dx, s8 dy);매개변수
dx부호 있는 가로 픽셀 변위이며, 범위는 −128~127입니다.
dy부호 있는 세로 픽셀 변위이며, 범위는 −128~127입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다. 결과는 256을 법으로 순환합니다. 가산은 보류값이나 임시 래스터 레지스터 값이 아니라 추적 중인 현재값에서 시작합니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
두 배경 스크롤 좌표를 나중에 반영할 값으로 저장합니다.
사용 형식
void __scroll_bg_set_buffered(u8 scx, u8 scy);매개변수
scx가로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 왼쪽으로 이동합니다.
scy세로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 위쪽으로 이동합니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
추적 중인 현재값이나 레지스터는 변경하지 않습니다. __scroll_flush 또는 Scroll_Flush로 반영하세요. 값을 저장하는 이 단계는 VBlank를 기다리지 않습니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
라이브러리가 추적하는 현재 가로 배경 스크롤 값을 읽습니다.
사용 형식
u8 __scroll_bg_x_get();반환값
추적 중인 현재 바이트 값(0~255)을 반환합니다. WX에는 하드웨어의 7픽셀 오프셋이 포함됩니다.
사용 조건과 제한
하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.
사용 예
result[6]=__scroll_bg_x_get();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
배경의 가로 스크롤을 즉시 설정합니다.
사용 형식
void __scroll_bg_x_set(u8 scx);매개변수
scx가로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 왼쪽으로 이동합니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다.
사용 예
__scroll_bg_x_set(250);result[4]=M_SCX;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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만 나중에 반영할 값으로 저장합니다.
사용 형식
void __scroll_bg_x_set_buffered(u8 scx);매개변수
scx가로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 왼쪽으로 이동합니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
다른 축의 보류값은 유지합니다. 반영 전까지 현재값과 레지스터는 바뀌지 않습니다. 먼저 좌표 쌍을 초기화하세요.
사용 예
__scroll_bg_x_set_buffered(32);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
라이브러리가 추적하는 현재 세로 배경 스크롤 값을 읽습니다.
사용 형식
u8 __scroll_bg_y_get();반환값
추적 중인 현재 바이트 값(0~255)을 반환합니다. WX에는 하드웨어의 7픽셀 오프셋이 포함됩니다.
사용 조건과 제한
하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.
사용 예
result[7]=__scroll_bg_y_get();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
배경의 세로 스크롤을 즉시 설정합니다.
사용 형식
void __scroll_bg_y_set(u8 scy);매개변수
scy세로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 위쪽으로 이동합니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다.
사용 예
__scroll_bg_y_set(2);result[5]=M_SCY;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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만 나중에 반영할 값으로 저장합니다.
사용 형식
void __scroll_bg_y_set_buffered(u8 scy);매개변수
scy세로 배경 스크롤이며, 범위는 0~255픽셀입니다. 값을 늘리면 배경이 위쪽으로 이동합니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
다른 축의 보류값은 유지합니다. 반영 전까지 현재값과 레지스터는 바뀌지 않습니다. 먼저 좌표 쌍을 초기화하세요.
사용 예
__scroll_bg_y_set_buffered(40);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
변경된 배경·윈도우 좌표를 추적 상태와 하드웨어에 반영하고 보류 플래그를 지웁니다.
사용 형식
void __scroll_flush();반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
변경되지 않은 좌표 쌍은 그대로 둡니다. 인터럽트를 마스킹하거나 VBlank를 기다리지 않으므로 필요한 타이밍은 호출자가 맞춰야 합니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
기본 윈도우 표시 상태를 저장하고 분할 테이블의 첫 주사선 이벤트를 준비합니다.
사용 형식
void __scroll_split_commit();반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 reset→push→commit 순서로 재구성하고, 처리 함수가 끝날 수 있도록 이벤트 사이에 충분한 간격을 두세요. 인터럽트 지연 때문에 지정한 주사선과 처음 영향을 받는 픽셀이 일치하지 않을 수 있습니다. 테이블이 비어 있지 않으면 IE에서 VBlank/STAT를 활성화하고 EI를 실행합니다. 빈 테이블은 LYC 소스를 비활성화하지만 IE나 CPU 인터럽트 상태를 복원하지는 않습니다.
사용 예
__scroll_split_commit();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
BG X=0→8→16으로 화면을 세 스크롤 구간으로 나눕니다. 24×16 사각형 세 개가 화면 (32,16), (24,80), (16,112)에 나타납니다. 이벤트는 LY=64와 104에 예약됩니다. 사각형은 DMG에서 검은색, CGB에서 빨간색입니다.


빌드 명령
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
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
지정한 주사선 이벤트에서 배경 스크롤을 바꾸는 분할 항목을 추가합니다.
사용 형식
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 순서로 재구성하고, 처리 함수가 끝날 수 있도록 이벤트 사이에 충분한 간격을 두세요. 인터럽트 지연 때문에 지정한 주사선과 처음 영향을 받는 픽셀이 일치하지 않을 수 있습니다.
사용 예
__scroll_split_push(64,8,0);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
BG X=0→8→16으로 화면을 세 스크롤 구간으로 나눕니다. 24×16 사각형 세 개가 화면 (32,16), (24,80), (16,112)에 나타납니다. 이벤트는 LY=64와 104에 예약됩니다. 사각형은 DMG에서 검은색, CGB에서 빨간색입니다.


빌드 명령
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
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 작업을 선택하는 분할 항목을 추가합니다.
사용 형식
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 바이트를 두 용도로 사용합니다.
사용 예
__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에서 빨간색입니다.


빌드 명령
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
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 인터럽트 허용 비트를 지웁니다.
사용 형식
void __scroll_split_reset();반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
공유 테이블에는 항목 8개가 들어갑니다. 추가할 때 즉시 적용하거나 정렬하지 않으며, 초과 항목은 무시합니다. 안전한 프레임 경계에서 reset→push→commit 순서로 재구성하고, 처리 함수가 끝날 수 있도록 이벤트 사이에 충분한 간격을 두세요. 인터럽트 지연 때문에 지정한 주사선과 처음 영향을 받는 픽셀이 일치하지 않을 수 있습니다. 테이블의 바이트를 지우거나 IE를 초기화하거나 CPU 인터럽트를 비활성화하지는 않습니다.
사용 예
__scroll_split_reset();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
BG X=0→8→16으로 화면을 세 스크롤 구간으로 나눕니다. 24×16 사각형 세 개가 화면 (32,16), (24,80), (16,112)에 나타납니다. 이벤트는 LY=64와 104에 예약됩니다. 사각형은 DMG에서 검은색, CGB에서 빨간색입니다.


빌드 명령
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
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
추적 중인 윈도우 좌표에 부호 있는 변위를 더하고 즉시 적용합니다.
사용 형식
void __scroll_win_add(s8 dx, s8 dy);매개변수
dx부호 있는 가로 픽셀 변위이며, 범위는 −128~127입니다.
dy부호 있는 세로 픽셀 변위이며, 범위는 −128~127입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다. 결과는 256을 법으로 순환합니다. 가산은 보류값이나 임시 래스터 레지스터 값이 아니라 추적 중인 현재값에서 시작합니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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의 윈도우 비트와 추적 중인 윈도우 표시 상태를 끕니다.
사용 형식
void __scroll_win_hide();반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
좌표나 맵 선택은 바꾸지 않습니다. 실제로 보이려면 LCD와 배경·윈도우 기능이 활성화되어 있어야 하며, 윈도우가 가시 영역 안에 있어야 합니다.
사용 예
__scroll_win_hide();result[27]=M_LCDC&32;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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를 즉시 설정합니다.
사용 형식
void __scroll_win_set(u8 wx, u8 wy);매개변수
wx가공하지 않은 WX 바이트 값이며 범위는 0~255입니다. 원하는 화면 X 좌표에 7을 더하세요. 왼쪽 가장자리는 7입니다.
wy화면 Y 픽셀 단위의 윈도우 상단 위치입니다. 범위는 0~255이고, 화면에 보이는 행은 0~143입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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
두 윈도우 좌표를 나중에 반영할 값으로 저장합니다.
사용 형식
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를 기다리지 않습니다.
사용 예
__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이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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의 윈도우 비트와 추적 중인 윈도우 표시 상태를 켭니다.
사용 형식
void __scroll_win_show();반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
좌표나 맵 선택은 바꾸지 않습니다. 실제로 보이려면 LCD와 배경·윈도우 기능이 활성화되어 있어야 하며, 윈도우가 가시 영역 안에 있어야 합니다.
사용 예
__scroll_win_show();result[26]=M_LCDC&32;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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 값을 읽습니다.
사용 형식
u8 __scroll_win_x_get();반환값
추적 중인 현재 바이트 값(0~255)을 반환합니다. WX에는 하드웨어의 7픽셀 오프셋이 포함됩니다.
사용 조건과 제한
하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.
사용 예
result[12]=__scroll_win_x_get();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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 좌표만 즉시 설정합니다.
사용 형식
void __scroll_win_x_set(u8 wx);매개변수
wx가공하지 않은 WX 바이트 값이며 범위는 0~255입니다. 원하는 화면 X 좌표에 7을 더하세요. 왼쪽 가장자리는 7입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다.
사용 예
__scroll_win_x_set(23);result[10]=M_WX;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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만 나중에 반영할 값으로 저장합니다.
사용 형식
void __scroll_win_x_set_buffered(u8 wx);매개변수
wx가공하지 않은 WX 바이트 값이며 범위는 0~255입니다. 원하는 화면 X 좌표에 7을 더하세요. 왼쪽 가장자리는 7입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
다른 축의 보류값은 유지합니다. 반영 전까지 현재값과 레지스터는 바뀌지 않습니다. 먼저 좌표 쌍을 초기화하세요.
사용 예
__scroll_win_x_set_buffered(55);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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 값을 읽습니다.
사용 형식
u8 __scroll_win_y_get();반환값
추적 중인 현재 바이트 값(0~255)을 반환합니다. WX에는 하드웨어의 7픽셀 오프셋이 포함됩니다.
사용 조건과 제한
하드웨어 레지스터를 읽는 기능이 아닙니다. 아직 반영하지 않은 버퍼 값, 레지스터에 직접 쓴 값, 래스터 분할에서 임시로 적용한 값은 포함하지 않습니다.
사용 예
result[13]=__scroll_win_y_get();호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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 좌표만 즉시 설정합니다.
사용 형식
void __scroll_win_y_set(u8 wy);매개변수
wy화면 Y 픽셀 단위의 윈도우 상단 위치입니다. 범위는 0~255이고, 화면에 보이는 행은 0~143입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
대기하지 않습니다. 즉시 설정·가산 함수는 영향을 받는 보류 좌표 쌍을 현재값과 동기화하고 변경 플래그를 지웁니다. 한 축만 즉시 설정해도 같은 좌표 쌍의 다른 축에 있던 보류값이 취소됩니다.
사용 예
__scroll_win_y_set(16);result[11]=M_WY;호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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만 나중에 반영할 값으로 저장합니다.
사용 형식
void __scroll_win_y_set_buffered(u8 wy);매개변수
wy화면 Y 픽셀 단위의 윈도우 상단 위치입니다. 범위는 0~255이고, 화면에 보이는 행은 0~143입니다.
반환값
반환값은 없습니다. 변경되는 상태와 적용 시점은 이 항목에서 설명합니다.
사용 조건과 제한
다른 축의 보류값은 유지합니다. 반영 전까지 현재값과 레지스터는 바뀌지 않습니다. 먼저 좌표 쌍을 초기화하세요.
사용 예
__scroll_win_y_set_buffered(48);호출 부분을 보여 주는 코드입니다. 완전한 프로그램에 넣을 때는 명시된 초기화 및 버퍼 조건을 충족해야 합니다.
예상 결과
즉시값과 보류값의 차이, 부호 있는 덧셈의 래핑을 익힙니다. BG X는 반영 전 250, 반영 후 16이며 WX는 23에서 39로 바뀝니다. BG X에 −20을 더하면 252, WY에 −40을 더하면 248이 됩니다. 화면 예제에는 배경 사각형과 독립된 윈도우 테두리도 표시됩니다.


빌드 명령
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사용 예
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에서는 모든 도형이 검은색입니다.


빌드 명령
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
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개의 정보를 모두 갱신합니다. 전송이 끝난 뒤 호출한 코드로 돌아갑니다.
사용 형식
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의 활성화 상태는 보존합니다. 인터럽트 처리는 전송이 끝날 때까지 지연됩니다.
사용 예
// 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에서는 명암으로 표시합니다. 나머지 슬롯은 모두 숨깁니다. 에뮬레이터의 두 하드웨어 모드에서 실제 이미지 픽셀을 대조했으며, 실제 기기에서는 검증하지 않았습니다.


빌드 명령
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
// 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
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: kitaqgb kqhelp <KQxxxx>usage: kitaqgb symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]usage: kitaqgb src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]usage: kitaqgb romdiff <old.gb> <new.gb> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]usage: kitaqgb template [out.c] [--out=<file.c>] [--cgb] [--overwrite] [--no-build-script] [--no-readme]usage: kitaqgb conventions [--out=<file>|--stdout]usage: kitaqgb snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]usage: kitaqgb fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]usage: kitaqgb irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]usage: kitaqgb devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>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 | 명령어 | 형식 | 문법 예 |
|---|---|---|---|
| 00 | NOP | IMP | NOP |
| F3 | DI | IMP | DI |
| FB | EI | IMP | EI |
| 76 | HALT | IMP | HALT |
| 10 | STOP | IMM8 | STOP #1 |
| C9 | RET | IMP | RET |
| C0 | RET_NZ | IMP | RET_NZ |
| C8 | RET_Z | IMP | RET_Z |
| D0 | RET_NC | IMP | RET_NC |
| D8 | RET_C | IMP | RET_C |
| D9 | RETI | IMP | RETI |
| C3 | JP | ABS | JP 0xC000 |
| E9 | JP_HL | IMP | JP_HL |
| 18 | JR | REL | JR +target |
| 20 | JR_NZ | REL | JR_NZ +target |
| 28 | JR_Z | REL | JR_Z +target |
| 30 | JR_NC | REL | JR_NC +target |
| 38 | JR_C | REL | JR_C +target |
| CD | CALL | ABS | CALL 0xC000 |
| C4 | CALL_NZ | ABS | CALL_NZ 0xC000 |
| CC | CALL_Z | ABS | CALL_Z 0xC000 |
| D4 | CALL_NC | ABS | CALL_NC 0xC000 |
| DC | CALL_C | ABS | CALL_C 0xC000 |
| C2 | JP_NZ | ABS | JP_NZ 0xC000 |
| CA | JP_Z | ABS | JP_Z 0xC000 |
| D2 | JP_NC | ABS | JP_NC 0xC000 |
| DA | JP_C | ABS | JP_C 0xC000 |
| C7 | RST_00 | IMP | RST_00 |
| CF | RST_08 | IMP | RST_08 |
| D7 | RST_10 | IMP | RST_10 |
| DF | RST_18 | IMP | RST_18 |
| E7 | RST_20 | IMP | RST_20 |
| EF | RST_28 | IMP | RST_28 |
| F7 | RST_30 | IMP | RST_30 |
| FF | RST_38 | IMP | RST_38 |
| 3E | LD_A_IMM | IMM8 | LD_A_IMM #1 |
| 06 | LD_B_IMM | IMM8 | LD_B_IMM #1 |
| 0E | LD_C_IMM | IMM8 | LD_C_IMM #1 |
| 16 | LD_D_IMM | IMM8 | LD_D_IMM #1 |
| 1E | LD_E_IMM | IMM8 | LD_E_IMM #1 |
| 26 | LD_H_IMM | IMM8 | LD_H_IMM #1 |
| 2E | LD_L_IMM | IMM8 | LD_L_IMM #1 |
| 7F | LD_A_A | IMP | LD_A_A |
| 78 | LD_A_B | IMP | LD_A_B |
| 79 | LD_A_C | IMP | LD_A_C |
| 7A | LD_A_D | IMP | LD_A_D |
| 7B | LD_A_E | IMP | LD_A_E |
| 7C | LD_A_H | IMP | LD_A_H |
| 7D | LD_A_L | IMP | LD_A_L |
| 47 | LD_B_A | IMP | LD_B_A |
| 40 | LD_B_B | IMP | LD_B_B |
| 41 | LD_B_C | IMP | LD_B_C |
| 42 | LD_B_D | IMP | LD_B_D |
| 43 | LD_B_E | IMP | LD_B_E |
| 44 | LD_B_H | IMP | LD_B_H |
| 45 | LD_B_L | IMP | LD_B_L |
| 4F | LD_C_A | IMP | LD_C_A |
| 48 | LD_C_B | IMP | LD_C_B |
| 49 | LD_C_C | IMP | LD_C_C |
| 4A | LD_C_D | IMP | LD_C_D |
| 4B | LD_C_E | IMP | LD_C_E |
| 4C | LD_C_H | IMP | LD_C_H |
| 4D | LD_C_L | IMP | LD_C_L |
| 57 | LD_D_A | IMP | LD_D_A |
| 50 | LD_D_B | IMP | LD_D_B |
| 51 | LD_D_C | IMP | LD_D_C |
| 52 | LD_D_D | IMP | LD_D_D |
| 53 | LD_D_E | IMP | LD_D_E |
| 54 | LD_D_H | IMP | LD_D_H |
| 55 | LD_D_L | IMP | LD_D_L |
| 5F | LD_E_A | IMP | LD_E_A |
| 58 | LD_E_B | IMP | LD_E_B |
| 59 | LD_E_C | IMP | LD_E_C |
| 5A | LD_E_D | IMP | LD_E_D |
| 5B | LD_E_E | IMP | LD_E_E |
| 5C | LD_E_H | IMP | LD_E_H |
| 5D | LD_E_L | IMP | LD_E_L |
| 67 | LD_H_A | IMP | LD_H_A |
| 60 | LD_H_B | IMP | LD_H_B |
| 61 | LD_H_C | IMP | LD_H_C |
| 62 | LD_H_D | IMP | LD_H_D |
| 63 | LD_H_E | IMP | LD_H_E |
| 64 | LD_H_H | IMP | LD_H_H |
| 65 | LD_H_L | IMP | LD_H_L |
| 6F | LD_L_A | IMP | LD_L_A |
| 68 | LD_L_B | IMP | LD_L_B |
| 69 | LD_L_C | IMP | LD_L_C |
| 6A | LD_L_D | IMP | LD_L_D |
| 6B | LD_L_E | IMP | LD_L_E |
| 6C | LD_L_H | IMP | LD_L_H |
| 6D | LD_L_L | IMP | LD_L_L |
| EA | LD_MEM_A | ABS | LD_MEM_A 0xC000 |
| FA | LD_A_MEM | ABS | LD_A_MEM 0xC000 |
| E0 | LDH_MEM_A | LDH | LDH_MEM_A 0x40 |
| F0 | LDH_A_MEM | LDH | LDH_A_MEM 0x40 |
| E2 | LD_C_MEM_A | IMP | LD_C_MEM_A |
| F2 | LD_A_MEM_C | IMP | LD_A_MEM_C |
| 77 | LD_HL_A | IMP | LD_HL_A |
| 7E | LD_A_HL | IMP | LD_A_HL |
| 46 | LD_B_HL | IMP | LD_B_HL |
| 4E | LD_C_HL | IMP | LD_C_HL |
| 56 | LD_D_HL | IMP | LD_D_HL |
| 5E | LD_E_HL | IMP | LD_E_HL |
| 66 | LD_H_HL | IMP | LD_H_HL |
| 6E | LD_L_HL | IMP | LD_L_HL |
| 70 | LD_HL_B | IMP | LD_HL_B |
| 71 | LD_HL_C | IMP | LD_HL_C |
| 72 | LD_HL_D | IMP | LD_HL_D |
| 73 | LD_HL_E | IMP | LD_HL_E |
| 74 | LD_HL_H | IMP | LD_HL_H |
| 75 | LD_HL_L | IMP | LD_HL_L |
| 36 | LD_HL_REF_IMM | IMM8 | LD_HL_REF_IMM #1 |
| 12 | LD_DE_A | IMP | LD_DE_A |
| 1A | LD_A_DE | IMP | LD_A_DE |
| 02 | LD_BC_A | IMP | LD_BC_A |
| 0A | LD_A_BC | IMP | LD_A_BC |
| 22 | LDI_HL_A | IMP | LDI_HL_A |
| 2A | LDI_A_HL | IMP | LDI_A_HL |
| 32 | LDD_HL_A | IMP | LDD_HL_A |
| 3A | LDD_A_HL | IMP | LDD_A_HL |
| 01 | LD_BC_IMM | IMM16 | LD_BC_IMM #0xC000 |
| 11 | LD_DE_IMM | IMM16 | LD_DE_IMM #0xC000 |
| 21 | LD_HL_IMM | IMM16 | LD_HL_IMM #0xC000 |
| 31 | LD_SP_IMM | IMM16 | LD_SP_IMM #0xC000 |
| F9 | LD_SP_HL | IMP | LD_SP_HL |
| F8 | LD_HL_SP_IMM | REL | LD_HL_SP_IMM +target |
| E8 | ADD_SP_IMM | REL | ADD_SP_IMM +target |
| 08 | LD_MEM_SP | IMM16 | LD_MEM_SP #0xC000 |
| C5 | PUSH_BC | IMP | PUSH_BC |
| C1 | POP_BC | IMP | POP_BC |
| D5 | PUSH_DE | IMP | PUSH_DE |
| D1 | POP_DE | IMP | POP_DE |
| E5 | PUSH_HL | IMP | PUSH_HL |
| E1 | POP_HL | IMP | POP_HL |
| F5 | PUSH_AF | IMP | PUSH_AF |
| F1 | POP_AF | IMP | POP_AF |
| 3C | INC_A | IMP | INC_A |
| 04 | INC_B | IMP | INC_B |
| 0C | INC_C | IMP | INC_C |
| 14 | INC_D | IMP | INC_D |
| 1C | INC_E | IMP | INC_E |
| 24 | INC_H | IMP | INC_H |
| 2C | INC_L | IMP | INC_L |
| 34 | INC_HL_REF | IMP | INC_HL_REF |
| 3D | DEC_A | IMP | DEC_A |
| 05 | DEC_B | IMP | DEC_B |
| 0D | DEC_C | IMP | DEC_C |
| 15 | DEC_D | IMP | DEC_D |
| 1D | DEC_E | IMP | DEC_E |
| 25 | DEC_H | IMP | DEC_H |
| 2D | DEC_L | IMP | DEC_L |
| 35 | DEC_HL_REF | IMP | DEC_HL_REF |
| 87 | ADD_A | IMP | ADD_A |
| 80 | ADD_B | IMP | ADD_B |
| 81 | ADD_C | IMP | ADD_C |
| 82 | ADD_D | IMP | ADD_D |
| 83 | ADD_E | IMP | ADD_E |
| 84 | ADD_H | IMP | ADD_H |
| 85 | ADD_L | IMP | ADD_L |
| 86 | ADD_HL_REF | IMP | ADD_HL_REF |
| C6 | ADD_A_IMM | IMM8 | ADD_A_IMM #1 |
| 8F | ADC_A | IMP | ADC_A |
| 88 | ADC_B | IMP | ADC_B |
| 89 | ADC_C | IMP | ADC_C |
| 8A | ADC_D | IMP | ADC_D |
| 8B | ADC_E | IMP | ADC_E |
| 8C | ADC_H | IMP | ADC_H |
| 8D | ADC_L | IMP | ADC_L |
| 8E | ADC_HL_REF | IMP | ADC_HL_REF |
| CE | ADC_IMM | IMM8 | ADC_IMM #1 |
| 97 | SUB_A | IMP | SUB_A |
| 90 | SUB_B | IMP | SUB_B |
| 91 | SUB_C | IMP | SUB_C |
| 92 | SUB_D | IMP | SUB_D |
| 93 | SUB_E | IMP | SUB_E |
| 94 | SUB_H | IMP | SUB_H |
| 95 | SUB_L | IMP | SUB_L |
| 96 | SUB_HL_REF | IMP | SUB_HL_REF |
| D6 | SUB_IMM | IMM8 | SUB_IMM #1 |
| 9F | SBC_A | IMP | SBC_A |
| 98 | SBC_B | IMP | SBC_B |
| 99 | SBC_C | IMP | SBC_C |
| 9A | SBC_D | IMP | SBC_D |
| 9B | SBC_E | IMP | SBC_E |
| 9C | SBC_H | IMP | SBC_H |
| 9D | SBC_L | IMP | SBC_L |
| 9E | SBC_HL_REF | IMP | SBC_HL_REF |
| DE | SBC_IMM | IMM8 | SBC_IMM #1 |
| A7 | AND_A | IMP | AND_A |
| A0 | AND_B | IMP | AND_B |
| A1 | AND_C | IMP | AND_C |
| A2 | AND_D | IMP | AND_D |
| A3 | AND_E | IMP | AND_E |
| A4 | AND_H | IMP | AND_H |
| A5 | AND_L | IMP | AND_L |
| A6 | AND_HL_REF | IMP | AND_HL_REF |
| E6 | AND_IMM | IMM8 | AND_IMM #1 |
| B7 | OR_A | IMP | OR_A |
| B0 | OR_B | IMP | OR_B |
| B1 | OR_C | IMP | OR_C |
| B2 | OR_D | IMP | OR_D |
| B3 | OR_E | IMP | OR_E |
| B4 | OR_H | IMP | OR_H |
| B5 | OR_L | IMP | OR_L |
| B6 | OR_HL_REF | IMP | OR_HL_REF |
| F6 | OR_IMM | IMM8 | OR_IMM #1 |
| AF | XOR_A | IMP | XOR_A |
| A8 | XOR_B | IMP | XOR_B |
| A9 | XOR_C | IMP | XOR_C |
| AA | XOR_D | IMP | XOR_D |
| AB | XOR_E | IMP | XOR_E |
| AC | XOR_H | IMP | XOR_H |
| AD | XOR_L | IMP | XOR_L |
| AE | XOR_HL_REF | IMP | XOR_HL_REF |
| EE | XOR_IMM | IMM8 | XOR_IMM #1 |
| BF | CP_A | IMP | CP_A |
| B8 | CP_B | IMP | CP_B |
| B9 | CP_C | IMP | CP_C |
| BA | CP_D | IMP | CP_D |
| BB | CP_E | IMP | CP_E |
| BC | CP_H | IMP | CP_H |
| BD | CP_L | IMP | CP_L |
| BE | CP_HL_REF | IMP | CP_HL_REF |
| FE | CP_IMM | IMM8 | CP_IMM #1 |
| 03 | INC_BC | IMP | INC_BC |
| 0B | DEC_BC | IMP | DEC_BC |
| 13 | INC_DE | IMP | INC_DE |
| 1B | DEC_DE | IMP | DEC_DE |
| 23 | INC_HL | IMP | INC_HL |
| 2B | DEC_HL | IMP | DEC_HL |
| 33 | INC_SP | IMP | INC_SP |
| 3B | DEC_SP | IMP | DEC_SP |
| 09 | ADD_HL_BC | IMP | ADD_HL_BC |
| 19 | ADD_HL_DE | IMP | ADD_HL_DE |
| 29 | ADD_HL_HL | IMP | ADD_HL_HL |
| 39 | ADD_HL_SP | IMP | ADD_HL_SP |
| 07 | RLCA | IMP | RLCA |
| 17 | RLA | IMP | RLA |
| 0F | RRCA | IMP | RRCA |
| 1F | RRA | IMP | RRA |
| CB | PREFIX_CB | IMP | PREFIX_CB |
| 2F | CPL | IMP | CPL |
| 3F | CCF | IMP | CCF |
| 37 | SCF | IMP | SCF |
| 27 | DAA | IMP | DAA |