KITAQ DEVELOPMENT SYSTEM
USER'S MANUAL 2026.09

KITAQ SERIES • REFERENCE EDITION

KITAQGB

Game Boy C programming

From your first line to execution, observation and retesting.
Source snapshot: September 14, 2026 · English edition: September 14, 2026

Native Rust compiler and helper tools

Build the compiler and all helper tools on Windows, Linux, macOS ARM or macOS Intel with Rust 1.85 or later. The native executables run without .NET; production asset tools also run without Python or Pillow.

Windows executables are at the repository root. Linux and macOS executables are under bin/ in the platform folders listed below. Keep lib/ and license notices with the tools. On Linux/macOS, run chmod +x on the downloaded executables and add their folder to PATH, or invoke them by their full path.

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

kitaqgb · Native Rust compiler and helper tools · GitHub Actions

Native builds and executable checks passed on Windows, Linux, macOS ARM and macOS Intel. KITAQGB passed 48 tests and 395 helper checks per platform; KITAQFC passed 55 tests and 401 helper checks. Rust 1.85 was also tested. PUBLIC_DISTRIBUTION.json records the installed binary hashes and validation provenance. The public GitHub Actions workflows rebuild and test this source independently.

Frozen reference outputs test ROM bytes, diagnostics and helper formats. Earlier C# emulator evidence remains historical evidence with its original source fingerprints. It does not automatically prove every Rust API, real hardware or complete FDS BIOS/game startup. The unavailable original PNG conversion script was reconstructed from its specification; byte parity with that missing script cannot be claimed.

--attach is accepted for compatibility and does not attach a managed debugger to the native executable. Use a native debugger for host debugging.

KITAQGB · SHA-256 · KITAQFC · SHA-256

Portable sample builds

PowerShell build scripts use the root Windows executable. For Linux/macOS use the native compiler command with the same C inputs and options, or PowerShell 7. Graphics CHR and C source are separate inputs; font.chr keeps the original sample font.

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

1. Meet KITAQGB

KITAQGB is a GB/CGB C-family compiler developed from Zachtronics' NORCAL.

NORCAL takes its name from Northern California. Inspired by this geographical naming, the author named KITAQGB after Kitakyushu, the city where they were born and raised. KITAQ + GB combines Game Boy with KITAQ, the nickname of Kitakyushu in Fukuoka Prefecture, Japan: 北九 (キタキュー, Kitakyū). Pronounce KITAQ as kee-tah-KYOO, IPA /ˌkiːtɑːˈkjuː/. The final Q sounds like the English letter Q. Read KITAQGB as kee-tah-KYOO jee bee, pronouncing G and B separately.

The name KITAQGB has two meanings. Kernel-Informed Toolchain for AI-Quality Game Boy Development expresses the goal of a toolchain that understands its target machine and supports both human programmers and generative AI.

The other meaning is Kids' Imagination Transformed into Actual Quests in Game Boy Forests: a tool that turns children’s imagination into real adventures in the forests of Game Boy. It expresses the creative wish to turn small ideas, sketches and AI-assisted prototypes into adventures people can actually play.

It reads C files, generates CPU instructions and packages them into a ROM. Its language, libraries and calling conventions differ from those of a desktop C compiler.

The GB has an 8-bit CPU and limited memory. Most screen graphics use 8-by-8-pixel tiles. Sprites are small independently positioned images. Text also needs tile graphics: a built-in universal text display is not assumed. These examples use the author's ascii.c glyphs, rearranged by ASCII code for GB without changing their bits. The FC edition converts the same shapes to NES CHR layout.

2. Requirements and compiler build

Build the compiler and all helper tools on Windows, Linux, macOS ARM or macOS Intel with Rust 1.85 or later. The native executables run without .NET; production asset tools also run without Python or Pillow.

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

Windows executables are at the repository root. Linux and macOS executables are under bin/ in the platform folders listed below. Keep lib/ and license notices with the tools. On Linux/macOS, run chmod +x on the downloaded executables and add their folder to PATH, or invoke them by their full path.

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

PowerShell build scripts use the root Windows executable. For Linux/macOS use the native compiler command with the same C inputs and options, or PowerShell 7. Graphics CHR and C source are separate inputs; font.chr keeps the original sample font.

3. Your first program

The supplied samples/gb_hello.c uses screen and text helpers from gb_common.h. Keep that header and the font files together. #include reads declarations or definitions from another file.

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

The example's target is HELLO WORLD and 042 on screen. Names beginning with m_ are teaching helpers defined in the common sample header, not standard KITAQGB commands. The compiler intrinsics include operations such as __wait_vblank and __vram_copy.

4. Basic syntax and types

End statements with ; and group statements with { }. // begins a line comment; /* ... */ is a block comment. Names are case-sensitive. Use void main() as the entry point. In this GB version, void main(void) produces a syntax error, so do not copy the FC entry-point declaration unchanged.

TypeMeaning and range
u8Unsigned 8-bit integer, 0 to 255
s8Signed 8-bit integer, -128 to 127
u16Unsigned 16-bit integer, 0 to 65535
s16Signed 16-bit integer, -32768 to 32767
voidNo return value
T*Pointer to data of type T

Start with these short type names. Do not assume desktop definitions of int, long, float, double or standard headers. This version treats char as unsigned 8-bit data; explicitly use s8 or s16 when signed arithmetic is needed.

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

(u16) is a cast. Assigning an already-overflowed small value to a larger variable does not recover lost bits: widen operands before the calculation. Use the fixed-point library for fractional movement.

5. Expressions and operators

GroupOperatorsExample or meaning
Arithmetic+ - * / %n / 10 is the integer quotient; n % 10 is the remainder
Comparison== != < <= > >=lives == 0 tests equality
Logic! && / ||Negation, both conditions, either condition
Bits& / | / ^ ~ << >>Button masks and other bit sets
Assignment= += -= and related formsx += 1 updates a value
Increment++ --i++ increments by one
Selectioncondition ? A : BChoose a value according to a condition
Pointers&variable / *pObtain an address or access its target

Distinguish = from ==. Use parentheses to make complex expressions clear, and avoid crowding calls and side effects into a single statement. Avoid division by zero and out-of-bounds array accesses. sizeof gives a size in bytes; offsetof gives a structure member's offset.

Expression results and evaluation

The comparison operators ==, !=, <, <=, >, >= and logical operators !, &&, || return 0 for false and 1 for true. You can store the result in a u16, pass or return it, or use it in arithmetic. For example, score = 500 + (lives != 0); produces 501 when a life remains and 500 otherwise.

&& skips its right operand when the left operand is zero. || skips its right operand when the left operand is nonzero. In pointer != 0 && pointer->active != 0, a null pointer prevents the member access. Both bytes of a 16-bit value participate in the truth test, so 256 is true. Bitwise & and | do not short-circuit.

++value returns the updated value; value++ returns the original value. These operators also accept array elements, dereferenced pointers and structure members. buffer[index()]++ calls index() once. For a u16 *p, p++ advances two bytes to the next element, while (*p)++ increments the pointed-to value.

sizeof(array) gives the entire array's size in bytes; sizeof(pointer) is 2. For u16 values[9];, sizeof(values) is 18. This includes ROM arrays, local arrays and array members. sizeof(function()) inspects the return type without calling the function.

A function declaration and definition must agree on parameter types and order. Their parameter names may differ; the body uses the definition's names. For example, u8 next(u8 input); can be defined as u8 next(u8 value) { return (u8)(value + 1); }. Parameters and local variables hide globals with the same name.

Selecting arrays or strings with ?: produces a pointer to the selected element type. You can pass it directly, as in show(ready ? "READY" : "WAIT");. For u16 arrays a and b, (ready ? a : b) + 1 advances two bytes to the second element of the selected array. This does not copy the array.

condition ? yes : no evaluates its condition and then only the selected arm. The condition and arms may contain shifts with variable counts. For example, on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; selects four or two for the chosen bit. Function calls in a right operand skipped by && or || are also skipped. A continue in a for loop runs the update expression once before reevaluating the condition; in while and do ... while, it advances to the condition.

6. Branches and loops

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

break leaves a loop or switch, continue starts the next iteration, and return leaves a function. Use the explicit fallthrough; statement when deliberately continuing from one switch case into the next; implicit fall-through is diagnosed. See the complete gb_control.c example.

7. Functions, arrays and structures

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

Array indexes begin at zero: a four-element array has indexes 0 through 3. player.x selects a member; pointer->x accesses a member through a pointer. Structures, unions and enums are parsed, but layout depends on types and __packed / __aligned attributes. Check sizeof before sharing data with hardware or binary formats.

The default Legacy ABI places arguments and local storage at fixed locations. Do not assume desktop-style recursion or interrupt reentrancy. __stackcall and --abi=stack are advanced calling-convention choices. When combining conventions, inspect ABI reports and verify execution. Function-pointer callback calls are unsupported with --abi=stack. Build the complete system and scene callback examples with the default Legacy ABI, as shown in their build commands.

8. Multiple files and the preprocessor

Put types, constants and declarations in headers and function bodies in .c files. Use #pragma once or include guards to prevent repeated inclusion. Conditional compilation supports #define, #undef, #if, #ifdef, #ifndef, #elif, #else and #endif.

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

-I adds a header search directory. Including a library header does not link its implementation: list the required .c files in the build command. Select the units you need; indiscriminately adding all library sources can duplicate register definitions or interrupt handlers.

9. ROM, memory and banks

ROM stores code and constants, WRAM stores variables, VRAM stores graphics and OAM stores sprite descriptions. Banking changes which physical memory appears at a CPU address. A 16-bit pointer alone cannot identify data in another bank.

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

__prg_rom places data in ROM. Attributes such as __location(0xFF40) choose a fixed address, while __wram / __hram select memory regions. When using #pragma bank or #pragma fixed_bank, inspect the map and ensure that code and data used by interrupts remain accessible.

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

--cgb=dmg declares DMG-oriented software, --cgb=cgb declares dual-mode software, and --cgb=cgb_only declares CGB-only software. Dual-mode games must detect hardware before using CGB-only features. A header flag does not implement dual-mode behavior inside your game.

10. Safe graphics updates

VBlank is the gap between display frames. Unscheduled VRAM and OAM writes can cause corruption or lost updates. Load initial assets while the display is off; use safe intrinsics or the VRAM queue for regular updates. Functions marked _unsafe or _fast require the caller to provide a safe transfer interval.

Align the OAM DMA buffer to a 256-byte boundary. On GB, __oam_dma takes the source address. The FC intrinsic with the same name takes no arguments: keep these APIs separate.

11. Build commands and outputs

-o selects the output, -O0 / -O1 select optimization, and --profile=dev|release|test chooses a group of settings. --no-disasm suppresses disassembly output. --debug-out=... and --trace-out=... choose investigation output locations. Use the amount of output appropriate for a fast build or a detailed investigation.

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

.map records names and placement; .funcsizes.txt records function sizes; .dbg2.json / .source_map.txt connect execution positions to source; .build_report.json summarizes the build. Explicitly producing --emit-ai-metadata JSON makes the path into SARAKURA easier to follow.

12. Read errors from the beginning

Read the filename, line and KQ diagnostic number at the first error. Later errors may be consequences of the first syntax mistake. For an undefined symbol, check its declaration, implementation and inclusion in the build. For ROM overflow, inspect asset sizes, function sizes and bank placement.

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

13. Inline assembly

__asm { ... } accepts KITAQGB's instruction names. It is not a promise to accept arbitrary source written for another GB assembler. Internal spellings include names such as LD_A_IMM. Understand arguments, return values, preserved registers and stack behavior before using inline assembly. The appendix lists instruction spellings and operand forms.

c
void nop_example() {
    __asm { NOP }
}

14. Reference example: developing a game with AI

Fill in the requirements, then give the complete prompt to your AI assistant. It covers implementation, emulator testing, SARAKURA analysis and retesting.

· Download Markdown

Fill in the requirements and give this entire document to the AI assistant. Commands assume sibling repositories named kitaqgb, kitaqfc, kokura, kurosaki, sarakura and kitaq-docs, with your game-gb or game-fc project beside them. Run commands from their parent directory; adapt paths to the actual environment.

Requirements

  • Game title: <fill in>
  • Genre and core gameplay: <fill in>
  • Controls, success and failure conditions: <fill in>
  • Required screens, stages, enemies and items: <fill in>
  • Visual style, music and sound effects: <fill in; identify any supplied assets>
  • Saving, communication, peripherals and other requirements: <fill in, or none>
  • Project directory: <fill in>
  • Redistribution requirements: <for example, original code and assets suitable for MIT publication>
  • Target: <original Game Boy / dual GB–CGB support / CGB only>
  • Performance: <for example, 60 gameplay updates per second in normal play; define acceptable behavior in demanding scenes>

Task

Implement the game with KITAQGB and its libraries. Use KOKURA for execution and debugging, and SARAKURA to organize diagnostics and compare results before and after a fix.

Repeat this cycle until the acceptance criteria are met: make the specification concrete → implement a small change → build → apply inputs and observe → investigate the cause → fix → retest under the same conditions. A plan, a code listing or a successful compilation is not completion.

Establish the environment and acceptance criteria

  1. Read workspace instructions, tool READMEs, HTML manuals, and the headers and implementations of the libraries you will use. Record executable paths and versions or SHA-256 hashes. Verify commands against actual --help output and APIs against source.
  2. Define measurable acceptance criteria for inputs, images, audio, progression and update frequency. Examples: pressing and releasing START begins the game; a collision removes one life; pausing silences the intended audio and resuming restores playback.
  3. Ask only about material ambiguities. Make ordinary reversible implementation decisions autonomously. Do not weaken requirements or acceptance criteria.
  4. First run a small supplied sample through the compiler, emulator and SARAKURA. This checks the tool connection, not completion of the requested game.

Implement a small playable slice

  • Use the KITAQGB C dialect and void main(). Do not assume desktop C or GBDK APIs are available. Include required .c implementation units, not just declarations; check initialization order, units, signedness, ranges, buffer lifetime and ROM banking.
  • Plan VRAM/OAM updates, VBlank, interrupts, stack, ROM/WRAM banks and tile/sprite limits. Transfer-queue capacity and free space are different from physical VRAM capacity and free space.
  • A DMG game must not depend on CGB-only features. Test both hardware modes for a dual-mode game.
  • Use the supplied original ascii.c font for letters, digits and symbols, and verify character-to-tile mapping.
  • First connect boot, title, a controllable player, success or failure, and restart. Then expand the game.
  • Keep editable graphics, music and sound-effect sources and their generation steps. Verify that the build actually consumes their exports.
  • Write source comments in English and progress reports in English. Keep SARAKURA’s standard reports in English.

Connect each build to its execution

Use a separate output directory for each iteration, such as out/iter-001. Record commands, exit codes, and hashes of source, assets, tools, ROM and metadata. Never run an older ROM after a failed build. Maps, source maps and debug information must come from the same build as the ROM.

The following is a basic DMG check. Supply main.c and every required library implementation unit, and adapt the options and input sequence to the game.

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

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

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

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

--hardware dmg selects the original GB. Match the ROM header and emulator hardware setting when testing CGB or dual support. The input sequence presses START once between released intervals. A 300-frame run does not test the whole game.

Check images, audio, state and performance

  • Save input scenarios with distinct presses, holds and releases. Exercise every specified path: boot, start, movement, actions, collisions, scrolling, stage changes, game over, restart, pause and saving or communication where applicable.
  • Preserve PNGs at relevant frames, input data, execution reports, diagnostic JSONL, WAVs and any necessary state or memory observations. Check the reached frame count and stop reason. Actually open the images; one screenshot cannot establish motion or input response. Compare counters, positions and state changes with expected values. Check screen edges, tile/attribute boundaries and crowded sprite scenes.
  • Check music, effects, simultaneous playback, dropouts, pause and resume. A generated WAV alone does not establish correct sound. If listening is unavailable, distinguish the waveform/numerical checks performed from unverified audible qualities.
  • Measure heavy scenes, target CPU/update workload and transfers; on FC, include NMI work. Host emulator throughput is not game update frequency or proof of hardware speed. Continuing with --allow-unimplemented, where available, does not demonstrate support for the missing feature.

Analyze, repair and retest

  • Feed SARAKURA the build metadata for the tested ROM and diagnostic JSONL from the tested execution. A CPU trace or ordinary run report is not a substitute. --frames specifies analysis conditions; SARAKURA does not execute the ROM or automatically edit the source.
  • Read report.html, ai_diagnostics.json, repair_prompt.md and retest_plan.json. Compare diagnoses with reproduction steps, images, audio and source. Distinguish inferred source locations or causes from verified facts, and normal waiting loops from hangs. Assess warnings individually and record unsupported events or analysis limits. Do not hide warnings with filters or shorten tests to obtain a passing result.
  • Reduce failures to minimal reproductions, fix their causes and rebuild. If the compiler or emulator is responsible, isolate its defect from game code and add regression verification for the tool fix.
  • Retest with matching input, random seed, hardware/video mode, mapper, observed frames and diagnostic settings. Use new metadata for each new ROM; do not blindly reuse save states after code or RAM layout changes.
powershell
& '.\sarakura\sarakura.exe' baseline-delta `
  --baseline '.\game-gb\out\iter-001\analysis' `
  --current '.\game-gb\out\iter-002\analysis' `
  --out '.\game-gb\out\delta.json' --markdown '.\game-gb\out\delta.md' `
  --fail-on-new error --fail-on-regression error --enforce

Use diagnostic differences alongside gameplay, graphics and audio acceptance checks. If the same failure repeats, revisit the evidence and hypothesis instead of continuing arbitrary changes.

Completion and deliverables

Rerun all required scenarios against the final ROM built from the delivered source and settings. Invincibility, automatic test input or another mapper alone does not verify normal play in the final build. Provide a requirement-to-test table, reasons for remaining warnings and explicit unverified or unsupported items. State “not tested on physical hardware” when applicable.

Deliver source, tool/library identities, editable assets, reproducible build and test scripts, the ROM, final verification evidence, and a README covering setup, controls and known limits. Include replay data and a test harness where needed. Publish or send files externally only within explicitly authorized scope. Delete unnecessary intermediate builds and temporary traces after verification, preserving source, assets, final deliverables and needed regression evidence.

If environment or permission constraints prevent a required check, report the exact reproduction steps and required action. Do not mark the work complete.

Source locations

The compiler sources and project file are in the repository’s same-named subdirectory. The executable is at the repository root, and the libraries are in lib/. See the directory layout for project paths and build requirements.

Complete sample programs

Each program, the shared headers, the supplied ascii.c and its converted assets are included. Names beginning with m_ are helpers written for these manuals. Read each build command together with its expected result and verification record. Run commands from the parent of the sibling repository folders.

Batch build script / Results and screenshots

Display the supplied character set gb_font.c

Purpose: See which letters, digits and punctuation marks the supplied original font contains and how to draw them.

How it works: m_init prepares the display and font. m_text draws each row; m_put draws the double quote using character code 34.

Controls and expected result: Look for 26 uppercase letters, 26 lowercase letters, 10 digits and 30 punctuation marks. Check that every glyph is legible. No input is required.

Display the supplied character set: Look for 26 uppercase letters, 26 lowercase letters, 10 digits and 30 punctuation marks. Check that every glyph is legible. No input is required.
Display example: Look for 26 uppercase letters, 26 lowercase letters, 10 digits and 30 punctuation marks. Check that every glyph is legible. No input is required.

Download source / Verification status

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


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

Purpose: Learn the basic sequence for starting a ROM and drawing text and a number on the background.

How it works: After m_init, m_text draws the greeting and m_number formats 42. The final loop waits for frames while leaving the screen visible.

Controls and expected result: The screen shows HELLO WORLD and 042. The leading zero is three-digit decimal formatting.

Your first text display: The screen shows HELLO WORLD and 042. The leading zero is three-digit decimal formatting.
Display example: The screen shows HELLO WORLD and 042. The leading zero is three-digit decimal formatting.

Download source / Verification status

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


// Initialize the display/font, write HELLO WORLD and a three-digit 042,
// then wait for frames so the result remains visible.
void main() {
    m_init();
    m_text(2,3,"HELLO");
    m_text(2,5,"HELLO WORLD");
    m_number(42);
    while (1) { m_wait();  }
}
Arithmetic with a wider intermediate gb_arithmetic.c

Purpose: Learn to keep an intermediate result that does not fit in eight bits.

How it works: (u16)200 + 100 produces 300. The program divides it by 10, then converts the result to u8 for display. Notice that the widening cast precedes the addition.

Controls and expected result: 030 represents the calculated result, 30.

Arithmetic with a wider intermediate: 030 represents the calculated result, 30.
Display example: 030 represents the calculated result, 30.

Download source / Verification status

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


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

Purpose: Try for, continue, while, do/while and switch in a small calculation.

How it works: Adding 0 through 7 while skipping 3 gives 25. The while loop advances to 30, do/while advances to 32, and switch selects case 32 to assign 42.

Controls and expected result: 042 is the final result of these loops and branches.

Branches and loops: 042 is the final result of these loops and branches.
Display example: 042 is the final result of these loops and branches.

Download source / Verification status

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


// Exercise loop control and a final branch, then display the selected value 42.
void main() {
    m_init();
    m_text(2,3,"CONTROL");
    u8 i; u8 sum; sum=0;
    for (i=0;i<8;i++) { if (i==3) continue; sum=(u8)(sum+i); }
    while (sum<30) { sum++; }
    do { sum++; } while (sum<32);
    switch (sum) { case 32: sum=42; break; default: sum=0; break; }
    m_number(sum);
    while (1) { m_wait();  }
}
Functions, arrays, structures and pointers gb_aggregate.c

Purpose: Learn to group values and access them through functions and pointers.

How it works: The program copies a Point containing (10,20), then writes 1 through 4 into an array through a pointer. It adds the function result 30 to array values 1, 2 and 4 and the constant 5.

Controls and expected result: The sum is displayed as 042. Follow the structure copy, the ptr[i] writes and the function return value.

Functions, arrays, structures and pointers: The sum is displayed as 042. Follow the structure copy, the ptr[i] writes and the function return value.
Display example: The sum is displayed as 042. Follow the structure copy, the ptr[i] writes and the function return value.

Download source / Verification status

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

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

Purpose: Pack flags into a byte and copy a buffer into separate storage.

How it works: __memset clears four bytes. The program sets bit 3, toggles bit 1, then copies all four bytes with __memcpy.

Controls and expected result: The first destination byte is displayed as 010: bit 3 contributes 8 and bit 1 contributes 2.

Bit flags and buffer copying: The first destination byte is displayed as 010: bit 3 contributes 8 and bit 1 contributes 2.
Display example: The first destination byte is displayed as 010: bit 3 contributes 8 and bit 1 contributes 2.

Download source / Verification status

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

// Clear the source bytes, set bits 3 and 1, copy the buffer, and display 8 + 2 = 10.
void main() {
    m_init();
    m_text(2,3,"BITS MEMORY");
    u8 data[4]; u8 copy[4];
    __memset(data,0,4); __bit_set(data,3); __bit_toggle(data,1);
    __memcpy(copy,data,4); m_number(copy[0]);
    while (1) { m_wait();  }
}
Count new button presses gb_input.c

Purpose: Distinguish a newly pressed button from one that remains held.

How it works: Each frame calls input_update. The counter advances only when input_pressed(BTN_A) is true.

Controls and expected result: The initial count is 000. Press A to get 001; holding it does not add more. Release and press again for 002. The byte counter wraps from 255 to 000.

Count new button presses: The initial count is 000. Press A to get 001; holding it does not add more. Release and press again for 002. The byte counter wraps from 255 to 000.
Display example: The initial count is 000. Press A to get 001; holding it does not add more. Release and press again for 002. The byte counter wraps from 255 to 000.

Download source / Verification status

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

Purpose: Learn integer-to-Q8.8 conversion, multiplication and conversion back to an integer.

How it works: fix_from_int converts 3 and 4 to Q8.8. fix_mul multiplies them, and fix_to_int converts the product back.

Controls and expected result: 012 means 3 × 4 = 12. It is the converted integer, not the internal value scaled by 256.

Multiply Q8.8 fixed-point values: 012 means 3 × 4 = 12. It is the converted integer, not the internal value scaled by 256.
Display example: 012 means 3 × 4 = 12. It is the converted integer, not the internal value scaled by 256.

Download source / Verification status

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

// Convert 3 and 4 to Q8.8, multiply them, convert the result back to an
// integer, and display 12.
void main() {
    m_init();
    m_text(2,3,"FIXED");
    fix8 a; fix8 b; a=fix_from_int(3); b=fix_from_int(4);
    m_number((u8)fix_to_int(fix_mul(a,b)));
    while (1) { m_wait();  }
}
Allocate from a fixed object pool gb_entity.c

Purpose: Create an object from a fixed set of slots without dynamic allocation.

How it works: entity_init resets the pool. entity_create creates type 1 at (20,30); the program checks for the failure ID 0xFF before setting vx to 2.

Controls and expected result: 001 is the active object count, not an ID or coordinate. This example does not update or draw the object, so no moving character appears.

Allocate from a fixed object pool: 001 is the active object count, not an ID or coordinate. This example does not update or draw the object, so no moving character appears.
Display example: 001 is the active object count, not an ID or coordinate. This example does not update or draw the object, so no moving character appears.

Download source / Verification status

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

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

Purpose: Keep a named game value in a diagnostic buffer separately from the screen display.

How it works: The program initializes debugging, sets frame tag 1 and records the value 42 under the name HP.

Controls and expected result: 001 is the number of trace entries. HP=42 is stored in the diagnostic buffer; the screen alone does not show or verify that payload.

Record a diagnostic value in RAM: 001 is the number of trace entries. HP=42 is stored in the diagnostic buffer; the screen alone does not show or verify that payload.
Display example: 001 is the number of trace entries. HP=42 is stored in the diagnostic buffer; the screen alone does not show or verify that payload.

Download source / Verification status

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

// Record HP = 42 with frame tag 1 and display the number of trace entries.
// The trace buffer contains the value; the screen displays the count, not HP.
void main() {
    m_init();
    m_text(2,3,"DEBUG");
    debug_init(); debug_set_frame(1); debug_trace_u8("HP",42);
    m_number(debug_get_trace_count());
    while (1) { m_wait();  }
}
Store a position history gb_chain.c

Purpose: Learn how to store a trail of positions in a ring buffer.

How it works: chain_init attaches an array with room for four points. chain_push_head then records (20,30).

Controls and expected result: 042 is a fixed completion marker, not a coordinate or count. Inspect trail and points to check the stored history. This program does not draw a following body.

Store a position history: 042 is a fixed completion marker, not a coordinate or count. Inspect trail and points to check the stored history. This program does not draw a following body.
Display example: 042 is a fixed completion marker, not a coordinate or count. Inspect trail and points to check the stored history. This program does not draw a following body.

Download source / Verification status

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

Purpose: See how counted frame waits affect the library frame counter.

How it works: After system_init, the program calls system_wait_vblank twice and displays system_get_frame8.

Controls and expected result: The result is 002. The idle loop uses the manual helper m_wait, so the displayed value does not keep increasing.

Read the library frame counter: The result is 002. The idle loop uses the manual helper m_wait, so the displayed value does not keep increasing.
Display example: The result is 002. The idle loop uses the manual helper m_wait, so the displayed value does not keep increasing.

Download source / Verification status

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

// Reset the library frame count, perform two counted waits, and display 2.
// Subsequent m_wait calls leave that library counter unchanged.
void main() {
    m_init();
    m_text(2,3,"SYSTEM");
    system_init(); system_wait_vblank(); system_wait_vblank();
    m_number(system_get_frame8());
    while (1) { m_wait();  }
}
Select the current scene ID gb_scene.c

Purpose: Learn to select an ID that can distinguish scenes such as a title screen and gameplay.

How it works: The program registers two zero-initialized SceneDef entries and selects ID 1 with scene_change.

Controls and expected result: 001 is the current ID returned by scene_get_current. No scene callbacks or transition drawing are registered in this example.

Select the current scene ID: 001 is the current ID returned by scene_get_current. No scene callbacks or transition drawing are registered in this example.
Display example: 001 is the current ID returned by scene_get_current. No scene callbacks or transition drawing are registered in this example.

Download source / Verification status

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

Purpose: Subtract the camera position to find where a world position belongs on screen.

How it works: Camera_Set receives Q8.8 value 2560, or X=10 pixels. Camera_WorldToScreenX then converts world X=52.

Controls and expected result: 052 minus 010 gives the displayed 042. This is a coordinate calculation; it does not scroll a background.

Convert world coordinates to screen coordinates: 052 minus 010 gives the displayed 042. This is a coordinate calculation; it does not scroll a background.
Display example: 052 minus 010 gives the displayed 042. This is a coordinate calculation; it does not scroll a background.

Download source / Verification status

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

Purpose: Try a compact count/value representation of repeated bytes.

How it works: The packed bytes {3,42,0} mean three copies of 42 followed by a zero-count terminator. rle_decode writes into a three-byte destination.

Controls and expected result: The last byte, out[2], is displayed as 042. All three decoded bytes are expected to contain 42.

Decode RLE into RAM: The last byte, out[2], is displayed as 042. All three decoded bytes are expected to contain 42.
Display example: The last byte, out[2], is displayed as 042. All three decoded bytes are expected to contain 42.

Download source / Verification status

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

Purpose: Learn how grid coordinates map to storage for a board game.

How it works: A 16-byte array backs a 4 × 4 board. The program clears it, writes 42 at cell (2,1), then reads the same cell.

Controls and expected result: 042 is the value read back. The program does not draw the 4 × 4 board itself.

Read and write a board cell: 042 is the value read back. The program does not draw the 4 × 4 board itself.
Display example: 042 is the value read back. The program does not draw the 4 × 4 board itself.

Download source / Verification status

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

Purpose: Initialize a world and box body, then update position from velocity.

How it works: The body starts at (40,40), with velocity (2,0) and zero gravity. The program calls kq2d_step once.

Controls and expected result: 042 is the resulting X coordinate, measured in integer pixels in this example. No box shape or continuous animation is drawn.

Advance a box by one physics step: 042 is the resulting X coordinate, measured in integer pixels in this example. No box shape or continuous animation is drawn.
Display example: 042 is the resulting X coordinate, measured in integer pixels in this example. No box shape or continuous animation is drawn.

Download source / Verification status

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

Purpose: Learn the arguments for copying ROM data into RAM.

How it works: far_data_read copies the two bytes 7 and 42 into copied, using bank argument 0 and a length of 2.

Controls and expected result: The second destination byte is displayed as 042. This example reads fixed ROM; it does not demonstrate transfer between different switchable banks.

Read ROM data with a bank argument: The second destination byte is displayed as 042. This example reads fixed ROM; it does not demonstrate transfer between different switchable banks.
Display example: The second destination byte is displayed as 042. This example reads fixed ROM; it does not demonstrate transfer between different switchable banks.

Download source / Verification status

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

Purpose: Register an asset location and length, then retrieve the bytes by ID.

How it works: Asset 0 has type RAW, bank 0, a pointer to {7,42} and length 2. asset_load_raw copies it into a two-byte RAM buffer.

Controls and expected result: The second byte is displayed as 042. This demonstrates asset lookup and copying, not image rendering.

Load data by asset ID: The second byte is displayed as 042. This demonstrates asset lookup and copying, not image rendering.
Display example: The second byte is displayed as 042. This demonstrates asset lookup and copying, not image rendering.

Download source / Verification status

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

Purpose: Initialize the bullet pool and inspect its occupancy after spawning.

How it works: After danmaku_reset, the program calls danmaku_spawn(40,40,16,0) and displays dm_count.

Controls and expected result: 001 is the number of allocated bullets. There is no bullet update loop or background compositor here, so no moving bullet is drawn.

Allocate one bullet: 001 is the number of allocated bullets. There is no bullet update loop or background compositor here, so no moving bullet is drawn.
Display example: 001 is the number of allocated bullets. There is no bullet update loop or background compositor here, so no moving bullet is drawn.

Download source / Verification status

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

// Reset the bullet pool, spawn one bullet and display the active count.
// This numeric example does not install the background bullet compositor.
void main() {
    m_init();
    m_text(2,3,"DANMAKU");
    danmaku_reset(); danmaku_spawn(40,40,16,0);
    m_number(dm_count);
    while (1) { m_wait();  }
}
Set a CGB background palette gb_cgb_palette.c

Purpose: Choose background and text colors using Game Boy Color RGB components.

