KITAQ SERIES • REFERENCE EDITION
KITAQGB
Game Boy C programming
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.
| OS | kitaqgb |
|---|---|
| Windows x64 | kitaqgb.exe |
| Linux x64 (musl) | bin/linux-x86_64/kitaqgb |
| Linux x64 (GNU) | bin/linux-gnu-x86_64/kitaqgb |
| macOS ARM64 | bin/macos-arm64/kitaqgb |
| macOS Intel | bin/macos-x86_64/kitaqgb |
| macOS Universal | bin/macos-universal/kitaqgb |
kitaqgb · 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.
kitaqgb game.c -I lib -o game.gb --cart=romonly --romsize=32k1. 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.
cd kitaqgb
cargo test --locked --tests
cargo build --locked --releaseWindows 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.
| OS | kitaqgb |
|---|---|
| Windows x64 | kitaqgb.exe |
| Linux x64 (musl) | bin/linux-x86_64/kitaqgb |
| Linux x64 (GNU) | bin/linux-gnu-x86_64/kitaqgb |
| macOS ARM64 | bin/macos-arm64/kitaqgb |
| macOS Intel | bin/macos-x86_64/kitaqgb |
| macOS Universal | bin/macos-universal/kitaqgb |
.\scripts\build.ps1
.\kitaqgb.exe --helpsh scripts/build.shPowerShell 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.
#include "gb_common.h"
void main() {
m_init();
m_text(2, 5, "HELLO WORLD");
m_number(42);
while (1) { m_wait(); }
}.\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.jsonThe 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.
| Type | Meaning and range |
|---|---|
u8 | Unsigned 8-bit integer, 0 to 255 |
s8 | Signed 8-bit integer, -128 to 127 |
u16 | Unsigned 16-bit integer, 0 to 65535 |
s16 | Signed 16-bit integer, -32768 to 32767 |
void | No 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.
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
| Group | Operators | Example 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 forms | x += 1 updates a value |
| Increment | ++ -- | i++ increments by one |
| Selection | condition ? A : B | Choose a value according to a condition |
| Pointers | &variable / *p | Obtain 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
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
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.
.\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.
#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.
.\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.
.\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.
.\kitaqgb\kitaqgb.exe kqhelp KQ1002
.\kitaqgb\kitaqgb.exe symfind main --map=.\out\game.map
.\kitaqgb\kitaqgb.exe romdiff .\out\before.gb .\out\after.gb13. 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.
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.
# Game development with KITAQGB, KOKURA and SARAKURA
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.
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
- 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
--helpoutput and APIs against source. - 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.
- Ask only about material ambiguities. Make ordinary reversible implementation decisions autonomously. Do not weaken requirements or acceptance criteria.
- 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.cimplementation 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.cfont 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.
$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.
--framesspecifies analysis conditions; SARAKURA does not execute the ROM or automatically edit the source. - Read
report.html,ai_diagnostics.json,repair_prompt.mdandretest_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.
& '.\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 --enforceUse 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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_font.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_font.gb --profile=dev --rst-disable --stack-bank=fixed// Display the letters, digits and punctuation from the supplied ascii.c font.
// Expected: Uppercase, lowercase, numbers and all 30 supplied punctuation glyphs
#include "gb_common.h"
// Lay out every supplied character category, yielding between rows. The final
// m_put uses character code 34 to display the double quote separately.
void main() {
m_init();
m_text(2,3,"FONT");
m_text(2,5,"ABCDEFGHIJKLMNOP"); m_wait();
m_text(2,6,"QRSTUVWXYZ"); m_wait();
m_text(2,8,"0123456789"); m_wait();
m_text(2,10,"abcdefghijklmnop"); m_wait();
m_text(2,11,"qrstuvwxyz"); m_wait();
m_text(2,13,"!#$%&'()*+,-./"); m_wait();
m_text(2,14,":;<=>?@[]^_`{}~"); m_wait();
m_put(16,13,34); m_wait();
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_hello.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_hello.gb --profile=dev --rst-disable --stack-bank=fixed// Display a first message on screen.
// Expected: HELLO WORLD and 042
#include "gb_common.h"
// Initialize the display/font, write HELLO WORLD and a three-digit 042,
// then wait for frames so the result remains visible.
void main() {
m_init();
m_text(2,3,"HELLO");
m_text(2,5,"HELLO WORLD");
m_number(42);
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_arithmetic.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_arithmetic.gb --profile=dev --rst-disable --stack-bank=fixed// Variables, arithmetic and type conversion.
// Expected: 030
#include "gb_common.h"
// Widen before adding 200 + 100, divide the 16-bit sum, and display 30.
// The cast demonstrates avoiding an 8-bit intermediate result.
void main() {
m_init();
m_text(2,3,"ARITHMETIC");
u16 sum;
sum = (u16)200 + 100;
m_number((u8)(sum / 10));
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_control.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_control.gb --profile=dev --rst-disable --stack-bank=fixed// Conditional branches and loops.
// Expected: 042
#include "gb_common.h"
// Exercise loop control and a final branch, then display the selected value 42.
void main() {
m_init();
m_text(2,3,"CONTROL");
u8 i; u8 sum; sum=0;
for (i=0;i<8;i++) { if (i==3) continue; sum=(u8)(sum+i); }
while (sum<30) { sum++; }
do { sum++; } while (sum<32);
switch (sum) { case 32: sum=42; break; default: sum=0; break; }
m_number(sum);
while (1) { m_wait(); }
}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.](../verification/gb_aggregate.png)
Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_aggregate.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_aggregate.gb --profile=dev --rst-disable --stack-bank=fixed// Functions, arrays, structures and pointers.
// Expected: 042
#include "gb_common.h"
typedef struct { u8 x; u8 y; } Point;
// Add two byte values and explicitly narrow the returned result to u8.
u8 add(u8 a,u8 b) { return (u8)(a+b); }
// Copy a structure, fill an array through a pointer, and combine selected values
// through add() to produce 42. Keep the final screen visible in the idle loop.
void main() {
m_init();
m_text(2,3,"AGGREGATE");
Point a; Point b; u8 i; u8 data[4]; u8* ptr;
a.x=10; a.y=20; b=a; ptr=data;
for(i=0;i<4;i++) ptr[i]=(u8)(i+1);
m_number((u8)(add(b.x,b.y)+ptr[0]+ptr[1]+ptr[3]+5));
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaq-docs/samples/gb_bits_memory.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_bits_memory.gb --profile=dev --rst-disable --stack-bank=fixed// Bit operations and memory copying.
// Expected: 010
#include "gb_common.h"
void __memset(void* dst,u8 value,u16 len);
void __memcpy(void* dst,const void* src,u16 len);
void __bit_set(u8* base,u16 bit);
void __bit_toggle(u8* base,u16 bit);
// Clear the source bytes, set bits 3 and 1, copy the buffer, and display 8 + 2 = 10.
void main() {
m_init();
m_text(2,3,"BITS MEMORY");
u8 data[4]; u8 copy[4];
__memset(data,0,4); __bit_set(data,3); __bit_toggle(data,1);
__memcpy(copy,data,4); m_number(copy[0]);
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/fixed.c kitaq-docs/samples/gb_fixed.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_fixed.gb --profile=dev --rst-disable --stack-bank=fixed// Calculate with signed Q8.8 fixed-point values.
// Expected: 012
#include "gb_common.h"
#include "fixed.h"
// Convert 3 and 4 to Q8.8, multiply them, convert the result back to an
// integer, and display 12.
void main() {
m_init();
m_text(2,3,"FIXED");
fix8 a; fix8 b; a=fix_from_int(3); b=fix_from_int(4);
m_number((u8)fix_to_int(fix_mul(a,b)));
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/entity.c kitaq-docs/samples/gb_entity.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_entity.gb --profile=dev --rst-disable --stack-bank=fixed// Allocate an entity from a fixed-capacity object pool.
// Expected: 001
#include "gb_common.h"
#include "entity.h"
// Create one entity and check the 0xFF allocation-failure sentinel before
// accessing its velocity. Display the pool occupancy, not the entity ID.
void main() {
m_init();
m_text(2,3,"ENTITY");
u8 id; entity_init(); id=entity_create(1,20,30);
if(id!=0xFF) entity_get(id)->vx=2; m_number(entity_count_active());
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/debug.c kitaq-docs/samples/gb_debug.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_debug.gb --profile=dev --rst-disable --stack-bank=fixed// Record diagnostic values in the debug trace.
// Expected: 001; trace log contains HP=42
#include "gb_common.h"
#include "debug.h"
// Record HP = 42 with frame tag 1 and display the number of trace entries.
// The trace buffer contains the value; the screen displays the count, not HP.
void main() {
m_init();
m_text(2,3,"DEBUG");
debug_init(); debug_set_frame(1); debug_trace_u8("HP",42);
m_number(debug_get_trace_count());
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/system.c kitaq-docs/samples/gb_system.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_system.gb --profile=dev --rst-disable --stack-bank=fixed// Count completed frame waits.
// Expected: 002
#include "gb_common.h"
#include "system.h"
// Reset the library frame count, perform two counted waits, and display 2.
// Subsequent m_wait calls leave that library counter unchanged.
void main() {
m_init();
m_text(2,3,"SYSTEM");
system_init(); system_wait_vblank(); system_wait_vblank();
m_number(system_get_frame8());
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/scroll.c kitaqgb/lib/camera.c kitaq-docs/samples/gb_camera.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_camera.gb --profile=dev --rst-disable --stack-bank=fixed// Convert world coordinates to screen coordinates.
// Expected: 042: world x=52 minus camera x=10
#include "gb_common.h"
#include "camera.h"
Camera8_8 cam;
// Set the camera X coordinate to 10 pixels in Q8.8 (10 * 256), then subtract
// it from world X = 52 to display screen X = 42.
void main() {
m_init();
m_text(2,3,"CAMERA");
Camera_Init(&cam); Camera_Set(&cam,2560,0);
m_number((u8)Camera_WorldToScreenX(&cam,52));
while (1) { m_wait(); }
}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.](../verification/gb_rle.png)
Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/rle.c kitaq-docs/samples/gb_rle.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_rle.gb --profile=dev --rst-disable --stack-bank=fixed// Decode run-length encoded data.
// Expected: 042; three bytes each equal to 42
#include "gb_common.h"
#include "rpg.h"
__prg_rom u8 packed[] = {3,42,0};
// Decode count/value pair (3,42), terminated by a zero count, into a
// three-byte buffer and display the final decoded byte.
void main() {
m_init();
m_text(2,3,"RLE");
u8 out[3]; rle_decode(out,packed); m_number(out[2]);
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/fixed.c kitaqgb/lib/physics2d.c kitaq-docs/samples/gb_physics.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_physics.gb --profile=dev --rst-disable --stack-bank=fixed// Advance a rectangular body through one physics step.
// Expected: 042: integer pixel coordinates
#include "gb_common.h"
#include "physics2d.h"
KQWorld2D world; KQBody2D boxes[1];
// Create one box at X = 40, set horizontal velocity 2 and zero gravity,
// then display its X coordinate after a single physics step.
void main() {
m_init();
m_text(2,3,"PHYSICS");
kq2d_world_init(&world,boxes,1);
kq2d_body_init(&boxes[0],40,40,4,4);
kq2d_body_set_velocity(&boxes[0],2,0);
world.gravity_x=0; world.gravity_y=0;
kq2d_step(&world); m_number((u8)boxes[0].x);
while (1) { m_wait(); }
}Handle serial completion and timeout gb_link.c
Purpose: Distinguish a completed transfer from one that does not finish within the wait limit.
How it works: The master sends 42 and waits with Link_WaitByte. Completion displays the received byte; timeout cancels the transfer and displays zero.
Controls and expected result: An unconnected transfer that completes can return 255. A timeout displays 000; with a connected peer, the display shows its received byte. The result is not always 042.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/link_hwregs_gb.c kitaqgb/lib/link.c kitaq-docs/samples/gb_link.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_link.gb --profile=dev --rst-disable --stack-bank=fixed// Handle serial transfers that do not deliver a byte in time.
// Expected: 255 for disconnected link; 000 if timeout; peer value when connected
#include "gb_common.h"
#include "link.h"
// Start a master transfer of byte 42 and wait with a bounded timeout.
// Display the received byte on completion; cancel and display zero on timeout.
void main() {
m_init();
m_text(2,3,"LINK");
u8 value; Link_InitMaster(); Link_BeginTransfer(42);
if(Link_WaitByte(4,&value)) m_number(value);
else { Link_Cancel(); m_number(0); }
while (1) { m_wait(); }
}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.

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

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/danmaku.c kitaq-docs/samples/gb_danmaku.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_danmaku.gb --profile=dev --rst-disable --stack-bank=fixed// Allocate a bullet and inspect the pool count.
// Expected: 001; numeric pool example, no bullet BG compositor
#include "gb_common.h"
#include "danmaku.h"
// Reset the bullet pool, spawn one bullet and display the active count.
// This numeric example does not install the background bullet compositor.
void main() {
m_init();
m_text(2,3,"DANMAKU");
danmaku_reset(); danmaku_spawn(40,40,16,0);
m_number(dm_count);
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/cgb_palette.c kitaq-docs/samples/gb_cgb_palette.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_cgb_palette.gb --profile=dev --rst-disable --stack-bank=fixed --cgb=cgb// Set Game Boy Color palette entries.
// Expected: 042 in purple on white; CGB mode
#include "gb_common.h"
#include "cgb_palette.h"
// Set palette-zero background colors to white and purple, then display 42.
// Run the ROM in CGB mode to observe the color result.
void main() {
m_init();
m_text(2,3,"CGB PALETTE");
// Disable the LCD during VBlank so both palette bytes remain accessible.
m_wait(); M_LCDC = 0;
cgb_bg_rgb(0,0,31,31,31); cgb_bg_rgb(0,3,12,0,22);
M_LCDC = 0x91;
m_number(42);
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/vram.c kitaq-docs/samples/gb_vram_queue.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_vram_queue.gb --profile=dev --rst-disable --stack-bank=fixed// Queue a VRAM update and flush it at VBlank.
// Expected: 4 at (3,8)
#include "gb_common.h"
#include "vram.h"
// Queue font tile 52 (the digit 4) at tile coordinate (3,8), then wait for
// VBlank and execute the queued transfer.
void main() {
m_init();
m_text(2,3,"VRAM QUEUE");
vram_init();
vram_queue_bg_tile(3,8,52); vram_flush();
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/sprite.c kitaq-docs/samples/gb_sprite.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_sprite.gb --profile=dev --rst-disable --stack-bank=fixed// Display a sprite through the OAM DMA path.
// Expected: letter A at sprite position
#include "gb_common.h"
#include "sprite.h"
// Allocate the first sprite, choose font tile 65 (A), set its position and
// enable sprite display. Flush shadow OAM after each frame wait.
void main() {
m_init();
m_text(2,3,"SPRITE");
// The freshly reset pool guarantees slot zero for this first allocation.
// In a running game, check the allocation-failure sentinel before using the returned ID.
sprite_init(); sprite_set_tile(sprite_alloc(),65);
sprite_set_pos(0,70,80); M_LCDC=(u8)(M_LCDC|2);
while (1) { m_wait(); sprite_flush_oam_now(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/rng.c kitaq-docs/samples/gb_rng.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_rng.gb --profile=dev --rst-disable --stack-bank=fixed// Generate a repeatable pseudorandom value from a seed.
// Expected: value 000 through 009; same seed repeats
#include "gb_common.h"
#include "rpg.h"
// Seed the generator with 1234 and display one value in [0,10).
// Restarting with the same seed reproduces the sequence.
void main() {
m_init();
m_text(2,3,"RNG");
rng_seed(1234); m_number(rng_range(10));
while (1) { m_wait(); }
}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.

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/flags.c kitaq-docs/samples/gb_flags.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_flags.gb --profile=dev --rst-disable --stack-bank=fixed// Read and write RAM-backed game-state flags.
// Expected: 001
#include "gb_common.h"
#include "rpg.h"
// Clear and set flag 12, then display its Boolean value. This does not write
// a save file or demonstrate persistence across power cycles.
void main() {
m_init();
m_text(2,3,"FLAGS");
flag_clear(12); flag_set(12); m_number(flag_get(12));
while (1) { m_wait(); }
}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.

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

Download source / Verification status
.\kitaqgb\kitaqgb.exe kitaqgb/lib/audio_hwregs_gb.c kitaqgb/lib/audio.c kitaq-docs/samples/gb_sound.c -I kitaqgb/lib -I kitaq-docs/samples -o out/gb_sound.gb --profile=dev --rst-disable --stack-bank=fixed// Play a Game Boy pulse-channel sound effect.
// Expected: 042 and a short pulse tone
#include "gb_common.h"
#include "audio.h"
__prg_rom u8 tone[] = { 24,0xF0,24,0xE0,24,0xC0,24,0xA0,24,0x80,24,0x60,0 };
// Start a pulse-channel effect and service Audio_Update once per frame
// so the effect stream advances while 42 remains on screen.
void main() {
m_init();
m_text(2,3,"SOUND");
Audio_Init(); Audio_PlaySFX(tone,1); m_number(42);
while (1) { m_wait(); Audio_Update(); }
}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
void __farmemcpy(void* dst, u8 bank, const void* src, u16 len);Parameters
dstDestination in writable memory. Reserve enough space for every byte copied. Except for a zero-byte transfer, a null or invalid destination is not checked.
bank8-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.
srcNear pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.
lenByte 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
__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.


Build command
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-disasmImplementation 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
void __vram_copy(u16 dst, const void* src, u16 len);Parameters
dstFirst 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.srcSource 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.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
void __farcall_ptr(u8 bank, const void* func);Parameters
bankAn 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.
funcA 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/bank.h:15
kitaqgb/kitaqgb/CodeGenerator.cs:8335
if (funcName == "__farcall_ptr")
{
if (args.Length != 2) Error(expr, "__farcall_ptr(bank, func) expects 2 arguments");
CType callbackType = TypeOf(args[1]);
if (callbackType.IsPointer) callbackType = callbackType.Subtype;
if (callbackType != null && callbackType.IsFunction &&
(callbackType.ParamTypes ?? Array.Empty<CType>()).Length != 0)
Error(expr, "__farcall_ptr requires a callback with no parameters");
CompileIntoA(args[0]);
EmitAsm("PUSH_AF");
CompileIntoHL(args[1]);
EmitAsm("POP_AF");
UseFarCallPointerBank0Helper = true;
EmitAsm("CALL", new AsmOperand("__kq_farcall_pointer_bank0", AddressMode.Absolute));
RecordCallEdge(expr, "<indirect-far>", -1, "farcall_pointer", viaThunk: true,
viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
return;
}__farpeek16
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
u16 __farpeek16(u8 bank, const void* addr);Parameters
bankAn 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.
addrNear 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/bank.h:13
kitaqgb/kitaqgb/CodeGenerator.cs:8295
if (funcName == "__farpeek16")
{
if (args.Length != 2) Error(expr, "__farpeek16(bank, addr) expects 2 arguments");
CompileIntoA(args[0]); EmitPushAAsWord(); // bank
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
EmitAsm("ADD_SP_IMM", new AsmOperand(-2, AddressMode.Relative)); // scratch
// At SP: scratch, saved address, saved bank; each occupies two bytes.
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("PUSH_HL"); // dst = &scratch
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL"); // bank
EmitAsm("POP_HL"); // dst
EmitAsm("LD_BC_IMM", new AsmOperand(2, AddressMode.Immediate16));
EmitCallFarMemcpyHelper();
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
LastCallReturnsHL = true;
return;
}__farpeek8
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
u8 __farpeek8(u8 bank, const void* addr);Parameters
bankAn 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.
addrNear 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/bank.h:12
kitaqgb/kitaqgb/CodeGenerator.cs:8262
if (funcName == "__farpeek8")
{
if (args.Length != 2) Error(expr, "__farpeek8(bank, addr) expects 2 arguments");
CompileIntoA(args[0]); EmitPushAAsWord(); // bank
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
EmitAsm("ADD_SP_IMM", new AsmOperand(-2, AddressMode.Relative)); // scratch
// At SP: scratch, saved address, saved bank; each occupies two bytes.
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("PUSH_HL"); // dst = &scratch
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL"); // bank
EmitAsm("POP_HL"); // dst
EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
EmitCallFarMemcpyHelper();
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
return;
}cgb_palette.h — CGB 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
u8 __cgb_is_cgb();Return value
u8: 0 for DMG mode, 1 for CGB mode.
Usage example
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.




Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_palette.h:8
Safe here means CGB hardware gating. Palette data writes still require an accessible PPU phase (for example LCD off or a sufficiently short VBlank update).
__cgb_safe_set_bcpd
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
void __cgb_safe_set_bcpd(u8 value);Parameters
valueOne 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
__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.




Build command
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-disasmImplementation 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
void __cgb_safe_set_bcps(u8 value);Parameters
valueByte 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
__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.




Build command
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-disasmImplementation 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
void __cgb_safe_set_ocpd(u8 value);Parameters
valueOne 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
__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.




Build command
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-disasmImplementation 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
void __cgb_safe_set_ocps(u8 value);Parameters
valueByte 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
__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.




Build command
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-disasmImplementation 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
void __cgb_safe_set_vbk(u8 value);Parameters
valueSelect the CPU's VRAM bank through VBK (
$FF4F) on CGB. Onlyvalue & 1is 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
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.




Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:28
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
void __settile(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:31
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
void __settile_bulk(u16 dest, const u8* src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:48
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
void __settile_bulk_fast(u16 dest, const u8* src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:50
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
void __settile_unsafe(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:33
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
void __settileat(u16 base, u8 x, u8 y, u8 tile);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:35
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
void __settileat_unsafe(u16 base, u8 x, u8 y, u8 tile);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:37
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
void __settileatattr(u16 base, u8 x, u8 y, u8 attr);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:62
Write attributes in bank one at the explicit map base on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settileatattr_unsafe
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
void __settileatattr_unsafe(u16 base, u8 x, u8 y, u8 attr);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:64
Write attributes in bank one at the explicit map base on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settileatcgb
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
void __settileatcgb(u16 base, u8 x, u8 y, u8 tile, u8 attr);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:66
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. Use the access-checked write path.
__settileatcgb_unsafe
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
void __settileatcgb_unsafe(u16 base, u8 x, u8 y, u8 tile, u8 attr);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:68
Write tile and CGB attributes at the explicit map base; DMG receives only the tile write. No VRAM access waits are inserted.
__settileattr
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
void __settileattr(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:53
Write attributes in bank one at map 9800 on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settileattr_bulk
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
void __settileattr_bulk(u16 dest, const u8* src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:89
Copy count attributes to dest in bank one on CGB, then leave VBK zero; skip on DMG. Transfers use access checks.
__settileattr_bulk_fast
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
void __settileattr_bulk_fast(u16 dest, const u8* src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:91
Copy count attributes to dest in bank one on CGB, then leave VBK zero; skip on DMG. The caller supplies writable VRAM timing.
__settileattr_unsafe
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
void __settileattr_unsafe(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:55
Write attributes in bank one at map 9800 on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilebg
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
void __settilebg(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:43
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
void __settilebg_unsafe(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:45
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
void __settilebgattr(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:80
Write attributes in bank one at the LCDC background map on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settilebgattr_unsafe
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
void __settilebgattr_unsafe(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:82
Write attributes in bank one at the LCDC background map on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilebgcgb
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
void __settilebgcgb(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:84
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. Use the access-checked write path.
__settilebgcgb_unsafe
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
void __settilebgcgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:86
Write tile and CGB attributes at the LCDC background map; DMG receives only the tile write. No VRAM access waits are inserted.
__settilecgb
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
void __settilecgb(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:57
Write tile and CGB attributes at map 9800; DMG receives only the tile write. Use the access-checked write path.
__settilecgb_bulk
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
void __settilecgb_bulk(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.tile_src, attr_srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:93
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. Transfers use access checks.
__settilecgb_bulk_fast
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
void __settilecgb_bulk_fast(u16 dest, const u8* tile_src, const u8* attr_src, u8 count);Parameters
destFirst VRAM byte address. For a map, use an address in
0x9800–0x9FFFand keep the full transfer within the intended map.tile_src, attr_srcPacked 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.
countNumber 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
// __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.
ABC

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:95
Copy tile and attribute streams to dest; DMG receives only tiles. Keep both sources readable. The caller supplies writable VRAM timing.
__settilecgb_unsafe
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
void __settilecgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:59
Write tile and CGB attributes at map 9800; DMG receives only the tile write. No VRAM access waits are inserted.
__settilewin
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
void __settilewin(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:39
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
void __settilewin_unsafe(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:41
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
void __settilewinattr(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:71
Write attributes in bank one at the LCDC window map on CGB, then leave VBK zero; skip on DMG. Use the access-checked write path.
__settilewinattr_unsafe
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
void __settilewinattr_unsafe(u8 x, u8 y, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:73
Write attributes in bank one at the LCDC window map on CGB, then leave VBK zero; skip on DMG. No VRAM access waits are inserted.
__settilewincgb
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
void __settilewincgb(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:75
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. Use the access-checked write path.
__settilewincgb_unsafe
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
void __settilewincgb_unsafe(u8 x, u8 y, u8 tile, u8 attr);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/lib/cgb_tile.h:77
Write tile and CGB attributes at the LCDC window map; DMG receives only the tile write. No VRAM access waits are inserted.
input.h — 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
u16 __readpadex(u8 prev_keys);Parameters
prev_keysPrevious 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
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.


Build command
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-cacheImplementation 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
u8 bank = __bankof(symbol);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
symbolThe 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
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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:432
kitaqgb/kitaqgb/CodeGenerator.cs:7360
if (funcName == "__bankof")
{
if (args.Length != 1) Program.Error("__bankof(symbol) expects 1 argument");
if (!args[0].Match(Tag.Name, out string symName)) Program.Error("__bankof(symbol) requires a symbol name argument");
if (TryGetKnownRomBankForSymbol(symName, out int knownBank))
{
EmitAsm("LD_A_IMM", new AsmOperand(knownBank & 0xFF, AddressMode.Immediate));
return;
}
EmitAsm("LD_A_IMM", new AsmOperand(symName, ImmediateModifier.Bank));
return;
}__bankswitch
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
void __bankswitch(u8 bank);Parameters
bankAn 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/bank.h:11
kitaqgb/kitaqgb/CodeGenerator.cs:7268
if (funcName == "__bankswitch")
{
if (args.Length != 1) Program.Error("__bankswitch(bank) expects 1 argument");
CompileIntoA(args[0]);
if (CurrentFunctionBank == 0)
{
EmitAsm("LD_MEM_A", new AsmOperand(0x2000, AddressMode.Absolute));
EmitStoreA(RomBankVar);
}
else
{
UseBankSwitchBank0Helper = true;
EmitAsm("CALL", new AsmOperand("__kq_bankswitch_bank0", AddressMode.Absolute));
}
return;
}__cgb_safe_set_bgpd
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
__cgb_safe_set_bgpd(/* 書式は実装と呼び出し例を参照 */);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
valueOne 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
__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.




Build command
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-disasmImplementation 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
__cgb_safe_set_bgpi(/* 書式は実装と呼び出し例を参照 */);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
valueByte 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
__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.




Build command
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-disasmImplementation 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
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
valueRegister 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
__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.



Build command
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-disasmImplementation 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
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
valueRegister 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
__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.



Build command
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-disasmImplementation 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
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
valueRegister 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
__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.



Build command
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-disasmImplementation 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
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
valueRegister 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
__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.



Build command
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-disasmImplementation 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
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
valueRegister 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
__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.



Build command
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-disasmImplementation 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
__cgb_safe_set_obpd(/* 書式は実装と呼び出し例を参照 */);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
valueOne 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
__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.




Build command
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-disasmImplementation 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
__cgb_safe_set_obpi(/* 書式は実装と呼び出し例を参照 */);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
valueByte 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
__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.




Build command
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-disasmImplementation 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
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
valueRegister 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
__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.

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:1862
__critical_enter
Increment software nesting depth and execute DI.
Syntax
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
*((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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7970
kitaqgb/kitaqgb/CodeGenerator.cs:7970
if (funcName == "__critical_enter")
{
if (args.Length != 0) Error(expr, "__critical_enter() expects 0 arguments");
EmitLoadA(CriticalDepthVar);
EmitAsm("LD_B_A"); // token = old depth
EmitAsm("INC_A");
EmitStoreA(CriticalDepthVar);
EmitAsm("DI");
EmitAsm("LD_A_B");
return;
}__critical_leave
Decrease nesting depth and execute EI for outer token zero.
Syntax
void __critical_leave(u8 token);Parameters
tokenToken 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
__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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7984
kitaqgb/kitaqgb/CodeGenerator.cs:7984
if (funcName == "__critical_leave")
{
if (args.Length != 1) Error(expr, "__critical_leave(token) expects 1 argument");
CompileIntoA(args[0]);
EmitAsm("LD_B_A"); // token
AsmOperand clAfterDec = MakeUniqueLabel("critleave_afterdec");
AsmOperand clDone = MakeUniqueLabel("critleave_done");
EmitLoadA(CriticalDepthVar);
EmitAsm("OR_A");
EmitAsm("JR_Z", clAfterDec);
EmitAsm("DEC_A");
EmitStoreA(CriticalDepthVar);
EmitLabel(clAfterDec);
// Re-enable only for outermost token (token==0).
EmitAsm("LD_A_B");
EmitAsm("OR_A");
EmitAsm("JR_NZ", clDone);
EmitAsm("EI");
EmitLabel(clDone);
return;
}__dot2_q8_8
Compute a unsigned two-term dot product.
Syntax
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);Parameters
a0Unsigned word, 0..65535.
a1Unsigned word, 0..65535.
b0Unsigned coefficient, 0..255.
b1Unsigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11575
kitaqgb/kitaqgb/CodeGenerator.cs:11575
if (funcName == "__dot2_q8_8")
{
if (args.Length != 4) Error(expr, "__dot2_q8_8 requires 4 arguments");
LastCallReturnsHL = true;
// Fast paths when coefficients are compile-time constants.
// Special-case: 0 / power-of-two coefficients (u8) => shift instead of mul.
if (TryGetU8Const(args[2], out int ax0) && (ax0 == 0) && TryGetU8Const(args[3], out int ay0) && (ay0 == 0))
{
CompileDiscard(args[0]);
CompileDiscard(args[1]);
EmitAsm("XOR_A");
EmitAsm("LD_H_A");
EmitAsm("LD_L_A");
return;
}
if (TryGetU8Const(args[2], out int axOnly) && (axOnly == 0))
{
CompileDiscard(args[0]);
// only y*ay
if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
{
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[3]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitMul16x8Shr8_HL("__dot2_y");
}
return;
}
if (TryGetU8Const(args[3], out int ayOnly) && (ayOnly == 0))
{
// only x*ax
if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitMul16x8Shr8_HL("__dot2_x");
}
// Preserve the scaled result while evaluating the zero-product input.
EmitAsm("PUSH_HL");
CompileDiscard(args[1]);
EmitAsm("POP_HL");
return;
}
// x*ax
if (!TryEmitDot2Pow2Term(args[0], args[2], signed: false, q1_7: false, out _))
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitMul16x8Shr8_HL("__dot2_x");
}
EmitAsm("PUSH_HL");
// y*ay
if (!TryEmitDot2Pow2Term(args[1], args[3], signed: false, q1_7: false, out _))
{
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[3]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitMul16x8Shr8_HL("__dot2_y");
}
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__dot3_q8_8
Compute a unsigned three-term dot product.
Syntax
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameters
a0Unsigned word, 0..65535.
a1Unsigned word, 0..65535.
a2Unsigned word, 0..65535.
b0Unsigned coefficient, 0..255.
b1Unsigned coefficient, 0..255.
b2Unsigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11550
kitaqgb/kitaqgb/CodeGenerator.cs:11550
if (funcName == "__dot3_q8_8")
{
if (args.Length != 6) Error(expr, "__dot3_q8_8 requires 6 arguments");
LastCallReturnsHL = true;
// x*ax
EmitDotTermToHL(args[0], args[3], signed: false, q1_7: false, labelPrefix: "__dot3_x");
EmitAsm("PUSH_HL");
// y*ay
EmitDotTermToHL(args[1], args[4], signed: false, q1_7: false, labelPrefix: "__dot3_y");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
EmitAsm("PUSH_HL");
// z*az
EmitDotTermToHL(args[2], args[5], signed: false, q1_7: false, labelPrefix: "__dot3_z");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__farcall
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
__farcall(bank, function_name);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
bankAn 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.
funcThe 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7288
kitaqgb/kitaqgb/CodeGenerator.cs:7288
if (funcName == "__farcall")
{
if (args.Length != 2) Program.Error("__farcall(bank, func) expects 2 arguments");
if (!args[1].Match(Tag.Name, out string targetName)) Program.Error("__farcall(bank, func) requires 2nd argument to be a function name");
// A named far call supplies no arguments. Reject data symbols
// and callbacks that would otherwise read stale argument storage.
CFunctionInfo targetInfo;
if (!Functions.TryGetValue(targetName, out targetInfo))
{
Error(expr, "__farcall requires a declared function name");
return;
}
if ((targetInfo.Parameters ?? Array.Empty<FieldInfo>()).Length != 0)
{
Error(expr, "__farcall requires a callback with no parameters");
return;
}
Expr bankExpr = FoldConstants(args[0]);
int targetBank = -1;
if (targetInfo != null)
{
targetBank = targetInfo.RomBank;
PrepareStructReturnDestination(expr, targetInfo.ReturnType, targetInfo, targetName);
if (targetInfo.ReturnType != null)
LastCallReturnsHL = ReturnsInHL(expr, targetInfo.ReturnType);
}
// A known bank permits direct dispatch when safe, otherwise request a fixed-bank thunk.
if (TryResolveBankExprToConstU8(args[0], out int bankLiteral))
{
int optimizedBank = bankLiteral & 0xFF;
bool farcallDirectOK = IsDirectCallBankSafe(optimizedBank, CurrentFunctionBank);
if (!farcallDirectOK)
{
string farcallThunkName = EnsureBank0Thunk(
targetName,
optimizedBank,
isStackCall: targetInfo != null && targetInfo.IsStackCall,
stackArgBytes: 0);
EmitAsm("CALL", new AsmOperand(farcallThunkName, AddressMode.Absolute));
RecordCallEdge(expr, targetName, optimizedBank, "farcall_bank_thunk", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
}
else
{
if (Program.CheckBankCalls && !InUnsafe && optimizedBank != 0)
{
AsmOperand ok = MakeUniqueLabel("kq_bank_ok");
EmitLoadA(RomBankVar);
EmitAsm("CP_IMM", new AsmOperand(optimizedBank & 0xFF, AddressMode.Immediate));
EmitAsm("JP_Z", ok);
EmitAsm("JP", EnsureCheckTrap());
EmitLabel(ok);
}
EmitAsm("CALL", new AsmOperand(targetName, AddressMode.Absolute));
RecordCallEdge(expr, targetName, optimizedBank, "farcall_direct", viaThunk: false, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
}
return;
}
// A = requested bank argument (runtime value)
CompileIntoA(bankExpr);
// Always dispatch via bank0 helper thunk.
string thunkName = EnsureBank0RuntimeFarcallThunk(targetName);
EmitAsm("CALL", new AsmOperand(thunkName, AddressMode.Absolute));
RecordCallEdge(expr, targetName, targetBank, "farcall_intrinsic", viaThunk: true, viaFarcall: true, actualArgSizes: new int[0], expectedArgSizes: new int[0]);
return;
}__getwinmapbase
Read LCDC bit 6 to select the window tile-map base.
Syntax
u16 __getwinmapbase();Return value
Returns $9800 or $9C00, the tile-map base rather than the tile graphics address.
Usage example
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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10642
kitaqgb/kitaqgb/CodeGenerator.cs:10642
if (funcName == "__getwinmapbase")
{
if (args.Length != 0) Error(expr, "__getwinmapbase requires 0 arguments");
AsmOperand win_base0 = MakeUniqueLabel("getwinbase_base0");
AsmOperand win_done = MakeUniqueLabel("getwinbase_done");
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)); // bit6 (Window tile map display select)
EmitAsm("JR_Z", win_base0);
EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
EmitAsm("JR", win_done);
EmitLabel(win_base0);
EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
EmitLabel(win_done);
LastCallReturnsHL = true;
return;
}__mac16
Compute a unsigned multiply-accumulate.
Syntax
u16 __mac16(u16 acc, u16 a, u8 b);Parameters
acc16-bit accumulator to add.
aUnsigned word, 0..65535.
bUnsigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11499
kitaqgb/kitaqgb/CodeGenerator.cs:11499
if (funcName == "__mac16")
{
if (args.Length != 3) Error(expr, "__mac16 requires 3 arguments");
// mul = (a*b)>>8
EmitDotTermToHL(args[1], args[2], signed: false, q1_7: false, labelPrefix: "__mac16_mul");
// acc + mul
EmitAsm("PUSH_HL");
CompileIntoHL(args[0]);
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
LastCallReturnsHL = true;
return;
}__mul16x8
Compute a unsigned multiplication.
Syntax
u16 __mul16x8(u16 a, u8 b);Parameters
aUnsigned word, 0..65535.
bUnsigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11467
kitaqgb/kitaqgb/CodeGenerator.cs:11467
if (funcName == "__mul16x8")
{
if (args.Length != 2) Error(expr, "__mul16x8 requires 2 arguments");
EmitDotTermToHL(args[0], args[1], signed: false, q1_7: false, labelPrefix: "__mul16x8");
LastCallReturnsHL = true;
return;
}__mul8x8_hi
Multiply two unsigned bytes and return the high byte.
Syntax
u8 __mul8x8_hi(u8 a, u8 b);Parameters
aUnsigned byte, 0..255.
bUnsigned byte, 0..255.
Return value
floor(a*b/256), in 0..254.
Usage example
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11452
kitaqgb/kitaqgb/CodeGenerator.cs:11452
if (funcName == "__mul8x8_hi")
{
if (args.Length != 2) Error(expr, "__mul8x8_hi requires 2 arguments");
CompileIntoA(args[0]);
EmitAsm("PUSH_AF");
CompileIntoA(args[1]);
EmitAsm("LD_C_A");
EmitAsm("POP_AF");
EmitAsm("LD_E_A");
EmitMul8x8ToHL_EC("__mul8x8_hi");
EmitAsm("LD_A_H");
return;
}__padrep
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
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
statePointer 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.keysCurrent full held-button mask in the GB
BTN_*layout. Supply a snapshot from__readpad, the low byte of__readpadex, orinput_current; the scheduler does not poll hardware itself.triggerNewly pressed bits for this update in the same layout as
keys, normallykeys & ~previous_keysor the high byte of__readpadex. Only include bits also present inkeys. Passing the held mask every time falsely restarts the initial press on every call.maskEligible 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
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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11249
{
if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");
AsmOperand pm_done = MakeUniqueLabel("padrepM_done");
AsmOperand pm_ret0 = MakeUniqueLabel("padrepM_ret0");
AsmOperand pm_no_trig = MakeUniqueLabel("padrepM_notrig");
AsmOperand pm_reset_all = MakeUniqueLabel("padrepM_reset");
AsmOperand pm_dir0 = MakeUniqueLabel("padrepM_dir0");
AsmOperand pm_have_dir = MakeUniqueLabel("padrepM_havedir");
AsmOperand pm_first_rep = MakeUniqueLabel("padrepM_firstrep");
AsmOperand pm_ret_dir = MakeUniqueLabel("padrepM_retdir");
AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");
// Evaluate args safely:
// HL = state*, B = keys, C = trigger, E = mask
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
EmitAsm("POP_HL");
// trigMasked = trigger & mask -> D
EmitAsm("LD_A_C");
EmitAsm("AND_E");
EmitAsm("LD_D_A");
EmitAsm("OR_A");
EmitAsm("JP_Z", pm_no_trig);
// select = lowbit(trigMasked) = x & -x
EmitAsm("LD_A_D");
EmitAsm("CPL");
EmitAsm("INC_A");
EmitAsm("AND_D");
EmitAsm("LD_C_A"); // save selected in C
// dirMask=selected, dasCnt=0, arrCnt=0
EmitAsm("LD_A_C");
EmitAsm("LD_HL_A"); // [0]=selected
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); Emi__padrep_down
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
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
statePointer 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.keysCurrent full held-button mask in the GB
BTN_*layout. Supply a snapshot from__readpad, the low byte of__readpadex, orinput_current; the scheduler does not poll hardware itself.triggerNewly pressed bits for this update in the same layout as
keys, normallykeys & ~previous_keysor the high byte of__readpadex. Only include bits also present inkeys. 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
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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11069
{
if (args.Length != 3) Error(expr, "__padrep_down requires 3 arguments (state*, keys, trigger)");
AsmOperand pd_done = MakeUniqueLabel("padrepD_done");
AsmOperand pd_ret0 = MakeUniqueLabel("padrepD_ret0");
AsmOperand pd_no_trig = MakeUniqueLabel("padrepD_notrig");
AsmOperand pd_dir0 = MakeUniqueLabel("padrepD_dir0");
AsmOperand pd_have_dir = MakeUniqueLabel("padrepD_havedir");
AsmOperand pd_first_rep = MakeUniqueLabel("padrepD_firstrep");
AsmOperand pd_ret_dir = MakeUniqueLabel("padrepD_retdir");
AsmOperand pd_skip_dasinc = MakeUniqueLabel("padrepD_skipdasinc");
AsmOperand pd_skip_arrinc = MakeUniqueLabel("padrepD_skiparrinc");
AsmOperand pd_reset_all = MakeUniqueLabel("padrepD_reset");
// Evaluate args safely:
// HL = state*, B = keys, C = trigger
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
EmitAsm("POP_HL");
// --- Trigger Down? (bit3 = 0x08) ---
EmitAsm("LD_A_C");
EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate));
EmitAsm("JP_Z", pd_no_trig);
// dirMask=0x08, dasCnt=0, arrCnt=0
EmitAsm("LD_A_IMM", new AsmOperand(0x08, AddressMode.Immediate));
EmitAsm("LD_HL_A"); // [0]=0x08
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // dasCnt=0
EmitAsm("INC_HL");
EmitAsm("LD_HL_A"); // arrCnt=0
EmitAsm("POP_HL");
EmitAsm__padrep_init
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
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
statePointer 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.dasInitial 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.
arrInterval 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10808
{
if (args.Length != 3) Error(expr, "__padrep_init requires 3 arguments (state*, das, arr)");
// Evaluate args with register safety
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]); EmitAsm("LD_B_A"); // das
CompileIntoA(args[2]); EmitAsm("LD_C_A"); // arr
EmitAsm("POP_HL");
// dirMask = 0; dasCnt=0; arrCnt=0; store params
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // [0]=0
EmitAsm("INC_HL");
EmitAsm("LD_A_B");
EmitAsm("LD_HL_A"); // [1]=das
EmitAsm("INC_HL");
EmitAsm("LD_A_C");
EmitAsm("LD_HL_A"); // [2]=arr
EmitAsm("INC_HL");
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // [3]=dasCnt
EmitAsm("INC_HL");
EmitAsm("LD_HL_A"); // [4]=arrCnt
return;
}
// 2.21 __padrep_reset(state_ptr) -> void
// Resets runtime counters for PadRepeatState while keeping das/arr params:
// dirMask=0, dasCnt=0, arrCnt=0
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__padrep_reset")
{
if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");
CompileIntoHL(args[0]);
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // [0]=0 (dirMask)
// [3]=0, [4]=0
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
EmitAsm("LD_HL_A");
EmitAsm("INC_HL"); // -> [4]
EmitAsm("LD_HL_A");
EmitAsm("POP_HL");
return;
}
// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new__padrep_lr
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
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
statePointer 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.keysCurrent full held-button mask in the GB
BTN_*layout. Supply a snapshot from__readpad, the low byte of__readpadex, orinput_current; the scheduler does not poll hardware itself.triggerNewly pressed bits for this update in the same layout as
keys, normallykeys & ~previous_keysor the high byte of__readpadex. Only include bits also present inkeys. 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
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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10864
{
if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");
AsmOperand pr_done = MakeUniqueLabel("padrep_done");
AsmOperand pr_ret0 = MakeUniqueLabel("padrep_ret0");
AsmOperand pr_check_left = MakeUniqueLabel("padrep_chkL");
AsmOperand pr_no_trig = MakeUniqueLabel("padrep_notrig");
AsmOperand pr_reset_all = MakeUniqueLabel("padrep_reset");
AsmOperand pr_dir0 = MakeUniqueLabel("padrep_dir0");
AsmOperand pr_have_dir = MakeUniqueLabel("padrep_havedir");
AsmOperand pr_first_rep = MakeUniqueLabel("padrep_firstrep");
AsmOperand pr_ret_dir = MakeUniqueLabel("padrep_retdir");
AsmOperand pr_skip_dasinc = MakeUniqueLabel("padrep_skipdasinc");
AsmOperand pr_skip_arrinc = MakeUniqueLabel("padrep_skiparrinc");
// Evaluate args safely:
// HL = state*, B = keys, C = trigger
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
EmitAsm("POP_HL");
// --- Trigger Right? (bit0) ---
EmitAsm("LD_A_C");
EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
EmitAsm("JP_Z", pr_check_left);
// dirMask=0x01, dasCnt=0, arrCnt=0
EmitAsm("LD_A_IMM", new AsmOperand(0x01, AddressMode.Immediate));
EmitAsm("LD_HL_A"); // [0]=0x01
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // dasCnt=0
EmitAsm("INC_HL");
EmitAsm("LD_HL_A")__padrep_mask
__padrep_mask 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
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
statePointer 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.keysCurrent full held-button mask in the GB
BTN_*layout. Supply a snapshot from__readpad, the low byte of__readpadex, orinput_current; the scheduler does not poll hardware itself.triggerNewly pressed bits for this update in the same layout as
keys, normallykeys & ~previous_keysor the high byte of__readpadex. Only include bits also present inkeys. Passing the held mask every time falsely restarts the initial press on every call.maskEligible 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
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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11249
{
if (args.Length != 4) Error(expr, "__padrep requires 4 arguments (state*, keys, trigger, mask)");
AsmOperand pm_done = MakeUniqueLabel("padrepM_done");
AsmOperand pm_ret0 = MakeUniqueLabel("padrepM_ret0");
AsmOperand pm_no_trig = MakeUniqueLabel("padrepM_notrig");
AsmOperand pm_reset_all = MakeUniqueLabel("padrepM_reset");
AsmOperand pm_dir0 = MakeUniqueLabel("padrepM_dir0");
AsmOperand pm_have_dir = MakeUniqueLabel("padrepM_havedir");
AsmOperand pm_first_rep = MakeUniqueLabel("padrepM_firstrep");
AsmOperand pm_ret_dir = MakeUniqueLabel("padrepM_retdir");
AsmOperand pm_skip_dasinc = MakeUniqueLabel("padrepM_skipdasinc");
AsmOperand pm_skip_arrinc = MakeUniqueLabel("padrepM_skiparrinc");
// Evaluate args safely:
// HL = state*, B = keys, C = trigger, E = mask
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]); EmitAsm("LD_B_A"); // keys
CompileIntoA(args[2]); EmitAsm("LD_C_A"); // trigger
CompileIntoA(args[3]); EmitAsm("LD_E_A"); // mask
EmitAsm("POP_HL");
// trigMasked = trigger & mask -> D
EmitAsm("LD_A_C");
EmitAsm("AND_E");
EmitAsm("LD_D_A");
EmitAsm("OR_A");
EmitAsm("JP_Z", pm_no_trig);
// select = lowbit(trigMasked) = x & -x
EmitAsm("LD_A_D");
EmitAsm("CPL");
EmitAsm("INC_A");
EmitAsm("AND_D");
EmitAsm("LD_C_A"); // save selected in C
// dirMask=selected, dasCnt=0, arrCnt=0
EmitAsm("LD_A_C");
EmitAsm("LD_HL_A"); // [0]=selected
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); Emi__padrep_reset
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
void __padrep_reset(u8* state);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
statePointer 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10840
{
if (args.Length != 1) Error(expr, "__padrep_reset requires 1 argument (state*)");
CompileIntoHL(args[0]);
EmitAsm("XOR_A");
EmitAsm("LD_HL_A"); // [0]=0 (dirMask)
// [3]=0, [4]=0
EmitAsm("PUSH_HL");
EmitAsm("INC_HL"); EmitAsm("INC_HL"); EmitAsm("INC_HL"); // -> [3]
EmitAsm("LD_HL_A");
EmitAsm("INC_HL"); // -> [4]
EmitAsm("LD_HL_A");
EmitAsm("POP_HL");
return;
}
// __padrep_lr(state*, keys, trigger) returns Right (1), Left (2), or zero.
// New triggers take priority, with Right selected first when both trigger bits are set.
// Without a new trigger, simultaneous Left/Right holds suppress movement without resetting counters.
// A single held direction is adopted without immediate movement when no direction is active.
// Subsequent updates use the five-byte state's initial delay and repeat interval; ARR=0 repeats each update.
if (funcName == "__padrep_lr")
{
if (args.Length != 3) Error(expr, "__padrep_lr requires 3 arguments (state*, keys, trigger)");
AsmOperand pr_done = MakeUniqueLabel("padrep_done");
AsmOperand pr_ret0 = MakeUniqueLabel("padrep_ret0");
AsmOperand pr_check_left = MakeUniqueLabel("padrep_chkL");
AsmOperand pr_no_trig = MakeUniqueLabel("padrep_notrig");
AsmOperand pr_reset_all = MakeUniqueLabel("padrep_reset");
AsmOperand pr_dir0 = MakeUniqueLabel("padrep_dir0");
AsmOperand pr_have_dir = MakeUniqueLabel("padrep_havedir");
AsmOperand pr_first_rep = MakeUniqueLabel("padrep_firstrep");
AsmOperand pr_ret_dir = MakeUniqueLabel("padrep__readpad
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
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
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.


Build command
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-cacheImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10669
{
if (args.Length != 0) Error(expr, "__readpad requires 0 arguments");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
// Directions (P1=0x20)
EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1); // dummy reads for settle
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
EmitAsm("LD_C_A"); // dpad in C
// Buttons (P1=0x10)
EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
// shift into upper nibble (<<4)
EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA"); EmitAsm("RLCA");
EmitAsm("OR_C");
// Restore P1 selection (0x30) without losing return value
EmitAsm("LD_C_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LD_A_C");
return;
}
// 2.17 __readpaddir() -> u8 in A (bits0..3 only)
// Returns dpad nibble (Right,Left,Up,Down) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpaddir")
{
if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm__readpadbtn
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
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
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.


Build command
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-cacheImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10729
{
if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
EmitAsm("LD_C_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LD_A_C");
return;
}
// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadex")
{
if (args.Length != 1) Error(expr, "__readpadex requires 1 argument (prev_keys)");
// prev -> B
CompileIntoA(args[0]);
EmitAsm("LD_B_A");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
// Directions (P1=0x20)
EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
EmitAsm("LD_C_A"); // dpad in C
// Buttons (P1=0x10)
EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1)__readpaddir
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
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
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.


Build command
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-cacheImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10706
{
if (args.Length != 0) Error(expr, "__readpaddir requires 0 arguments");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
EmitAsm("LD_C_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LD_A_C");
return;
}
// 2.18 __readpadbtn() -> u8 in A (bits0..3 only)
// Returns buttons nibble (A,B,Select,Start) with 1 when pressed.
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__readpadbtn")
{
if (args.Length != 0) Error(expr, "__readpadbtn requires 0 arguments");
AsmOperand p1 = new AsmOperand(0x00, AddressMode.HighMem); // P1 / JOYP
EmitAsm("LD_A_IMM", new AsmOperand(0x10, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("LDH_A_MEM", p1);
EmitAsm("CPL");
EmitAsm("AND_IMM", new AsmOperand(0x0F, AddressMode.Immediate));
EmitAsm("LD_C_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x30, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", p1);
EmitAsm("LD_A_C");
return;
}
// 2.19 __readpadex(prev_u8) -> u16 in HL
// Returns: L = keys (same encoding as __readpad), H = trigger (newly pressed edge: keys & ~prev).
// This mirrors the common pattern:
// prev = keys; keys = __readpad(); trigger = keys & ~prev;
// Treated as a c__sdot2_q1_7
Compute a signed two-term dot product.
Syntax
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);Parameters
a0Signed 16-bit bit pattern, representing −32768..32767.
a1Signed 16-bit bit pattern, representing −32768..32767.
b0Signed byte bit pattern, coefficient −128..127.
b1Signed 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11732
kitaqgb/kitaqgb/CodeGenerator.cs:11732
if (funcName == "__sdot2_q1_7")
{
if (args.Length != 4) Error(expr, "__sdot2_q1_7 requires 4 arguments");
LastCallReturnsHL = true;
// x*ax
if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: true, out _))
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_q1_7_x");
EmitAsm("ADD_HL_HL");
}
EmitAsm("PUSH_HL");
// y*ay
if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: true, out _))
{
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[3]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_q1_7_y");
EmitAsm("ADD_HL_HL");
}
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__sdot2_q8_8
Compute a signed two-term dot product.
Syntax
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);Parameters
a0Signed 16-bit bit pattern, representing −32768..32767.
a1Signed 16-bit bit pattern, representing −32768..32767.
b0Signed byte bit pattern, coefficient −128..127.
b1Signed 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11654
kitaqgb/kitaqgb/CodeGenerator.cs:11654
if (funcName == "__sdot2_q8_8")
{
if (args.Length != 4) Error(expr, "__sdot2_q8_8 requires 4 arguments");
LastCallReturnsHL = true;
// Fast paths when coefficients are compile-time constants.
// Special-case: 0 / power-of-two coefficients (s8 two's complement) => arithmetic shift instead of mul.
if (TryGetS8Const(args[2], out int sax0) && (sax0 == 0) && TryGetS8Const(args[3], out int say0) && (say0 == 0))
{
CompileDiscard(args[0]);
CompileDiscard(args[1]);
EmitAsm("XOR_A");
EmitAsm("LD_H_A");
EmitAsm("LD_L_A");
return;
}
if (TryGetS8Const(args[2], out int saxOnly) && (saxOnly == 0))
{
CompileDiscard(args[0]);
// only y*ay
if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
{
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[3]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_y");
}
return;
}
if (TryGetS8Const(args[3], out int sayOnly) && (sayOnly == 0))
{
// only x*ax
if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_x");
}
// Preserve the scaled result while evaluating the zero-product input.
EmitAsm("PUSH_HL");
CompileDiscard(args[1]);
EmitAsm("POP_HL");
return;
}
// x*ax
if (!TryEmitDot2Pow2Term(args[0], args[2], signed: true, q1_7: false, out _))
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_x");
}
EmitAsm("PUSH_HL");
// y*ay
if (!TryEmitDot2Pow2Term(args[1], args[3], signed: true, q1_7: false, out _))
{
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[3]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitSMul16x8Shr8_HL("__sdot2_y");
}
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__sdot3_q1_7
Compute a signed three-term dot product.
Syntax
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameters
a0Signed 16-bit bit pattern, representing −32768..32767.
a1Signed 16-bit bit pattern, representing −32768..32767.
a2Signed 16-bit bit pattern, representing −32768..32767.
b0Signed byte bit pattern, coefficient −128..127.
b1Signed byte bit pattern, coefficient −128..127.
b2Signed 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11790
kitaqgb/kitaqgb/CodeGenerator.cs:11790
if (funcName == "__sdot3_q1_7")
{
if (args.Length != 6) Error(expr, "__sdot3_q1_7 requires 6 arguments");
LastCallReturnsHL = true;
// x*ax
EmitDotTermToHL(args[0], args[3], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_x");
EmitAsm("PUSH_HL");
// y*ay
EmitDotTermToHL(args[1], args[4], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_y");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
EmitAsm("PUSH_HL");
// z*az
EmitDotTermToHL(args[2], args[5], signed: true, q1_7: true, labelPrefix: "__sdot3_q1_7_z");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__sdot3_q8_8
Compute a signed three-term dot product.
Syntax
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameters
a0Signed 16-bit bit pattern, representing −32768..32767.
a1Signed 16-bit bit pattern, representing −32768..32767.
a2Signed 16-bit bit pattern, representing −32768..32767.
b0Signed byte bit pattern, coefficient −128..127.
b1Signed byte bit pattern, coefficient −128..127.
b2Signed 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11767
kitaqgb/kitaqgb/CodeGenerator.cs:11767
if (funcName == "__sdot3_q8_8")
{
if (args.Length != 6) Error(expr, "__sdot3_q8_8 requires 6 arguments");
LastCallReturnsHL = true;
// x*ax
EmitDotTermToHL(args[0], args[3], signed: true, q1_7: false, labelPrefix: "__sdot3_x");
EmitAsm("PUSH_HL");
// y*ay
EmitDotTermToHL(args[1], args[4], signed: true, q1_7: false, labelPrefix: "__sdot3_y");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
EmitAsm("PUSH_HL");
// z*az
EmitDotTermToHL(args[2], args[5], signed: true, q1_7: false, labelPrefix: "__sdot3_z");
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
return;
}__settile_fast
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
__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, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:9321
{
if (args.Length != 3) Error(expr, "__settile_unsafe requires 3 arguments");
// Evaluate args left-to-right (matches normal call semantics)
// Preserve x,y while evaluating tile.
CompileIntoA(args[0]);
EmitAsm("LD_L_A");
EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("PUSH_HL"); // x
CompileIntoA(args[1]);
EmitAsm("LD_L_A");
EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("PUSH_HL"); // y
CompileIntoA(args[2]);
EmitAsm("LD_C_A"); // tile
// Restore y -> A, x -> B
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitAsm("LD_D_A"); // y temp
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitAsm("LD_B_A"); // x
EmitAsm("LD_A_D"); // y
UsedSetTileFastCore = true;
EmitAsm("CALL", new AsmOperand("__settile_fast_core", AddressMode.Absolute));
return;
}
// 2.6b __settile_bulk(dest_u16, src_u16, count_u8)
// v9: bulk tile-map copy (LCD ON safe with wait+DI/EI inside core).
if (funcName == "__settile_bulk")
{
if (args.Length != 3) Error(expr, "__settile_bulk requires 3 arguments");
Expr bulkCountExpr = FoldConstants(args[2]);
// dest__settilebg16_buf
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
__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
bufA 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 are0,1,32,33from there.x, yCoordinates 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.
tileTile 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
// __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.
AB
CD

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10564
{
if (args.Length != 4) Error(expr, "__settilebg16_buf requires 4 arguments");
EmitTile16BufferedWriteIntrinsic(args[0], args[1], args[2], args[3], incrementQuad: true, labelPrefix: "settilebg16buf");
return;
}
// Update tile and attribute RAM buffers separately, repeating the attribute across the cell.
if (funcName == "__settilebg16cgb_buf")
{
if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
return;
}
// Flush both tile rows belonging to the requested buffered cell row.
if (funcName == "__settilebg16_flush")
{
if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
return;
}
// Flush the tile buffer first and copy attributes only when CGB hardware is available.
if (funcName == "__settilebg16cgb_flush")
{
if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
EmitTile16FlushRowsIn__settilebg16_flush
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
__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
bufReadable 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, countArgument order is
y,x,count.yis a 16-pixel cell row (0–15);xandcountare 8-pixel tile units. Keepx+count <= 32. For one cell at(2,3), usey=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
// __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.
AB
CD

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10581
{
if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
return;
}
// Flush the tile buffer first and copy attributes only when CGB hardware is available.
if (funcName == "__settilebg16cgb_flush")
{
if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
if (IsKnownCgbRuntimeTrue())
{
EmitSetVbkUnchecked(1);
EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
}
else
{
AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
EmitRuntimeCgbCheckCall();
EmitAsm("OR_A");
// Two unrolled row copies can exceed the relative-branch range.
EmitAsm("JP_Z", skip);
EmitSetVbkUnchecked(1);
EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
EmitAsm("JR", done);__settilebg16cgb_buf
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
__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, attrbufA 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 are0,1,32,33from there.x, yCoordinates 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.
tileTile number, 0–255. LCDC bit 4 determines how the number addresses tile-image data. It is not a VRAM byte address.
attrCGB 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
// __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.
AB
CD

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10572
{
if (args.Length != 6) Error(expr, "__settilebg16cgb_buf requires 6 arguments");
EmitTile16BufferedWriteIntrinsic(args[0], args[2], args[3], args[4], incrementQuad: true, labelPrefix: "settilebg16cgbbuf_tile");
EmitTile16BufferedWriteIntrinsic(args[1], args[2], args[3], args[5], incrementQuad: false, labelPrefix: "settilebg16cgbbuf_attr");
return;
}
// Flush both tile rows belonging to the requested buffered cell row.
if (funcName == "__settilebg16_flush")
{
if (args.Length != 4) Error(expr, "__settilebg16_flush requires 4 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[1], args[2], args[3], cgbAttr: false, labelPrefix: "settilebg16flush");
return;
}
// Flush the tile buffer first and copy attributes only when CGB hardware is available.
if (funcName == "__settilebg16cgb_flush")
{
if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
if (IsKnownCgbRuntimeTrue())
{
EmitSetVbkUnchecked(1);
EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
}
else
{
AsmOperand ski__settilebg16cgb_flush
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
__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, attrbufReadable 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, countArgument order is
y,x,count.yis a 16-pixel cell row (0–15);xandcountare 8-pixel tile units. Keepx+count <= 32. For one cell at(2,3), usey=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
// __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.
AB
CD

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:10589
{
if (args.Length != 5) Error(expr, "__settilebg16cgb_flush requires 5 arguments");
EmitTile16FlushRowsIntrinsic(args[0], args[2], args[3], args[4], cgbAttr: false, labelPrefix: "settilebg16cgbflush_tile");
if (IsKnownCgbRuntimeTrue())
{
EmitSetVbkUnchecked(1);
EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
}
else
{
AsmOperand skip = MakeUniqueLabel("settilebg16cgbflush_skip");
AsmOperand done = MakeUniqueLabel("settilebg16cgbflush_done");
EmitRuntimeCgbCheckCall();
EmitAsm("OR_A");
// Two unrolled row copies can exceed the relative-branch range.
EmitAsm("JP_Z", skip);
EmitSetVbkUnchecked(1);
EmitTile16FlushRowsIntrinsic(args[1], args[2], args[3], args[4], cgbAttr: true, labelPrefix: "settilebg16cgbflush_attr");
EmitAsm("JR", done);
EmitLabel(skip);
EmitLabel(done);
}
return;
}
// 2.14 __getbgmapbase() -> u16 in HL
// Returns the BG tilemap base address selected by LCDC bit3:
// bit3=0 => 0x9800, bit3=1 => 0x9C00
// Treated as a compiler intrinsic (no C definition required).
if (funcName == "__getbgmapbase")
{
if (args.Length != 0) Error(expr, "__getb__smac16
Compute a signed multiply-accumulate.
Syntax
u16 __smac16(u16 acc, u16 a, u8 b);Parameters
acc16-bit accumulator to add.
aSigned 16-bit bit pattern, representing −32768..32767.
bSigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11516
kitaqgb/kitaqgb/CodeGenerator.cs:11516
if (funcName == "__smac16")
{
if (args.Length != 3) Error(expr, "__smac16 requires 3 arguments");
// mul = (a*b)>>8 (signed)
EmitDotTermToHL(args[1], args[2], signed: true, q1_7: false, labelPrefix: "__smac16_mul");
// acc + mul
EmitAsm("PUSH_HL");
CompileIntoHL(args[0]);
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
LastCallReturnsHL = true;
return;
}__smac16_q1_7
Compute a signed multiply-accumulate.
Syntax
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);Parameters
acc16-bit accumulator to add.
aSigned 16-bit bit pattern, representing −32768..32767.
bSigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11533
kitaqgb/kitaqgb/CodeGenerator.cs:11533
if (funcName == "__smac16_q1_7")
{
if (args.Length != 3) Error(expr, "__smac16_q1_7 requires 3 arguments");
// mul = (a*b)>>7 (approx via >>8 then *2)
EmitDotTermToHL(args[1], args[2], signed: true, q1_7: true, labelPrefix: "__smac16_q1_7_mul");
// acc + mul
EmitAsm("PUSH_HL");
CompileIntoHL(args[0]);
EmitAsm("POP_DE");
EmitAsm("ADD_HL_DE");
LastCallReturnsHL = true;
return;
}__smul16x8_q1_7
Compute a signed multiplication.
Syntax
u16 __smul16x8_q1_7(u16 a, u8 b);Parameters
aSigned 16-bit bit pattern, representing −32768..32767.
bSigned 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
// 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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:11490
kitaqgb/kitaqgb/CodeGenerator.cs:11490
if (funcName == "__smul16x8_q1_7")
{
if (args.Length != 2) Error(expr, "__smul16x8_q1_7 requires 2 arguments");
EmitDotTermToHL(args[0], args[1], signed: true, q1_7: true, labelPrefix: "__smul16x8_q1_7");
LastCallReturnsHL = true;
return;
}__sram_read8
Read one SRAM byte at a CPU address.
Syntax
u8 __sram_read8(u16 address);Parameters
addressCPU 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
*((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.


Build command
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=8kImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:8825
kitaqgb/kitaqgb/CodeGenerator.cs:8825
if (funcName == "__sram_read8")
{
if (args.Length != 1) Error(expr, "__sram_read8(addr) expects 1 argument");
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
EmitAsm("LD_A_IMM", new AsmOperand(0x0A, AddressMode.Immediate));
EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
EmitAsm("POP_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_B_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x00, AddressMode.Immediate));
EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
EmitAsm("LD_A_B");
return;
}__sram_write8
Write one SRAM byte at a CPU address.
Syntax
void __sram_write8(u16 address, u8 value);Parameters
addressCPU address in $A000..$BFFF.
valueByte 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
__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.


Build command
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=8kImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:8846
kitaqgb/kitaqgb/CodeGenerator.cs:8846
if (funcName == "__sram_write8")
{
if (args.Length != 2) Error(expr, "__sram_write8(addr, value) expects 2 arguments");
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("LD_C_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x0A, AddressMode.Immediate));
EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
EmitAsm("POP_HL");
EmitAsm("LD_A_C");
EmitAsm("LD_HL_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x00, AddressMode.Immediate));
EmitAsm("LD_MEM_A", new AsmOperand(0x0000, AddressMode.Absolute));
return;
}__svbk_get
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
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
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.

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7387
{
if (args.Length != 0) Error(expr, "__svbk_get() expects 0 arguments");
if (!Program.IsCgbOnlyTargetRequested())
Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_get() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");
EmitReadNormalizedSvbkIntoA();
return;
}
// Select a WRAM bank and return the prior normalized bank; diagnose out-of-range constant requests.
if (funcName == "__svbk_set")
{
if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
if (!Program.IsCgbOnlyTargetRequested())
Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");
Expr bankExpr = FoldConstants(args[0]);
if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);
EmitReadNormalizedSvbkIntoA();
EmitAsm("LD_B_A");
CompileIntoA(bankExpr);
EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
EmitAsm("LD_A_B");
return;
}
// __cgb_safe_set_* (safe CGB-only register write wrappers)
if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
{__svbk_set
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
u8 __svbk_set(u8 bank);The parameter names in this call syntax explain the compiler intrinsic; they are not a separate C prototype.
Parameters
bankRequested 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
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.

Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7397
{
if (args.Length != 1) Error(expr, "__svbk_set(bank_u8) expects 1 argument");
if (!Program.IsCgbOnlyTargetRequested())
Error(expr, ErrorCode.BankedWramRequiresCgbOnly, "__svbk_set() requires #pragma rom_cgb cgb_only or --cgb=cgb_only");
Expr bankExpr = FoldConstants(args[0]);
if (bankExpr.Match(Tag.Integer, out int bankLiteral) && (bankLiteral < 1 || bankLiteral > 7))
Error(expr, ErrorCode.InvalidWramXBank, "__svbk_set() bank must be in range 1..7 (got {0})", bankLiteral);
EmitReadNormalizedSvbkIntoA();
EmitAsm("LD_B_A");
CompileIntoA(bankExpr);
EmitAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem));
EmitAsm("LD_A_B");
return;
}
// __cgb_safe_set_* (safe CGB-only register write wrappers)
if (TryGetCgbSafeRegister(funcName, out int cgbOffset, out string cgbReg))
{
if (args.Length != 1) Program.Error(funcName + "(value_u8) expects 1 argument");
if (IsKnownCgbRuntimeTrue())
{
CompileIntoA(args[0]);
if (cgbOffset == 0x4F)
EmitAsm("AND_IMM", new AsmOperand(0x01, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", new AsmOperand(cgbOffset, AddressMode.HighMem));
CgbGuardedWriteCount++;
CgbGuardedRegisters.Add(cgbReg);
return;__vram_memcpy_unsafe
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
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
dstFirst 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.srcSource 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.
lenNumber 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
// 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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:8104
{
if (args.Length != 3) Error(expr, "__vram_memcpy_unsafe(dst, src, len) expects 3 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
// Use the same count specialization without emitting LCD-mode waits.
Expr vramCopyUnsafeLen = FoldConstants(args[2]);
EmitLengthExprIntoBC(vramCopyUnsafeLen);
EmitAsm("POP_DE"); // src
EmitAsm("POP_HL"); // dst
if (vramCopyUnsafeLen.Match(Tag.Integer, out int vramCopyUnsafeLenConst) &&
TryEmitVramMemcpyConstCount(false, vramCopyUnsafeLenConst, "vramcpyu_small"))
return;
AsmOperand vcuLoop = MakeUniqueLabel("vramcpyu_loop");
AsmOperand vcuDone = MakeUniqueLabel("vramcpyu_done");
EmitLabel(vcuLoop);
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JR_Z", vcuDone);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_BC");
EmitAsm("JR", vcuLoop);
EmitLabel(vcuDone);
return;
}
// __vram_memset(dst_ptr, value_u8, len_u16)
// Safe VRAM fill: if LCD is on, each byte write is gated by STAT mode 0/1.
if (funcName == "__vram_memset")
{
if (args.Length != 3) Error(expr, "__vram_memset(dst, value, l__vram_memset_unsafe
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
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
dstFirst 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.valueOne byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.
lenNumber 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:8196
{
if (args.Length != 3) Error(expr, "__vram_memset_unsafe(dst, value, len) expects 3 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
CompileIntoA(args[1]); EmitAsm("LD_D_A"); // value
// Specialize a constant unsafe fill or fall back to its unchecked byte loop.
Expr vramSetUnsafeLen = FoldConstants(args[2]);
EmitLengthExprIntoBC(vramSetUnsafeLen);
EmitAsm("POP_HL"); // dst
if (vramSetUnsafeLen.Match(Tag.Integer, out int vramSetUnsafeLenConst) &&
TryEmitVramMemsetConstCount(false, vramSetUnsafeLenConst, "vramsetu_small"))
return;
AsmOperand vsuLoop = MakeUniqueLabel("vramsetu_loop");
AsmOperand vsuDone = MakeUniqueLabel("vramsetu_done");
EmitLabel(vsuLoop);
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JR_Z", vsuDone);
EmitAsm("LD_A_D");
EmitAsm("LDI_HL_A");
EmitAsm("DEC_BC");
EmitAsm("JR", vsuLoop);
EmitLabel(vsuDone);
return;
}
// __far_memcpy(dst_ptr, bank_u8, src_ptr, len_u16)
// Copies from switchable ROM bank via a bank0 helper and restores original bank.
if (funcName == "__far_memcpy")
{
if (args.Length != 4) Error(expr, "__far_memcpy(dst, bank, src, len) expects 4 arguments");
CompileIntoHL(__wait_ly
With LCD running, wait until LY equals the target line.
Syntax
void __wait_ly(u8 ly);Parameters
lyScanline 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
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.


Build command
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=noneImplementation and source
kitaqgb/kitaqgb/CodeGenerator.cs:7483
kitaqgb/kitaqgb/CodeGenerator.cs:7483
if (funcName == "__wait_ly")
{
if (args.Length != 1) Error(expr, "__wait_ly(target) expects 1 argument");
CompileIntoA(args[0]);
EmitAsm("LD_B_A");
AsmOperand wlyLoop = MakeUniqueLabel("waitly_loop");
AsmOperand wlyDone = MakeUniqueLabel("waitly_done");
// If LCD is off, LY is not meaningful; return.
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
EmitAsm("JR_Z", wlyDone);
EmitLabel(wlyLoop);
EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
EmitAsm("CP_B");
EmitAsm("JR_NZ", wlyLoop);
EmitLabel(wlyDone);
return;
}physics2d.h — 2D 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
s16 __smul16x8(s16 a, s8 b);Parameters
aSigned 16-bit value to scale.
bSigned 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/physics2d.h:9
Multiply a signed word by a signed byte; divide by 256, truncating toward zero.
kitaqgb/kitaqgb/CodeGenerator.cs:11478
if (funcName == "__smul16x8")
{
if (args.Length != 2) Error(expr, "__smul16x8 requires 2 arguments");
EmitDotTermToHL(args[0], args[1], signed: true, q1_7: false, labelPrefix: "__smul16x8");
LastCallReturnsHL = true;
return;
}rpg.h — 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
void __bit_clear(u8* p, u16 bit_index);Parameters
pPointer 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_indexZero-based bit index as
u16(0–65535), not a byte offset or a bit mask. Index 0 selects mask0x01in byte 0; index 7 selects0x80in byte 0; index 8 selects0x01in byte 1. In general, the byte offset isindex / 8and the mask is1 << (index % 8). For an N-byte array, requireindex < 8*Nand 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:187
kitaqgb/kitaqgb/CodeGenerator.cs:8582
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
{
if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
EmitAsm("LD_B_A"); // bit number 0..7
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitShiftRightLogicalHL(3);
EmitAsm("PUSH_HL"); // byte offset
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)
AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
EmitAsm("LD_A_B");
EmitAsm("OR_A");
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR_Z", maskReady);
EmitLabel(maskLoop);
EmitAsm("ADD_A");
EmitAsm("DEC_B");
EmitAsm("JR_NZ", maskLoop);
EmitLabel(maskReady);
EmitAsm("LD_B_A"); // mask
if (funcName == "__bit_test")
{
AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
AsmOperand done = MakeUniqueLabel("bit_test_done");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("JR_Z", retZero);
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR", done);
EmitLabel(retZero);
EmitAsm("XOR_A");
EmitLabel(done);
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_set")
{
EmitAsm("LD_A_HL");
EmitAsm("OR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_clear")
{
EmitAsm("LD_A_B");
EmitAsm("CPL");
EmitAsm("LD_B_A");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
EmitAsm("LD_A_HL");
EmitAsm("XOR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}__bit_set
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
void __bit_set(u8* p, u16 bit_index);Parameters
pPointer 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_indexZero-based bit index as
u16(0–65535), not a byte offset or a bit mask. Index 0 selects mask0x01in byte 0; index 7 selects0x80in byte 0; index 8 selects0x01in byte 1. In general, the byte offset isindex / 8and the mask is1 << (index % 8). For an N-byte array, requireindex < 8*Nand 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:186
kitaqgb/kitaqgb/CodeGenerator.cs:8582
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
{
if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
EmitAsm("LD_B_A"); // bit number 0..7
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitShiftRightLogicalHL(3);
EmitAsm("PUSH_HL"); // byte offset
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)
AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
EmitAsm("LD_A_B");
EmitAsm("OR_A");
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR_Z", maskReady);
EmitLabel(maskLoop);
EmitAsm("ADD_A");
EmitAsm("DEC_B");
EmitAsm("JR_NZ", maskLoop);
EmitLabel(maskReady);
EmitAsm("LD_B_A"); // mask
if (funcName == "__bit_test")
{
AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
AsmOperand done = MakeUniqueLabel("bit_test_done");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("JR_Z", retZero);
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR", done);
EmitLabel(retZero);
EmitAsm("XOR_A");
EmitLabel(done);
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_set")
{
EmitAsm("LD_A_HL");
EmitAsm("OR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_clear")
{
EmitAsm("LD_A_B");
EmitAsm("CPL");
EmitAsm("LD_B_A");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
EmitAsm("LD_A_HL");
EmitAsm("XOR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}__bit_test
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
u8 __bit_test(const u8* p, u16 bit_index);Parameters
pPointer 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_indexZero-based bit index as
u16(0–65535), not a byte offset or a bit mask. Index 0 selects mask0x01in byte 0; index 7 selects0x80in byte 0; index 8 selects0x01in byte 1. In general, the byte offset isindex / 8and the mask is1 << (index % 8). For an N-byte array, requireindex < 8*Nand 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:185
kitaqgb/kitaqgb/CodeGenerator.cs:8582
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
{
if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
EmitAsm("LD_B_A"); // bit number 0..7
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitShiftRightLogicalHL(3);
EmitAsm("PUSH_HL"); // byte offset
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)
AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
EmitAsm("LD_A_B");
EmitAsm("OR_A");
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR_Z", maskReady);
EmitLabel(maskLoop);
EmitAsm("ADD_A");
EmitAsm("DEC_B");
EmitAsm("JR_NZ", maskLoop);
EmitLabel(maskReady);
EmitAsm("LD_B_A"); // mask
if (funcName == "__bit_test")
{
AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
AsmOperand done = MakeUniqueLabel("bit_test_done");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("JR_Z", retZero);
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR", done);
EmitLabel(retZero);
EmitAsm("XOR_A");
EmitLabel(done);
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_set")
{
EmitAsm("LD_A_HL");
EmitAsm("OR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_clear")
{
EmitAsm("LD_A_B");
EmitAsm("CPL");
EmitAsm("LD_B_A");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
EmitAsm("LD_A_HL");
EmitAsm("XOR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}__bit_toggle
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
void __bit_toggle(u8* p, u16 bit_index);Parameters
pPointer 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_indexZero-based bit index as
u16(0–65535), not a byte offset or a bit mask. Index 0 selects mask0x01in byte 0; index 7 selects0x80in byte 0; index 8 selects0x01in byte 1. In general, the byte offset isindex / 8and the mask is1 << (index % 8). For an N-byte array, requireindex < 8*Nand 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:188
kitaqgb/kitaqgb/CodeGenerator.cs:8582
if (funcName == "__bit_test" || funcName == "__bit_set" || funcName == "__bit_clear" || funcName == "__bit_toggle")
{
if (args.Length != 2) Error(expr, funcName + "(ptr, bit_index) expects 2 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // ptr
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // bit_index
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("AND_IMM", new AsmOperand(7, AddressMode.Immediate));
EmitAsm("LD_B_A"); // bit number 0..7
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitShiftRightLogicalHL(3);
EmitAsm("PUSH_HL"); // byte offset
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
EmitAsm("ADD_HL_DE"); // HL = ptr + (bit_index >> 3)
AsmOperand maskReady = MakeUniqueLabel("bit_mask_ready");
AsmOperand maskLoop = MakeUniqueLabel("bit_mask_loop");
EmitAsm("LD_A_B");
EmitAsm("OR_A");
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR_Z", maskReady);
EmitLabel(maskLoop);
EmitAsm("ADD_A");
EmitAsm("DEC_B");
EmitAsm("JR_NZ", maskLoop);
EmitLabel(maskReady);
EmitAsm("LD_B_A"); // mask
if (funcName == "__bit_test")
{
AsmOperand retZero = MakeUniqueLabel("bit_test_zero");
AsmOperand done = MakeUniqueLabel("bit_test_done");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("JR_Z", retZero);
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR", done);
EmitLabel(retZero);
EmitAsm("XOR_A");
EmitLabel(done);
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_set")
{
EmitAsm("LD_A_HL");
EmitAsm("OR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
if (funcName == "__bit_clear")
{
EmitAsm("LD_A_B");
EmitAsm("CPL");
EmitAsm("LD_B_A");
EmitAsm("LD_A_HL");
EmitAsm("AND_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}
EmitAsm("LD_A_HL");
EmitAsm("XOR_B");
EmitAsm("LD_HL_A");
EmitAsm("ADD_SP_IMM", new AsmOperand(4, AddressMode.Relative));
return;
}__copy16
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
void __copy16(void* dst, const void* src);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
srcPointer 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:177
kitaqgb/kitaqgb/CodeGenerator.cs:8247
if (funcName == "__copy16" || funcName == "__copy32")
{
if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");
int copyLen = (funcName == "__copy16") ? 16 : 32;
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
EmitAsm("POP_DE"); // src
EmitAsm("POP_HL"); // dst
EmitRamMemcpyLoop(funcName.TrimStart('_'));
return;
}
void EmitRamMemcpyLoop(string labelPrefix)
{
AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");
EmitLabel(loop);
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JR_Z", done);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_BC");
EmitAsm("JR", loop);
EmitLabel(done);
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__copy32
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
void __copy32(void* dst, const void* src);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
srcPointer 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:178
kitaqgb/kitaqgb/CodeGenerator.cs:8247
if (funcName == "__copy16" || funcName == "__copy32")
{
if (args.Length != 2) Error(expr, funcName + "(dst, src) expects 2 arguments");
int copyLen = (funcName == "__copy16") ? 16 : 32;
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
CompileIntoHL(args[1]); EmitAsm("PUSH_HL"); // src
EmitAsm("LD_BC_IMM", new AsmOperand(copyLen, AddressMode.Immediate16));
EmitAsm("POP_DE"); // src
EmitAsm("POP_HL"); // dst
EmitRamMemcpyLoop(funcName.TrimStart('_'));
return;
}
void EmitRamMemcpyLoop(string labelPrefix)
{
AsmOperand loop = MakeUniqueLabel(labelPrefix + "_loop");
AsmOperand done = MakeUniqueLabel(labelPrefix + "_done");
EmitLabel(loop);
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JR_Z", done);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_BC");
EmitAsm("JR", loop);
EmitLabel(done);
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__far_memcpy
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
void __far_memcpy(void* dst, u8 bank, const void* src, u16 len);Parameters
dstDestination in writable memory. Reserve enough space for every byte copied. Except for a zero-byte transfer, a null or invalid destination is not checked.
bank8-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.
srcNear pointer to readable source bytes in the selected ROM bank. It does not contain a bank number.
lenByte 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
__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.


Build command
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-disasmImplementation 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
void __fill_tilemap(u16 dst, u8 value, u16 len);Parameters
dstFirst 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.valueOne byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.
lenNumber 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:157
__getbgmapbase
Read LCDC bit 3 to select the background tile-map base.
Syntax
u16 __getbgmapbase();Return value
Returns $9800 or $9C00, the tile-map base rather than the tile graphics address.
Usage example
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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:140
kitaqgb/kitaqgb/CodeGenerator.cs:10619
if (funcName == "__getbgmapbase")
{
if (args.Length != 0) Error(expr, "__getbgmapbase requires 0 arguments");
AsmOperand gb_base0 = MakeUniqueLabel("getbgbase_base0");
AsmOperand gb_done = MakeUniqueLabel("getbgbase_done");
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("AND_IMM", new AsmOperand(0x08, AddressMode.Immediate)); // bit3 (BG tile map display select)
EmitAsm("JR_Z", gb_base0);
EmitAsm("LD_HL_IMM", new AsmOperand(0x9C00, AddressMode.Immediate16));
EmitAsm("JR", gb_done);
EmitLabel(gb_base0);
EmitAsm("LD_HL_IMM", new AsmOperand(0x9800, AddressMode.Immediate16));
EmitLabel(gb_done);
LastCallReturnsHL = true;
return;
}__manhattan
Add the absolute X and Y differences between two points.
Syntax
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);Parameters
x1Point 1 X coordinate, 0..255.
y1Point 1 Y coordinate, 0..255.
x2Point 2 X coordinate, 0..255.
y2Point 2 Y coordinate, 0..255.
Return value
Returns the low byte of |x1−x2|+|y1−y2|; distances above 255 wrap.
Usage example
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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:183
kitaqgb/kitaqgb/CodeGenerator.cs:8516
if (funcName == "__manhattan")
{
if (args.Length != 4) Error(expr, "__manhattan(x1, y1, x2, y2) expects 4 arguments");
Expr[] foldedManhattanArgs = args.Select(FoldConstants).ToArray();
if (foldedManhattanArgs[0].Match(Tag.Integer, out int constX1) &&
foldedManhattanArgs[1].Match(Tag.Integer, out int constY1) &&
foldedManhattanArgs[2].Match(Tag.Integer, out int constX2) &&
foldedManhattanArgs[3].Match(Tag.Integer, out int constY2))
{
int dx = Math.Abs((constX1 & 0xFF) - (constX2 & 0xFF));
int dy = Math.Abs((constY1 & 0xFF) - (constY2 & 0xFF));
EmitAsm("LD_A_IMM", new AsmOperand((dx + dy) & 0xFF, AddressMode.Immediate));
return;
}
EmitPushExprAsWord(args[0]); // x1
EmitPushExprAsWord(args[1]); // y1
EmitPushExprAsWord(args[2]); // x2
EmitPushExprAsWord(args[3]); // y2
AsmOperand dxGe = MakeUniqueLabel("manhattan_dx_ge");
AsmOperand dxDone = MakeUniqueLabel("manhattan_dx_done");
AsmOperand dyGe = MakeUniqueLabel("manhattan_dy_ge");
AsmOperand dyDone = MakeUniqueLabel("manhattan_dy_done");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_B_A"); // x1
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // x2
EmitAsm("LD_A_B");
EmitAsm("CP_C");
EmitAsm("JR_NC", dxGe);
EmitAsm("LD_A_C");
EmitAsm("SUB_B");
EmitAsm("JR", dxDone);
EmitLabel(dxGe);
EmitAsm("LD_A_B");
EmitAsm("SUB_C");
EmitLabel(dxDone);
EmitAsm("LD_B_A"); // dx
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // y1
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A"); // y2
EmitAsm("LD_A_C");
EmitAsm("CP_D");
EmitAsm("JR_NC", dyGe);
EmitAsm("LD_A_D");
EmitAsm("SUB_C");
EmitAsm("JR", dyDone);
EmitLabel(dyGe);
EmitAsm("LD_A_C");
EmitAsm("SUB_D");
EmitLabel(dyDone);
EmitAsm("ADD_B");
EmitAsm("ADD_SP_IMM", new AsmOperand(8, AddressMode.Relative));
return;
}__map_index
Convert a cell coordinate to a row-major array index.
Syntax
u16 __map_index(u8 x, u8 y, u8 width);Parameters
xColumn 0..255, normally less than width.
yRow 0..255.
widthCells per row, 0..255.
Return value
Returns y*width+x as u16; does not validate coordinates.
Usage example
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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:181
kitaqgb/kitaqgb/CodeGenerator.cs:8365
if (funcName == "__map_index")
{
if (args.Length != 3) Error(expr, "__map_index(x, y, width) expects 3 arguments");
Expr xExpr = FoldConstants(args[0]);
Expr yExpr = FoldConstants(args[1]);
Expr widthExpr = FoldConstants(args[2]);
// Apply the width parameter's byte conversion before choosing a shift specialization.
if (widthExpr.Match(Tag.Integer, out int byteWidth))
widthExpr = Expr.Make(Tag.Integer, byteWidth & 0xFF).WithSource(widthExpr.Source);
if (xExpr.Match(Tag.Integer, out int fullXConst) &&
yExpr.Match(Tag.Integer, out int fullYConst) &&
widthExpr.Match(Tag.Integer, out int fullWidthConst))
{
// Apply the byte argument widths before calculating the word-sized constant index.
int fullIndexConst = (((fullYConst & 0xFF) * (fullWidthConst & 0xFF)) + (fullXConst & 0xFF)) & 0xFFFF;
EmitAsm("LD_HL_IMM", new AsmOperand(fullIndexConst, AddressMode.Immediate16));
LastCallReturnsHL = true;
return;
}
if (widthExpr.Match(Tag.Integer, out int widthConst) && widthConst > 0 && IsPow2(widthConst))
{
CompileIntoA(yExpr);
EmitAsm("LD_L_A");
EmitAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate));
int sh = Log2Pow2(widthConst);
for (int i = 0; i < sh; i++) EmitAsm("ADD_HL_HL");
if (xExpr.Match(Tag.Integer, out int xConst) && xConst == 0)
{
LastCallReturnsHL = true;
return;
}
CompileIntoA(xExpr);
EmitAsm("LD_E_A");
EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("ADD_HL_DE");
LastCallReturnsHL = true;
return;
}
CompileIntoA(args[0]); EmitPushAAsWord(); // x
CompileIntoA(args[1]); EmitPushAAsWord(); // y
CompileIntoA(args[2]); EmitPushAAsWord(); // width
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // width
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A"); // y
EmitMul8x8ToHL_EC("__map_index");
// Loading the saved X argument uses HL, so preserve the multiplication result first.
EmitAsm("PUSH_HL");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("POP_HL");
EmitAsm("ADD_HL_DE");
EmitAsm("ADD_SP_IMM", new AsmOperand(6, AddressMode.Relative));
LastCallReturnsHL = true;
return;
}__memcpy
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
void __memcpy(void* dst, const void* src, u16 count);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
srcPointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.
countNumber 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:142
kitaqgb/kitaqgb/CodeGenerator.cs:9084
if (funcName == "__memcpy" || funcName == "__memcpy_small")
{
// Select byte or word count semantics before optional fixed-array bounds diagnostics.
bool useU8Count = (funcName == "__memcpy_small");
if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }
bool doCheck = Program.CheckMemCopy && !InUnsafe;
bool hasDst = false, hasSrc = false;
int dstBytes = 0, srcBytes = 0;
string dstName = null, srcName = null;
if (doCheck)
{
hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
}
bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
int normalizedConstCount = constCountAny;
if (isConstCountAny && useU8Count)
normalizedConstCount &= 0xFF;
if (doCheck && isConstCountAny)
{
if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
{
Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
{
Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
}
if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
{
if (normalizedConstCount == 0) return;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
for (int k = 0; k < normalizedConstCount; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
if (normalizedConstCount <= 64)
{
int blocks8 = normalizedConstCount / 8;
int rem8 = normalizedConstCount % 8;
if (blocks8 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
// Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
EmitLabel(loop8);
for (int k = 0; k < 8; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop8);
}
for (int k = 0; k < rem8; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
int blocks16 = normalizedConstCount / 16;
int rem16 = normalizedConstCount % 16;
if (blocks16 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
// Use sixteen-byte groups while keeping the residual copy length known at compile time.
AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
EmitLabel(loop16);
for (int k = 0; k < 16; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop16);
}
for (int k = 0; k < rem16; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
if (useU8Count)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_B_A");
if (doCheck)
{
if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
}
AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
EmitAsm("OR_A");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
EmitAsm("JP_Z", doneLabel);
EmitLabel(smallLoopLabel);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", smallLoopLabel);
EmitLabel(doneLabel);
return;
}
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[2]);
if (doCheck && !isConstCountAny)
{
if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
}
EmitAsm("LD_B_H");
EmitAsm("LD_C_L");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
// A zero word count must skip the first write as well as the loop.
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_Z", wordDoneLabel);
EmitLabel(loopLabel);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_BC");
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_NZ", loopLabel);
EmitLabel(wordDoneLabel);
return;
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memcpy_small
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
void __memcpy_small(void* dst, const void* src, u8 len);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
srcPointer to the first readable source byte. The entire range must remain accessible and unchanged throughout the copy.
lenNumber 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_smallfor 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:175
kitaqgb/kitaqgb/CodeGenerator.cs:9084
if (funcName == "__memcpy" || funcName == "__memcpy_small")
{
// Select byte or word count semantics before optional fixed-array bounds diagnostics.
bool useU8Count = (funcName == "__memcpy_small");
if (args.Length != 3) { Error(expr, "__memcpy requires 3 arguments"); return; }
bool doCheck = Program.CheckMemCopy && !InUnsafe;
bool hasDst = false, hasSrc = false;
int dstBytes = 0, srcBytes = 0;
string dstName = null, srcName = null;
if (doCheck)
{
hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
hasSrc = TryGetFixedArrayByteLength(args[1], out srcBytes, out srcName);
}
bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
int normalizedConstCount = constCountAny;
if (isConstCountAny && useU8Count)
normalizedConstCount &= 0xFF;
if (doCheck && isConstCountAny)
{
if (hasDst && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
{
Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed dst array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
if (hasSrc && normalizedConstCount >= 0 && normalizedConstCount > srcBytes)
{
Program.Warning(args[2].Source, "__memcpy count {0} exceeds fixed src array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, srcName, srcBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
}
if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
{
if (normalizedConstCount == 0) return;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
for (int k = 0; k < normalizedConstCount; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
if (isConstCountAny && normalizedConstCount > 16 && normalizedConstCount <= 255)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
if (normalizedConstCount <= 64)
{
int blocks8 = normalizedConstCount / 8;
int rem8 = normalizedConstCount % 8;
if (blocks8 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
// Advance source and destination through eight-byte groups, then emit the remaining constant bytes.
AsmOperand loop8 = MakeUniqueLabel("memcpy8_loop");
EmitLabel(loop8);
for (int k = 0; k < 8; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop8);
}
for (int k = 0; k < rem8; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
int blocks16 = normalizedConstCount / 16;
int rem16 = normalizedConstCount % 16;
if (blocks16 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
// Use sixteen-byte groups while keeping the residual copy length known at compile time.
AsmOperand loop16 = MakeUniqueLabel("memcpy16_loop");
EmitLabel(loop16);
for (int k = 0; k < 16; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop16);
}
for (int k = 0; k < rem16; k++)
{
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
}
return;
}
if (useU8Count)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoA(args[2]);
EmitAsm("LD_B_A");
if (doCheck)
{
if (hasDst) EmitU8LeqConstOrTrapInA(dstBytes);
if (hasSrc) EmitU8LeqConstOrTrapInA(srcBytes);
}
AsmOperand doneLabel = MakeUniqueLabel("memcpy_small_done");
AsmOperand smallLoopLabel = MakeUniqueLabel("memcpy_small_loop");
EmitAsm("OR_A");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
EmitAsm("JP_Z", doneLabel);
EmitLabel(smallLoopLabel);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", smallLoopLabel);
EmitLabel(doneLabel);
return;
}
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[1]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[2]);
if (doCheck && !isConstCountAny)
{
if (hasDst) EmitU16LeqConstOrTrap(dstBytes);
if (hasSrc) EmitU16LeqConstOrTrap(srcBytes);
}
EmitAsm("LD_B_H");
EmitAsm("LD_C_L");
EmitAsm("POP_DE");
EmitAsm("POP_HL");
AsmOperand loopLabel = MakeUniqueLabel("memcpy_loop");
AsmOperand wordDoneLabel = MakeUniqueLabel("memcpy_done");
// A zero word count must skip the first write as well as the loop.
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_Z", wordDoneLabel);
EmitLabel(loopLabel);
EmitAsm("LD_A_DE");
EmitAsm("LDI_HL_A");
EmitAsm("INC_DE");
EmitAsm("DEC_BC");
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_NZ", loopLabel);
EmitLabel(wordDoneLabel);
return;
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memset
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
void __memset(void* dst, u8 value, u16 count);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
valueThe byte repeated at every destination address: 0–255. Use 0 to clear the range. To initialize a
u16array to a nonzero numeric value, assign its elements individually instead.countNumber 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:141
kitaqgb/kitaqgb/CodeGenerator.cs:8926
if (funcName == "__memset" || funcName == "__memset_small")
{
// The small API truncates its count to a byte; the general API uses a word count.
bool useU8Count = (funcName == "__memset_small");
if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }
// -Zcheck: fixed-array range check for memset length
bool doCheck = Program.CheckMemCopy && !InUnsafe;
bool hasDst = false;
int dstBytes = 0;
string dstName = null;
if (doCheck)
hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
int normalizedConstCount = constCountAny;
if (isConstCountAny && useU8Count)
normalizedConstCount &= 0xFF;
if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
{
Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
// Size-specialized memset for small constant counts (<=16): unrolled stores.
if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
{
if (normalizedConstCount == 0) return;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
for (int k = 0; k < normalizedConstCount; k++)
EmitAsm("LDI_HL_A");
return;
}
// Size-specialized memset for constant counts beyond 16
if (isConstCountAny && normalizedConstCount >= 0)
{
if (normalizedConstCount == 0) return;
if (normalizedConstCount > 16 && normalizedConstCount <= 64)
{
int blocks8 = normalizedConstCount / 8;
int rem = normalizedConstCount % 8;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
if (blocks8 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
// Emit groups of eight stores followed by the constant remainder for medium fills.
AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
EmitLabel(loop8);
for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop8);
}
for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
return;
}
if (normalizedConstCount <= 255)
{
int blocks16 = normalizedConstCount / 16;
int rem = normalizedConstCount % 16;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
if (blocks16 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
// Use sixteen-store groups for larger byte-range constant fills.
AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
EmitLabel(loop16);
for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop16);
}
for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
return;
}
}
if (useU8Count)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("LD_D_A");
CompileIntoA(args[2]);
EmitAsm("LD_B_A");
if (doCheck && hasDst)
EmitU8LeqConstOrTrapInA(dstBytes);
AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
EmitAsm("OR_A");
EmitAsm("POP_HL");
EmitAsm("JP_Z", doneLabel);
EmitAsm("LD_A_D");
EmitLabel(smallLoopLabel);
EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", smallLoopLabel);
EmitLabel(doneLabel);
return;
}
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[2]);
if (doCheck && hasDst && !isConstCountAny)
EmitU16LeqConstOrTrap(dstBytes);
EmitAsm("LD_B_H");
EmitAsm("LD_C_L");
CompileIntoA(args[1]);
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
// A zero word count must skip the first write as well as the loop.
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_Z", wordDoneLabel);
EmitLabel(loopLabel);
EmitAsm("LD_A_D");
EmitAsm("LDI_HL_A");
EmitAsm("DEC_BC");
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_NZ", loopLabel);
EmitLabel(wordDoneLabel);
return;
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__memset_small
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
void __memset_small(void* dst, u8 value, u8 len);Parameters
dstPointer to the first writable destination byte. Reserve enough consecutive bytes for the complete operation; no buffer is allocated for you.
valueThe byte repeated at every destination address: 0–255. Use 0 to clear the range. To initialize a
u16array to a nonzero numeric value, assign its elements individually instead.lenNumber 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_smallfor 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:176
kitaqgb/kitaqgb/CodeGenerator.cs:8926
if (funcName == "__memset" || funcName == "__memset_small")
{
// The small API truncates its count to a byte; the general API uses a word count.
bool useU8Count = (funcName == "__memset_small");
if (args.Length != 3) { Error(expr, "__memset requires 3 arguments"); return; }
// -Zcheck: fixed-array range check for memset length
bool doCheck = Program.CheckMemCopy && !InUnsafe;
bool hasDst = false;
int dstBytes = 0;
string dstName = null;
if (doCheck)
hasDst = TryGetFixedArrayByteLength(args[0], out dstBytes, out dstName);
bool isConstCountAny = args[2].Match(Tag.Integer, out int constCountAny);
int normalizedConstCount = constCountAny;
if (isConstCountAny && useU8Count)
normalizedConstCount &= 0xFF;
if (doCheck && hasDst && isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount > dstBytes)
{
Program.Warning(args[2].Source, "__memset count {0} exceeds fixed array '{1}' size {2} bytes (-Zcheck)", normalizedConstCount, dstName, dstBytes);
EmitAsm("JP", EnsureCheckTrap());
return;
}
// Size-specialized memset for small constant counts (<=16): unrolled stores.
if (isConstCountAny && normalizedConstCount >= 0 && normalizedConstCount <= 16)
{
if (normalizedConstCount == 0) return;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
for (int k = 0; k < normalizedConstCount; k++)
EmitAsm("LDI_HL_A");
return;
}
// Size-specialized memset for constant counts beyond 16
if (isConstCountAny && normalizedConstCount >= 0)
{
if (normalizedConstCount == 0) return;
if (normalizedConstCount > 16 && normalizedConstCount <= 64)
{
int blocks8 = normalizedConstCount / 8;
int rem = normalizedConstCount % 8;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
if (blocks8 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks8, AddressMode.Immediate));
// Emit groups of eight stores followed by the constant remainder for medium fills.
AsmOperand loop8 = MakeUniqueLabel("memset8_loop");
EmitLabel(loop8);
for (int k = 0; k < 8; k++) EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop8);
}
for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
return;
}
if (normalizedConstCount <= 255)
{
int blocks16 = normalizedConstCount / 16;
int rem = normalizedConstCount % 16;
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("POP_HL");
if (blocks16 > 0)
{
EmitAsm("LD_B_IMM", new AsmOperand(blocks16, AddressMode.Immediate));
// Use sixteen-store groups for larger byte-range constant fills.
AsmOperand loop16 = MakeUniqueLabel("memset16_loop");
EmitLabel(loop16);
for (int k = 0; k < 16; k++) EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", loop16);
}
for (int k = 0; k < rem; k++) EmitAsm("LDI_HL_A");
return;
}
}
if (useU8Count)
{
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoA(args[1]);
EmitAsm("LD_D_A");
CompileIntoA(args[2]);
EmitAsm("LD_B_A");
if (doCheck && hasDst)
EmitU8LeqConstOrTrapInA(dstBytes);
AsmOperand doneLabel = MakeUniqueLabel("memset_small_done");
AsmOperand smallLoopLabel = MakeUniqueLabel("memset_small_loop");
EmitAsm("OR_A");
EmitAsm("POP_HL");
EmitAsm("JP_Z", doneLabel);
EmitAsm("LD_A_D");
EmitLabel(smallLoopLabel);
EmitAsm("LDI_HL_A");
EmitAsm("DEC_B");
EmitAsm("JP_NZ", smallLoopLabel);
EmitLabel(doneLabel);
return;
}
CompileIntoHL(args[0]);
EmitAsm("PUSH_HL");
CompileIntoHL(args[2]);
if (doCheck && hasDst && !isConstCountAny)
EmitU16LeqConstOrTrap(dstBytes);
EmitAsm("LD_B_H");
EmitAsm("LD_C_L");
CompileIntoA(args[1]);
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
AsmOperand loopLabel = MakeUniqueLabel("memset_loop");
AsmOperand wordDoneLabel = MakeUniqueLabel("memset_done");
// A zero word count must skip the first write as well as the loop.
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_Z", wordDoneLabel);
EmitLabel(loopLabel);
EmitAsm("LD_A_D");
EmitAsm("LDI_HL_A");
EmitAsm("DEC_BC");
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JP_NZ", loopLabel);
EmitLabel(wordDoneLabel);
return;
}
// kitaqgb/kitaqgb/Lowerer.cs:2145
if (expr.MatchAny(Tag.Call, out callFunc, out callArgs))
{
string knownCallName;
if (callFunc != null && callFunc.Match(Tag.Name, out knownCallName) &&
knownCallName == "__cgb_is_cgb" &&
callArgs != null && callArgs.Length == 0 &&
// A resolved target configuration can replace the zero-argument CGB query with a literal.
Program.TryGetKnownCgbRuntimeValue(out int knownCgbValue))
{
return Expr.Make(Tag.Integer, knownCgbValue).WithSource(expr.Source);
}
Expr newFunc = LowerExpr(ctx, callFunc, false, prefix);
// Optimization (post-A3): when we must spill a non-atomic argument into a temp,
// prefer a u8 temp if the *parameter type* is u8-like.
// This preserves semantics (implicit truncation to u8) while avoiding needless
// u16 temporaries created by integer promotions.
CType[] paramTypes = null;
string __fname = null;
if (newFunc.Match(Tag.Name, out __fname))
{
paramTypes = ctx.FindFuncParams(__fname);
if (paramTypes == null)
{
CType fnTypeFromName;
if (TryGetCallableFunctionType(ctx.FindTypeOfName(__fname), out fnTypeFromName))
paramTypes = fnTypeFromName.ParamTypes;
}
}
else
{
CType fnTypeFromExpr;
if (TryGetCallableFunctionType(InferType(ctx, newFunc), out fnTypeFromExpr))
paramTypes = fnTypeFromExpr.ParamTypes;
}
Expr[] newArgs = new Expr[callArgs.Length];
for (int i = 0; i < callArgs.Length; i++)
{
Expr a = LowerExpr(ctx, callArgs[i], allowExtract, prefix);
bool preserveConstantArgIntrinsic = false;
if (a.MatchAny(Tag.Call, out Expr argFunc, out Expr[] _argCallArgs) &&
argFunc.Match(Tag.Name, out string argFuncName))
{
preserveConstantArgIntrinsic = (argFuncName == "__bankof" || argFuncName == "__cgb_is_cgb");
}
// Keep evaluable constants and special intrinsic/bank operands visible to code generation instead of spilling them.
bool preserveCompileTimeConstantArg = ctx.Global.TryEvalConstInt(a, out _);
bool preserveFarcallBankArg = (__fname == "__farcall" && i == 0);
if (allowExtract && !preserveFarcallBankArg && !preserveConstantArgIntrinsic && !preserveCompileTimeConstantArg && !IsAtom(a))
{
// If we know the callee's parameter type, use it to choose a smaller temp.
// Example: (u8)(row*32) passed to a u8 param should spill as u8, not u16.
CType spillAs = null;
if (paramTypes != null && i < paramTypes.Length)
{
CType pt = paramTypes[i];
if (IsU8Like(pt)) spillAs = CType.UInt8;
}
if (spillAs == null) spillAs = InferType(ctx, a);
if (!IsAggregateType(spillAs) && SizeOfType(spillAs) <= 2)
a = SpillToTemp(ctx, a, spillAs, prefix);
}
newArgs[i] = a;
}
// Lint: const discard for argument passing
// Warn when passing a const-qualified pointer (or const array decay) to a non-const pointer parameter.
// Suppress when the argument has an explicit cast, or inside __unsafe.
if (!ctx.InUnsafe && paramTypes != null)
{
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
if (!IsPointerToNonConst(pt)) continue;
if (HasTopLevelCast(newArgs[i])) continue;
CType at = InferType(ctx, newArgs[i]);
if (at != null && (IsPointerToConst(at) || IsArrayOfConst(at)))
{
string fn = __fname ?? "<call>";
WarnConstDiscard(expr, $"argument {i} to '{fn}'");
break; // keep it quiet: one warning per call is enough
}
}
}
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
FilePosition argPos = newArgs[i].Source;
if (string.IsNullOrEmpty(argPos.Filename) || argPos.Filename == "<unknown>")
argPos = expr.Source;
WarnImplicitNarrowingIfNeeded(ctx, newArgs[i], InferType(ctx, newArgs[i]), paramTypes[i], argPos,
"argument " + i + " to '" + fn + "'");
}
}
// Lint: __enum_strict argument passing.
if (paramTypes != null)
{
string fn = __fname ?? "<call>";
for (int i = 0; i < newArgs.Length && i < paramTypes.Length; i++)
{
CType pt = paramTypes[i];
if (pt == null) continue;
CType at = InferType(ctx, newArgs[i]);
if (at == null) continue;
if (HasTopLevelCast(newArgs[i])) continue;
if (IsStrictEnum(pt))
{
if (!IsSameEnum(pt, at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' expects __enum_strict {2}, got {3}; cast explicitly to silence",
i, fn, pt.Show(), at.Show());
}
}
else if (IsPlainInteger(pt) && IsStrictEnum(at))
{
Program.Warning(expr.Source, ErrorCode.EnumStrictMix,
"argument {0} to '{1}' passes __enum_strict {2} to integer parameter {3}; cast explicitly to silence",
i, fn, at.Show(), pt.Show());
}
}
}
if (!ctx.InUnsafe)
RestrictAliasLint(ctx, newFunc, newArgs, expr.Source);
// __slice(ptr,len) helper: represent a "slice" (ptr+len) in the IR.
// Semantics: evaluate both arguments, result type is the pointer/array (same as arg0).
// Bounds checks are injected only with -Zcheck (see Program.CheckSliceBounds).
// Note: keep the intrinsic name conservative to avoid collisions ("slice" is allowed only
// when there is no declared function named "slice").
if (newFunc.Match(Tag.Name, out string __sfname))
{
bool isSliceIntrinsic = (__sfname == "__slice") || (__sfname == "slice" && ctx.FindFuncParams("slice") == null);
if (isSliceIntrinsic)
{
if (newArgs.Length != 2)
{
Program.Error(expr.Source, ErrorCode.ParseError, "{0}: expected 2 arguments: __slice(ptr,len)", __sfname);
}
else
{
CType pty = InferType(ctx, newArgs[0]);
if (pty == null || !(pty.IsPointer || pty.IsArray))
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: first argument must be a pointer or array, got: {1}", __sfname, (pty == null ? "<unknown>" : pty.Show()));
CType lty = InferType(ctx, newArgs[1]);
if (lty == null || !lty.IsInteger)
Program.Error(expr.Source, ErrorCode.ExpectedType, "{0}: second argument must be an integer length, got: {1}", __sfname, (lty == null ? "<unknown>" : lty.Show()));
}
return Expr.Make(Tag.Slice, newArgs[0], newArgs[1]).WithSource(expr.Source);
}
}
return MakeCall(newFunc, newArgs).WithSource(expr.Source);
}__rle_decode_vram
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
u16 __rle_decode_vram(u16 dst, u8 bank, const void* src);Parameters
dst16-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.
bankSource 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.
srcCPU 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
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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:191
Compiler intrinsic, separate from the RAM rle_decode helpers below; it is not a bounded-stream API.
kitaqgb/kitaqgb/CodeGenerator.cs:8673
if (funcName == "__rle_decode_vram")
{
if (args.Length != 3) Error(expr, "__rle_decode_vram(dst, bank, src) expects 3 arguments");
CompileIntoHL(args[0]); EmitAsm("PUSH_HL"); // dst
CompileIntoA(args[1]); EmitPushAAsWord(); // bank
CompileIntoHL(args[2]); EmitAsm("PUSH_HL"); // src
EmitAsm("ADD_SP_IMM", new AsmOperand(-4, AddressMode.Relative)); // total(2) + scratch(2)
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("XOR_A");
EmitAsm("LD_HL_A");
EmitAsm("INC_HL");
EmitAsm("LD_HL_A");
AsmOperand loop = MakeUniqueLabel("rlevram_loop");
// Keep total, scratch, source, bank and destination in stack slots across repeated far-memory calls.
AsmOperand countDone = MakeUniqueLabel("rlevram_count_inc_done");
AsmOperand valueDone = MakeUniqueLabel("rlevram_value_inc_done");
AsmOperand finished = MakeUniqueLabel("rlevram_finished");
EmitLabel(loop);
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("PUSH_HL"); // &scratch
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A"); // src
EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
EmitAsm("LD_A_HL"); // bank
EmitAsm("POP_HL"); // &scratch
EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
EmitCallFarMemcpyHelper();
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("LD_A_HL"); // count
EmitAsm("OR_A");
EmitAsm("JP_Z", finished);
EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
EmitAsm("LD_HL_A"); // save count in scratch+1
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("LD_HL_A");
EmitAsm("JR_NC", countDone);
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("LD_HL_A");
EmitLabel(countDone);
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("PUSH_HL"); // &scratch
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A"); // src
EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
EmitAsm("LD_A_HL"); // bank
EmitAsm("POP_HL"); // &scratch
EmitAsm("LD_BC_IMM", new AsmOperand(1, AddressMode.Immediate16));
EmitCallFarMemcpyHelper();
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A"); // value
EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A");
EmitAsm("LD_B_IMM", new AsmOperand(0, AddressMode.Immediate)); // count
EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E"); // dst
// Loading dst used D for its high byte. Recover the run value
// from stack scratch while preserving the completed HL pointer.
EmitAsm("PUSH_HL");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("POP_HL");
// Use the LCD-aware fill loop for each decoded run, then store its advanced destination pointer.
EmitVramMemsetLoop(true, "rlevram_fill");
EmitAsm("PUSH_HL");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
EmitAsm("POP_DE");
EmitAsm("LD_A_E");
EmitAsm("LD_HL_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_D");
EmitAsm("LD_HL_A"); // save dst
EmitAsm("LD_HL_SP_IMM", new AsmOperand(3, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_B_A"); // count
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("ADD_B");
EmitAsm("LD_HL_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("LD_HL_A"); // total += count
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("ADD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("LD_HL_A");
EmitAsm("JR_NC", valueDone);
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("ADC_IMM", new AsmOperand(0, AddressMode.Immediate));
EmitAsm("LD_HL_A");
EmitLabel(valueDone);
EmitAsm("JP", loop);
EmitLabel(finished);
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_E_A");
EmitAsm("INC_HL");
EmitAsm("LD_A_HL");
EmitAsm("LD_D_A");
EmitAsm("LD_H_D");
EmitAsm("LD_L_E");
EmitAsm("ADD_SP_IMM", new AsmOperand(10, AddressMode.Relative));
LastCallReturnsHL = true;
return;
}__rng8
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
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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:149
kitaqgb/kitaqgb/CodeGenerator.cs:8881
if (funcName == "__rng8")
{
if (args.Length != 0) Error(expr, "__rng8() expects 0 arguments");
EmitLoadA(RngStateLoVar);
EmitAsm("LD_B_A");
EmitLoadA(RngStateHiVar);
EmitAsm("LD_C_A");
// Zero state fallback for deterministic startup.
AsmOperand rngSeeded = MakeUniqueLabel("rng_seeded");
EmitAsm("LD_A_B");
EmitAsm("OR_C");
EmitAsm("JR_NZ", rngSeeded);
EmitAsm("LD_A_IMM", new AsmOperand(0xA5, AddressMode.Immediate));
EmitAsm("LD_B_A");
EmitAsm("LD_A_IMM", new AsmOperand(0x5A, AddressMode.Immediate));
EmitAsm("LD_C_A");
EmitLabel(rngSeeded);
// 16-bit mixed add/xor/rotate step.
EmitAsm("LD_A_B");
EmitAsm("ADD_A_IMM", new AsmOperand(0x17, AddressMode.Immediate));
EmitAsm("LD_B_A");
EmitAsm("LD_A_C");
EmitAsm("ADC_IMM", new AsmOperand(0x5D, AddressMode.Immediate));
EmitAsm("LD_C_A");
EmitAsm("LD_A_B");
EmitAsm("XOR_C");
EmitAsm("LD_B_A");
EmitAsm("LD_A_C");
EmitAsm("RLCA");
EmitAsm("ADD_B");
EmitAsm("LD_C_A");
EmitAsm("LD_A_B");
EmitStoreA(RngStateLoVar);
EmitAsm("LD_A_C");
EmitStoreA(RngStateHiVar);
EmitAsm("LD_A_C");
return;
}__rng_seed
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
void __rng_seed(u16 seed);Parameters
seedUnsigned 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
__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.


Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:150
kitaqgb/kitaqgb/CodeGenerator.cs:8868
if (funcName == "__rng_seed")
{
if (args.Length != 1) Error(expr, "__rng_seed(seed) expects 1 argument");
CompileIntoHL(args[0]);
EmitAsm("LD_A_L");
EmitStoreA(RngStateLoVar);
EmitAsm("LD_A_H");
EmitStoreA(RngStateHiVar);
return;
}__scroll_bg_set
Write background SCX/SCY immediately and synchronize current/pending software values.
Syntax
void __scroll_bg_set(u8 scx, u8 scy);Parameters
scxHorizontal scroll, 0..255 pixels.
scyVertical 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
__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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:151
kitaqgb/kitaqgb/CodeGenerator.cs:7507
if (funcName == "__scroll_bg_set")
{
if (args.Length != 2) Error(expr, "__scroll_bg_set(scx, scy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_B_A");
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitStoreA(ScrollBgXCurVar);
EmitStoreA(ScrollBgXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
EmitAsm("LD_A_B");
EmitStoreA(ScrollBgYCurVar);
EmitStoreA(ScrollBgYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__settile_col
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
void __settile_col(u8 x, u8 y, const u8* src, u8 len);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
srcPacked 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.
lenNumber 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
// __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.
A
B
C

Build command
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-disasmImplementation 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
void __settile_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
w, hWidth and height in 8×8 tiles. Keep
x+w <= 32andy+h <= 32. A zero dimension performs no write.tileTile 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
// __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.
AAA
AAA

Build command
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-disasmImplementation 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
void __settile_row(u8 x, u8 y, const u8* src, u8 len);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
srcPacked 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.
lenNumber 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
// __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.
ABC

Build command
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-disasmImplementation 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
void __settile_xy(u8 x, u8 y, u8 tile);Parameters
x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
tileTile 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
// __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.
A

Build command
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-disasmImplementation 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
void __settilemap_rect(u16 base, u8 x, u8 y, u8 w, u8 h, const u8* src);Parameters
basebaseexplicitly selects the map (0x9800or0x9C00); the byte address isbase + 32*y + x.x, yCoordinates in 8×8 tiles, each from 0 to 31. Keep the complete write inside the 32×32 map; coordinates are not clipped.
w, hWidth and height in 8×8 tiles. Keep
x+w <= 32andy+h <= 32. A zero dimension performs no write.srcPacked 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
// __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.
ABC
DEF

Build command
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-disasmImplementation and source
kitaqgb/lib/rpg.h:158
__tile_addr
Compute one cell’s CPU address in a 32-tile-wide map.
Syntax
u16 __tile_addr(u16 base, u8 x, u8 y);Parameters
baseTile-map base, normally $9800/$9C00.
xColumn, normally 0..31.
yRow, normally 0..31.
Return value
Returns (base + y*32 + x) modulo 65536 without accessing memory.
Usage example
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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:180
kitaqgb/kitaqgb/CodeGenerator.cs:8355
if (funcName == "__tile_addr")
{
if (args.Length != 3) Error(expr, "__tile_addr(base, x, y) expects 3 arguments");
EmitTileMapAddressIntoHL(args[0], args[1], args[2]);
LastCallReturnsHL = true;
return;
}__vram_copy_dma
__vram_copy_dma 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
void __vram_copy_dma(u16 dst, const void* src, u16 len);Parameters
dstFirst 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.srcSource 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.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
void __vram_copy_hblank(u16 dst, const void* src, u16 len);Parameters
dstFirst 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.srcSource 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.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
void __vram_fill(u16 dst, u8 value, u16 len);Parameters
dstFirst 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.valueOne byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
void __vram_memcpy(u16 dst, const void* src, u16 len);Parameters
dstFirst 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.srcSource 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.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
void __vram_memset(u16 dst, u8 value, u16 len);Parameters
dstFirst 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.valueOne byte, 0–255, written repeatedly. Its visible meaning depends on whether the destination contains tile indices, pixel planes or another byte-based structure.
lenNumber 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
__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.


Build command
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-disasmImplementation 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
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
__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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:138
kitaqgb/kitaqgb/CodeGenerator.cs:7451
if (funcName == "__wait_vblank")
{
if (args.Length != 0) Error(expr, "__wait_vblank() expects 0 arguments");
AsmOperand wbRet = MakeUniqueLabel("waitvb_ret");
AsmOperand wbExitVblank = MakeUniqueLabel("waitvb_exit");
AsmOperand wbEnterVblank = MakeUniqueLabel("waitvb_enter");
// If LCD is off, return immediately.
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("AND_IMM", new AsmOperand(0x80, AddressMode.Immediate));
EmitAsm("JR_Z", wbRet);
// while (LY >= 144) {}
EmitLabel(wbExitVblank);
EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
EmitAsm("JR_C", wbEnterVblank);
EmitAsm("JR", wbExitVblank);
// while (LY < 144) {}
EmitLabel(wbEnterVblank);
EmitAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)); // LY
EmitAsm("CP_IMM", new AsmOperand(144, AddressMode.Immediate));
EmitAsm("JR_C", wbEnterVblank);
EmitLabel(wbRet);
return;
}__xy_in_rect
Test membership in a rectangle including its top/left and excluding its bottom/right.
Syntax
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);Parameters
xPoint X, 0..255.
yPoint Y, 0..255.
rxRectangle left, 0..255.
ryRectangle top, 0..255.
rwWidth, 0..255.
rhHeight, 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
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.


Build command
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=noneImplementation and source
kitaqgb/lib/rpg.h:182
kitaqgb/kitaqgb/CodeGenerator.cs:8436
if (funcName == "__xy_in_rect")
{
if (args.Length != 6) Error(expr, "__xy_in_rect(x, y, rx, ry, rw, rh) expects 6 arguments");
Expr[] foldedRectArgs = args.Select(FoldConstants).ToArray();
if (foldedRectArgs[0].Match(Tag.Integer, out int constX) &&
foldedRectArgs[1].Match(Tag.Integer, out int constY) &&
foldedRectArgs[2].Match(Tag.Integer, out int constRx) &&
foldedRectArgs[3].Match(Tag.Integer, out int constRy) &&
foldedRectArgs[4].Match(Tag.Integer, out int constRw) &&
foldedRectArgs[5].Match(Tag.Integer, out int constRh))
{
int x = constX & 0xFF;
int y = constY & 0xFF;
int rx = constRx & 0xFF;
int ry = constRy & 0xFF;
int rw = constRw & 0xFF;
int rh = constRh & 0xFF;
bool inside = x >= rx && y >= ry && (x - rx) < rw && (y - ry) < rh;
if (inside) EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
else EmitAsm("XOR_A");
return;
}
EmitPushExprAsWord(args[0]); // x
EmitPushExprAsWord(args[1]); // y
EmitPushExprAsWord(args[2]); // rx
EmitPushExprAsWord(args[3]); // ry
EmitPushExprAsWord(args[4]); // rw
EmitPushExprAsWord(args[5]); // rh
AsmOperand ret0 = MakeUniqueLabel("xyrect_ret0");
AsmOperand done = MakeUniqueLabel("xyrect_done");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(10, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_B_A"); // x
EmitAsm("LD_HL_SP_IMM", new AsmOperand(6, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // rx
EmitAsm("LD_A_B");
EmitAsm("CP_C");
EmitAsm("JR_C", ret0);
EmitAsm("SUB_C");
EmitAsm("LD_B_A");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(2, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // rw
EmitAsm("LD_A_B");
EmitAsm("CP_C");
EmitAsm("JR_NC", ret0);
EmitAsm("LD_HL_SP_IMM", new AsmOperand(8, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_B_A"); // y
EmitAsm("LD_HL_SP_IMM", new AsmOperand(4, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // ry
EmitAsm("LD_A_B");
EmitAsm("CP_C");
EmitAsm("JR_C", ret0);
EmitAsm("SUB_C");
EmitAsm("LD_B_A");
EmitAsm("LD_HL_SP_IMM", new AsmOperand(0, AddressMode.Relative));
EmitAsm("LD_A_HL");
EmitAsm("LD_C_A"); // rh
EmitAsm("LD_A_B");
EmitAsm("CP_C");
EmitAsm("JR_NC", ret0);
EmitAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate));
EmitAsm("JR", done);
EmitLabel(ret0);
EmitAsm("XOR_A");
EmitLabel(done);
EmitAsm("ADD_SP_IMM", new AsmOperand(12, AddressMode.Relative));
return;
}scroll.h — 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
void __scroll_bg_add(s8 dx, s8 dy);Parameters
dxSigned horizontal pixel delta, −128 through 127.
dySigned 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:37
kitaqgb/kitaqgb/CodeGenerator.cs:7835
if (funcName == "__scroll_bg_add")
{
if (args.Length != 2) Error(expr, "__scroll_bg_add(dx, dy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitAsm("LD_B_L");
EmitLoadA(ScrollBgXCurVar);
EmitAsm("ADD_B");
EmitStoreA(ScrollBgXCurVar);
EmitStoreA(ScrollBgXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
EmitLoadA(ScrollBgYCurVar);
EmitAsm("ADD_C");
EmitStoreA(ScrollBgYCurVar);
EmitStoreA(ScrollBgYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_bg_set_buffered
Stage both background scroll coordinates for a later flush.
Syntax
void __scroll_bg_set_buffered(u8 scx, u8 scy);Parameters
scxHorizontal background scroll, 0–255 pixels; increasing it moves the background left.
scyVertical 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:22
kitaqgb/kitaqgb/CodeGenerator.cs:7671
if (funcName == "__scroll_bg_set_buffered")
{
if (args.Length != 2) Error(expr, "__scroll_bg_set_buffered(scx, scy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_B_A");
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitStoreA(ScrollBgXNextVar);
EmitAsm("LD_A_B");
EmitStoreA(ScrollBgYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_bg_x_get
Read the tracked current horizontal background scroll.
Syntax
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
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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:13
kitaqgb/kitaqgb/CodeGenerator.cs:7571
if (funcName == "__scroll_bg_x_get")
{
if (args.Length != 0) Error(expr, "__scroll_bg_x_get() expects 0 arguments");
EnsureScrollState();
EmitLoadA(ScrollBgXCurVar);
return;
}__scroll_bg_x_set
Set horizontal background scroll immediately.
Syntax
void __scroll_bg_x_set(u8 scx);Parameters
scxHorizontal 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:11
kitaqgb/kitaqgb/CodeGenerator.cs:7535
if (funcName == "__scroll_bg_x_set")
{
if (args.Length != 1) Error(expr, "__scroll_bg_x_set(scx) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollBgXCurVar);
EmitStoreA(ScrollBgXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
EmitLoadA(ScrollBgYCurVar);
EmitStoreA(ScrollBgYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_bg_x_set_buffered
Stage only background X.
Syntax
void __scroll_bg_x_set_buffered(u8 scx);Parameters
scxHorizontal 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:23
kitaqgb/kitaqgb/CodeGenerator.cs:7693
if (funcName == "__scroll_bg_x_set_buffered")
{
if (args.Length != 1) Error(expr, "__scroll_bg_x_set_buffered(scx) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollBgXNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_bg_y_get
Read the tracked current vertical background scroll.
Syntax
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
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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:14
kitaqgb/kitaqgb/CodeGenerator.cs:7580
if (funcName == "__scroll_bg_y_get")
{
if (args.Length != 0) Error(expr, "__scroll_bg_y_get() expects 0 arguments");
EnsureScrollState();
EmitLoadA(ScrollBgYCurVar);
return;
}__scroll_bg_y_set
Set vertical background scroll immediately.
Syntax
void __scroll_bg_y_set(u8 scy);Parameters
scyVertical 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:12
kitaqgb/kitaqgb/CodeGenerator.cs:7553
if (funcName == "__scroll_bg_y_set")
{
if (args.Length != 1) Error(expr, "__scroll_bg_y_set(scy) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollBgYCurVar);
EmitStoreA(ScrollBgYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
EmitLoadA(ScrollBgXCurVar);
EmitStoreA(ScrollBgXNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_bg_y_set_buffered
Stage only background Y.
Syntax
void __scroll_bg_y_set_buffered(u8 scy);Parameters
scyVertical 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:24
kitaqgb/kitaqgb/CodeGenerator.cs:7707
if (funcName == "__scroll_bg_y_set_buffered")
{
if (args.Length != 1) Error(expr, "__scroll_bg_y_set_buffered(scy) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollBgYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_flush
Apply dirty background/window coordinates to tracked state and hardware, then clear their pending flags.
Syntax
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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:28
kitaqgb/kitaqgb/CodeGenerator.cs:7771
if (funcName == "__scroll_flush")
{
if (args.Length != 0) Error(expr, "__scroll_flush() expects 0 arguments");
EnsureScrollState();
AsmOperand flushSkipBg = MakeUniqueLabel("scroll_flush_skip_bg");
AsmOperand flushSkipWin = MakeUniqueLabel("scroll_flush_skip_win");
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyBgMask, AddressMode.Immediate));
EmitAsm("JR_Z", flushSkipBg);
EmitLoadA(ScrollBgXNextVar);
EmitStoreA(ScrollBgXCurVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem)); // SCX
EmitLoadA(ScrollBgYNextVar);
EmitStoreA(ScrollBgYCurVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem)); // SCY
EmitLabel(flushSkipBg);
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
EmitAsm("JR_Z", flushSkipWin);
EmitLoadA(ScrollWinXNextVar);
EmitStoreA(ScrollWinXCurVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
EmitLoadA(ScrollWinYNextVar);
EmitStoreA(ScrollWinYCurVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
EmitLabel(flushSkipWin);
EmitAsm("XOR_A");
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_split_commit
Capture base window visibility and arm the first scanline event in the split table.
Syntax
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
__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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:33
kitaqgb/kitaqgb/CodeGenerator.cs:7959
if (funcName == "__scroll_split_commit")
{
if (args.Length != 0) Error(expr, "__scroll_split_commit() expects 0 arguments");
EnsureScrollState();
UseScrollSplitHelpers = true;
EmitAsm("CALL", new AsmOperand("__kq_scroll_split_commit_core", AddressMode.Absolute));
return;
}
void AppendScrollHelpersIfUsed(List<Expr> rawLines)
{
bool needScrollHelpers = UseScrollSplitHelpers;
if (!needScrollHelpers)
{
foreach (var e in rawLines)
{
string m;
AsmOperand o;
if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
string target = o.Base.Value;
if (target == "__kq_scroll_split_reset_core" ||
target == "__kq_scroll_split_push_core" ||
target == "__kq_scroll_split_commit_core")
{
needScrollHelpers = true;
break;
}
}
}
if (!needScrollHelpers) return;
EnsureScrollState();
var injected = new List<Expr>();
injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));
// Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
string resetDone = "__kq_scroll_split_reset_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, resetDone));
injected.Add(Expr.MakeAsm("RET"));
// Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
string pushHaveRoom = "__kq_scroll_split_push_have_room";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("RET"));
// Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
string schedDisable = "__kq_scroll_schedule_next_disable";
string schedDisabled = "__kq_scroll_schedule_next_disabled";
string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, schedDisable));
// Do not rewrite a disabled STAT source: on DMG even writing zero
// can raise a fresh request and repeatedly re-enter the STAT handler.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, schedDisabled));
injected.Add(Expr.MakeAsm("RET"));
// Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
string applySkipWin = "__kq_scroll_apply_entry_skip_win";
string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
string applyDone = "__kq_scroll_apply_entry_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
injected.Add(Expr.MakeAsm("INC_HL")); // scx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // scy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_C_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_D_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // flags
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_H_A"));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.Make(Tag.Label, applySkipBg));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_D"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_E"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.Make(Tag.Label, applySkipWin));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipHide));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
// CGB palette data writes are blocked during pixel transfer. Wait through
// OAM search as well, so both color bytes fit in the following HBlank.
// The LY-zero entry runs in VBlank and does not wait here.
injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
injected.Add(Expr.Make(Tag.Label, applyDone));
injected.Add(Expr.MakeAsm("RET"));
// Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
string commitDisable = "__kq_scroll_split_commit_disable";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("EI"));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, commitDisable));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
// Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
// A first entry at LY zero is applied immediately before scheduling the following entry.
string vbDisable = "__kq_vblank_disable";
string vbFirstNonZero = "__kq_vblank_first_nonzero";
string vbDone = "__kq_vblank_done";
injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
// Set the existing fixed-address frame-ready flag used by the VBlank path.
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("OR_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDisable));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
// Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
string statDisable = "__kq_stat_disable";
string statDone = "__kq_stat_done";
injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
// DMG STAT writes can request an interrupt unrelated to LYC. Consume
// this entry only on its scheduled line; leave the index intact otherwise.
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, statDisable));
// Acknowledge an already-disabled source without writing STAT again.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, statDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
int insertAt = 0;
while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
rawLines.InsertRange(insertAt, injected);
}__scroll_split_push
Append a split entry that changes background scrolling at the requested scanline event.
Syntax
void __scroll_split_push(u8 ly, u8 scx, u8 scy);Parameters
lyScanline event number; use 0–143 for visible events and append in ascending order. A first entry at zero is applied during VBlank.
scxHorizontal background scroll, 0–255 pixels; increasing it moves the background left.
scyVertical 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
__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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:31
kitaqgb/kitaqgb/CodeGenerator.cs:7907
if (funcName == "__scroll_split_push")
{
if (args.Length != 3) Error(expr, "__scroll_split_push(ly, scx, scy) expects 3 arguments");
EnsureScrollState();
UseScrollSplitHelpers = true;
CompileIntoA(args[0]);
EmitStoreA(ScrollSplitTmpLyVar);
CompileIntoA(args[1]);
EmitStoreA(ScrollSplitTmpScxVar);
CompileIntoA(args[2]);
EmitStoreA(ScrollSplitTmpScyVar);
EmitAsm("XOR_A");
EmitStoreA(ScrollSplitTmpWxVar);
EmitStoreA(ScrollSplitTmpWyVar);
EmitAsm("LD_A_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate));
EmitStoreA(ScrollSplitTmpFlagsVar);
EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
return;
}
void AppendScrollHelpersIfUsed(List<Expr> rawLines)
{
bool needScrollHelpers = UseScrollSplitHelpers;
if (!needScrollHelpers)
{
foreach (var e in rawLines)
{
string m;
AsmOperand o;
if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
string target = o.Base.Value;
if (target == "__kq_scroll_split_reset_core" ||
target == "__kq_scroll_split_push_core" ||
target == "__kq_scroll_split_commit_core")
{
needScrollHelpers = true;
break;
}
}
}
if (!needScrollHelpers) return;
EnsureScrollState();
var injected = new List<Expr>();
injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));
// Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
string resetDone = "__kq_scroll_split_reset_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, resetDone));
injected.Add(Expr.MakeAsm("RET"));
// Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
string pushHaveRoom = "__kq_scroll_split_push_have_room";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("RET"));
// Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
string schedDisable = "__kq_scroll_schedule_next_disable";
string schedDisabled = "__kq_scroll_schedule_next_disabled";
string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, schedDisable));
// Do not rewrite a disabled STAT source: on DMG even writing zero
// can raise a fresh request and repeatedly re-enter the STAT handler.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, schedDisabled));
injected.Add(Expr.MakeAsm("RET"));
// Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
string applySkipWin = "__kq_scroll_apply_entry_skip_win";
string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
string applyDone = "__kq_scroll_apply_entry_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
injected.Add(Expr.MakeAsm("INC_HL")); // scx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // scy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_C_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_D_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // flags
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_H_A"));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.Make(Tag.Label, applySkipBg));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_D"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_E"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.Make(Tag.Label, applySkipWin));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipHide));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
// CGB palette data writes are blocked during pixel transfer. Wait through
// OAM search as well, so both color bytes fit in the following HBlank.
// The LY-zero entry runs in VBlank and does not wait here.
injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
injected.Add(Expr.Make(Tag.Label, applyDone));
injected.Add(Expr.MakeAsm("RET"));
// Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
string commitDisable = "__kq_scroll_split_commit_disable";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("EI"));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, commitDisable));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
// Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
// A first entry at LY zero is applied immediately before scheduling the following entry.
string vbDisable = "__kq_vblank_disable";
string vbFirstNonZero = "__kq_vblank_first_nonzero";
string vbDone = "__kq_vblank_done";
injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
// Set the existing fixed-address frame-ready flag used by the VBlank path.
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("OR_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDisable));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
// Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
string statDisable = "__kq_stat_disable";
string statDone = "__kq_stat_done";
injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
// DMG STAT writes can request an interrupt unrelated to LYC. Consume
// this entry only on its scheduled line; leave the index intact otherwise.
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, statDisable));
// Acknowledge an already-disabled source without writing STAT again.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, statDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
int insertAt = 0;
while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
rawLines.InsertRange(insertAt, injected);
}__scroll_split_push_ex
Append a split entry whose flags select background, window, visibility or background-color-zero operations.
Syntax
void __scroll_split_push_ex(u8 ly, u8 scx, u8 scy, u8 wx, u8 wy, u8 flags);Parameters
lyScanline event number; use 0–143 for visible events and append in ascending order. A first entry at zero is applied during VBlank.
scxHorizontal background scroll, 0–255 pixels; increasing it moves the background left.
scyVertical background scroll, 0–255 pixels; increasing it moves the background up.
wxRaw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.
wyWindow top in screen Y pixels, 0–255; visible rows are 0–143.
flagsOperation 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
__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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:32
kitaqgb/kitaqgb/CodeGenerator.cs:7929
if (funcName == "__scroll_split_push_ex")
{
if (args.Length != 6) Error(expr, "__scroll_split_push_ex(ly, scx, scy, wx, wy, flags) expects 6 arguments");
EnsureScrollState();
UseScrollSplitHelpers = true;
if (TryGetU8Const(args[5], out int splitFlagsConst))
{
int unknownBits = splitFlagsConst & ~ScrollSplitFlagMask;
if (unknownBits != 0)
Warning(args[5], string.Format("__scroll_split_push_ex() ignores unknown flag bits 0x{0:X2}", unknownBits));
}
CompileIntoA(args[0]);
EmitStoreA(ScrollSplitTmpLyVar);
CompileIntoA(args[1]);
EmitStoreA(ScrollSplitTmpScxVar);
CompileIntoA(args[2]);
EmitStoreA(ScrollSplitTmpScyVar);
CompileIntoA(args[3]);
EmitStoreA(ScrollSplitTmpWxVar);
CompileIntoA(args[4]);
EmitStoreA(ScrollSplitTmpWyVar);
CompileIntoA(args[5]);
EmitStoreA(ScrollSplitTmpFlagsVar);
EmitAsm("CALL", new AsmOperand("__kq_scroll_split_push_core", AddressMode.Absolute));
return;
}
void AppendScrollHelpersIfUsed(List<Expr> rawLines)
{
bool needScrollHelpers = UseScrollSplitHelpers;
if (!needScrollHelpers)
{
foreach (var e in rawLines)
{
string m;
AsmOperand o;
if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
string target = o.Base.Value;
if (target == "__kq_scroll_split_reset_core" ||
target == "__kq_scroll_split_push_core" ||
target == "__kq_scroll_split_commit_core")
{
needScrollHelpers = true;
break;
}
}
}
if (!needScrollHelpers) return;
EnsureScrollState();
var injected = new List<Expr>();
injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));
// Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
string resetDone = "__kq_scroll_split_reset_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, resetDone));
injected.Add(Expr.MakeAsm("RET"));
// Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
string pushHaveRoom = "__kq_scroll_split_push_have_room";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("RET"));
// Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
string schedDisable = "__kq_scroll_schedule_next_disable";
string schedDisabled = "__kq_scroll_schedule_next_disabled";
string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, schedDisable));
// Do not rewrite a disabled STAT source: on DMG even writing zero
// can raise a fresh request and repeatedly re-enter the STAT handler.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, schedDisabled));
injected.Add(Expr.MakeAsm("RET"));
// Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
string applySkipWin = "__kq_scroll_apply_entry_skip_win";
string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
string applyDone = "__kq_scroll_apply_entry_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
injected.Add(Expr.MakeAsm("INC_HL")); // scx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // scy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_C_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_D_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // flags
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_H_A"));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.Make(Tag.Label, applySkipBg));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_D"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_E"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.Make(Tag.Label, applySkipWin));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipHide));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
// CGB palette data writes are blocked during pixel transfer. Wait through
// OAM search as well, so both color bytes fit in the following HBlank.
// The LY-zero entry runs in VBlank and does not wait here.
injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
injected.Add(Expr.Make(Tag.Label, applyDone));
injected.Add(Expr.MakeAsm("RET"));
// Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
string commitDisable = "__kq_scroll_split_commit_disable";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("EI"));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, commitDisable));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
// Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
// A first entry at LY zero is applied immediately before scheduling the following entry.
string vbDisable = "__kq_vblank_disable";
string vbFirstNonZero = "__kq_vblank_first_nonzero";
string vbDone = "__kq_vblank_done";
injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
// Set the existing fixed-address frame-ready flag used by the VBlank path.
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("OR_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDisable));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
// Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
string statDisable = "__kq_stat_disable";
string statDone = "__kq_stat_done";
injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
// DMG STAT writes can request an interrupt unrelated to LYC. Consume
// this entry only on its scheduled line; leave the index intact otherwise.
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, statDisable));
// Acknowledge an already-disabled source without writing STAT again.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, statDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
int insertAt = 0;
while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
rawLines.InsertRange(insertAt, injected);
}__scroll_split_reset
Clear the split count and index, disable split processing and clear STAT’s LYC interrupt-enable bit.
Syntax
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
__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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:30
kitaqgb/kitaqgb/CodeGenerator.cs:7897
if (funcName == "__scroll_split_reset")
{
if (args.Length != 0) Error(expr, "__scroll_split_reset() expects 0 arguments");
EnsureScrollState();
UseScrollSplitHelpers = true;
EmitAsm("CALL", new AsmOperand("__kq_scroll_split_reset_core", AddressMode.Absolute));
return;
}
void AppendScrollHelpersIfUsed(List<Expr> rawLines)
{
bool needScrollHelpers = UseScrollSplitHelpers;
if (!needScrollHelpers)
{
foreach (var e in rawLines)
{
string m;
AsmOperand o;
if (!e.Match(Tag.Asm, out m, out o) || o == null || !o.Base.HasValue) continue;
string target = o.Base.Value;
if (target == "__kq_scroll_split_reset_core" ||
target == "__kq_scroll_split_push_core" ||
target == "__kq_scroll_split_commit_core")
{
needScrollHelpers = true;
break;
}
}
}
if (!needScrollHelpers) return;
EnsureScrollState();
var injected = new List<Expr>();
injected.Add(Expr.Make(Tag.Comment, "[KITAQGB] injected scroll split helpers"));
// Reset the split count/index and disable STAT coincidence interrupts; existing table bytes are left in place.
string resetDone = "__kq_scroll_split_reset_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_reset_core"));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, resetDone));
injected.Add(Expr.MakeAsm("RET"));
// Append staged LY, SCX, SCY, WX, WY and masked flags; silently ignore entries beyond the eight-slot capacity.
string pushHaveRoom = "__kq_scroll_split_push_have_room";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_push_core"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_IMM", new AsmOperand(ScrollSplitMaxEntries, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(pushHaveRoom, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, pushHaveRoom));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpLyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpScyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWxVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpWyVar));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitTmpFlagsVar));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDI_HL_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("RET"));
// Schedule entry A by programming LYC and STAT bit 6, or disable that bit when A is outside the current count.
string schedDisable = "__kq_scroll_schedule_next_disable";
string schedDisabled = "__kq_scroll_schedule_next_disabled";
string schedHaveTarget = "__kq_scroll_schedule_next_have_target";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_schedule_next"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisable, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, schedHaveTarget));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x45, AddressMode.HighMem))); // LYC
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, schedDisable));
// Do not rewrite a disabled STAT source: on DMG even writing zero
// can raise a fresh request and repeatedly re-enter the STAT handler.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(schedDisabled, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, schedDisabled));
injected.Add(Expr.MakeAsm("RET"));
// Apply the fields selected by an entry's flags. Show follows hide when both are set; palette color zero reuses SCX/SCY bytes.
string applySkipBg = "__kq_scroll_apply_entry_skip_bg";
string applySkipWin = "__kq_scroll_apply_entry_skip_win";
string applySkipHide = "__kq_scroll_apply_entry_skip_hide";
string applySkipBgColor0 = "__kq_scroll_apply_entry_skip_bg_color0";
string applyWaitPalette = "__kq_scroll_apply_entry_wait_palette";
string applyDone = "__kq_scroll_apply_entry_done";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_apply_entry"));
injected.Add(Expr.MakeAsm("INC_HL")); // scx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // scy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_C_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wx
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_D_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // wy
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("INC_HL")); // flags
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagMask, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_H_A"));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseBg, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBg, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.Make(Tag.Label, applySkipBg));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagUseWin, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipWin, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_D"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_E"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.Make(Tag.Label, applySkipWin));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinHide, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipHide, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipHide));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagWinShow, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applySkipBgColor0, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, applySkipBgColor0));
injected.Add(Expr.MakeAsm("LD_A_H"));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(ScrollSplitFlagBgColor0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(applyDone, AddressMode.Absolute)));
// CGB palette data writes are blocked during pixel transfer. Wait through
// OAM search as well, so both color bytes fit in the following HBlank.
// The LY-zero entry runs in VBlank and does not wait here.
injected.Add(Expr.Make(Tag.Label, applyWaitPalette));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x02, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(applyWaitPalette, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(0x80, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x68, AddressMode.HighMem))); // BCPS: palette 0, color 0, auto increment
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD low
injected.Add(Expr.MakeAsm("LD_A_C"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x69, AddressMode.HighMem))); // BCPD high
injected.Add(Expr.Make(Tag.Label, applyDone));
injected.Add(Expr.MakeAsm("RET"));
// Capture base window visibility and arm a nonempty table at index zero, enabling VBlank/STAT interrupts and issuing EI.
string commitDisable = "__kq_scroll_split_commit_disable";
injected.Add(Expr.Make(Tag.Function, "__kq_scroll_split_commit_core"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x20, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(commitDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(0xFFFF, AddressMode.Absolute))); // IE
injected.Add(Expr.MakeAsm("OR_IMM", new AsmOperand(0x03, AddressMode.Immediate))); // VBlank | STAT
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xFFFF, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("EI"));
injected.Add(Expr.MakeAsm("RET"));
injected.Add(Expr.Make(Tag.Label, commitDisable));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("RET"));
// Save registers/SVBK, select WRAM bank 1, restore base scroll state and restart the split sequence for the frame.
// A first entry at LY zero is applied immediately before scheduling the following entry.
string vbDisable = "__kq_vblank_disable";
string vbFirstNonZero = "__kq_vblank_first_nonzero";
string vbDone = "__kq_vblank_done";
injected.Add(Expr.Make(Tag.Function, "__kq_vblank_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
// Set the existing fixed-address frame-ready flag used by the VBlank path.
injected.Add(Expr.MakeAsm("LD_MEM_A", new AsmOperand(0xC29C, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x43, AddressMode.HighMem))); // SCX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollBgYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x42, AddressMode.HighMem))); // SCY
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinXCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem))); // WX
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinYCurVar));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem))); // WY
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem))); // LCDC
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollWinVisibleVar));
injected.Add(Expr.MakeAsm("OR_B"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(vbDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_A_MEM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(vbFirstNonZero, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_HL_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbFirstNonZero));
injected.Add(Expr.MakeAsm("XOR_A"));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(vbDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDisable));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, vbDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
// Save registers/SVBK, apply the current entry and advance its index, or disable further coincidence interrupts at the end.
string statDisable = "__kq_stat_disable";
string statDone = "__kq_stat_done";
injected.Add(Expr.Make(Tag.Function, "__kq_stat_vector"));
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("PUSH_BC"));
injected.Add(Expr.MakeAsm("PUSH_DE"));
injected.Add(Expr.MakeAsm("PUSH_HL"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x70, AddressMode.HighMem))); // SVBK
injected.Add(Expr.MakeAsm("PUSH_AF"));
injected.Add(Expr.MakeAsm("LD_A_IMM", new AsmOperand(1, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitEnabledVar));
injected.Add(Expr.MakeAsm("OR_A"));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitCountVar));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_C", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDisable, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_B"));
injected.Add(Expr.MakeAsm("LD_E_A"));
injected.Add(Expr.MakeAsm("LD_D_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LD_L_A"));
injected.Add(Expr.MakeAsm("LD_H_IMM", new AsmOperand(0, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
injected.Add(Expr.MakeAsm("ADD_HL_HL"));
injected.Add(Expr.MakeAsm("LD_DE_IMM", new AsmOperand(ScrollSplitTableAddr, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("ADD_HL_DE"));
// DMG STAT writes can request an interrupt unrelated to LYC. Consume
// this entry only on its scheduled line; leave the index intact otherwise.
injected.Add(Expr.MakeAsm("LD_A_HL"));
injected.Add(Expr.MakeAsm("LD_B_A"));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x44, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("CP_B"));
injected.Add(Expr.MakeAsm("JR_NZ", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_apply_entry", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LD_A_MEM", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("INC_A"));
injected.Add(Expr.MakeAsm("LD_MEM_A", ScrollSplitIndexVar));
injected.Add(Expr.MakeAsm("CALL", new AsmOperand("__kq_scroll_schedule_next", AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("JR", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.Make(Tag.Label, statDisable));
// Acknowledge an already-disabled source without writing STAT again.
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0x40, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("JR_Z", new AsmOperand(statDone, AddressMode.Absolute)));
injected.Add(Expr.MakeAsm("LDH_A_MEM", new AsmOperand(0x41, AddressMode.HighMem))); // STAT
injected.Add(Expr.MakeAsm("AND_IMM", new AsmOperand(0xBF, AddressMode.Immediate)));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x41, AddressMode.HighMem)));
injected.Add(Expr.Make(Tag.Label, statDone));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("LDH_MEM_A", new AsmOperand(0x70, AddressMode.HighMem))); // restore SVBK
injected.Add(Expr.MakeAsm("POP_HL"));
injected.Add(Expr.MakeAsm("POP_DE"));
injected.Add(Expr.MakeAsm("POP_BC"));
injected.Add(Expr.MakeAsm("POP_AF"));
injected.Add(Expr.MakeAsm("RETI"));
int insertAt = 0;
while (insertAt < rawLines.Count && rawLines[insertAt].MatchTag(Tag.RstMap)) insertAt++;
rawLines.InsertRange(insertAt, injected);
}__scroll_win_add
Add signed deltas to tracked window coordinates and apply them immediately.
Syntax
void __scroll_win_add(s8 dx, s8 dy);Parameters
dxSigned horizontal pixel delta, −128 through 127.
dySigned 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:38
kitaqgb/kitaqgb/CodeGenerator.cs:7866
if (funcName == "__scroll_win_add")
{
if (args.Length != 2) Error(expr, "__scroll_win_add(dx, dy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_C_A");
EmitAsm("POP_HL");
EmitAsm("LD_B_L");
EmitLoadA(ScrollWinXCurVar);
EmitAsm("ADD_B");
EmitStoreA(ScrollWinXCurVar);
EmitStoreA(ScrollWinXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
EmitLoadA(ScrollWinYCurVar);
EmitAsm("ADD_C");
EmitStoreA(ScrollWinYCurVar);
EmitStoreA(ScrollWinYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_hide
Clear the LCDC window bit and tracked window visibility state.
Syntax
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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:36
kitaqgb/kitaqgb/CodeGenerator.cs:7821
if (funcName == "__scroll_win_hide")
{
if (args.Length != 0) Error(expr, "__scroll_win_hide() expects 0 arguments");
EnsureScrollState();
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("AND_IMM", new AsmOperand(0xDF, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
EmitAsm("XOR_A");
EmitStoreA(ScrollWinVisibleVar);
return;
}__scroll_win_set
Set window WX and WY immediately.
Syntax
void __scroll_win_set(u8 wx, u8 wy);Parameters
wxRaw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.
wyWindow 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:16
kitaqgb/kitaqgb/CodeGenerator.cs:7589
if (funcName == "__scroll_win_set")
{
if (args.Length != 2) Error(expr, "__scroll_win_set(wx, wy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_B_A");
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitStoreA(ScrollWinXCurVar);
EmitStoreA(ScrollWinXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
EmitAsm("LD_A_B");
EmitStoreA(ScrollWinYCurVar);
EmitStoreA(ScrollWinYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_set_buffered
Stage both window coordinates for a later flush.
Syntax
void __scroll_win_set_buffered(u8 wx, u8 wy);Parameters
wxRaw WX byte, 0–255: add 7 to the desired screen X; the left edge uses 7.
wyWindow 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:25
kitaqgb/kitaqgb/CodeGenerator.cs:7721
if (funcName == "__scroll_win_set_buffered")
{
if (args.Length != 2) Error(expr, "__scroll_win_set_buffered(wx, wy) expects 2 arguments");
EnsureScrollState();
CompileIntoA(args[0]);
EmitPushAAsWord();
CompileIntoA(args[1]);
EmitAsm("LD_B_A");
EmitAsm("POP_HL");
EmitAsm("LD_A_L");
EmitStoreA(ScrollWinXNextVar);
EmitAsm("LD_A_B");
EmitStoreA(ScrollWinYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_show
Enable the LCDC window bit and the tracked window visibility state.
Syntax
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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:35
kitaqgb/kitaqgb/CodeGenerator.cs:7807
if (funcName == "__scroll_win_show")
{
if (args.Length != 0) Error(expr, "__scroll_win_show() expects 0 arguments");
EnsureScrollState();
EmitAsm("LDH_A_MEM", new AsmOperand(0x40, AddressMode.HighMem)); // LCDC
EmitAsm("OR_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitAsm("LDH_MEM_A", new AsmOperand(0x40, AddressMode.HighMem));
EmitAsm("LD_A_IMM", new AsmOperand(0x20, AddressMode.Immediate));
EmitStoreA(ScrollWinVisibleVar);
return;
}__scroll_win_x_get
Read the tracked current window WX value.
Syntax
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
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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:19
kitaqgb/kitaqgb/CodeGenerator.cs:7653
if (funcName == "__scroll_win_x_get")
{
if (args.Length != 0) Error(expr, "__scroll_win_x_get() expects 0 arguments");
EnsureScrollState();
EmitLoadA(ScrollWinXCurVar);
return;
}__scroll_win_x_set
Set only the window’s raw WX coordinate immediately.
Syntax
void __scroll_win_x_set(u8 wx);Parameters
wxRaw 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:17
kitaqgb/kitaqgb/CodeGenerator.cs:7617
if (funcName == "__scroll_win_x_set")
{
if (args.Length != 1) Error(expr, "__scroll_win_x_set(wx) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollWinXCurVar);
EmitStoreA(ScrollWinXNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4B, AddressMode.HighMem)); // WX
EmitLoadA(ScrollWinYCurVar);
EmitStoreA(ScrollWinYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_x_set_buffered
Stage only window WX.
Syntax
void __scroll_win_x_set_buffered(u8 wx);Parameters
wxRaw 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:26
kitaqgb/kitaqgb/CodeGenerator.cs:7743
if (funcName == "__scroll_win_x_set_buffered")
{
if (args.Length != 1) Error(expr, "__scroll_win_x_set_buffered(wx) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollWinXNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_y_get
Read the tracked current window WY value.
Syntax
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
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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:20
kitaqgb/kitaqgb/CodeGenerator.cs:7662
if (funcName == "__scroll_win_y_get")
{
if (args.Length != 0) Error(expr, "__scroll_win_y_get() expects 0 arguments");
EnsureScrollState();
EmitLoadA(ScrollWinYCurVar);
return;
}__scroll_win_y_set
Set only the window’s WY coordinate immediately.
Syntax
void __scroll_win_y_set(u8 wy);Parameters
wyWindow 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:18
kitaqgb/kitaqgb/CodeGenerator.cs:7635
if (funcName == "__scroll_win_y_set")
{
if (args.Length != 1) Error(expr, "__scroll_win_y_set(wy) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollWinYCurVar);
EmitStoreA(ScrollWinYNextVar);
EmitAsm("LDH_MEM_A", new AsmOperand(0x4A, AddressMode.HighMem)); // WY
EmitLoadA(ScrollWinXCurVar);
EmitStoreA(ScrollWinXNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("AND_IMM", new AsmOperand(0xFF ^ ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}__scroll_win_y_set_buffered
Stage only window WY.
Syntax
void __scroll_win_y_set_buffered(u8 wy);Parameters
wyWindow 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
__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.


Build command
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=64kUsage example
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.


Build command
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=64kImplementation and source
kitaqgb/lib/scroll.h:27
kitaqgb/kitaqgb/CodeGenerator.cs:7757
if (funcName == "__scroll_win_y_set_buffered")
{
if (args.Length != 1) Error(expr, "__scroll_win_y_set_buffered(wy) expects 1 argument");
EnsureScrollState();
CompileIntoA(args[0]);
EmitStoreA(ScrollWinYNextVar);
EmitLoadA(ScrollDirtyVar);
EmitAsm("OR_IMM", new AsmOperand(ScrollDirtyWinMask, AddressMode.Immediate));
EmitStoreA(ScrollDirtyVar);
return;
}sprite.h — OBJ 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
void __oam_dma(u16 src_ptr);Parameters
src_ptrSource address, in bytes. Only the high byte is used:
0xC480selects0xC400–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
// 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.


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