How it works: The program disables the LCD during VBlank, sets palette 0 color 0 to white (31,31,31) and color 3 to purple (12,0,22), then enables the display.

Controls and expected result: Run in CGB mode to see purple text and 042 on white. DMG monochrome output cannot demonstrate these colors.

Set a CGB background palette: Run in CGB mode to see purple text and 042 on white. DMG monochrome output cannot demonstrate these colors.
Display example: Run in CGB mode to see purple text and 042 on white. DMG monochrome output cannot demonstrate these colors.

Download source / Verification status

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

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

Purpose: Move the viewing position without redrawing the background contents.

How it works: Each frame increments phase and passes it to Scroll_SetBg. The eight-bit position wraps after 255.

Controls and expected result: The word SCROLL moves left. Observe its position over time; a single screenshot cannot show the motion.

Scroll the background horizontally: The word SCROLL moves left. Observe its position over time; a single screenshot cannot show the motion.
Display example: The word SCROLL moves left. Observe its position over time; a single screenshot cannot show the motion.

Download source / Verification status

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

Purpose: Separate a drawing request from the VRAM transfer that applies it.

How it works: vram_queue_bg_tile queues tile 52 at tile coordinate (3,8), and vram_flush transfers it. In the supplied font, tile 52 is the digit 4.

Controls and expected result: Look for a single 4 at background tile coordinate (3,8). It is a tile update, not a numeric counter.

Queue a tile update: Look for a single 4 at background tile coordinate (3,8). It is a tile update, not a numeric counter.
Display example: Look for a single 4 at background tile coordinate (3,8). It is a tile update, not a numeric counter.

Download source / Verification status

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

// Queue font tile 52 (the digit 4) at tile coordinate (3,8), then wait for
// VBlank and execute the queued transfer.
void main() {
    m_init();
    m_text(2,3,"VRAM QUEUE");
    vram_init();
    vram_queue_bg_tile(3,8,52); vram_flush();
    while (1) { m_wait();  }
}
Display a sprite with OAM DMA gb_sprite.c

Purpose: Allocate and display a sprite independently of the background.

How it works: The fresh pool supplies slot 0. The program selects tile 65, sets position (70,80), enables OBJ display and transfers shadow OAM each frame.

Controls and expected result: A stationary letter A appears at the specified sprite position. In a game, check allocation failure before using the returned ID.

Display a sprite with OAM DMA: A stationary letter A appears at the specified sprite position. In a game, check allocation failure before using the returned ID.
Display example: A stationary letter A appears at the specified sprite position. In a game, check allocation failure before using the returned ID.

Download source / Verification status

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

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

Purpose: Use a seed to reproduce a pseudorandom sequence.

How it works: rng_seed(1234) initializes the generator, then rng_range(10) is called once. Its upper bound, 10, is excluded.

Controls and expected result: The screen shows a value from 000 to 009. Restarting the same program with the same seed reproduces it; the value does not change every frame.

Generate a repeatable random value: The screen shows a value from 000 to 009. Restarting the same program with the same seed reproduces it; the value does not change every frame.
Display example: The screen shows a value from 000 to 009. Restarting the same program with the same seed reproduces it; the value does not change every frame.

Download source / Verification status

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

// Seed the generator with 1234 and display one value in [0,10).
// Restarting with the same seed reproduces the sequence.
void main() {
    m_init();
    m_text(2,3,"RNG");
    rng_seed(1234); m_number(rng_range(10));
    while (1) { m_wait();  }
}
Store a game-state flag gb_flags.c

Purpose: Represent a Boolean state, such as whether an event is complete, using a flag number.

How it works: The program clears flag 12, sets it and reads it back with flag_get.

Controls and expected result: 001 means flag 12 is true. This example operates on RAM flags and does not save them across power cycles.

Store a game-state flag: 001 means flag 12 is true. This example operates on RAM flags and does not save them across power cycles.
Display example: 001 means flag 12 is true. This example operates on RAM flags and does not save them across power cycles.

Download source / Verification status

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

// Clear and set flag 12, then display its Boolean value. This does not write
// a save file or demonstrate persistence across power cycles.
void main() {
    m_init();
    m_text(2,3,"FLAGS");
    flag_clear(12); flag_set(12); m_number(flag_get(12));
    while (1) { m_wait();  }
}
Advance a circle by one physics step gb_circle.c

Purpose: Set up a circular body and update its position from velocity.

How it works: The active circle has radius 4, position (40,40) and velocity (2,0). Gravity is zero and linear_damping_q8 is 255; kq2dc_step runs once.

Controls and expected result: 042 is the resulting integer X coordinate. This example does not draw a circle outline or collision animation.

Advance a circle by one physics step: 042 is the resulting integer X coordinate. This example does not draw a circle outline or collision animation.
Display example: 042 is the resulting integer X coordinate. This example does not draw a circle outline or collision animation.

Download source / Verification status

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

Purpose: Start a sound-effect stream and advance it through regular frame updates.

How it works: After Audio_Init, Audio_PlaySFX starts tone on channel ID 1. The main loop calls Audio_Update once per frame.

Controls and expected result: A short pulse effect plays while 042 remains visible. That number is a fixed marker, not volume or playback position. Use audio output or a recording to assess the sound.

Service a sound effect each frame: A short pulse effect plays while 042 remains visible. That number is a fixed marker, not volume or playback position. Use audio output or a recording to assess the sound.
Display example: A short pulse effect plays while 042 remains visible. That number is a fixed marker, not volume or playback position. Use audio output or a recording to assess the sound.

Download source / Verification status

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

Compiler intrinsic dictionary

149 entries. Each entry explains the operation, arguments, return value, constraints, usage example and expected result.

asset.h — Asset IDs and data descriptors

The asset library looks up banked ROM resources by an 8-bit ID. Include asset.c; FC also needs bank.c. Each packed, 6-byte AssetDesc stores a type byte, a payload-bank byte, a 16-bit address and a byte length. The descriptor table and payload may occupy different banks.

Register the table before looking up resources. Registration is shared global state: do not change it concurrently with a lookup. The library checks IDs and asset_get’s output pointer, but does not validate the registered table, payload addresses or cartridge contents.

Look up a RAW or TILES resource and upload all desc.len bytes with __vram_copy. Select the payload’s ROM bank before calling: this GB function uses desc.ptr directly and does not select desc.bank for the upload.

__farmemcpy

Temporarily select a ROM bank, copy len bytes from src to dst in increasing address order, then restore the compiler’s saved ROM bank. __farmemcpy and __far_memcpy use the same implementation on this platform.

Syntax

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

Parameters

dst

Destination in writable memory. Reserve enough space for every byte copied. Except for a zero-byte transfer, a null or invalid destination is not checked.

bank

8-bit bank selector for the source ROM mapping. Use a bank supported by the cartridge and the compiler’s bank layout; this argument is separate from the source address.

src

Near pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.

len

Byte count, 0–65535. Zero performs no memory copy, but the bank-selection and restoration steps still run.

Return value

None (void).

Conditions and limits

Keep the full source range in the intended ROM mapping and the full destination range in writable memory. Copying does not select the next bank at a window boundary, handle overlapping ranges like memmove, or provide VRAM-safe writes. Use the graphics transfer APIs for VRAM.

The samples place calling code in fixed bank 0 so source-bank changes cannot unmap the code being executed. These transfers are synchronous. Do not interrupt a transfer with another bank-changing copy or assume the helper makes a whole application interrupt-safe.

The GB helper restores the compiler’s ROM-bank shadow. Use __bankswitch to maintain it; raw writes to the mapper do not update that shadow. Its saved-bank slot in HRAM is shared, so nested transfers that overwrite it are unsafe.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Copy two patterns, 32 bytes, from payload bank 2 while bank 1 is initially selected. The program compares every copied byte, checks that bank 1 is restored and verifies that a zero-length copy leaves a 0x55 sentinel unchanged.

Expected image: a red row starts at pixel (16,32) and spans 72×8 pixels. It repeats an 8×8 triangle, an 8×8 square outline with a one-pixel border, and a tile with its left four columns filled, three times. The triangle grows from one pixel at the top left to eight pixels on its bottom row.

On GB, a blue triangle and outline at (16,64) show the __farmemcpy result; a green pair at (16,96) shows __far_memcpy. Each pair occupies 16×8 pixels. CGB shows these colors; DMG shows the same shapes in black on white.

Expected status: FAILED CHECKS 000 and FIRST FAILURE 00. The verification runs 300 frames and compares text, shape masks and colors against these expectations. This is emulator evidence, not a physical-hardware test.

__farmemcpy: DMG
DMG
__farmemcpy: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/asset.h:17

__vram_copy

__vram_copy is a compiler alias for __vram_memcpy. It uses the same byte-count argument and the same immediate CPU transfer, including its LCD-access rules.

Copy len bytes from src to dst immediately, in increasing address order. The function returns after the copy finishes; it does not enqueue work. Source and destination advance by one byte, without tilemap row strides or tile-format conversion.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

src

Source byte pointer, valid and mapped for the whole call. Data is read immediately; it is not retained for a later flush. The source bank is not switched automatically. Use non-overlapping spans: this forward copy does not provide memmove semantics.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_copy to place three map bytes at tile (2,3), pixel (16,24). Expect the three patterns [1,2,3] from left to right in a 24 × 8 area. Neighboring cells must remain blank. The full program places each tested copy or fill on its own row.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_copy: DMG
DMG
__vram_copy: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/asset.h:18

bank.h — Bank-aware code and data access

A ROM bank is a block of cartridge storage mapped into the CPU's address space. Different banks can appear at the same CPU address, so a 16-bit pointer alone does not identify banked data. BankPtr is a packed three-byte descriptor containing a one-byte bank number and a two-byte pointer. Use farptr_make to construct it and farptr_read8, farptr_read16 or farptr_read to access its contents.

The GB example uses an MBC5 cartridge: code in fixed bank 0 stays accessible at $0000–$3FFF while banks 1, 2 and 3 are selected at $4000–$7FFF. Keep bank_switch and the code that resumes after it in fixed ROM. Initialize the library's bank record with bank_switch before querying it. GB far_call accepts a callback pointer; __farcall requires a function name.

__farcall_ptr

Invoke a zero-argument callback at a runtime address in the supplied bank. The fixed-ROM helper saves each caller's bank on the CPU stack and restores it on return. The intrinsic returns no value.

Syntax

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

Parameters

bank

An unsigned 8-bit ROM-bank number. For a call, it must agree with the function's placement; for a read, it selects the source mapping. Use the target cartridge's valid range.

func

A valid executable address for a callback with no parameters. A function name or runtime pointer may supply it. Null pointers and a wrong bank/address combination are not checked at runtime.

Return value

None (void).

Conditions and limits

These calls do not forward user arguments or make the callback interrupt-reentrant. Keep the callback's own data, stack and interrupt use valid. The bank-restoring call paths are tested with nested calls and repeated calls; physical hardware and arbitrary interrupt interleavings are not covered by this example.

These GB bank interfaces carry eight bank bits and do not set the ninth MBC5 bank bit. Cartridge rules still apply, including mapper-specific treatment of bank zero. The example explicitly uses MBC5 banks 1–3; do not mix this state with untracked raw mapper writes.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

At this point in the complete program, __farcall_ptr raises callback_count to 3. The callback reads D3 from bank 3. A later near read of B2 from home2 verifies that execution returned with bank 2 mapped.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__farcall_ptr: DMG
DMG
__farcall_ptr: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/bank.h:15

kitaqgb/kitaqgb/CodeGenerator.cs:8335

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

Read a little-endian 16-bit value at addr and addr+1 under a temporary bank selection, then restore the bank. This is the intrinsic used by far_data_read16.

Syntax

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

Parameters

bank

An unsigned 8-bit ROM-bank number. For a call, it must agree with the function's placement; for a read, it selects the source mapping. Use the target cartridge's valid range.

addr

Near pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.

Return value

The little-endian word read, as u16 (0–65535).

Conditions and limits

The complete byte or word must be readable in the selected CPU mapping. These reads do not validate pointers, clip addresses or advance automatically to another ROM bank at a window boundary. A word read needs two valid consecutive bytes.

Far reads and copies are synchronous and use shared compiler scratch. Do not let an interrupt run another banked transfer over the same scratch while one is active. On GB, the copy helper also uses one shared HRAM slot for the saved bank. The sample performs these operations from fixed code with no competing banked-transfer handler.

These GB bank interfaces carry eight bank bits and do not set the ninth MBC5 bank bit. Cartridge rules still apply, including mapper-specific treatment of bank zero. The example explicitly uses MBC5 banks 1–3; do not mix this state with untracked raw mapper writes.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This excerpt uses __farpeek16 to read payload+1 in bank 3 and checks 5C7A. In the full program, the near read from home2 also confirms that the bank-2 mapping is restored.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__farpeek16: DMG
DMG
__farpeek16: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/bank.h:13

kitaqgb/kitaqgb/CodeGenerator.cs:8295

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

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

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

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

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

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

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

Temporarily select the supplied bank, read one byte at the CPU address and restore the previous bank. This is the intrinsic used by far_data_read8.

Syntax

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

Parameters

bank

An unsigned 8-bit ROM-bank number. For a call, it must agree with the function's placement; for a read, it selects the source mapping. Use the target cartridge's valid range.

addr

Near pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.

Return value

The byte read, as u8 (0–255).

Conditions and limits

The complete byte or word must be readable in the selected CPU mapping. These reads do not validate pointers, clip addresses or advance automatically to another ROM bank at a window boundary. A word read needs two valid consecutive bytes.

Far reads and copies are synchronous and use shared compiler scratch. Do not let an interrupt run another banked transfer over the same scratch while one is active. On GB, the copy helper also uses one shared HRAM slot for the saved bank. The sample performs these operations from fixed code with no competing banked-transfer handler.

These GB bank interfaces carry eight bank bits and do not set the ninth MBC5 bank bit. Cartridge rules still apply, including mapper-specific treatment of bank zero. The example explicitly uses MBC5 banks 1–3; do not mix this state with untracked raw mapper writes.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This excerpt uses __farpeek8 to read payload in bank 3 and checks D3. In the full program, the near read from home2 also confirms that the bank-2 mapping is restored.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__farpeek8: DMG
DMG
__farpeek8: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/bank.h:12

kitaqgb/kitaqgb/CodeGenerator.cs:8262

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

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

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

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

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

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

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

cgb_palette.h — CGB BG and OBJ palettes

cgb_palette.h and cgb_palette.c provide RGB15 packing and immediate CGB palette updates. Choose BG helpers for background/window colors and OBJ helpers for sprite colors. Single-color and RGB-component functions change one color; range functions update consecutive words; full-palette functions write four words. The raw range functions use absolute color slots, whereas register intrinsics use byte positions. Set the palette assignment separately through map attributes or OAM. Include the implementation once in the program, as the complete sample does.

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

BG/window and OBJ palettes are separate stores. These library writes enable index auto-increment and leave the index register changed. OBJ color index zero is transparent when rendering sprites, even though its palette bytes can be stored and read.

__cgb_is_cgb

Return the compiler's CGB-mode Boolean. An explicit --cgb=dmg target folds to 0 and --cgb=cgb_only to 1; a dual-mode --cgb=cgb target uses the runtime detector. This reports the selected execution mode, not every physical model feature.

Syntax

c
u8 __cgb_is_cgb();

Return value

u8: 0 for DMG mode, 1 for CGB mode.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_palette.h:8

Declaration notes (original source)
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

Write one byte to the background/window palette data register BCPD/BGPD at $FF69 on CGB. __cgb_safe_set_bgpd is another name for the same register. The associated index register selects the byte position; a full RGB15 color requires a low byte and a high byte.

Syntax

c
void __cgb_safe_set_bcpd(u8 value);

Parameters

value

One byte, 0–255. Write the low byte followed by the high byte of RGB15; enable index auto-increment first, or explicitly select each position. A data write does not enable auto-increment itself.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_palette.h:10

__cgb_safe_set_bcps

Write the background/window palette byte-index register BCPS/BGPI at $FF68 on CGB. __cgb_safe_set_bgpi writes the same register. This selects where a subsequent data-register access goes; it does not itself write a color.

Syntax

c
void __cgb_safe_set_bcps(u8 value);

Parameters

value

Byte index in bits 0–5 (0–63), with bit 7 set for auto-increment after data writes; bit 6 is unused. Color slot n starts at byte 2n. Auto-increment wraps after byte 63, so raw register writes require your own range control.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_palette.h:9

__cgb_safe_set_ocpd

Write one byte to the sprite (OBJ) palette data register OCPD/OBPD at $FF6B on CGB. __cgb_safe_set_obpd is another name for the same register. The associated index register selects the byte position; a full RGB15 color requires a low byte and a high byte.

Syntax

c
void __cgb_safe_set_ocpd(u8 value);

Parameters

value

One byte, 0–255. Write the low byte followed by the high byte of RGB15; enable index auto-increment first, or explicitly select each position. A data write does not enable auto-increment itself.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_palette.h:12

__cgb_safe_set_ocps

Write the sprite (OBJ) palette byte-index register OCPS/OBPI at $FF6A on CGB. __cgb_safe_set_obpi writes the same register. This selects where a subsequent data-register access goes; it does not itself write a color.

Syntax

c
void __cgb_safe_set_ocps(u8 value);

Parameters

value

Byte index in bits 0–5 (0–63), with bit 7 set for auto-increment after data writes; bit 6 is unused. Color slot n starts at byte 2n. Auto-increment wraps after byte 63, so raw register writes require your own range control.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_palette.h:11

cgb_tile.h — CGB tile numbers and attributes

cgb_tile.h groups declarations for tile-map intrinsics and macros for constructing CGB tile attributes. A tile number selects a pattern; its attribute selects the palette, pattern VRAM bank, flips and background priority. Setting attributes does not alter the pattern’s pixels.

The __settile family addresses map 0x9800; __settileat uses an explicit map base; __settilebg uses the background map selected by LCDC; and __settilewin uses its window map. The attr forms write attributes only, while the cgb forms write both tile numbers and attributes. The bulk forms transfer consecutive bytes at the supplied address. Choose an entry point by both its destination and the data it writes.

Use macros such as KQ_CGB_ATTR_PAL for individual attribute bits, or KQ_CGB_ATTR to combine the fields. The bank field selects the VRAM bank containing the pattern, not a ROM bank. Ordinary tile-number writes use the currently selected VRAM bank, so select bank zero before combined tile/attribute operations. On DMG, attribute-only operations do nothing; combined operations still write the tile numbers.

The compiler emits the implementation of the __ intrinsics; they do not require linking cgb_tile.c. Ordinary write paths wait for VRAM access, but some also change the interrupt-enable state. The _unsafe and _fast forms omit those waits: use them only when the caller guarantees writable VRAM, such as while the LCD is off. Check each entry’s interrupt, bank and transfer-length conditions.

__cgb_safe_set_vbk

Select the CPU's VRAM bank through VBK ($FF4F) on CGB. Only value & 1 is written: even values select bank 0, odd values bank 1. This affects CPU accesses in $8000–$9FFF, not the tile-data bank selected by a map attribute. It does not restore the previous bank.

Syntax

c
void __cgb_safe_set_vbk(u8 value);

Parameters

value

Select the CPU's VRAM bank through VBK ($FF4F) on CGB. Only value & 1 is written: even values select bank 0, odd values bank 1. This affects CPU accesses in $8000–$9FFF, not the tile-data bank selected by a map attribute. It does not restore the previous bank.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:28

Declaration notes (original source)
Write the VRAM bank selection only on CGB. Safe here means hardware-mode guarded, not PPU-timed.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settile: DMG
DMG
__settile: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:31

Declaration notes (original source)
Write a tile number to map 9800, waiting for VRAM access when necessary.
__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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settile_bulk: DMG
DMG
__settile_bulk: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:48

Declaration notes (original source)
Copy count sequential bytes to dest using the current VRAM bank and access-checked transfer.
__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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settile_bulk_fast: DMG
DMG
__settile_bulk_fast: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:50

Declaration notes (original source)
Copy count sequential bytes to dest without VRAM access waits; retain source-bank visibility.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settile_unsafe: DMG
DMG
__settile_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:33

Declaration notes (original source)
Write a tile number to map 9800 without waiting for writable VRAM.
__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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileat: DMG
DMG
__settileat: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:35

Declaration notes (original source)
Write at base+32*y+x with VRAM access checks; supply a valid tile-map base.
__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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileat_unsafe: DMG
DMG
__settileat_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:37

Declaration notes (original source)
Use an explicit map base without waiting for VRAM access.
__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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileatattr: DMG
DMG
__settileatattr: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:62

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileatattr_unsafe: DMG
DMG
__settileatattr_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:64

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileatcgb: DMG
DMG
__settileatcgb: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:66

Declaration notes (original source)
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. Use the access-checked write path.
__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.

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.

Syntax

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

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileatcgb_unsafe: DMG
DMG
__settileatcgb_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:68

Declaration notes (original source)
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. No VRAM access waits are inserted.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileattr: DMG
DMG
__settileattr: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:53

Declaration notes (original source)
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

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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, A, B and C are red, blue and green respectively: attribute bytes 1,2,3 select three different prepared palettes. On DMG all three are black.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settileattr_bulk: DMG
DMG
__settileattr_bulk: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:89

Declaration notes (original source)
Copy count attributes to dest in bank one on CGB, then leave VBK zero; skip on DMG. Transfers use access checks.
__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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, A, B and C are red, blue and green respectively: attribute bytes 1,2,3 select three different prepared palettes. On DMG all three are black.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settileattr_bulk_fast: DMG
DMG
__settileattr_bulk_fast: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:91

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settileattr_unsafe: DMG
DMG
__settileattr_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:55

Declaration notes (original source)
Write attributes in bank one at map 9800 on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebg: DMG
DMG
__settilebg: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:43

Declaration notes (original source)
Select the background map from LCDC bit 3, then write with access checks.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebg_unsafe: DMG
DMG
__settilebg_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:45

Declaration notes (original source)
Select the LCDC background map and write without access waits.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebgattr: DMG
DMG
__settilebgattr: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:80

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebgattr_unsafe: DMG
DMG
__settilebgattr_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:82

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebgcgb: DMG
DMG
__settilebgcgb: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:84

Declaration notes (original source)
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. Use the access-checked write path.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilebgcgb_unsafe: DMG
DMG
__settilebgcgb_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:86

Declaration notes (original source)
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. No VRAM access waits are inserted.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilecgb: DMG
DMG
__settilecgb: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:57

Declaration notes (original source)
Write tile and CGB attributes at map 9800; DMG receives only the tile write. Use the access-checked write path.
__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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

tile_src, attr_src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

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.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, A, B and C are red, blue and green respectively: attribute bytes 1,2,3 select three different prepared palettes. On DMG all three are black.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settilecgb_bulk: DMG
DMG
__settilecgb_bulk: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/cgb_tile.h:93

Declaration notes (original source)
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. Transfers use access checks.
__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.

Syntax

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

Parameters

dest

First VRAM byte address. For a map, use an address in 0x9800–0x9FFF and keep the full transfer within the intended map.

tile_src, attr_src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

count

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

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.

Keep each source buffer readable and its ROM bank visible throughout the copy. Return type: void.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, A, B and C are red, blue and green respectively: attribute bytes 1,2,3 select three different prepared palettes. On DMG all three are black.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settilecgb_bulk_fast: DMG
DMG
__settilecgb_bulk_fast: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb_bulk_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb_bulk_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:95

Declaration notes (original source)
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. The caller supplies writable VRAM timing.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilecgb_unsafe: DMG
DMG
__settilecgb_unsafe: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilecgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilecgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:59

Declaration notes (original source)
Write tile and CGB attributes at map 9800; DMG receives only the tile write. No VRAM access waits are inserted.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewin: DMG
DMG
__settilewin: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewin.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewin.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:39

Declaration notes (original source)
Select the window map from LCDC bit 6, then write a tile with access checks.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewin_unsafe: DMG
DMG
__settilewin_unsafe: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewin_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewin_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:41

Declaration notes (original source)
Select the LCDC window map and write without access waits.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewinattr: DMG
DMG
__settilewinattr: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewinattr.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewinattr.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:71

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

On CGB, temporarily selects VRAM bank 1 and leaves VBK=0 afterward. On DMG, no attribute write occurs.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewinattr_unsafe: DMG
DMG
__settilewinattr_unsafe: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewinattr_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewinattr_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:73

Declaration notes (original source)
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

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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewincgb: DMG
DMG
__settilewincgb: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewincgb.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewincgb.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:75

Declaration notes (original source)
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. Use the access-checked write path.
__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB the A is blue: attribute value 2 selects the blue palette prepared by this example. On DMG the same character is black because CGB attributes are not applied.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settilewincgb_unsafe: DMG
DMG
__settilewincgb_unsafe: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilewincgb_unsafe.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilewincgb_unsafe.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/cgb_tile.h:77

Declaration notes (original source)
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. No VRAM access waits are inserted.

input.h — Button state, press, release and repeat

Use this library to separate continuous movement (input_down), one-shot actions (input_pressed), stopping on release (input_released) and menu repetition (input_repeat). Initialize once, update once per input tick, then query as often as needed. btn_down, btn_pressed, btn_released and btn_repeat are aliases for those four queries.

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Repeat timing counts updates, not milliseconds. A new press emits immediately. With defaults INPUT_REPEAT_DELAY=18 and INPUT_REPEAT_RATE=5, the next pulse is 19 updates later, followed by pulses every 6 updates. Each button has its own counter. Define these macros consistently when compiling input.c; changing only the caller's header does not reconfigure the library.

__readpadex

Read all buttons and compare them with the supplied previous held mask. Pack the current mask into the low byte and newly pressed bits, current & ~previous, into the high byte. No previous state is stored internally and no release or repeat mask is returned.

Syntax

c
u16 __readpadex(u8 prev_keys);

Parameters

prev_keys

Previous full held mask from __readpad() or the low byte of the preceding __readpadex() result. Pass 0 on the first sample, then save the new low byte for the next call.

Return value

A u16: (u8)result is the current mask; (u8)(result >> 8) is the new-press mask. Store the result in a 16-bit variable or the press bits will be lost.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Usage example

c
packed_first=__readpadex(0); // First sample: current 0x11, new presses 0x11.
current_keys=(u8)packed_first;
press_keys=(u8)(packed_first>>8);
packed_held=__readpadex(current_keys); // Held without a change: new presses are 0.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Use this program to compare the three button-mask layouts and the packed press result. Hold A and Right together. ALL KEYS is 17; DIRECTIONS and BUTTONS LOW are each 1. With a previous mask of 0, EX CURRENT and EX NEW PRESS are both 17. Passing the current mask to the next read makes HELD PRESS 0. FAILED CHECKS must be 0. The values remain on screen after the snapshot, so releasing the buttons does not erase them.

__readpadex: DMG
DMG
__readpadex: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
Implementation and source

kitaqgb/lib/input.h:28

intrinsics — Compiler intrinsics

This section groups intrinsics handled directly by the KITAQGB code generator. intrinsics is a documentation category, not an instruction to include a shared GB header named intrinsics.h. Use the headers and initialization shown in each complete example. You do not need to define the intrinsic's implementation in your game.

Some intrinsics use build-time information; others access hardware or memory while the game runs. For example, __bankof(symbol) resolves the assigned bank of a named object or function, while __bankswitch(bank) changes the visible ROM mapping at runtime. Keep bank numbers, CPU addresses and pointers distinct when supplying arguments.

A leading __ does not guarantee bounds checking, VRAM access waits or safe use from an interrupt. For example, the caller of __vram_memcpy_unsafe must provide a writable VRAM period. Check each entry's units, valid ranges, bank mapping and interrupt requirements before choosing an operation.

__bankof

Obtain the logical ROM-bank number assigned to a named data object or function. This resolves placement information; it does not inspect the currently mapped bank or dereference a pointer.

Syntax

c
u8 bank = __bankof(symbol);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

symbol

The name of a ROM data object or function whose placement is known to the compiler or linker. A pointer variable does not mean the object it points to; runtime expressions are not accepted as symbol names.

Return value

A u8 bank number. Zero can identify common ROM; it is not necessarily a valid switch-bank selector on every target.

Usage example

c
bank_expect(__bankof(payload)==3,3);
bank_expect(__bankof(callback)==3,4);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The sample checks that both payload and callback are placed in bank 3. This proves placement metadata; separate reads and callback checks prove the runtime bank behavior.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__bankof: DMG
DMG
__bankof: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:432

kitaqgb/kitaqgb/CodeGenerator.cs:7360

csharp
if (funcName == "__bankof")
            {
                if (args.Length != 1) Program.Error("__bankof(symbol) expects 1 argument");
                if (!args[0].Match(Tag.Name, out string symName)) Program.Error("__bankof(symbol) requires a symbol name argument");
                if (TryGetKnownRomBankForSymbol(symName, out int knownBank))
                {
                    EmitAsm("LD_A_IMM", new AsmOperand(knownBank & 0xFF, AddressMode.Immediate));
                    return;
                }
                EmitAsm("LD_A_IMM", new AsmOperand(symName, ImmediateModifier.Bank));
                return;
            }
__bankswitch

Write the low bank byte to cartridge address $2000 and update the compiler's bank state. The selection remains in effect after this call; bank_get_current is not updated.

Syntax

c
void __bankswitch(u8 bank);

Parameters

bank

An unsigned 8-bit ROM-bank number. For a call, it must agree with the function's placement; for a read, it selects the source mapping. Use the target cartridge's valid range.

Return value

None (void).

Conditions and limits

A permanent bank change must not unmap the executing code or its return address. Put this call, bank_switch itself when used, and the code that continues afterwards in common/fixed bank 0, as the sample does.

The library record starts at zero and does not discover the cartridge's startup mapping. Initialize it with bank_switch. Direct intrinsics and raw mapper writes do not update this separate record; bank_get_current therefore cannot verify the actual hardware mapping.

These GB bank interfaces carry eight bank bits and do not set the ninth MBC5 bank bit. Cartridge rules still apply, including mapper-specific treatment of bank zero. The example explicitly uses MBC5 banks 1–3; do not mix this state with untracked raw mapper writes.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The initial bank_switch(2) records and selects bank 2. Later, direct switching selects bank 1 and reads A1 from home1 while bank_get_current still reports 2; switching back makes home2 read B2 again.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__bankswitch: DMG
DMG
__bankswitch: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/bank.h:11

kitaqgb/kitaqgb/CodeGenerator.cs:7268

csharp
if (funcName == "__bankswitch")
            {
                if (args.Length != 1) Program.Error("__bankswitch(bank) expects 1 argument");
                CompileIntoA(args[0]);
                if (CurrentFunctionBank == 0)
                {
                    EmitAsm("LD_MEM_A", new AsmOperand(0x2000, AddressMode.Absolute));
                    EmitStoreA(RomBankVar);
                }
                else
                {
                    UseBankSwitchBank0Helper = true;
                    EmitAsm("CALL", new AsmOperand("__kq_bankswitch_bank0", AddressMode.Absolute));
                }
                return;
            }
__cgb_safe_set_bgpd

Write one byte to the background/window palette data register BCPD/BGPD at $FF69 on CGB. __cgb_safe_set_bcpd is another name for the same register. The associated index register selects the byte position; a full RGB15 color requires a low byte and a high byte.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

One byte, 0–255. Write the low byte followed by the high byte of RGB15; enable index auto-increment first, or explicitly select each position. A data write does not enable auto-increment itself.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

c
__cgb_safe_set_bgpd(31); __cgb_safe_set_bgpd(0); // Alias of BCPD: red at slot 31.
expect_color(0,31,31);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1865

__cgb_safe_set_bgpi

Write the background/window palette byte-index register BCPS/BGPI at $FF68 on CGB. __cgb_safe_set_bcps writes the same register. This selects where a subsequent data-register access goes; it does not itself write a color.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Byte index in bits 0–5 (0–63), with bit 7 set for auto-increment after data writes; bit 6 is unused. Color slot n starts at byte 2n. Auto-increment wraps after byte 63, so raw register writes require your own range control.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1863

__cgb_safe_set_hdma1

Write HDMA1 ($FF51), the high byte of the CGB DMA source address. This configures the source; it neither copies data nor starts a transfer. Set HDMA2 for the low byte.

Syntax

c
void __cgb_safe_set_hdma1(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

One block is 16 bytes. New transfers support 1–128 blocks (16–2048 bytes), so control value 0 means 16 bytes, not an empty transfer. GDMA pauses the CPU until complete. HBlank DMA transfers one block per visible-line HBlank and runs across frames; the caller must poll or otherwise track completion before reusing its data.

Use GDMA with the LCD off or within a sufficiently long writable period. Start HBlank DMA with the LCD running, outside STAT mode 0; this sample starts it in VBlank. Do not change source banks, destination VBK or the source data while it is active. HALT pauses HBlank DMA too. No wait, interrupt protection or source/destination range check is added by these setters.

Read $FF55 separately: bit 7 clear means active; $FF means completed. After cancellation, bit 7 is set and bits 0–6 retain remaining blocks minus one. Cancellation does not undo copied bytes. Only issue a cancel command while a transfer is active; otherwise bit 7 clear starts GDMA. Keep the entire transfer inside valid source memory and the destination VRAM bank; do not rely on address overflow.

These HDMA register setters control CGB hardware DMA. __vram_copy_dma is a separate compiler alias for a CPU copy loop; its name does not make it use this DMA controller.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program compares three transfer paths using a 16-byte-aligned buffer at $C600. GDMA copies 32 bytes to $8010: at screen (16,24) a red 8 × 8 triangle grows from one leftmost pixel to a full bottom row, followed at (24,24) by a red one-pixel square outline. HBlank DMA copies 32 bytes to $8030: at (16,48) a blue 4 × 8 left half-tile is followed at (24,48) by a blue 8 × 8 square. These are actual transferred tile patterns, not numerical placeholders.

The CANCELED row contains a green 4 × 8 half-tile at (16,72) and a green 8 × 8 square at (24,72), initially written by the CPU. A two-block HBlank transfer targeting these tiles is armed and canceled in the same VBlank, before its first block; $FF55 must read $81, and both patterns must remain unchanged after a frame. On DMG, the first two rows stay blank and only these two sentinels appear in black. Text remains black on white. FAILED CHECKS 000 also checks copied bytes and completion status. The test covers dual-mode DMG/CGB and CGB-only execution in KOKURA; partial cancellation, double speed and real hardware are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1871

__cgb_safe_set_hdma2

Write HDMA2 ($FF52), the low byte of the DMA source address. Hardware ignores bits 0–3, aligning the source downward to 16 bytes. For example, HDMA1=$C6, HDMA2=$0F selects $C600. It does not start DMA.

Syntax

c
void __cgb_safe_set_hdma2(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

One block is 16 bytes. New transfers support 1–128 blocks (16–2048 bytes), so control value 0 means 16 bytes, not an empty transfer. GDMA pauses the CPU until complete. HBlank DMA transfers one block per visible-line HBlank and runs across frames; the caller must poll or otherwise track completion before reusing its data.

Use GDMA with the LCD off or within a sufficiently long writable period. Start HBlank DMA with the LCD running, outside STAT mode 0; this sample starts it in VBlank. Do not change source banks, destination VBK or the source data while it is active. HALT pauses HBlank DMA too. No wait, interrupt protection or source/destination range check is added by these setters.

Read $FF55 separately: bit 7 clear means active; $FF means completed. After cancellation, bit 7 is set and bits 0–6 retain remaining blocks minus one. Cancellation does not undo copied bytes. Only issue a cancel command while a transfer is active; otherwise bit 7 clear starts GDMA. Keep the entire transfer inside valid source memory and the destination VRAM bank; do not rely on address overflow.

These HDMA register setters control CGB hardware DMA. __vram_copy_dma is a separate compiler alias for a CPU copy loop; its name does not make it use this DMA controller.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program compares three transfer paths using a 16-byte-aligned buffer at $C600. GDMA copies 32 bytes to $8010: at screen (16,24) a red 8 × 8 triangle grows from one leftmost pixel to a full bottom row, followed at (24,24) by a red one-pixel square outline. HBlank DMA copies 32 bytes to $8030: at (16,48) a blue 4 × 8 left half-tile is followed at (24,48) by a blue 8 × 8 square. These are actual transferred tile patterns, not numerical placeholders.

The CANCELED row contains a green 4 × 8 half-tile at (16,72) and a green 8 × 8 square at (24,72), initially written by the CPU. A two-block HBlank transfer targeting these tiles is armed and canceled in the same VBlank, before its first block; $FF55 must read $81, and both patterns must remain unchanged after a frame. On DMG, the first two rows stay blank and only these two sentinels appear in black. Text remains black on white. FAILED CHECKS 000 also checks copied bytes and completion status. The test covers dual-mode DMG/CGB and CGB-only execution in KOKURA; partial cancellation, double speed and real hardware are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1872

__cgb_safe_set_hdma3

Write HDMA3 ($FF53), the high part of the DMA destination within VRAM. Only bits 0–4 are used; the destination is always in $8000–$9FFF. Pair it with HDMA4. This does not select the VRAM bank or start DMA.

Syntax

c
void __cgb_safe_set_hdma3(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

One block is 16 bytes. New transfers support 1–128 blocks (16–2048 bytes), so control value 0 means 16 bytes, not an empty transfer. GDMA pauses the CPU until complete. HBlank DMA transfers one block per visible-line HBlank and runs across frames; the caller must poll or otherwise track completion before reusing its data.

Use GDMA with the LCD off or within a sufficiently long writable period. Start HBlank DMA with the LCD running, outside STAT mode 0; this sample starts it in VBlank. Do not change source banks, destination VBK or the source data while it is active. HALT pauses HBlank DMA too. No wait, interrupt protection or source/destination range check is added by these setters.

Read $FF55 separately: bit 7 clear means active; $FF means completed. After cancellation, bit 7 is set and bits 0–6 retain remaining blocks minus one. Cancellation does not undo copied bytes. Only issue a cancel command while a transfer is active; otherwise bit 7 clear starts GDMA. Keep the entire transfer inside valid source memory and the destination VRAM bank; do not rely on address overflow.

These HDMA register setters control CGB hardware DMA. __vram_copy_dma is a separate compiler alias for a CPU copy loop; its name does not make it use this DMA controller.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program compares three transfer paths using a 16-byte-aligned buffer at $C600. GDMA copies 32 bytes to $8010: at screen (16,24) a red 8 × 8 triangle grows from one leftmost pixel to a full bottom row, followed at (24,24) by a red one-pixel square outline. HBlank DMA copies 32 bytes to $8030: at (16,48) a blue 4 × 8 left half-tile is followed at (24,48) by a blue 8 × 8 square. These are actual transferred tile patterns, not numerical placeholders.

The CANCELED row contains a green 4 × 8 half-tile at (16,72) and a green 8 × 8 square at (24,72), initially written by the CPU. A two-block HBlank transfer targeting these tiles is armed and canceled in the same VBlank, before its first block; $FF55 must read $81, and both patterns must remain unchanged after a frame. On DMG, the first two rows stay blank and only these two sentinels appear in black. Text remains black on white. FAILED CHECKS 000 also checks copied bytes and completion status. The test covers dual-mode DMG/CGB and CGB-only execution in KOKURA; partial cancellation, double speed and real hardware are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1873

__cgb_safe_set_hdma4

Write HDMA4 ($FF54), the low byte of the VRAM destination address. Bits 0–3 are ignored, so the destination is aligned downward to 16 bytes. HDMA3=$E0, HDMA4=$1F selects $8010. This sets an address, not a transfer length.

Syntax

c
void __cgb_safe_set_hdma4(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

One block is 16 bytes. New transfers support 1–128 blocks (16–2048 bytes), so control value 0 means 16 bytes, not an empty transfer. GDMA pauses the CPU until complete. HBlank DMA transfers one block per visible-line HBlank and runs across frames; the caller must poll or otherwise track completion before reusing its data.

Use GDMA with the LCD off or within a sufficiently long writable period. Start HBlank DMA with the LCD running, outside STAT mode 0; this sample starts it in VBlank. Do not change source banks, destination VBK or the source data while it is active. HALT pauses HBlank DMA too. No wait, interrupt protection or source/destination range check is added by these setters.

Read $FF55 separately: bit 7 clear means active; $FF means completed. After cancellation, bit 7 is set and bits 0–6 retain remaining blocks minus one. Cancellation does not undo copied bytes. Only issue a cancel command while a transfer is active; otherwise bit 7 clear starts GDMA. Keep the entire transfer inside valid source memory and the destination VRAM bank; do not rely on address overflow.

These HDMA register setters control CGB hardware DMA. __vram_copy_dma is a separate compiler alias for a CPU copy loop; its name does not make it use this DMA controller.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program compares three transfer paths using a 16-byte-aligned buffer at $C600. GDMA copies 32 bytes to $8010: at screen (16,24) a red 8 × 8 triangle grows from one leftmost pixel to a full bottom row, followed at (24,24) by a red one-pixel square outline. HBlank DMA copies 32 bytes to $8030: at (16,48) a blue 4 × 8 left half-tile is followed at (24,48) by a blue 8 × 8 square. These are actual transferred tile patterns, not numerical placeholders.

The CANCELED row contains a green 4 × 8 half-tile at (16,72) and a green 8 × 8 square at (24,72), initially written by the CPU. A two-block HBlank transfer targeting these tiles is armed and canceled in the same VBlank, before its first block; $FF55 must read $81, and both patterns must remain unchanged after a frame. On DMG, the first two rows stay blank and only these two sentinels appear in black. Text remains black on white. FAILED CHECKS 000 also checks copied bytes and completion status. The test covers dual-mode DMG/CGB and CGB-only execution in KOKURA; partial cancellation, double speed and real hardware are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1874

__cgb_safe_set_hdma5

Write HDMA5 ($FF55) to start DMA, or cancel an active HBlank transfer. For a new transfer, bits 0–6 encode block count minus one and bit 7 chooses GDMA (0) or HBlank DMA (1). During an active HBlank transfer, writing bit 7 clear cancels the remaining work; it does not start a new GDMA transfer.

Syntax

c
void __cgb_safe_set_hdma5(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

One block is 16 bytes. New transfers support 1–128 blocks (16–2048 bytes), so control value 0 means 16 bytes, not an empty transfer. GDMA pauses the CPU until complete. HBlank DMA transfers one block per visible-line HBlank and runs across frames; the caller must poll or otherwise track completion before reusing its data.

Use GDMA with the LCD off or within a sufficiently long writable period. Start HBlank DMA with the LCD running, outside STAT mode 0; this sample starts it in VBlank. Do not change source banks, destination VBK or the source data while it is active. HALT pauses HBlank DMA too. No wait, interrupt protection or source/destination range check is added by these setters.

Read $FF55 separately: bit 7 clear means active; $FF means completed. After cancellation, bit 7 is set and bits 0–6 retain remaining blocks minus one. Cancellation does not undo copied bytes. Only issue a cancel command while a transfer is active; otherwise bit 7 clear starts GDMA. Keep the entire transfer inside valid source memory and the destination VRAM bank; do not rely on address overflow.

These HDMA register setters control CGB hardware DMA. __vram_copy_dma is a separate compiler alias for a CPU copy loop; its name does not make it use this DMA controller.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program compares three transfer paths using a 16-byte-aligned buffer at $C600. GDMA copies 32 bytes to $8010: at screen (16,24) a red 8 × 8 triangle grows from one leftmost pixel to a full bottom row, followed at (24,24) by a red one-pixel square outline. HBlank DMA copies 32 bytes to $8030: at (16,48) a blue 4 × 8 left half-tile is followed at (24,48) by a blue 8 × 8 square. These are actual transferred tile patterns, not numerical placeholders.

The CANCELED row contains a green 4 × 8 half-tile at (16,72) and a green 8 × 8 square at (24,72), initially written by the CPU. A two-block HBlank transfer targeting these tiles is armed and canceled in the same VBlank, before its first block; $FF55 must read $81, and both patterns must remain unchanged after a frame. On DMG, the first two rows stay blank and only these two sentinels appear in black. Text remains black on white. FAILED CHECKS 000 also checks copied bytes and completion status. The test covers dual-mode DMG/CGB and CGB-only execution in KOKURA; partial cancellation, double speed and real hardware are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1875

__cgb_safe_set_obpd

Write one byte to the sprite (OBJ) palette data register OCPD/OBPD at $FF6B on CGB. __cgb_safe_set_ocpd is another name for the same register. The associated index register selects the byte position; a full RGB15 color requires a low byte and a high byte.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

One byte, 0–255. Write the low byte followed by the high byte of RGB15; enable index auto-increment first, or explicitly select each position. A data write does not enable auto-increment itself.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

c
__cgb_safe_set_obpd(0); __cgb_safe_set_obpd(0x7C); // Alias of OCPD: blue at slot 31.
expect_color(1,31,31744);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1869

__cgb_safe_set_obpi

Write the sprite (OBJ) palette byte-index register OCPS/OBPI at $FF6A on CGB. __cgb_safe_set_ocps writes the same register. This selects where a subsequent data-register access goes; it does not itself write a color.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Byte index in bits 0–5 (0–63), with bit 7 set for auto-increment after data writes; bit 6 is unused. Color slot n starts at byte 2n. Auto-increment wraps after byte 63, so raw register writes require your own range control.

Return value

None (void).

Conditions and limits

Palette and guarded register writes do nothing in DMG mode. Here, safe means a CGB-mode guard, not a PPU access wait or a critical section. Use LCD-off setup or a sufficiently short update in an accessible PPU phase. These functions do not wait for VBlank or protect a multi-byte update from interrupts.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program teaches how palette contents affect the same tile pattern differently in BG and OBJ. Each triangle is 8 × 8 pixels; its top row has one pixel on the left, expanding to a full bottom row. At x = 16, 32, 48, 64, 80, 96, 112, the BG row at y = 24 is red, blue, green, red, blue, green, red. The OBJ row at y = 64 is blue, green, red, blue, green, red, blue. The columns use single-color, component, indexed-range, full-palette, absolute-range, canonical-register and alias-register writes, respectively.

At y = 88, the seven 8 × 8 cells show: red triangle (x = 16), blue one-pixel square outline from bank 1 (32), horizontally reflected green triangle (48), vertically reflected red triangle (64), blue triangle reflected both ways (80), green triangle over a red sprite with red visible through color-zero pixels (96), and a full red sprite in front of the BG triangle as a priority control (112). Coordinates are screen pixels measured from the upper-left corner. Blank space and black text remain on white.

FAILED CHECKS 000 confirms RGB masking, truth-valued flags (including input 2), palette index wrapping, writes crossing a palette boundary, clamping at slot 31, null/zero-length no-ops and palette readback while LCD is off. Four target/hardware combinations are tested: dual-mode ROM on DMG and CGB, DMG-only on DMG, and CGB-only on CGB. On DMG, CGB MODE is 0, all triangles are black, the bank/flip attributes have no display effect, and the last two bottom cells are black squares. CGB MODE is 1 in CGB tests. Real hardware and LCD-on palette contention are not covered.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1867

__cgb_safe_set_svbk

Write the raw WRAM selector SVBK ($FF70) on CGB. Hardware uses bits 0–2; raw values 0 and 1 both map bank 1 at $D000–$DFFF. For example, 8 also maps bank 1. Unlike __svbk_set, this guarded setter permits a dual-mode target and does not return the previous bank.

Syntax

c
void __cgb_safe_set_svbk(u8 value);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

value

Register value as u8 (0–255). The bit meanings for this particular register are given above; the function accepts a byte, not a pointer or a byte count.

Return value

None (void).

Conditions and limits

Writes are guarded by the compiler's CGB-mode check and do nothing in DMG mode. Safe denotes this mode guard only. The call does not save or restore the previous register value.

Switching banks changes every access in $D000–$DFFF; fixed WRAM at $C000–$CFFF stays mapped. Keep the stack and all state needed during the switch outside the switched window. The sample uses --stack-bank=fixed and explicitly located fixed-WRAM state. Coordinate interrupts and banked DMA with the switch; these APIs add no critical section.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete CGB-only program stores 1 at $D100 in WRAM bank 2 and 2 at the same address in bank 3, then reads both back and restores bank 1. The values choose an 8 × 8 red square at (16,24) and a blue one-pixel 8 × 8 square outline at (16,48). FAILED CHECKS 000 checks raw-selector normalization, bank separation and the previous-bank return for constant, variable, arithmetic and function-result arguments. Six separate invalid-build fixtures verify KQ2103 for DMG/dual targets and KQ2102 for constant banks 0 and 8. Runtime verification uses KOKURA; real hardware and interrupt-driven concurrent bank changes are not tested.

__cgb_safe_set_svbk: --cgb=cgb_only / CGB
--cgb=cgb_only / CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:1862

__critical_enter

Increment software nesting depth and execute DI.

Syntax

c
u8 __critical_enter();

Return value

Returns the previous depth as a u8 token: zero for the outer scope, one for its first nested scope.

Conditions and limits

Pass tokens to leave in reverse order and avoid depth wrapping at 256. Previous hardware IME is not saved; do not use this pair where a pre-existing DI state must be preserved.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__critical_enter: DMG
DMG
__critical_enter: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7970

kitaqgb/kitaqgb/CodeGenerator.cs:7970

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

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

Decrease nesting depth and execute EI for outer token zero.

Syntax

c
void __critical_leave(u8 token);

Parameters

token

Token returned by the matching __critical_enter.

Return value

No return value.

Conditions and limits

Nonzero tokens do not execute EI. Passing an arbitrary zero can enable interrupts despite other active scopes; pair calls correctly.

Usage example

c
__critical_leave(inner);__critical_leave(outer);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__critical_leave: DMG
DMG
__critical_leave: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7984

kitaqgb/kitaqgb/CodeGenerator.cs:7984

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

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

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

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

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

Compute a unsigned two-term dot product.

Syntax

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

Parameters

a0

Unsigned word, 0..65535.

a1

Unsigned word, 0..65535.

b0

Unsigned coefficient, 0..255.

b1

Unsigned coefficient, 0..255.

Return value

Each term is floor(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[36]=509, result[37]=509. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__dot2_q8_8: DMG
DMG
__dot2_q8_8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11575

kitaqgb/kitaqgb/CodeGenerator.cs:11575

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

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

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

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

Compute a unsigned three-term dot product.

Syntax

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

Parameters

a0

Unsigned word, 0..65535.

a1

Unsigned word, 0..65535.

a2

Unsigned word, 0..65535.

b0

Unsigned coefficient, 0..255.

b1

Unsigned coefficient, 0..255.

b2

Unsigned coefficient, 0..255.

Return value

Each term is floor(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[6]=521, result[7]=521. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__dot3_q8_8: DMG
DMG
__dot3_q8_8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11550

kitaqgb/kitaqgb/CodeGenerator.cs:11550

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

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

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

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

Call a named, zero-argument function using the supplied bank number. When a bank change is needed, a helper in fixed ROM restores the caller's bank after the function returns.

Syntax

c
__farcall(bank, function_name);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

bank

An unsigned 8-bit ROM-bank number. For a call, it must agree with the function's placement; for a read, it selects the source mapping. Use the target cartridge's valid range.

func

The declared name of a function with no parameters. A variable holding a function address is not accepted by this named-call intrinsic. The bank argument is not forwarded as a callback parameter.

Return value

The called function's result type determines the result. A void callback yields no value; u8 and u16 callbacks return their scalar value. The displayed sample uses void callbacks.

Conditions and limits

These calls do not forward user arguments or make the callback interrupt-reentrant. Keep the callback's own data, stack and interrupt use valid. The bank-restoring call paths are tested with nested calls and repeated calls; physical hardware and arbitrary interrupt interleavings are not covered by this example.

These GB bank interfaces carry eight bank bits and do not set the ninth MBC5 bank bit. Cartridge rules still apply, including mapper-specific treatment of bank zero. The example explicitly uses MBC5 banks 1–3; do not mix this state with untracked raw mapper writes.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

At this point in the complete program, __farcall raises callback_count to 2. The callback reads D3 from bank 3. A later near read of B2 from home2 verifies that execution returned with bank 2 mapped.

The complete program teaches how a bank number and near address identify data, how callbacks cross banks, and how to distinguish the library's record from the actual mapping. All displayed numbers are hexadecimal. BANK IN and BANK OUT must show 02, BYTE HEX D3, WORD HEX 5C7A, and both FAILED CHECKS and FIRST FAILURE 00. CGB and FC display blue text; DMG displays black text.

CALL COUNT is 03: far_call, __farcall and __farcall_ptr each invoke the callback once.

__farcall: DMG
DMG
__farcall: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7288

kitaqgb/kitaqgb/CodeGenerator.cs:7288

csharp
if (funcName == "__farcall")
            {
                if (args.Length != 2) Program.Error("__farcall(bank, func) expects 2 arguments");
                if (!args[1].Match(Tag.Name, out string targetName)) Program.Error("__farcall(bank, func) requires 2nd argument to be a function name");

                // A named far call supplies no arguments. Reject data symbols
                // and callbacks that would otherwise read stale argument storage.
                CFunctionInfo targetInfo;
                if (!Functions.TryGetValue(targetName, out targetInfo))
                {
                    Error(expr, "__farcall requires a declared function name");
                    return;
                }
                if ((targetInfo.Parameters ?? Array.Empty<FieldInfo>()).Length != 0)
                {
                    Error(expr, "__farcall requires a callback with no parameters");
                    return;
                }
                Expr bankExpr = FoldConstants(args[0]);
                int targetBank = -1;
                if (targetInfo != null)
                {
                    targetBank = targetInfo.RomBank;
                    PrepareStructReturnDestination(expr, targetInfo.ReturnType, targetInfo, targetName);
                    if (targetInfo.ReturnType != null)
                        LastCallReturnsHL = ReturnsInHL(expr, targetInfo.ReturnType);
                }

                // A known bank permits direct dispatch when safe, otherwise request a fixed-bank thunk.
                if (TryResolveBankExprToConstU8(args[0], out int bankLiteral))
                {
                    int optimizedBank = bankLiteral & 0xFF;
                    bool farcallDirectOK = IsDirectCallBankSafe(optimizedBank, CurrentFunctionBank);
                    if (!farcallDirectOK)
                    {
                        string farcallThunkName = EnsureBank0Thunk(
                            targetName,
                            optimizedBank,
                            isStackCall: targetInfo != null && targetInfo.IsStackCall,
                            stackArgBytes: 0);
                        EmitAsm("CALL", new AsmOperand(farcallThunkName, AddressMode.Absolute));
                        RecordCallEdge(expr, targetName, optimizedBank, "farcall_bank_thunk", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                    }
                    else
                    {
                        if (Program.CheckBankCalls && !InUnsafe && optimizedBank != 0)
                        {
                            AsmOperand ok = MakeUniqueLabel("kq_bank_ok");
                            EmitLoadA(RomBankVar);
                            EmitAsm("CP_IMM", new AsmOperand(optimizedBank & 0xFF, AddressMode.Immediate));
                            EmitAsm("JP_Z", ok);
                            EmitAsm("JP", EnsureCheckTrap());
                            EmitLabel(ok);
                        }

                        EmitAsm("CALL", new AsmOperand(targetName, AddressMode.Absolute));
                        RecordCallEdge(expr, targetName, optimizedBank, "farcall_direct", viaThunk: false, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                    }
                    return;
                }

                // A = requested bank argument (runtime value)
                CompileIntoA(bankExpr);

                // Always dispatch via bank0 helper thunk.
                string thunkName = EnsureBank0RuntimeFarcallThunk(targetName);
                EmitAsm("CALL", new AsmOperand(thunkName, AddressMode.Absolute));
                RecordCallEdge(expr, targetName, targetBank, "farcall_intrinsic", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
                return;
            }
__getwinmapbase

Read LCDC bit 6 to select the window tile-map base.

Syntax

c
u16 __getwinmapbase();

Return value

Returns $9800 or $9C00, the tile-map base rather than the tile graphics address.

Usage example

c
M_LCDC=0;result[2]=__getwinmapbase();M_LCDC=0x40;result[3]=__getwinmapbase();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__getwinmapbase: DMG
DMG
__getwinmapbase: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:10642

kitaqgb/kitaqgb/CodeGenerator.cs:10642

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

    AsmOperand win_base0 = MakeUniqueLabel("getwinbase_base0");
    AsmOperand win_done  = MakeUniqueLabel("getwinbase_done");

    EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
    EmitAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)); // bit6 (Window tile map display select)
    EmitAsm("JR_Z", win_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
    EmitAsm("JR", win_done);
    EmitLabel(win_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
    EmitLabel(win_done);
    LastCallReturnsHL = true;
    return;
}
__mac16

Compute a unsigned multiply-accumulate.

Syntax

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

Parameters

acc

16-bit accumulator to add.

a

Unsigned word, 0..65535.

b

Unsigned coefficient, 0..255.

Return value

Each term is floor(a*b/256), added to acc; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[34]=218, result[35]=218. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__mac16: DMG
DMG
__mac16: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11499

kitaqgb/kitaqgb/CodeGenerator.cs:11499

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

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

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

Compute a unsigned multiplication.

Syntax

c
u16 __mul16x8(u16 a, u8 b);

Parameters

a

Unsigned word, 0..65535.

b

Unsigned coefficient, 0..255.

Return value

Each term is floor(a*b/256); returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[24]=254, result[25]=254. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__mul16x8: DMG
DMG
__mul16x8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11467

kitaqgb/kitaqgb/CodeGenerator.cs:11467

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

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

Multiply two unsigned bytes and return the high byte.

Syntax

c
u8 __mul8x8_hi(u8 a, u8 b);

Parameters

a

Unsigned byte, 0..255.

b

Unsigned byte, 0..255.

Return value

floor(a*b/256), in 0..254.

Usage example

c
// Compare literal coefficients with values read from RAM.
result[4]=__mul8x8_hi(255,255);
v0=255;v1=255;
result[5]=__mul8x8_hi(v0,v1);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[4]=254, result[5]=254. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__mul8x8_hi: DMG
DMG
__mul8x8_hi: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11452

kitaqgb/kitaqgb/CodeGenerator.cs:11452

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

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

Schedule one repeating action selected by mask. Among newly pressed eligible bits, choose the lowest set bit and emit it immediately. Otherwise keep the active action while it remains eligible and held. If that action ends, adopt the lowest remaining held bit without an immediate pulse. No eligible held bits clears the runtime state.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

keys

Current full held-button mask in the GB BTN_* layout. Supply a snapshot from __readpad, the low byte of __readpadex, or input_current; the scheduler does not poll hardware itself.

trigger

Newly pressed bits for this update in the same layout as keys, normally keys & ~previous_keys or the high byte of __readpadex. Only include bits also present in keys. Passing the held mask every time falsely restarts the initial press on every call.

mask

Eligible GB button bits combined with |. Zero disables all actions and clears the runtime state. Multiple bits enable several candidates, but the function emits only one selected bit per call, not a combined pulse mask.

Return value

Zero when no action pulse is due; otherwise one of 1, 2, 4, 8, 16, 32, 64, 128. The nonzero result is a button bit, not a normalized Boolean or a count of repeats.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Initial delay, 0–255 scheduler calls. With a positive value N, the first held repeat occurs exactly N calls after the initial press pulse. With 0, the initial-delay equality case is skipped and the repeat interval determines when the first held pulse occurs. These counts are not milliseconds.

Interval for subsequent repeats, 0–255 calls. Positive N emits every N eligible held updates after the first repeat. Zero emits on every eligible held update after the initial delay, so 0 and 1 both give one pulse per update at that stage. With both delay and interval set to 0, each held update emits a pulse.

Call the scheduler once per intended input tick. It modifies the state on every call and does not wait for VBlank. If a held button is adopted without a new-press bit, that adoption call returns 0 and starts a fresh delay. These functions schedule one active action per state; input_repeat instead maintains independent timers for all eight buttons.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep: DMG
DMG
__padrep: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:11249

csharp
{
    if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");

    AsmOperand pm_done        = MakeUniqueLabel("padrepM_done");
    AsmOperand pm_ret0        = MakeUniqueLabel("padrepM_ret0");
    AsmOperand pm_no_trig     = MakeUniqueLabel("padrepM_notrig");
    AsmOperand pm_reset_all   = MakeUniqueLabel("padrepM_reset");
    AsmOperand pm_dir0        = MakeUniqueLabel("padrepM_dir0");
    AsmOperand pm_have_dir    = MakeUniqueLabel("padrepM_havedir");
    AsmOperand pm_first_rep   = MakeUniqueLabel("padrepM_firstrep");
    AsmOperand pm_ret_dir     = MakeUniqueLabel("padrepM_retdir");
    AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
    AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger, E = mask
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
    EmitAsm("POP_HL");

    // trigMasked = trigger & mask -> D
    EmitAsm("LD_A_C");
    EmitAsm("AND_E");
    EmitAsm("LD_D_A");
    EmitAsm("OR_A");
    EmitAsm("JP_Z", pm_no_trig);

    // select = lowbit(trigMasked) = x & -x
    EmitAsm("LD_A_D");
    EmitAsm("CPL");
    EmitAsm("INC_A");
    EmitAsm("AND_D");
    EmitAsm("LD_C_A"); // save selected in C

    // dirMask=selected, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [0]=selected
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); Emi
__padrep_down

Advance a repeat action for Down only. A new Down press returns 8 immediately and restarts the counters. Held Down produces later pulses according to the state's timing; releasing Down clears the runtime state. Other buttons do not trigger this scheduler.

Syntax

c
u8 __padrep_down(u8* state, u8 keys, u8 trigger);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

keys

Current full held-button mask in the GB BTN_* layout. Supply a snapshot from __readpad, the low byte of __readpadex, or input_current; the scheduler does not poll hardware itself.

trigger

Newly pressed bits for this update in the same layout as keys, normally keys & ~previous_keys or the high byte of __readpadex. Only include bits also present in keys. Passing the held mask every time falsely restarts the initial press on every call.

Return value

Zero when no action pulse is due; otherwise one of 8. The nonzero result is a button bit, not a normalized Boolean or a count of repeats.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Initial delay, 0–255 scheduler calls. With a positive value N, the first held repeat occurs exactly N calls after the initial press pulse. With 0, the initial-delay equality case is skipped and the repeat interval determines when the first held pulse occurs. These counts are not milliseconds.

Interval for subsequent repeats, 0–255 calls. Positive N emits every N eligible held updates after the first repeat. Zero emits on every eligible held update after the initial delay, so 0 and 1 both give one pulse per update at that stage. With both delay and interval set to 0, each held update emits a pulse.

Call the scheduler once per intended input tick. It modifies the state on every call and does not wait for VBlank. If a held button is adopted without a new-press bit, that adoption call returns 0 and starts a fresh delay. These functions schedule one active action per state; input_repeat instead maintains independent timers for all eight buttons.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep_down: DMG
DMG
__padrep_down: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:11069

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_down requires 3 arguments (state*, keys, trigger)");

    AsmOperand pd_done        = MakeUniqueLabel("padrepD_done");
    AsmOperand pd_ret0        = MakeUniqueLabel("padrepD_ret0");
    AsmOperand pd_no_trig     = MakeUniqueLabel("padrepD_notrig");
    AsmOperand pd_dir0        = MakeUniqueLabel("padrepD_dir0");
    AsmOperand pd_have_dir    = MakeUniqueLabel("padrepD_havedir");
    AsmOperand pd_first_rep   = MakeUniqueLabel("padrepD_firstrep");
    AsmOperand pd_ret_dir     = MakeUniqueLabel("padrepD_retdir");
    AsmOperand pd_skip_dasinc = MakeUniqueLabel("padrepD_skipdasinc");
    AsmOperand pd_skip_arrinc = MakeUniqueLabel("padrepD_skiparrinc");
    AsmOperand pd_reset_all   = MakeUniqueLabel("padrepD_reset");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    EmitAsm("POP_HL");

    // --- Trigger Down? (bit3 = 0x08) ---
    EmitAsm("LD_A_C");
    EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate));
    EmitAsm("JP_Z", pd_no_trig);

    // dirMask=0x08, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_IMM", new AsmOperand(0x08, AddressMode.Immediate));
    EmitAsm("LD_HL_A"); // [0]=0x08
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // dasCnt=0
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A"); // arrCnt=0
    EmitAsm("POP_HL");

    EmitAsm
__padrep_init

Initialize a caller-owned five-byte repeat state: clear the active action and both counters, and store the supplied initial delay and repeat interval. No buttons are read and no repeat pulse is emitted.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

das

Initial delay, 0–255 scheduler calls. With a positive value N, the first held repeat occurs exactly N calls after the initial press pulse. With 0, the initial-delay equality case is skipped and the repeat interval determines when the first held pulse occurs. These counts are not milliseconds.

arr

Interval for subsequent repeats, 0–255 calls. Positive N emits every N eligible held updates after the first repeat. Zero emits on every eligible held update after the initial delay, so 0 and 1 both give one pulse per update at that stage. With both delay and interval set to 0, each held update emits a pulse.

Return value

None (void).

Usage example

c
__padrep_init(lr,3,2);
// Active action and counters are zero; delay and interval are retained.
expect(lr[0],0); expect(lr[1],3); expect(lr[2],2);
expect(lr[3],0); expect(lr[4],0);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep_init: DMG
DMG
__padrep_init: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10808

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_init requires 3 arguments (state*, das, arr)");

    // Evaluate args with register safety
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // das
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // arr
    EmitAsm("POP_HL");

    // dirMask = 0; dasCnt=0; arrCnt=0; store params
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0
    EmitAsm("INC_HL");
    EmitAsm("LD_A_B");
    EmitAsm("LD_HL_A"); // [1]=das
    EmitAsm("INC_HL");
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [2]=arr
    EmitAsm("INC_HL");
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [3]=dasCnt
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A"); // [4]=arrCnt
    return;
}

// 2.21 __padrep_reset(state_ptr) -> void
// Resets runtime counters for PadRepeatState while keeping das/arr params:
// dirMask=0, dasCnt=0, arrCnt=0
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__padrep_reset")
{
    if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");

    CompileIntoHL(args[0]);

    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0 (dirMask)

    // [3]=0, [4]=0
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("LD_HL_A");
    EmitAsm("INC_HL"); // -> [4]
    EmitAsm("LD_HL_A");
    EmitAsm("POP_HL");
    return;
}

// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new
__padrep_lr

Advance a single Left/Right repeat action. A new Right press emits 1 immediately; otherwise a new Left press emits 2. If both are newly pressed, Right wins. Without a new press, holding both directions returns 0 and freezes the counters; holding neither clears the runtime state.

Syntax

c
u8 __padrep_lr(u8* state, u8 keys, u8 trigger);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

keys

Current full held-button mask in the GB BTN_* layout. Supply a snapshot from __readpad, the low byte of __readpadex, or input_current; the scheduler does not poll hardware itself.

trigger

Newly pressed bits for this update in the same layout as keys, normally keys & ~previous_keys or the high byte of __readpadex. Only include bits also present in keys. Passing the held mask every time falsely restarts the initial press on every call.

Return value

Zero when no action pulse is due; otherwise one of 1, 2. The nonzero result is a button bit, not a normalized Boolean or a count of repeats.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Initial delay, 0–255 scheduler calls. With a positive value N, the first held repeat occurs exactly N calls after the initial press pulse. With 0, the initial-delay equality case is skipped and the repeat interval determines when the first held pulse occurs. These counts are not milliseconds.

Interval for subsequent repeats, 0–255 calls. Positive N emits every N eligible held updates after the first repeat. Zero emits on every eligible held update after the initial delay, so 0 and 1 both give one pulse per update at that stage. With both delay and interval set to 0, each held update emits a pulse.

Call the scheduler once per intended input tick. It modifies the state on every call and does not wait for VBlank. If a held button is adopted without a new-press bit, that adoption call returns 0 and starts a fresh delay. These functions schedule one active action per state; input_repeat instead maintains independent timers for all eight buttons.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep_lr: DMG
DMG
__padrep_lr: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10864

csharp
{
    if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");

    AsmOperand pr_done        = MakeUniqueLabel("padrep_done");
    AsmOperand pr_ret0        = MakeUniqueLabel("padrep_ret0");
    AsmOperand pr_check_left  = MakeUniqueLabel("padrep_chkL");
    AsmOperand pr_no_trig     = MakeUniqueLabel("padrep_notrig");
    AsmOperand pr_reset_all   = MakeUniqueLabel("padrep_reset");
    AsmOperand pr_dir0        = MakeUniqueLabel("padrep_dir0");
    AsmOperand pr_have_dir    = MakeUniqueLabel("padrep_havedir");
    AsmOperand pr_first_rep   = MakeUniqueLabel("padrep_firstrep");
    AsmOperand pr_ret_dir     = MakeUniqueLabel("padrep_retdir");
    AsmOperand pr_skip_dasinc = MakeUniqueLabel("padrep_skipdasinc");
    AsmOperand pr_skip_arrinc = MakeUniqueLabel("padrep_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    EmitAsm("POP_HL");

    // --- Trigger Right? (bit0) ---
    EmitAsm("LD_A_C");
    EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
    EmitAsm("JP_Z", pr_check_left);

    // dirMask=0x01, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_IMM", new AsmOperand(0x01, AddressMode.Immediate));
    EmitAsm("LD_HL_A"); // [0]=0x01
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // dasCnt=0
    EmitAsm("INC_HL");
    EmitAsm("LD_HL_A")
__padrep_mask

__padrep_mask is an alternative spelling of __padrep; both enter the same compiler implementation. It has the same single-action selection, timing, state layout and return values. The explicit name can make a masked action scheduler easier to recognize at a call site.

Schedule one repeating action selected by mask. Among newly pressed eligible bits, choose the lowest set bit and emit it immediately. Otherwise keep the active action while it remains eligible and held. If that action ends, adopt the lowest remaining held bit without an immediate pulse. No eligible held bits clears the runtime state.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

keys

Current full held-button mask in the GB BTN_* layout. Supply a snapshot from __readpad, the low byte of __readpadex, or input_current; the scheduler does not poll hardware itself.

trigger

Newly pressed bits for this update in the same layout as keys, normally keys & ~previous_keys or the high byte of __readpadex. Only include bits also present in keys. Passing the held mask every time falsely restarts the initial press on every call.

mask

Eligible GB button bits combined with |. Zero disables all actions and clears the runtime state. Multiple bits enable several candidates, but the function emits only one selected bit per call, not a combined pulse mask.

Return value

Zero when no action pulse is due; otherwise one of 1, 2, 4, 8, 16, 32, 64, 128. The nonzero result is a button bit, not a normalized Boolean or a count of repeats.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

Initial delay, 0–255 scheduler calls. With a positive value N, the first held repeat occurs exactly N calls after the initial press pulse. With 0, the initial-delay equality case is skipped and the repeat interval determines when the first held pulse occurs. These counts are not milliseconds.

Interval for subsequent repeats, 0–255 calls. Positive N emits every N eligible held updates after the first repeat. Zero emits on every eligible held update after the initial delay, so 0 and 1 both give one pulse per update at that stage. With both delay and interval set to 0, each held update emits a pulse.

Call the scheduler once per intended input tick. It modifies the state on every call and does not wait for VBlank. If a held button is adopted without a new-press bit, that adoption call returns 0 and starts a fresh delay. These functions schedule one active action per state; input_repeat instead maintains independent timers for all eight buttons.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep_mask: DMG
DMG
__padrep_mask: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:11249

csharp
{
    if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");

    AsmOperand pm_done        = MakeUniqueLabel("padrepM_done");
    AsmOperand pm_ret0        = MakeUniqueLabel("padrepM_ret0");
    AsmOperand pm_no_trig     = MakeUniqueLabel("padrepM_notrig");
    AsmOperand pm_reset_all   = MakeUniqueLabel("padrepM_reset");
    AsmOperand pm_dir0        = MakeUniqueLabel("padrepM_dir0");
    AsmOperand pm_have_dir    = MakeUniqueLabel("padrepM_havedir");
    AsmOperand pm_first_rep   = MakeUniqueLabel("padrepM_firstrep");
    AsmOperand pm_ret_dir     = MakeUniqueLabel("padrepM_retdir");
    AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
    AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");

    // Evaluate args safely:
    // HL = state*, B = keys, C = trigger, E = mask
    CompileIntoHL(args[0]);
    EmitAsm("PUSH_HL");
    CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
    CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
    CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
    EmitAsm("POP_HL");

    // trigMasked = trigger & mask -> D
    EmitAsm("LD_A_C");
    EmitAsm("AND_E");
    EmitAsm("LD_D_A");
    EmitAsm("OR_A");
    EmitAsm("JP_Z", pm_no_trig);

    // select = lowbit(trigMasked) = x & -x
    EmitAsm("LD_A_D");
    EmitAsm("CPL");
    EmitAsm("INC_A");
    EmitAsm("AND_D");
    EmitAsm("LD_C_A"); // save selected in C

    // dirMask=selected, dasCnt=0, arrCnt=0
    EmitAsm("LD_A_C");
    EmitAsm("LD_HL_A"); // [0]=selected
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); Emi
__padrep_reset

Cancel the active action and clear both elapsed counters while preserving the configured delay and interval. Use this when closing a menu or changing input focus so a previous hold does not carry its repeat timing into the next interaction.

Syntax

c
void __padrep_reset(u8* state);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

state

Pointer to five writable, consecutive RAM bytes, for example u8 state[5]. The layout is [0] active button bit, [1] initial delay, [2] repeat interval, [3] elapsed initial-delay count, [4] elapsed repeat count. Allocate a separate state for each independent scheduler. Initialize it before use; a null pointer, fewer than five bytes, or sharing the same state between directional and general schedulers is not supported.

Return value

None (void).

Usage example

c
__padrep_reset(lr);
expect(lr[0],0); expect(lr[1],3); expect(lr[2],2);
expect(lr[3],0); expect(lr[4],0);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This program feeds controlled button snapshots into four independent states to show exactly when a repeat fires. With delay 3 and interval 2, eight calls numbered 0–7 emit pulses on calls 0, 3, 5 and 7. RIGHT PULSES, DOWN PULSES, ACTION PULSES and ALIAS PULSES therefore each display 004. The general schedulers receive A and B together and select A (16). The program also checks initialization, reset, two-direction priority, release, adoption of an already held direction, zero timing values and an expression argument. CHECKS displays 057 and FAILED CHECKS must display 000. This is a deterministic state-processing example; it does not wait for you to press buttons.

__padrep_reset: DMG
DMG
__padrep_reset: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\pad_repeat_state.c -I .\kitaq-docs\samples -I .\kitaqgb\lib -o .\out\pad_repeat_state.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10840

csharp
{
    if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");

    CompileIntoHL(args[0]);

    EmitAsm("XOR_A");
    EmitAsm("LD_HL_A"); // [0]=0 (dirMask)

    // [3]=0, [4]=0
    EmitAsm("PUSH_HL");
    EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
    EmitAsm("LD_HL_A");
    EmitAsm("INC_HL"); // -> [4]
    EmitAsm("LD_HL_A");
    EmitAsm("POP_HL");
    return;
}

// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new trigger, simultaneous Left/Right holds suppress movement without resetting counters.
// A single held direction is adopted without immediate movement when no direction is active.
// Subsequent updates use the five-byte state's initial delay and repeat interval; ARR=0 repeats each update.
if (funcName == "__padrep_lr")
{
    if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");

    AsmOperand pr_done        = MakeUniqueLabel("padrep_done");
    AsmOperand pr_ret0        = MakeUniqueLabel("padrep_ret0");
    AsmOperand pr_check_left  = MakeUniqueLabel("padrep_chkL");
    AsmOperand pr_no_trig     = MakeUniqueLabel("padrep_notrig");
    AsmOperand pr_reset_all   = MakeUniqueLabel("padrep_reset");
    AsmOperand pr_dir0        = MakeUniqueLabel("padrep_dir0");
    AsmOperand pr_have_dir    = MakeUniqueLabel("padrep_havedir");
    AsmOperand pr_first_rep   = MakeUniqueLabel("padrep_firstrep");
    AsmOperand pr_ret_dir     = MakeUniqueLabel("padrep
__readpad

Read directions and action buttons from JOYP and combine them into the shared 8-bit button mask. Selects each JOYP group, performs two reads for settling, then restores the group-selection bits to 0x30.

Syntax

c
u8 __readpad();

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Return value

An 8-bit mask in the shared BTN_* layout. Several buttons may be set simultaneously; 0 means none.

Conditions and limits

Shared masks: BTN_RIGHT=0x01, BTN_LEFT=0x02, BTN_UP=0x04, BTN_DOWN=0x08, BTN_A=0x10, BTN_B=0x20, BTN_SELECT=0x40, BTN_START=0x80. A set bit means held. This layout applies to input_* on both GB and FC; raw FC pad functions use a different order.

This call reads a fresh hardware snapshot; it does not update input_* or nes_pad1_* cached state and does not generate pressed/released edges or repeats. Read once, save the result, and derive edges against your saved previous mask when needed.

Usage example

c
all_keys=__readpad(); // A + Right: 0x11, decimal 17.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Use this program to compare the three button-mask layouts and the packed press result. Hold A and Right together. ALL KEYS is 17; DIRECTIONS and BUTTONS LOW are each 1. With a previous mask of 0, EX CURRENT and EX NEW PRESS are both 17. Passing the current mask to the next read makes HELD PRESS 0. FAILED CHECKS must be 0. The values remain on screen after the snapshot, so releasing the buttons does not erase them.

__readpad: DMG
DMG
__readpad: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10669

csharp
{
    if (args.Length != 0) Error(expr, "__readpad requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    // Directions (P1=0x20)
    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1); // dummy reads for settle
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    EmitAsm("LD_C_A"); // dpad in C

    // Buttons (P1=0x10)
    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    // shift into upper nibble (<<4)
    EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA");
    EmitAsm("OR_C");

    // Restore P1 selection (0x30) without losing return value
    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.17 __readpaddir() -> u8 in A (bits0..3 only)
// Returns dpad nibble (Right,Left,Up,Down) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpaddir")
{
    if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm
__readpadbtn

Read only the JOYP action-button group as a low nibble: A=1, B=2, Select=4, Start=8. These values are not BTN_A through BTN_START; those shared masks occupy the high nibble. JOYP selection is restored to 0x30.

Syntax

c
u8 __readpadbtn();

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Return value

A mask from 0 to 15 in the bit order described above. More than one bit can be set; test a button with result & bit, rather than comparing the whole result to 1.

Conditions and limits

This call reads a fresh hardware snapshot; it does not update input_* or nes_pad1_* cached state and does not generate pressed/released edges or repeats. Read once, save the result, and derive edges against your saved previous mask when needed.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Use this program to compare the three button-mask layouts and the packed press result. Hold A and Right together. ALL KEYS is 17; DIRECTIONS and BUTTONS LOW are each 1. With a previous mask of 0, EX CURRENT and EX NEW PRESS are both 17. Passing the current mask to the next read makes HELD PRESS 0. FAILED CHECKS must be 0. The values remain on screen after the snapshot, so releasing the buttons does not erase them.

__readpadbtn: DMG
DMG
__readpadbtn: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10729

csharp
{
    if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadex")
{
    if (args.Length != 1) Error(expr, "__readpadex requires 1 argument (prev_keys)");

    // prev -> B
    CompileIntoA(args[0]);
    EmitAsm("LD_B_A");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    // Directions (P1=0x20)
    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
    EmitAsm("LD_C_A"); // dpad in C

    // Buttons (P1=0x10)
    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1)
__readpaddir

Read only the JOYP direction group. The result uses bits 0–3: Right=1, Left=2, Up=4, Down=8; bits 4–7 are zero. Action buttons are ignored and JOYP selection is restored to 0x30.

Syntax

c
u8 __readpaddir();

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Return value

A mask from 0 to 15 in the bit order described above. More than one bit can be set; test a button with result & bit, rather than comparing the whole result to 1.

Conditions and limits

This call reads a fresh hardware snapshot; it does not update input_* or nes_pad1_* cached state and does not generate pressed/released edges or repeats. Read once, save the result, and derive edges against your saved previous mask when needed.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Use this program to compare the three button-mask layouts and the packed press result. Hold A and Right together. ALL KEYS is 17; DIRECTIONS and BUTTONS LOW are each 1. With a previous mask of 0, EX CURRENT and EX NEW PRESS are both 17. Passing the current mask to the next read makes HELD PRESS 0. FAILED CHECKS must be 0. The values remain on screen after the snapshot, so releasing the buttons does not erase them.

__readpaddir: DMG
DMG
__readpaddir: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\input_raw.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\input_raw.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm --no-cache
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10706

csharp
{
    if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.18 __readpadbtn() -> u8 in A (bits0..3 only)
// Returns buttons nibble (A,B,Select,Start) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadbtn")
{
    if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");

    AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP

    EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("LDH_A_MEM", p1);
    EmitAsm("CPL");
    EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));

    EmitAsm("LD_C_A");
    EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
    EmitAsm("LDH_MEM_A", p1);
    EmitAsm("LD_A_C");
    return;
}

// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a c
__sdot2_q1_7

Compute a signed two-term dot product.

Syntax

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

Parameters

a0

Signed 16-bit bit pattern, representing −32768..32767.

a1

Signed 16-bit bit pattern, representing −32768..32767.

b0

Signed byte bit pattern, coefficient −128..127.

b1

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is 2*trunc(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[20]=1018, result[21]=1018, result[22]=64518, result[23]=64518. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__sdot2_q1_7: DMG
DMG
__sdot2_q1_7: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11732

kitaqgb/kitaqgb/CodeGenerator.cs:11732

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

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

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

Compute a signed two-term dot product.

Syntax

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

Parameters

a0

Signed 16-bit bit pattern, representing −32768..32767.

a1

Signed 16-bit bit pattern, representing −32768..32767.

b0

Signed byte bit pattern, coefficient −128..127.

b1

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is trunc(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[30]=509, result[31]=509, result[32]=65027, result[33]=65027. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__sdot2_q8_8: DMG
DMG
__sdot2_q8_8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11654

kitaqgb/kitaqgb/CodeGenerator.cs:11654

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

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

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

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

Compute a signed three-term dot product.

Syntax

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

Parameters

a0

Signed 16-bit bit pattern, representing −32768..32767.

a1

Signed 16-bit bit pattern, representing −32768..32767.

a2

Signed 16-bit bit pattern, representing −32768..32767.

b0

Signed byte bit pattern, coefficient −128..127.

b1

Signed byte bit pattern, coefficient −128..127.

b2

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is 2*trunc(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[16]=1042, result[17]=1042, result[18]=64494, result[19]=64494. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__sdot3_q1_7: DMG
DMG
__sdot3_q1_7: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11790

kitaqgb/kitaqgb/CodeGenerator.cs:11790

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

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

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

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

Compute a signed three-term dot product.

Syntax

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

Parameters

a0

Signed 16-bit bit pattern, representing −32768..32767.

a1

Signed 16-bit bit pattern, representing −32768..32767.

a2

Signed 16-bit bit pattern, representing −32768..32767.

b0

Signed byte bit pattern, coefficient −128..127.

b1

Signed byte bit pattern, coefficient −128..127.

b2

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is trunc(a*b/256). Sum the terms; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[8]=521, result[9]=521, result[10]=65015, result[11]=65015. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__sdot3_q8_8: DMG
DMG
__sdot3_q8_8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11767

kitaqgb/kitaqgb/CodeGenerator.cs:11767

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

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

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

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

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.

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.

Syntax

c
__settile_fast(x, y, tile);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settile_fast: DMG
DMG
__settile_fast: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_fast.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_fast.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:9321

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

                // Evaluate args left-to-right (matches normal call semantics)
                // Preserve x,y while evaluating tile.
                CompileIntoA(args[0]);
                EmitAsm("LD_L_A");
                EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("PUSH_HL"); // x

                CompileIntoA(args[1]);
                EmitAsm("LD_L_A");
                EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("PUSH_HL"); // y

                CompileIntoA(args[2]);
                EmitAsm("LD_C_A"); // tile

                // Restore y -> A, x -> B
                EmitAsm("POP_HL");
                EmitAsm("LD_A_L");
                EmitAsm("LD_D_A"); // y temp

                EmitAsm("POP_HL");
                EmitAsm("LD_A_L");
                EmitAsm("LD_B_A"); // x

                EmitAsm("LD_A_D"); // y

                UsedSetTileFastCore = true;
                EmitAsm("CALL", new AsmOperand("__settile_fast_core", AddressMode.Absolute));
                return;
            }

            // 2.6b __settile_bulk(dest_u16, src_u16, count_u8)
            // v9: bulk tile-map copy (LCD ON safe with wait+DI/EI inside core).
            if (funcName == "__settile_bulk")
            {
                if (args.Length != 3) Error(expr, "__settile_bulk requires 3 arguments");

                Expr bulkCountExpr = FoldConstants(args[2]);

                // dest
__settilebg16_buf

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.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

buf

A RAM buffer with 32 bytes per tile row. A full 32×32 map needs 1024 bytes. The upper-left byte is at 64*y + 2*x; the four offsets are 0,1,32,33 from there.

x, y

Coordinates in 16×16-pixel cells, each from 0 to 15 for a full 32×32-tile buffer. These differ from the 8×8-tile coordinates used by scalar setters.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

The visible cell contains AB above CD, at tile coordinates (2,2). This is one 16×16-pixel cell at cell coordinates (1,1). Buffer examples first verify that RAM preparation leaves VRAM unchanged, then flush the cell; flush examples prepare the RAM bytes explicitly before transferring them.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=2), or pixel coordinates (16,16). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
AB
CD
__settilebg16_buf: DMG
DMG
__settilebg16_buf: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16_buf.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16_buf.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10564

csharp
{
                if (args.Length != 4) Error(expr, "__settilebg16_buf requires 4 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[1], args[2], args[3], incrementQuad: true, labelPrefix: "settilebg16buf");
                return;
            }

            // Update tile and attribute RAM buffers separately, repeating the attribute across the cell.
            if (funcName == "__settilebg16cgb_buf")
            {
                if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
                EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
                return;
            }

            // Flush both tile rows belonging to the requested buffered cell row.
            if (funcName == "__settilebg16_flush")
            {
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIn
__settilebg16_flush

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.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

buf

Readable tile-map buffer with a 32-byte row stride. A full map occupies 1024 bytes. The two-buffer form takes separate tile-number and attribute arrays with the same layout; it does not interleave them.

y, x, count

Argument order is y,x,count. y is a 16-pixel cell row (0–15); x and count are 8-pixel tile units. Keep x+count <= 32. For one cell at (2,3), use y=3,x=4,count=2.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

The visible cell contains AB above CD, at tile coordinates (2,2). This is one 16×16-pixel cell at cell coordinates (1,1). Buffer examples first verify that RAM preparation leaves VRAM unchanged, then flush the cell; flush examples prepare the RAM bytes explicitly before transferring them.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=2), or pixel coordinates (16,16). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
AB
CD
__settilebg16_flush: DMG
DMG
__settilebg16_flush: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16_flush.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16_flush.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10581

csharp
{
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
                    AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
                    EmitRuntimeCgbCheckCall();
                    EmitAsm("OR_A");
                    // Two unrolled row copies can exceed the relative-branch range.
                    EmitAsm("JP_Z", skip);
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                    EmitAsm("JR", done);
__settilebg16cgb_buf

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.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

tilebuf, attrbuf

A RAM buffer with 32 bytes per tile row. A full 32×32 map needs 1024 bytes. The upper-left byte is at 64*y + 2*x; the four offsets are 0,1,32,33 from there.

x, y

Coordinates in 16×16-pixel cells, each from 0 to 15 for a full 32×32-tile buffer. These differ from the 8×8-tile coordinates used by scalar setters.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

attr

CGB attribute byte: bits 0–2 select palette 0–7; bit 3 selects tile-data bank; bits 5 and 6 flip horizontally and vertically; bit 7 sets BG/window priority. Keep unused bit 4 zero.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

The visible cell contains AB above CD, at tile coordinates (2,2). This is one 16×16-pixel cell at cell coordinates (1,1). Buffer examples first verify that RAM preparation leaves VRAM unchanged, then flush the cell; flush examples prepare the RAM bytes explicitly before transferring them.

All four characters are blue on CGB because the four attribute bytes are 2. They are black on DMG.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=2), or pixel coordinates (16,16). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
AB
CD
__settilebg16cgb_buf: DMG
DMG
__settilebg16cgb_buf: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16cgb_buf.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16cgb_buf.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10572

csharp
{
                if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
                EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
                EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
                return;
            }

            // Flush both tile rows belonging to the requested buffered cell row.
            if (funcName == "__settilebg16_flush")
            {
                if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
                return;
            }

            // Flush the tile buffer first and copy attributes only when CGB hardware is available.
            if (funcName == "__settilebg16cgb_flush")
            {
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand ski
__settilebg16cgb_flush

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.

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.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

tilebuf, attrbuf

Readable tile-map buffer with a 32-byte row stride. A full map occupies 1024 bytes. The two-buffer form takes separate tile-number and attribute arrays with the same layout; it does not interleave them.

y, x, count

Argument order is y,x,count. y is a 16-pixel cell row (0–15); x and count are 8-pixel tile units. Keep x+count <= 32. For one cell at (2,3), use y=3,x=4,count=2.

Return value

None (void).

Conditions and limits

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

c
// __settilebg16cgb_flush
// Transfer two adjacent tile rows from the RAM buffer to the LCDC-selected BG map. The
// source offset is `64*y + x`; `count` bytes are copied from that row and from the row
// 32 bytes below it. Write a tile number and its CGB attribute. On DMG, only the tile-
// number write is performed. On CGB, enter with VRAM bank 0 selected; attributes use
// bank 1 and the call finishes with `VBK=0`.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_example.h"
u8 example_tiles[1024];
u8 example_attrs[1024];
void __memset(void* dest, u8 value, u16 len);
void __settilebg16_buf(u8* buf, u8 x, u8 y, u8 tile);
void __settilebg16cgb_buf(u8* tilebuf, u8* attrbuf, u8 x, u8 y, u8 tile, u8 attr);
void __settilebg16_flush(const u8* buf, u8 y, u8 x, u8 count);
void __settilebg16cgb_flush(const u8* tilebuf, const u8* attrbuf, u8 y, u8 x, u8 count);

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

The visible cell contains AB above CD, at tile coordinates (2,2). This is one 16×16-pixel cell at cell coordinates (1,1). Buffer examples first verify that RAM preparation leaves VRAM unchanged, then flush the cell; flush examples prepare the RAM bytes explicitly before transferring them.

All four characters are blue on CGB because the four attribute bytes are 2. They are black on DMG.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=2), or pixel coordinates (16,16). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
AB
CD
__settilebg16cgb_flush: DMG
DMG
__settilebg16cgb_flush: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilebg16cgb_flush.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilebg16cgb_flush.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/kitaqgb/CodeGenerator.cs:10589

csharp
{
                if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
                EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
                if (IsKnownCgbRuntimeTrue())
                {
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                }
                else
                {
                    AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
                    AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
                    EmitRuntimeCgbCheckCall();
                    EmitAsm("OR_A");
                    // Two unrolled row copies can exceed the relative-branch range.
                    EmitAsm("JP_Z", skip);
                    EmitSetVbkUnchecked(1);
                    EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
                    EmitAsm("JR", done);
                    EmitLabel(skip);
                    EmitLabel(done);
                }
                return;
            }

            // 2.14 __getbgmapbase() -> u16 in HL
// Returns the BG tilemap base address selected by LCDC bit3:
// bit3=0 => 0x9800, bit3=1 => 0x9C00
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__getbgmapbase")
{
    if (args.Length != 0) Error(expr, "__getb
__smac16

Compute a signed multiply-accumulate.

Syntax

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

Parameters

acc

16-bit accumulator to add.

a

Signed 16-bit bit pattern, representing −32768..32767.

b

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is trunc(a*b/256), added to acc; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[26]=92, result[27]=92, result[28]=65372, result[29]=65372. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__smac16: DMG
DMG
__smac16: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11516

kitaqgb/kitaqgb/CodeGenerator.cs:11516

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

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

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

Compute a signed multiply-accumulate.

Syntax

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

Parameters

acc

16-bit accumulator to add.

a

Signed 16-bit bit pattern, representing −32768..32767.

b

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is 2*trunc(a*b/256), added to acc; returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[0]=220, result[1]=220, result[2]=65244, result[3]=65244. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__smac16_q1_7: DMG
DMG
__smac16_q1_7: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11533

kitaqgb/kitaqgb/CodeGenerator.cs:11533

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

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

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

Compute a signed multiplication.

Syntax

c
u16 __smul16x8_q1_7(u16 a, u8 b);

Parameters

a

Signed 16-bit bit pattern, representing −32768..32767.

b

Signed byte bit pattern, coefficient −128..127.

Return value

Each term is 2*trunc(a*b/256); returns the low 16 bits.

Conditions and limits

GB products are divided by 256; signed results truncate toward zero. Q1.7 variants double that truncated result and can differ by one from exact division by 128. Terms are rounded separately; final addition wraps at 16 bits.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Compare literal inputs with values stored in RAM. Representative results appear as unsigned 16-bit values; negatives use two’s-complement bit patterns. GB rescales fixed-point products, while FC returns integer products, so identical inputs need not give identical results.

The excerpt stores these 16-bit values: result[12]=256, result[13]=256, result[14]=65280, result[15]=65280. Each literal-input calculation matches its RAM-input counterpart. The screen shows representative values; the result array holds every result.

__smul16x8_q1_7: DMG
DMG
__smul16x8_q1_7: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:11490

kitaqgb/kitaqgb/CodeGenerator.cs:11490

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

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

Read one SRAM byte at a CPU address.

Syntax

c
u8 __sram_read8(u16 address);

Parameters

address

CPU address in $A000..$BFFF.

Return value

Returns the byte without checking RAM presence or data validity.

Conditions and limits

Writes 0x0A to $0000 to enable RAM before access, then writes zero to disable it afterward. Does not select the bank or validate the address; the caller configures the RAM bank. No save header or checksum is managed.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Save three bytes, 7/42/255, then test length mismatch, an invalid slot and a damaged checksum. A successful load yields middle byte 42; a failed read preserves destination 77; validation after clearing returns 0.

__sram_read8: DMG
DMG
__sram_read8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:8825

kitaqgb/kitaqgb/CodeGenerator.cs:8825

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

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

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

                EmitAsm("POP_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A");

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

Write one SRAM byte at a CPU address.

Syntax

c
void __sram_write8(u16 address, u8 value);

Parameters

address

CPU address in $A000..$BFFF.

value

Byte value 0..255.

Return value

No return value.

Conditions and limits

Writes 0x0A to $0000 to enable RAM before access, then writes zero to disable it afterward. Does not select the bank or validate the address; the caller configures the RAM bank. No save header or checksum is managed.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Save three bytes, 7/42/255, then test length mismatch, an invalid slot and a damaged checksum. A successful load yields middle byte 42; a failed read preserves destination 77; validation after clearing returns 0.

__sram_write8: DMG
DMG
__sram_write8: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:8846

kitaqgb/kitaqgb/CodeGenerator.cs:8846

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

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

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

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

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

Read the effective WRAM bank mapped at $D000–$DFFF. The compiler reads SVBK bits 0–2 and converts raw selector 0 to bank 1. This returns a bank number, not RAM capacity, an address or a pointer.

Syntax

c
u8 __svbk_get();

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Return value

Current effective bank number as u8, 1–7; raw selector 0 is reported as 1.

Conditions and limits

__svbk_get and __svbk_set require --cgb=cgb_only or #pragma rom_cgb cgb_only. DMG and dual-mode builds reject them with KQ2103, even if a runtime condition would skip the call. Use the guarded raw setter only when its different contract fits your program.

Switching banks changes every access in $D000–$DFFF; fixed WRAM at $C000–$CFFF stays mapped. Keep the stack and all state needed during the switch outside the switched window. The sample uses --stack-bank=fixed and explicitly located fixed-WRAM state. Coordinate interrupts and banked DMA with the switch; these APIs add no critical section.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete CGB-only program stores 1 at $D100 in WRAM bank 2 and 2 at the same address in bank 3, then reads both back and restores bank 1. The values choose an 8 × 8 red square at (16,24) and a blue one-pixel 8 × 8 square outline at (16,48). FAILED CHECKS 000 checks raw-selector normalization, bank separation and the previous-bank return for constant, variable, arithmetic and function-result arguments. Six separate invalid-build fixtures verify KQ2103 for DMG/dual targets and KQ2102 for constant banks 0 and 8. Runtime verification uses KOKURA; real hardware and interrupt-driven concurrent bank changes are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7387

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

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

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

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

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

Switch the WRAM bank mapped at $D000–$DFFF and return the effective bank selected before the switch. Save this return value if you need to restore the caller's bank. The memory contents are not copied or cleared, and the function does not restore the bank automatically.

Syntax

c
u8 __svbk_set(u8 bank);

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

bank

Requested bank as u8, 1–7. Constant values outside this range produce KQ2102. Dynamic values are not range-checked by this intrinsic; validate them before calling rather than relying on register masking.

Return value

Effective bank number before the switch as u8, 1–7. This is the old bank, not the newly selected bank.

Conditions and limits

__svbk_get and __svbk_set require --cgb=cgb_only or #pragma rom_cgb cgb_only. DMG and dual-mode builds reject them with KQ2103, even if a runtime condition would skip the call. Use the guarded raw setter only when its different contract fits your program.

Switching banks changes every access in $D000–$DFFF; fixed WRAM at $C000–$CFFF stays mapped. Keep the stack and all state needed during the switch outside the switched window. The sample uses --stack-bank=fixed and explicitly located fixed-WRAM state. Coordinate interrupts and banked DMA with the switch; these APIs add no critical section.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete CGB-only program stores 1 at $D100 in WRAM bank 2 and 2 at the same address in bank 3, then reads both back and restores bank 1. The values choose an 8 × 8 red square at (16,24) and a blue one-pixel 8 × 8 square outline at (16,48). FAILED CHECKS 000 checks raw-selector normalization, bank separation and the previous-bank return for constant, variable, arithmetic and function-result arguments. Six separate invalid-build fixtures verify KQ2103 for DMG/dual targets and KQ2102 for constant banks 0 and 8. Runtime verification uses KOKURA; real hardware and interrupt-driven concurrent bank changes are not tested.

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

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7397

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

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

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

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

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

Copy len bytes from src to dst immediately, in increasing address order. The function returns after the copy finishes; it does not enqueue work. Source and destination advance by one byte, without tilemap row strides or tile-format conversion.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

src

Source byte pointer, valid and mapped for the whole call. Data is read immediately; it is not retained for a later flush. The source bank is not switched automatically. Use non-overlapping spans: this forward copy does not provide memmove semantics.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

Skip all LCD/STAT waiting and write directly with the CPU. Keep the LCD off, or ensure the entire transfer fits in a writable interval you control. Merely entering VBlank does not make an arbitrarily long transfer safe. This intrinsic emits neither DI nor EI for the transfer and provides no clipping or bounds checks.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_memcpy_unsafe to place three map bytes at tile (2,6), pixel (16,48). Expect the three patterns [1,2,3] from left to right in a 24 × 8 area. Neighboring cells must remain blank. The full program places each tested copy or fill on its own row.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_memcpy_unsafe: DMG
DMG
__vram_memcpy_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:8104

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

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

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

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

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

Immediately fill len consecutive bytes starting at dst with the byte value. This repeats one byte, not a whole tile pattern. A map-byte fill repeats a tile number; a pattern-byte fill instead changes the encoded pixel planes.

Syntax

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

The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

value

One byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

Skip all LCD/STAT waiting and write directly with the CPU. Keep the LCD off, or ensure the entire transfer fits in a writable interval you control. Merely entering VBlank does not make an arbitrarily long transfer safe. This intrinsic emits neither DI nor EI for the transfer and provides no clipping or bounds checks.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_memset_unsafe to fill three map bytes at tile (2,10), pixel (16,80), with tile number 1. Expect a solid 24 × 8 bar with blank neighboring cells. The count is computed from variables or a function result; all three bytes must still receive value 1.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_memset_unsafe: DMG
DMG
__vram_memset_unsafe: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:8196

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

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

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

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

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

                CompileIntoHL(
__wait_ly

With LCD running, wait until LY equals the target line.

Syntax

c
void __wait_ly(u8 ly);

Parameters

ly

Scanline 0..153; values above 153 do not complete while the LCD runs.

Return value

No return value.

Conditions and limits

Returns immediately on the target line; if that line has passed, waits until the next frame. Also returns immediately with LCD off.

Usage example

c
M_LCDC=0x91;__wait_ly(80);result[9]=*((u8*)0xFF44);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__wait_ly: DMG
DMG
__wait_ly: CGB
CGB

Complete example program

Build command

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

kitaqgb/kitaqgb/CodeGenerator.cs:7483

kitaqgb/kitaqgb/CodeGenerator.cs:7483

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

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

                AsmOperand wlyLoop = MakeUniqueLabel("waitly_loop");
                AsmOperand wlyDone = MakeUniqueLabel("waitly_done");

                // If LCD is off, LY is not meaningful; return.
                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
                EmitAsm("JR_Z", wlyDone);

                EmitLabel(wlyLoop);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_B");
                EmitAsm("JR_NZ", wlyLoop);
                EmitLabel(wlyDone);
                return;
            }

physics2d.h — 2D motion and collision

Provides box integration/AABB contacts and an optional surface-response solver. Bodies use centers and half extents; active=0 excludes a slot and inv_mass_q8<=0 makes it static. KQRect instead uses top-left coordinates and width/height. Box-pair contacts do not use the body restitution field; surface responses use KQSurface2D coefficients.

Choose consistent integer units for positions, radii, half extents, velocities and gravity. One step is one simulation tick. The samples use one unit per pixel. These routines neither convert positions to Q8.8 automatically nor render them.

A world borrows its array. The caller allocates and owns body storage and keeps it alive. Initializing a world does not initialize its array elements.

__smul16x8

Multiply a signed 16-bit value by a signed 8-bit coefficient, divide by 256 and truncate toward zero.

Syntax

c
s16 __smul16x8(s16 a, s8 b);

Parameters

a

Signed 16-bit value to scale.

b

Signed byte coefficient in -128..127.

Return value

Returns the signed 16-bit scaled product.

Conditions and limits

The coefficient range is -128..127, scaled with 256 representing 1.0. Products truncate toward zero for either sign: -257*64/256 returns -64.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Left shows the initial state; right shows one tick later. A red 16x16 box with center (24,24) and velocity (4,16) reaches (28,36), with Y velocity zero on the blue floor. Panel origins are screen (8,48) and (88,48). DMG renders all marks in black. RAM checks also cover initialization, setters, integration, rectangle/point queries and signed scaling.

__smul16x8: DMG — Left shows the initial state; right shows one tick later. A red 16x16 box with center (24,24) and velocity (4,16) reaches (28,36), with Y velocity zero on the blue floor. Panel origins are screen (8,48) and (88,48). DMG renders all marks in black. RAM checks also cover initialization, setters, integration, rectangle/point queries and signed scaling.
DMG — Left shows the initial state; right shows one tick later. A red 16x16 box with center (24,24) and velocity (4,16) reaches (28,36), with Y velocity zero on the blue floor. Panel origins are screen (8,48) and (88,48). DMG renders all marks in black. RAM checks also cover initialization, setters, integration, rectangle/point queries and signed scaling.
__smul16x8: CGB — Left shows the initial state; right shows one tick later. A red 16x16 box with center (24,24) and velocity (4,16) reaches (28,36), with Y velocity zero on the blue floor. Panel origins are screen (8,48) and (88,48). DMG renders all marks in black. RAM checks also cover initialization, setters, integration, rectangle/point queries and signed scaling.
CGB — Left shows the initial state; right shows one tick later. A red 16x16 box with center (24,24) and velocity (4,16) reaches (28,36), with Y velocity zero on the blue floor. Panel origins are screen (8,48) and (88,48). DMG renders all marks in black. RAM checks also cover initialization, setters, integration, rectangle/point queries and signed scaling.

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\physics_body2d.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\physics_body2d.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --cart=mbc5 --romsize=128k --no-cache --no-disasm
Implementation and source

kitaqgb/lib/physics2d.h:9

Declaration notes (original source)
Multiply a signed word by a signed byte; divide by 256, truncating toward zero.

kitaqgb/kitaqgb/CodeGenerator.cs:11478

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

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

rpg.h — Random numbers, text, maps, saves and RPG/ADV/SLG services

Absolute text helpers draw directly on the background map. text_window opens a framed area, text_clear_rect fills a region, and text_print_xy writes tile bytes without control decoding. text_print_u8, text_print_u16 and text_print_s16 convert numbers to decimal with only the required cells. Reserve and clear a full field when replacing values of different lengths.

The RNG functions declared in rpg.h are implemented in lib/rng.c. They share the compiler's GB generator: rng_seed resets it, rng8/rng_next8 consume one byte, rng16/rng_next16 consume two, and rand_range/rng_range, rng_chance and weighted_choice adapt draws for gameplay choices. Compile rng.c with your program when calling these library functions. For reproducible tests, keep the seed and call order fixed; modulo-based choices do not promise exact probabilities.

RLE decoding uses count/value byte pairs and a zero count terminator. rle_decode reads an ordinary pointer into RAM; rle_decode_far reads a ROM-bank-qualified pointer into RAM; __rle_decode_vram expands banked input directly to VRAM with LCD-aware stores. The RAM functions live in lib/rle.c, while the direct VRAM operation is compiler-generated. All three return output bytes and require a valid stream and enough destination space.

Game-state storage declared in rpg.h is implemented by lib/flags.c. The three flag functions operate on 2048 independent Boolean bits; quest_state and quest_set_state operate on 64 independent bytes for game-defined stages. Compile flags.c when using them, keep IDs within those limits, and use a separate save system if progress must survive power-off.

Windowed dialogue shares one active rectangle, cursor and character-speed setting. text_open initializes the area, text_close erases it, and text_set_speed sets per-character VBlank waits. text_print and text_print_far interpret control streams; text_choice runs a blocking vertical selector. Load the tiles, fit the area and plan how the game handles these blocking calls.

Provides map/metatile drawing, scripts, SRAM saves and pathfinding. Ordinary cells are 8×8 pixels; metatiles are 16×16. Loading/drawing, movement masks and attack masks are separate operations; the game coordinates graphics loading and event execution.

Search uses about 4 KiB of shared, non-reentrant workspace. Limit maps to 1024 cells and explored distances to 254. Distance 0xFF means unreached, so a 255-step route is not representable. Cost/mask outputs need width*height writable bytes.

Defaults to four 512-byte slots, each with a six-byte header and up to 506 payload bytes. The sample uses MBC5 with 8 KiB SRAM. Calls finish with RAM disabled and do not restore its previous bank or enable state.

Menus support both blocking selection and separate input/draw calls driven by the game loop. The inventory menu displays item names and quantities.

__bit_clear

Clear the selected flag to 0 without clearing the rest of its byte. Use this to remove one stored condition, such as a temporary status flag. Clearing the same bit again still leaves it at 0.

Syntax

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

Parameters

p

Pointer to the first byte of the flag array in CPU-accessible memory. The array is not allocated or initialized by this call. Use writable RAM for set, clear and toggle operations; the test operation only reads it.

bit_index

Zero-based bit index as u16 (0–65535), not a byte offset or a bit mask. Index 0 selects mask 0x01 in byte 0; index 7 selects 0x80 in byte 0; index 8 selects 0x01 in byte 1. In general, the byte offset is index / 8 and the mask is 1 << (index % 8). For an N-byte array, require index < 8*N and a valid CPU address range.

Return value

None (void).

Conditions and limits

The caller must provide enough storage. There is no array-length argument, bounds clipping, bank switching or automatic graphics upload. A large bit number can select a byte well beyond the allocated array. Keep the memory mapping stable; do not use these RAM-flag helpers as a shortcut for hardware registers whose reads or writes have side effects.

Set, clear and toggle read a byte, modify the selected bit, then write the byte back. This sequence is not an atomic update. Coordinate writers in the main loop and interrupt handlers so that an intervening update to another bit in the same byte is not lost.

Usage example

c
__bit_clear(bit_flags,2);
bit_clear_value=bit_flags[0]; // A6 & FB = A2, decimal 162.
__bit_clear(bit_flags,2);     // Clearing it twice leaves it clear.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program reaches this step with byte 0 equal to 0xA6. Clearing bit 2 changes it to 0xA2 (binary 10100010, decimal 162); the CLEAR BIT2 row must show 162. Clearing bit 2 again leaves the result unchanged. The other stored flags survive both calls.

Expected GB rows, in decimal: TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE comes from setting index 9 in the second byte, changing 0x80 to 0x82 without modifying byte 0. DMG uses black text on white; CGB uses blue text on white.

The program checks every displayed transition and shows the number of failures. Set, clear and toggle themselves return no value; their rows display the resulting RAM byte. Separate emulator checks compare all 520 bytes of guarded buffers in 136 cases per platform, including constant and runtime indices across byte and 256-byte boundaries and all eight possible nonzero bit-test masks. These are RAM-state checks, not a drawing demonstration. Physical hardware and concurrent interrupt writers have not been tested.

__bit_clear: DMG
DMG
__bit_clear: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:187

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_set

Set the selected flag to 1 while preserving the other seven bits in the same byte. For example, mark a quest as completed or an item as collected. Setting a bit that is already 1 leaves it unchanged; this is not a toggle.

Syntax

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

Parameters

p

Pointer to the first byte of the flag array in CPU-accessible memory. The array is not allocated or initialized by this call. Use writable RAM for set, clear and toggle operations; the test operation only reads it.

bit_index

Zero-based bit index as u16 (0–65535), not a byte offset or a bit mask. Index 0 selects mask 0x01 in byte 0; index 7 selects 0x80 in byte 0; index 8 selects 0x01 in byte 1. In general, the byte offset is index / 8 and the mask is 1 << (index % 8). For an N-byte array, require index < 8*N and a valid CPU address range.

Return value

None (void).

Conditions and limits

The caller must provide enough storage. There is no array-length argument, bounds clipping, bank switching or automatic graphics upload. A large bit number can select a byte well beyond the allocated array. Keep the memory mapping stable; do not use these RAM-flag helpers as a shortcut for hardware registers whose reads or writes have side effects.

Set, clear and toggle read a byte, modify the selected bit, then write the byte back. This sequence is not an atomic update. Coordinate writers in the main loop and interrupt handlers so that an intervening update to another bit in the same byte is not lost.

Usage example

c
__bit_set(bit_flags,1);
bit_set_value=bit_flags[0]; // A4 | 02 = A6, decimal 166.
__bit_set(bit_flags,1);     // Setting it twice leaves it set.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Set bit 1 of 0xA4: 10100100 | 00000010 becomes 10100110, or 0xA6 (decimal 166). The SET BIT1 row must show 166. The sample sets the bit a second time and verifies that the byte remains 0xA6, demonstrating that repeated setting does not invert a flag.

Expected GB rows, in decimal: TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE comes from setting index 9 in the second byte, changing 0x80 to 0x82 without modifying byte 0. DMG uses black text on white; CGB uses blue text on white.

The program checks every displayed transition and shows the number of failures. Set, clear and toggle themselves return no value; their rows display the resulting RAM byte. Separate emulator checks compare all 520 bytes of guarded buffers in 136 cases per platform, including constant and runtime indices across byte and 256-byte boundaries and all eight possible nonzero bit-test masks. These are RAM-state checks, not a drawing demonstration. Physical hardware and concurrent interrupt writers have not been tested.

__bit_set: DMG
DMG
__bit_set: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:186

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_test

Read one flag from a packed array of bits without modifying the array. This lets eight independent on/off flags share each byte of RAM. The result tells you whether the selected bit is clear or set; its numeric representation depends on the target.

Syntax

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

Parameters

p

Pointer to the first byte of the flag array in CPU-accessible memory. The array is not allocated or initialized by this call. Use writable RAM for set, clear and toggle operations; the test operation only reads it.

bit_index

Zero-based bit index as u16 (0–65535), not a byte offset or a bit mask. Index 0 selects mask 0x01 in byte 0; index 7 selects 0x80 in byte 0; index 8 selects 0x01 in byte 1. In general, the byte offset is index / 8 and the mask is 1 << (index % 8). For an N-byte array, require index < 8*N and a valid CPU address range.

Return value

u8: exactly 0 when the bit is clear, or 1 when it is set. The GB implementation normalizes the masked byte to these two values.

Conditions and limits

The caller must provide enough storage. There is no array-length argument, bounds clipping, bank switching or automatic graphics upload. A large bit number can select a byte well beyond the allocated array. Keep the memory mapping stable; do not use these RAM-flag helpers as a shortcut for hardware registers whose reads or writes have side effects.

Usage example

c
bit_off=__bit_test(bit_flags,1); // Bit 1 of A4 is clear: zero.
bit_on=__bit_test(bit_flags,7);  // Bit 7 is set: GB returns 1; FC returns 128.
bit_is_on=0;
if(bit_on!=0)bit_is_on=1; // The same nonzero test works on both targets.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Start with byte 0 equal to 0xA4 (binary 10100100). Testing bit 1 returns 0. Testing bit 7 returns 1 on GB or 128 on FC; the sample also converts that nonzero result to a shared on/off flag and checks that the original byte is unchanged. Look at TEST OFF and TEST ON in the screen below.

Expected GB rows, in decimal: TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE comes from setting index 9 in the second byte, changing 0x80 to 0x82 without modifying byte 0. DMG uses black text on white; CGB uses blue text on white.

The program checks every displayed transition and shows the number of failures. Set, clear and toggle themselves return no value; their rows display the resulting RAM byte. Separate emulator checks compare all 520 bytes of guarded buffers in 136 cases per platform, including constant and runtime indices across byte and 256-byte boundaries and all eight possible nonzero bit-test masks. These are RAM-state checks, not a drawing demonstration. Physical hardware and concurrent interrupt writers have not been tested.

__bit_test: DMG
DMG
__bit_test: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:185

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__bit_toggle

Invert the selected flag: 0 becomes 1 and 1 becomes 0. The remaining bits are preserved. Two calls on the same bit restore its original state, provided nothing else writes that bit between the calls.

Syntax

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

Parameters

p

Pointer to the first byte of the flag array in CPU-accessible memory. The array is not allocated or initialized by this call. Use writable RAM for set, clear and toggle operations; the test operation only reads it.

bit_index

Zero-based bit index as u16 (0–65535), not a byte offset or a bit mask. Index 0 selects mask 0x01 in byte 0; index 7 selects 0x80 in byte 0; index 8 selects 0x01 in byte 1. In general, the byte offset is index / 8 and the mask is 1 << (index % 8). For an N-byte array, require index < 8*N and a valid CPU address range.

Return value

None (void).

Conditions and limits

The caller must provide enough storage. There is no array-length argument, bounds clipping, bank switching or automatic graphics upload. A large bit number can select a byte well beyond the allocated array. Keep the memory mapping stable; do not use these RAM-flag helpers as a shortcut for hardware registers whose reads or writes have side effects.

Set, clear and toggle read a byte, modify the selected bit, then write the byte back. This sequence is not an atomic update. Coordinate writers in the main loop and interrupt handlers so that an intervening update to another bit in the same byte is not lost.

Usage example

c
__bit_toggle(bit_flags,7);
bit_toggle_value=bit_flags[0]; // A2 ^ 80 = 22, decimal 34.
__bit_toggle(bit_flags,7);
bit_restore_value=bit_flags[0]; // Two toggles restore A2.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The complete program enters this step with 0xA2. Toggling bit 7 produces 0x22 (decimal 34), so TOGGLE BIT7 shows 034. Toggling it again restores 0xA2, and RESTORE shows 162. These two observations demonstrate both directions of the inversion while preserving the other bits.

Expected GB rows, in decimal: TEST OFF 000, TEST ON 001, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE comes from setting index 9 in the second byte, changing 0x80 to 0x82 without modifying byte 0. DMG uses black text on white; CGB uses blue text on white.

The program checks every displayed transition and shows the number of failures. Set, clear and toggle themselves return no value; their rows display the resulting RAM byte. Separate emulator checks compare all 520 bytes of guarded buffers in 136 cases per platform, including constant and runtime indices across byte and 256-byte boundaries and all eight possible nonzero bit-test masks. These are RAM-state checks, not a drawing demonstration. Physical hardware and concurrent interrupt writers have not been tested.

__bit_toggle: DMG
DMG
__bit_toggle: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\bit_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:188

kitaqgb/kitaqgb/CodeGenerator.cs:8582

csharp
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
            {
                if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
                EmitAsm("LD_B_A"); // bit number 0..7

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E");
                EmitShiftRightLogicalHL(3);
                EmitAsm("PUSH_HL"); // byte offset

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

                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)

                AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
                AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
                EmitAsm("LD_A_B");
                EmitAsm("OR_A");
                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR_Z", maskReady);
                EmitLabel(maskLoop);
                EmitAsm("ADD_A");
                EmitAsm("DEC_B");
                EmitAsm("JR_NZ", maskLoop);
                EmitLabel(maskReady);
                EmitAsm("LD_B_A"); // mask

                if (funcName == "__bit_test")
                {
                    AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
                    AsmOperand done = MakeUniqueLabel("bit_test_done");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("JR_Z", retZero);
                    EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    EmitAsm("JR", done);
                    EmitLabel(retZero);
                    EmitAsm("XOR_A");
                    EmitLabel(done);
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_set")
                {
                    EmitAsm("LD_A_HL");
                    EmitAsm("OR_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                if (funcName == "__bit_clear")
                {
                    EmitAsm("LD_A_B");
                    EmitAsm("CPL");
                    EmitAsm("LD_B_A");
                    EmitAsm("LD_A_HL");
                    EmitAsm("AND_B");
                    EmitAsm("LD_HL_A");
                    EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                    return;
                }

                EmitAsm("LD_A_HL");
                EmitAsm("XOR_B");
                EmitAsm("LD_HL_A");
                EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                return;
            }
__copy16

Copy exactly 16 bytes from src to dst, with no length argument. For example, copy one 16-byte record or one Game Boy/NES tile stored in CPU-accessible memory. The number 16 means bytes, not bits; this call does not upload graphics to the display.

Syntax

c
void __copy16(void* dst, const void* src);

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

src

Pointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.

Return value

None (void).

Conditions and limits

Use separate, non-overlapping source and destination ranges. These routines copy forward and do not provide overlap-safe memmove behavior; writing into the unread part of the source can corrupt later bytes.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __copy16 to copy 16 patterned bytes starting at offset 1. memory_check compares the entire source and destination arrays; the COPY16 row must show 016. Starting inside the buffer leaves a leading guard byte, and bytes after the payload must also remain unchanged.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__copy16: DMG
DMG
__copy16: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:177

kitaqgb/kitaqgb/CodeGenerator.cs:8247

csharp
if (funcName == "__copy16" || funcName == "__copy32")
            {
                if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");

                int copyLen = (funcName == "__copy16") ? 16 : 32;
                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst
                EmitRamMemcpyLoop(funcName.TrimStart('_'));
                return;
            }

void EmitRamMemcpyLoop(string labelPrefix)
    {
        AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
        AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");

        EmitLabel(loop);
        EmitAsm("LD_A_B");
        EmitAsm("OR_C");
        EmitAsm("JR_Z", done);
        EmitAsm("LD_A_DE");
        EmitAsm("LDI_HL_A");
        EmitAsm("INC_DE");
        EmitAsm("DEC_BC");
        EmitAsm("JR", loop);
        EmitLabel(done);
    }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__copy32

Copy exactly 32 bytes from src to dst, with no length argument. Use it for a fixed 32-byte record or two adjacent 16-byte tile patterns in CPU-accessible memory. It performs a forward byte copy, not a single 32-bit load or a banked transfer.

Syntax

c
void __copy32(void* dst, const void* src);

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

src

Pointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.

Return value

None (void).

Conditions and limits

Use separate, non-overlapping source and destination ranges. These routines copy forward and do not provide overlap-safe memmove behavior; writing into the unread part of the source can corrupt later bytes.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __copy32 to copy 32 patterned bytes starting at offset 1. memory_check compares the entire source and destination arrays; the COPY32 row must show 032. Starting inside the buffer leaves a leading guard byte, and bytes after the payload must also remain unchanged.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__copy32: DMG
DMG
__copy32: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:178

kitaqgb/kitaqgb/CodeGenerator.cs:8247

csharp
if (funcName == "__copy16" || funcName == "__copy32")
            {
                if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");

                int copyLen = (funcName == "__copy16") ? 16 : 32;
                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
                EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
                EmitAsm("POP_DE"); // src
                EmitAsm("POP_HL"); // dst
                EmitRamMemcpyLoop(funcName.TrimStart('_'));
                return;
            }

void EmitRamMemcpyLoop(string labelPrefix)
    {
        AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
        AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");

        EmitLabel(loop);
        EmitAsm("LD_A_B");
        EmitAsm("OR_C");
        EmitAsm("JR_Z", done);
        EmitAsm("LD_A_DE");
        EmitAsm("LDI_HL_A");
        EmitAsm("INC_DE");
        EmitAsm("DEC_BC");
        EmitAsm("JR", loop);
        EmitLabel(done);
    }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__far_memcpy

Temporarily select a ROM bank, copy len bytes from src to dst in increasing address order, then restore the compiler’s saved ROM bank. __farmemcpy and __far_memcpy use the same implementation on this platform.

Syntax

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

Parameters

dst

Destination in writable memory. Reserve enough space for every byte copied. Except for a zero-byte transfer, a null or invalid destination is not checked.

bank

8-bit bank selector for the source ROM mapping. Use a bank supported by the cartridge and the compiler’s bank layout; this argument is separate from the source address.

src

Near pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.

len

Byte count, 0–65535. Zero performs no memory copy, but the bank-selection and restoration steps still run.

Return value

None (void).

Conditions and limits

Keep the full source range in the intended ROM mapping and the full destination range in writable memory. Copying does not select the next bank at a window boundary, handle overlapping ranges like memmove, or provide VRAM-safe writes. Use the graphics transfer APIs for VRAM.

The samples place calling code in fixed bank 0 so source-bank changes cannot unmap the code being executed. These transfers are synchronous. Do not interrupt a transfer with another bank-changing copy or assume the helper makes a whole application interrupt-safe.

The GB helper restores the compiler’s ROM-bank shadow. Use __bankswitch to maintain it; raw writes to the mapper do not update that shadow. Its saved-bank slot in HRAM is shared, so nested transfers that overwrite it are unsafe.

Usage example

c
__far_memcpy(buffer,2,asset_shapes,32); // Same backend as __farmemcpy.
expect(*asset_bank_marker==0xA1,29);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),32);
buffer[32]=0x55;
__far_memcpy(buffer+32,2,asset_shapes,0); // Zero count writes no bytes and restores the bank.
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Copy two patterns, 32 bytes, from payload bank 2 while bank 1 is initially selected. The program compares every copied byte, checks that bank 1 is restored and verifies that a zero-length copy leaves a 0x55 sentinel unchanged.

Expected image: a red row starts at pixel (16,32) and spans 72×8 pixels. It repeats an 8×8 triangle, an 8×8 square outline with a one-pixel border, and a tile with its left four columns filled, three times. The triangle grows from one pixel at the top left to eight pixels on its bottom row.

On GB, a blue triangle and outline at (16,64) show the __farmemcpy result; a green pair at (16,96) shows __far_memcpy. Each pair occupies 16×8 pixels. CGB shows these colors; DMG shows the same shapes in black on white.

Expected status: FAILED CHECKS 000 and FIRST FAILURE 00. The verification runs 300 frames and compares text, shape masks and colors against these expectations. This is emulator evidence, not a physical-hardware test.

__far_memcpy: DMG
DMG
__far_memcpy: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:148

__fill_tilemap

__fill_tilemap is a compiler alias for __vram_memset. It uses the same byte-count argument and the same immediate CPU transfer, including its LCD-access rules.

Immediately fill len consecutive bytes starting at dst with the byte value. This repeats one byte, not a whole tile pattern. A map-byte fill repeats a tile number; a pattern-byte fill instead changes the encoded pixel planes.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

value

One byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

Supply an explicit starting address and byte count. This alias neither chooses the background map nor fills a whole map automatically. For one complete 32 × 32 tile-number map, use its base address and 1024 bytes with VRAM bank 0 selected.

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__fill_tilemap(0x9922,1,length_base+length_extra); // Explicit address and byte count, not a whole-map clear.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __fill_tilemap to fill three map bytes at tile (2,9), pixel (16,72), with tile number 1. Expect a solid 24 × 8 bar with blank neighboring cells. The count is computed from variables or a function result; all three bytes must still receive value 1.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__fill_tilemap: DMG
DMG
__fill_tilemap: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:157

__getbgmapbase

Read LCDC bit 3 to select the background tile-map base.

Syntax

c
u16 __getbgmapbase();

Return value

Returns $9800 or $9C00, the tile-map base rather than the tile graphics address.

Usage example

c
M_LCDC=0;result[0]=__getbgmapbase();M_LCDC=8;result[1]=__getbgmapbase();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__getbgmapbase: DMG
DMG
__getbgmapbase: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:140

kitaqgb/kitaqgb/CodeGenerator.cs:10619

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

    AsmOperand gb_base0 = MakeUniqueLabel("getbgbase_base0");
    AsmOperand gb_done  = MakeUniqueLabel("getbgbase_done");

    EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
    EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate)); // bit3 (BG tile map display select)
    EmitAsm("JR_Z", gb_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
    EmitAsm("JR", gb_done);
    EmitLabel(gb_base0);
    EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
    EmitLabel(gb_done);
    LastCallReturnsHL = true;
    return;
}
__manhattan

Add the absolute X and Y differences between two points.

Syntax

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

Parameters

x1

Point 1 X coordinate, 0..255.

y1

Point 1 Y coordinate, 0..255.

x2

Point 2 X coordinate, 0..255.

y2

Point 2 Y coordinate, 0..255.

Return value

Returns the low byte of |x1−x2|+|y1−y2|; distances above 255 wrap.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Exercise the interior, boundaries, outside point and zero width: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verify distance 15, wrapped distance 254 and array indices 64/65280.

__manhattan: DMG
DMG
__manhattan: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:183

kitaqgb/kitaqgb/CodeGenerator.cs:8516

csharp
if (funcName == "__manhattan")
            {
                if (args.Length != 4) Error(expr, "__manhattan(x1, y1, x2, y2) expects 4 arguments");

                Expr[] foldedManhattanArgs = args.Select(FoldConstants).ToArray();
                if (foldedManhattanArgs[0].Match(Tag.Integer, out int constX1) &&
                    foldedManhattanArgs[1].Match(Tag.Integer, out int constY1) &&
                    foldedManhattanArgs[2].Match(Tag.Integer, out int constX2) &&
                    foldedManhattanArgs[3].Match(Tag.Integer, out int constY2))
                {
                    int dx = Math.Abs((constX1 & 0xFF) - (constX2 & 0xFF));
                    int dy = Math.Abs((constY1 & 0xFF) - (constY2 & 0xFF));
                    EmitAsm("LD_A_IMM", new AsmOperand((dx + dy) & 0xFF, AddressMode.Immediate));
                    return;
                }

                EmitPushExprAsWord(args[0]); // x1
                EmitPushExprAsWord(args[1]); // y1
                EmitPushExprAsWord(args[2]); // x2
                EmitPushExprAsWord(args[3]); // y2

                AsmOperand dxGe = MakeUniqueLabel("manhattan_dx_ge");
                AsmOperand dxDone = MakeUniqueLabel("manhattan_dx_done");
                AsmOperand dyGe = MakeUniqueLabel("manhattan_dy_ge");
                AsmOperand dyDone = MakeUniqueLabel("manhattan_dy_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // x2
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", dxGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_B");
                EmitAsm("JR", dxDone);
                EmitLabel(dxGe);
                EmitAsm("LD_A_B");
                EmitAsm("SUB_C");
                EmitLabel(dxDone);
                EmitAsm("LD_B_A"); // dx

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // y1
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // y2
                EmitAsm("LD_A_C");
                EmitAsm("CP_D");
                EmitAsm("JR_NC", dyGe);
                EmitAsm("LD_A_D");
                EmitAsm("SUB_C");
                EmitAsm("JR", dyDone);
                EmitLabel(dyGe);
                EmitAsm("LD_A_C");
                EmitAsm("SUB_D");
                EmitLabel(dyDone);
                EmitAsm("ADD_B");
                EmitAsm("ADD_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                return;
            }
__map_index

Convert a cell coordinate to a row-major array index.

Syntax

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

Parameters

x

Column 0..255, normally less than width.

y

Row 0..255.

width

Cells per row, 0..255.

Return value

Returns y*width+x as u16; does not validate coordinates.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Exercise the interior, boundaries, outside point and zero width: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verify distance 15, wrapped distance 254 and array indices 64/65280.

__map_index: DMG
DMG
__map_index: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:181

kitaqgb/kitaqgb/CodeGenerator.cs:8365

csharp
if (funcName == "__map_index")
            {
                if (args.Length != 3) Error(expr, "__map_index(x, y, width) expects 3 arguments");

                Expr xExpr = FoldConstants(args[0]);
                Expr yExpr = FoldConstants(args[1]);
                Expr widthExpr = FoldConstants(args[2]);
                // Apply the width parameter's byte conversion before choosing a shift specialization.
                if (widthExpr.Match(Tag.Integer, out int byteWidth))
                    widthExpr = Expr.Make(Tag.Integer, byteWidth & 0xFF).WithSource(widthExpr.Source);
                if (xExpr.Match(Tag.Integer, out int fullXConst) &&
                    yExpr.Match(Tag.Integer, out int fullYConst) &&
                    widthExpr.Match(Tag.Integer, out int fullWidthConst))
                {
                    // Apply the byte argument widths before calculating the word-sized constant index.
                    int fullIndexConst = (((fullYConst & 0xFF) * (fullWidthConst & 0xFF)) + (fullXConst & 0xFF)) & 0xFFFF;
                    EmitAsm("LD_HL_IMM", new AsmOperand(fullIndexConst, AddressMode.Immediate16));
                    LastCallReturnsHL = true;
                    return;
                }

                if (widthExpr.Match(Tag.Integer, out int widthConst) && widthConst > 0 && IsPow2(widthConst))
                {
                    CompileIntoA(yExpr);
                    EmitAsm("LD_L_A");
                    EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
                    int sh = Log2Pow2(widthConst);
                    for (int i = 0; i < sh; i++) EmitAsm("ADD_HL_HL");

                    if (xExpr.Match(Tag.Integer, out int xConst) && xConst == 0)
                    {
                        LastCallReturnsHL = true;
                        return;
                    }

                    CompileIntoA(xExpr);
                    EmitAsm("LD_E_A");
                    EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                    EmitAsm("ADD_HL_DE");
                    LastCallReturnsHL = true;
                    return;
                }

                CompileIntoA(args[0]); EmitPushAAsWord(); // x
                CompileIntoA(args[1]); EmitPushAAsWord(); // y
                CompileIntoA(args[2]); EmitPushAAsWord(); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // width

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A"); // y

                EmitMul8x8ToHL_EC("__map_index");

                // Loading the saved X argument uses HL, so preserve the multiplication result first.
                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("POP_HL");
                EmitAsm("ADD_HL_DE");
                EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                LastCallReturnsHL = true;
                return;
            }
__memcpy

Copy a caller-specified number of bytes from src to dst. The 16-bit count supports transfers longer than 255 bytes, such as copying a complete RAM table. Bytes are read and written in ascending address order.

Syntax

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

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

src

Pointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.

count

Number of bytes as u16 (0–65535), not an element count. The type's maximum is not a promise that such a large valid buffer exists on the target machine. Keep the whole range within the selected memory mapping.

Return value

None (void).

Conditions and limits

A zero count transfers no bytes. Side effects in argument expressions still occur: the example calls two pointer-returning functions and observes both calls even though the copy length is zero.

Use separate, non-overlapping source and destination ranges. These routines copy forward and do not provide overlap-safe memmove behavior; writing into the unread part of the source can corrupt later bytes.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __memcpy to copy 257 patterned bytes starting at offset 1. memory_check compares the entire source and destination arrays; the COPY WORD row must show 257. Starting inside the buffer leaves a leading guard byte, and bytes after the payload must also remain unchanged.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__memcpy: DMG
DMG
__memcpy: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:142

kitaqgb/kitaqgb/CodeGenerator.cs:9084

csharp
if (funcName == "__memcpy" || funcName == "__memcpy_small")
            {
                // Select byte or word count semantics before optional fixed-array bounds diagnostics.
                bool useU8Count = (funcName == "__memcpy_small");
                if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }

                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false, hasSrc = false;
                int dstBytes = 0, srcBytes = 0;
                string dstName = null, srcName = null;
                if (doCheck)
                {
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
                    hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
                }

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && isConstCountAny)
                {
                    if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                    if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                }

                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    if (normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem8 = normalizedConstCount % 8;
                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
                            AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++)
                            {
                                EmitAsm("LD_A_DE");
                                EmitAsm("LDI_HL_A");
                                EmitAsm("INC_DE");
                            }
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem8; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        return;
                    }

                    int blocks16 = normalizedConstCount / 16;
                    int rem16 = normalizedConstCount % 16;
                    if (blocks16 > 0)
                    {
                        EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                        // Use sixteen-byte groups while keeping the residual copy length known at compile time.
                        AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
                        EmitLabel(loop16);
                        for (int k = 0; k < 16; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        EmitAsm("DEC_B");
                        EmitAsm("JP_NZ", loop16);
                    }
                    for (int k = 0; k < rem16; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck)
                    {
                        if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
                        if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
                    }

                    AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);

                    EmitLabel(smallLoopLabel);
                    EmitAsm("LD_A_DE");
                    EmitAsm("LDI_HL_A");
                    EmitAsm("INC_DE");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[1]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && !isConstCountAny)
                {
                    if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
                    if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
                }
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                EmitAsm("POP_DE");
                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memcpy_small

Copy a block of at most 255 bytes using an 8-bit count. Use this form when the transfer length fits in a byte. _small describes the count width; it is not a promise of a particular execution time or generated code size.

Syntax

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

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

src

Pointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.

len

Number of bytes as u8: 0–255. Do not pass 256 to mean a full 256-byte block; that length does not fit this parameter. Use the corresponding function without _small for 256 bytes or more.

Return value

None (void).

Conditions and limits

A zero count transfers no bytes. Side effects in argument expressions still occur: the example calls two pointer-returning functions and observes both calls even though the copy length is zero.

Use separate, non-overlapping source and destination ranges. These routines copy forward and do not provide overlap-safe memmove behavior; writing into the unread part of the source can corrupt later bytes.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __memcpy_small to copy 255 patterned bytes starting at offset 1. memory_check compares the entire source and destination arrays; the COPY BYTE row must show 255. Starting inside the buffer leaves a leading guard byte, and bytes after the payload must also remain unchanged.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__memcpy_small: DMG
DMG
__memcpy_small: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:175

kitaqgb/kitaqgb/CodeGenerator.cs:9084

csharp
if (funcName == "__memcpy" || funcName == "__memcpy_small")
            {
                // Select byte or word count semantics before optional fixed-array bounds diagnostics.
                bool useU8Count = (funcName == "__memcpy_small");
                if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }

                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false, hasSrc = false;
                int dstBytes = 0, srcBytes = 0;
                string dstName = null, srcName = null;
                if (doCheck)
                {
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
                    hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
                }

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && isConstCountAny)
                {
                    if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                    if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
                    {
                        Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
                        EmitAsm("JP", EnsureCheckTrap());
                        return;
                    }
                }

                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    for (int k = 0; k < normalizedConstCount; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");
                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");

                    if (normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem8 = normalizedConstCount % 8;
                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
                            AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++)
                            {
                                EmitAsm("LD_A_DE");
                                EmitAsm("LDI_HL_A");
                                EmitAsm("INC_DE");
                            }
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem8; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        return;
                    }

                    int blocks16 = normalizedConstCount / 16;
                    int rem16 = normalizedConstCount % 16;
                    if (blocks16 > 0)
                    {
                        EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                        // Use sixteen-byte groups while keeping the residual copy length known at compile time.
                        AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
                        EmitLabel(loop16);
                        for (int k = 0; k < 16; k++)
                        {
                            EmitAsm("LD_A_DE");
                            EmitAsm("LDI_HL_A");
                            EmitAsm("INC_DE");
                        }
                        EmitAsm("DEC_B");
                        EmitAsm("JP_NZ", loop16);
                    }
                    for (int k = 0; k < rem16; k++)
                    {
                        EmitAsm("LD_A_DE");
                        EmitAsm("LDI_HL_A");
                        EmitAsm("INC_DE");
                    }
                    return;
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoHL(args[1]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck)
                    {
                        if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
                        if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
                    }

                    AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_DE");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);

                    EmitLabel(smallLoopLabel);
                    EmitAsm("LD_A_DE");
                    EmitAsm("LDI_HL_A");
                    EmitAsm("INC_DE");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[1]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && !isConstCountAny)
                {
                    if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
                    if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
                }
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                EmitAsm("POP_DE");
                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_DE");
                EmitAsm("LDI_HL_A");
                EmitAsm("INC_DE");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memset

Write the same byte value throughout a destination range. The 16-bit count is useful for clearing a large buffer or initializing a table. For example, filling with 0x5A writes 5A 5A 5A…; it does not repeat a multibyte integer.

Syntax

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

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

value

The byte repeated at every destination address: 0–255. Use 0 to clear the range. To initialize a u16 array to a nonzero numeric value, assign its elements individually instead.

count

Number of bytes as u16 (0–65535), not an element count. The type's maximum is not a promise that such a large valid buffer exists on the target machine. Keep the whole range within the selected memory mapping.

Return value

None (void).

Conditions and limits

A zero count transfers no bytes. Side effects in argument expressions still occur: the example calls two pointer-returning functions and observes both calls even though the copy length is zero.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __memset to fill 257 bytes at offset 1 with 0x5A. memory_check requires every byte in that range to equal 0x5A, while the guard bytes stay 0xCC and the separate source buffer remains untouched. The FILL WORD row must show 257.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__memset: DMG
DMG
__memset: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:141

kitaqgb/kitaqgb/CodeGenerator.cs:8926

csharp
if (funcName == "__memset" || funcName == "__memset_small")
            {
                // The small API truncates its count to a byte; the general API uses a word count.
                bool useU8Count = (funcName == "__memset_small");
                if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }

                // -Zcheck: fixed-array range check for memset length
                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false;
                int dstBytes = 0;
                string dstName = null;
                if (doCheck)
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                {
                    Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                    EmitAsm("JP", EnsureCheckTrap());
                    return;
                }

                // Size-specialized memset for small constant counts (<=16): unrolled stores.
                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

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

                    for (int k = 0; k < normalizedConstCount; k++)
                        EmitAsm("LDI_HL_A");
                    return;
                }

                // Size-specialized memset for constant counts beyond 16
                if (isConstCountAny && normalizedConstCount >= 0)
                {
                    if (normalizedConstCount == 0) return;

                    if (normalizedConstCount > 16 && normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem = normalizedConstCount % 8;

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

                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Emit groups of eight stores followed by the constant remainder for medium fills.
                            AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }

                    if (normalizedConstCount <= 255)
                    {
                        int blocks16 = normalizedConstCount / 16;
                        int rem = normalizedConstCount % 16;

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

                        if (blocks16 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                            // Use sixteen-store groups for larger byte-range constant fills.
                            AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
                            EmitLabel(loop16);
                            for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop16);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[1]);
                    EmitAsm("LD_D_A");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck && hasDst)
                        EmitU8LeqConstOrTrapInA(dstBytes);

                    AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);
                    EmitAsm("LD_A_D");
                    EmitLabel(smallLoopLabel);
                    EmitAsm("LDI_HL_A");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && hasDst && !isConstCountAny)
                    EmitU16LeqConstOrTrap(dstBytes);
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                CompileIntoA(args[1]);
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__memset_small

Fill up to 255 consecutive bytes with one byte value, using an 8-bit count. This is suitable for initializing a small working buffer. Choose __memset when the required length exceeds 255 bytes.

Syntax

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

Parameters

dst

Pointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.

value

The byte repeated at every destination address: 0–255. Use 0 to clear the range. To initialize a u16 array to a nonzero numeric value, assign its elements individually instead.

len

Number of bytes as u8: 0–255. Do not pass 256 to mean a full 256-byte block; that length does not fit this parameter. Use the corresponding function without _small for 256 bytes or more.

Return value

None (void).

Conditions and limits

A zero count transfers no bytes. Side effects in argument expressions still occur: the example calls two pointer-returning functions and observes both calls even though the copy length is zero.

The operation finishes before the caller continues. It neither switches ROM/RAM banks nor clips a range to a buffer boundary. Keep mappings stable, use valid memory ranges and coordinate access with interrupt handlers that use the same buffers.

On GB, these are ordinary CPU memory accesses. They do not wait for safe VRAM/OAM access, start DMA or select a CGB VRAM/WRAM bank. Use the dedicated graphics-transfer APIs for those jobs.

Usage example

c
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __memset_small to fill 255 bytes at offset 1 with 0x5A. memory_check requires every byte in that range to equal 0x5A, while the guard bytes stay 0xCC and the separate source buffer remains untouched. The FILL BYTE row must show 255.

The complete program resets two 264-byte buffers before each operation, then checks every source byte, every destination byte and the untouched guard regions. The expected rows are COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 and FAILED 000. WORD/BYTE describe the count width, not the size of each copied item. The numbers are produced by the sample's checks, not return values from these void functions.

The captured screens show black lettering on white for DMG, blue on white for CGB and blue on black for FC. This example verifies RAM operations; the lettering presents the results rather than demonstrating a drawing primitive. Separate emulator checks cover 88 memory cases on each platform, including constant and runtime lengths at 0, 1, 16, 17, 32, 33, 64, 65 and 255 bytes, plus 256 and 257 for the word-count forms. Physical hardware has not been tested.

__memset_small: DMG
DMG
__memset_small: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\memory_intrinsics.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:176

kitaqgb/kitaqgb/CodeGenerator.cs:8926

csharp
if (funcName == "__memset" || funcName == "__memset_small")
            {
                // The small API truncates its count to a byte; the general API uses a word count.
                bool useU8Count = (funcName == "__memset_small");
                if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }

                // -Zcheck: fixed-array range check for memset length
                bool doCheck = Program.CheckMemCopy && !InUnsafe;
                bool hasDst = false;
                int dstBytes = 0;
                string dstName = null;
                if (doCheck)
                    hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);

                bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
                int normalizedConstCount = constCountAny;
                if (isConstCountAny && useU8Count)
                    normalizedConstCount &= 0xFF;

                if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
                {
                    Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
                    EmitAsm("JP", EnsureCheckTrap());
                    return;
                }

                // Size-specialized memset for small constant counts (<=16): unrolled stores.
                if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
                {
                    if (normalizedConstCount == 0) return;

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

                    for (int k = 0; k < normalizedConstCount; k++)
                        EmitAsm("LDI_HL_A");
                    return;
                }

                // Size-specialized memset for constant counts beyond 16
                if (isConstCountAny && normalizedConstCount >= 0)
                {
                    if (normalizedConstCount == 0) return;

                    if (normalizedConstCount > 16 && normalizedConstCount <= 64)
                    {
                        int blocks8 = normalizedConstCount / 8;
                        int rem = normalizedConstCount % 8;

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

                        if (blocks8 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
                            // Emit groups of eight stores followed by the constant remainder for medium fills.
                            AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
                            EmitLabel(loop8);
                            for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop8);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }

                    if (normalizedConstCount <= 255)
                    {
                        int blocks16 = normalizedConstCount / 16;
                        int rem = normalizedConstCount % 16;

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

                        if (blocks16 > 0)
                        {
                            EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
                            // Use sixteen-store groups for larger byte-range constant fills.
                            AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
                            EmitLabel(loop16);
                            for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
                            EmitAsm("DEC_B");
                            EmitAsm("JP_NZ", loop16);
                        }
                        for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
                        return;
                    }
                }

                if (useU8Count)
                {
                    CompileIntoHL(args[0]);
                    EmitAsm("PUSH_HL");

                    CompileIntoA(args[1]);
                    EmitAsm("LD_D_A");

                    CompileIntoA(args[2]);
                    EmitAsm("LD_B_A");
                    if (doCheck && hasDst)
                        EmitU8LeqConstOrTrapInA(dstBytes);

                    AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
                    AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
                    EmitAsm("OR_A");
                    EmitAsm("POP_HL");
                    EmitAsm("JP_Z", doneLabel);
                    EmitAsm("LD_A_D");
                    EmitLabel(smallLoopLabel);
                    EmitAsm("LDI_HL_A");
                    EmitAsm("DEC_B");
                    EmitAsm("JP_NZ", smallLoopLabel);
                    EmitLabel(doneLabel);
                    return;
                }

                CompileIntoHL(args[0]);
                EmitAsm("PUSH_HL");

                CompileIntoHL(args[2]);
                if (doCheck && hasDst && !isConstCountAny)
                    EmitU16LeqConstOrTrap(dstBytes);
                EmitAsm("LD_B_H");
                EmitAsm("LD_C_L");

                CompileIntoA(args[1]);
                EmitAsm("LD_D_A");

                EmitAsm("POP_HL");

                AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
                AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
                // A zero word count must skip the first write as well as the loop.
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_Z", wordDoneLabel);
                EmitLabel(loopLabel);

                EmitAsm("LD_A_D");
                EmitAsm("LDI_HL_A");

                EmitAsm("DEC_BC");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JP_NZ", loopLabel);
                EmitLabel(wordDoneLabel);

                return;
            }

// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
        {
            string knownCallName;
            if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
                knownCallName == "__cgb_is_cgb" &&
                callArgs != null && callArgs.Length == 0 &&
                // A resolved target configuration can replace the zero-argument CGB query with a literal.
                Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
            {
                return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
            }

            Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);

            // Optimization (post-A3): when we must spill a non-atomic argument into a temp,
            // prefer a u8 temp if the *parameter type* is u8-like.
            // This preserves semantics (implicit truncation to u8) while avoiding needless
            // u16 temporaries created by integer promotions.
            CType[] paramTypes = null;
            string __fname = null;
            if (newFunc.Match(Tag.Name, out __fname))
            {
                paramTypes = ctx.FindFuncParams(__fname);
                if (paramTypes == null)
                {
                    CType fnTypeFromName;
                    if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
                        paramTypes = fnTypeFromName.ParamTypes;
                }
            }
            else
            {
                CType fnTypeFromExpr;
                if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
                    paramTypes = fnTypeFromExpr.ParamTypes;
            }

            Expr[] newArgs = new Expr[callArgs.Length];
            for (int i = 0; i < callArgs.Length; i++)
            {
                Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
                bool preserveConstantArgIntrinsic = false;
                if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
                    argFunc.Match(Tag.Name, out string argFuncName))
                {
                    preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
                }
                // Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
                bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
                bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);

                if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
                {
                    // If we know the callee's parameter type, use it to choose a smaller temp.
                    // Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
                    CType spillAs = null;
                    if (paramTypes != null && i < paramTypes.Length)
                    {
                        CType pt = paramTypes[i];
                        if (IsU8Like(pt)) spillAs = CType.UInt8;
                    }
                    if (spillAs == null) spillAs = InferType(ctx, a);
                    if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
                        a = SpillToTemp(ctx, a, spillAs, prefix);
                }
                newArgs[i] = a;
            }

            // Lint: const discard for argument passing
            // Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
            // Suppress when the argument has an explicit cast, or inside __unsafe.
            if (!ctx.InUnsafe && paramTypes != null)
            {
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;
                    if (!IsPointerToNonConst(pt)) continue;
                    if (HasTopLevelCast(newArgs[i])) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
                    {
                        string fn = __fname ?? "<call>";
                        WarnConstDiscard(expr, $"argument {i} to '{fn}'");
                        break; // keep it quiet: one warning per call is enough
                    }
                }
            }

            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    FilePosition argPos = newArgs[i].Source;
                    if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
                        argPos = expr.Source;
                    WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
                        "argument " + i + " to '" + fn + "'");
                }
            }

            // Lint: __enum_strict argument passing.
            if (paramTypes != null)
            {
                string fn = __fname ?? "<call>";
                for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
                {
                    CType pt = paramTypes[i];
                    if (pt == null) continue;

                    CType at = InferType(ctx, newArgs[i]);
                    if (at == null) continue;

                    if (HasTopLevelCast(newArgs[i])) continue;

                    if (IsStrictEnum(pt))
                    {
                        if (!IsSameEnum(pt, at))
                        {
                            Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                                "argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
                                i, fn, pt.Show(), at.Show());
                        }
                    }
                    else if (IsPlainInteger(pt) && IsStrictEnum(at))
                    {
                        Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
                            "argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
                            i, fn, at.Show(), pt.Show());
                    }
                }
            }

            if (!ctx.InUnsafe)
                RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);

            // __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
            // Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
            // Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
            // Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
            // when there is no declared function named "slice").
            if (newFunc.Match(Tag.Name, out string __sfname))
            {
                bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
                if (isSliceIntrinsic)
                {
                    if (newArgs.Length != 2)
                    {
                        Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
                    }
                    else
                    {
                        CType pty = InferType(ctx, newArgs[0]);
                        if (pty == null || !(pty.IsPointer || pty.IsArray))
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));

                        CType lty = InferType(ctx, newArgs[1]);
                        if (lty == null || !lty.IsInteger)
                            Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
                    }

                    return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
                }
            }

            return MakeCall(newFunc, newArgs).WithSource(expr.Source);
        }
__rle_decode_vram

Expand a bank-qualified count/value stream directly into VRAM, without an intermediate output buffer in RAM. The compiler emits a loop that fetches each pair, fills its run using the LCD-aware VRAM write helper, and returns the number of output bytes. This is useful when loading compressed tile data or a byte-oriented tile map.

Syntax

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

Parameters

dst

16-bit CPU address of the first output byte, within 0x8000–0x9FFF. Reserve enough consecutive bytes without passing 0x9FFF. For example, 0x8860 is tile 134 in the unsigned tile-data layout. The function does not allocate tiles or check the destination range.

bank

Source ROM bank number as u8. The bank and CPU address must match your cartridge mapper and asset placement. Bank selection is restored after each encoded-byte fetch; it is not a destination VRAM-bank selector and cannot express a 9-bit MBC5 bank number.

src

CPU pointer to the encoded stream in the supplied bank. The function increments this address, but keeps the bank number fixed. Keep the full stream, including its terminating zero, inside a readable mapping; crossing 0x7FFF does not automatically continue in the next ROM bank.

Return value

Number of bytes written, as u16. An immediate zero count produces zero bytes and returns 0. The terminator is not copied. Keep the expanded length representable in u16; the return value has no separate error code and is not a tile count.

Conditions and limits

The encoding is byte-oriented, not text-oriented. For example, {3, 0x12, 2, 0xA5, 0} expands to five bytes: 12 12 12 A5 A5. Values are hexadecimal in that output listing. A zero value inside a nonzero-count pair is ordinary data, not the end marker.

There is no encoded-size, output-capacity or malformed-data argument. Validate assets when creating or loading them, provide the zero terminator, and calculate their expanded size before decoding. An invalid or unterminated stream can read past the source and write past the destination.

With the LCD off, runs are written directly. With it on, the helper waits for a writable LCD mode for each output byte, using DI/EI around the wait and store. A long stream can therefore span multiple frames; this is a synchronous call, not a queued transfer. The LCD-on path enables interrupts after each byte instead of preserving an initially disabled interrupt state.

On CGB, writes use the currently selected VBK bank and leave that selection unchanged. Choose VBK 0 for the sample's tile data. The bank argument still selects ROM input, even when VBK 1 is selected for output. The decoder does not manage CGB palettes or map attributes.

Do not let an interrupt routine change the ROM mapping or reuse the far-copy helper while a bank-qualified fetch is in progress. The helpers use shared bank bookkeeping. Place a near decoder and its source in compatible mappings; switching to a function's bank must not hide an ordinary source pointer.

rle_decode and rle_decode_far are declared in rpg.h and implemented in lib/rle.c. Compile that implementation once with your game. The complete example includes rle.c after #pragma bank 0 so that its caller and decoders stay in the common bank; do not also pass rle.c as a separate input for this example. The __rle_decode_vram intrinsic itself needs no library implementation file.

Usage example

c
vram_count=__rle_decode_vram(0x8860,2,banked_rle); // Expand directly to 48 bytes of tile data.
if(*rle_bank_marker!=0xA1)failures++;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example expands the bank-2 stream directly to VRAM at 0x8860, without a RAM output buffer. DIRECT VRAM reads 048. The bottom row is blue on CGB and must contain the same three tile patterns as the RAM-based rows. This teaching program performs the upload with the LCD off.

Each row contains, from left to right, an 8×8 filled square, an 8×8 one-pixel outline, and a filled 4×8 rectangle occupying the left half of an 8×8 tile. Their tile columns are 2, 5 and 8 (pixel x = 16, 40 and 64). The three rows start at pixel y = 32, 72 and 112. DMG uses black shapes; CGB uses red, green and blue by row. FAILED must be 000. The numbers are byte counts: three 16-byte GB tiles require 48 decoded bytes.

The verification matrix checks 42 cases covering an empty stream, a one-byte run, zero-valued data, a 255-byte run, 510-byte output across a destination page, and encoded data across a source page. It compares all 528 observed destination/guard bytes, output length and restored ROM mapping. Direct VRAM cases include LCD on/off on DMG and CGB, plus CGB VBK 1. The two teaching screens are checked after 240 emulator frames. Invalid assets, concurrent interrupt use and physical hardware are not covered.

__rle_decode_vram: DMG
DMG
__rle_decode_vram: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:191

Declaration notes (original source)
Compiler intrinsic, separate from the RAM rle_decode helpers below; it is not a bounded-stream API.

kitaqgb/kitaqgb/CodeGenerator.cs:8673

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

                CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
                CompileIntoA(args[1]); EmitPushAAsWord(); // bank
                CompileIntoHL(args[2]); EmitAsm("PUSH_HL"); // src
                EmitAsm("ADD_SP_IMM", new AsmOperand(-4, AddressMode.Relative)); // total(2) + scratch(2)

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("XOR_A");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_HL_A");

                AsmOperand loop = MakeUniqueLabel("rlevram_loop");
                // Keep total, scratch, source, bank and destination in stack slots across repeated far-memory calls.
                AsmOperand countDone = MakeUniqueLabel("rlevram_count_inc_done");
                AsmOperand valueDone = MakeUniqueLabel("rlevram_value_inc_done");
                AsmOperand finished = MakeUniqueLabel("rlevram_finished");

                EmitLabel(loop);

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

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

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

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

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL"); // count
                EmitAsm("OR_A");
                EmitAsm("JP_Z", finished);
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_HL_A"); // save count in scratch+1

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitAsm("JR_NC", countDone);
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitLabel(countDone);

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

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

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

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

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A"); // value

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A");
                EmitAsm("LD_B_IMM", new AsmOperand(0, AddressMode.Immediate)); // count

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_E_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("LD_H_D");
                EmitAsm("LD_L_E"); // dst
                // Loading dst used D for its high byte. Recover the run value
                // from stack scratch while preserving the completed HL pointer.
                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_D_A");
                EmitAsm("POP_HL");

                // Use the LCD-aware fill loop for each decoded run, then store its advanced destination pointer.
                EmitVramMemsetLoop(true, "rlevram_fill");

                EmitAsm("PUSH_HL");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                EmitAsm("POP_DE");
                EmitAsm("LD_A_E");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_D");
                EmitAsm("LD_HL_A"); // save dst

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // count
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_B");
                EmitAsm("LD_HL_A");
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A"); // total += count

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitAsm("JR_NC", valueDone);
                EmitAsm("INC_HL");
                EmitAsm("LD_A_HL");
                EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
                EmitAsm("LD_HL_A");
                EmitLabel(valueDone);

                EmitAsm("JP", loop);

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

Advance the shared GB pseudorandom generator once and return its new high byte. Use this intrinsic when you need one byte without linking the rng.c library wrappers.

Syntax

c
u8 __rng8();

Return value

One u8 result (0–255), after advancing the shared generator once. This describes the value's type and bounds, not equal likelihood for every byte.

Conditions and limits

The GB step adds 0x5D17 to the 16-bit state with wraparound, XORs the updated low byte with the high byte, then adds that low byte to a one-bit left rotation of the high byte. It stores both updated bytes and returns the high byte.

A zero state is accepted. On the next byte draw, the GB generator substitutes 0x5AA5 before advancing; __rng_seed(0) followed by __rng8() therefore returns 122. Repeatedly reseeding restarts the sequence instead of adding randomness.

These calls share persistent state. Reproduction requires the same target, seed and sequence of calls, including calls made by other helpers. Keep seed and draw operations in one execution context; these checks do not establish safe concurrent use from an interrupt handler.

GB and FC use different algorithms and do not promise the same sequence for the same seed. These are deterministic gameplay generators, not a source of cryptographic randomness; the tests check implementation behavior, not statistical quality or period.

Include rpg.h and compile lib/rng.c alongside your game when using the library functions. The __rng_seed and __rng8 intrinsics themselves are emitted by the compiler; a program using only those two needs no rng.c object.

Usage example

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.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This sample demonstrates that each draw advances the sequence. After seed 0x1234, the first two __rng8 calls display FIRST 00002 and SECOND 00034. Repeating the seed restarts at 00002.

The complete program prints decimal results on a white background: black text on DMG, blue text on CGB. FAILED must read 00000. The pictures show the actual emulator output of this sample.

Verification compared 32 outputs and eight subsequent byte draws per case with an independent host model: 93 GB cases and 18 FC cases, including constant/runtime seeds and GB range/chance/weight boundaries. The teaching screens were checked after 240 emulator frames. Physical hardware and statistical quality were not tested.

__rng8: DMG
DMG
__rng8: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\rng_values.c .\kitaqgb\lib\rng.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\rng_values.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:149

kitaqgb/kitaqgb/CodeGenerator.cs:8881

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

                EmitLoadA(RngStateLoVar);
                EmitAsm("LD_B_A");
                EmitLoadA(RngStateHiVar);
                EmitAsm("LD_C_A");

                // Zero state fallback for deterministic startup.
                AsmOperand rngSeeded = MakeUniqueLabel("rng_seeded");
                EmitAsm("LD_A_B");
                EmitAsm("OR_C");
                EmitAsm("JR_NZ", rngSeeded);
                EmitAsm("LD_A_IMM", new AsmOperand(0xA5, AddressMode.Immediate));
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_IMM", new AsmOperand(0x5A, AddressMode.Immediate));
                EmitAsm("LD_C_A");
                EmitLabel(rngSeeded);

                // 16-bit mixed add/xor/rotate step.
                EmitAsm("LD_A_B");
                EmitAsm("ADD_A_IMM", new AsmOperand(0x17, AddressMode.Immediate));
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_C");
                EmitAsm("ADC_IMM", new AsmOperand(0x5D, AddressMode.Immediate));
                EmitAsm("LD_C_A");
                EmitAsm("LD_A_B");
                EmitAsm("XOR_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_A_C");
                EmitAsm("RLCA");
                EmitAsm("ADD_B");
                EmitAsm("LD_C_A");

                EmitAsm("LD_A_B");
                EmitStoreA(RngStateLoVar);
                EmitAsm("LD_A_C");
                EmitStoreA(RngStateHiVar);
                EmitAsm("LD_A_C");
                return;
            }
__rng_seed

Replace the shared GB generator's 16-bit state. Use a fixed seed when you want to replay the same sequence during debugging. Seeding itself does not draw a random value.

Syntax

c
void __rng_seed(u16 seed);

Parameters

seed

Unsigned 16-bit seed, 0–65535. Both bytes are stored as supplied. Choose and record the seed before the sequence of gameplay draws you want to reproduce.

Return value

None (void).

Conditions and limits

A zero state is accepted. On the next byte draw, the GB generator substitutes 0x5AA5 before advancing; __rng_seed(0) followed by __rng8() therefore returns 122. Repeatedly reseeding restarts the sequence instead of adding randomness.

These calls share persistent state. Reproduction requires the same target, seed and sequence of calls, including calls made by other helpers. Keep seed and draw operations in one execution context; these checks do not establish safe concurrent use from an interrupt handler.

GB and FC use different algorithms and do not promise the same sequence for the same seed. These are deterministic gameplay generators, not a source of cryptographic randomness; the tests check implementation behavior, not statistical quality or period.

Include rpg.h and compile lib/rng.c alongside your game when using the library functions. The __rng_seed and __rng8 intrinsics themselves are emitted by the compiler; a program using only those two needs no rng.c object.

Usage example

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

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This sample demonstrates resetting a test sequence. FIRST and RESEEDED both show 00002 after seed 0x1234; SECOND shows 00034. The library seed resets the same state, so RNG8 also shows 00002.

The complete program prints decimal results on a white background: black text on DMG, blue text on CGB. FAILED must read 00000. The pictures show the actual emulator output of this sample.

Verification compared 32 outputs and eight subsequent byte draws per case with an independent host model: 93 GB cases and 18 FC cases, including constant/runtime seeds and GB range/chance/weight boundaries. The teaching screens were checked after 240 emulator frames. Physical hardware and statistical quality were not tested.

__rng_seed: DMG
DMG
__rng_seed: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\rng_values.c .\kitaqgb\lib\rng.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\rng_values.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-cache --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:150

kitaqgb/kitaqgb/CodeGenerator.cs:8868

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

                CompileIntoHL(args[0]);
                EmitAsm("LD_A_L");
                EmitStoreA(RngStateLoVar);
                EmitAsm("LD_A_H");
                EmitStoreA(RngStateHiVar);
                return;
            }
__scroll_bg_set

Write background SCX/SCY immediately and synchronize current/pending software values.

Syntax

c
void __scroll_bg_set(u8 scx, u8 scy);

Parameters

scx

Horizontal scroll, 0..255 pixels.

scy

Vertical scroll, 0..255 pixels.

Return value

No return value.

Conditions and limits

Clears the pending background-update flag. Mid-frame writes affect subsequent scanning; normally call during VBlank.

Usage example

c
__scroll_bg_set(19,27);result[5]=*((u8*)0xFF43);result[6]=*((u8*)0xFF42);__scroll_bg_set(0,0);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__scroll_bg_set: DMG
DMG
__scroll_bg_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:151

kitaqgb/kitaqgb/CodeGenerator.cs:7507

csharp
if (funcName == "__scroll_bg_set")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_set(scx, scy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX

                EmitAsm("LD_A_B");
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__settile_col

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.

Syntax

c
void __settile_col(u8 x, u8 y, const u8* src, u8 len);

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

len

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

For a horizontal copy keep x+len <= 32; for a vertical copy keep y+len <= 32. The routine does not wrap coordinates at the map edge.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

c
// __settile_col
// Copy `len` successive tile numbers from `src` vertically, starting at `(x,y)` in the
// LCDC-selected BG map. The source advances one byte and the destination 32 bytes per
// tile. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_col(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9CA2, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C63, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
B
C
__settile_col: DMG
DMG
__settile_col: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_col.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_col.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:156

__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.

Syntax

c
void __settile_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

w, h

Width and height in 8×8 tiles. Keep x+w <= 32 and y+h <= 32. A zero dimension performs no write.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

c
// __settile_rect
// Fill a `w` by `h` rectangle in the LCDC-selected BG map with one tile number. Rows are
// 32 bytes apart; this repeats a tile, rather than copying an image array. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
AAA
AAA
__settile_rect: DMG
DMG
__settile_rect: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_rect.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_rect.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:154

__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.

Syntax

c
void __settile_row(u8 x, u8 y, const u8* src, u8 len);

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

len

Number of bytes, 0–255. Zero copies nothing. Supply at least this many readable source bytes.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

For a horizontal copy keep x+len <= 32; for a vertical copy keep y+len <= 32. The routine does not wrap coordinates at the map edge.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

c
// __settile_row
// Copy `len` successive tile numbers from `src` horizontally, starting at `(x,y)` in the
// LCDC-selected BG map. Source and destination advance by one byte per tile. Waits for
// writable VRAM when necessary. The LCD-on path uses interrupt masking and can enable
// interrupts afterward; it is not a promise to preserve the caller's interrupt state or
// to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settile_row(2, 3, example_tiles, 3);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C64, 0) != 'C') { failures++; }
    if (tile_example_read(0x9C82, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
__settile_row: DMG
DMG
__settile_row: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_row.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_row.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:155

__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.

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.

Syntax

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

Parameters

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

tile

Tile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

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

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

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
A
__settile_xy: DMG
DMG
__settile_xy: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settile_xy.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settile_xy.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:153

__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.

Syntax

c
void __settilemap_rect(u16 base, u8 x, u8 y, u8 w, u8 h, const u8* src);

Parameters

base

base explicitly selects the map (0x9800 or 0x9C00); the byte address is base + 32*y + x.

x, y

Coordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.

w, h

Width and height in 8×8 tiles. Keep x+w <= 32 and y+h <= 32. A zero dimension performs no write.

src

Packed source bytes in RAM or a visible ROM bank. A tile stream contains tile numbers; an attribute stream contains CGB attribute bytes. The routine copies now and does not retain the pointer.

Return value

None (void).

Conditions and limits

Tile bytes go to the currently selected VRAM bank. On CGB, select bank 0 for tile numbers; this call does not switch banks for you.

The example uses gb_common.h: m_init() loads the supplied ASCII font with character codes as tile numbers. m_wait(); M_LCDC=0; stops the LCD during VBlank before unsafe transfers. The final LCDC value selects which map is visible.

Usage example

c
// __settilemap_rect
// Copy a tightly packed `w*h` array of tile numbers to a rectangle at `(x,y)` in the map
// selected by `base`. Source rows have `w` bytes; destination rows have a 32-byte
// stride. Waits for writable VRAM when necessary. The LCD-on path uses interrupt masking
// and can enable interrupts afterward; it is not a promise to preserve the caller's
// interrupt state or to finish within one VBlank.
// Expected: FAILED CHECKS 000, with the written tile data in the selected map.
#include "gb_tile_color_example.h"
const u8 example_tiles[] = {'A','B','C','D','E','F'};

void main() {
    u8 failures;
    failures = 0;
    tile_color_example_begin();
    __settilemap_rect(0x9C00, 2, 3, 3, 2, example_tiles);
    if (tile_example_read(0x9C62, 0) != 'A') { failures++; }
    if (tile_example_read(0x9C84, 0) != 'F') { failures++; }
    if (tile_example_read(0x9C65, 0) != 0) { failures++; }
    tile_example_finish(failures, 1);
}

Expected result

The selected map shows the written characters or filled area. The program reads back the addressed tile bytes and, on CGB, the attribute bytes; FAILED CHECKS 000 means those comparisons passed. Both maps initially contain zero, and the example selects 0x9C00 for BG/window before the demonstrated call, so a fixed-map call can be distinguished from a selected-map call. These examples perform the writes with the LCD off; they do not measure LCD-on timing.

On CGB, the sample characters and status text are blue on white. The setup gives palette 0 a blue ink color; it does not change the tile-map attributes. On DMG, the same characters are black on white.

Expected image: the following tile pattern starts at tile coordinates (x=2, y=3), or pixel coordinates (16,24). Each character occupies one 8×8 tile. Read the pattern from top to bottom and left to right; the surrounding tiles remain blank.

text
ABC
DEF
__settilemap_rect: DMG
DMG
__settilemap_rect: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\__settilemap_rect.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\__settilemap_rect.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb --no-disasm
Implementation and source

kitaqgb/lib/rpg.h:158

__tile_addr

Compute one cell’s CPU address in a 32-tile-wide map.

Syntax

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

Parameters

base

Tile-map base, normally $9800/$9C00.

x

Column, normally 0..31.

y

Row, normally 0..31.

Return value

Returns (base + y*32 + x) modulo 65536 without accessing memory.

Usage example

c
result[4]=__tile_addr(0x9800,3,2);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__tile_addr: DMG
DMG
__tile_addr: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:180

kitaqgb/kitaqgb/CodeGenerator.cs:8355

csharp
if (funcName == "__tile_addr")
            {
                if (args.Length != 3) Error(expr, "__tile_addr(base, x, y) expects 3 arguments");

                EmitTileMapAddressIntoHL(args[0], args[1], args[2]);
                LastCallReturnsHL = true;
                return;
            }
__vram_copy_dma

__vram_copy_dma is a compiler alias for __vram_memcpy. It uses the same byte-count argument and the same immediate CPU transfer, including its LCD-access rules.

Copy len bytes from src to dst immediately, in increasing address order. The function returns after the copy finishes; it does not enqueue work. Source and destination advance by one byte, without tilemap row strides or tile-format conversion.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

src

Source byte pointer, valid and mapped for the whole call. Data is read immediately; it is not retained for a later flush. The source bank is not switched automatically. Use non-overlapping spans: this forward copy does not provide memmove semantics.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

The name does not select a DMA engine or schedule work for a later HBlank. The caller waits for completion. With the LCD active, the same CPU polling accepts either mode 0 (HBlank) or mode 1 (VBlank); CGB HDMA is not configured.

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__vram_copy_dma(0x98A2,strip,3); // This alias also uses CPU writes, not a DMA engine.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_copy_dma to place three map bytes at tile (2,5), pixel (16,40). Expect the three patterns [1,2,3] from left to right in a 24 × 8 area. Neighboring cells must remain blank. The full program places each tested copy or fill on its own row.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_copy_dma: DMG
DMG
__vram_copy_dma: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:163

__vram_copy_hblank

__vram_copy_hblank is a compiler alias for __vram_memcpy. It uses the same byte-count argument and the same immediate CPU transfer, including its LCD-access rules.

Copy len bytes from src to dst immediately, in increasing address order. The function returns after the copy finishes; it does not enqueue work. Source and destination advance by one byte, without tilemap row strides or tile-format conversion.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

src

Source byte pointer, valid and mapped for the whole call. Data is read immediately; it is not retained for a later flush. The source bank is not switched automatically. Use non-overlapping spans: this forward copy does not provide memmove semantics.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

The name does not select a DMA engine or schedule work for a later HBlank. The caller waits for completion. With the LCD active, the same CPU polling accepts either mode 0 (HBlank) or mode 1 (VBlank); CGB HDMA is not configured.

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__vram_copy_hblank(0x9882,strip,3); // Immediate safe copy; no scheduled HBlank DMA.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_copy_hblank to place three map bytes at tile (2,4), pixel (16,32). Expect the three patterns [1,2,3] from left to right in a 24 × 8 area. Neighboring cells must remain blank. The full program places each tested copy or fill on its own row.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_copy_hblank: DMG
DMG
__vram_copy_hblank: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:162

__vram_fill

__vram_fill is a compiler alias for __vram_memset. It uses the same byte-count argument and the same immediate CPU transfer, including its LCD-access rules.

Immediately fill len consecutive bytes starting at dst with the byte value. This repeats one byte, not a whole tile pattern. A map-byte fill repeats a tile number; a pattern-byte fill instead changes the encoded pixel planes.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

value

One byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__vram_fill(0x9902,1,computed_length());

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_fill to fill three map bytes at tile (2,8), pixel (16,64), with tile number 1. Expect a solid 24 × 8 bar with blank neighboring cells. The count is computed from variables or a function result; all three bytes must still receive value 1.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_fill: DMG
DMG
__vram_fill: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:161

__vram_memcpy

Copy len bytes from src to dst immediately, in increasing address order. The function returns after the copy finishes; it does not enqueue work. Source and destination advance by one byte, without tilemap row strides or tile-format conversion.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

src

Source byte pointer, valid and mapped for the whole call. Data is read immediately; it is not retained for a later flush. The source bank is not switched automatically. Use non-overlapping spans: this forward copy does not provide memmove semantics.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__vram_memcpy(0x9842,strip,length_base+length_extra); // Three map bytes at tile (2,2).

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_memcpy to place three map bytes at tile (2,2), pixel (16,16). Expect the three patterns [1,2,3] from left to right in a 24 × 8 area. Neighboring cells must remain blank. The full program places each tested copy or fill on its own row.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_memcpy: DMG
DMG
__vram_memcpy: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:146

__vram_memset

Immediately fill len consecutive bytes starting at dst with the byte value. This repeats one byte, not a whole tile pattern. A map-byte fill repeats a tile number; a pattern-byte fill instead changes the encoded pixel planes.

Syntax

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

Parameters

dst

First destination byte address, normally in VRAM 0x8000–0x9FFF. Keep the entire span valid. CGB uses the current VRAM bank; bank 0 contains tile numbers and bank 1 contains map attributes. These routines do not choose a map from LCDC, wrap rows or allocate VRAM.

value

One byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.

len

Number of consecutive bytes, 0–65535. It is not a tile count. Keep the complete address range inside valid source and writable destination storage; the API does not check bounds or prevent 16-bit address wrap.

Return value

None (void).

Conditions and limits

With the LCD on, each byte write waits for STAT mode 0 or 1 under DI and is followed by EI. With the LCD off, a plain CPU loop is used. The call can span frames, does not preserve an interrupt-disabled entry state, and does not program DMA registers. Zero length performs no data transfer.

Usage example

c
__vram_memset(0x98E2,1,length_base+length_extra); // Three solid tiles, not three tile patterns.

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

This example uses __vram_memset to fill three map bytes at tile (2,7), pixel (16,56), with tile number 1. Expect a solid 24 × 8 bar with blank neighboring cells. The count is computed from variables or a function result; all three bytes must still receive value 1.

The supplied original graphics use tile 1 for a solid 8 × 8 square, tile 2 for its left four columns, and tile 3 for a left-aligned triangle that widens by one pixel per row from 1 to 8 pixels. DMG uses black on white; CGB distinguishes the operations with colors while keeping text black.

The square at pixel (16,88) must remain after zero-length safe and unsafe calls. With the LCD on, safe copy and fill add squares at (16,96) and (32,96). FAILED CHECKS must read 000. The emulator tests compare the drawn pixels as well as the counter. CGB uses bank 0 in this example; other banks and physical hardware are not tested here.

In CGB mode, the operation rows from MEMCPY through FILL UNSAFE cycle through red, blue and green. The ZERO COUNT square is red. In LCD ON, the left square is blue and the right square is green. Text stays black. The checks verify these colors at every drawn pixel.

__vram_memset: DMG
DMG
__vram_memset: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:147

__wait_vblank

With LCD running, wait out any current VBlank and then wait for the next LY≥144.

Syntax

c
void __wait_vblank();

Return value

No return value.

Conditions and limits

Returns immediately with LCD off. Polls LY without enabling interrupts or advancing a library frame counter.

Usage example

c
__wait_vblank();result[10]=*((u8*)0xFF44);M_LCDC=0;__wait_vblank();__wait_ly(200);result[11]=1;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn map selection, scrolling, nesting and scanline waits. Bases are 38912/39936; nested tokens are 0/1; observed target LY values are 80/144. Also verify waits return with the LCD off.

__wait_vblank: DMG
DMG
__wait_vblank: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:138

kitaqgb/kitaqgb/CodeGenerator.cs:7451

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

                AsmOperand wbRet = MakeUniqueLabel("waitvb_ret");
                AsmOperand wbExitVblank = MakeUniqueLabel("waitvb_exit");
                AsmOperand wbEnterVblank = MakeUniqueLabel("waitvb_enter");

                // If LCD is off, return immediately.
                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
                EmitAsm("JR_Z", wbRet);

                // while (LY >= 144) {}
                EmitLabel(wbExitVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);
                EmitAsm("JR", wbExitVblank);

                // while (LY < 144) {}
                EmitLabel(wbEnterVblank);
                EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
                EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
                EmitAsm("JR_C", wbEnterVblank);

                EmitLabel(wbRet);
                return;
            }
__xy_in_rect

Test membership in a rectangle including its top/left and excluding its bottom/right.

Syntax

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

Parameters

x

Point X, 0..255.

y

Point Y, 0..255.

rx

Rectangle left, 0..255.

ry

Rectangle top, 0..255.

rw

Width, 0..255.

rh

Height, 0..255.

Return value

Returns 1 inside, otherwise 0. Zero width or height is empty.

Conditions and limits

Uses coordinate differences: a right bound above 255 does not wrap to the left.

Usage example

c
result[0]=__xy_in_rect(10,20,10,20,3,2);result[1]=__xy_in_rect(13,20,10,20,3,2);result[2]=__xy_in_rect(9,20,10,20,3,2);result[3]=__xy_in_rect(255,1,250,0,10,2);result[4]=__xy_in_rect(10,20,10,20,0,2);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Exercise the interior, boundaries, outside point and zero width: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verify distance 15, wrapped distance 254 and array indices 64/65280.

__xy_in_rect: DMG
DMG
__xy_in_rect: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/rpg.h:182

kitaqgb/kitaqgb/CodeGenerator.cs:8436

csharp
if (funcName == "__xy_in_rect")
            {
                if (args.Length != 6) Error(expr, "__xy_in_rect(x, y, rx, ry, rw, rh) expects 6 arguments");

                Expr[] foldedRectArgs = args.Select(FoldConstants).ToArray();
                if (foldedRectArgs[0].Match(Tag.Integer, out int constX) &&
                    foldedRectArgs[1].Match(Tag.Integer, out int constY) &&
                    foldedRectArgs[2].Match(Tag.Integer, out int constRx) &&
                    foldedRectArgs[3].Match(Tag.Integer, out int constRy) &&
                    foldedRectArgs[4].Match(Tag.Integer, out int constRw) &&
                    foldedRectArgs[5].Match(Tag.Integer, out int constRh))
                {
                    int x = constX & 0xFF;
                    int y = constY & 0xFF;
                    int rx = constRx & 0xFF;
                    int ry = constRy & 0xFF;
                    int rw = constRw & 0xFF;
                    int rh = constRh & 0xFF;
                    bool inside = x >= rx && y >= ry && (x - rx) < rw && (y - ry) < rh;
                    if (inside) EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                    else EmitAsm("XOR_A");
                    return;
                }

                EmitPushExprAsWord(args[0]); // x
                EmitPushExprAsWord(args[1]); // y
                EmitPushExprAsWord(args[2]); // rx
                EmitPushExprAsWord(args[3]); // ry
                EmitPushExprAsWord(args[4]); // rw
                EmitPushExprAsWord(args[5]); // rh

                AsmOperand ret0 = MakeUniqueLabel("xyrect_ret0");
                AsmOperand done = MakeUniqueLabel("xyrect_done");

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // x
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rx
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rw
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_B_A"); // y
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // ry
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_C", ret0);
                EmitAsm("SUB_C");
                EmitAsm("LD_B_A");
                EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
                EmitAsm("LD_A_HL");
                EmitAsm("LD_C_A"); // rh
                EmitAsm("LD_A_B");
                EmitAsm("CP_C");
                EmitAsm("JR_NC", ret0);

                EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
                EmitAsm("JR", done);
                EmitLabel(ret0);
                EmitAsm("XOR_A");
                EmitLabel(done);
                EmitAsm("ADD_SP_IMM", new AsmOperand(12, AddressMode.Relative));
                return;
            }

scroll.h — Background, window and split scrolling

Control immediate and buffered background/window scrolling and up to eight raster splits. Distinguish getter-managed current values, live hardware registers and pending values.

__scroll_bg_add

Add signed deltas to tracked background coordinates and apply them immediately.

Syntax

c
void __scroll_bg_add(s8 dx, s8 dy);

Parameters

dx

Signed horizontal pixel delta, −128 through 127.

dy

Signed vertical pixel delta, −128 through 127.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair. Results wrap modulo 256. Additions start from tracked current values, not pending values or temporary raster register values.

Usage example

c
__scroll_bg_add(-20,3);result[22]=M_SCX;result[23]=M_SCY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_add: DMG
DMG
__scroll_bg_add: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_add: DMG
DMG
__scroll_bg_add: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:37

kitaqgb/kitaqgb/CodeGenerator.cs:7835

csharp
if (funcName == "__scroll_bg_add")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_add(dx, dy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_HL");
                EmitAsm("LD_B_L");

                EmitLoadA(ScrollBgXCurVar);
                EmitAsm("ADD_B");
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX

                EmitLoadA(ScrollBgYCurVar);
                EmitAsm("ADD_C");
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_set_buffered

Stage both background scroll coordinates for a later flush.

Syntax

c
void __scroll_bg_set_buffered(u8 scx, u8 scy);

Parameters

scx

Horizontal background scroll, 0–255 pixels; increasing it moves the background left.

scy

Vertical background scroll, 0–255 pixels; increasing it moves the background up.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Does not change current tracked values or registers. Apply it with __scroll_flush or Scroll_Flush; staging itself does not wait for VBlank.

Usage example

c
__scroll_bg_set_buffered(16,24);result[14]=M_SCX;result[15]=__scroll_bg_x_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_set_buffered: DMG
DMG
__scroll_bg_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_set_buffered: DMG
DMG
__scroll_bg_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:22

kitaqgb/kitaqgb/CodeGenerator.cs:7671

csharp
if (funcName == "__scroll_bg_set_buffered")
            {
                if (args.Length != 2) Error(expr, "__scroll_bg_set_buffered(scx, scy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LD_A_B");
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_x_get

Read the tracked current horizontal background scroll.

Syntax

c
u8 __scroll_bg_x_get();

Return value

Returns the tracked current byte, 0–255. WX includes its seven-pixel hardware offset.

Conditions and limits

This is not a hardware-register read. It excludes pending buffered values, direct register writes and temporary values applied by raster splits.

Usage example

c
result[6]=__scroll_bg_x_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_x_get: DMG
DMG
__scroll_bg_x_get: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_x_get: DMG
DMG
__scroll_bg_x_get: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:13

kitaqgb/kitaqgb/CodeGenerator.cs:7571

csharp
if (funcName == "__scroll_bg_x_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_bg_x_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollBgXCurVar);
                return;
            }
__scroll_bg_x_set

Set horizontal background scroll immediately.

Syntax

c
void __scroll_bg_x_set(u8 scx);

Parameters

scx

Horizontal background scroll, 0–255 pixels; increasing it moves the background left.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair.

Usage example

c
__scroll_bg_x_set(250);result[4]=M_SCX;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_x_set: DMG
DMG
__scroll_bg_x_set: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_x_set: DMG
DMG
__scroll_bg_x_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:11

kitaqgb/kitaqgb/CodeGenerator.cs:7535

csharp
if (funcName == "__scroll_bg_x_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_x_set(scx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
                EmitLoadA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_x_set_buffered

Stage only background X.

Syntax

c
void __scroll_bg_x_set_buffered(u8 scx);

Parameters

scx

Horizontal background scroll, 0–255 pixels; increasing it moves the background left.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Retains the other pending axis. Current values and registers remain unchanged until flush; initialize the coordinate pair first.

Usage example

c
__scroll_bg_x_set_buffered(32);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_x_set_buffered: DMG
DMG
__scroll_bg_x_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_x_set_buffered: DMG
DMG
__scroll_bg_x_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:23

kitaqgb/kitaqgb/CodeGenerator.cs:7693

csharp
if (funcName == "__scroll_bg_x_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_x_set_buffered(scx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_y_get

Read the tracked current vertical background scroll.

Syntax

c
u8 __scroll_bg_y_get();

Return value

Returns the tracked current byte, 0–255. WX includes its seven-pixel hardware offset.

Conditions and limits

This is not a hardware-register read. It excludes pending buffered values, direct register writes and temporary values applied by raster splits.

Usage example

c
result[7]=__scroll_bg_y_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_y_get: DMG
DMG
__scroll_bg_y_get: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_y_get: DMG
DMG
__scroll_bg_y_get: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:14

kitaqgb/kitaqgb/CodeGenerator.cs:7580

csharp
if (funcName == "__scroll_bg_y_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_bg_y_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollBgYCurVar);
                return;
            }
__scroll_bg_y_set

Set vertical background scroll immediately.

Syntax

c
void __scroll_bg_y_set(u8 scy);

Parameters

scy

Vertical background scroll, 0–255 pixels; increasing it moves the background up.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair.

Usage example

c
__scroll_bg_y_set(2);result[5]=M_SCY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_y_set: DMG
DMG
__scroll_bg_y_set: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_y_set: DMG
DMG
__scroll_bg_y_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:12

kitaqgb/kitaqgb/CodeGenerator.cs:7553

csharp
if (funcName == "__scroll_bg_y_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_y_set(scy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgYCurVar);
                EmitStoreA(ScrollBgYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
                EmitLoadA(ScrollBgXCurVar);
                EmitStoreA(ScrollBgXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_bg_y_set_buffered

Stage only background Y.

Syntax

c
void __scroll_bg_y_set_buffered(u8 scy);

Parameters

scy

Vertical background scroll, 0–255 pixels; increasing it moves the background up.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Retains the other pending axis. Current values and registers remain unchanged until flush; initialize the coordinate pair first.

Usage example

c
__scroll_bg_y_set_buffered(40);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_bg_y_set_buffered: DMG
DMG
__scroll_bg_y_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_bg_y_set_buffered: DMG
DMG
__scroll_bg_y_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:24

kitaqgb/kitaqgb/CodeGenerator.cs:7707

csharp
if (funcName == "__scroll_bg_y_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_bg_y_set_buffered(scy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollBgYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_flush

Apply dirty background/window coordinates to tracked state and hardware, then clear their pending flags.

Syntax

c
void __scroll_flush();

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

A clean coordinate pair is left alone. No interrupt masking or VBlank wait is performed; arrange the required timing in the caller.

Usage example

c
__scroll_flush();result[18]=M_SCX;result[19]=M_SCY;result[20]=M_WX;result[21]=M_WY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_flush: DMG
DMG
__scroll_flush: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_flush: DMG
DMG
__scroll_flush: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:28

kitaqgb/kitaqgb/CodeGenerator.cs:7771

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

                AsmOperand flushSkipBg = MakeUniqueLabel("scroll_flush_skip_bg");
                AsmOperand flushSkipWin = MakeUniqueLabel("scroll_flush_skip_win");

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
                EmitAsm("JR_Z", flushSkipBg);
                EmitLoadA(ScrollBgXNextVar);
                EmitStoreA(ScrollBgXCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
                EmitLoadA(ScrollBgYNextVar);
                EmitStoreA(ScrollBgYCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
                EmitLabel(flushSkipBg);

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitAsm("JR_Z", flushSkipWin);
                EmitLoadA(ScrollWinXNextVar);
                EmitStoreA(ScrollWinXCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
                EmitLoadA(ScrollWinYNextVar);
                EmitStoreA(ScrollWinYCurVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
                EmitLabel(flushSkipWin);

                EmitAsm("XOR_A");
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_split_commit

Capture base window visibility and arm the first scanline event in the split table.

Syntax

c
void __scroll_split_commit();

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

The shared table holds eight entries. Appending neither applies nor sorts them, and excess entries are ignored. Rebuild with reset→push→commit at a safe frame boundary and space events far enough apart for the handler to finish. Interrupt latency means the scheduled line and first affected pixel need not coincide. A nonempty table enables VBlank/STAT in IE and executes EI. An empty table disables the LYC source but does not restore IE or CPU interrupt state.

Usage example

c
__scroll_split_commit();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Split the screen into three scrolling bands with BG X=0→8→16. Three 24×16 rectangles appear at screen (32,16), (24,80) and (16,112). Events are scheduled at LY=64 and 104. Rectangles are black on DMG and red on CGB.

__scroll_split_commit: DMG
DMG
__scroll_split_commit: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:33

kitaqgb/kitaqgb/CodeGenerator.cs:7959

csharp
if (funcName == "__scroll_split_commit")
            {
                if (args.Length != 0) Error(expr, "__scroll_split_commit() expects 0 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_commit_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_push

Append a split entry that changes background scrolling at the requested scanline event.

Syntax

c
void __scroll_split_push(u8 ly, u8 scx, u8 scy);

Parameters

ly

Scanline event number; use 0–143 for visible events and append in ascending order. A first entry at zero is applied during VBlank.

scx

Horizontal background scroll, 0–255 pixels; increasing it moves the background left.

scy

Vertical background scroll, 0–255 pixels; increasing it moves the background up.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

The shared table holds eight entries. Appending neither applies nor sorts them, and excess entries are ignored. Rebuild with reset→push→commit at a safe frame boundary and space events far enough apart for the handler to finish. Interrupt latency means the scheduled line and first affected pixel need not coincide.

Usage example

c
__scroll_split_push(64,8,0);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Split the screen into three scrolling bands with BG X=0→8→16. Three 24×16 rectangles appear at screen (32,16), (24,80) and (16,112). Events are scheduled at LY=64 and 104. Rectangles are black on DMG and red on CGB.

__scroll_split_push: DMG
DMG
__scroll_split_push: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:31

kitaqgb/kitaqgb/CodeGenerator.cs:7907

csharp
if (funcName == "__scroll_split_push")
            {
                if (args.Length != 3) Error(expr, "__scroll_split_push(ly, scx, scy) expects 3 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;

                CompileIntoA(args[0]);
                EmitStoreA(ScrollSplitTmpLyVar);
                CompileIntoA(args[1]);
                EmitStoreA(ScrollSplitTmpScxVar);
                CompileIntoA(args[2]);
                EmitStoreA(ScrollSplitTmpScyVar);
                EmitAsm("XOR_A");
                EmitStoreA(ScrollSplitTmpWxVar);
                EmitStoreA(ScrollSplitTmpWyVar);
                EmitAsm("LD_A_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate));
                EmitStoreA(ScrollSplitTmpFlagsVar);
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_push_ex

Append a split entry whose flags select background, window, visibility or background-color-zero operations.

Syntax

c
void __scroll_split_push_ex(u8 ly, u8 scx, u8 scy, u8 wx, u8 wy, u8 flags);

Parameters

ly

Scanline event number; use 0–143 for visible events and append in ascending order. A first entry at zero is applied during VBlank.

scx

Horizontal background scroll, 0–255 pixels; increasing it moves the background left.

scy

Vertical background scroll, 0–255 pixels; increasing it moves the background up.

wx

Raw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.

wy

Window top in screen Y pixels, 0–255; visible rows are 0–143.

flags

Operation bits: BG=1, window position=2, show=4, hide=8, CGB background palette 0/color 0=16. Unknown bits are discarded. Color-zero mode reuses scx/scy for the RGB15 low/high bytes.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

The shared table holds eight entries. Appending neither applies nor sorts them, and excess entries are ignored. Rebuild with reset→push→commit at a safe frame boundary and space events far enough apart for the handler to finish. Interrupt latency means the scheduled line and first affected pixel need not coincide. Show wins when both visibility bits are set. Color zero is CGB-only; combining it with BG movement uses the same scx/scy bytes for both purposes.

Usage example

c
__scroll_split_push_ex(104,16,0,7,0,KQ_SCROLL_SPLIT_USE_BG|KQ_SCROLL_SPLIT_WIN_HIDE);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Split the screen into three scrolling bands with BG X=0→8→16. Three 24×16 rectangles appear at screen (32,16), (24,80) and (16,112). Events are scheduled at LY=64 and 104. Rectangles are black on DMG and red on CGB.

__scroll_split_push_ex: DMG
DMG
__scroll_split_push_ex: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:32

kitaqgb/kitaqgb/CodeGenerator.cs:7929

csharp
if (funcName == "__scroll_split_push_ex")
            {
                if (args.Length != 6) Error(expr, "__scroll_split_push_ex(ly, scx, scy, wx, wy, flags) expects 6 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;

                if (TryGetU8Const(args[5], out int splitFlagsConst))
                {
                    int unknownBits = splitFlagsConst & ~ScrollSplitFlagMask;
                    if (unknownBits != 0)
                        Warning(args[5], string.Format("__scroll_split_push_ex() ignores unknown flag bits 0x{0:X2}", unknownBits));
                }

                CompileIntoA(args[0]);
                EmitStoreA(ScrollSplitTmpLyVar);
                CompileIntoA(args[1]);
                EmitStoreA(ScrollSplitTmpScxVar);
                CompileIntoA(args[2]);
                EmitStoreA(ScrollSplitTmpScyVar);
                CompileIntoA(args[3]);
                EmitStoreA(ScrollSplitTmpWxVar);
                CompileIntoA(args[4]);
                EmitStoreA(ScrollSplitTmpWyVar);
                CompileIntoA(args[5]);
                EmitStoreA(ScrollSplitTmpFlagsVar);
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_split_reset

Clear the split count and index, disable split processing and clear STAT’s LYC interrupt-enable bit.

Syntax

c
void __scroll_split_reset();

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

The shared table holds eight entries. Appending neither applies nor sorts them, and excess entries are ignored. Rebuild with reset→push→commit at a safe frame boundary and space events far enough apart for the handler to finish. Interrupt latency means the scheduled line and first affected pixel need not coincide. It does not erase table bytes, clear IE, or disable CPU interrupts.

Usage example

c
__scroll_split_reset();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Split the screen into three scrolling bands with BG X=0→8→16. Three 24×16 rectangles appear at screen (32,16), (24,80) and (16,112). Events are scheduled at LY=64 and 104. Rectangles are black on DMG and red on CGB.

__scroll_split_reset: DMG
DMG
__scroll_split_reset: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:30

kitaqgb/kitaqgb/CodeGenerator.cs:7897

csharp
if (funcName == "__scroll_split_reset")
            {
                if (args.Length != 0) Error(expr, "__scroll_split_reset() expects 0 arguments");
                EnsureScrollState();
                UseScrollSplitHelpers = true;
                EmitAsm("CALL", new AsmOperand("__kq_scroll_split_reset_core", AddressMode.Absolute));
                return;
            }

void AppendScrollHelpersIfUsed(List<Expr> rawLines)
    {
        bool needScrollHelpers = UseScrollSplitHelpers;
        if (!needScrollHelpers)
        {
            foreach (var e in rawLines)
            {
                string m;
                AsmOperand o;
                if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
                string target = o.Base.Value;
                if (target == "__kq_scroll_split_reset_core" ||
                    target == "__kq_scroll_split_push_core" ||
                    target == "__kq_scroll_split_commit_core")
                {
                    needScrollHelpers = true;
                    break;
                }
            }
        }

        if (!needScrollHelpers) return;

        EnsureScrollState();

        var injected = new List<Expr>();
        injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));

        // Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
        string resetDone = "__kq_scroll_split_reset_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, resetDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
        string pushHaveRoom = "__kq_scroll_split_push_have_room";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDI_HL_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("RET"));

        // Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
        string schedDisable = "__kq_scroll_schedule_next_disable";
        string schedDisabled = "__kq_scroll_schedule_next_disabled";
        string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, schedDisable));
        // Do not rewrite a disabled STAT source: on DMG even writing zero
        // can raise a fresh request and repeatedly re-enter the STAT handler.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, schedDisabled));
        injected.Add(Expr.MakeAsm("RET"));

        // Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
        string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
        string applySkipWin = "__kq_scroll_apply_entry_skip_win";
        string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
        string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
        string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
        string applyDone = "__kq_scroll_apply_entry_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // scy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_C_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wx
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_D_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // wy
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("INC_HL")); // flags
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_H_A"));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.Make(Tag.Label, applySkipBg));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_D"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_E"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.Make(Tag.Label, applySkipWin));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipHide));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
        injected.Add(Expr.MakeAsm("LD_A_H"));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
        // CGB palette data writes are blocked during pixel transfer. Wait through
        // OAM search as well, so both color bytes fit in the following HBlank.
        // The LY-zero entry runs in VBlank and does not wait here.
        injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
        injected.Add(Expr.MakeAsm("LD_A_C"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
        injected.Add(Expr.Make(Tag.Label, applyDone));
        injected.Add(Expr.MakeAsm("RET"));

        // Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
        string commitDisable = "__kq_scroll_split_commit_disable";
        injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
        injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("EI"));
        injected.Add(Expr.MakeAsm("RET"));
        injected.Add(Expr.Make(Tag.Label, commitDisable));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("RET"));

        // Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
        // A first entry at LY zero is applied immediately before scheduling the following entry.
        string vbDisable = "__kq_vblank_disable";
        string vbFirstNonZero = "__kq_vblank_first_nonzero";
        string vbDone = "__kq_vblank_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        // Set the existing fixed-address frame-ready flag used by the VBlank path.
        injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
        injected.Add(Expr.MakeAsm("OR_B"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
        injected.Add(Expr.MakeAsm("XOR_A"));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDisable));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, vbDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        // Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
        string statDisable = "__kq_stat_disable";
        string statDone = "__kq_stat_done";
        injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("PUSH_BC"));
        injected.Add(Expr.MakeAsm("PUSH_DE"));
        injected.Add(Expr.MakeAsm("PUSH_HL"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
        injected.Add(Expr.MakeAsm("PUSH_AF"));
        injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
        injected.Add(Expr.MakeAsm("OR_A"));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_B"));
        injected.Add(Expr.MakeAsm("LD_E_A"));
        injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LD_L_A"));
        injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        injected.Add(Expr.MakeAsm("ADD_HL_HL"));
        injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("ADD_HL_DE"));
        // DMG STAT writes can request an interrupt unrelated to LYC. Consume
        // this entry only on its scheduled line; leave the index intact otherwise.
        injected.Add(Expr.MakeAsm("LD_A_HL"));
        injected.Add(Expr.MakeAsm("LD_B_A"));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("CP_B"));
        injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("INC_A"));
        injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
        injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.Make(Tag.Label, statDisable));
        // Acknowledge an already-disabled source without writing STAT again.
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
        injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
        injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
        injected.Add(Expr.Make(Tag.Label, statDone));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
        injected.Add(Expr.MakeAsm("POP_HL"));
        injected.Add(Expr.MakeAsm("POP_DE"));
        injected.Add(Expr.MakeAsm("POP_BC"));
        injected.Add(Expr.MakeAsm("POP_AF"));
        injected.Add(Expr.MakeAsm("RETI"));

        int insertAt = 0;
        while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
        rawLines.InsertRange(insertAt, injected);
    }
__scroll_win_add

Add signed deltas to tracked window coordinates and apply them immediately.

Syntax

c
void __scroll_win_add(s8 dx, s8 dy);

Parameters

dx

Signed horizontal pixel delta, −128 through 127.

dy

Signed vertical pixel delta, −128 through 127.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair. Results wrap modulo 256. Additions start from tracked current values, not pending values or temporary raster register values.

Usage example

c
__scroll_win_add(4,-40);result[24]=M_WX;result[25]=M_WY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_add: DMG
DMG
__scroll_win_add: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_add: DMG
DMG
__scroll_win_add: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:38

kitaqgb/kitaqgb/CodeGenerator.cs:7866

csharp
if (funcName == "__scroll_win_add")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_add(dx, dy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_C_A");
                EmitAsm("POP_HL");
                EmitAsm("LD_B_L");

                EmitLoadA(ScrollWinXCurVar);
                EmitAsm("ADD_B");
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX

                EmitLoadA(ScrollWinYCurVar);
                EmitAsm("ADD_C");
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_hide

Clear the LCDC window bit and tracked window visibility state.

Syntax

c
void __scroll_win_hide();

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Does not change coordinates or map selection. Visibility also requires the LCD/background-window facilities to be enabled and the window to lie within the visible area.

Usage example

c
__scroll_win_hide();result[27]=M_LCDC&32;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_hide: DMG
DMG
__scroll_win_hide: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_hide: DMG
DMG
__scroll_win_hide: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:36

kitaqgb/kitaqgb/CodeGenerator.cs:7821

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

                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate));
                EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
                EmitAsm("XOR_A");
                EmitStoreA(ScrollWinVisibleVar);
                return;
            }
__scroll_win_set

Set window WX and WY immediately.

Syntax

c
void __scroll_win_set(u8 wx, u8 wy);

Parameters

wx

Raw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.

wy

Window top in screen Y pixels, 0–255; visible rows are 0–143.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair.

Usage example

c
__scroll_win_set(15,8);result[8]=M_WX;result[9]=M_WY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_set: DMG
DMG
__scroll_win_set: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_set: DMG
DMG
__scroll_win_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:16

kitaqgb/kitaqgb/CodeGenerator.cs:7589

csharp
if (funcName == "__scroll_win_set")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_set(wx, wy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX

                EmitAsm("LD_A_B");
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY

                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_set_buffered

Stage both window coordinates for a later flush.

Syntax

c
void __scroll_win_set_buffered(u8 wx, u8 wy);

Parameters

wx

Raw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.

wy

Window top in screen Y pixels, 0–255; visible rows are 0–143.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Does not change current tracked values or registers. Apply it with __scroll_flush or Scroll_Flush; staging itself does not wait for VBlank.

Usage example

c
__scroll_win_set_buffered(39,32);result[16]=M_WX;result[17]=__scroll_win_x_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_set_buffered: DMG
DMG
__scroll_win_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_set_buffered: DMG
DMG
__scroll_win_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:25

kitaqgb/kitaqgb/CodeGenerator.cs:7721

csharp
if (funcName == "__scroll_win_set_buffered")
            {
                if (args.Length != 2) Error(expr, "__scroll_win_set_buffered(wx, wy) expects 2 arguments");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitPushAAsWord();
                CompileIntoA(args[1]);
                EmitAsm("LD_B_A");
                EmitAsm("POP_HL");

                EmitAsm("LD_A_L");
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LD_A_B");
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_show

Enable the LCDC window bit and the tracked window visibility state.

Syntax

c
void __scroll_win_show();

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Does not change coordinates or map selection. Visibility also requires the LCD/background-window facilities to be enabled and the window to lie within the visible area.

Usage example

c
__scroll_win_show();result[26]=M_LCDC&32;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_show: DMG
DMG
__scroll_win_show: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_show: DMG
DMG
__scroll_win_show: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:35

kitaqgb/kitaqgb/CodeGenerator.cs:7807

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

                EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
                EmitAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate));
                EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
                EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
                EmitStoreA(ScrollWinVisibleVar);
                return;
            }
__scroll_win_x_get

Read the tracked current window WX value.

Syntax

c
u8 __scroll_win_x_get();

Return value

Returns the tracked current byte, 0–255. WX includes its seven-pixel hardware offset.

Conditions and limits

This is not a hardware-register read. It excludes pending buffered values, direct register writes and temporary values applied by raster splits.

Usage example

c
result[12]=__scroll_win_x_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_x_get: DMG
DMG
__scroll_win_x_get: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_x_get: DMG
DMG
__scroll_win_x_get: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:19

kitaqgb/kitaqgb/CodeGenerator.cs:7653

csharp
if (funcName == "__scroll_win_x_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_x_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollWinXCurVar);
                return;
            }
__scroll_win_x_set

Set only the window’s raw WX coordinate immediately.

Syntax

c
void __scroll_win_x_set(u8 wx);

Parameters

wx

Raw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair.

Usage example

c
__scroll_win_x_set(23);result[10]=M_WX;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_x_set: DMG
DMG
__scroll_win_x_set: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_x_set: DMG
DMG
__scroll_win_x_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:17

kitaqgb/kitaqgb/CodeGenerator.cs:7617

csharp
if (funcName == "__scroll_win_x_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_x_set(wx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
                EmitLoadA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_x_set_buffered

Stage only window WX.

Syntax

c
void __scroll_win_x_set_buffered(u8 wx);

Parameters

wx

Raw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Retains the other pending axis. Current values and registers remain unchanged until flush; initialize the coordinate pair first.

Usage example

c
__scroll_win_x_set_buffered(55);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_x_set_buffered: DMG
DMG
__scroll_win_x_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_x_set_buffered: DMG
DMG
__scroll_win_x_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:26

kitaqgb/kitaqgb/CodeGenerator.cs:7743

csharp
if (funcName == "__scroll_win_x_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_x_set_buffered(wx) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_y_get

Read the tracked current window WY value.

Syntax

c
u8 __scroll_win_y_get();

Return value

Returns the tracked current byte, 0–255. WX includes its seven-pixel hardware offset.

Conditions and limits

This is not a hardware-register read. It excludes pending buffered values, direct register writes and temporary values applied by raster splits.

Usage example

c
result[13]=__scroll_win_y_get();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_y_get: DMG
DMG
__scroll_win_y_get: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_y_get: DMG
DMG
__scroll_win_y_get: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:20

kitaqgb/kitaqgb/CodeGenerator.cs:7662

csharp
if (funcName == "__scroll_win_y_get")
            {
                if (args.Length != 0) Error(expr, "__scroll_win_y_get() expects 0 arguments");
                EnsureScrollState();
                EmitLoadA(ScrollWinYCurVar);
                return;
            }
__scroll_win_y_set

Set only the window’s WY coordinate immediately.

Syntax

c
void __scroll_win_y_set(u8 wy);

Parameters

wy

Window top in screen Y pixels, 0–255; visible rows are 0–143.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

No wait is performed. Immediate setters/additions synchronize the affected pending pair to current values and clear its dirty flag. Even a single-axis immediate setter cancels the other pending axis in the same pair.

Usage example

c
__scroll_win_y_set(16);result[11]=M_WY;

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_y_set: DMG
DMG
__scroll_win_y_set: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_y_set: DMG
DMG
__scroll_win_y_set: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:18

kitaqgb/kitaqgb/CodeGenerator.cs:7635

csharp
if (funcName == "__scroll_win_y_set")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_y_set(wy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinYCurVar);
                EmitStoreA(ScrollWinYNextVar);
                EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
                EmitLoadA(ScrollWinXCurVar);
                EmitStoreA(ScrollWinXNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }
__scroll_win_y_set_buffered

Stage only window WY.

Syntax

c
void __scroll_win_y_set_buffered(u8 wy);

Parameters

wy

Window top in screen Y pixels, 0–255; visible rows are 0–143.

Return value

No return value. The affected state and when it takes effect are described in this entry.

Conditions and limits

Retains the other pending axis. Current values and registers remain unchanged until flush; initialize the coordinate pair first.

Usage example

c
__scroll_win_y_set_buffered(48);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

Learn immediate versus pending values and wrapping signed additions. BG X is 250 before flush and 16 afterward; WX changes from 23 to 39. Adding −20 to BG X produces 252; adding −40 to WY produces 248. The visual example also shows a background rectangle and an independent window frame.

__scroll_win_y_set_buffered: DMG
DMG
__scroll_win_y_set_buffered: CGB
CGB

Complete example program

Build command

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

Usage example

c
Camera_Init(&view);
Camera_Set(&view,2048,4096);
Camera_ApplyBg(&view);
Scroll_SetWindow(103,80);
Scroll_WindowShow();

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

With camera (8,16), the red 32×24 rectangle appears at screen (24,16). The window shows a blue 64×64 frame at (96,80) and a green 16×16 square at (112,96). All shapes are black on DMG.

__scroll_win_y_set_buffered: DMG
DMG
__scroll_win_y_set_buffered: CGB
CGB

Complete example program

Build command

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

kitaqgb/lib/scroll.h:27

kitaqgb/kitaqgb/CodeGenerator.cs:7757

csharp
if (funcName == "__scroll_win_y_set_buffered")
            {
                if (args.Length != 1) Error(expr, "__scroll_win_y_set_buffered(wy) expects 1 argument");
                EnsureScrollState();

                CompileIntoA(args[0]);
                EmitStoreA(ScrollWinYNextVar);
                EmitLoadA(ScrollDirtyVar);
                EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
                EmitStoreA(ScrollDirtyVar);
                return;
            }

sprite.h — OBJ allocation, positioning, metasprites and animation

Sprites are movable hardware objects, normally 8×8 or 8×16 pixels, described by entries in Object Attribute Memory (OAM). Allocate slots, set their position, tile and attributes in shadow OAM, then transfer that data. A metasprite combines several slots into one object. GB provides 40 slots with a 10-per-scanline limit; FC provides 64 with an 8-per-scanline limit.

GB attributes: bit 4 selects DMG palette OBP1; bits 5/6 flip X/Y; bit 7 places the sprite behind nonzero background pixels. On CGB, bits 0..2 select the object palette and bit 3 the tile-data VRAM bank; the DMG palette-select bit is not used for native CGB colors.

The example uses the full GB sprite library. Defining KQ_SPRITE_MINIMAL_RUNTIME omits sprite_free, sprite_set_pos, sprite_count_used, both scanline queries, metasprite_draw and anim_update from sprite.c; do not enable it when using those functions.

__oam_dma

Copy 160 bytes from a memory page to hardware OAM (0xFE00–0xFE9F), replacing all 40 sprite entries. The call returns after the transfer finishes.

Syntax

c
void __oam_dma(u16 src_ptr);

Parameters

src_ptr

Source address, in bytes. Only the high byte is used: 0xC480 selects 0xC400–0xC49F. Provide 160 readable bytes at a 256-byte boundary. Use a hardware-supported source page (0x00–0xDF) and keep any required ROM or RAM bank mapped. The compiler does not validate the address or alignment.

Return value

None (void).

Conditions and limits

Each entry has four bytes: Y, X, tile number, attributes. Screen coordinates are (X−8, Y−16). Setting Y to 0 hides an unused entry. This intrinsic copies raw entries; it does not allocate sprites or update the sprite library’s counters or shadow buffer.

Call during VBlank or while the LCD is off. The function does not wait for VBlank or change LCD settings. The hardware transfer takes 160 CPU machine cycles, plus the function’s setup and return overhead.

Programs containing this call reserve 8 bytes of HRAM for the transfer routine. The routine is initialized on each call and waits in HRAM while DMA is active. Interrupt sources are masked temporarily; IE and the IME enable state are preserved. Interrupt service is delayed until the transfer completes.

Usage example

c
// raw_oam is a 160-byte array at address 0xC400.
m_wait();
__oam_dma((u16)raw_oam);

This is a calling fragment. Follow the stated initialization and buffer requirements before using it in a complete program.

Expected result

The sample builds a complete OAM page directly. It displays an 8×8 right-pointing arrow at (88,32), its horizontal mirror at (88,56), and an 8×8 square at (88,80). CGB uses red arrow edges, a green shaft, blue tip details and a green square; DMG uses gray shades. All other slots are hidden. The verification checks the actual image pixels on both emulator hardware modes; physical hardware has not been tested.

__oam_dma: DMG
DMG
__oam_dma: CGB
CGB

Complete example program

Build command

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqgb\kitaqgb.exe .\kitaq-docs\samples\api-examples\gb\oam_dma.c -I .\kitaqgb\lib -I .\kitaq-docs\samples -o .\out\oam_dma.gb --cgb=cgb --profile=dev --rst-disable --stack-bank=fixed --no-cache --no-disasm
Implementation and source

kitaqgb/lib/sprite.h:46

csharp
// Copy one 160-byte, page-aligned OAM source and return after DMA completes.
            if (funcName == "__oam_dma")
            {
                if (args.Length != 1) Error(expr, "__oam_dma(src_ptr) expects 1 argument");

                CompileIntoHL(args[0]); // Evaluate once; hardware uses only the high byte.
                // Mask interrupt sources without changing IME, including calls made inside an ISR.
                EmitAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute));
                EmitAsm("PUSH_AF");
                EmitAsm("XOR_A");
                EmitAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute));
                // LDH [FF46],A; LD B,40; DEC B; JR NZ,-3; RET.
                // Initialize on every call, so no startup copy or readiness flag is needed.
                byte[] dmaStub = { 0xE0, 0x46, 0x06, 0x28, 0x05, 0x20, 0xFD, 0xC9 };
                for (int i = 0; i < dmaStub.Length; i++)
                {
                    EmitAsm("LD_A_IMM", new AsmOperand(dmaStub[i], AddressMode.Immediate));
                    EmitAsm("LDH_MEM_A", new AsmOperand((OamDmaStubAddr + i) & 0xFF, AddressMode.HighMem));
                }
                EmitAsm("LD_A_H");
                // CALL pushes before DMA starts; RET reads the stack only after the wait.
                EmitAsm("CALL", new AsmOperand(OamDmaStubAddr, AddressMode.Absolute));
                EmitAsm("POP_AF");
                EmitAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute));
                return;
            }

Command syntax reference

The following help was captured for the source edition. Consult it for optional arguments, defaults and accepted values. Replace the illustrative input filenames with your own outputs. Captured tool responses retain their original wording.

kitaqgb
help
KITAQGB native Rust compiler

Commands:
  disasm ROM -o dis.s
  header ROM -o patched.gb [--title=NAME --cgb=dmg|cgb|cgb_only ...]
  assemble-ir INPUT.assembly.json -o output.gb [header options]
  tokenize SOURCE.c ... -I INCLUDE_DIR -o tokens.json
  parse SOURCE.c ... -I INCLUDE_DIR -o ast.json
  lower SOURCE.c ... -I INCLUDE_DIR -o lowered.json
  emit-ir SOURCE.c ... -I INCLUDE_DIR -o raw.assembly.json
  compile SOURCE.c ... -I INCLUDE_DIR -o output.gb [-O0|-O1]
  optimize-ir INPUT.assembly.json -o optimized.json [-O0|-O1]
  strip-ir INPUT.assembly.json -o stripped.json [--strip-keep=LABELS]

C compilation, aggregates, banked calls and hardware intrinsics are implemented and covered by differential tests. Utility commands: kqhelp, symfind, src2asm, romdiff, template, snippet, conventions, fixhint, irsum, attrviz, recipe, test.

Development and recovery:
  SOURCE.c ... --watch [--watch-max-builds=N]
  devserver SOURCE.c ... [--once --poll-ms=N --debounce-ms=N --max-builds=N]
  recipe [--history=FILE --out=FILE --script=FILE --last=N --stdout]
  SOURCE.c ... --minimize[=FILE] [--minimize-out=FILE --minimize-work=DIR --minimize-quick --minimize-trace=final|all]
  SOURCE.c ... --minimize-on-fail --repro-pack[=DIR]
  SOURCE.c ... --debug-output --disasm-changed[=GIT_BASE]
Watch loops stop with Ctrl+C; the Rust API also accepts a CancellationToken. See PORT_STATUS.md for executed verification and compatibility limits.

Developer command syntax

These usages come from Program.DebugTools.cs and Program.VibeTools.cs. ROM comparison, symbol search and templates are subcommands separate from ordinary C compilation.

usage
usage: kitaqgb kqhelp <KQxxxx>
usage
usage: kitaqgb symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]
usage
usage: kitaqgb src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]
usage
usage: kitaqgb romdiff <old.gb> <new.gb> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]
usage
usage: kitaqgb template [out.c] [--out=<file.c>] [--cgb] [--overwrite] [--no-build-script] [--no-readme]
usage
usage: kitaqgb conventions [--out=<file>|--stdout]
usage
usage: kitaqgb snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]
usage
usage: kitaqgb fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]
usage
usage: kitaqgb irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]
usage
usage: kitaqgb devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>
usage
usage: kitaqgb recipe [--history=<file>] [--last=N] [--out=<file>] [--script=<file>] [--stdout]

Long options recognized by the parser

Spellings were extracted from src/cli.rs, including compatibility and investigative options. Consult help and the corresponding handler to determine whether each option takes a value.

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

Appendix: assembly instruction index

This table is extracted from src/asm_info.rs. It describes the compiler internal spellings and operand formats. Entries with branch destinations or memory addresses are syntax examples, not standalone programs. Consult calling conventions and code generation for preserved registers and flag changes.

opcodeInstructionFormatSyntax example
00NOPIMPNOP
F3DIIMPDI
FBEIIMPEI
76HALTIMPHALT
10STOPIMM8STOP #1
C9RETIMPRET
C0RET_NZIMPRET_NZ
C8RET_ZIMPRET_Z
D0RET_NCIMPRET_NC
D8RET_CIMPRET_C
D9RETIIMPRETI
C3JPABSJP 0xC000
E9JP_HLIMPJP_HL
18JRRELJR +target
20JR_NZRELJR_NZ +target
28JR_ZRELJR_Z +target
30JR_NCRELJR_NC +target
38JR_CRELJR_C +target
CDCALLABSCALL 0xC000
C4CALL_NZABSCALL_NZ 0xC000
CCCALL_ZABSCALL_Z 0xC000
D4CALL_NCABSCALL_NC 0xC000
DCCALL_CABSCALL_C 0xC000
C2JP_NZABSJP_NZ 0xC000
CAJP_ZABSJP_Z 0xC000
D2JP_NCABSJP_NC 0xC000
DAJP_CABSJP_C 0xC000
C7RST_00IMPRST_00
CFRST_08IMPRST_08
D7RST_10IMPRST_10
DFRST_18IMPRST_18
E7RST_20IMPRST_20
EFRST_28IMPRST_28
F7RST_30IMPRST_30
FFRST_38IMPRST_38
3ELD_A_IMMIMM8LD_A_IMM #1
06LD_B_IMMIMM8LD_B_IMM #1
0ELD_C_IMMIMM8LD_C_IMM #1
16LD_D_IMMIMM8LD_D_IMM #1
1ELD_E_IMMIMM8LD_E_IMM #1
26LD_H_IMMIMM8LD_H_IMM #1
2ELD_L_IMMIMM8LD_L_IMM #1
7FLD_A_AIMPLD_A_A
78LD_A_BIMPLD_A_B
79LD_A_CIMPLD_A_C
7ALD_A_DIMPLD_A_D
7BLD_A_EIMPLD_A_E
7CLD_A_HIMPLD_A_H
7DLD_A_LIMPLD_A_L
47LD_B_AIMPLD_B_A
40LD_B_BIMPLD_B_B
41LD_B_CIMPLD_B_C
42LD_B_DIMPLD_B_D
43LD_B_EIMPLD_B_E
44LD_B_HIMPLD_B_H
45LD_B_LIMPLD_B_L
4FLD_C_AIMPLD_C_A
48LD_C_BIMPLD_C_B
49LD_C_CIMPLD_C_C
4ALD_C_DIMPLD_C_D
4BLD_C_EIMPLD_C_E
4CLD_C_HIMPLD_C_H
4DLD_C_LIMPLD_C_L
57LD_D_AIMPLD_D_A
50LD_D_BIMPLD_D_B
51LD_D_CIMPLD_D_C
52LD_D_DIMPLD_D_D
53LD_D_EIMPLD_D_E
54LD_D_HIMPLD_D_H
55LD_D_LIMPLD_D_L
5FLD_E_AIMPLD_E_A
58LD_E_BIMPLD_E_B
59LD_E_CIMPLD_E_C
5ALD_E_DIMPLD_E_D
5BLD_E_EIMPLD_E_E
5CLD_E_HIMPLD_E_H
5DLD_E_LIMPLD_E_L
67LD_H_AIMPLD_H_A
60LD_H_BIMPLD_H_B
61LD_H_CIMPLD_H_C
62LD_H_DIMPLD_H_D
63LD_H_EIMPLD_H_E
64LD_H_HIMPLD_H_H
65LD_H_LIMPLD_H_L
6FLD_L_AIMPLD_L_A
68LD_L_BIMPLD_L_B
69LD_L_CIMPLD_L_C
6ALD_L_DIMPLD_L_D
6BLD_L_EIMPLD_L_E
6CLD_L_HIMPLD_L_H
6DLD_L_LIMPLD_L_L
EALD_MEM_AABSLD_MEM_A 0xC000
FALD_A_MEMABSLD_A_MEM 0xC000
E0LDH_MEM_ALDHLDH_MEM_A 0x40
F0LDH_A_MEMLDHLDH_A_MEM 0x40
E2LD_C_MEM_AIMPLD_C_MEM_A
F2LD_A_MEM_CIMPLD_A_MEM_C
77LD_HL_AIMPLD_HL_A
7ELD_A_HLIMPLD_A_HL
46LD_B_HLIMPLD_B_HL
4ELD_C_HLIMPLD_C_HL
56LD_D_HLIMPLD_D_HL
5ELD_E_HLIMPLD_E_HL
66LD_H_HLIMPLD_H_HL
6ELD_L_HLIMPLD_L_HL
70LD_HL_BIMPLD_HL_B
71LD_HL_CIMPLD_HL_C
72LD_HL_DIMPLD_HL_D
73LD_HL_EIMPLD_HL_E
74LD_HL_HIMPLD_HL_H
75LD_HL_LIMPLD_HL_L
36LD_HL_REF_IMMIMM8LD_HL_REF_IMM #1
12LD_DE_AIMPLD_DE_A
1ALD_A_DEIMPLD_A_DE
02LD_BC_AIMPLD_BC_A
0ALD_A_BCIMPLD_A_BC
22LDI_HL_AIMPLDI_HL_A
2ALDI_A_HLIMPLDI_A_HL
32LDD_HL_AIMPLDD_HL_A
3ALDD_A_HLIMPLDD_A_HL
01LD_BC_IMMIMM16LD_BC_IMM #0xC000
11LD_DE_IMMIMM16LD_DE_IMM #0xC000
21LD_HL_IMMIMM16LD_HL_IMM #0xC000
31LD_SP_IMMIMM16LD_SP_IMM #0xC000
F9LD_SP_HLIMPLD_SP_HL
F8LD_HL_SP_IMMRELLD_HL_SP_IMM +target
E8ADD_SP_IMMRELADD_SP_IMM +target
08LD_MEM_SPIMM16LD_MEM_SP #0xC000
C5PUSH_BCIMPPUSH_BC
C1POP_BCIMPPOP_BC
D5PUSH_DEIMPPUSH_DE
D1POP_DEIMPPOP_DE
E5PUSH_HLIMPPUSH_HL
E1POP_HLIMPPOP_HL
F5PUSH_AFIMPPUSH_AF
F1POP_AFIMPPOP_AF
3CINC_AIMPINC_A
04INC_BIMPINC_B
0CINC_CIMPINC_C
14INC_DIMPINC_D
1CINC_EIMPINC_E
24INC_HIMPINC_H
2CINC_LIMPINC_L
34INC_HL_REFIMPINC_HL_REF
3DDEC_AIMPDEC_A
05DEC_BIMPDEC_B
0DDEC_CIMPDEC_C
15DEC_DIMPDEC_D
1DDEC_EIMPDEC_E
25DEC_HIMPDEC_H
2DDEC_LIMPDEC_L
35DEC_HL_REFIMPDEC_HL_REF
87ADD_AIMPADD_A
80ADD_BIMPADD_B
81ADD_CIMPADD_C
82ADD_DIMPADD_D
83ADD_EIMPADD_E
84ADD_HIMPADD_H
85ADD_LIMPADD_L
86ADD_HL_REFIMPADD_HL_REF
C6ADD_A_IMMIMM8ADD_A_IMM #1
8FADC_AIMPADC_A
88ADC_BIMPADC_B
89ADC_CIMPADC_C
8AADC_DIMPADC_D
8BADC_EIMPADC_E
8CADC_HIMPADC_H
8DADC_LIMPADC_L
8EADC_HL_REFIMPADC_HL_REF
CEADC_IMMIMM8ADC_IMM #1
97SUB_AIMPSUB_A
90SUB_BIMPSUB_B
91SUB_CIMPSUB_C
92SUB_DIMPSUB_D
93SUB_EIMPSUB_E
94SUB_HIMPSUB_H
95SUB_LIMPSUB_L
96SUB_HL_REFIMPSUB_HL_REF
D6SUB_IMMIMM8SUB_IMM #1
9FSBC_AIMPSBC_A
98SBC_BIMPSBC_B
99SBC_CIMPSBC_C
9ASBC_DIMPSBC_D
9BSBC_EIMPSBC_E
9CSBC_HIMPSBC_H
9DSBC_LIMPSBC_L
9ESBC_HL_REFIMPSBC_HL_REF
DESBC_IMMIMM8SBC_IMM #1
A7AND_AIMPAND_A
A0AND_BIMPAND_B
A1AND_CIMPAND_C
A2AND_DIMPAND_D
A3AND_EIMPAND_E
A4AND_HIMPAND_H
A5AND_LIMPAND_L
A6AND_HL_REFIMPAND_HL_REF
E6AND_IMMIMM8AND_IMM #1
B7OR_AIMPOR_A
B0OR_BIMPOR_B
B1OR_CIMPOR_C
B2OR_DIMPOR_D
B3OR_EIMPOR_E
B4OR_HIMPOR_H
B5OR_LIMPOR_L
B6OR_HL_REFIMPOR_HL_REF
F6OR_IMMIMM8OR_IMM #1
AFXOR_AIMPXOR_A
A8XOR_BIMPXOR_B
A9XOR_CIMPXOR_C
AAXOR_DIMPXOR_D
ABXOR_EIMPXOR_E
ACXOR_HIMPXOR_H
ADXOR_LIMPXOR_L
AEXOR_HL_REFIMPXOR_HL_REF
EEXOR_IMMIMM8XOR_IMM #1
BFCP_AIMPCP_A
B8CP_BIMPCP_B
B9CP_CIMPCP_C
BACP_DIMPCP_D
BBCP_EIMPCP_E
BCCP_HIMPCP_H
BDCP_LIMPCP_L
BECP_HL_REFIMPCP_HL_REF
FECP_IMMIMM8CP_IMM #1
03INC_BCIMPINC_BC
0BDEC_BCIMPDEC_BC
13INC_DEIMPINC_DE
1BDEC_DEIMPDEC_DE
23INC_HLIMPINC_HL
2BDEC_HLIMPDEC_HL
33INC_SPIMPINC_SP
3BDEC_SPIMPDEC_SP
09ADD_HL_BCIMPADD_HL_BC
19ADD_HL_DEIMPADD_HL_DE
29ADD_HL_HLIMPADD_HL_HL
39ADD_HL_SPIMPADD_HL_SP
07RLCAIMPRLCA
17RLAIMPRLA
0FRRCAIMPRRCA
1FRRAIMPRRA
CBPREFIX_CBIMPPREFIX_CB
2FCPLIMPCPL
3FCCFIMPCCF
37SCFIMPSCF
27DAAIMPDAA